Update files
This commit is contained in:
@@ -1,17 +1,41 @@
|
||||
{
|
||||
"name": "@getpaseo/server",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Paseo backend server",
|
||||
"type": "module",
|
||||
"types": "./dist/server/server/exports.d.ts",
|
||||
"files": [
|
||||
"dist/server",
|
||||
"dist/scripts",
|
||||
"README.md",
|
||||
".env.example",
|
||||
"agent-prompt.md"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/server/exports.ts",
|
||||
"./utils/tool-call-parsers": "./src/utils/tool-call-parsers.ts"
|
||||
".": {
|
||||
"types": "./dist/server/server/exports.d.ts",
|
||||
"default": [
|
||||
"./dist/server/server/exports.js",
|
||||
"./src/server/exports.ts"
|
||||
]
|
||||
},
|
||||
"./utils/tool-call-parsers": {
|
||||
"types": "./dist/server/utils/tool-call-parsers.d.ts",
|
||||
"default": [
|
||||
"./dist/server/utils/tool-call-parsers.js",
|
||||
"./src/utils/tool-call-parsers.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development tsx scripts/dev-runner.ts",
|
||||
"dev:tsx": "NODE_ENV=development tsx watch --ignore '**/*.timestamp-*' src/server/index.ts",
|
||||
"build": "tsc -p tsconfig.server.json",
|
||||
"start": "NODE_ENV=production node dist/server/index.js",
|
||||
"build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && npm run build:lib && npm run build:scripts",
|
||||
"build:lib": "tsc -p tsconfig.server.json --incremental false",
|
||||
"build:scripts": "tsc -p tsconfig.scripts.json --incremental false && mkdir -p dist/scripts && cp scripts/mcp-stdio-socket-bridge-cli.mjs dist/scripts/mcp-stdio-socket-bridge-cli.mjs",
|
||||
"prepack": "npm run build",
|
||||
"start": "NODE_ENV=production node dist/server/server/index.js",
|
||||
"typecheck": "tsc -p tsconfig.server.typecheck.json --noEmit",
|
||||
"generate:config-schema": "tsx scripts/generate-config-schema.ts",
|
||||
"speech:models": "tsx scripts/list-speech-models.ts",
|
||||
@@ -25,7 +49,8 @@
|
||||
"test:e2e:mobile": "playwright test --project='Mobile Chrome'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.52",
|
||||
"@getpaseo/relay": "*",
|
||||
"@ai-sdk/openai": "2.0.52",
|
||||
"@deepgram/sdk": "^3.4.0",
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@lezer/css": "^1.3.0",
|
||||
@@ -39,7 +64,7 @@
|
||||
"@opencode-ai/sdk": "^1.1.12",
|
||||
"@sctg/sentencepiece-js": "^1.1.0",
|
||||
"@xterm/headless": "^6.0.0",
|
||||
"ai": "^5.0.76",
|
||||
"ai": "5.0.78",
|
||||
"ajv": "^8.17.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.18.2",
|
||||
@@ -50,7 +75,6 @@
|
||||
"openai": "^4.20.0",
|
||||
"pino": "^10.2.0",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"playwright": "^1.56.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"sherpa-onnx": "^1.12.23",
|
||||
"sherpa-onnx-node": "^1.12.23",
|
||||
@@ -62,8 +86,8 @@
|
||||
"zod-to-json-schema": "^3.25.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@getpaseo/relay": "*",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"playwright": "^1.56.1",
|
||||
"@types/express": "^4.17.20",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { fileURLToPath } from "url";
|
||||
import { existsSync } from "node:fs";
|
||||
import { runSupervisor } from "./supervisor.js";
|
||||
|
||||
function resolveWorkerEntry(): string {
|
||||
const candidates = [
|
||||
fileURLToPath(new URL("../server/server/index.js", import.meta.url)),
|
||||
fileURLToPath(new URL("../dist/server/server/index.js", import.meta.url)),
|
||||
fileURLToPath(new URL("../src/server/index.ts", import.meta.url)),
|
||||
fileURLToPath(new URL("../../src/server/index.ts", import.meta.url)),
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
function resolveWorkerExecArgv(): string[] {
|
||||
const workerEntry = resolveWorkerEntry();
|
||||
return workerEntry.endsWith(".ts") ? ["--import", "tsx"] : [];
|
||||
}
|
||||
|
||||
runSupervisor({
|
||||
name: "DaemonRunner",
|
||||
startupMessage: "Starting daemon worker (IPC restart enabled)",
|
||||
resolveWorkerEntry,
|
||||
workerArgs: process.argv.slice(2),
|
||||
workerEnv: process.env,
|
||||
workerExecArgv: resolveWorkerExecArgv(),
|
||||
restartOnCrash: false,
|
||||
shutdownReasons: ["cli_shutdown"],
|
||||
});
|
||||
@@ -1,63 +1,36 @@
|
||||
import { fork, type ChildProcess } from "child_process";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
import { existsSync } from "node:fs";
|
||||
import { runSupervisor } from "./supervisor.js";
|
||||
|
||||
const serverEntry = fileURLToPath(
|
||||
new URL("../src/server/index.ts", import.meta.url)
|
||||
);
|
||||
function resolveWorkerEntry(): string {
|
||||
const candidates = [
|
||||
fileURLToPath(new URL("../server/server/index.js", import.meta.url)),
|
||||
fileURLToPath(new URL("../dist/server/server/index.js", import.meta.url)),
|
||||
fileURLToPath(new URL("../src/server/index.ts", import.meta.url)),
|
||||
fileURLToPath(new URL("../../src/server/index.ts", import.meta.url)),
|
||||
];
|
||||
|
||||
let child: ChildProcess | null = null;
|
||||
let restarting = false;
|
||||
|
||||
function spawnServer() {
|
||||
child = fork(serverEntry, process.argv.slice(2), {
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
execArgv: ["--import", "tsx"],
|
||||
});
|
||||
|
||||
child.on("message", (msg: any) => {
|
||||
if (msg?.type === "paseo:restart") {
|
||||
restartServer();
|
||||
for (const candidate of candidates) {
|
||||
if (existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
const exitDescriptor =
|
||||
signal ?? (typeof code === "number" ? `code ${code}` : "unknown");
|
||||
|
||||
// Restart on: explicit restart request, or any non-zero exit (crash)
|
||||
if (restarting || (code !== 0 && code !== null)) {
|
||||
restarting = false;
|
||||
process.stderr.write(`[DevRunner] Server exited (${exitDescriptor}). Restarting...\n`);
|
||||
spawnServer();
|
||||
return;
|
||||
}
|
||||
|
||||
process.stderr.write(`[DevRunner] Server exited (${exitDescriptor}). Shutting down.\n`);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
function restartServer() {
|
||||
if (!child || restarting) {
|
||||
return;
|
||||
}
|
||||
|
||||
restarting = true;
|
||||
process.stderr.write("[DevRunner] Restart requested. Stopping current server...\n");
|
||||
child.kill("SIGTERM");
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
function forwardSignal(signal: NodeJS.Signals) {
|
||||
if (!child) {
|
||||
process.exit(0);
|
||||
}
|
||||
child.kill(signal);
|
||||
function resolveWorkerExecArgv(): string[] {
|
||||
const workerEntry = resolveWorkerEntry();
|
||||
return workerEntry.endsWith(".ts") ? ["--import", "tsx"] : [];
|
||||
}
|
||||
|
||||
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
||||
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
||||
|
||||
process.stdout.write("[DevRunner] Starting server with tsx (explicit restarts only)\n");
|
||||
spawnServer();
|
||||
runSupervisor({
|
||||
name: "DevRunner",
|
||||
startupMessage: "Starting server worker (crash restarts enabled)",
|
||||
resolveWorkerEntry,
|
||||
workerArgs: process.argv.slice(2),
|
||||
workerEnv: process.env,
|
||||
workerExecArgv: resolveWorkerExecArgv(),
|
||||
restartOnCrash: true,
|
||||
shutdownReasons: ["cli_shutdown"],
|
||||
});
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { fork, type ChildProcess } from "child_process";
|
||||
|
||||
type RestartMessage = {
|
||||
type: "paseo:restart";
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
type SupervisorOptions = {
|
||||
name: string;
|
||||
startupMessage: string;
|
||||
resolveWorkerEntry: () => string;
|
||||
workerArgs?: string[];
|
||||
workerEnv?: NodeJS.ProcessEnv;
|
||||
workerExecArgv?: string[];
|
||||
restartOnCrash?: boolean;
|
||||
shutdownReasons?: string[];
|
||||
};
|
||||
|
||||
function describeExit(code: number | null, signal: NodeJS.Signals | null): string {
|
||||
return signal ?? (typeof code === "number" ? `code ${code}` : "unknown");
|
||||
}
|
||||
|
||||
function isRestartMessage(msg: unknown): msg is RestartMessage {
|
||||
return (
|
||||
typeof msg === "object" &&
|
||||
msg !== null &&
|
||||
"type" in msg &&
|
||||
(msg as { type?: unknown }).type === "paseo:restart"
|
||||
);
|
||||
}
|
||||
|
||||
export function runSupervisor(options: SupervisorOptions): void {
|
||||
const shutdownReasons = new Set(options.shutdownReasons ?? ["cli_shutdown"]);
|
||||
const restartOnCrash = options.restartOnCrash ?? false;
|
||||
const workerArgs = options.workerArgs ?? process.argv.slice(2);
|
||||
const workerEnv = options.workerEnv ?? process.env;
|
||||
const workerExecArgv = options.workerExecArgv ?? ["--import", "tsx"];
|
||||
|
||||
let child: ChildProcess | null = null;
|
||||
let restarting = false;
|
||||
let shuttingDown = false;
|
||||
|
||||
const log = (message: string): void => {
|
||||
process.stderr.write(`[${options.name}] ${message}\n`);
|
||||
};
|
||||
|
||||
const spawnWorker = () => {
|
||||
let workerEntry: string;
|
||||
try {
|
||||
// Resolve at spawn time so restarts pick up current filesystem state.
|
||||
workerEntry = options.resolveWorkerEntry();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
log(`Failed to resolve worker entry: ${message}`);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
child = fork(workerEntry, workerArgs, {
|
||||
stdio: "inherit",
|
||||
env: workerEnv,
|
||||
execArgv: workerExecArgv,
|
||||
});
|
||||
|
||||
child.on("message", (msg: unknown) => {
|
||||
if (!isRestartMessage(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.reason && shutdownReasons.has(msg.reason)) {
|
||||
requestShutdown(`Shutdown requested by worker (${msg.reason})`);
|
||||
return;
|
||||
}
|
||||
|
||||
requestRestart("Restart requested by worker");
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
const exitDescriptor = describeExit(code, signal);
|
||||
|
||||
if (shuttingDown) {
|
||||
log(`Worker exited (${exitDescriptor}). Supervisor shutting down.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (restarting || (restartOnCrash && code !== 0 && code !== null)) {
|
||||
restarting = false;
|
||||
log(`Worker exited (${exitDescriptor}). Restarting worker...`);
|
||||
spawnWorker();
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Worker exited (${exitDescriptor}). Supervisor exiting.`);
|
||||
process.exit(typeof code === "number" ? code : 0);
|
||||
});
|
||||
};
|
||||
|
||||
const requestRestart = (reason: string) => {
|
||||
if (!child || restarting || shuttingDown) {
|
||||
return;
|
||||
}
|
||||
restarting = true;
|
||||
log(`${reason}. Stopping worker for restart...`);
|
||||
child.kill("SIGTERM");
|
||||
};
|
||||
|
||||
const requestShutdown = (reason: string) => {
|
||||
if (shuttingDown) {
|
||||
return;
|
||||
}
|
||||
shuttingDown = true;
|
||||
restarting = false;
|
||||
log(`${reason}. Stopping worker...`);
|
||||
if (!child) {
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
child.kill("SIGTERM");
|
||||
};
|
||||
|
||||
const forwardSignal = (signal: NodeJS.Signals) => {
|
||||
requestShutdown(`Received ${signal}`);
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
||||
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
||||
|
||||
process.stdout.write(`[${options.name}] ${options.startupMessage}\n`);
|
||||
spawnWorker();
|
||||
}
|
||||
@@ -977,7 +977,6 @@ function mapCodexPatchNotificationToToolCall(params: {
|
||||
stdout?: string | null;
|
||||
stderr?: string | null;
|
||||
success?: boolean | null;
|
||||
latestUnifiedDiff?: string | null;
|
||||
running: boolean;
|
||||
}): ToolCallTimelineItem {
|
||||
const files = parseCodexPatchChanges(params.changes);
|
||||
@@ -985,7 +984,7 @@ function mapCodexPatchNotificationToToolCall(params: {
|
||||
const firstPatchText = files
|
||||
.map((file) => file.content?.trim())
|
||||
.find((value): value is string => typeof value === "string" && value.length > 0);
|
||||
const patchText = params.latestUnifiedDiff?.trim() || firstPatchText;
|
||||
const patchText = firstPatchText;
|
||||
const patchFields = codexPatchTextFields(patchText);
|
||||
const mapped = mapCodexRolloutToolCall({
|
||||
callId: params.callId ?? null,
|
||||
@@ -1736,7 +1735,6 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
private warnedUnknownNotificationMethods = new Set<string>();
|
||||
private warnedInvalidNotificationPayloads = new Set<string>();
|
||||
private warnedIncompleteEditToolCallIds = new Set<string>();
|
||||
private latestTurnUnifiedDiff: string | null = null;
|
||||
private latestUsage: AgentUsage | undefined;
|
||||
private connected = false;
|
||||
private collaborationModes: Array<{
|
||||
@@ -2422,7 +2420,6 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
|
||||
if (parsed.kind === "turn_started") {
|
||||
this.currentTurnId = parsed.turnId;
|
||||
this.latestTurnUnifiedDiff = null;
|
||||
this.emittedItemStartedIds.clear();
|
||||
this.emittedItemCompletedIds.clear();
|
||||
this.pendingCommandOutputDeltas.clear();
|
||||
@@ -2444,7 +2441,6 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
} else {
|
||||
this.emitEvent({ type: "turn_completed", provider: CODEX_PROVIDER, usage: this.latestUsage });
|
||||
}
|
||||
this.latestTurnUnifiedDiff = null;
|
||||
this.emittedItemStartedIds.clear();
|
||||
this.emittedItemCompletedIds.clear();
|
||||
this.pendingCommandOutputDeltas.clear();
|
||||
@@ -2470,8 +2466,6 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
}
|
||||
|
||||
if (parsed.kind === "diff_updated") {
|
||||
const trimmedDiff = parsed.diff.trim();
|
||||
this.latestTurnUnifiedDiff = trimmedDiff.length > 0 ? trimmedDiff : null;
|
||||
// NOTE: Codex app-server emits frequent `turn/diff/updated` notifications
|
||||
// containing a full accumulated unified diff for the *entire turn*.
|
||||
// This is not a concrete file-change tool call; it is progress telemetry.
|
||||
@@ -2557,7 +2551,6 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
callId: parsed.callId,
|
||||
changes: parsed.changes,
|
||||
cwd: this.config.cwd ?? null,
|
||||
latestUnifiedDiff: this.latestTurnUnifiedDiff,
|
||||
running: true,
|
||||
});
|
||||
this.warnOnIncompleteEditToolCall(timelineItem, "patch_apply_started", {
|
||||
@@ -2580,7 +2573,6 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
stdout: parsed.stdout ?? bufferedOutput,
|
||||
stderr: parsed.stderr,
|
||||
success: parsed.success,
|
||||
latestUnifiedDiff: this.latestTurnUnifiedDiff,
|
||||
running: false,
|
||||
});
|
||||
this.warnOnIncompleteEditToolCall(timelineItem, "patch_apply_completed", {
|
||||
|
||||
@@ -463,4 +463,38 @@ describe("codex tool-call mapper", () => {
|
||||
expect(item.detail.newString).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("maps path-only fileChange payloads to unknown detail instead of empty edit detail", () => {
|
||||
const item = mapCodexToolCallFromThreadItem(
|
||||
{
|
||||
type: "fileChange",
|
||||
id: "codex-file-change-path-only",
|
||||
status: "completed",
|
||||
changes: [{ path: "/tmp/repo/src/path-only.ts", kind: "modify" }],
|
||||
},
|
||||
{ cwd: "/tmp/repo" }
|
||||
);
|
||||
|
||||
expect(item?.detail.type).toBe("unknown");
|
||||
if (item?.detail.type === "unknown") {
|
||||
expect(item.detail.input).toEqual({
|
||||
files: [{ path: "src/path-only.ts", kind: "modify" }],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("maps path-only apply_patch rollout payloads to unknown detail instead of empty edit detail", () => {
|
||||
const item = mapCodexRolloutToolCall({
|
||||
callId: "codex-call-apply-path-only",
|
||||
name: "apply_patch",
|
||||
input: { path: "/tmp/repo/src/path-only-rollout.ts" },
|
||||
output: { files: [{ path: "/tmp/repo/src/path-only-rollout.ts", kind: "modify" }] },
|
||||
cwd: "/tmp/repo",
|
||||
});
|
||||
|
||||
expect(item.detail.type).toBe("unknown");
|
||||
if (item.detail.type === "unknown") {
|
||||
expect(item.detail.input).toEqual({ path: "/tmp/repo/src/path-only-rollout.ts" });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -309,6 +309,17 @@ function asPatchOrContentFields(text: string | undefined): { patch?: string; con
|
||||
return { content: text };
|
||||
}
|
||||
|
||||
function hasRenderableEditContent(detail: ToolCallDetail): boolean {
|
||||
if (detail.type !== "edit") {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(typeof detail.unifiedDiff === "string" && detail.unifiedDiff.length > 0) ||
|
||||
(typeof detail.newString === "string" && detail.newString.length > 0) ||
|
||||
(typeof detail.oldString === "string" && detail.oldString.length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
function pickFirstPatchLikeString(values: unknown[]): string | undefined {
|
||||
for (const value of values) {
|
||||
if (typeof value === "string" && value.length > 0) {
|
||||
@@ -610,12 +621,19 @@ function mapFileChangeItem(
|
||||
|
||||
const firstFile = files[0];
|
||||
const firstTextFields = asEditTextFields(firstFile?.diff);
|
||||
const hasFirstTextFields = Object.keys(firstTextFields).length > 0;
|
||||
const detail = firstFile?.path
|
||||
? {
|
||||
? hasFirstTextFields
|
||||
? {
|
||||
type: "edit" as const,
|
||||
filePath: firstFile.path,
|
||||
...firstTextFields,
|
||||
}
|
||||
: {
|
||||
type: "unknown" as const,
|
||||
input,
|
||||
output,
|
||||
}
|
||||
: {
|
||||
type: "unknown" as const,
|
||||
input,
|
||||
@@ -755,6 +773,13 @@ export function mapCodexRolloutToolCall(params: {
|
||||
detail = fallbackDetail;
|
||||
}
|
||||
}
|
||||
if (detail.type === "edit" && !hasRenderableEditContent(detail)) {
|
||||
detail = {
|
||||
type: "unknown",
|
||||
input,
|
||||
output,
|
||||
};
|
||||
}
|
||||
|
||||
return buildToolCall({
|
||||
callId,
|
||||
|
||||
@@ -147,18 +147,22 @@ export async function createPaseoDaemon(
|
||||
);
|
||||
}
|
||||
|
||||
const serverId = getOrCreateServerId(config.paseoHome, { logger });
|
||||
const daemonKeyPair = await loadOrCreateDaemonKeyPair(config.paseoHome, logger);
|
||||
let relayTransport: RelayTransportController | null = null;
|
||||
// Acquire PID lock before expensive bootstrap work so duplicate starts fail immediately.
|
||||
await acquirePidLock(config.paseoHome, config.listen);
|
||||
|
||||
const staticDir = config.staticDir;
|
||||
const downloadTokenTtlMs = config.downloadTokenTtlMs ?? 60000;
|
||||
try {
|
||||
const serverId = getOrCreateServerId(config.paseoHome, { logger });
|
||||
const daemonKeyPair = await loadOrCreateDaemonKeyPair(config.paseoHome, logger);
|
||||
let relayTransport: RelayTransportController | null = null;
|
||||
|
||||
const downloadTokenStore = new DownloadTokenStore({ ttlMs: downloadTokenTtlMs });
|
||||
const staticDir = config.staticDir;
|
||||
const downloadTokenTtlMs = config.downloadTokenTtlMs ?? 60000;
|
||||
|
||||
const listenTarget = parseListenString(config.listen);
|
||||
const downloadTokenStore = new DownloadTokenStore({ ttlMs: downloadTokenTtlMs });
|
||||
|
||||
const app = express();
|
||||
const listenTarget = parseListenString(config.listen);
|
||||
|
||||
const app = express();
|
||||
|
||||
// Host allowlist / DNS rebinding protection (vite-like semantics).
|
||||
// For non-TCP (unix sockets), skip host validation.
|
||||
@@ -495,12 +499,9 @@ export async function createPaseoDaemon(
|
||||
}
|
||||
);
|
||||
|
||||
const start = async () => {
|
||||
// Acquire PID lock
|
||||
await acquirePidLock(config.paseoHome, config.listen);
|
||||
|
||||
// Start main HTTP server
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const start = async () => {
|
||||
// Start main HTTP server
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const onError = (err: Error) => {
|
||||
httpServer.off("listening", onListening);
|
||||
reject(err);
|
||||
@@ -581,43 +582,47 @@ export async function createPaseoDaemon(
|
||||
}
|
||||
httpServer.listen(listenTarget.path);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const stop = async () => {
|
||||
await closeAllAgents(logger, agentManager);
|
||||
await agentManager.flush().catch(() => undefined);
|
||||
detachAgentStoragePersistence();
|
||||
await agentStorage.flush().catch(() => undefined);
|
||||
await shutdownProviders(logger);
|
||||
terminalManager.killAll();
|
||||
cleanupSpeechRuntime();
|
||||
await relayTransport?.stop().catch(() => undefined);
|
||||
if (wsServer) {
|
||||
await wsServer.close();
|
||||
}
|
||||
if (voiceMcpBridgeManager) {
|
||||
await voiceMcpBridgeManager.stop().catch(() => undefined);
|
||||
}
|
||||
await new Promise<void>((resolve) => {
|
||||
httpServer.close(() => resolve());
|
||||
});
|
||||
// Clean up socket files
|
||||
if (listenTarget.type === "socket" && existsSync(listenTarget.path)) {
|
||||
unlinkSync(listenTarget.path);
|
||||
}
|
||||
// Release PID lock
|
||||
await releasePidLock(config.paseoHome);
|
||||
};
|
||||
const stop = async () => {
|
||||
await closeAllAgents(logger, agentManager);
|
||||
await agentManager.flush().catch(() => undefined);
|
||||
detachAgentStoragePersistence();
|
||||
await agentStorage.flush().catch(() => undefined);
|
||||
await shutdownProviders(logger);
|
||||
terminalManager.killAll();
|
||||
cleanupSpeechRuntime();
|
||||
await relayTransport?.stop().catch(() => undefined);
|
||||
if (wsServer) {
|
||||
await wsServer.close();
|
||||
}
|
||||
if (voiceMcpBridgeManager) {
|
||||
await voiceMcpBridgeManager.stop().catch(() => undefined);
|
||||
}
|
||||
await new Promise<void>((resolve) => {
|
||||
httpServer.close(() => resolve());
|
||||
});
|
||||
// Clean up socket files
|
||||
if (listenTarget.type === "socket" && existsSync(listenTarget.path)) {
|
||||
unlinkSync(listenTarget.path);
|
||||
}
|
||||
// Release PID lock
|
||||
await releasePidLock(config.paseoHome);
|
||||
};
|
||||
|
||||
return {
|
||||
config,
|
||||
agentManager,
|
||||
agentStorage,
|
||||
terminalManager,
|
||||
start,
|
||||
stop,
|
||||
};
|
||||
return {
|
||||
config,
|
||||
agentManager,
|
||||
agentStorage,
|
||||
terminalManager,
|
||||
start,
|
||||
stop,
|
||||
};
|
||||
} catch (err) {
|
||||
await releasePidLock(config.paseoHome).catch(() => undefined);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function closeAllAgents(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { z } from "zod";
|
||||
import { AGENT_PROVIDER_IDS } from "./agent/provider-manifest.js";
|
||||
@@ -157,10 +157,18 @@ export function loadPersistedConfig(
|
||||
): PersistedConfig {
|
||||
const log = getLogger(logger);
|
||||
const configPath = getConfigPath(paseoHome);
|
||||
const defaultConfig = PersistedConfigSchema.parse({});
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
log?.info(`No config file at ${configPath}, using defaults`);
|
||||
return PersistedConfigSchema.parse({});
|
||||
try {
|
||||
mkdirSync(path.dirname(configPath), { recursive: true });
|
||||
writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n");
|
||||
log?.info(`Initialized config file at ${configPath}`);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(`[Config] Failed to initialize ${configPath}: ${message}`);
|
||||
}
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
let raw: string;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ES2020"],
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"declaration": false,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"scripts/daemon-runner.ts",
|
||||
"scripts/dev-runner.ts",
|
||||
"scripts/supervisor.ts"
|
||||
],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user