feat(agent): conversation par paire + entrée médiée + pivot terminal/MCP

Coeur inter-agents consolidé et surface front réalignée sur la décision
"terminal natif PTY, pas d'UI chat" (Option 1).

Domaine
- nouveaux modules conversation, mailbox, input, fileguard (ports + types)
- orchestrator/profile/events étendus (conversation par paire, FIFO)

Application / Infrastructure
- orchestrator/service + context_guard : sérialisation FIFO par agent,
  garde RW mémoire/contexte, dispatch ask/reply
- adapters in-memory conversation / mailbox / input / fileguard
- registry session + lifecycle agent durcis (1 agent = 1 session vivante)
- outils MCP idea_* alignés sur le nouveau dispatch

Frontend
- MediatedInput + useAgentBusy : entrée utilisateur médiée par IdeA,
  terminal = vue sortie inchangée
- suppression de la vue chat structurée (AgentChatView) — abandonnée
- adapter input + ports mis à jour

Divers
- .ideai/ : mémoire projet + briefs de cadrage versionnés ;
  requests/ runtime ignoré ; agents projet réels (DevBackend/DevFrontend/QA)

Tests : Rust (domain/application/infrastructure/app-tauri) + front (346) verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 07:33:04 +02:00
parent 5f45c22941
commit eca2ba95c4
61 changed files with 9491 additions and 2512 deletions

View File

@ -16,9 +16,6 @@ import { Channel, invoke } from "@tauri-apps/api/core";
import type {
Agent,
CellKind,
ReattachChatDto,
ReplyChunk,
ResumableAgent,
TerminalSession,
} from "@/domain";
@ -41,8 +38,6 @@ interface LaunchAgentResponse {
cols: number;
/** Conversation id minted by this launch (omitted when nothing was assigned). */
assignedConversationId?: string;
/** How the hosting cell should render (`"chat"` ⇒ structured AI cell, §17.6). */
cellKind: CellKind;
}
export class TauriAgentGateway implements AgentGateway {
@ -155,11 +150,9 @@ export class TauriAgentGateway implements AgentGateway {
const base = makeTerminalHandle(res.sessionId, channel);
// Surface the id assigned by this launch (so the caller persists it on the
// leaf and resumes next time) and the derived `cellKind` (so the leaf routes
// to `AgentChatView` vs `TerminalView`, §17.6).
// leaf and resumes next time).
return {
...base,
cellKind: res.cellKind,
...(res.assignedConversationId
? { assignedConversationId: res.assignedConversationId }
: {}),
@ -197,40 +190,4 @@ export class TauriAgentGateway implements AgentGateway {
request: { projectId, agentId, conversationId },
});
}
async sendPrompt(
sessionId: string,
prompt: string,
onReply: (chunk: ReplyChunk) => void,
): Promise<void> {
// Per-turn reply channel. The backend streams typed `ReplyChunk`s (tagged on
// `kind`); the pump runs to the `final` chunk then detaches its generation.
const channel = new Channel<ReplyChunk>();
channel.onmessage = (chunk) => onReply(chunk);
// `agent_send` takes top-level args (Tauri renames the snake_case command
// params to camelCase): `sessionId`, `prompt`, `onReply`.
await invoke("agent_send", { sessionId, prompt, onReply: channel });
}
async reattachChat(
sessionId: string,
onReply: (chunk: ReplyChunk) => void,
): Promise<ReplyChunk[]> {
// Re-bind to a still-living structured session without re-spawning it. The
// returned scrollback repaints prior turns; subsequent chunks arrive over the
// freshly-registered channel.
const channel = new Channel<ReplyChunk>();
channel.onmessage = (chunk) => onReply(chunk);
const res = await invoke<ReattachChatDto>("reattach_agent_chat", {
sessionId,
onReply: channel,
});
return res.scrollback;
}
async closeAgentSession(sessionId: string): Promise<void> {
await invoke("close_agent_session", { sessionId });
}
}