From 4de2766114852254d26eb363d650dc75e137f1a6 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 11 Apr 2026 12:36:49 +0700 Subject: [PATCH] Strip relay and pairing URLs from daemon logs The pairing URL and relay WebSocket URLs contain secrets (serverId, daemon public key, relay endpoint) that should never leak into log files. Access to pairing info should only happen via explicit `paseo daemon pair` commands. Removed `url` from all relay-transport and bootstrap log entries while keeping connectionId and log messages for debuggability. --- packages/server/src/server/bootstrap.ts | 182 ++++++------------ packages/server/src/server/relay-transport.ts | 16 +- 2 files changed, 67 insertions(+), 131 deletions(-) diff --git a/packages/server/src/server/bootstrap.ts b/packages/server/src/server/bootstrap.ts index e06ac834d..d6a7a67a6 100644 --- a/packages/server/src/server/bootstrap.ts +++ b/packages/server/src/server/bootstrap.ts @@ -6,7 +6,6 @@ import { randomUUID } from "node:crypto"; import { hostname as getHostname } from "node:os"; import path from "node:path"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import type { Logger } from "pino"; @@ -96,7 +95,11 @@ import { AgentManager } from "./agent/agent-manager.js"; import { AgentStorage } from "./agent/agent-storage.js"; import { attachAgentStoragePersistence } from "./persistence-hooks.js"; import { createAgentMcpServer } from "./agent/mcp-server.js"; -import { createAllClients, shutdownProviders } from "./agent/provider-registry.js"; +import { + buildProviderRegistry, + createAllClients, + shutdownProviders, +} from "./agent/provider-registry.js"; import { bootstrapWorkspaceRegistries } from "./workspace-registry-bootstrap.js"; import { FileBackedProjectRegistry, FileBackedWorkspaceRegistry } from "./workspace-registry.js"; import { FileBackedChatService } from "./chat/chat-service.js"; @@ -112,36 +115,21 @@ import { resolveDaemonVersion } from "./daemon-version.js"; import type { AgentClient, AgentProvider } from "./agent/agent-sdk-types.js"; import type { AgentProviderRuntimeSettingsMap } from "./agent/provider-launch-config.js"; import { isHostAllowed, type AllowedHostsConfig } from "./allowed-hosts.js"; -import { - createVoiceMcpSocketBridgeManager, - type VoiceMcpSocketBridgeManager, -} from "./voice-mcp-bridge.js"; -import { resolveVoiceMcpBridgeFromRuntime } from "./voice-mcp-bridge-command.js"; type AgentMcpTransportMap = Map; -function resolveVoiceMcpBridgeCommand( - logger: Logger, -): { command: string; baseArgs: string[] } | null { - try { - const decision = resolveVoiceMcpBridgeFromRuntime({ - bootstrapModuleUrl: import.meta.url, - execPath: process.execPath, - explicitScriptPath: process.env.PASEO_MCP_STDIO_SOCKET_BRIDGE_SCRIPT, - }); - logger.info( - { - source: decision.source, - command: decision.resolved.command, - baseArgs: decision.resolved.baseArgs, - }, - "Resolved voice MCP bridge command", - ); - return decision.resolved; - } catch (err) { - logger.warn({ err }, "Voice MCP bridge script not available — voice MCP via stdio disabled"); +function formatHostForHttpUrl(host: string): string { + return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; +} + +function createAgentMcpBaseUrl(listenTarget: ListenTarget | null): string | null { + if (!listenTarget || listenTarget.type !== "tcp") { return null; } + return new URL( + "/mcp/agents", + `http://${formatHostForHttpUrl(listenTarget.host)}:${listenTarget.port}`, + ).toString(); } export type PaseoOpenAIConfig = OpenAiSpeechProviderConfig; @@ -171,6 +159,7 @@ export type PaseoDaemonConfig = { corsAllowedOrigins: string[]; allowedHosts?: AllowedHostsConfig; mcpEnabled?: boolean; + mcpInjectIntoAgents?: boolean; staticDir: string; mcpDebug: boolean; agentClients: Partial>; @@ -360,6 +349,9 @@ export async function createPaseoDaemon( registry: agentStorage, logger, }); + const providerRegistry = buildProviderRegistry(logger, { + runtimeSettings: config.agentProviderSettings, + }); const terminalManager = createTerminalManager(); @@ -410,31 +402,9 @@ export async function createPaseoDaemon( ); logger.info({ elapsed: elapsed() }, "Preparing voice and MCP runtime"); let wsServer: VoiceAssistantWebSocketServer | null = null; - let voiceMcpBridgeManager: VoiceMcpSocketBridgeManager | null = null; - - // Create in-memory transport for Session's Agent MCP client (voice assistant tools) - const createInMemoryAgentMcpTransport = async (): Promise => { - const agentMcpServer = await createAgentMcpServer({ - agentManager, - agentStorage, - terminalManager, - paseoHome: config.paseoHome, - enableVoiceTools: false, - resolveSpeakHandler: (callerAgentId) => - wsServer?.resolveVoiceSpeakHandler(callerAgentId) ?? null, - resolveCallerContext: (callerAgentId) => - wsServer?.resolveVoiceCallerContext(callerAgentId) ?? null, - logger, - }); - - const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); - - await agentMcpServer.connect(serverTransport); - - return clientTransport; - }; const mcpEnabled = config.mcpEnabled ?? true; + let agentMcpBaseUrl: string | null = null; if (mcpEnabled) { const agentMcpRoute = "/mcp/agents"; const agentMcpTransports: AgentMcpTransportMap = new Map(); @@ -444,6 +414,8 @@ export async function createPaseoDaemon( agentManager, agentStorage, terminalManager, + scheduleService, + providerRegistry, paseoHome: config.paseoHome, callerAgentId, enableVoiceTools: false, @@ -554,25 +526,6 @@ export async function createPaseoDaemon( logger.info("Agent MCP HTTP endpoint disabled"); } - const voiceMcpSocketDir = path.join(config.paseoHome, "runtime", "voice-mcp"); - const voiceMcpBridgeCommand = resolveVoiceMcpBridgeCommand(logger); - voiceMcpBridgeManager = createVoiceMcpSocketBridgeManager({ - runtimeDir: voiceMcpSocketDir, - logger, - createAgentMcpServerForCaller: async (callerAgentId) => { - return createAgentMcpServer({ - agentManager, - agentStorage, - terminalManager, - paseoHome: config.paseoHome, - callerAgentId, - voiceOnly: true, - resolveSpeakHandler: (agentId) => wsServer?.resolveVoiceSpeakHandler(agentId) ?? null, - resolveCallerContext: (agentId) => wsServer?.resolveVoiceCallerContext(agentId) ?? null, - logger, - }); - }, - }); const speechService = createSpeechService({ logger, openaiConfig: config.openai, @@ -580,55 +533,6 @@ export async function createPaseoDaemon( }); logger.info({ elapsed: elapsed() }, "Speech service created"); - wsServer = new VoiceAssistantWebSocketServer( - httpServer, - logger, - serverId, - agentManager, - agentStorage, - downloadTokenStore, - config.paseoHome, - createInMemoryAgentMcpTransport, - { allowedOrigins, allowedHosts: config.allowedHosts }, - speechService, - terminalManager, - { - voiceAgentMcpStdio: voiceMcpBridgeCommand - ? { - command: voiceMcpBridgeCommand.command, - baseArgs: [...voiceMcpBridgeCommand.baseArgs], - env: { - ELECTRON_RUN_AS_NODE: "1", - PASEO_HOME: config.paseoHome, - }, - } - : null, - ensureVoiceMcpSocketForAgent: (agentId) => - voiceMcpBridgeManager?.ensureBridgeForCaller(agentId) ?? - Promise.reject(new Error("Voice MCP bridge manager is not initialized")), - removeVoiceMcpSocketForAgent: (agentId) => - voiceMcpBridgeManager?.removeBridgeForCaller(agentId) ?? Promise.resolve(), - }, - { - finalTimeoutMs: config.dictationFinalTimeoutMs, - }, - config.agentProviderSettings, - daemonVersion, - (intent) => { - try { - config.onLifecycleIntent?.(intent); - } catch (error) { - logger.error({ err: error, intent }, "Failed to handle daemon lifecycle intent"); - } - }, - projectRegistry, - workspaceRegistry, - chatService, - loopService, - scheduleService, - checkoutDiffManager, - ); - logger.info({ elapsed: elapsed() }, "Bootstrap complete, ready to start listening"); const start = async () => { @@ -642,6 +546,9 @@ export async function createPaseoDaemon( httpServer.off("error", onError); const logAndResolve = async () => { boundListenTarget = resolveBoundListenTarget(listenTarget, httpServer); + const mcpBaseUrl = mcpEnabled ? createAgentMcpBaseUrl(boundListenTarget) : null; + agentMcpBaseUrl = config.mcpInjectIntoAgents === false ? null : mcpBaseUrl; + agentManager.setMcpBaseUrl(agentMcpBaseUrl); const relayEnabled = config.relayEnabled ?? true; const relayEndpoint = config.relayEndpoint ?? "relay.paseo.sh:443"; const relayPublicEndpoint = config.relayPublicEndpoint ?? relayEndpoint; @@ -660,9 +567,41 @@ export async function createPaseoDaemon( logger.info( { path: boundListenTarget.path, elapsed: elapsed() }, `Server listening on ${boundListenTarget.path}`, - ); + ); } + wsServer = new VoiceAssistantWebSocketServer( + httpServer, + logger, + serverId, + agentManager, + agentStorage, + downloadTokenStore, + config.paseoHome, + mcpBaseUrl, + { allowedOrigins, allowedHosts: config.allowedHosts }, + speechService, + terminalManager, + { + finalTimeoutMs: config.dictationFinalTimeoutMs, + }, + config.agentProviderSettings, + daemonVersion, + (intent) => { + try { + config.onLifecycleIntent?.(intent); + } catch (error) { + logger.error({ err: error, intent }, "Failed to handle daemon lifecycle intent"); + } + }, + projectRegistry, + workspaceRegistry, + chatService, + loopService, + scheduleService, + checkoutDiffManager, + ); + if (typeof process.send === "function" && process.env.PASEO_SUPERVISED === "1") { process.send({ type: "paseo:ready", @@ -681,7 +620,7 @@ export async function createPaseoDaemon( }); const url = encodeOfferToFragmentUrl({ offer, appBaseUrl }); - logger.info({ url }, "pairing_offer"); + logger.info("pairing_offer"); relayTransport?.stop().catch(() => undefined); relayTransport = startRelayTransport({ @@ -734,9 +673,6 @@ export async function createPaseoDaemon( if (wsServer) { await wsServer.close(); } - if (voiceMcpBridgeManager) { - await voiceMcpBridgeManager.stop().catch(() => undefined); - } await new Promise((resolve) => { httpServer.close(() => resolve()); }); diff --git a/packages/server/src/server/relay-transport.ts b/packages/server/src/server/relay-transport.ts index 4ee2fe7c0..4e6059200 100644 --- a/packages/server/src/server/relay-transport.ts +++ b/packages/server/src/server/relay-transport.ts @@ -149,7 +149,7 @@ export function startRelayTransport({ clearTimeout(controlReadyTimeout); controlReadyTimeout = null; } - relayLogger.info({ url, connectionId }, "relay_control_connected"); + relayLogger.info({ connectionId }, "relay_control_connected"); }; socket.on("open", () => { @@ -203,7 +203,7 @@ export function startRelayTransport({ try { socket.send(JSON.stringify({ type: "ping", ts: now })); } catch (error) { - relayLogger.warn({ err: error, url, connectionId }, "relay_control_ping_send_failed"); + relayLogger.warn({ err: error, connectionId }, "relay_control_ping_send_failed"); try { socket.terminate(); } catch { @@ -214,14 +214,14 @@ export function startRelayTransport({ try { socket.send(JSON.stringify({ type: "ping", ts: Date.now() })); } catch (error) { - relayLogger.warn({ err: error, url, connectionId }, "relay_control_ping_send_failed"); + relayLogger.warn({ err: error, connectionId }, "relay_control_ping_send_failed"); try { socket.terminate(); } catch { // ignore } } - relayLogger.debug({ url, connectionId }, "relay_control_open_waiting_for_ready"); + relayLogger.debug({ connectionId }, "relay_control_open_waiting_for_ready"); }); socket.on("close", (code, reason) => { @@ -244,7 +244,7 @@ export function startRelayTransport({ socket.on("error", (err) => { if (controlWs !== socket) return; - relayLogger.warn({ err, url, connectionId }, "relay_error"); + relayLogger.warn({ err, connectionId }, "relay_error"); // close event will schedule reconnect }); @@ -319,7 +319,7 @@ export function startRelayTransport({ const openTimeout = setTimeout(() => { if (stopped) return; if (socket.readyState === WebSocket.OPEN) return; - relayLogger.warn({ url, connectionId }, "relay_data_open_timeout_terminating"); + relayLogger.warn({ connectionId }, "relay_data_open_timeout_terminating"); try { socket.terminate(); } catch { @@ -329,7 +329,7 @@ export function startRelayTransport({ socket.on("open", () => { clearTimeout(openTimeout); - relayLogger.info({ url, connectionId }, "relay_data_connected"); + relayLogger.info({ connectionId }, "relay_data_connected"); if (attached) return; attached = true; const externalMetadata: ExternalSocketMetadata = { @@ -361,7 +361,7 @@ export function startRelayTransport({ }); socket.on("error", (err) => { - relayLogger.warn({ err, url, connectionId }, "relay_data_error"); + relayLogger.warn({ err, connectionId }, "relay_data_error"); }); };