Commit Graph

71 Commits

Author SHA1 Message Date
38aecf2cac feat(model-server): source Hugging Face (-hf) + options structurées llama.cpp + migration store V2
Backend du support natif d'une source modèle Hugging Face en alternative au
chemin .gguf local pour le serveur llama.cpp intégré, et refonte des options.

- domaine : ModelSource {LocalPath|HuggingFace} + HfModelRef,
  LlamaCppOptions {host,gpu_layers,context_size,jinja}, invariant auto_start
  exigeant une source, validate_free_args (rejet des flags réservés).
- infra : build_argv partagé avec build_spawn_spec, émission -hf vs --model,
  ordre argv figé ; migration model-servers.json V1 -> V2.
- app-tauri : DTO V2 (modelSource, compat modelPath, conflit = INVALID),
  commande preview_model_server_command.

QA : cargo test ciblés verts (domain 7, application 8, infra model_server 4,
app-tauri dto 5). Échecs des suites complètes infra/app-tauri environnementaux
(sandbox bind/socket), hors périmètre.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 14:31:54 +02:00
b82ac76f8b feat(model-server): backend modèles locaux — serveur llama.cpp intégré (#35) et profils OpenCode locaux multiples (#36)
Sprint « Modeles locaux », couche backend (domain/application/infra/app-tauri).

#35 — Serveur de modèle local intégré (llama.cpp) :
- domain: model_server.rs (agrégat + statut), ports ModelServerProbe /
  ManagedProcess / ModelServerRuntime / ModelServerRegistry, événements
  model_server_status_changed et agent_launch_failed.
- application: use case EnsureLocalModelServer branché sur LaunchAgent.
- infrastructure: adapters HttpOpenAiCompatibleProbe, LlamaCppRuntime,
  LocalManagedProcess, FsModelServerRegistry.
- app-tauri: DTO plat LocalModelServerConfigDto, commandes
  list/save/delete_model_server avec garde model_server_in_use, wiring.

#36 — Profils OpenCode locaux multiples :
- domain: VO LocalModelServerId, OpenCodeConfig.local_model_server_id.
- application: use case CloneOpenCodeProfileFromSeed.
- app-tauri: commande clone_opencode_profile_from_seed, DTO/wiring.

Tests verts (exécution réelle) : domain+application OK, app-tauri
dto_model_servers 3/3 et dto_profiles 10/10, infra model_server 2/2,
application model_server+profile_usecases 19/19, cargo build workspace Finished.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 15:50:08 +02:00
4e70631c40 feat(opencode): remplace le provider Ollama par llama.cpp
Le tool-calling local ne fonctionnait jamais via Ollama. Refonte du
support local d'OpenCode autour de llama.cpp: profil, catalogue,
matérialisation de la config OpenCode et surface first-run alignés sur
llama-server (backend + frontend).

QA vert (commandes réelles): domain 244, application 81+64, infra 263
(10 échecs = bind-port sandbox identiques sur develop, non-régression),
frontend 574, tsc propre. Réserve E2E live non bloquante faute de
llama-server joignable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:41:19 +02:00
eaba05d27d feat(opencode): remplace le profil Ollama HTTP par OpenCode
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 15:48:07 +02:00
3abf2fce98 fix(runtime): detection non bloquante, bornée dans le temps, HTTP pour les profils locaux (#28)
Le first-run wizard restait figé : `CliAgentRuntime::detect` construisait un
runtime tokio courant-thread via `futures_block_on` pour piloter un
`ProcessSpawner` async. Appelé depuis le runtime async de Tauri, ce
`block_on` imbriqué panique ; la commande IPC ne répond alors jamais, la
promesse `detectProfiles` reste pendante et `busy` ne redescend plus.

Ce commit s'attaque à la cause côté backend :

- `AgentRuntime::detect` devient `async fn` (`#[async_trait]`) : le port cesse
  de mentir sur sa nature. Il pilote un spawner async, il est async. Le
  `futures_block_on` disparaît, et avec lui le runtime imbriqué.
- La sonde CLI est bornée par `tokio::time::timeout(DETECTION_TIMEOUT)` :
  un binaire qui ne rend jamais la main dégrade la détection en `Err`, il
  ne gèle plus l'appelant.
- Les profils `StructuredAdapter::OpenAiCompatible` n'ont pas de CLI à
  spawner : les sonder revenait à tester un binaire inexistant. Ils sont
  désormais sondés par un GET HTTP sur l'endpoint `/models` dérivé de
  `chat_http.endpoint`, borné par le même timeout.

`DetectProfiles` séquence les `await` sur les candidats. Les fakes
`AgentRuntime` des crates application/infrastructure/app-tauri suivent la
nouvelle signature.

Tests: cargo test -p domain (241), -p application (81), -p infrastructure
(269), -p app-tauri (63+15) — verts. `rg futures_block_on crates` sans match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:11:14 +02:00
aab4bcafb6 feat(session): adapter HTTP OpenAI-compatible pour profils locaux/LAN (#14)
Ajoute un adapter de session HTTP OpenAI-compatible, purement additif,
permettant d'intégrer des modèles locaux/LAN comme profils IdeA canoniques
avec parité tool-calling/MCP.

- domain: extension du profil et des ports pour l'adapter OpenAI-compatible
- infrastructure: adapter openai_compat + routage factory
- app-tauri: mapping des outils OpenAI (openai_tools) + wiring state/lib
- application: catalogue d'agents + tests de use-cases profils

Validé QA (backend GO): round-trip byte-identique Claude/Codex, mapping
erreurs, dégradation tools, conversation_id None, conformance un seul Final,
routage factory. Suites vertes domain 467 / application 530 /
infrastructure 523 / app-tauri 247, 0 échec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:09:16 +02:00
961a2cadc4 fix(assistant): retire le plan sandbox project_read_only de l'assistant de ticket (#25)
La surface « assistant de ticket » imposait un SessionPlan Landlock
restrictif (project_read_only) qui gouvernait la classe exec de la
sandbox OS et empêchait le spawn de la CLI (EACCES / os error 13).

factory.start reçoit désormais SessionPlan::None + sandbox absent :
l'assistant n'impose plus ce plan OS-sandbox. La classe exec n'est
plus verrouillée, le spawn de la CLI aboutit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 17:28:30 +02:00
5417bd756b feat(tickets): suppression d'un ticket (backend + frontend)
Ajoute la suppression complète d'un ticket (#6).

Backend Rust :
- port IssueStore::delete (NotFound si absent) + event IssueDeleted{freed_sprint}
- FsIssueStore::delete : supprime .ideai/tickets/<N>/ et l'index sous lock
- use case DeleteIssue + adaptations des tests sprint/ticket_assistant au port
- commande Tauri ticket_delete + câblage events/state/lib

Frontend :
- gateway delete (ports, adapter ticket + mock, domain)
- useTicketDetail : retrait de la liste et fermeture du détail via event issueDeleted
- intégration TicketDetail + tests

Tests verts : application/issue_usecases (6), infrastructure/issue_store (7),
app-tauri --lib (56), frontend vitest (503), npm run build (exit 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 06:27:09 +02:00
1fdf62c089 feat(tickets): création d'un assistant IA de ticket (backend + frontend)
Ajoute le chat assistant IA attaché à un ticket (#8).

Backend Rust :
- use cases OpenTicketAssistant/CloseTicketAssistant + tests
- politique d'outils par agent (domain/agent_tool_policy) et policy MCP
- store de contexte assistant + gabarit default_ticket_assistant.md + tests
- events TicketAssistantOpened/Closed
- commandes Tauri open_ticket_chat/close_ticket_chat et câblage state/lib/events

Frontend :
- gateway (ports, adapters ticket + mock, domain)
- hook useTicketAssistant + composant TicketAssistantPanel
- intégration dans TicketDetail

Tests verts : vitest tickets.test.tsx (23), cargo test application::ticket_assistant (1),
infrastructure::assistant_context_store (2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 06:06:24 +02:00
48b652ff91 feat(sprints): modèle de sprints — domaine, use-cases, persistance et surfaces (backend)
Ticket #10 — introduction du modèle de sprints côté backend.

- Domaine : nouvel agrégat Sprint (sprint.rs), IDs, événements et invariants ;
  rattachement des issues à un sprint (issue.rs) et ports associés.
- Application : use-cases sprints (application/src/sprints) + erreurs dédiées.
- Infrastructure : store de sprints (infrastructure/src/sprints.rs), adaptation
  du store d'issues et exposition MCP via orchestrator/mcp/tickets.rs.
- app-tauri : commandes, state et events pour piloter les sprints depuis l'UI.

Tests domaine/application/infra/app-tauri verts (sprint_usecases, sprint_store,
issue_store, mcp_server).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 10:57:33 +02:00
eb9cc16181 feat(background): livraison auto au propriétaire + projection des tâches de fond dans le work-state (T1+T3)
T1 — Wake automatique du propriétaire à la complétion.
La complétion d'une tâche de fond est désormais livrée à l'agent
propriétaire dès que la session accepte l'envoi (wake.rs :
mark_completion_delivered au send accepté), via un drain de flux dédié
(structured.rs : drain_reply_stream_with_readiness). L'inbox médiée
enfile l'item sans démarrer de tour ni marquer l'agent busy
(input/mod.rs : enqueue FIFO silencieux). Régression couverte
(tests/agent_wake.rs, tests input/mod.rs).

T3 — Tâches de fond projetées dans le read-model du panneau Work.
AgentWorkState porte désormais background_tasks
(VO AgentBackgroundTaskState), alimenté par un builder best-effort
with_background_tasks(store) : union list_open_for_agent + dispatch des
completions non livrées par owner_agent_id, erreur store => Vec vide
(aucune régression live/busy/tickets). DTO Tauri backgroundTasks et
wiring du BackgroundTaskStore côté state.rs. Le frontend, déjà câblé,
affiche Cancel/Retry (mapping queued/waiting -> pending, tri sur
updatedAtMs). Borne V1 : une tâche terminale déjà livrée n'est plus
énumérable (Retry limité à la fenêtre non livrée).

Tests : cargo build --workspace OK ; cargo test -p application /
-p app-tauri / -p infrastructure verts ; frontend build + vitest verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:21:33 +02:00
f08dae62eb feat(background): déclencheur in-app des tâches de fond via MCP idea_run_in_background (BE-1+BE-2)
Ferme la boucle B8 côté surface agent : un agent peut lancer une commande
en tâche de fond depuis l'app, sans dépendance à un déclencheur externe.

- domain : variante OrchestratorCommand::RunInBackground.
- infrastructure : outil MCP idea_run_in_background (déclaration + schéma +
  map_tool_call), tests de mapping et compteur de catalogue.
- application : handler → SpawnBackgroundCommand (owner=requester, WakeOwner).
- app-tauri : câblage .with_spawn_background_command + catalogue MCP 23→24.

Tests verts : domain 452 / application 467 / infrastructure 489 /
app-tauri 236, build --release vert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:44:48 +02:00
41278bd632 Merge branch 'feature/issue-ticket-system' into feature/background-tasks-first-class
Intègre le système de tickets/issues V1 (validé QA vert de bout en bout,
mémoire tickets-v1-e2e-validated-qa) dans la branche des tâches de fond.
Conflits résolus : app-tauri/state.rs, domain/events.rs, domain/lib.rs, domain/ports.rs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:15:43 +02:00
8de7be01a8 feat(tickets): checkpoint backend V1 du système de tickets (T1-T5)
Checkpoint de travail — QA formelle encore à venir (après reset Codex).
`cargo build` workspace OK, tests ticket/issue verts (domaine, store,
use cases, app-tauri 47/51). Le domaine s'appelle `Issue`, exposé
« ticket » côté MCP/UI.

- T1 domaine : entité Issue (statut, priorité, liens, carnet),
  events, ids, ports.
- T2 infra : store FS Markdown + allocator de références #N.
- T3 application : use cases Issue (create/read/list/update/status/
  priority/carnet/link/unlink/assign) + erreurs dédiées.
- T4 surface MCP : 10 outils publics idea_ticket_* (23 outils au
  total), mappés vers les use cases Issue.
- T5 app-tauri : commandes Tauri ticket_* + DTOs d'events Issue +
  câblage state.rs.

Dette de test PRÉ-EXISTANTE héritée de develop (4 tests app-tauri
mcp_e2e_loopback / mcp_serve_peer rouges) hors périmètre tickets,
non traitée ici.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 22:34:04 +02:00
f94b54239b feat(application): réveil d'agent et rendez-vous comme tâche de fond (B5-B6)
Orchestration du réveil (wake) d'un agent sur complétion/message et
traitement du rendez-vous inter-agent comme tâche de fond de 1re classe.
Couvert par agent_wake (vert).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 15:47:34 +02:00
a9653bc417 fix(orchestrator): délégation inter-agent toujours headless, jamais d'injection PTY
La conversation inter-agent (idea_ask_agent) doit rester strictement
headless : la cible répond via une session structured capturée par IdeA,
sans jamais écrire dans le PTY d'une cellule visible ni fermer le terminal
que l'utilisateur observe.

- lifecycle: flag `allow_structured_alongside_pty` — ouvre une session
  structured pour la délégation sans fermer le PTY visible (coexistence).
- orchestrator/service: `ensure_structured_session` ne ferme plus le PTY
  visible ; mapping typé de l'erreur no-reply ; coexistence PTY/structured.
- infrastructure/input: garantit zéro `DelegationReady` et zéro write PTY
  pour une délégation headless, même quand l'entrée est `front_owned`.
- app-tauri (commands/state): câblage du flag de coexistence.
- tests: fixtures portées vers le modèle structured/headless, assertions
  cibles mises à jour (aucun #[ignore] ajouté, aucun test retiré).

Validé réel : cargo build OK ; orchestrator_service 60/0, agent_lifecycle
62/0, structured_launch_d3 22/0, infrastructure input 35/0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 10:22:06 +02:00
1fc7869160 agent conversation fix 2026-06-27 12:42:37 +02:00
e916ecd95e fix(layout): auto-réparer l'onglet actif
stale

Le backend renvoie l'id actif autoritaire et le
    frontend l'adopte pour éviter de rejouer un layout
    disparu après overwrite externe.

Co-Authored-By: Claude Opus 4.8
    <noreply@anthropic.com>
2026-06-25 16:23:40 +02:00
40ca3e522f feat(domain,infra,app): rotation/rétention log.jsonl + lecture paginée (LS6)
Backend uniquement (UI React repoussée à LS7) :
- domain : port ConversationArchive + structs SegmentStats/PageCursor/PageDirection/
  TurnSlice/RotationDecision/RotationThresholds, fn pures rotation_plan/clamp_page_limit
  + consts ; re-exports lib.
- infrastructure : impl ConversationArchive pour FsConversationLog (stats/rotate/page
  + helpers), archive segmentée hors chemin chaud.
- application : ReadConversationPage + DTO + ConversationArchiveProvider (conversation/paginate),
  RotateConversationLog (conversation/rotate), exports mod/lib.
- app-tauri : AppConversationArchiveProvider + wiring (state), rotation détachée dans
  launch_agent + commande read_conversation_page (commands), DTOs (dto),
  commande enregistrée (generate_handler!).
- tests (QA, verts) : conversation_log, conversation_rotate_paginate (nouveau),
  dto (module test). Pivots INV-LS6 et cohérence fold-après-rotation verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:58:40 +02:00
13a953cb05 feat(domain,infra,app): borne summary_md du handoff + durcissement seam LLM (LS5)
Clôt le must-have perf du handoff :
- domain : HANDOFF_SUMMARY_MAX_CHARS + bound_handoff_summary (helpers privés), re-export lib.
- infrastructure : TURN_LINE_MAX_CHARS + élision dans render_turn (summarizer), re-exports.
- application : borne entre fold et save (conversation/record), borne défensive
  dans resolve_handoff (agent/lifecycle) + module test.
- seam LLM prêt mais NON activé (aucun LLM câblé).
- tests (QA, verts) : conversation_log, conversation_record, agent_lifecycle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:29:44 +02:00
533a9c57f3 feat(domain,app,infra): injection « État du projet » + outils MCP workstate (LS4)
Clôt le cold-start de coordination du programme live-state :
- domain : WorkStatus::parse/label, commands ReadWorkState/SetWorkState
  (request status/intent/progress/lastDelegation), erreur UnknownWorkStatus + validate.
- application : section bornée « # État du projet » injectée au lancement
  (port LiveStateLeanProvider, InjectedLiveRow, LIVE_STATE_INJECT_MAX, resolve_live_state) ;
  LiveStateReadProvider + read_workstate/set_workstate câblés au dispatch.
- infrastructure : 2 ToolDef idea_workstate_read / idea_workstate_set (mapping,
  tool_returns_reply), compteur d'outils MCP 12→14.
- app-tauri : providers câblés, garde du nombre d'outils 12→14.
- tests (QA, verts) : domain/orchestrator, application lifecycle + orchestrator_service,
  infrastructure mcp_server, app-tauri state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 08:09:57 +02:00
9d46e6cd21 feat(app): auto-update live-state depuis le cycle de délégation (LS3)
Branche la mise à jour automatique du live-state sur le cycle de
délégation de l'orchestrateur, en best-effort non bloquant.
- Trait `LiveStateProvider` (résolution par root) + champ `Option` dans
  l'`OrchestratorService`, injecté via le builder `with_live_state` ;
  `None` ⇒ aucun effet (zéro régression).
- Transitions dérivées du cycle : `ask` → entrée `Working`,
  `reply` → `Done` + `last_delegation`.
- Helpers best-effort : un échec de mise à jour du live-state ne
  transforme jamais un succès de délégation en erreur.
- `AppLiveStateProvider` + wiring côté app-tauri (provider par root).

cargo test -p application : 0 échec ; --test orchestrator_service : 55/0
(dont 4 nouveaux tests LS3) ; cargo fmt --all --check : exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 19:28:02 +02:00
11dc8d7ba3 feat(skill-awareness): T1→T5 — manifeste de skills + outil MCP idea_skill_read
Surface les skills assignés à un agent « à la MCP » : description sur l'entité
Skill (+ effective_description fallback), section « # Skills disponibles » haute
altitude dans le convention-file (mode MCP), outil read-only idea_skill_read
(résolution projet→global), use case ReadSkill (port SkillStore existant), et
câblage au composition root. Dump legacy du corps complet conservé en mode
sans-MCP (zéro régression). Rétro-compat index.json (serde default).

Tests : domain+application+infrastructure 1212 passed / 0 failed (23 ajoutés).
Reste : T6 (champ description front) + T7 (e2e après rebuild AppImage).

NB topologie : commit réalisé par l'orchestrateur car l'agent Git était
injoignable (bug de livraison cold-start) ; à faire relire/rebaser par Git.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 18:30:47 +02:00
9684b7bbec fix(delegation): résout idea_ask_agent quand la cible revient au prompt sans idea_reply
Introduit le seam TurnResolution : sur prompt-ready, après un délai de grâce,
la délégation en attente est résolue de façon typée plutôt que de rester bloquée
indéfiniment (jusquà 24h). Couvre domain (mailbox), infrastructure (mailbox/input)
et application (orchestrator/error), avec tests étendus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 10:06:29 +02:00
b12081be18 feat(memory): récolte automatique contrôlée de la mémoire (Lot E1)
Câble la récolte automatique de mémoire de bout en bout, sous contrôle
explicite, du domaine jusqu'à l'UI :

- domain : modèle `memory_harvest` (candidats de récolte, décision) +
  exports `lib.rs`.
- application : use case `memory/harvest` branché dans `memory/mod`,
  `lib.rs`, le cycle de vie d'agent (`agent/lifecycle`) et
  l'orchestrateur (`orchestrator/service`).
- app-tauri : wiring runtime dans `state`.
- frontend : DTO `domain/index` + hook `useMemory`.

Couverture : domain `memory_harvest` (14), application `memory_harvest`
(5) et `orchestrator_service` (récolte, 4), plus patch test-only du mock
gateway `workState` (`mock.test.ts`) et UI `memory.test`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 02:00:05 +02:00
3408c96974 feat(workstate): actions contrôlées sur le work-state (Lot D backend)
Ajoute un module d'actions contrôlées (`workstate/actions.rs`) côté application
et l'expose via des commandes Tauri : DTO camelCase, câblage commands/state/lib.
Les actions valident leurs invariants avant d'agir sur le read-model.

- application : use cases d'actions contrôlées + intégration au work-state.
- app-tauri : commandes dédiées, DTO et enregistrement dans le handler.

Tests verts : application workstate_actions (7), workstate (21),
app-tauri dto_agents (25), list_live_agents_r0b (5), cargo check OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:51:46 +02:00
e9edadca50 feat(workstate): résumés de conversation dans le read-model (Lot C backend)
Étend le read-model work-state pour exposer un résumé par conversation
(participants, dernier message/activité, compteurs) câblé jusqu'au DTO
camelCase de l'état app-tauri.

- application : assemblage des résumés de conversation dans le work-state.
- app-tauri : DTO camelCase des résumés + exposition dans l'état.

Tests verts : application workstate (21), app-tauri dto_agents (21),
aucun warning Rust.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:28:55 +02:00
cc7d99a63e feat(workstate): snapshot des délégations en file par agent (Lot B backend)
Ajoute un port de lecture ségrégué `AgentQueueSnapshot` (ISP) distinct du
`AgentMailbox` mutant : il expose `queue_for(agent)` qui renvoie des
`QueuedTicketSnapshot` clonés, ordonnés FIFO, avec position recalculée
(0 = tête). Observer la file ne la mute jamais ; le one-shot reply sender
reste dans l'adaptateur.

- domain : value object `QueuedTicketSnapshot` + trait `AgentQueueSnapshot`
  (object-safe, partagé en `Arc<dyn …>`).
- infrastructure : `InMemoryMailbox` implémente la vue lecture en plus de la
  vue mutation ; positions recalculées à chaque appel.
- application : le read-model work-state liste les délégations en attente via
  ce port, troncature de l'aperçu de tâche en conservant la longueur d'origine.
- app-tauri : DTO `camelCase` des tickets en file câblé dans l'état.

Tests verts : domain mailbox (6), infrastructure mailbox (13),
application workstate (12), app-tauri dto_agents (20).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:52:16 +02:00
aae18499a9 feat(workstate): read-model live-state minimal des conversations/délégations (Lot A backend)
Introduit le module application `workstate` (modèle de read-model live + snapshots
des conversations/délégations en cours) et l'expose via la couche terminal
(exports mod/registry). Câble la surface Tauri : DTO, commande et state pour
exposer le live-state au frontend (lib + state + commands), avec tests.

QA verte. Réserve environnementale non bloquante : tests loopback socket Unix
réels non exécutables en sandbox (UnixListener::bind PermissionDenied),
alternatives avec skips vertes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:06:22 +02:00
287681c198 feat(orchestrator): modèle de désignation d'orchestrateur + sink de diagnostic
Introduit le modèle AgentManifest { version, entries, orchestrator } et la
garde d'écriture directe may_write_directly(..., &OrchestratorDesignation) :
seul l'orchestrateur désigné peut écrire directement, les autres passent par
le rendez-vous médié. Câble la désignation à travers domain → application →
infrastructure → app-tauri (context_guard, service, lifecycle, ports).

Ajoute crates/application/src/diag.rs : sink de diagnostic best-effort, sans
dépendance, qui miroite les traces du rendez-vous inter-agents de
l'orchestrateur vers un fichier de log persistant (utile au lancement via
AppImage où stderr est jeté), avec la même discipline « zéro dépendance,
ne casse jamais le rendez-vous ».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 08:56:39 +02:00
c480d2820a feat(session-limits): LS8-backend — filet humain niveau 3 (set_resume_at)
Permet à l'humain de confirmer/forcer l'heure de reprise quand le
niveau 2 a détecté une limite sans heure exploitable.

- application/agent/session_limit.rs : refactor privé arm_scheduled
  (param resets_at_ms brut) partagé par on_rate_limited + nouvelle
  confirm_human_resume(agent_id, node_id, conversation_id, resets_at_ms)
  (source Human, réutilise la branche Scheduled, annulable).
- app-tauri/commands.rs : commande set_resume_at(agent_id, resets_at_ms)
  (résout node_id via node_for_agent + conversation_id best-effort,
  NOT_FOUND si pas de cellule vivante).
- app-tauri/lib.rs : set_resume_at enregistrée après cancel_resume.

Réutilise les événements existants (AgentRateLimited + AgentResumeScheduled),
aucun nouvel événement. Tests : +6 session_limit_service, +2 wiring, verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 08:44:45 +02:00
9df592389c feat(session-limits): LS7 — câblage backend app-tauri (taps niveaux 1&2 + reprise annulable)
Branche le SessionLimitService dans l'application Tauri et expose la
surface de reprise/annulation au front.

- application/agent/lifecycle.rs : LaunchAgentOutput.profile exposé
  (None sur réattache/idempotent, Some sur lancement effectif).
- application/terminal/registry.rs : StructuredSessions::meta_for_session()
  (lookup agent/node par SessionId pour le tap niveau 1).
- app-tauri/state.rs : ResumeContext(s), AppAgentResumer (impl du port
  AgentResumer au-dessus de LaunchAgent), instanciation + câblage du
  SessionLimitService (TokioScheduler + drain des réveils) dans AppState::build.
- app-tauri/commands.rs : taps niveau 1 (agent_send) et niveau 2
  (launch_agent, parser regex confiné), alimentation de resume_contexts,
  commande cancel_resume.
- app-tauri/lib.rs : enregistrement de cancel_resume dans le handler.
- app-tauri/Cargo.toml : dépendance async-trait.

Tests : session_limit_wiring.rs (2 tests de composition) + meta_for_session
dans structured_registry_d1.rs ; fixtures dto_agents/dto_chat ajustées
(profile: None). Tout vert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 07:56:08 +02:00
9000b4d09f feat(session-limits): LS4 — service application + réconciliation T4
Orchestre la détection et la reprise au niveau application :
- session_limit.rs (nouveau) : SessionLimitService + port AgentResumer +
  const RESUME_PROMPT.
- structured.rs : réconciliation T4 — enum TurnOutcome +
  drain_with_readiness_outcome ; signatures historiques préservées.
- agent/mod.rs + lib.rs : modules et re-exports.

Tests QA (nouveaux) : tests/session_limit_service.rs (9) +
tests/session_limit_t4.rs (7).
`cargo test -p application` = tous binaires verts / 0 failed (16 nouveaux),
zéro régression (drain_with_readiness_lot1 7/7, send_blocking_d1 9/9) ;
builds domaine+infra+application 0 warning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 18:55:26 +02:00
6236cd727b feat(permissions): LP4-4 — enforcement Landlock sur le chemin structuré
Étend l'enforcement OS au chemin structuré (sessions Claude/Codex mode JSON),
jusqu'ici seulement advisory. Approche validée par l'Architecte : transposer la
technique du PTY plutôt qu'un pre_exec (rejeté — landlock alloue, deadlock malloc
post-fork en process multithreadé).

Mécanique (cfg(target_os=linux)) : run_turn_sandboxed/drain_sandboxed exécutent
enforce(plan) sur un thread jetable AVANT le spawn std, puis std::process::spawn
depuis ce thread ; l'enfant hérite le domaine Landlock via les credentials de la
tâche (garanti à travers fork/clone/execve, y compris posix_spawn — pas de pre_exec
nécessaire, forbid(unsafe_code) préservé). Fail-closed sur Err d'enforce (aucun
child). Timeout sous sandbox : oneshot killer + tokio::time::timeout → kill → EOF →
reap (pas de zombie/thread bloqué). Chemin non-sandboxé (plan None / pas d'enforcer /
non-Linux) = drain async tokio inchangé.

Contrat : SpawnLine.sandbox ; AgentSessionFactory::start(.., sandbox) ;
StructuredSessionFactory::with_sandbox_enforcer (jumeau du PTY) ; plan calculé en
step 5d de lifecycle relayé à launch_structured ; default_enforcer() injecté au
composition root.

Tests : 7 invariants e2e (parité, companion négatif, fail-closed, no-op natif,
confinement de l'irréversibilité entre tours, timeout, resume préservé) — zéro
token (sh/FakeCli). 80 suites vertes, 0 failed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 08:47:33 +02:00
b05d04ab7a feat(permissions): LP4-0 — fondations domaine de l'enforcement OS (pur)
Fondations pures (zéro I/O, zéro dépendance landlock, aucun câblage
runtime — SpawnSpec.sandbox posé mais jamais lu ⇒ zéro régression) de la
voie « airtight » des permissions, complément de la voie projection LP3.

- domain/sandbox.rs : SandboxPlan/PathGrant/PathAccess (RO|RW|EXEC),
  SandboxContext, SandboxKind/Status/Error, port SandboxEnforcer, et la
  fonction pure compile_sandbox_plan(EffectivePermissions → plan OS).
- domain/permission.rs : render_permission_summary (bloc Markdown injecté
  plus tard ; mentionne explicitement fichiers OS-enforced vs commandes
  advisory).
- domain/ports.rs : SpawnSpec.sandbox: Option<SandboxPlan> (None ⇒ natif),
  propagé à tous les sites de construction.

Sémantique de compile_sandbox_plan :
- Invariant produit : eff == None ⇒ None (rien posé ⇒ CLI 100 % native).
- Borne Landlock : seules les capabilities fichier produisent des grants
  (Read→RO, Write/Delete→RW) ; ExecuteBash reste advisory (non verrouillable
  par chemin).
- Deny-wins PAR CLASSE D'ACCÈS (RO/RW), fail-closed intra-classe : un Deny
  ne ferme que les grants de sa propre classe (un Deny Write n'ampute pas un
  Allow Read). Choix retenu pour maximiser l'autonomie des agents : on
  respecte exactement la politique pré-renseignée sans sur-restreindre, donc
  moins de blocages qui forceraient l'agent à redemander l'utilisateur.
- Globs réduits à leur préfixe statique ; grant abandonné si une barrière de
  même classe chevauche (égal/ancêtre/descendant) — sous-approximation
  conservatrice (un sandbox additif ne peut pas carver un deny sous-arbre).

Tests : 16 tests purs sur sandbox + 3 sur render_permission_summary,
cargo test --workspace 100 % vert, 0 ignored.

Reste LP4 : LP4-1 adapter LandlockSandbox + pre_exec PTY (fail-open+warning
sauf posture Deny), LP4-2 câblage application, LP4-3 composition root.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:37:34 +02:00
27597eb64e feat(permissions): voie projection CLI (LP0→LP3) + checkpoint Codex/input
Jalon vert regroupant deux chantiers entrelacés dans le working tree,
indissociables au niveau fichier mais tous deux verts (cargo test
--workspace + tests frontend permissions au vert).

Permissions — voie « projection CLI » (advisory), complète :
- LP0 domaine pur : modèle PermissionSet/EffectivePermissions, resolve
  deny-wins + postures Allow<Ask<Deny (crates/domain/src/permission.rs).
- LP1 store : FsPermissionStore (.ideai/permissions.json).
- LP2 use cases : Get/Update project, Update agent override, Resolve.
- LP3 projecteurs Claude/Codex (settings.local.json / config.toml),
  câblage launch-path + PermissionProjectorRegistry, nettoyage des
  fichiers Replace orphelins au swap de profil (LP3-4), composition root
  + commandes Tauri, UI PermissionsPanel (projet + override agent).
- ports.rs : PermissionStore + FileSystem::remove_file (cleanup au swap).

Reste ouvert (hors scope, marqué dans le code) : LP4 enforcement OS
airtight (Landlock fichiers) + résumé de permissions injecté.

Inclut aussi le chantier Codex/input/sessions structurées en cours
(McpConfigStrategy, StructuredAdapter, gestion d'input) partageant les
mêmes fichiers (lifecycle.rs, commands.rs, dto.rs, state.rs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:39:18 +02:00
46492506e1 fix(orchestrator): garde RAII contre une cible coincée Busy après une délégation interrompue
Une délégation `idea_ask_agent` interrompue/annulée côté demandeur (futur
`ask_agent` *dropped*) laissait la cible en `Busy` à vie : sur un drop, aucune
branche du `select!` ne s'exécute, donc `mark_idle` n'était jamais appelé. Toute
délégation suivante restait en file derrière ce tour fantôme et n'était jamais
livrée au PTY (symptôme : « DevFrontend/QA ne répondent plus », pont MCP pourtant
ESTAB).

Ajoute un garde RAII `BusyTurnGuard` posé juste après l'enqueue sur les deux
chemins (`ask` PTY et `ask_structured`) : son `Drop` fait `cancel_head` +
`mark_idle` quoi qu'il arrive (erreur, timeout, drop), sauf `disarm()` sur la
branche succès. Indispensable d'être un garde et non un `mark_idle` dans les
branches : le cas réel est un futur droppé. Retire les `cancel_head` redondants
des branches erreur/timeout (positionnel/idempotent).

`sweep_stalled` reste advisory (n'appelle jamais `mark_idle`) — non concerné.

Tests: dropped_ask_future_frees_busy_target, second_delegation_delivered_after_dropped_ask,
cancelled_ask_marks_target_idle + 2 tests du garde. cargo test --workspace vert (80 suites).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 19:15:01 +02:00
0f8ba38d51 feat(agents): pont Codex inter-agents + readiness/heartbeat lot 1
Deux chantiers livrés au vert (workspace entier : domain+application+
infrastructure 42 + app-tauri --lib 128, 0 échec).

## Codex inter-agents
- domaine: McpConfigStrategy::TomlConfigHome { target, home_env } +
  toml_config_home(...); AgentProfile::materializes_idea_bridge()
  (whitelist Claude/ConfigFile + Codex/TomlConfigHome); McpServerWiring
  + encodeur TOML.
- application: lifecycle apply_mcp_config bras TomlConfigHome (écrit
  {runDir}/<target>, pousse (home_env, parent) dans spec.env);
  guard_mcp_bridge_supported ré-exprimée via materializes_idea_bridge();
  catalogue Codex porte toml_config_home(".codex/config.toml","CODEX_HOME").
- app-tauri: is_codex_mcp_profile, migrate_codex_run_dir,
  mcp_server_entry_toml.
- tests: matrice domaine TomlConfigHome + round-trip dual Claude/Codex
  sur loopback réel (fakes, zéro token).

## Readiness/heartbeat lot 1
- domaine: readiness.rs — ReadinessPolicy::classify (Final => TurnEnded),
  variantes ReplyEvent::Heartbeat / ToolActivity.
- application: drain_with_readiness consulte la policy et appelle
  mark_idle sur le signal déterministe; branché dans ask_agent.
  Corrige la cause racine: une cible qui ne renvoie qu'un Final (sans
  idea_reply) débloque désormais sa file Busy.
- infrastructure: adapters de session émettent Heartbeat/ToolActivity.
- tests: drain_with_readiness_lot1 (points QA 5 & 6) verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:28:44 +02:00
fdcf16c387 chore(wip): checkpoint P8/C avant chantier Codex inter-agents
Sauvegarde de l'arbre de travail en cours (persistance P8, conversations
C-series, write-portal frontend, médiation d'entrée) avant d'attaquer le
support de la délégation inter-agents pour les profils Codex.

Le round-trip inter-agent question/réponse est couvert sans tokens par
les tests loopback existants (state::mcp_e2e_loopback_tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:42:53 +02:00
4509f0db9d feat(persistence): P8d — swap cross-profile préserve l'id de paire + handoff
Capstone du chantier handoff : un agent change de moteur (Claude↔Codex) en
gardant la continuité du travail.

- clean_conversation → invalidate_engine_link : préserve conversation_id (id de
  paire stable) et n'efface que engine_session_id (lien moteur étranger) ;
  renvoie l'id de paire préservé
- relaunch_if_live relance avec l'id de paire (repli for_pair si session de
  fond) ⇒ handoff P7 réinjecté dans le nouveau moteur, resume P8c routé via
  providers.json[nouveau provider] (vide ⇒ SessionPlan::None : l'ancien
  resumable n'est jamais repassé ; fidélité par le handoff)
- tests : 4 cas swap (préservation id de paire, engine_session_id vidé, handoff
  réinjecté, pas de --resume de l'ancien moteur, repli for_pair) ;
  change_agent_profile 12 verts, agrégat 829 passed 0 failed

Chantier persistance conversationnelle + handoff cross-profile (P1→P8d) complet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:52:50 +02:00
d87b8f6ed2 feat(persistence): P8c — routage --resume moteur via providers.json (structuré)
Referme la régression latente de P8a : pour un profil structuré, le resumable
passé au moteur vient de providers.json[provider] (vrai id Claude/Codex), jamais
de l'id de paire désormais porté par la cellule.

- resolve_session_plan devient async + prend root ; branche structurée câblée :
  get(pair, provider_key) ⇒ Resume{engine_id}, sinon None ; repli gracieux sur
  l'ancien comportement si le store n'est pas câblé (zéro régression tests) ;
  branche non structurée strictement inchangée
- tests : 6 cas (claude/codex Resume{engine} avec assert_ne! vs id de paire,
  discrimination de clé provider, store vide/cellule neuve/non-uuid ⇒ None) ;
  structured_launch_d3 22 verts, domain+application+app-tauri sans régression

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:42:35 +02:00
9b053216e3 feat(persistence): P8b — écriture providers.json (resumable par provider)
Au lancement structuré, le resumable exposé par le moteur est rangé dans
providers.json sous (id de paire, provider) — best-effort, n'altère jamais le
lancement.

- domaine : StructuredAdapter::provider_key() ⇒ "claude"/"codex" (clé stable
  par famille, lisible, indépendante de l'uuid d'instance)
- application : port ProviderSessionProvider (root par appel) +
  with_provider_session_provider ; helper persist_provider_session dans
  launch_structured (skip si provider/adapter/id moteur absents ou set KO)
- app-tauri : AppProviderSessionProvider (FsProviderSessionStore sur le root)
- tests : 6 cas (nominal claude/codex, no-op sans provider/sans id moteur,
  best-effort, mapping provider_key) ; domain+application verts

Lecture du store pour --resume = P8c.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:36:19 +02:00
09cc8f0902 test(persistence): P8a — déterminisme registre + cohérence de clé end-to-end
Scelle la cohérence corrigée en P8a :
- infra (5) : resolve stable à travers 2 instances de registre (restart),
  resolve.id == for_pair == uuid agent, commutativité, distinction des paires
- application (2) : round-trip réel « handoff sauvé sous la clé resolve ↔
  rechargé au relancement de la cellule portant cette clé » + contre-épreuve
  (handoff d'une autre paire ⇒ pas de reprise)

Clarifie : une cellule neuve (conversation_id=None) ne charge pas de handoff ;
la reprise opère à la réouverture. domain/infra/application verts, zéro
régression. Code de prod non modifié.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:27:09 +02:00
e583b2b49f feat(persistence): P8a — cohérence de clé conversation (id de paire stable)
Corrige l'incohérence P6/P7 : la cellule et la persistance partagent désormais
le MÊME id de paire IdeA, déterministe et stable au redémarrage — la clé sous
laquelle P6b sauve log/handoff == celle sous laquelle P7 les charge.

- domaine : ConversationId::for_pair(a,b) pur/déterministe (User↔Agent = uuid
  agent ; Agent↔Agent = XOR commutatif) ; LeafCell gagne engine_session_id
  (cache resumable moteur, additif serde default)
- infrastructure : InMemoryConversationRegistry::resolve utilise for_pair au
  lieu de new_random() → id recalculable sans état, identique après restart
- application : launch_structured persiste l'id de paire sur la cellule (et l'id
  moteur sur engine_session_id) ; cellule neuve ⇒ dérive for_pair(User,agent) ;
  resolve_conversation repli factorisé sur for_pair ; withers layout clone-and-
  mutate (préservent les champs additifs)
- app-tauri : engine_session_id propagé dans les DTO

Suites domain/infra/application/app-tauri vertes (assertion structured_launch_d3
recodée sur le contrat). Test déterminisme/restart dédié à ajouter au retour de
quota agent (binôme Test interrompu par limite de session).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:10:50 +02:00
c1411d3b69 feat(persistence): P7 — reprise par injection du handoff au lancement
Au (re)lancement, l'agent retrouve sur quoi il travaillait : si la cellule a une
conversation_id avec un handoff, son objectif + résumé sont injectés en dernière
section du convention file (# Reprise de la conversation), best-effort et
provider-indépendant.

- application : port HandoffProvider (root par appel, comme MemoryRecall) ;
  LaunchAgent.with_handoff_provider + resolve_handoff (parse uuid + load,
  jamais bloquant) ; section rendue dans compose_convention_file après la
  mémoire projet
- app-tauri : AppHandoffProvider (FsHandoffStore sur le root du projet) câblé
- tests : 4 purs (objectif présent/None/blanc/absent) + 5 intégration
  (happy path, provider absent, conversation_id None, uuid invalide, sans
  handoff ⇒ lancement OK sans section) ; application 311 + app-tauri 204 verts

Volet --resume/providers.json = P8 (swap cross-profile).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:42:19 +02:00
0bf7a5c43c feat(persistence): P6b — câblage live du checkpoint conversationnel (best-effort)
ask_agent persiste désormais chaque paire dans son conversationId : tour Prompt
à l'enqueue, tour Response au succès — best-effort (un échec de persistance ne
dégrade jamais la délégation).

- application : port RecordTurnProvider (matérialise un RecordTurn sur le bon
  project root — OrchestratorService est mono-instance multi-projets, le log est
  par root) ; wither with_record_turn(provider, Clock) ; helper
  record_turn_best_effort ; horodatage via domain::ports::Clock (pas d'horloge
  infra dans application)
- app-tauri : AppRecordTurnProvider (Fs* sur le root du projet) câblé au
  composition root avec le SystemClock partagé
- tests : 5 cas (paire Prompt→Response, fil A↔B vs User↔B, no-op sans provider,
  ask Ok même si record échoue) ; orchestrator_service 40, application +
  app-tauri verts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:31:13 +02:00
2a5873dcf0 feat(persistence): P6a — use case RecordTurn (checkpoint fin de tour, isolé)
Logique du checkpoint conversationnel isolée et testable, sans câblage live :
append du tour au log canonique → load handoff → fold incrémental (tour neuf
seul) → save. Mapping StoreError→AppError via From existant. Debounce repoussé.

- application/src/conversation/{mod,record}.rs : RecordTurn::record
- tests : 6 cas (incrémentalité prouvée via summarizer-espion, propagation
  d'erreur sans effet de bord, isolation par conversation) ; suite verte

Câblage sur les seams réels (reply/ask) + composition root = P6b.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:20:05 +02:00
eca2ba95c4 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>
2026-06-12 07:33:04 +02:00
cf89b3b9a5 feat(agent): bind transport S-MCP — outils idea_* vivants de bout en bout (M5a-e) — §14.3.1
Dernier kilomètre de l'orchestration native : une CLI MCP réellement lancée
joint le serveur MCP du projet et ses outils idea_* aboutissent au vrai dispatch.

- M5a endpoint loopback par projet (interprocess UDS/named pipe, source unique
  mcp_endpoint, cleanup au close) — zéro port réseau, AppImage/SSH-safe.
- M5b sous-commande `idea mcp-server` : pont stdio↔loopback headless (avant init
  Tauri), handshake {"project","requester"}, endpoint-absent borné, EOF propre.
- M5c McpServerHandle boucle accept + serve_peer par pair ; requester réel
  propagé jusqu'à OrchestratorRequestProcessed (fin du "mcp" figé) ; isolation
  des pairs ; terminaison propre.
- M5d apply_mcp_config écrit la déclaration réelle (current_exe + --endpoint
  mcp_endpoint + --project simple-uuid + --requester) ; McpRuntime injecté comme
  donnée (application ne dépend pas de app-tauri).
- M5e smoke e2e sur vrai loopback : list/ask inline, cible PTY → erreur typée,
  JSON malformé → pas de panic, requester propagé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 18:09:41 +02:00
6ca519b815 feat(agent): robustesse routage ask — fix registre session + concurrence (R0+A0) — §14.3/§15
Stabilise le routage de `ask` avant le bind transport MCP (v5). Invariant
« 1 agent = 1 employé » durci ; un agent traite un tour à la fois.

- R0a garde LaunchAgent : lève AgentAlreadyRunning pour un lancement neuf
  ciblant un agent déjà vivant sur un autre node (PTY + structuré) ; rebind
  seulement même-node ou réattache explicite (conversation_id). Idem spawn_agent.
- R0b list_live_agents agrège PTY + structuré (LiveSessions) + dédup.
- R0c réconciliation des layouts.json à doublons à l'ouverture (host déterministe,
  idempotent) — corrige « une cellule reset au retour d'onglet ».
- R0d UI : option agent désactivée si vivant ailleurs + « aller à la cellule »,
  mapping AGENT_ALREADY_RUNNING.
- A0 sérialisation FIFO des tours par agent_id dans ask_agent (verrou tokio par
  agent ; agents différents en parallèle ; timeout tour 300s, cap attente 600s).

Cadrage : .ideai/briefs/orchestration-v5-transport-bind-cadrage.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 17:34:57 +02:00