feat(chat): valide le routing cellKind=chat côté frontend et corrige UX

- Ajoute cellKind dans AgentChatHandle et vérifie que launchAgentChat reçoit cellKind:chat
- Rejette avec erreur STRUCTURED_ROUTED_TO_PTY si le backend route vers PTY
- Ajoute cellKind:chat dans MockAgentGateway.launchAgentChat
- Corrige l'overflow du layout CustomAgentChatView (bounded shell avec scroll + composer fixe)
- Dédouble les prompts utilisateur quand le stream les echo
- Ajoute les tests launchAgentChat returns handle only when confirms chat et launchAgentChat rejects PTY-routed launch
- Ajoute les tests does not duplicate user prompt when echoed et keeps chat shell bounded

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 23:39:50 +02:00
parent c43f29b2f2
commit 937ea07df1
6 changed files with 322 additions and 22 deletions

View File

@ -18,6 +18,7 @@ import type {
Agent,
AgentContextDocument,
EffortSelection,
GatewayError,
ReplyChunk,
ResumableAgent,
TerminalSession,
@ -42,10 +43,28 @@ interface LaunchAgentResponse {
cwd: string;
rows: number;
cols: number;
/** Backend-derived routing: `chat` for structured sessions, `pty` for native TUI. */
cellKind?: "chat" | "pty";
/** Conversation id minted by this launch (omitted when nothing was assigned). */
assignedConversationId?: string;
}
function structuredLaunchRoutedToPtyError(
projectId: string,
agentId: string,
options: OpenTerminalOptions,
response: LaunchAgentResponse,
): GatewayError {
const actual = response.cellKind ?? "missing";
return {
code: "STRUCTURED_ROUTED_TO_PTY",
message:
`custom CLI launch for agent ${agentId} in project ${projectId} ` +
`returned cellKind=${actual}; expected chat. ` +
`sessionId=${response.sessionId}; nodeId=${options.nodeId ?? "none"}`,
};
}
export class TauriAgentGateway implements AgentGateway {
listAgents(projectId: string): Promise<Agent[]> {
return invoke<Agent[]>("list_agents", { projectId });
@ -194,13 +213,32 @@ export class TauriAgentGateway implements AgentGateway {
agentId,
rows: options.rows,
cols: options.cols,
cellKind: "chat",
conversationId: options.conversationId ?? null,
nodeId: options.nodeId ?? null,
},
onOutput: channel,
});
if (res.cellKind !== "chat") {
const error = structuredLaunchRoutedToPtyError(projectId, agentId, options, res);
console.error("[ticket149] launchAgentChat:routed-to-non-chat", {
projectId,
agentId,
request: {
rows: options.rows,
cols: options.cols,
cellKind: "chat",
conversationId: options.conversationId ?? null,
nodeId: options.nodeId ?? null,
},
response: res,
error,
});
throw error;
}
return {
sessionId: res.sessionId,
cellKind: "chat",
...(res.assignedConversationId
? { assignedConversationId: res.assignedConversationId }
: {}),