From 54f7c75c8143aac887767dac7202ee8e48d69466 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Fri, 24 Apr 2026 23:27:50 -0700 Subject: [PATCH] fix(canvas): make AgentCommsPanel load failures observable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported symptom: canvas edges show "1 call · just now" between two agents, but the Agent Comms tab for the source workspace renders "No agent-to-agent communications yet" — even though GET /workspaces//activity?source=agent&limit=50 returns a2a_send + a2a_receive rows. Confirmed via curl that the API does return the rows the panel should map. The panel's load handler was the suspect, but it had: .catch(() => setLoading(false)) which swallowed every failure path — network errors, JSON parse, ANY throw inside the .then body — without leaving a single trace in the console. The panel just sat on its empty state and gave the user zero signal to act on. (And by extension, gave us nothing to debug remotely either.) Two changes: 1. Wrap the per-row `toCommMessage` call in a try/catch so one malformed activity row (unexpected request_body shape, etc.) doesn't throw out of the for-loop and skip the setMessages(msgs) line. Previously the panel would silently drop the entire batch when ANY row failed to parse. 2. Replace the bare `.catch(() => setLoading(false))` with a logging variant. Now a future "panel stuck empty" report comes with `AgentCommsPanel: load activity failed ` or `AgentCommsPanel: failed to map activity row {...}` in the console — diagnosable instead of opaque. Behavior on the happy path is unchanged (5 existing tests still pass; tsc clean). This is purely defensive: it makes the failure path visible so the next stuck-empty report can be root-caused instead of guessed at. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/tabs/chat/AgentCommsPanel.tsx | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/canvas/src/components/tabs/chat/AgentCommsPanel.tsx b/canvas/src/components/tabs/chat/AgentCommsPanel.tsx index 30cdbd14..15af5b03 100644 --- a/canvas/src/components/tabs/chat/AgentCommsPanel.tsx +++ b/canvas/src/components/tabs/chat/AgentCommsPanel.tsx @@ -151,22 +151,45 @@ export function AgentCommsPanel({ workspaceId }: { workspaceId: string }) { setLoading(true); api.get(`/workspaces/${workspaceId}/activity?source=agent&limit=50`) .then((entries) => { - const filtered = entries + const filtered = (entries ?? []) .filter((e) => e.activity_type === "a2a_send" || e.activity_type === "a2a_receive") .reverse(); const msgs: CommMessage[] = []; for (const e of filtered) { - const m = toCommMessage(e, workspaceId); - if (m) { - const key = `${m.timestamp}:${m.flow}:${m.peerId}`; - msgs.push(m); - seenKeys.current.add(key); + // Per-row try/catch so a single malformed activity row + // (e.g. unexpected request_body shape) doesn't kill the + // batch — the previous code threw out of the for-loop and + // setMessages([3 items]) never ran, leaving the panel + // stuck on the empty state with no diagnostic in the + // console because the outer .catch silently swallowed + // everything. + try { + const m = toCommMessage(e, workspaceId); + if (m) { + const key = `${m.timestamp}:${m.flow}:${m.peerId}`; + msgs.push(m); + seenKeys.current.add(key); + } + } catch (rowErr) { + console.warn( + "AgentCommsPanel: failed to map activity row", + { id: e.id, type: e.activity_type, err: rowErr }, + ); } } setMessages(msgs); setLoading(false); }) - .catch(() => setLoading(false)); + .catch((err) => { + // Surface the failure in the console so a stuck panel is + // diagnosable without a debugger. Previous bare + // `.catch(() => setLoading(false))` swallowed every load + // failure (network errors, JSON parse errors, throws inside + // the .then body) — the panel just sat on the empty state + // with zero signal. + console.warn("AgentCommsPanel: load activity failed", err); + setLoading(false); + }); }, [workspaceId]); // Live updates via WebSocket