feat(chat): persistance bornee du brouillon custom chat par projet/layout (#182)

This commit is contained in:
2026-08-11 16:24:20 +02:00
parent 25ad44a3d9
commit 94b4ce7e5f
23 changed files with 1552 additions and 70 deletions

View File

@ -103,6 +103,8 @@ import type {
import type {
AgentChatHandle,
AgentGateway,
ChatDraftScope,
ChatDraftSnapshot,
ConversationGateway,
ConversationPageRequest,
ConversationDetails,
@ -401,6 +403,8 @@ export class MockAgentGateway implements AgentGateway {
private liveKindByAgent = new Map<string, "pty" | "structured">();
/** Retained structured reply chunks per live chat session. */
private chatScrollback = new Map<string, ReplyChunk[]>();
/** Persisted custom chat drafts keyed by project/layout/node. */
private chatDrafts = new Map<string, ChatDraftSnapshot>();
private slashCommands: SlashCommand[] = [
{
name: "/help",
@ -437,6 +441,10 @@ export class MockAgentGateway implements AgentGateway {
return `${projectId}::${agentId}`;
}
private chatDraftKey(scope: ChatDraftScope): string {
return `${scope.projectId}::${scope.layoutId}::${scope.nodeId}`;
}
async listAgents(projectId: string): Promise<Agent[]> {
return structuredClone(this.getAgents(projectId));
}
@ -985,6 +993,28 @@ export class MockAgentGateway implements AgentGateway {
};
}
async readChatDraft(scope: ChatDraftScope): Promise<ChatDraftSnapshot> {
return structuredClone(
this.chatDrafts.get(this.chatDraftKey(scope)) ?? { text: null, updatedAtMs: null },
);
}
async saveChatDraft(scope: ChatDraftScope, text: string): Promise<void> {
const key = this.chatDraftKey(scope);
if (text.length === 0) {
this.chatDrafts.delete(key);
return;
}
this.chatDrafts.set(key, {
text,
updatedAtMs: Date.now(),
});
}
async clearChatDraft(scope: ChatDraftScope): Promise<void> {
this.chatDrafts.delete(this.chatDraftKey(scope));
}
async closeAgentChat(sessionId: string): Promise<void> {
this.chatScrollback.delete(sessionId);
for (const [agentId, liveSessionId] of this.liveSessionByAgent) {