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.
This commit is contained in:
@@ -6,7 +6,6 @@ import { randomUUID } from "node:crypto";
|
|||||||
import { hostname as getHostname } from "node:os";
|
import { hostname as getHostname } from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||||
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
||||||
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import type { Logger } from "pino";
|
import type { Logger } from "pino";
|
||||||
|
|
||||||
@@ -96,7 +95,11 @@ import { AgentManager } from "./agent/agent-manager.js";
|
|||||||
import { AgentStorage } from "./agent/agent-storage.js";
|
import { AgentStorage } from "./agent/agent-storage.js";
|
||||||
import { attachAgentStoragePersistence } from "./persistence-hooks.js";
|
import { attachAgentStoragePersistence } from "./persistence-hooks.js";
|
||||||
import { createAgentMcpServer } from "./agent/mcp-server.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 { bootstrapWorkspaceRegistries } from "./workspace-registry-bootstrap.js";
|
||||||
import { FileBackedProjectRegistry, FileBackedWorkspaceRegistry } from "./workspace-registry.js";
|
import { FileBackedProjectRegistry, FileBackedWorkspaceRegistry } from "./workspace-registry.js";
|
||||||
import { FileBackedChatService } from "./chat/chat-service.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 { AgentClient, AgentProvider } from "./agent/agent-sdk-types.js";
|
||||||
import type { AgentProviderRuntimeSettingsMap } from "./agent/provider-launch-config.js";
|
import type { AgentProviderRuntimeSettingsMap } from "./agent/provider-launch-config.js";
|
||||||
import { isHostAllowed, type AllowedHostsConfig } from "./allowed-hosts.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<string, StreamableHTTPServerTransport>;
|
type AgentMcpTransportMap = Map<string, StreamableHTTPServerTransport>;
|
||||||
|
|
||||||
function resolveVoiceMcpBridgeCommand(
|
function formatHostForHttpUrl(host: string): string {
|
||||||
logger: Logger,
|
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
||||||
): { command: string; baseArgs: string[] } | null {
|
}
|
||||||
try {
|
|
||||||
const decision = resolveVoiceMcpBridgeFromRuntime({
|
function createAgentMcpBaseUrl(listenTarget: ListenTarget | null): string | null {
|
||||||
bootstrapModuleUrl: import.meta.url,
|
if (!listenTarget || listenTarget.type !== "tcp") {
|
||||||
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");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return new URL(
|
||||||
|
"/mcp/agents",
|
||||||
|
`http://${formatHostForHttpUrl(listenTarget.host)}:${listenTarget.port}`,
|
||||||
|
).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaseoOpenAIConfig = OpenAiSpeechProviderConfig;
|
export type PaseoOpenAIConfig = OpenAiSpeechProviderConfig;
|
||||||
@@ -171,6 +159,7 @@ export type PaseoDaemonConfig = {
|
|||||||
corsAllowedOrigins: string[];
|
corsAllowedOrigins: string[];
|
||||||
allowedHosts?: AllowedHostsConfig;
|
allowedHosts?: AllowedHostsConfig;
|
||||||
mcpEnabled?: boolean;
|
mcpEnabled?: boolean;
|
||||||
|
mcpInjectIntoAgents?: boolean;
|
||||||
staticDir: string;
|
staticDir: string;
|
||||||
mcpDebug: boolean;
|
mcpDebug: boolean;
|
||||||
agentClients: Partial<Record<AgentProvider, AgentClient>>;
|
agentClients: Partial<Record<AgentProvider, AgentClient>>;
|
||||||
@@ -360,6 +349,9 @@ export async function createPaseoDaemon(
|
|||||||
registry: agentStorage,
|
registry: agentStorage,
|
||||||
logger,
|
logger,
|
||||||
});
|
});
|
||||||
|
const providerRegistry = buildProviderRegistry(logger, {
|
||||||
|
runtimeSettings: config.agentProviderSettings,
|
||||||
|
});
|
||||||
|
|
||||||
const terminalManager = createTerminalManager();
|
const terminalManager = createTerminalManager();
|
||||||
|
|
||||||
@@ -410,31 +402,9 @@ export async function createPaseoDaemon(
|
|||||||
);
|
);
|
||||||
logger.info({ elapsed: elapsed() }, "Preparing voice and MCP runtime");
|
logger.info({ elapsed: elapsed() }, "Preparing voice and MCP runtime");
|
||||||
let wsServer: VoiceAssistantWebSocketServer | null = null;
|
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<InMemoryTransport> => {
|
|
||||||
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;
|
const mcpEnabled = config.mcpEnabled ?? true;
|
||||||
|
let agentMcpBaseUrl: string | null = null;
|
||||||
if (mcpEnabled) {
|
if (mcpEnabled) {
|
||||||
const agentMcpRoute = "/mcp/agents";
|
const agentMcpRoute = "/mcp/agents";
|
||||||
const agentMcpTransports: AgentMcpTransportMap = new Map();
|
const agentMcpTransports: AgentMcpTransportMap = new Map();
|
||||||
@@ -444,6 +414,8 @@ export async function createPaseoDaemon(
|
|||||||
agentManager,
|
agentManager,
|
||||||
agentStorage,
|
agentStorage,
|
||||||
terminalManager,
|
terminalManager,
|
||||||
|
scheduleService,
|
||||||
|
providerRegistry,
|
||||||
paseoHome: config.paseoHome,
|
paseoHome: config.paseoHome,
|
||||||
callerAgentId,
|
callerAgentId,
|
||||||
enableVoiceTools: false,
|
enableVoiceTools: false,
|
||||||
@@ -554,25 +526,6 @@ export async function createPaseoDaemon(
|
|||||||
logger.info("Agent MCP HTTP endpoint disabled");
|
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({
|
const speechService = createSpeechService({
|
||||||
logger,
|
logger,
|
||||||
openaiConfig: config.openai,
|
openaiConfig: config.openai,
|
||||||
@@ -580,55 +533,6 @@ export async function createPaseoDaemon(
|
|||||||
});
|
});
|
||||||
logger.info({ elapsed: elapsed() }, "Speech service created");
|
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");
|
logger.info({ elapsed: elapsed() }, "Bootstrap complete, ready to start listening");
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
@@ -642,6 +546,9 @@ export async function createPaseoDaemon(
|
|||||||
httpServer.off("error", onError);
|
httpServer.off("error", onError);
|
||||||
const logAndResolve = async () => {
|
const logAndResolve = async () => {
|
||||||
boundListenTarget = resolveBoundListenTarget(listenTarget, httpServer);
|
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 relayEnabled = config.relayEnabled ?? true;
|
||||||
const relayEndpoint = config.relayEndpoint ?? "relay.paseo.sh:443";
|
const relayEndpoint = config.relayEndpoint ?? "relay.paseo.sh:443";
|
||||||
const relayPublicEndpoint = config.relayPublicEndpoint ?? relayEndpoint;
|
const relayPublicEndpoint = config.relayPublicEndpoint ?? relayEndpoint;
|
||||||
@@ -660,9 +567,41 @@ export async function createPaseoDaemon(
|
|||||||
logger.info(
|
logger.info(
|
||||||
{ path: boundListenTarget.path, elapsed: elapsed() },
|
{ path: boundListenTarget.path, elapsed: elapsed() },
|
||||||
`Server listening on ${boundListenTarget.path}`,
|
`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") {
|
if (typeof process.send === "function" && process.env.PASEO_SUPERVISED === "1") {
|
||||||
process.send({
|
process.send({
|
||||||
type: "paseo:ready",
|
type: "paseo:ready",
|
||||||
@@ -681,7 +620,7 @@ export async function createPaseoDaemon(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const url = encodeOfferToFragmentUrl({ offer, appBaseUrl });
|
const url = encodeOfferToFragmentUrl({ offer, appBaseUrl });
|
||||||
logger.info({ url }, "pairing_offer");
|
logger.info("pairing_offer");
|
||||||
|
|
||||||
relayTransport?.stop().catch(() => undefined);
|
relayTransport?.stop().catch(() => undefined);
|
||||||
relayTransport = startRelayTransport({
|
relayTransport = startRelayTransport({
|
||||||
@@ -734,9 +673,6 @@ export async function createPaseoDaemon(
|
|||||||
if (wsServer) {
|
if (wsServer) {
|
||||||
await wsServer.close();
|
await wsServer.close();
|
||||||
}
|
}
|
||||||
if (voiceMcpBridgeManager) {
|
|
||||||
await voiceMcpBridgeManager.stop().catch(() => undefined);
|
|
||||||
}
|
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
httpServer.close(() => resolve());
|
httpServer.close(() => resolve());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export function startRelayTransport({
|
|||||||
clearTimeout(controlReadyTimeout);
|
clearTimeout(controlReadyTimeout);
|
||||||
controlReadyTimeout = null;
|
controlReadyTimeout = null;
|
||||||
}
|
}
|
||||||
relayLogger.info({ url, connectionId }, "relay_control_connected");
|
relayLogger.info({ connectionId }, "relay_control_connected");
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.on("open", () => {
|
socket.on("open", () => {
|
||||||
@@ -203,7 +203,7 @@ export function startRelayTransport({
|
|||||||
try {
|
try {
|
||||||
socket.send(JSON.stringify({ type: "ping", ts: now }));
|
socket.send(JSON.stringify({ type: "ping", ts: now }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
relayLogger.warn({ err: error, url, connectionId }, "relay_control_ping_send_failed");
|
relayLogger.warn({ err: error, connectionId }, "relay_control_ping_send_failed");
|
||||||
try {
|
try {
|
||||||
socket.terminate();
|
socket.terminate();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -214,14 +214,14 @@ export function startRelayTransport({
|
|||||||
try {
|
try {
|
||||||
socket.send(JSON.stringify({ type: "ping", ts: Date.now() }));
|
socket.send(JSON.stringify({ type: "ping", ts: Date.now() }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
relayLogger.warn({ err: error, url, connectionId }, "relay_control_ping_send_failed");
|
relayLogger.warn({ err: error, connectionId }, "relay_control_ping_send_failed");
|
||||||
try {
|
try {
|
||||||
socket.terminate();
|
socket.terminate();
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// 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) => {
|
socket.on("close", (code, reason) => {
|
||||||
@@ -244,7 +244,7 @@ export function startRelayTransport({
|
|||||||
|
|
||||||
socket.on("error", (err) => {
|
socket.on("error", (err) => {
|
||||||
if (controlWs !== socket) return;
|
if (controlWs !== socket) return;
|
||||||
relayLogger.warn({ err, url, connectionId }, "relay_error");
|
relayLogger.warn({ err, connectionId }, "relay_error");
|
||||||
// close event will schedule reconnect
|
// close event will schedule reconnect
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -319,7 +319,7 @@ export function startRelayTransport({
|
|||||||
const openTimeout = setTimeout(() => {
|
const openTimeout = setTimeout(() => {
|
||||||
if (stopped) return;
|
if (stopped) return;
|
||||||
if (socket.readyState === WebSocket.OPEN) 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 {
|
try {
|
||||||
socket.terminate();
|
socket.terminate();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -329,7 +329,7 @@ export function startRelayTransport({
|
|||||||
|
|
||||||
socket.on("open", () => {
|
socket.on("open", () => {
|
||||||
clearTimeout(openTimeout);
|
clearTimeout(openTimeout);
|
||||||
relayLogger.info({ url, connectionId }, "relay_data_connected");
|
relayLogger.info({ connectionId }, "relay_data_connected");
|
||||||
if (attached) return;
|
if (attached) return;
|
||||||
attached = true;
|
attached = true;
|
||||||
const externalMetadata: ExternalSocketMetadata = {
|
const externalMetadata: ExternalSocketMetadata = {
|
||||||
@@ -361,7 +361,7 @@ export function startRelayTransport({
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on("error", (err) => {
|
socket.on("error", (err) => {
|
||||||
relayLogger.warn({ err, url, connectionId }, "relay_data_error");
|
relayLogger.warn({ err, connectionId }, "relay_data_error");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user