fix(chat): relance propre au lieu de re-tenter l'attach sur une session structurée morte (#148)
Sur reattach NOT_FOUND, le composant retentait implicitement l'attach sur le même sessionId mort au lieu de nettoyer l'état avant de relancer, ce qui produisait l'erreur "not found: structured session …" côté utilisateur. recoverStructuredSession() nettoie désormais sessionId/conversationId avant de relancer et retente une seule fois le reattach sur NOT_FOUND post-launch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -151,6 +151,250 @@ describe("CustomAgentChatView", () => {
|
|||||||
expect(screen.queryByText(/structured session gone/)).toBeNull();
|
expect(screen.queryByText(/structured session gone/)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("recovers and retries the prompt when send reports NOT_FOUND", 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({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "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(
|
||||||
|
1,
|
||||||
|
"stale-session",
|
||||||
|
"please recover",
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
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(),
|
||||||
|
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||||
|
sessionId,
|
||||||
|
scrollback: [],
|
||||||
|
})),
|
||||||
|
sendAgentChat: vi.fn(() => new Promise<void>(() => {})),
|
||||||
|
cancelAgentChat: vi.fn().mockRejectedValueOnce({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "not found: structured session gone-session",
|
||||||
|
}),
|
||||||
|
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="gone-session"
|
||||||
|
conversationId="conversation-1"
|
||||||
|
onSessionId={onSessionId}
|
||||||
|
onConversationId={vi.fn()}
|
||||||
|
/>
|
||||||
|
</DIProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(agent.reattachAgentChat).toHaveBeenCalledWith(
|
||||||
|
"gone-session",
|
||||||
|
expect.any(Function),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
|
||||||
|
target: { value: "long turn" },
|
||||||
|
});
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
|
||||||
|
await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalled());
|
||||||
|
|
||||||
|
fireEvent.click(await screen.findByRole("button", { name: "Cancel" }));
|
||||||
|
|
||||||
|
expect(await screen.findByText("Session déjà fermée.")).toBeTruthy();
|
||||||
|
expect(onSessionId).toHaveBeenCalledWith(null);
|
||||||
|
expect(agent.closeAgentChat).not.toHaveBeenCalled();
|
||||||
|
expect(screen.queryByText(/not found: structured session/)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not swallow post-launch reattach errors", async () => {
|
||||||
|
const agent = {
|
||||||
|
launchAgentChat: vi.fn(async () => ({
|
||||||
|
sessionId: "new-session",
|
||||||
|
assignedConversationId: "conversation-2",
|
||||||
|
})),
|
||||||
|
reattachAgentChat: vi.fn().mockRejectedValueOnce({
|
||||||
|
code: "PROCESS",
|
||||||
|
message: "post-launch attach failed",
|
||||||
|
}),
|
||||||
|
sendAgentChat: vi.fn(() => new Promise<void>(() => {})),
|
||||||
|
cancelAgentChat: vi.fn(async () => {}),
|
||||||
|
closeAgentChat: vi.fn(async () => {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
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={null}
|
||||||
|
conversationId="conversation-1"
|
||||||
|
onSessionId={vi.fn()}
|
||||||
|
onConversationId={vi.fn()}
|
||||||
|
/>
|
||||||
|
</DIProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect((await screen.findByRole("alert")).textContent).toContain(
|
||||||
|
"post-launch attach failed",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("recovers when post-launch reattach reports NOT_FOUND", async () => {
|
||||||
|
const agent = {
|
||||||
|
launchAgentChat: vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
sessionId: "new-session-1",
|
||||||
|
assignedConversationId: "conversation-2",
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
sessionId: "new-session-2",
|
||||||
|
assignedConversationId: "conversation-2",
|
||||||
|
}),
|
||||||
|
reattachAgentChat: vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "not found: structured session new-session-1",
|
||||||
|
})
|
||||||
|
.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={null}
|
||||||
|
conversationId="conversation-1"
|
||||||
|
onSessionId={onSessionId}
|
||||||
|
onConversationId={vi.fn()}
|
||||||
|
/>
|
||||||
|
</DIProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => expect(agent.launchAgentChat).toHaveBeenCalledTimes(2));
|
||||||
|
expect(agent.reattachAgentChat).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
"new-session-1",
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
expect(agent.reattachAgentChat).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
"new-session-2",
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
expect(onSessionId).toHaveBeenCalledWith(null);
|
||||||
|
expect(onSessionId).toHaveBeenCalledWith("new-session-2");
|
||||||
|
expect(screen.queryByText(/not found: structured session/)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("surfaces non-NOT_FOUND reattach errors instead of launching a new session", async () => {
|
it("surfaces non-NOT_FOUND reattach errors instead of launching a new session", async () => {
|
||||||
const agent = {
|
const agent = {
|
||||||
launchAgentChat: vi.fn(),
|
launchAgentChat: vi.fn(),
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
* deliberately does not try to parse PTY bytes.
|
* deliberately does not try to parse PTY bytes.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
import type { AgentProfile, GatewayError, ReplyChunk } from "@/domain";
|
import type { AgentProfile, GatewayError, ReplyChunk } from "@/domain";
|
||||||
import { useGateways } from "@/app/di";
|
import { useGateways } from "@/app/di";
|
||||||
@ -40,6 +40,10 @@ function describe(e: unknown): string {
|
|||||||
return String(e);
|
return String(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNotFound(e: unknown): boolean {
|
||||||
|
return Boolean(e && typeof e === "object" && (e as GatewayError).code === "NOT_FOUND");
|
||||||
|
}
|
||||||
|
|
||||||
function unknownChunkLabel(chunk: unknown): string {
|
function unknownChunkLabel(chunk: unknown): string {
|
||||||
try {
|
try {
|
||||||
return JSON.stringify(chunk);
|
return JSON.stringify(chunk);
|
||||||
@ -122,6 +126,7 @@ export function CustomAgentChatView({
|
|||||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||||
const sessionRef = useRef<string | null>(sessionId);
|
const sessionRef = useRef<string | null>(sessionId);
|
||||||
sessionRef.current = currentSession;
|
sessionRef.current = currentSession;
|
||||||
|
const mountedRef = useRef(false);
|
||||||
const onSessionIdRef = useRef(onSessionId);
|
const onSessionIdRef = useRef(onSessionId);
|
||||||
onSessionIdRef.current = onSessionId;
|
onSessionIdRef.current = onSessionId;
|
||||||
const onConversationIdRef = useRef(onConversationId);
|
const onConversationIdRef = useRef(onConversationId);
|
||||||
@ -136,6 +141,78 @@ export function CustomAgentChatView({
|
|||||||
agent.closeAgentChat,
|
agent.closeAgentChat,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
mountedRef.current = true;
|
||||||
|
return () => {
|
||||||
|
mountedRef.current = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const receive = useCallback((chunk: ReplyChunk) => {
|
||||||
|
setTurns((prev) => foldChunk(prev, chunk));
|
||||||
|
if (chunk.kind === "final" || chunk.kind === "error") setBusy(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const reattachStructuredSession = useCallback(
|
||||||
|
async (sid: string, options: { applyScrollback?: boolean } = {}) => {
|
||||||
|
if (!agent.reattachAgentChat) throw new Error("Structured reattach unavailable");
|
||||||
|
const reattached = await agent.reattachAgentChat(sid, receive);
|
||||||
|
if (!mountedRef.current) return reattached.sessionId;
|
||||||
|
setCurrentSession(reattached.sessionId);
|
||||||
|
if (options.applyScrollback) {
|
||||||
|
setTurns(reattached.scrollback.reduce(foldChunk, [] as ChatTurn[]));
|
||||||
|
}
|
||||||
|
return reattached.sessionId;
|
||||||
|
},
|
||||||
|
[agent, receive],
|
||||||
|
);
|
||||||
|
|
||||||
|
const recoverStructuredSession = useCallback(
|
||||||
|
async (options: { applyScrollback?: boolean; retryAttachNotFound?: boolean } = {}) => {
|
||||||
|
if (!agent.launchAgentChat) throw new Error("Structured launch unavailable");
|
||||||
|
let retryAttachNotFound = Boolean(options.retryAttachNotFound);
|
||||||
|
for (;;) {
|
||||||
|
const launched = await agent.launchAgentChat(projectId, agentId, {
|
||||||
|
cwd,
|
||||||
|
rows: 24,
|
||||||
|
cols: 80,
|
||||||
|
conversationId: conversationId ?? undefined,
|
||||||
|
nodeId,
|
||||||
|
});
|
||||||
|
if (!mountedRef.current) return launched.sessionId;
|
||||||
|
setCurrentSession(launched.sessionId);
|
||||||
|
onSessionIdRef.current(launched.sessionId);
|
||||||
|
if (launched.assignedConversationId) {
|
||||||
|
onConversationIdRef.current(launched.assignedConversationId);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await reattachStructuredSession(launched.sessionId, {
|
||||||
|
applyScrollback: options.applyScrollback,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (isNotFound(e) && retryAttachNotFound) {
|
||||||
|
retryAttachNotFound = false;
|
||||||
|
if (mountedRef.current) {
|
||||||
|
setCurrentSession(null);
|
||||||
|
onSessionIdRef.current(null);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
agent,
|
||||||
|
projectId,
|
||||||
|
agentId,
|
||||||
|
cwd,
|
||||||
|
nodeId,
|
||||||
|
conversationId,
|
||||||
|
reattachStructuredSession,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = scrollRef.current;
|
const el = scrollRef.current;
|
||||||
if (el) el.scrollTop = el.scrollHeight;
|
if (el) el.scrollTop = el.scrollHeight;
|
||||||
@ -144,10 +221,6 @@ export function CustomAgentChatView({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!supported) return;
|
if (!supported) return;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const receive = (chunk: ReplyChunk) => {
|
|
||||||
setTurns((prev) => foldChunk(prev, chunk));
|
|
||||||
if (chunk.kind === "final" || chunk.kind === "error") setBusy(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function openOrAttach() {
|
async function openOrAttach() {
|
||||||
setOpening(true);
|
setOpening(true);
|
||||||
@ -155,33 +228,25 @@ export function CustomAgentChatView({
|
|||||||
try {
|
try {
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
try {
|
try {
|
||||||
const reattached = await agent.reattachAgentChat!(sessionId, receive);
|
await reattachStructuredSession(sessionId, { applyScrollback: true });
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setCurrentSession(reattached.sessionId);
|
|
||||||
setTurns(reattached.scrollback.reduce(foldChunk, [] as ChatTurn[]));
|
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// The backend contract for `reattach_agent_chat` (NOT_FOUND) is that
|
// The backend contract for `reattach_agent_chat` (NOT_FOUND) is that
|
||||||
// the session is gone (closed/never live) and the caller falls back
|
// the session is gone (closed/never live) and the caller falls back
|
||||||
// to a fresh launch — any other error still surfaces to the user.
|
// to a fresh launch — any other error still surfaces to the user.
|
||||||
if ((e as GatewayError)?.code !== "NOT_FOUND") throw e;
|
if (!isNotFound(e)) throw e;
|
||||||
|
if (!cancelled) {
|
||||||
|
setCurrentSession(null);
|
||||||
|
onSessionIdRef.current(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const launched = await agent.launchAgentChat!(projectId, agentId, {
|
await recoverStructuredSession({
|
||||||
cwd,
|
applyScrollback: false,
|
||||||
rows: 24,
|
retryAttachNotFound: true,
|
||||||
cols: 80,
|
|
||||||
conversationId: conversationId ?? undefined,
|
|
||||||
nodeId,
|
|
||||||
});
|
});
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setCurrentSession(launched.sessionId);
|
|
||||||
onSessionIdRef.current(launched.sessionId);
|
|
||||||
if (launched.assignedConversationId) {
|
|
||||||
onConversationIdRef.current(launched.assignedConversationId);
|
|
||||||
}
|
|
||||||
// Attach the view so any in-flight chunks can be replayed after launch.
|
|
||||||
await agent.reattachAgentChat!(launched.sessionId, receive).catch(() => {});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!cancelled) setError(describe(e));
|
if (!cancelled) setError(describe(e));
|
||||||
} finally {
|
} finally {
|
||||||
@ -195,23 +260,18 @@ export function CustomAgentChatView({
|
|||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
supported,
|
supported,
|
||||||
agent,
|
|
||||||
projectId,
|
|
||||||
agentId,
|
|
||||||
cwd,
|
|
||||||
nodeId,
|
|
||||||
sessionId,
|
sessionId,
|
||||||
conversationId,
|
reattachStructuredSession,
|
||||||
|
recoverStructuredSession,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const canSend = useMemo(
|
const canSend = useMemo(
|
||||||
() =>
|
() =>
|
||||||
supported &&
|
supported &&
|
||||||
Boolean(currentSession) &&
|
|
||||||
Boolean(draft.trim()) &&
|
Boolean(draft.trim()) &&
|
||||||
!busy &&
|
!busy &&
|
||||||
!opening,
|
!opening,
|
||||||
[supported, currentSession, draft, busy, opening],
|
[supported, draft, busy, opening],
|
||||||
);
|
);
|
||||||
|
|
||||||
async function pickAttachment() {
|
async function pickAttachment() {
|
||||||
@ -221,7 +281,7 @@ export function CustomAgentChatView({
|
|||||||
|
|
||||||
async function send() {
|
async function send() {
|
||||||
const text = draft.trim();
|
const text = draft.trim();
|
||||||
if (!canSend || !currentSession || !agent.sendAgentChat) return;
|
if (!canSend || !agent.sendAgentChat) return;
|
||||||
const prompt = attachment ? `${text}\n\n[Fichier joint: ${attachment}]` : text;
|
const prompt = attachment ? `${text}\n\n[Fichier joint: ${attachment}]` : text;
|
||||||
setDraft("");
|
setDraft("");
|
||||||
setAttachment(null);
|
setAttachment(null);
|
||||||
@ -229,11 +289,31 @@ export function CustomAgentChatView({
|
|||||||
setError(null);
|
setError(null);
|
||||||
setTurns((prev) => [...prev, { role: "user", text, attachment: attachment ?? undefined }]);
|
setTurns((prev) => [...prev, { role: "user", text, attachment: attachment ?? undefined }]);
|
||||||
try {
|
try {
|
||||||
await agent.sendAgentChat(currentSession, prompt, (chunk) => {
|
const sid =
|
||||||
setTurns((prev) => foldChunk(prev, chunk));
|
currentSession ??
|
||||||
if (chunk.kind === "final" || chunk.kind === "error") setBusy(false);
|
(await recoverStructuredSession({
|
||||||
});
|
applyScrollback: false,
|
||||||
|
retryAttachNotFound: true,
|
||||||
|
}));
|
||||||
|
await agent.sendAgentChat(sid, prompt, receive);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isNotFound(e)) {
|
||||||
|
try {
|
||||||
|
setCurrentSession(null);
|
||||||
|
onSessionIdRef.current(null);
|
||||||
|
const recoveredSession = await recoverStructuredSession({
|
||||||
|
applyScrollback: false,
|
||||||
|
retryAttachNotFound: true,
|
||||||
|
});
|
||||||
|
await agent.sendAgentChat(recoveredSession, prompt, receive);
|
||||||
|
return;
|
||||||
|
} catch (recoveryError) {
|
||||||
|
setBusy(false);
|
||||||
|
setError(describe(recoveryError));
|
||||||
|
setTurns((prev) => [...prev, { role: "error", text: describe(recoveryError) }]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
setError(describe(e));
|
setError(describe(e));
|
||||||
setTurns((prev) => [...prev, { role: "error", text: describe(e) }]);
|
setTurns((prev) => [...prev, { role: "error", text: describe(e) }]);
|
||||||
@ -250,6 +330,12 @@ export function CustomAgentChatView({
|
|||||||
await agent.cancelAgentChat(sid);
|
await agent.cancelAgentChat(sid);
|
||||||
setTurns((prev) => [...prev, { role: "tool", label: "Tour interrompu." }]);
|
setTurns((prev) => [...prev, { role: "tool", label: "Tour interrompu." }]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isNotFound(e)) {
|
||||||
|
setCurrentSession(null);
|
||||||
|
onSessionIdRef.current(null);
|
||||||
|
setTurns((prev) => [...prev, { role: "tool", label: "Session déjà fermée." }]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setError(describe(e));
|
setError(describe(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user