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:
@ -1,8 +1,8 @@
|
||||
---
|
||||
issueRef: "#149"
|
||||
version: 14
|
||||
version: 15
|
||||
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
|
||||
updatedAt: 1785934744273
|
||||
updatedAt: 1785937428431
|
||||
---
|
||||
# Historique des tentatives
|
||||
|
||||
@ -13,7 +13,7 @@ updatedAt: 1785934744273
|
||||
- Action Main: reouverture du ticket et relance d'un cycle complet Architect -> Git -> DevFrontend -> QA.
|
||||
|
||||
## Piste precedemment documentee dans la description
|
||||
- Hypothese precedente: fallback premature dans `frontend/src/features/layout/LayoutGrid.tsx` pendant le chargement asynchrone initial du catalogue agent/profil, ecrasant `cellMode=custom` restaure depuis le storage.
|
||||
- Hypothese precedente: fallback premature dans `frontend/src/features/layout/LayoutGrid.tsx` pendant le chargement asynchrone initial du catalogue agent/profil, ecrasant `cellMode="custom"` restaure depuis le storage.
|
||||
- Cette piste n'a pas suffi a eliminer le symptome utilisateur, donc elle doit etre revalidee ou completee avant nouvelle correction.
|
||||
|
||||
## 2026-08-05 — Recadrage Architecture (avant nouvelle implementation)
|
||||
@ -124,3 +124,28 @@ updatedAt: 1785934744273
|
||||
2. rebuild/reinstall AppImage depuis la branche contenant `ce9ba0dc`, relancer IdeA, puis retester ;
|
||||
3. si la cellule retombe encore vers Plain/TUI, capturer les logs `[ticket149]` au moment exact du repro et comparer: `openOrAttach:self-session-echo:skip`, `vm.setSession`, `shouldFallbackCustomCliMode`, `customCliAvailable`, `effectiveCustomCliAvailable`, `trustedCustomCli`;
|
||||
4. si les logs montrent que `openOrAttach` reste stable, la prochaine hypothese doit sortir de `CustomAgentChatView` et porter sur la condition de rendu `customCliAgent/customCliProfile/effectiveCustomCliAvailable` ou sur un evenement externe qui remplace le mode/vue, avec trace runtime avant patch.
|
||||
|
||||
## 2026-08-05 — Nouveau symptome apres le fix session echo
|
||||
- Retour utilisateur recu apres `ce9ba0dc`: la CLI custom peut maintenant se lancer, mais l'UI affiche l'erreur exacte `not found: structured session 69b57638-0ac7-4a79-8252-e0436a3265f6`.
|
||||
- Changement de symptome important: on n'est plus sur la retombee immediate vers Plain/TUI telle que documentee plus haut ; on est maintenant sur un echec de rattachement/reutilisation d'une session structuree.
|
||||
- Verification statique Main (sans nouvelle implementation a ce stade):
|
||||
1. `frontend/src/features/agents/CustomAgentChatView.tsx` est cense **absorber** `NOT_FOUND` sur `reattachAgentChat` puis relancer une session fraiche, y compris pour un `sessionId` stale restaure depuis le parent ; les tests couvrent deja ce cas.
|
||||
2. Le meme composant sait aussi recuperer d'un premier `launchAgentChat` qui renvoie un `sessionId` mort, a condition qu'un second launch/reattach reussisse ; ce cas est aussi teste.
|
||||
3. Voir quand meme cette erreur dans l'UI suggere donc plutot l'un de ces ecarts restants:
|
||||
- l'erreur reelle renvoyee par `invoke(...)` n'est pas typée `code: "NOT_FOUND"` cote frontend, donc `isNotFound(...)` ne la reconnait pas et elle remonte telle quelle ;
|
||||
- ou bien `launch_agent` renvoie lui-meme un `sessionId` structure qui n'est deja plus present dans `structured_sessions` au moment du `reattach`, potentiellement plus d'une fois, ce qui epuise la logique de retry et surface finalement `not found: structured session ...`.
|
||||
- Ownership provisoire pour la reprise:
|
||||
- piste prioritaire **frontend/runtime contract**: confirmer la forme exacte de l'erreur recue par `CustomAgentChatView` (`code`, `message`, `name`, `raw`) dans les logs `[ticket149]` deja poses ;
|
||||
- piste secondaire **backend structured registry**: si `code === "NOT_FOUND"` est bien present sur un `sessionId` fraichement renvoye par `launch_agent`, alors l'anomalie bascule cote creation/enregistrement/liveness de `structured_sessions`.
|
||||
- Commandes de lecture executees par Main pour ce recadrage:
|
||||
1. `git status --short --branch` -> branche courante `feature/ticket149-customchat-session-instrumentation`, avec un changement non lie `.ideai/idea-android-plugin.json` deja present ;
|
||||
2. `git log --oneline --decorate -n 12` -> HEAD `62ac05a0` au-dessus de `ce9ba0dc` ;
|
||||
3. lecture de `frontend/src/features/agents/CustomAgentChatView.tsx`, de ses tests, et de `crates/app-tauri/src/commands.rs` autour de `reattach_agent_chat` / `launch_agent`.
|
||||
- Blocage de pilotage dans ce tour:
|
||||
- `idea_ask_agent(Architect)` et `idea_ask_agent(Git)` ont tous deux repondu `You've hit your session limit · resets 4:50pm (Europe/Paris)` ; la delegation specialisee est donc temporairement indisponible jusqu'au **2026-08-05 16:50 Europe/Paris**.
|
||||
- Etape suivante imposee a la reprise apres 16:50:
|
||||
1. refaire la delegation `Architect` pour trancher frontend vs backend sur ce nouveau symptome ;
|
||||
2. faire trancher `Git` sur la branche de travail a conserver/reprendre ;
|
||||
3. envoyer a `DevFrontend` une tache d'instrumentation/verification du contrat d'erreur si `code` manque ;
|
||||
4. envoyer a `DevBackend` une tache de verification de l'enregistrement/liveness `structured_sessions` si un `sessionId` fraichement lance ressort deja en `NOT_FOUND` ;
|
||||
5. faire valider par `QA` sur repro reel avec le message exact et, si possible, les logs `[ticket149]` au moment du repro.
|
||||
@ -11,8 +11,8 @@ attachments: []
|
||||
createdBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
|
||||
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
|
||||
createdAt: 1785930073255
|
||||
updatedAt: 1785934744273
|
||||
version: 14
|
||||
updatedAt: 1785937428431
|
||||
version: 15
|
||||
---
|
||||
Bug report utilisateur du 2026-08-05: la CLI custom ne se lance plus; à l'ouverture elle se ferme immédiatement et la cellule revient sur Plain/TUI. Diagnostic Architecture: bug distinct de #148. Dans `frontend/src/features/layout/LayoutGrid.tsx`, `cellMode` restauré à `custom` depuis le storage est forcé trop tôt vers `tui` par l'effet garde-fou `if (!customCliAvailable && cellMode !== \"tui\") setCellMode(\"tui\")`, alors que `agents`/`profiles` sont encore vides pendant leur chargement asynchrone initial. Résultat: la préférence `custom` est écrasée silencieusement avant même que la vue puisse se monter. Objectif: retarder ce fallback jusqu'à la fin du premier chargement du catalogue, couvrir par test de non-régression, valider QA, puis rebuild AppImage Linux.
|
||||
|
||||
|
||||
@ -1960,7 +1960,7 @@
|
||||
"kind": "agent",
|
||||
"agent_id": "a6c6ea12-bfc6-4bdc-8031-324102dfa34d"
|
||||
},
|
||||
"updatedAt": 1785934744273
|
||||
"updatedAt": 1785937428431
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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