diff --git a/canvas/src/components/tabs/chat/__tests__/a2aErrorHint.test.ts b/canvas/src/components/tabs/chat/__tests__/a2aErrorHint.test.ts index 8518829ba..0a64aa070 100644 --- a/canvas/src/components/tabs/chat/__tests__/a2aErrorHint.test.ts +++ b/canvas/src/components/tabs/chat/__tests__/a2aErrorHint.test.ts @@ -41,6 +41,19 @@ describe("inferA2AErrorHint", () => { expect(inferA2AErrorHint("RuntimeException in tool call")).toMatch(/runtime threw an exception/); }); + it("points at the Activity tab (the real in-product logs surface), not 'workspace/container logs' (internal#212)", () => { + // Pre-#212 these hints sent users to "workspace logs" / "container + // logs" — neither has a UI affordance in the canvas. Activity tab + // is the in-product surface where the full row lives. Lock the + // copy so a future refactor cannot re-introduce the dangling + // pointer. + expect(inferA2AErrorHint("Agent error: boom")).toMatch(/Activity tab/); + expect(inferA2AErrorHint("some completely novel error nobody has matched yet")).toMatch(/Activity tab/); + // And the two strings together must not regress to the old text. + expect(inferA2AErrorHint("Agent error: boom")).not.toMatch(/container logs/); + expect(inferA2AErrorHint("some novel error")).not.toMatch(/workspace logs/); + }); + it("recognises peer-unreachable cases (Activity-tab originals)", () => { expect(inferA2AErrorHint("workspace not found")).toMatch(/can't be reached/); expect(inferA2AErrorHint("not accessible")).toMatch(/can't be reached/); @@ -53,7 +66,8 @@ describe("inferA2AErrorHint", () => { it("returns a generic fallback for unrecognised text", () => { const hint = inferA2AErrorHint("some completely novel error nobody has matched yet"); - expect(hint).toMatch(/Check the workspace logs|delivery failure/); + // Fallback now sends the user to the Activity tab (post-#212). + expect(hint).toMatch(/Activity tab|delivery failure/); }); it("Claude SDK wedge wins over the more general timeout pattern", () => { diff --git a/canvas/src/components/tabs/chat/a2aErrorHint.ts b/canvas/src/components/tabs/chat/a2aErrorHint.ts index e29e643a6..db9a7069c 100644 --- a/canvas/src/components/tabs/chat/a2aErrorHint.ts +++ b/canvas/src/components/tabs/chat/a2aErrorHint.ts @@ -38,7 +38,11 @@ export function inferA2AErrorHint(detail: string): string { return "The connection to the remote agent dropped before a reply arrived. Usually a transient network blip — retry once. If it repeats, the remote container may have crashed mid-request; check its logs."; } if (t.includes("agent error") || t.includes("exception")) { - return "The remote agent's runtime threw an exception. Check the workspace's container logs for the traceback. Restart usually clears transient runtime crashes."; + // internal#212 closeout: end users have no "container logs" surface + // in the canvas; the Activity tab IS the user-visible logs surface + // (full row carries request/response body + error_detail). Point + // there so the hint is actionable from inside the product. + return "The remote agent's runtime threw an exception. Open the Activity tab for the full row (request body, response, error_detail) — Restart usually clears transient runtime crashes."; } if ( t.includes("not found") || @@ -50,5 +54,9 @@ export function inferA2AErrorHint(detail: string): string { if (detail === "") { return "The remote agent returned no error detail (the underlying httpx exception had an empty message — typically a connection-reset or silent timeout). A workspace restart is the safe first move."; } - return "The remote agent reported a delivery failure. Check the workspace logs or try restarting."; + // internal#212 closeout: "workspace logs" pointed at a tab that does + // not exist — Activity tab is the in-product logs surface. Keep the + // hint generic enough for the unrecognised-detail fallback but point + // the user at a real affordance. + return "The remote agent reported a delivery failure. Open the Activity tab for the full row, or try restarting the workspace."; }