feat(agent): routage LaunchAgent structuré vs PTY + réconciliation A/B (D3) — §17

- LaunchAgent route sur profile.structured_adapter : Some ⇒ AgentSession via
  factory + enregistrement StructuredSessions (aucun pty.spawn) ; None ⇒ PTY
  inchangé. Dépendances structurées injectées par builders additifs
  with_structured (signatures publiques inchangées ⇒ A/B legacy verts).
- LaunchAgentOutput étendu d'un champ optionnel structured (non cassant).
- A (ChangeAgentProfile) : kill polymorphe — session structurée ⇒ shutdown(),
  PTY ⇒ kill ; détection sur les deux registres.
- B : resume_supported vrai pour profil structuré ; resolve_session_plan ⇒
  Resume{conversation_id} (Claude --resume / Codex exec resume).
- Codex : build_spawn_line porte --sandbox workspace-write --ask-for-approval
  never (autonomie d'écriture ; à terme piloté par les permissions).

Tests : structured_launch 8 + codex flags 2 ; application/infrastructure 0 échec.

Reste D4 : ChatBridge (Channel) + commandes agent_send/reattach + StructuredSessions
dans AppState + DTO cellKind/ReplyChunk.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 18:48:25 +02:00
parent f104682477
commit 56913b9053
6 changed files with 1524 additions and 52 deletions

View File

@ -23,15 +23,17 @@ use application::{
LiveAgentRegistry, LoadLayout, MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject,
OpenTerminal, OrchestratorService, ReadAgentContext, ReadMemoryIndex, ReadProjectContext,
RecallMemory, ReferenceProfiles, RenameLayout, ResizeTerminal, ResolveMemoryLinks,
SaveEmbedderProfile, SaveProfile, SetActiveLayout, SnapshotRunningAgents, SuggestedThisSession,
SaveEmbedderProfile, SaveProfile, SetActiveLayout, SnapshotRunningAgents, StructuredSessions,
SuggestedThisSession,
SyncAgentWithTemplate, TerminalSessions, UnassignSkillFromAgent, UpdateAgentContext,
UpdateMemory, UpdateProjectContext, UpdateSkill, UpdateTemplate, WriteToTerminal,
AGENT_MEMORY_RECALL_BUDGET,
};
use domain::ports::{
AgentContextStore, AgentRuntime, Clock, Embedder, EmbedderEnvInspector, EmbedderProfileStore,
EmbedderPromptStore, EventBus, FileSystem, GitPort, IdGenerator, MemoryRecall, MemoryStore,
ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore, TemplateStore,
AgentContextStore, AgentRuntime, AgentSessionFactory, Clock, Embedder, EmbedderEnvInspector,
EmbedderProfileStore, EmbedderPromptStore, EventBus, FileSystem, GitPort, IdGenerator,
MemoryRecall, MemoryStore, ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore,
TemplateStore,
};
use domain::{DomainEvent, EmbedderProfile, Project, ProjectId};
@ -40,7 +42,8 @@ use infrastructure::{
EmbedderEnvProbe, FsEmbedderProfileStore, FsEmbedderPromptStore, FsMemoryStore,
FsOrchestratorWatcher, FsProfileStore, FsProjectStore, FsSkillStore, FsTemplateStore,
Git2Repository, IdeaiContextStore, LocalFileSystem, LocalProcessSpawner, NaiveMemoryRecall,
OrchestratorWatchHandle, PortablePtyAdapter, SystemClock, TokioBroadcastEventBus,
OrchestratorWatchHandle, PortablePtyAdapter, StructuredSessionFactory, SystemClock,
TokioBroadcastEventBus,
UuidGenerator, VectorMemoryRecall, DEFAULT_OLLAMA_BASE_URL, ONNX_CACHE_SUBDIR,
RECOMMENDED_ONNX_MODELS, VECTOR_HTTP_ENABLED, VECTOR_ONNX_ENABLED,
};
@ -291,6 +294,14 @@ impl AppState {
let pty_port = Arc::clone(&pty) as Arc<dyn PtyPort>;
let terminal_sessions = Arc::new(TerminalSessions::new());
// --- Sessions structurées (IA / cellules chat, §17) ---
// Registre jumeau de TerminalSessions + fabrique infra routée par
// `profile.structured_adapter`. Injectés dans LaunchAgent (routage §17.4) et
// ChangeAgentProfile (shutdown polymorphe au hot-swap).
let structured_sessions = Arc::new(StructuredSessions::new());
let session_factory =
Arc::new(StructuredSessionFactory::new()) as Arc<dyn AgentSessionFactory>;
let open_terminal = Arc::new(OpenTerminal::new(
Arc::clone(&pty_port),
Arc::clone(&terminal_sessions),
@ -507,33 +518,45 @@ impl AppState {
));
// LaunchAgent shares the SAME pty_port and terminal_sessions as the terminal
// use cases — indispensable for the PtyBridge to work correctly.
let launch_agent = Arc::new(LaunchAgent::new(
Arc::clone(&contexts_port),
Arc::clone(&profile_store_port),
Arc::clone(&runtime_port),
Arc::clone(&fs_port),
Arc::clone(&pty_port),
Arc::clone(&skill_store_port),
Arc::clone(&terminal_sessions),
Arc::clone(&events_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
Arc::clone(&memory_recall_port),
Some(Arc::clone(&check_embedder_suggestion)),
));
let launch_agent = Arc::new(
LaunchAgent::new(
Arc::clone(&contexts_port),
Arc::clone(&profile_store_port),
Arc::clone(&runtime_port),
Arc::clone(&fs_port),
Arc::clone(&pty_port),
Arc::clone(&skill_store_port),
Arc::clone(&terminal_sessions),
Arc::clone(&events_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
Arc::clone(&memory_recall_port),
Some(Arc::clone(&check_embedder_suggestion)),
)
// Routage structuré §17.4 : un profil avec `structured_adapter` lance une
// AgentSession (cellule chat) au lieu d'un PTY ; sinon chemin PTY inchangé.
.with_structured(
Arc::clone(&session_factory),
Arc::clone(&structured_sessions),
),
);
// Hot-swap an agent's runtime profile (§15.1). Reuses the shared context/
// profile/project/fs stores, the live-session registry and PTY port, and
// *composes* the launcher above for the in-place relaunch (no duplication).
let change_agent_profile = Arc::new(ChangeAgentProfile::new(
Arc::clone(&contexts_port),
Arc::clone(&profile_store_port),
Arc::clone(&store_port),
Arc::clone(&fs_port),
Arc::clone(&terminal_sessions),
Arc::clone(&pty_port),
Arc::clone(&launch_agent),
Arc::clone(&events_port),
));
// Voit aussi le registre structuré pour un « kill » polymorphe (§17.4).
let change_agent_profile = Arc::new(
ChangeAgentProfile::new(
Arc::clone(&contexts_port),
Arc::clone(&profile_store_port),
Arc::clone(&store_port),
Arc::clone(&fs_port),
Arc::clone(&terminal_sessions),
Arc::clone(&pty_port),
Arc::clone(&launch_agent),
Arc::clone(&events_port),
)
.with_structured(Arc::clone(&structured_sessions)),
);
// Read-only inventory of resumable agent cells (§15.2). Reuses the shared
// project/fs/context/profile stores already injected above — no new port.