fix(chat): handle raw structured session NOT_FOUND errors from Tauri
Elargit isNotFound() pour reconnaitre aussi la forme brute
'not found: structured session <uuid>' que Tauri peut retourner,
en plus de la forme typée {code: 'NOT_FOUND'}.
Ajoute deux tests couvrant:
- fallback sur reattach avec erreur brute
- retry prompt sur send avec erreur brute
QA: tests passés sur cette correction.
This commit is contained in:
@ -152,6 +152,65 @@ describe("CustomAgentChatView", () => {
|
||||
expect(screen.queryByText(/structured session gone/)).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back when Tauri surfaces structured session NOT_FOUND as a raw string", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(async () => ({
|
||||
sessionId: "chat-session-2",
|
||||
assignedConversationId: "conversation-2",
|
||||
})),
|
||||
reattachAgentChat: vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(
|
||||
"not found: structured session 69b57638-0ac7-4a79-8252-e0436a3265f6",
|
||||
)
|
||||
.mockImplementationOnce(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(() => new Promise<void>(() => {})),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
const onSessionId = vi.fn();
|
||||
|
||||
render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
system: { pickFile: vi.fn(async () => null) },
|
||||
} as unknown as Gateways}
|
||||
>
|
||||
<CustomAgentChatView
|
||||
projectId="project-1"
|
||||
agentId="agent-1"
|
||||
agentName="Worker"
|
||||
profile={profile}
|
||||
cwd="/repo"
|
||||
nodeId="node-1"
|
||||
sessionId="69b57638-0ac7-4a79-8252-e0436a3265f6"
|
||||
conversationId="conversation-1"
|
||||
onSessionId={onSessionId}
|
||||
onConversationId={vi.fn()}
|
||||
/>
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(agent.launchAgentChat).toHaveBeenCalledTimes(1));
|
||||
expect(agent.reattachAgentChat).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"69b57638-0ac7-4a79-8252-e0436a3265f6",
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(agent.reattachAgentChat).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"chat-session-2",
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(onSessionId).toHaveBeenCalledWith(null);
|
||||
expect(onSessionId).toHaveBeenCalledWith("chat-session-2");
|
||||
expect(screen.queryByText(/not found: structured session/)).toBeNull();
|
||||
});
|
||||
|
||||
it("does not reopen when the parent echoes a launched session id", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(async () => ({
|
||||
@ -286,6 +345,74 @@ describe("CustomAgentChatView", () => {
|
||||
expect(screen.queryByText(/not found: structured session/)).toBeNull();
|
||||
});
|
||||
|
||||
it("recovers and retries the prompt when send reports structured session NOT_FOUND as a raw string", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(async () => ({
|
||||
sessionId: "chat-session-2",
|
||||
assignedConversationId: "conversation-2",
|
||||
})),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce("not found: structured session stale-session")
|
||||
.mockImplementationOnce(async (_sessionId: string, _prompt: string, onChunk) => {
|
||||
onChunk({ kind: "final", content: "Recovered reply" });
|
||||
}),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
const onSessionId = vi.fn();
|
||||
|
||||
render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
system: { pickFile: vi.fn(async () => null) },
|
||||
} as unknown as Gateways}
|
||||
>
|
||||
<CustomAgentChatView
|
||||
projectId="project-1"
|
||||
agentId="agent-1"
|
||||
agentName="Worker"
|
||||
profile={profile}
|
||||
cwd="/repo"
|
||||
nodeId="node-1"
|
||||
sessionId="stale-session"
|
||||
conversationId="conversation-1"
|
||||
onSessionId={onSessionId}
|
||||
onConversationId={vi.fn()}
|
||||
/>
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(agent.reattachAgentChat).toHaveBeenCalledWith(
|
||||
"stale-session",
|
||||
expect.any(Function),
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
|
||||
target: { value: "please recover" },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
|
||||
|
||||
await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalledTimes(2));
|
||||
expect(agent.sendAgentChat).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"chat-session-2",
|
||||
"please recover",
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(onSessionId).toHaveBeenCalledWith(null);
|
||||
expect(onSessionId).toHaveBeenCalledWith("chat-session-2");
|
||||
expect(await screen.findByText("Recovered reply")).toBeTruthy();
|
||||
expect(screen.queryByText(/not found: structured session/)).toBeNull();
|
||||
});
|
||||
|
||||
it("treats NOT_FOUND during cancel as an already-gone session", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
|
||||
@ -54,7 +54,16 @@ function instrumentationError(e: unknown): Record<string, unknown> {
|
||||
}
|
||||
|
||||
function isNotFound(e: unknown): boolean {
|
||||
return Boolean(e && typeof e === "object" && (e as GatewayError).code === "NOT_FOUND");
|
||||
if (e && typeof e === "object" && (e as GatewayError).code === "NOT_FOUND") {
|
||||
return true;
|
||||
}
|
||||
const message =
|
||||
typeof e === "string"
|
||||
? e
|
||||
: e && typeof e === "object" && "message" in e
|
||||
? String((e as GatewayError).message)
|
||||
: "";
|
||||
return /^not found: structured session\b/i.test(message);
|
||||
}
|
||||
|
||||
function unknownChunkLabel(chunk: unknown): string {
|
||||
|
||||
Reference in New Issue
Block a user