From b37542107a12d61ab80c917731b29e161df03070 Mon Sep 17 00:00:00 2001 From: Blomios Date: Wed, 5 Aug 2026 12:11:38 +0200 Subject: [PATCH] =?UTF-8?q?fix(chat):=20fallback=20vers=20un=20nouveau=20l?= =?UTF-8?q?ancement=20quand=20la=20session=20structur=C3=A9e=20reattach=20?= =?UTF-8?q?est=20morte=20(#147)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reattachAgentChat pouvait échouer avec NOT_FOUND (session structurée fermée ou jamais vivante) et laisser fuiter l'erreur brute jusqu'à l'utilisateur ("not found: structured session ... not found") au lieu de relancer une session fraîche. Le fallback ne s'applique qu'au code NOT_FOUND ; toute autre erreur de reattach continue de remonter telle quelle. QA vert 2026-08-05 : npx vitest run CustomAgentChatView.test.tsx, npx vitest run agents.test.tsx CustomAgentChatView.test.tsx, npm run typecheck. Co-Authored-By: Claude Opus 4.8 --- .../agents/CustomAgentChatView.test.tsx | 96 +++++++++++++++++++ .../features/agents/CustomAgentChatView.tsx | 17 +++- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/agents/CustomAgentChatView.test.tsx b/frontend/src/features/agents/CustomAgentChatView.test.tsx index 56815ce..77e42f8 100644 --- a/frontend/src/features/agents/CustomAgentChatView.test.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.test.tsx @@ -94,4 +94,100 @@ describe("CustomAgentChatView", () => { expect.any(Function), ); }); + + it("falls back to a fresh launch when reattach reports NOT_FOUND", async () => { + const agent = { + launchAgentChat: vi.fn(async () => ({ + sessionId: "chat-session-2", + assignedConversationId: "conversation-2", + })), + reattachAgentChat: vi + .fn() + .mockRejectedValueOnce({ code: "NOT_FOUND", message: "structured session gone" }) + .mockImplementationOnce(async (sessionId: string) => ({ + sessionId, + scrollback: [], + })), + sendAgentChat: vi.fn(() => new Promise(() => {})), + cancelAgentChat: vi.fn(async () => {}), + closeAgentChat: vi.fn(async () => {}), + }; + const onSessionId = vi.fn(); + + render( + null) }, + } as unknown as Gateways} + > + + , + ); + + await waitFor(() => + expect(agent.launchAgentChat).toHaveBeenCalledWith("project-1", "agent-1", { + cwd: "/repo", + rows: 24, + cols: 80, + conversationId: "conversation-1", + nodeId: "node-1", + }), + ); + await waitFor(() => + expect(onSessionId).toHaveBeenCalledWith("chat-session-2"), + ); + expect(screen.queryByText(/structured session gone/)).toBeNull(); + }); + + it("surfaces non-NOT_FOUND reattach errors instead of launching a new session", async () => { + const agent = { + launchAgentChat: vi.fn(), + reattachAgentChat: vi.fn().mockRejectedValueOnce({ + code: "PROCESS", + message: "structured session failed to attach", + }), + sendAgentChat: vi.fn(() => new Promise(() => {})), + cancelAgentChat: vi.fn(async () => {}), + closeAgentChat: vi.fn(async () => {}), + }; + + render( + null) }, + } as unknown as Gateways} + > + + , + ); + + expect((await screen.findByRole("alert")).textContent).toContain( + "structured session failed to attach", + ); + expect(agent.launchAgentChat).not.toHaveBeenCalled(); + }); }); diff --git a/frontend/src/features/agents/CustomAgentChatView.tsx b/frontend/src/features/agents/CustomAgentChatView.tsx index 7d79681..ad8e60e 100644 --- a/frontend/src/features/agents/CustomAgentChatView.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.tsx @@ -154,11 +154,18 @@ export function CustomAgentChatView({ setError(null); try { if (sessionId) { - const reattached = await agent.reattachAgentChat!(sessionId, receive); - if (cancelled) return; - setCurrentSession(reattached.sessionId); - setTurns(reattached.scrollback.reduce(foldChunk, [] as ChatTurn[])); - return; + try { + const reattached = await agent.reattachAgentChat!(sessionId, receive); + if (cancelled) return; + setCurrentSession(reattached.sessionId); + setTurns(reattached.scrollback.reduce(foldChunk, [] as ChatTurn[])); + return; + } catch (e) { + // The backend contract for `reattach_agent_chat` (NOT_FOUND) is that + // the session is gone (closed/never live) and the caller falls back + // to a fresh launch — any other error still surfaces to the user. + if ((e as GatewayError)?.code !== "NOT_FOUND") throw e; + } } const launched = await agent.launchAgentChat!(projectId, agentId, { cwd,