From a4f8cbd978890f97fbecb96cee9e561d74f67adc Mon Sep 17 00:00:00 2001 From: Jiang Bohan Date: Wed, 4 Feb 2026 13:26:22 +0800 Subject: [PATCH] feat(desktop): improve profile IPC and error handling - Add reloadSystemPrompt call after profile updates - Add error handling and logging for Hub initialization - Clean up debug logs from agent IPC handlers Co-Authored-By: Claude Opus 4.5 --- apps/desktop/electron/ipc/agent.ts | 3 -- apps/desktop/electron/ipc/hub.ts | 26 +++++++++++----- apps/desktop/electron/ipc/profile.ts | 11 +++++++ apps/desktop/electron/main.ts | 46 ++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 10 deletions(-) diff --git a/apps/desktop/electron/ipc/agent.ts b/apps/desktop/electron/ipc/agent.ts index 73ca65b5..a358b88a 100644 --- a/apps/desktop/electron/ipc/agent.ts +++ b/apps/desktop/electron/ipc/agent.ts @@ -125,8 +125,6 @@ export function registerAgentIpcHandlers(): void { * Persists the change to profile config and reloads tools. */ ipcMain.handle('tools:toggle', async (_event, toolName: string) => { - console.log(`[IPC] tools:toggle called for: ${toolName}`) - const agent = getDefaultAgent() if (!agent) { return { error: 'No agent available' } @@ -145,7 +143,6 @@ export function registerAgentIpcHandlers(): void { // Get updated status const newActiveTools = agent.getActiveTools() const isNowEnabled = newActiveTools.includes(toolName) - console.log(`[IPC] Tool ${toolName} toggled: ${isCurrentlyEnabled} -> ${isNowEnabled}`) return { name: toolName, diff --git a/apps/desktop/electron/ipc/hub.ts b/apps/desktop/electron/ipc/hub.ts index 6992d7ca..6896375f 100644 --- a/apps/desktop/electron/ipc/hub.ts +++ b/apps/desktop/electron/ipc/hub.ts @@ -13,31 +13,43 @@ import type { AsyncAgent } from '../../../../src/agent/async-agent.js' let hub: Hub | null = null let defaultAgentId: string | null = null +/** + * Safe log function that catches EPIPE errors. + * Electron main process stdout can be closed unexpectedly. + */ +function safeLog(...args: unknown[]): void { + try { + console.log(...args) + } catch { + // Ignore EPIPE errors when stdout is closed + } +} + /** * Initialize Hub on app startup. * Creates Hub and a default Agent automatically. */ export async function initializeHub(): Promise { if (hub) { - console.log('[Desktop] Hub already initialized') + safeLog('[Desktop] Hub already initialized') return } const gatewayUrl = process.env['GATEWAY_URL'] ?? 'http://localhost:3000' - console.log(`[Desktop] Initializing Hub, connecting to Gateway: ${gatewayUrl}`) + safeLog(`[Desktop] Initializing Hub, connecting to Gateway: ${gatewayUrl}`) hub = new Hub(gatewayUrl) // Create default agent if none exists const agents = hub.listAgents() if (agents.length === 0) { - console.log('[Desktop] Creating default agent...') + safeLog('[Desktop] Creating default agent...') const agent = hub.createAgent() defaultAgentId = agent.sessionId - console.log(`[Desktop] Default agent created: ${defaultAgentId}`) + safeLog(`[Desktop] Default agent created: ${defaultAgentId}`) } else { defaultAgentId = agents[0] - console.log(`[Desktop] Using existing agent: ${defaultAgentId}`) + safeLog(`[Desktop] Using existing agent: ${defaultAgentId}`) } } @@ -47,7 +59,7 @@ export async function initializeHub(): Promise { function getHub(): Hub { if (!hub) { const gatewayUrl = process.env['GATEWAY_URL'] ?? 'http://localhost:3000' - console.log(`[Desktop] Creating Hub, connecting to Gateway: ${gatewayUrl}`) + safeLog(`[Desktop] Creating Hub, connecting to Gateway: ${gatewayUrl}`) hub = new Hub(gatewayUrl) } return hub @@ -228,7 +240,7 @@ export function registerHubIpcHandlers(): void { */ export function cleanupHub(): void { if (hub) { - console.log('[Desktop] Shutting down Hub') + safeLog('[Desktop] Shutting down Hub') hub.shutdown() hub = null } diff --git a/apps/desktop/electron/ipc/profile.ts b/apps/desktop/electron/ipc/profile.ts index bfb924da..ed30130f 100644 --- a/apps/desktop/electron/ipc/profile.ts +++ b/apps/desktop/electron/ipc/profile.ts @@ -71,10 +71,21 @@ export function registerProfileIpcHandlers(): void { ipcMain.handle('profile:updateUser', async (_event, content: string) => { const agent = getDefaultAgent() if (!agent) { + console.error('[Profile IPC] No agent available for updateUser') return { error: 'No agent available' } } + console.log('[Profile IPC] Updating user content:', content.substring(0, 50) + '...') agent.setUserContent(content) + + // Reload system prompt to apply changes immediately + console.log('[Profile IPC] Reloading system prompt...') + agent.reloadSystemPrompt() + + // Verify the change + const newUserContent = agent.getUserContent() + console.log('[Profile IPC] New user content:', newUserContent?.substring(0, 50) + '...') + return { ok: true } }) } diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index a360a499..fb6ff0a9 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -1,3 +1,49 @@ +// Patch console methods to handle EPIPE errors in Electron main process +// This MUST be done before any other imports that might use console +// EPIPE happens when stdout/stderr pipes are closed unexpectedly +const originalConsoleLog = console.log.bind(console) +const originalConsoleError = console.error.bind(console) +const originalConsoleWarn = console.warn.bind(console) + +const safeLog = (...args: unknown[]) => { + try { + originalConsoleLog(...args) + } catch { + // Ignore EPIPE errors silently + } +} + +const safeError = (...args: unknown[]) => { + try { + originalConsoleError(...args) + } catch { + // Ignore EPIPE errors silently + } +} + +const safeWarn = (...args: unknown[]) => { + try { + originalConsoleWarn(...args) + } catch { + // Ignore EPIPE errors silently + } +} + +// Override global console +console.log = safeLog +console.error = safeError +console.warn = safeWarn + +// Also handle process stdout/stderr EPIPE errors +process.stdout?.on?.('error', (err: NodeJS.ErrnoException) => { + if (err.code === 'EPIPE') return // Ignore + throw err +}) +process.stderr?.on?.('error', (err: NodeJS.ErrnoException) => { + if (err.code === 'EPIPE') return // Ignore + throw err +}) + import { app, BrowserWindow } from 'electron' import { fileURLToPath } from 'node:url' import path from 'node:path'