fix(chat): le transcript s'affiche à nouveau après redémarrage automatique d'un agent — #295 (app-tauri ChatBridge: cartes successors/owners, rebind_session déplace sink+scrolloback du logical chat vers la session de remplacement et canonical_session résout les écritures avec l'ancien id sur toutes les opérations register/scrollback/detach/send_output; backend stream: ReplayOutputBridge::rekey prépare le scrollback source devant le destination et invalide les générations pré-rekey des deux pumps; commands: agent_send binde l'owner stable (projectId,agentId) au tour humain et rebind avant le flux de continuation au handoff restart; reattach_agent_chat résout le successeur du bridge puis fallback identité stable projectId/agentId explicite ou owner bindé, renvoie sessionId effectif + conversationId, NOT_FOUND sinon; web-server chat.attach même fallback stale→live par project/agent; DTO ReattachChatDto/ReattachResultDto documentent l'id effectif post-restart; frontend: ReattachAgentChatOptions, conversationId dans ChatAttachedPayload, CustomAgentChatView réattache sur agentRestartCompleted (dédupe par restartId, scrollback appliqué, tours restart préservés), retry identité stable sur NOT_FOUND dans openOrAttach avant fallback launch frais, publication sessionId effectif + conversationId, état vide «Conversation liée» quand une conversation/run/workstate lié existe; QA verte reconfirmée par Git: git diff --check, cargo fmt --all --check, app-tauri chat_bridge 34/34 dont 3 nouveaux restart_rebind + dto_chat 24/24, cargo check -p app-tauri, backend 91+7 ignored permissions socket, web-server websocket_chat_attach 3/3, npm test 77/77 CustomAgentChatView+agentGateway, npm run build; réserves non bloquantes: warnings Vite esbuild/oxc et chunk >500kB connus)
This commit is contained in:
@ -275,11 +275,14 @@ export class TauriAgentGateway implements AgentGateway {
|
||||
async reattachAgentChat(
|
||||
sessionId: string,
|
||||
onChunk: (chunk: ReplyChunk) => void,
|
||||
options: { projectId?: string; agentId?: string } = {},
|
||||
): Promise<ReattachAgentChatResult> {
|
||||
const channel = new Channel<ReplyChunk>();
|
||||
channel.onmessage = onChunk;
|
||||
return invoke<ReattachAgentChatResult>("reattach_agent_chat", {
|
||||
sessionId,
|
||||
...(options.projectId ? { projectId: options.projectId } : {}),
|
||||
...(options.agentId ? { agentId: options.agentId } : {}),
|
||||
onReply: channel,
|
||||
});
|
||||
}
|
||||
|
||||
@ -313,21 +313,30 @@ describe("HttpAgentGateway WS round-trip (B6 frames)", () => {
|
||||
freshStart: false,
|
||||
});
|
||||
|
||||
const reattached = gw.reattachAgentChat("chat-1", () => {});
|
||||
const reattached = gw.reattachAgentChat("chat-1", () => {}, {
|
||||
projectId: "proj-1",
|
||||
agentId: "agent-1",
|
||||
});
|
||||
const attach = await replyToLast(socket, 1, (id) => ({
|
||||
kind: "chat.attached",
|
||||
replyTo: id,
|
||||
payload: {
|
||||
sessionId: "chat-1",
|
||||
conversationId: "conv-replacement",
|
||||
scrollback: [{ kind: "final", content: "Recovered reply" }],
|
||||
},
|
||||
}));
|
||||
await expect(reattached).resolves.toEqual({
|
||||
sessionId: "chat-1",
|
||||
conversationId: "conv-replacement",
|
||||
scrollback: [{ kind: "final", content: "Recovered reply" }],
|
||||
});
|
||||
expect(attach.kind).toBe("chat.attach");
|
||||
expect(attach.payload).toEqual({ sessionId: "chat-1" });
|
||||
expect(attach.payload).toEqual({
|
||||
sessionId: "chat-1",
|
||||
projectId: "proj-1",
|
||||
agentId: "agent-1",
|
||||
});
|
||||
|
||||
const chunks: unknown[] = [];
|
||||
const sent = gw.sendAgentChat("chat-1", "Hello", (chunk) => chunks.push(chunk), {
|
||||
|
||||
@ -152,5 +152,7 @@ export interface ChatAttachedPayload {
|
||||
};
|
||||
cellKind?: "chat" | "pty";
|
||||
assignedConversationId?: string;
|
||||
conversationId?: string;
|
||||
engineSessionId?: string;
|
||||
scrollback?: ReplyChunk[];
|
||||
}
|
||||
|
||||
@ -379,8 +379,9 @@ export class HttpAgentGateway implements AgentGateway {
|
||||
async reattachAgentChat(
|
||||
sessionId: string,
|
||||
onChunk: (chunk: ReplyChunk) => void,
|
||||
options: { projectId?: string; agentId?: string } = {},
|
||||
): Promise<ReattachAgentChatResult> {
|
||||
const ack = await this.ws.send("chat.attach", { sessionId });
|
||||
const ack = await this.ws.send("chat.attach", { sessionId, ...options });
|
||||
if (ack.kind !== "chat.attached") {
|
||||
throw {
|
||||
code: "CHAT_ATTACH_FAILED",
|
||||
@ -392,6 +393,14 @@ export class HttpAgentGateway implements AgentGateway {
|
||||
this.ws.setChatSink(attachedSessionId, onChunk);
|
||||
return {
|
||||
sessionId: attachedSessionId,
|
||||
...(payload.conversationId ?? payload.assignedConversationId ?? payload.engineSessionId
|
||||
? {
|
||||
conversationId:
|
||||
payload.conversationId ??
|
||||
payload.assignedConversationId ??
|
||||
payload.engineSessionId,
|
||||
}
|
||||
: {}),
|
||||
scrollback: payload.scrollback ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user