fix(canvas): make AgentCommsPanel load failures observable

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/<id>/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 <err>` 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) <noreply@anthropic.com>
This commit is contained in:
Hongming Wang 2026-04-24 23:27:50 -07:00
parent 28911ded40
commit 54f7c75c81

View File

@ -151,22 +151,45 @@ export function AgentCommsPanel({ workspaceId }: { workspaceId: string }) {
setLoading(true);
api.get<ActivityEntry[]>(`/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