Add managed desktop daemon runtime support

This commit is contained in:
Mohamed Boudra
2026-03-08 15:40:02 +07:00
parent faa5aaab6f
commit 2d5d0dcacd
35 changed files with 5377 additions and 421 deletions
@@ -1,10 +1,11 @@
import os from "node:os";
import path from "node:path";
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { Writable } from "node:stream";
import pino from "pino";
import { describe, expect, test } from "vitest";
import { createPaseoDaemon, type PaseoDaemonConfig } from "./bootstrap.js";
import { createPaseoDaemon, parseListenString, type PaseoDaemonConfig } from "./bootstrap.js";
import { createTestPaseoDaemon } from "./test-utils/paseo-daemon.js";
import { createTestAgentClients } from "./test-utils/fake-agent-client.js";
@@ -75,4 +76,65 @@ describe("paseo daemon bootstrap", () => {
await rm(staticDir, { recursive: true, force: true });
}
});
test("parses Windows named pipes as managed IPC listen targets", () => {
expect(parseListenString(String.raw`\\.\pipe\paseo-managed-test`)).toEqual({
type: "pipe",
path: String.raw`\\.\pipe\paseo-managed-test`,
});
expect(parseListenString(`pipe://${String.raw`\\.\pipe\paseo-managed-test`}`)).toEqual({
type: "pipe",
path: String.raw`\\.\pipe\paseo-managed-test`,
});
});
test("emits a relay pairing offer for unix socket listeners", async () => {
const paseoHomeRoot = await mkdtemp(path.join(os.tmpdir(), "paseo-socket-relay-"));
const paseoHome = path.join(paseoHomeRoot, ".paseo");
const staticDir = await mkdtemp(path.join(os.tmpdir(), "paseo-static-"));
const socketPath = path.join(paseoHomeRoot, "run", "paseo.sock");
await mkdir(path.dirname(socketPath), { recursive: true });
await mkdir(paseoHome, { recursive: true });
const lines: string[] = [];
const logger = pino(
{ level: "info" },
new Writable({
write(chunk, _encoding, callback) {
lines.push(chunk.toString("utf8"));
callback();
},
})
);
const config: PaseoDaemonConfig = {
listen: socketPath,
paseoHome,
corsAllowedOrigins: [],
allowedHosts: true,
mcpEnabled: false,
staticDir,
mcpDebug: false,
agentClients: createTestAgentClients(),
agentStoragePath: path.join(paseoHome, "agents"),
relayEnabled: true,
relayEndpoint: "127.0.0.1:9",
relayPublicEndpoint: "127.0.0.1:9",
appBaseUrl: "https://app.paseo.sh",
openai: undefined,
speech: undefined,
};
const daemon = await createPaseoDaemon(config, logger);
try {
await daemon.start();
expect(lines.some((line) => line.includes('"msg":"pairing_offer"'))).toBe(true);
} finally {
await daemon.stop().catch(() => undefined);
await daemon.agentManager.flush().catch(() => undefined);
await rm(paseoHomeRoot, { recursive: true, force: true });
await rm(staticDir, { recursive: true, force: true });
}
});
});
+40 -36
View File
@@ -11,9 +11,16 @@ import type { Logger } from "pino";
type ListenTarget =
| { type: "tcp"; host: string; port: number }
| { type: "socket"; path: string };
| { type: "socket"; path: string }
| { type: "pipe"; path: string };
function parseListenString(listen: string): ListenTarget {
export function parseListenString(listen: string): ListenTarget {
if (listen.startsWith("\\\\.\\pipe\\") || listen.startsWith("pipe://")) {
return {
type: "pipe",
path: listen.startsWith("pipe://") ? listen.slice("pipe://".length) : listen,
};
}
// Unix socket: starts with / or ~ or contains .sock
if (listen.startsWith("/") || listen.startsWith("~") || listen.includes(".sock")) {
return { type: "socket", path: listen };
@@ -570,46 +577,44 @@ export async function createPaseoDaemon(
const onListening = () => {
httpServer.off("error", onError);
const logAndResolve = async () => {
const relayEnabled = config.relayEnabled ?? true;
const relayEndpoint = config.relayEndpoint ?? "relay.paseo.sh:443";
const relayPublicEndpoint = config.relayPublicEndpoint ?? relayEndpoint;
const appBaseUrl = config.appBaseUrl ?? "https://app.paseo.sh";
if (listenTarget.type === "tcp") {
logger.info(
{ host: listenTarget.host, port: listenTarget.port },
`Server listening on http://${listenTarget.host}:${listenTarget.port}`
);
const relayEnabled = config.relayEnabled ?? true;
const relayEndpoint = config.relayEndpoint ?? "relay.paseo.sh:443";
const relayPublicEndpoint = config.relayPublicEndpoint ?? relayEndpoint;
const appBaseUrl = config.appBaseUrl ?? "https://app.paseo.sh";
if (relayEnabled) {
const offer = await createConnectionOfferV2({
serverId,
daemonPublicKeyB64: daemonKeyPair.publicKeyB64,
relay: { endpoint: relayPublicEndpoint },
});
const url = encodeOfferToFragmentUrl({ offer, appBaseUrl });
logger.info({ url }, "pairing_offer");
}
if (relayEnabled) {
relayTransport?.stop().catch(() => undefined);
relayTransport = startRelayTransport({
logger,
attachSocket: (ws, metadata) => {
if (!wsServer) {
throw new Error("WebSocket server not initialized");
}
return wsServer.attachExternalSocket(ws, metadata);
},
relayEndpoint,
serverId,
daemonKeyPair: daemonKeyPair.keyPair,
});
}
} else {
logger.info({ path: listenTarget.path }, `Server listening on ${listenTarget.path}`);
}
if (relayEnabled) {
const offer = await createConnectionOfferV2({
serverId,
daemonPublicKeyB64: daemonKeyPair.publicKeyB64,
relay: { endpoint: relayPublicEndpoint },
});
const url = encodeOfferToFragmentUrl({ offer, appBaseUrl });
logger.info({ url }, "pairing_offer");
relayTransport?.stop().catch(() => undefined);
relayTransport = startRelayTransport({
logger,
attachSocket: (ws, metadata) => {
if (!wsServer) {
throw new Error("WebSocket server not initialized");
}
return wsServer.attachExternalSocket(ws, metadata);
},
relayEndpoint,
serverId,
daemonKeyPair: daemonKeyPair.keyPair,
});
}
};
logAndResolve().then(resolve, reject);
@@ -620,8 +625,7 @@ export async function createPaseoDaemon(
if (listenTarget.type === "tcp") {
httpServer.listen(listenTarget.port, listenTarget.host);
} else {
// Remove stale socket file if it exists
if (existsSync(listenTarget.path)) {
if (listenTarget.type === "socket" && existsSync(listenTarget.path)) {
unlinkSync(listenTarget.path);
}
httpServer.listen(listenTarget.path);