mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-30 21:23:04 +00:00
perf(desktop): reduce daemon management memory overhead (#5007)
Desktop and CLI helpers imported daemon bootstrap through the server root, while supervisor configuration retained the full wire schema tree. Expose existing capabilities through narrow package entrypoints and share configuration schemas without loading message definitions. Guard the dependency trees without tree-shaking so unused exports cannot hide the runtime cost.
This commit is contained in:
@@ -54,6 +54,15 @@ The heart of Paseo. A Node.js process that:
|
||||
|
||||
All paths are under `packages/server/src/`.
|
||||
|
||||
Desktop and CLI import server capabilities through explicit package subpaths such as
|
||||
`@getpaseo/server/daemon-control`, `/configuration`, and `/process`. The root export
|
||||
loads daemon bootstrap eagerly, even when a caller only uses a path or process helper.
|
||||
Keep process-management and configuration dependencies independent of bootstrap and
|
||||
WebSocket message schemas. Shared configuration schemas belong in protocol leaf modules;
|
||||
`messages` imports and re-exports them. This prevents every supervising process from
|
||||
retaining the daemon's runtime and wire-schema allocations. The server export tests guard
|
||||
these dependency trees with tree-shaking disabled to match unbundled production imports.
|
||||
|
||||
Project identity is daemon-global rather than session-owned. After registry bootstrap, the daemon's
|
||||
project Git observer keeps one non-recursive watch on each lexically equivalent active project root
|
||||
and listens only for the root `.git` entry, with a slow rescan as a missed-event fallback. It runs
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "../../utils/timeline.js";
|
||||
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
|
||||
import type { AgentTimelineItem } from "@getpaseo/protocol/agent-types";
|
||||
import { curateAgentActivity } from "@getpaseo/server";
|
||||
import { curateAgentActivity } from "@getpaseo/server/agent-activity";
|
||||
|
||||
export function addLogsOptions(cmd: Command): Command {
|
||||
return cmd
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Command, Option } from "commander";
|
||||
import { getStructuredAgentResponse, StructuredAgentResponseError } from "@getpaseo/server";
|
||||
import {
|
||||
getStructuredAgentResponse,
|
||||
StructuredAgentResponseError,
|
||||
} from "@getpaseo/server/agent-response";
|
||||
import type { AgentSnapshotPayload } from "@getpaseo/protocol/messages";
|
||||
import { connectToDaemon } from "../../utils/client.js";
|
||||
import type {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { addLocalDaemonOptions } from "../../utils/command-options.js";
|
||||
import { Command } from "commander";
|
||||
import { readDaemonInstance } from "@getpaseo/server/daemon-control";
|
||||
import {
|
||||
readDaemonInstance,
|
||||
readPersistedConfig,
|
||||
getPersistedConfigValue,
|
||||
editPersistedConfig,
|
||||
} from "@getpaseo/server";
|
||||
} from "@getpaseo/server/configuration";
|
||||
import { connectToDaemon } from "../../utils/client.js";
|
||||
import { withOutput, type CommandOptions } from "../../output/index.js";
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import net from "node:net";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { startDaemonInstance, readDaemonInstance } from "@getpaseo/server";
|
||||
import { startDaemonInstance, readDaemonInstance } from "@getpaseo/server/daemon-control";
|
||||
import { expect, test } from "vitest";
|
||||
import { connectToDaemon } from "../../utils/client.js";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Command, Option } from "commander";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { startDaemonInstance, resolvePaseoHome } from "@getpaseo/server";
|
||||
import { startDaemonInstance, resolvePaseoHome } from "@getpaseo/server/daemon-control";
|
||||
const require = createRequire(import.meta.url);
|
||||
function resolveServerRunnerFromDir(currentDir: string): string | null {
|
||||
const packageJsonPath = path.join(currentDir, "package.json");
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { confirm, isCancel, log } from "@clack/prompts";
|
||||
import { Command } from "commander";
|
||||
import chalk from "chalk";
|
||||
import { generateLocalPairingOffer } from "@getpaseo/server/pairing";
|
||||
import { readDaemonInstance } from "@getpaseo/server/daemon-control";
|
||||
import {
|
||||
generateLocalPairingOffer,
|
||||
readDaemonInstance,
|
||||
readPersistedConfig,
|
||||
editPersistedConfig,
|
||||
resolveConfigFromPersisted,
|
||||
} from "@getpaseo/server";
|
||||
} from "@getpaseo/server/configuration";
|
||||
import { connectToDaemon } from "../../utils/client.js";
|
||||
import type { DaemonTarget } from "../../utils/daemon-target.js";
|
||||
import { addJsonAndDaemonHostOptions, withGlobalOptions } from "../../utils/command-options.js";
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Command } from "commander";
|
||||
import { readDaemonInstance, isSameDaemonInstance, DaemonInstanceError } from "@getpaseo/server";
|
||||
import {
|
||||
readDaemonInstance,
|
||||
isSameDaemonInstance,
|
||||
DaemonInstanceError,
|
||||
} from "@getpaseo/server/daemon-control";
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
import { connectToDaemon } from "../../utils/client.js";
|
||||
import { withOutput, type CommandOptions } from "../../output/index.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { platform } from "node:os";
|
||||
import { execCommand } from "@getpaseo/server";
|
||||
import { execCommand } from "@getpaseo/server/process";
|
||||
|
||||
export interface NodePathFromPidResult {
|
||||
nodePath: string | null;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import path from "node:path";
|
||||
import type { Command } from "commander";
|
||||
import { isCancel, password as passwordPrompt } from "@clack/prompts";
|
||||
import { hashDaemonPassword } from "@getpaseo/server/auth";
|
||||
import {
|
||||
hashDaemonPassword,
|
||||
readPersistedConfig,
|
||||
resolvePaseoHome,
|
||||
savePersistedConfig,
|
||||
type PersistedConfig,
|
||||
} from "@getpaseo/server";
|
||||
} from "@getpaseo/server/configuration";
|
||||
import { resolvePaseoHome } from "@getpaseo/server/daemon-control";
|
||||
import type {
|
||||
CommandError,
|
||||
CommandOptions,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { addLocalDaemonOptions } from "../../utils/command-options.js";
|
||||
import { Command } from "commander";
|
||||
import { daemonLogPath } from "@getpaseo/server";
|
||||
import { daemonLogPath } from "@getpaseo/server/daemon-control";
|
||||
import { launchLocalDaemon, parseTimeoutMs, rejectRemovedLaunchFlags } from "./local-daemon.js";
|
||||
import { withOutput, type CommandOptions } from "../../output/index.js";
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@ import { Command } from "commander";
|
||||
import { DaemonConnectionError } from "@getpaseo/client/internal/daemon-client";
|
||||
import {
|
||||
readDaemonInstance,
|
||||
readPersistedConfig,
|
||||
resolveConfigFromPersisted,
|
||||
daemonLogPath,
|
||||
isSameDaemonInstance,
|
||||
DaemonInstanceError,
|
||||
} from "@getpaseo/server";
|
||||
} from "@getpaseo/server/daemon-control";
|
||||
import { readPersistedConfig, resolveConfigFromPersisted } from "@getpaseo/server/configuration";
|
||||
import { connectToDaemon, buildDaemonConnectionCommandError } from "../../utils/client.js";
|
||||
import { withOutput, toCommandError, type CommandOptions } from "../../output/index.js";
|
||||
import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Command } from "commander";
|
||||
import { stopDaemonInstance, type DaemonInstance } from "@getpaseo/server";
|
||||
import { stopDaemonInstance, type DaemonInstance } from "@getpaseo/server/daemon-control";
|
||||
import { connectToDaemon } from "../../utils/client.js";
|
||||
import { withOutput, type CommandOptions } from "../../output/index.js";
|
||||
import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
|
||||
|
||||
@@ -5,10 +5,9 @@ import path from "node:path";
|
||||
import {
|
||||
readPersistedConfig as loadPersistedConfig,
|
||||
savePersistedConfig,
|
||||
readDaemonInstance,
|
||||
waitForDaemonReady,
|
||||
type PersistedConfig,
|
||||
} from "@getpaseo/server";
|
||||
} from "@getpaseo/server/configuration";
|
||||
import { readDaemonInstance, waitForDaemonReady } from "@getpaseo/server/daemon-control";
|
||||
import { withGlobalOptions } from "../utils/command-options.js";
|
||||
import type { CommandOptions } from "../output/index.js";
|
||||
import { launchLocalDaemon, parseTimeoutMs } from "./daemon/local-daemon.js";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawnProcess } from "@getpaseo/server";
|
||||
import { spawnProcess } from "@getpaseo/server/process";
|
||||
import { buildAgentDeepLink, type AgentDeepLinkTarget } from "@getpaseo/protocol/agent-deep-link";
|
||||
|
||||
function findDesktopApp(): string | null {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ensurePrivateDirectory } from "@getpaseo/server";
|
||||
import { ensurePrivateDirectory } from "@getpaseo/server/daemon-control";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { join } from "node:path";
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { waitForDaemonReady, resolvePaseoHome, type DaemonInstance } from "@getpaseo/server";
|
||||
import {
|
||||
waitForDaemonReady,
|
||||
resolvePaseoHome,
|
||||
type DaemonInstance,
|
||||
} from "@getpaseo/server/daemon-control";
|
||||
import { describeDaemonTarget, type DaemonTarget } from "./daemon-target.js";
|
||||
export type { DaemonTarget } from "./daemon-target.js";
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { resolvePaseoHome } from "@getpaseo/server";
|
||||
import { resolvePaseoHome } from "@getpaseo/server/daemon-control";
|
||||
|
||||
export type DaemonTarget = { kind: "instance"; home: string } | { kind: "endpoint"; host: string };
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
DaemonConnectionError,
|
||||
type WebSocketLike,
|
||||
} from "@getpaseo/client/internal/daemon-client";
|
||||
import { readDaemonInstance, isSameDaemonInstance } from "@getpaseo/server";
|
||||
import { readDaemonInstance, isSameDaemonInstance } from "@getpaseo/server/daemon-control";
|
||||
import { runLocalPaseo } from "./helpers/local-cli.ts";
|
||||
import { getAvailablePort } from "./helpers/network.ts";
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { Command } from "commander";
|
||||
import { isBearerTokenValid } from "@getpaseo/server";
|
||||
import { isBearerTokenValid } from "@getpaseo/server/auth";
|
||||
import {
|
||||
runSetPasswordCommand,
|
||||
setDaemonPasswordInConfig,
|
||||
|
||||
@@ -6,7 +6,7 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { buildRelayWebSocketUrl } from "@getpaseo/protocol/daemon-endpoints";
|
||||
import { parseConnectionOfferFromUrl } from "@getpaseo/protocol/connection-offer";
|
||||
import { generateLocalPairingOffer } from "@getpaseo/server";
|
||||
import { generateLocalPairingOffer } from "@getpaseo/server/pairing";
|
||||
import { DaemonClient } from "@getpaseo/client/internal/daemon-client";
|
||||
import { WebSocket } from "ws";
|
||||
import { getAvailablePort } from "../helpers/network.ts";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import net from "node:net";
|
||||
import { _electron as electron, expect } from "playwright/test";
|
||||
import { readDaemonInstance } from "@getpaseo/server";
|
||||
import { readDaemonInstance } from "@getpaseo/server/daemon-control";
|
||||
|
||||
// Run under the test's private Xvfb display; native dialog input never targets
|
||||
// a user's desktop. All daemon operations still cross the real preload/IPC.
|
||||
|
||||
@@ -7,12 +7,12 @@ import path from "node:path";
|
||||
import net from "node:net";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { _electron as electron, expect } from "playwright/test";
|
||||
import { savePersistedConfig } from "@getpaseo/server/configuration";
|
||||
import {
|
||||
savePersistedConfig,
|
||||
startDaemonInstance,
|
||||
stopDaemonInstance,
|
||||
readDaemonInstance,
|
||||
} from "@getpaseo/server";
|
||||
} from "@getpaseo/server/daemon-control";
|
||||
|
||||
const repo = fileURLToPath(new URL("../../..", import.meta.url));
|
||||
const root = await mkdtemp(path.join(tmpdir(), "paseo desktop lifecycle "));
|
||||
|
||||
@@ -15,7 +15,7 @@ const mocks = vi.hoisted(() => ({
|
||||
spawnProcess: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@getpaseo/server", () => ({
|
||||
vi.mock("@getpaseo/server/process", () => ({
|
||||
spawnProcess: mocks.spawnProcess,
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { spawnProcess } from "@getpaseo/server";
|
||||
import { spawnProcess } from "@getpaseo/server/process";
|
||||
import log from "electron-log/main";
|
||||
import type { NodeEntrypointInvocation } from "../node-entrypoint-launcher.js";
|
||||
import { createNodeEntrypointInvocation } from "../runtime-paths.js";
|
||||
|
||||
@@ -51,7 +51,7 @@ vi.mock("electron-log/main", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@getpaseo/server", () => ({
|
||||
vi.mock("@getpaseo/server/daemon-control", () => ({
|
||||
resolvePaseoHome: vi.fn(() => mocks.paseoHome),
|
||||
spawnProcess: mocks.spawnProcess,
|
||||
}));
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
readDaemonInstance,
|
||||
isSameDaemonInstance,
|
||||
type DaemonInstance,
|
||||
} from "@getpaseo/server";
|
||||
} from "@getpaseo/server/daemon-control";
|
||||
import {
|
||||
copyAttachmentFileToManagedStorage,
|
||||
deleteManagedAttachmentFile,
|
||||
|
||||
@@ -21,10 +21,6 @@ vi.mock("electron-log/main", () => ({
|
||||
default: { warn: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock("@getpaseo/server", () => ({
|
||||
spawnProcess: vi.fn(),
|
||||
}));
|
||||
|
||||
const originalPlatform = process.platform;
|
||||
const originalExecPath = process.execPath;
|
||||
const originalResourcesPath = process.resourcesPath;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { resolvePaseoHome } from "@getpaseo/server";
|
||||
import { resolvePaseoHome } from "@getpaseo/server/daemon-control";
|
||||
|
||||
const ATTACHMENTS_DIRNAME = "desktop-attachments";
|
||||
const ATTACHMENT_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* A named launch bundle: a provider plus the agent-config values a client would
|
||||
* otherwise set one control at a time. Field names mirror `AgentSessionConfig`
|
||||
* so applying a profile is a copy rather than a translation table.
|
||||
*
|
||||
* There is deliberately no system prompt here. `AgentSessionConfig.systemPrompt`
|
||||
* is creation-only, so a profile carrying one would apply when starting a new
|
||||
* agent and silently do nothing when applied to a running one.
|
||||
*/
|
||||
export const AgentProfileSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
/** A key into the client's icon registry, not a glyph. Unknown keys draw the default. */
|
||||
icon: z.string().optional(),
|
||||
/** An identity colour name shared with host badges. Unknown values draw unthemed. */
|
||||
color: z.string().optional(),
|
||||
provider: z.string(),
|
||||
model: z.string().optional(),
|
||||
modeId: z.string().optional(),
|
||||
thinkingOptionId: z.string().optional(),
|
||||
featureValues: z.record(z.string(), z.unknown()).optional(),
|
||||
/** Free text, surfaced to orchestrating agents by the `list_profiles` MCP tool. */
|
||||
notes: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export type AgentProfile = z.infer<typeof AgentProfileSchema>;
|
||||
|
||||
export const AgentSkillSelectionSchema = z.discriminatedUnion("mode", [
|
||||
z.object({ mode: z.literal("all") }).strict(),
|
||||
z.object({ mode: z.literal("custom"), skills: z.array(z.string()) }).strict(),
|
||||
]);
|
||||
export type AgentSkillSelection = z.infer<typeof AgentSkillSelectionSchema>;
|
||||
@@ -1,3 +1,21 @@
|
||||
import { AgentProfileSchema, AgentSkillSelectionSchema } from "./agent-profile.js";
|
||||
export {
|
||||
AgentProfileSchema,
|
||||
AgentSkillSelectionSchema,
|
||||
type AgentProfile,
|
||||
type AgentSkillSelection,
|
||||
} from "./agent-profile.js";
|
||||
import { PluginIdSchema, PluginRequirementsSchema, PluginSourceSchema } from "./plugin-config.js";
|
||||
export {
|
||||
PluginIdSchema,
|
||||
PluginRequirementsSchema,
|
||||
DirectoryPluginSourceSchema,
|
||||
PluginSourceSchema,
|
||||
type PluginRequirements,
|
||||
type PluginSource,
|
||||
} from "./plugin-config.js";
|
||||
import { TerminalProfileSchema } from "./terminal-profile.js";
|
||||
export { TerminalProfileSchema, type TerminalProfile } from "./terminal-profile.js";
|
||||
import { z } from "zod";
|
||||
import { TerminalActivitySchema } from "./terminal-activity.js";
|
||||
import { CLIENT_CAPS } from "./client-capabilities.js";
|
||||
@@ -140,47 +158,6 @@ const MutableMetadataGenerationConfigSchema = z
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const TerminalProfileSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
command: z.string(),
|
||||
args: z.array(z.string()).optional(),
|
||||
icon: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export type TerminalProfile = z.infer<typeof TerminalProfileSchema>;
|
||||
|
||||
/**
|
||||
* A named launch bundle: a provider plus the agent-config values a client would
|
||||
* otherwise set one control at a time. Field names mirror `AgentSessionConfig`
|
||||
* so applying a profile is a copy rather than a translation table.
|
||||
*
|
||||
* There is deliberately no system prompt here. `AgentSessionConfig.systemPrompt`
|
||||
* is creation-only, so a profile carrying one would apply when starting a new
|
||||
* agent and silently do nothing when applied to a running one.
|
||||
*/
|
||||
export const AgentProfileSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
/** A key into the client's icon registry, not a glyph. Unknown keys draw the default. */
|
||||
icon: z.string().optional(),
|
||||
/** An identity colour name shared with host badges. Unknown values draw unthemed. */
|
||||
color: z.string().optional(),
|
||||
provider: z.string(),
|
||||
model: z.string().optional(),
|
||||
modeId: z.string().optional(),
|
||||
thinkingOptionId: z.string().optional(),
|
||||
featureValues: z.record(z.string(), z.unknown()).optional(),
|
||||
/** Free text, surfaced to orchestrating agents by the `list_profiles` MCP tool. */
|
||||
notes: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export type AgentProfile = z.infer<typeof AgentProfileSchema>;
|
||||
|
||||
const MutableBrowserToolsConfigSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().default(false),
|
||||
@@ -192,29 +169,6 @@ const MutableRelayConfigSchema = z
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const PluginIdSchema = z.string().regex(/^[a-z][a-z0-9-]*$/);
|
||||
// Semver validation belongs at the manifest/runtime boundary, not on the wire.
|
||||
export const PluginRequirementsSchema = z.object({ paseo: z.string().optional() });
|
||||
export type PluginRequirements = z.infer<typeof PluginRequirementsSchema>;
|
||||
|
||||
export const DirectoryPluginSourceSchema = z
|
||||
.object({
|
||||
source: z.literal("directory"),
|
||||
path: z.string().min(1),
|
||||
enabled: z.boolean().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const PluginSourceSchema = z.discriminatedUnion("source", [DirectoryPluginSourceSchema]);
|
||||
|
||||
export type PluginSource = z.infer<typeof PluginSourceSchema>;
|
||||
|
||||
export const AgentSkillSelectionSchema = z.discriminatedUnion("mode", [
|
||||
z.object({ mode: z.literal("all") }).strict(),
|
||||
z.object({ mode: z.literal("custom"), skills: z.array(z.string()) }).strict(),
|
||||
]);
|
||||
export type AgentSkillSelection = z.infer<typeof AgentSkillSelectionSchema>;
|
||||
|
||||
export const MutableDaemonConfigSchema = z
|
||||
.object({
|
||||
// COMPAT(relayConfig): added in v0.2.6, remove after 2027-01-31 when old daemons are unsupported.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const PluginIdSchema = z.string().regex(/^[a-z][a-z0-9-]*$/);
|
||||
// Semver validation belongs at the manifest/runtime boundary, not on the wire.
|
||||
export const PluginRequirementsSchema = z.object({ paseo: z.string().optional() });
|
||||
export type PluginRequirements = z.infer<typeof PluginRequirementsSchema>;
|
||||
|
||||
export const DirectoryPluginSourceSchema = z
|
||||
.object({
|
||||
source: z.literal("directory"),
|
||||
path: z.string().min(1),
|
||||
enabled: z.boolean().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const PluginSourceSchema = z.discriminatedUnion("source", [DirectoryPluginSourceSchema]);
|
||||
|
||||
export type PluginSource = z.infer<typeof PluginSourceSchema>;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const TerminalProfileSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
command: z.string(),
|
||||
args: z.array(z.string()).optional(),
|
||||
icon: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export type TerminalProfile = z.infer<typeof TerminalProfileSchema>;
|
||||
@@ -27,6 +27,41 @@
|
||||
"types": "./dist/server/terminal/agent-hooks/provider-registry.d.ts",
|
||||
"source": "./src/terminal/agent-hooks/provider-registry.ts",
|
||||
"default": "./dist/server/terminal/agent-hooks/provider-registry.js"
|
||||
},
|
||||
"./daemon-control": {
|
||||
"types": "./dist/server/server/daemon-instance.d.ts",
|
||||
"source": "./src/server/daemon-instance.ts",
|
||||
"default": "./dist/server/server/daemon-instance.js"
|
||||
},
|
||||
"./configuration": {
|
||||
"types": "./dist/server/server/config.d.ts",
|
||||
"source": "./src/server/config.ts",
|
||||
"default": "./dist/server/server/config.js"
|
||||
},
|
||||
"./process": {
|
||||
"types": "./dist/server/utils/spawn.d.ts",
|
||||
"source": "./src/utils/spawn.ts",
|
||||
"default": "./dist/server/utils/spawn.js"
|
||||
},
|
||||
"./auth": {
|
||||
"types": "./dist/server/server/auth.d.ts",
|
||||
"source": "./src/server/auth.ts",
|
||||
"default": "./dist/server/server/auth.js"
|
||||
},
|
||||
"./pairing": {
|
||||
"types": "./dist/server/server/pairing-offer.d.ts",
|
||||
"source": "./src/server/pairing-offer.ts",
|
||||
"default": "./dist/server/server/pairing-offer.js"
|
||||
},
|
||||
"./agent-activity": {
|
||||
"types": "./dist/server/server/agent/activity-curator.d.ts",
|
||||
"source": "./src/server/agent/activity-curator.ts",
|
||||
"default": "./dist/server/server/agent/activity-curator.js"
|
||||
},
|
||||
"./agent-response": {
|
||||
"types": "./dist/server/server/agent/agent-response-loop.d.ts",
|
||||
"source": "./src/server/agent/agent-response-loop.ts",
|
||||
"default": "./dist/server/server/agent/agent-response-loop.js"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@@ -26,6 +26,15 @@ import type { RequestedSpeechProviders } from "./speech/speech-types.js";
|
||||
import { mergeHostnames, parseHostnamesEnv, type HostnamesConfig } from "./hostnames.js";
|
||||
import { resolveGitProcessPolicy } from "../utils/git-process-scheduler.js";
|
||||
|
||||
export {
|
||||
loadPersistedConfig,
|
||||
readPersistedConfig,
|
||||
savePersistedConfig,
|
||||
getPersistedConfigValue,
|
||||
editPersistedConfig,
|
||||
type PersistedConfig,
|
||||
} from "./persisted-config.js";
|
||||
|
||||
const DEFAULT_PORT = 6767;
|
||||
const DEFAULT_RELAY_ENDPOINT = "relay.paseo.sh:443";
|
||||
const DEFAULT_APP_BASE_URL = "https://app.paseo.sh";
|
||||
|
||||
@@ -23,6 +23,10 @@ const killTree = (pid: number, signal: string): Promise<void> =>
|
||||
}),
|
||||
);
|
||||
|
||||
// Process ownership only: desktop and CLI load this entry in their own processes.
|
||||
// Keep daemon bootstrap and WebSocket schemas out of its dependency tree.
|
||||
export { resolvePaseoHome } from "./paseo-home.js";
|
||||
export { ensurePrivateDirectory } from "./private-files.js";
|
||||
export { daemonLaunchEnvironment } from "./config-environment.js";
|
||||
export {
|
||||
isSamePidLock as isSameDaemonInstance,
|
||||
|
||||
@@ -17,3 +17,58 @@ test("keeps daemon-client APIs out of the server public entry", async () => {
|
||||
expect(serverExports).not.toHaveProperty(name);
|
||||
}
|
||||
});
|
||||
|
||||
// Keep the emitted dependency tree intact: production runs unbundled modules,
|
||||
// so tree-shaking must not hide the cost of an unused re-export.
|
||||
test.each([
|
||||
["desktop daemon management", "../../../desktop/src/daemon/daemon-manager.ts"],
|
||||
["supervisor", "../../scripts/supervisor-entrypoint.ts"],
|
||||
["daemon control", "@getpaseo/server/daemon-control"],
|
||||
["configuration", "@getpaseo/server/configuration"],
|
||||
])("%s does not load the daemon runtime or wire schemas", async (_name, entry) => {
|
||||
const inputs = await runtimeDependencies(entry);
|
||||
expect(inputs.filter((file) => /\/(?:bootstrap|messages)\.[jt]s$/.test(file))).toEqual([]);
|
||||
});
|
||||
|
||||
test("CLI command registration does not load the daemon implementation", async () => {
|
||||
const inputs = await runtimeDependencies("../../../cli/src/cli.ts");
|
||||
expect(
|
||||
inputs.filter((file) => /\/(?:bootstrap|agent-manager|session)\.[jt]s$/.test(file)),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
async function runtimeDependencies(entry: string): Promise<string[]> {
|
||||
const { build } = await import("esbuild");
|
||||
const { fileURLToPath } = await import("node:url");
|
||||
const { isAbsolute } = await import("node:path");
|
||||
const result = await build({
|
||||
stdin: {
|
||||
contents: `import ${JSON.stringify(entry.startsWith("@") ? entry : fileURLToPath(new URL(entry, import.meta.url)))};`,
|
||||
resolveDir: fileURLToPath(new URL(".", import.meta.url)),
|
||||
},
|
||||
bundle: true,
|
||||
write: false,
|
||||
metafile: true,
|
||||
treeShaking: false,
|
||||
platform: "node",
|
||||
// Server unit CI builds dependency workspaces, then tests server source.
|
||||
conditions: ["source"],
|
||||
format: "esm",
|
||||
logLevel: "silent",
|
||||
plugins: [
|
||||
{
|
||||
name: "external-vendors",
|
||||
setup(builder) {
|
||||
builder.onResolve({ filter: /^[^./]/ }, ({ path }) => {
|
||||
if (!isAbsolute(path) && !path.startsWith("@getpaseo/")) {
|
||||
return { path, external: true };
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const inputs = Object.keys(result.metafile.inputs);
|
||||
expect(inputs.length).toBeGreaterThan(1);
|
||||
return inputs;
|
||||
}
|
||||
|
||||
@@ -9,13 +9,9 @@ import {
|
||||
} from "./agent/provider-launch-config.js";
|
||||
import type { AgentProviderRuntimeSettingsMap } from "./agent/provider-launch-config.js";
|
||||
import { ensurePrivateFile, writePrivateFileAtomicSync } from "./private-files.js";
|
||||
import {
|
||||
AgentProfileSchema,
|
||||
AgentSkillSelectionSchema,
|
||||
PluginIdSchema,
|
||||
PluginSourceSchema,
|
||||
TerminalProfileSchema,
|
||||
} from "@getpaseo/protocol/messages";
|
||||
import { AgentProfileSchema, AgentSkillSelectionSchema } from "@getpaseo/protocol/agent-profile";
|
||||
import { PluginIdSchema, PluginSourceSchema } from "@getpaseo/protocol/plugin-config";
|
||||
import { TerminalProfileSchema } from "@getpaseo/protocol/terminal-profile";
|
||||
import { PaseoServicePortAllocationSchema } from "@getpaseo/protocol/paseo-config-schema";
|
||||
|
||||
export const LogLevelSchema = z.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
|
||||
|
||||
Reference in New Issue
Block a user