From ae4678bca88359b7996cf082f434cd22d3d61274 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 28 Nov 2025 17:44:07 +0000 Subject: [PATCH] fix: eliminate duplicate WebSocket messages during agent history priming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Skip dispatching individual agent_stream events when replaying history (the snapshot is sent after priming anyway, so individual events were wasted) - Add WebSocket message logging on client (type, size, id) for debugging - Change default daemon URL from dev to localhost - Clean up unused imports in session.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/contexts/daemon-registry-context.tsx | 2 +- packages/app/src/hooks/use-websocket.ts | 22 ++++++++++++++----- .../server/src/server/agent/agent-manager.ts | 6 ++++- packages/server/src/server/session.ts | 2 +- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/app/src/contexts/daemon-registry-context.tsx b/packages/app/src/contexts/daemon-registry-context.tsx index b011b74b0..c94d50200 100644 --- a/packages/app/src/contexts/daemon-registry-context.tsx +++ b/packages/app/src/contexts/daemon-registry-context.tsx @@ -7,7 +7,7 @@ const REGISTRY_STORAGE_KEY = "@paseo:daemon-registry"; const LEGACY_SETTINGS_KEY = "@paseo:settings"; const FALLBACK_DAEMON_URL = "ws://localhost:6767/ws"; const DEFAULT_DAEMONS: Array<{ label: string; wsUrl: string; restUrl?: string | null }> = [ - { label: "dev", wsUrl: "ws://dev:6767/ws" }, + { label: "localhost", wsUrl: "ws://localhost:6767/ws" }, { label: "macbook", wsUrl: "ws://mohameds-macbook-pro:6767/ws" }, ]; const DAEMON_REGISTRY_QUERY_KEY = ["daemon-registry"]; diff --git a/packages/app/src/hooks/use-websocket.ts b/packages/app/src/hooks/use-websocket.ts index 7e83e059a..c1a53218f 100644 --- a/packages/app/src/hooks/use-websocket.ts +++ b/packages/app/src/hooks/use-websocket.ts @@ -123,12 +123,17 @@ export function useWebSocket(url: string, conversationId?: string | null): UseWe ws.onmessage = (event) => { try { - const wsMessage: WSOutboundMessage = JSON.parse(event.data); + const rawData = event.data; + const size = typeof rawData === "string" ? rawData.length : 0; + const wsMessage: WSOutboundMessage = JSON.parse(rawData); // Only session messages trigger handlers if (wsMessage.type === "session") { const sessionMessage = wsMessage.message; - console.log(`[WS] Received session message type: ${sessionMessage.type}`); + const id = (sessionMessage as { requestId?: string }).requestId ?? + (sessionMessage as { agentId?: string }).agentId ?? + (sessionMessage as { payload?: { agentId?: string } }).payload?.agentId; + console.log(`[WS] ← ${sessionMessage.type}`, { size, id: id ?? "-" }); // Track conversation ID when loaded if (sessionMessage.type === "conversation_loaded") { @@ -147,8 +152,8 @@ export function useWebSocket(url: string, conversationId?: string | null): UseWe }); } } else { - // pong - just log - console.log(`[WS] Received ${wsMessage.type}`); + // pong + console.log(`[WS] ← ${wsMessage.type}`, { size }); } } catch (err) { console.error("[WS] Failed to parse message:", err); @@ -200,7 +205,14 @@ export function useWebSocket(url: string, conversationId?: string | null): UseWe const send = useCallback((message: WSInboundMessage) => { if (wsRef.current?.readyState === WebSocket.OPEN) { - wsRef.current.send(JSON.stringify(message)); + const payload = JSON.stringify(message); + const type = message.type === "session" ? message.message.type : message.type; + const id = message.type === "session" + ? (message.message as { requestId?: string; agentId?: string }).requestId ?? + (message.message as { agentId?: string }).agentId + : undefined; + console.log(`[WS] → ${type}`, { size: payload.length, id: id ?? "-" }); + wsRef.current.send(payload); } else { console.warn("[WS] Cannot send message - not connected"); } diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index 7b32bad6c..4c39e4989 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -707,7 +707,11 @@ export class AgentManager { break; } - this.dispatchStream(agent.id, event); + // Skip dispatching individual stream events during history replay. + // The caller will send a batched agent_stream_snapshot after priming. + if (!options?.fromHistory) { + this.dispatchStream(agent.id, event); + } } private recordTimeline(agent: ManagedAgent, item: AgentTimelineItem): void { diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index 99b95a4cf..0d130430f 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -1,6 +1,6 @@ import { v4 as uuidv4 } from "uuid"; import { readFile, mkdir, writeFile } from "fs/promises"; -import { exec, spawn } from "child_process"; +import { exec } from "child_process"; import { promisify, inspect } from "util"; import { join } from "path"; import invariant from "tiny-invariant";