diff --git a/.ideai/memory/MEMORY.md b/.ideai/memory/MEMORY.md index bc181e0..8adb35b 100644 --- a/.ideai/memory/MEMORY.md +++ b/.ideai/memory/MEMORY.md @@ -43,3 +43,19 @@ - [mcp-t10a-harness-interrupt-does-not-cancel-rendezvous](mcp-t10a-harness-interrupt-does-not-cancel-rendezvous.md) — memory note mcp-t10a-harness-interrupt-does-not-cancel-rendezvous - [mcp-t10b-pending-reboot-verification](mcp-t10b-pending-reboot-verification.md) — memory note mcp-t10b-pending-reboot-verification - [headless-interagent-conversation-objective](headless-interagent-conversation-objective.md) — memory note headless-interagent-conversation-objective +- [checkpoint-b2-bootstrap-applied-await-codex-reset-1430](checkpoint-b2-bootstrap-applied-await-codex-reset-1430.md) — memory note checkpoint-b2-bootstrap-applied-await-codex-reset-1430 +- [background-tasks-first-class-design](background-tasks-first-class-design.md) — memory note background-tasks-first-class-design +- [b7-wiring-anchor-state-rs](b7-wiring-anchor-state-rs.md) — memory note b7-wiring-anchor-state-rs +- [appimage-build-no-strip-relr-dyn-fix](appimage-build-no-strip-relr-dyn-fix.md) — memory note appimage-build-no-strip-relr-dyn-fix +- [checkpoint-b7-blocked-codex-session-limit](checkpoint-b7-blocked-codex-session-limit.md) — memory note checkpoint-b7-blocked-codex-session-limit +- [issue-ticket-system-design](issue-ticket-system-design.md) — memory note issue-ticket-system-design +- [checkpoint-issue-ticket-backend-v1-done](checkpoint-issue-ticket-backend-v1-done.md) — memory note checkpoint-issue-ticket-backend-v1-done +- [tickets-frontend-v1-done](tickets-frontend-v1-done.md) — État du frontend du système de tickets — livré, vert, décisions de contrat clés. +- [tickets-v1-e2e-validated-qa](tickets-v1-e2e-validated-qa.md) — Verdict QA du système de tickets V1 sans attachments sur feature/issue-ticket-system — vert de bout en bout, avec la seule réserve du typage de conflit côté UI. +- [b8-command-runner-pty-framing](b8-command-runner-pty-framing.md) — Cadrage hexagonal figé du lot B8 : runner concret sur le port BackgroundTaskRunner, fermeture de la boucle sink en composition root, contrat de complétion, commandes cancel/retry. +- [checkpoint-b8-command-runner-loop-closed](checkpoint-b8-command-runner-loop-closed.md) — memory note checkpoint-b8-command-runner-loop-closed +- [b8-arbitration-outcomes](b8-arbitration-outcomes.md) — Décisions d'arbitrage Architecture sur les 5 écarts B8 remontés par DevBackend : ce qui reste dans B8 vs part en dette/ticket. +- [b8-in-app-trigger-run-in-background](b8-in-app-trigger-run-in-background.md) — Arbitrage de l'écart de périmètre #1 — la surface agent MCP idea_run_in_background ferme la boucle B8, contrat et lots figés. +- [checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending](checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending.md) — memory note checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending +- [workstate-background-tasks-projection-fix](workstate-background-tasks-projection-fix.md) — Décision d'archi pour rendre visibles Cancel/Retry dans le panneau Work — extension du read-model GetProjectWorkState, contrat DTO et borne de livraison. +- [tickets-t3-frontend-validation-verdict](tickets-t3-frontend-validation-verdict.md) — Verdict de validation frontend du fix T3 (projection backgroundTasks par agent) sur feature/background-tasks-first-class — vert, avec 3 désalignements de contrat mineurs. diff --git a/.ideai/memory/appimage-build-no-strip-relr-dyn-fix.md b/.ideai/memory/appimage-build-no-strip-relr-dyn-fix.md new file mode 100644 index 0000000..121908e --- /dev/null +++ b/.ideai/memory/appimage-build-no-strip-relr-dyn-fix.md @@ -0,0 +1,52 @@ +--- +name: appimage-build-no-strip-relr-dyn-fix +description: memory note appimage-build-no-strip-relr-dyn-fix +metadata: + type: project +--- +--- +name: appimage-build-no-strip-relr-dyn-fix +description: Le build AppImage échoue sur cette machine (CachyOS/Arch) à l'étape linuxdeploy/strip (.relr.dyn) ; correctif = NO_STRIP=true. Explique probablement les "builds qui ne produisent rien". +metadata: + type: reference +--- +# Build AppImage — échec linuxdeploy `.relr.dyn`, correctif NO_STRIP=true + +## Symptôme +`tauri build --bundles appimage` : la **compilation release réussit** (binaire à +`target/release/app-tauri`), puis le **bundling échoue** : +``` +[gtk/stdout] ERROR: Strip call failed: .../usr/bin/strip: .../libgobject-2.0.so...: + unknown type [0x13] section `.relr.dyn' +ERROR: Failed to run plugin: gtk (exit code: 1) +failed to bundle project `failed to run .../.cache/tauri/linuxdeploy-x86_64.AppImage` +``` +→ **aucune AppImage produite**, exit code 1. Le `tail` du log ne montrait au début que +« failed to run linuxdeploy » sans détail ⇒ facile à prendre pour un blocage/bash de fond. + +## Cause racine (environnement, PAS le code IdeA) +Le `strip` (binutils) embarqué dans le vieux `linuxdeploy-x86_64.AppImage` ne comprend pas la +section ELF moderne `.relr.dyn` (relocations `DT_RELR`, `unknown type [0x13]`) présente dans les +libs système GTK/glibc de CachyOS/Arch (rolling). Le plugin `gtk` de linuxdeploy strip ces libs +→ échec → tout le bundling casse. + +## Correctif VALIDÉ (2026-07-02) +Lancer le build avec `NO_STRIP=true` (le binaire release est déjà strippé par cargo) : +``` +cd crates/app-tauri +NO_STRIP=true ../../frontend/node_modules/.bin/tauri build --bundles appimage +``` +Résultat : `Finished 1 bundle at target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage`. + +## Séquence de build complète (rappel, pas de beforeBuildCommand dans tauri.conf.json) +1. `npm --prefix frontend run build` (produit `frontend/dist`). +2. depuis `crates/app-tauri` : `NO_STRIP=true build --bundles appimage`. + CLI tauri = `frontend/node_modules/.bin/tauri` (@tauri-apps/cli). Config = crates/app-tauri/tauri.conf.json. + +## Implication produit +C'est très probablement la vraie cause des « builds AppImage qui ne produisent rien / attente +sans résultat » signalés par l'utilisateur. À intégrer idéalement dans le flux de build d'IdeA +(passer NO_STRIP quand on cible AppImage sur Arch, ou vendoriser un linuxdeploy récent). + +Lien : [[mcp-bridge-and-delegation-runtime-notes]], +[[checkpoint-b2-bootstrap-applied-await-codex-reset-1430]]. \ No newline at end of file diff --git a/.ideai/memory/b7-wiring-anchor-state-rs.md b/.ideai/memory/b7-wiring-anchor-state-rs.md new file mode 100644 index 0000000..0fb9996 --- /dev/null +++ b/.ideai/memory/b7-wiring-anchor-state-rs.md @@ -0,0 +1,44 @@ +--- +name: b7-wiring-anchor-state-rs +description: memory note b7-wiring-anchor-state-rs +metadata: + type: project +--- +--- +name: b7-wiring-anchor-state-rs +description: Ancre runtime précise pour câbler B7 (tâches de fond) dans app-tauri/state.rs — repérée par Main pour dé-risquer la délégation DevBackend. +metadata: + type: reference +--- +# B7 — Ancre de câblage runtime (crates/app-tauri/src/state.rs) + +Composition root = builder `AppState` dans `crates/app-tauri/src/state.rs`. Repères exacts +(lecture Main 2026-07-02, base `feature/background-tasks-first-class`) : + +- **Mailbox / inbox** construits l.1207-1242 : `InMemoryMailbox::new()` (1207) → `mailbox` + (1208, `dyn AgentMailbox`) → `MediatedInbox::with_pty(...)` (1213) → `input_mediator` + (1242, `dyn InputMediator`). C'est LA file existante ; B4 (`AgentInbox`) s'y branche. +- **clock** dispo : `Arc` (cloné partout, ex. 1255, 1384). +- **Pattern de boucle de fond OBLIGATOIRE** : `tauri::async_runtime::spawn` (PAS `tokio::spawn` + — `build` tourne dans le hook `setup` sans runtime tokio ambiant). Exemples : sweep_stalled + l.1234, drain scheduler session-limit l.1317. Le sink B3 + bridge B4 + wake B5 doivent être + spawnés sur ce même pattern. +- **Builder OrchestratorService** l.1356-1454 : chaîne `.with_input_mediator` (1369), + `.with_events`, `.with_record_turn`, `.with_live_state`/`.with_live_state_read` (providers + PAR ROOT, root fixée par appel), `.with_ask_liveness_probe`, `.with_ask_ceiling`, + `.with_structured` (1453). + **⚠ POINT CLÉ : `.with_background_tasks(store, clock)` de B6 N'EST PAS présent ici** → le + rendez-vous-as-task est DORMANT au runtime. B7 doit l'ajouter. +- **Nuance per-root** : `FsBackgroundTaskStore` écrit `/.ideai/background-tasks/.json` + (per-projet), mais `OrchestratorService` est GLOBAL multi-projets. Suivre le précédent + `AppLiveStateProvider` / `AppReconcileLiveState` (provider keyé par root, câblé dans + `open_project`, cf. commentaire l.1247-1252) plutôt qu'une instance de store globale unique. +- **Reconcile au boot** : précédent = `reconcile_live_state` (l.1252) appelé dans `open_project` + (commands.rs l.134). Le reconcile des tâches de fond + ré-enqueue des complétions non livrées + peut se greffer au même endroit (per project root à l'ouverture). + +Point d'accroche B8 (déféré, couplage PTY) : création commande longue côté `pty.rs` / +`LocalProcessSpawner` → créer `BackgroundTask{kind:Command}` au spawn + pousser exit/stdout +dans le sink. + +Lien : [[background-tasks-first-class-design]], [[checkpoint-b2-bootstrap-applied-await-codex-reset-1430]]. \ No newline at end of file diff --git a/.ideai/memory/b8-arbitration-outcomes.md b/.ideai/memory/b8-arbitration-outcomes.md new file mode 100644 index 0000000..6ef1c16 --- /dev/null +++ b/.ideai/memory/b8-arbitration-outcomes.md @@ -0,0 +1,21 @@ +--- +name: b8-arbitration-outcomes +description: Décisions d'arbitrage Architecture sur les 5 écarts B8 remontés par DevBackend : ce qui reste dans B8 vs part en dette/ticket. +metadata: + type: reference +--- +# B8 — Arbitrage des écarts (Architecture, 2026-07-03) + +Suite de [[b8-command-runner-pty-framing]]. Build vert (sink 6/6, tail 5/5, b7 1/1). + +1. **Tee PTY live** : ACCEPTER B8 sans rendu live (bloqueur levé). Le cœur complétion→wake fonctionne ; tee = orthogonal. NE PAS toucher PtyPort ni broadcast multi-conso dans B8. Dette = ticket A : ajouter `PtyPort::wait`/`try_wait` pour découpler détection d'exit de la consommation d'output (l'EOF-comme-proxy-de-fin est fragile), puis tee UI. Pas de broadcast multi-consommateur. +2. **BackgroundCommandArchive** : REFACTORER — retirer le trait DOMAINE. Retry = registre runtime in-memory (app/runner), NON persisté (SpawnSpec porte des secrets, ne pas écrire dans .ideai/background-tasks/*.json qui voyage avec le projet). Conséquence : retry SESSION-SCOPED en V1, pas après reboot. Reboot-retry = dette ticket B (persistance sûre = redaction/store machine-local hors projet). +3. **spawn_background_command** : ACCEPTER, nécessaire (point d'entrée manquant du cadrage). B8 a donc 4 commandes : spawn/cancel/retry/list. +4. **stderr_tail = None** : ACCEPTER V1. Sémantique pty correcte (TTY fusionne stdout/stderr). Champ réservé à une future variante ProcessSpawner-backed. stdout_tail = sortie tty fusionnée. +5. **list sans agentId dégradé** : ACCEPTER V1 (F2 centré agent). Limite : store n'énumère pas les tâches terminales/ouvertes par projet ; historique completed/failed partiel. Enrichir BackgroundTaskStore = dette ticket B. + +## Tickets de suivi à ouvrir +- A : PtyPort::wait + tee live + robustesse détection de fin. +- B : persistance sûre de l'invocation (retry-after-reboot, secrets) + énumération terminale/projet du store. + +B8 clôturable une fois le point 2 refactoré + points 1/5 tracés en dette. \ No newline at end of file diff --git a/.ideai/memory/b8-command-runner-pty-framing.md b/.ideai/memory/b8-command-runner-pty-framing.md new file mode 100644 index 0000000..39368a1 --- /dev/null +++ b/.ideai/memory/b8-command-runner-pty-framing.md @@ -0,0 +1,28 @@ +--- +name: b8-command-runner-pty-framing +description: Cadrage hexagonal figé du lot B8 : runner concret sur le port BackgroundTaskRunner, fermeture de la boucle sink en composition root, contrat de complétion, commandes cancel/retry. +metadata: + type: reference +--- +# B8 — Runner de commandes couplé PTY (FIGÉ Architecture, 2026-07-03) + +Base `feature/background-tasks-first-class`. Fait suite à [[background-tasks-first-class-design]] et [[b7-wiring-anchor-state-rs]]. + +## Trou constaté +`BackgroundCompletionSink::new`/`start_from_runner` n'existent QUE dans les tests. En prod `state.rs`, `background_ready_tx` n'est alimenté que par le reconcile boot ; aucun `impl BackgroundTaskRunner` concret. `LocalProcessSpawner.run` = one-shot Output, non couplé. + +## Décisions +- **Le spawner ne crée pas la task.** Concepts owner/project/wake_policy = application. On implémente le port FIGÉ `BackgroundTaskRunner` (domain/ports.rs:1349) via nouvel adapter infra `CommandBackgroundRunner` composant `Arc` (résolu via RemoteHost → Liskov SSH/WSL), PAS un PortablePtyAdapter en dur. +- Application = use case `SpawnBackgroundCommand` : alloue TaskId, store.create(Queued)→Running, runner.spawn(BackgroundTaskSpec). Le spec (ports.rs:207) porte déjà task_id/project_id/owner_agent_id/kind/wake_policy/command:Option/deadline. +- Le runner n'écrit NI store NI inbox : il émet 1 `BackgroundTaskCompletion` sur subscribe_completions(). Le sink (single-writer) fait store.save PUIS ready_tx.send (persist-avant-signal). +- Composition root ferme la boucle : construire runner + `BackgroundCompletionSink::new(store_port, background_ready_tx)` + `sink.start_from_runner(runner)` sur `tauri::async_runtime::spawn` (pas de tokio ambiant). Ancre state.rs ~1540-1660. +- Frontière : seuls SpawnSpec/BackgroundTaskSpec/BackgroundTaskCompletion (domaine) franchissent ; portable-pty reste en infra. Rendu xterm = tee du même spawn vers PtyBridge, orthogonal au tracking. + +## Contrat complétion +BackgroundTaskResult Success/Failure { finished_at_ms, exit_code:Some, summary, stdout_tail, stderr_tail bornés (ring UTF-8-safe, cap IDEA_BG_TAIL_BYTES ~8-16KiB) }. Cancel→runner kill→Cancelled{reason}, pas de wake succès. Invariants tenus par le sink (dédup task_id, IgnoredAlreadyTerminal, mark_completion_delivered) + pont enqueue_message (jamais busy) + wake si idle. Final reste terminal-de-tour, la complétion est un InboxItem::BackgroundCompletion. + +## Commandes Tauri (dépend B8) +cancel_background_task({taskId}); retry_background_task({taskId})→BackgroundTaskDto (NOUVEAU task_id, jamais réutilisé); list_background_tasks({projectId,agentId?}). DTO camelCase {taskId,ownerAgentId,projectId,kind,state,exitCode?,summary?,stdoutTail?,stderrTail?,createdAtMs,updatedAtMs}. + +## Sous-tâches DevBackend (ordre) +1 util tail borné (infra). 2 CommandBackgroundRunner (infra/background_task.rs + lib.rs). 3 use cases SpawnBackgroundCommand/Cancel/Retry (application). 4 fermer boucle sink en composition root (app-tauri/state.rs). 5 handlers+DTO (commands.rs, events.rs). 6 tee PTY live (pty.rs). 7 tests (réutiliser tests/background_completion_sink.rs). \ No newline at end of file diff --git a/.ideai/memory/b8-in-app-trigger-run-in-background.md b/.ideai/memory/b8-in-app-trigger-run-in-background.md new file mode 100644 index 0000000..79b3e42 --- /dev/null +++ b/.ideai/memory/b8-in-app-trigger-run-in-background.md @@ -0,0 +1,30 @@ +--- +name: b8-in-app-trigger-run-in-background +description: Arbitrage de l'écart de périmètre #1 — la surface agent MCP idea_run_in_background ferme la boucle B8, contrat et lots figés. +metadata: + type: reference +--- +# B8 — Déclencheur in-app (FIGÉ Architecture, 2026-07-03) + +Suite de [[b8-command-runner-pty-framing]] et [[b8-arbitration-outcomes]]. Constat : le consommateur B8 (runner PTY → sink → inbox → wake) est livré (8cac147) mais SANS producteur — `spawn_background_command` (commands.rs:2586) n'est appelé par personne. #1 n'a donc aucun déclencheur réel. + +## Décision +- **Retenu : outil MCP agent `idea_run_in_background`** (surface principale, fermante). Chemin humain UI (bouton panel F2) = secondaire optionnel, `wake_policy=RecordOnly`. **Promotion auto d'une commande PTY longue : REJETÉE** (pas d'owner/intention, heuristique fragile, change la sémantique de write_terminal). +- Pourquoi (a) : toute la machinerie (WakeOwner, inbox, AgentWakePort) est agent-centrée ; seul un agent déclarant explicitement une tâche de fond exerce la boucle bout-en-bout. + +## Contrat `idea_run_in_background` +Params : label(req), command(req), args[], cwd?(déf=project root), deadline_ms?. **owner = identité handshake du demandeur (non paramètre, non usurpable, rejet si absente)** ; project = contexte du demandeur ; record_only NON exposé côté agent → **WakeOwner forcé**. Retour SYNCHRONE {taskId,state} ; le résultat arrive plus tard en InboxItem::BackgroundCompletion (fire-and-forget-avec-tracking, distinct d'idea_ask_agent synchrone). + +## Frontière — ZÉRO nouveau port/adapter +- Domaine : variante `OrchestratorCommand::RunInBackground` (domain/orchestrator.rs). +- Adapter MCP : décl outil + map_tool_call (mcp/tools.rs), comme idea_ask_agent. +- Exécution : handler route vers use case EXISTANT application::SpawnBackgroundCommand (déjà en composition root state.rs), owner=requester, WakeOwner. Suivre le MÊME dispatch qu'AskAgent pour atteindre la couche app ; ne pas ré-router par commande Tauri front. + +## Lots +- BE-1 : variante enum + outil + mapping + tests mapping. +- BE-2 : handler RunInBackground → SpawnBackgroundCommand (rejet si demandeur absent). +- FE-1 (secondaire) : formulaire création dans panel F2 → spawn_background_command record_only. +- QA T1 : agent appelle idea_run_in_background(`sh -c 'sleep 8; echo done'`), finit son tour → owner ré-invoqué avec exit+résumé. T3 cancel/retry sur ce taskId (retry session-scoped, pas reboot — dette ticket B). + +## Branche +Tient sur feature/background-tasks-first-class (additif, réutilise la boucle figée). Rien à préparer côté Git. \ No newline at end of file diff --git a/.ideai/memory/background-tasks-first-class-design.md b/.ideai/memory/background-tasks-first-class-design.md new file mode 100644 index 0000000..3af8e50 --- /dev/null +++ b/.ideai/memory/background-tasks-first-class-design.md @@ -0,0 +1,118 @@ +--- +name: background-tasks-first-class-design +description: memory note background-tasks-first-class-design +metadata: + type: project +--- +--- +name: background-tasks-first-class-design +description: Cadrage hexagonal du modèle BackgroundTask + mailbox bornée par agent + wake owner après complétion post-tour. Fait autorité pour les lots B1-B7 / F1-F4. +metadata: + type: reference +--- +# Tâches de fond de 1re classe — CADRAGE FIGÉ (Architect, 2026-07-02) + +Base : `feature/background-tasks-first-class` (@ fccc1e2, empilée sur v2 `62915ee`+`fccc1e2`, +au-dessus de develop `a9653bc`, ligne CLI/PTY « toujours headless »). + +## Objectif (2 défauts à corriger) +1. **Complétion post-tour perdue** : une tâche de fond (ex. build `run_in_background`) finit + après la fin du tour de l'agent → IdeA ne ré-invoque pas le propriétaire avec le résultat. +2. **Pas de mailbox** : message concurrent pendant working/waiting rejeté « still busy » au + lieu d'être mis en file et drainé au tour suivant. + +## Modèle +`BackgroundTask` { task_id, owner_agent_id, project_id, kind, state, started/updated_at_ms, +deadline_ms?, correlation, result, wake_policy }. +- kind : `Command | HeadlessRendezvous | SessionResume | Maintenance` +- state : `Queued | Running | Waiting | Completed | Failed | Cancelled | Expired` +- result : `None | Success(payload) | Failure(error) | Cancelled(reason)` +- wake_policy : `WakeOwner | RecordOnly` +Règle centrale : la complétion n'est JAMAIS seulement un retour de future local ; elle est +persistée/observée comme événement de tâche, puis transformée en message mailbox pour le +propriétaire. + +## Réutilisé vs nouveau +Réutilisé : `InputMediator`/`AgentMailbox` (FIFO par agent), `TicketId`, `AgentBusyState`, +`run_ask_with_watchdog`+plafond+liveness, `live-state.json` (projection maigre), +`ReplyEvent::Final`/`Announcement`, session-limit scheduler (réveil différé annulable). +Nouveau : `BackgroundTask` persistant, store/registry, completion sink durable, +`AgentWakePort`, mailbox entrante bornée par agent (couvre user/agents/complétions système), +reconcile au boot. + +## Ports +- `BackgroundTaskStore` : create/get/save/list_open_for_agent/list_undelivered_completions/ + mark_completion_delivered. **B1 figé** (async_trait, `BackgroundTaskPortError`). +- `BackgroundTaskRunner` : spawn(spec)->handle / cancel / subscribe_completions()->stream. + **B1 figé.** +- `AgentInbox` (façade au-dessus d'`InputMediator`) : enqueue_message / dequeue_next / + snapshot. **Lot B4.** +- `AgentWakePort` : wake_agent(project, agent, reason) — ne connaît PAS Tauri ; adapter + lance/rattache session structured/headless. **Lot B5.** + +## Contrats/DTO +`InboxItem` { id, agent_id, source: Human|Agent{id}|BackgroundTask{task_id}|System, +kind: UserMessage|AgentDelegation|BackgroundCompletion|ResumeNotice, body, created_at_ms, +correlation_id?, priority (FIFO défaut, pas de priorité cachée V1) }. +`BackgroundCompletionDto` camelCase { taskId, ownerAgentId, projectId, kind, status, +exitCode, stdoutTail, stderrTail, finishedAtMs }. +Events `DomainEvent` : BackgroundTaskStarted/Progress(borné)/Completed/Failed/Cancelled, +AgentInboxQueued/Drained, AgentWakeScheduled/Started/Failed. (Events = observabilité/UI ; +store+mailbox = autorité.) + +## Flux nominal run_in_background +1 agent lance commande → 2 crée BackgroundTask{owner,Running} → 3 adapter démarre → 4 tour +peut finir → 5 commande finit plus tard → 6 completion sink reçoit exit/stdout/stderr → +7 écrit Completed/Failed dans store → 8 enqueue InboxItem::BackgroundCompletion → 9 si owner +Idle: AgentWakePort.wake_agent démarre nouveau tour headless avec résultat → 10 si Busy/Waiting: +FIFO, drainé au prochain tour libre. **Idempotence : 1 seule completion livrée par task_id ; +crash entre store et wake réparé par reconcile.** + +## Mailbox bornée +1 par agent, FIFO stricte, capacité configurable (`IDEA_AGENT_INBOX_CAPACITY`, défaut 100). +Enqueue pendant Working/Waiting → `queued` (plus jamais `busy`). Overflow : messages +humains/agents → `InboxFull` typée ; complétions de tâches → persistées delivery_pending, +JAMAIS perdues. « Busy » = tour en cours, pas entrée refusée. + +## Lots backend +- **B1** domaine : types, ports Store/Runner, events, invariants purs. ✅ FAIT (12 tests). + (AgentInbox/AgentWakePort reportés à B4/B5.) +- **B2** store+registry : adapter FS/sqlite-like simple, écriture atomique, reconcile boot + (Running sans handle vivant → Unknown/Failed ou delivery_pending). +- **B3** completion sink : runner publie complétions sur canal interne ; sink persiste AVANT + tout wake ; tests idempotence double completion. +- **B4** mailbox unifiée : étendre `InputMediator`/`AgentMailbox` (pas de 2e FIFO concurrente) ; + enqueue_message, snapshots workstate ; remplacer refus « still busy » par `queued`. +- **B5** wake owner : adapter `AgentWakePort` au-dessus de `StructuredSessions`/ + `AgentSession::send` ; wake seulement si idle ; sinon launch/rattach headless ; résultat + injecté comme message système explicite. +- **B6** rendezvous-as-task : `idea_ask_agent` reste synchrone pour l'appelant mais son + exécution interne = BackgroundTask{HeadlessRendezvous} ; timeouts/backstops → résultats de + tâche, plus des états invisibles. +- **B7** reconcile boot : lire store, ré-enqueue complétions non livrées, recalculer live-state. + +## Lots frontend +- **F1** workstate : queue depth par agent, « Queued » au lieu de « busy rejected ». +- **F2** panel tâches de fond : liste par agent running/completed/failed ; cancel/open output/retry. +- **F3** agent cell : badge « messages en attente » + « tâche de fond terminée » ; pas de + transcript brut auto-injecté. +- **F4** notifications : toast sobre à la fin d'une tâche longue ; clic ouvre owner/détail. + +## Invariants +completion persistée avant wake ; livrée au plus une fois ; aucun message vers agent connu +rejeté pour Busy ; mailbox ne dépasse jamais capacité ; overflow ne perd jamais une completion +système ; 1 item traité à la fois ; live-state = projection jamais autorité ; `Final` reste le +seul terminal normal headless ; un redémarrage n'oublie pas les complétions persistées non +drainées. + +## Critères QA (backend) +commande background finissant après le tour → owner réveillé avec exit code + résumé ; idem +avec IdeA redémarré entre fin et wake → completion retrouvée ; message user à agent busy → +`queued` ; deux agents vers même agent busy → FIFO ; mailbox pleine → `InboxFull` sur message +normal, completion système en pending ; double event même task_id → 1 livraison ; annulation → +`Cancelled`, pas de wake succès ; rendezvous silencieux → backstop = tâche Failed/NoReply, +libère la queue ; session-limit resume continue et ne contourne pas la mailbox. + +Liens : [[checkpoint-b2-bootstrap-applied-await-codex-reset-1430]], +[[rendezvous-no-reply-backstop-design]], [[session-limit-handling-design]], +[[headless-interagent-conversation-objective]], [[inter-agent-live-context-shared-per-agent]]. \ No newline at end of file diff --git a/.ideai/memory/checkpoint-b2-bootstrap-applied-await-codex-reset-1430.md b/.ideai/memory/checkpoint-b2-bootstrap-applied-await-codex-reset-1430.md new file mode 100644 index 0000000..4cc722c --- /dev/null +++ b/.ideai/memory/checkpoint-b2-bootstrap-applied-await-codex-reset-1430.md @@ -0,0 +1,45 @@ +--- +name: checkpoint-b2-bootstrap-applied-await-codex-reset-1430 +description: memory note checkpoint-b2-bootstrap-applied-await-codex-reset-1430 +metadata: + type: project +--- +--- +name: checkpoint-b2-bootstrap-applied-await-codex-reset-1430 +description: Reprise 2026-07-02 15h58 — B1-B6 tâches de fond commités+verts, AppImage fraîche buildée (NO_STRIP), prochaine étape = relance puis B7. +metadata: + type: project +--- +# Checkpoint — chantier tâches de fond de 1re classe (2026-07-02 ~15h58) + +## Fait & COMMITÉ sur `feature/background-tasks-first-class` +- `62915ee` fix(codex) Final canal ; `fccc1e2` chore(gitignore) target/ sous-crates. +- **B1-B6 tâches de fond** (4 commits) : `f4a55e9` domaine (12 tests) · `c537da5` infra + store+sink+mailbox (B2-B4, 7+6+7 tests) · `f94b542` application wake+rendezvous-as-task + (B5-B6, agent_wake 5, orchestrator_watcher 11 passed) · `54c8ecf` app-tauri events. +- `cargo check --workspace` + `fmt` verts. Validés QA jusqu'à B5. +- Cadrage complet : [[background-tasks-first-class-design]]. Ancre de câblage B7 : + [[b7-wiring-anchor-state-rs]]. + +## AppImage FRAÎCHE prête (à relancer par l'utilisateur) +`target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage` (2026-07-02 15:57, 111 Mo), +contient B1-B6. Buildée avec `NO_STRIP=true` (sinon linuxdeploy casse sur `.relr.dyn` — +cf. [[appimage-build-no-strip-relr-dyn-fix]], correctif à retenir). + +## PROCHAINES ÉTAPES (dès relance sur la nouvelle AppImage) +1. **B7** = câbler la chaîne au runtime dans `crates/app-tauri/src/state.rs` (ancre précise + dans [[b7-wiring-anchor-state-rs]] : `.with_background_tasks(...)` ABSENT du builder + OrchestratorService l.1356-1453 → rendezvous-as-task dormant ; sink/bridge/wake à spawner + via `tauri::async_runtime::spawn` ; store per-root façon `AppLiveStateProvider` ; reconcile + greffé dans `open_project`). Déléguer à DevBackend (session fraîche recommandée). +2. **F1-F4** front (queue depth/queued, panel tâches, badges agent cell, toasts). +3. **QA e2e** complet (critères du cadrage) puis rebuild AppImage final (NO_STRIP=true). +4. **B8 déféré** : coupler PTY/commandes longues (run_in_background) → BackgroundTask{Command}. + +## Historique du blocage (résolu autrement) +B7 a wedgé 4× en délégation DevBackend sur l'ANCIEN binaire (13:30, pré-B4/B6). D'où le +rebuild pour déployer les fixes de canal avant de finir. Ne PAS re-tenter B7 sur l'ancien +binaire : relancer d'abord. + +Liens : [[background-tasks-first-class-design]], [[b7-wiring-anchor-state-rs]], +[[appimage-build-no-strip-relr-dyn-fix]], [[mcp-e2e-findings-reply-wedge-phantom-busy]]. \ No newline at end of file diff --git a/.ideai/memory/checkpoint-b7-blocked-codex-session-limit.md b/.ideai/memory/checkpoint-b7-blocked-codex-session-limit.md new file mode 100644 index 0000000..d299f6f --- /dev/null +++ b/.ideai/memory/checkpoint-b7-blocked-codex-session-limit.md @@ -0,0 +1,47 @@ +--- +name: checkpoint-b7-blocked-codex-session-limit +description: memory note checkpoint-b7-blocked-codex-session-limit +metadata: + type: project +--- +--- +name: checkpoint-b7-blocked-codex-session-limit +description: RÉSOLU 2026-07-02 — B7 (câblage runtime tâches de fond) + F1-F4 livrés, QA vert, commités ; rebuild AppImage lancé. Historique du blocage Codex conservé. +metadata: + type: reference +--- +# Checkpoint — B7 + F1-F4 LIVRÉS (2026-07-02) — anciennement bloqué Codex + +## RÉSOLUTION (à jour) +Codex est revenu, le cycle complet a tourné via le canal inter-agent (fiable cette fois) : +- **B7** câblé par DevBackend dans `crates/app-tauri/src/state.rs` (store per-root routé par + project_id façon AppLiveStateProvider, `AppReconcileBackgroundTasks` reconcile boot, + `AppWakeSessionProvider`, boucle ready→MediatedInbox→AgentWakeService via + `tauri::async_runtime::spawn`, `.with_background_tasks(store,clock)` au builder + OrchestratorService ~l.1858) + `commands.rs` (`reconcile_background_tasks` dans open_project). +- **F1-F4** front par DevFrontend (workStateNormalization, ProjectWorkStatePanel, LayoutGrid + badges Queued/Task done, ProjectsView toast, useProjectWorkState abonné aux events + backgroundTaskChanged/agentInboxChanged/agentWakeChanged). +- **QA vert** : app-tauri 52/52 (dont test reconcile boot dédié + 4 tests MCP périmés alignés + au nouveau protocole : idea_reply retiré→JSON-RPC -32601, idea_ask_agent exposé=13 tools, + rendez-vous capture Final inline), domain 224+12, application 5, infrastructure 30+, + frontend 449. Zéro régression (les 4 tests MCP échouaient déjà à l'identique sur base @54c8ecf). +- **Commité** (pas de push/merge sans validation user) : `e05edc6` backend (B7 + tests), + `5d88c95` frontend. `.ideai/memory` + `.ideai/proposals` volontairement non commités. +- **Rebuild AppImage** lancé : `NO_STRIP=true` (cf. [[appimage-build-no-strip-relr-dyn-fix]]). + +## Réserves connues (assumées V1, = lot B8 déféré) +- Pas de `BackgroundTaskRunner` concret dans infrastructure → le sink de complétion ne s'abonne + pas à un runner réel ; complétion d'une commande `run_in_background` post-tour PAS testable + e2e (le rendez-vous inter-agent B6/HeadlessRendezvous, lui, EST vivant — c'était la douleur + principale). Boutons UI cancel/retry désactivés (pas de commande Tauri) — tooltip explicite. +- Prochain lot naturel B8 : runner de commandes couplé PTY (crée BackgroundTask{Command} au + spawn, pousse exit/stdout dans le sink) → active le flux run_in_background complet + cancel/retry. + +## Historique du blocage (résolu) +Codex était en limite de session ; les 4 workers (Architect/DevBackend/DevFrontend/QA, profil +`664cc20c-…-dce4c09c3da4`) tous murés en même temps ; Main+Git sur `…dce3c09c0de4` libres. +Décision user = attendre le reset (pas d'entorse Main/Git ne code). Reset survenu → cycle repris. + +Lien : [[b7-wiring-anchor-state-rs]], [[background-tasks-first-class-design]], +[[appimage-build-no-strip-relr-dyn-fix]], [[session-limit-handling-design]]. \ No newline at end of file diff --git a/.ideai/memory/checkpoint-b8-command-runner-loop-closed.md b/.ideai/memory/checkpoint-b8-command-runner-loop-closed.md new file mode 100644 index 0000000..b830755 --- /dev/null +++ b/.ideai/memory/checkpoint-b8-command-runner-loop-closed.md @@ -0,0 +1,24 @@ +--- +name: checkpoint-b8-command-runner-loop-closed +description: memory note checkpoint-b8-command-runner-loop-closed +metadata: + type: project +--- +# B8 — Runner de commandes + boucle sink fermée (DevBackend, 2026-07-03) + +Base `feature/background-tasks-first-class`. Suite de [[b8-command-runner-pty-framing]]. **Build workspace vert** ; sink tests (6) + tail units (5) + reconcile B7 verts. **Non committé** (Git tranche). QA à suivre. + +## Livré (sous-tâches 1-5) +- **Domaine** : nouveau port `BackgroundCommandArchive { spec_for(TaskId)->Option }` (ports.rs, exporté lib). Ajouté car le retry a besoin de la commande d'origine, qui n'est PAS un champ persisté du `BackgroundTask`. **À faire bénir par Architect** (ajout hors port figé). +- **Infra** : `background_task.rs` → module dir (`sink.rs` déplacé verbatim, `tail.rs`, `runner.rs`). + - `tail.rs` : `BoundedTail` ring UTF-8-safe + `bounded_tail()` + `tail_cap_bytes()` (env `IDEA_BG_TAIL_BYTES`, défaut 8 KiB, clamp [1 KiB, domaine 16 KiB]). + - `runner.rs` : `CommandBackgroundRunner` compose `Arc` + `Arc`. `spawn` → pty.spawn(24x80) + worker détaché ; détection de fin = EOF du stream single-consumer ; tail lu du **scrollback** (pas du stream, robuste à la course subscribe) ; exit code via `kill()` (wait après EOF naturel) ; `cancel` = flag+Notify (la branche cancel gagne la course EOF) ; deadline→Expired via select. Implémente `BackgroundTaskRunner` + `BackgroundCommandArchive`. Émet 1 complétion/tâche, n'écrit ni store ni inbox. +- **Application** : module `background` : `SpawnBackgroundCommand` (alloue TaskId, create Queued→save Running, runner.spawn ; si spawn KO → task Failed best-effort), `CancelBackgroundTask`, `RetryBackgroundTask` (nouveau TaskId, deadline **droppée** pour éviter l'expiry instantané ; réutilise `create_and_run`). +- **Composition root** (state.rs ~1545) : runner + `BackgroundCompletionSink::new(store, ready_tx.clone())` + `sink.start_from_runner(runner)` DANS `tauri::async_runtime::spawn` (Handle::current requis) ; JoinHandle du drain gardé vivant via `.await`. 4 champs AppState + use cases câblés. **Boucle B2 fermée** (avant : sink/runner uniquement en tests). +- **app-tauri** : `BackgroundTaskDto` camelCase (aplati result→exitCode/summary/tails), `parse_task_id`, commandes `spawn_background_command` (AJOUTÉE hors liste framing pour rendre la boucle atteignable/testable), `cancel_background_task`, `retry_background_task`, `list_background_tasks` — enregistrées dans invoke_handler. + +## Écarts au cadrage (à arbitrer) +1. **Tee PTY live (sous-tâche 6) NON câblé** : le `Broadcast` de `PortablePtyAdapter` est **single-consumer** (chaque `subscribe_output` supersede le précédent). Un tee UI re-souscrirait et **casserait la détection EOF du runner**. Un vrai tee live exige un broadcast multi-consommateur OU un port `wait/try_wait` sur PtyPort → **décision Architect** (PtyPort figé). Documenté dans `runner.rs`. +2. **stderr_tail toujours None** : le PTY fusionne stdout/stderr → sortie fusionnée dans `stdout_tail`. +3. Port `BackgroundCommandArchive` ajouté (voir ci-dessus). +4. `list_background_tasks` sans `agentId` ne renvoie que les complétions non livrées du projet (le store ne sait pas énumérer les tâches ouvertes par projet). \ No newline at end of file diff --git a/.ideai/memory/checkpoint-issue-ticket-backend-v1-done.md b/.ideai/memory/checkpoint-issue-ticket-backend-v1-done.md new file mode 100644 index 0000000..76898a3 --- /dev/null +++ b/.ideai/memory/checkpoint-issue-ticket-backend-v1-done.md @@ -0,0 +1,54 @@ +--- +name: checkpoint-issue-ticket-backend-v1-done +description: memory note checkpoint-issue-ticket-backend-v1-done +metadata: + type: project +--- +--- +name: checkpoint-issue-ticket-backend-v1-done +description: Checkpoint 2026-07-02 — backend V1 du système de tickets (T1-T5) terminé et vérifié vert sur feature/issue-ticket-system ; frontend F1-F5/F7 + QA formelle en attente du reset Codex. +metadata: + type: reference +--- +# Checkpoint — système de tickets, backend V1 (T1-T5) fait & vérifié (2026-07-02) + +Cadrage/design = [[issue-ticket-system-design]]. Branche `feature/issue-ticket-system` (base develop a9653bc). + +## FAIT & vérifié (build + tests verts, harvesté sur disque) +- **T1** domaine `Issue` : `crates/domain/src/issue.rs` (+ tests `domain/tests/issue.rs`), + câblé dans `domain/src/{events.rs,ids.rs,lib.rs,ports.rs}`. +- **T2** store FS Markdown + allocator : `crates/infrastructure/src/issues.rs` + (`FsIssueStore`, `FsIssueNumberAllocator`, index.json, check version), tests `issue_store.rs`. +- **T3** use cases : `crates/application/src/issues/` (Create/Read/List/Update/UpdateCarnet/ + Link/Unlink/AssignAgent, optimistic concurrency), tests `application/tests/issue_usecases.rs`. +- **T4** surface MCP : 10 outils `idea_ticket_{create,read,list,update,update_status, + update_priority,read_carnet,update_carnet,link,unlink}` enregistrés dans + `infrastructure/src/orchestrator/mcp/{mod,server,tools}.rs`. +- **T5** Tauri : `crates/app-tauri/src/tickets.rs` (commands + assign) + câblage composition-root + `crates/app-tauri/src/state.rs` (~l.824 : FsIssueStore/FsIssueNumberAllocator/use cases/ + ticket_tool_provider). `cargo build` workspace OK ; app-tauri 47/51 tests verts. + +## Dette pré-existante à NE PAS confondre avec régression +4 tests app-tauri rouges (`mcp_e2e_loopback_tests::{orphan_reply_is_typed_error, +ask_then_reply_round_trips_inline (+_codex)}`, `mcp_serve_peer_tests::handshake_then_tools_list`) ++ `infrastructure::orchestrator_watcher::ask_request_surfaces_reply_alongside_detail` : +tests de l'ANCIEN protocole ask/reply, échouent DÉJÀ sur `develop` vierge (prouvé par stash). +Le fix existe sur `feature/background-tasks-first-class` (alignement QA). Résorbés quand +background-tasks merge dans develop, ou à cherry-pick. NE PAS les compter contre les tickets. + +## RESTE (bloqué reset Codex — les workers Architect/DevBackend/DevFrontend/QA partagent le +profil Codex ; Main+Git libres) +- **Frontend V1** : F1 gateway `IssueGateway` · F2 liste filtrable · F3 détail/édition + (conflits expectedVersion) · F4 éditeur Carnet Markdown · F5 liens & assignations · F7 `#42` + cliquable + pré-remplissage délégation. (F6 attachments = fast-follow hors V1.) +- **QA formelle** backend+frontend (create→lire via MCP, #N séquentiel, carnet édité par agent, + liens, assignation agent connu, optimistic concurrency conflit). +- Puis Git commit final + rebuild AppImage (NO_STRIP=true, cf. [[appimage-build-no-strip-relr-dyn-fix]]). + +## Note process +Rendez-vous inter-agent timeout sur tâches LOURDES (binaire courant pré-B7) : l'agent produit +quand même le code, harvesté sur disque par Main (build+tests réels) puis commité via Git libre. +Confirme l'intérêt de B7/B8 (le rebuild B7 de 21:35 n'est pas encore le binaire lancé). + +Lien : [[issue-ticket-system-design]], [[checkpoint-b7-blocked-codex-session-limit]], +[[background-tasks-first-class-design]]. \ No newline at end of file diff --git a/.ideai/memory/checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending.md b/.ideai/memory/checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending.md new file mode 100644 index 0000000..ecc0dc3 --- /dev/null +++ b/.ideai/memory/checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending.md @@ -0,0 +1,34 @@ +--- +name: checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending +description: memory note checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending +metadata: + type: project +--- +--- +name: checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending +description: Ticket #1 — T1 VERT live. T3 root cause = tâches de fond jamais projetées dans work-state ; fix BE+FE livré unit-green ; rebuild AppImage en cours ; reste re-QA live T3 + T2 reboot, puis merge. +metadata: + type: project +--- +# Checkpoint ticket #1 — état au 2026-07-03 + +## T1 — VERT (validé live QA) +Wake auto du propriétaire à la complétion + `completionDelivered: true` prouvés dans le store (`.ideai/background-tasks/.json`, task `02aa2493`). Fixes portant T1 (unit-green, NON commités) : wake.rs:119 (mark_completion_delivered dès session.send accepté), structured.rs:121 (drain_reply_stream_with_readiness), agent_wake.rs:504 (régression), + itér.1 MediatedInbox enqueue FIFO sans busy. +Note : `idea_workstate_read` n'expose PAS les BackgroundTasks — lire le store JSON. + +## T3 — root cause trouvée + fix livré (unit-green, NON commité) +Défaut live : aucun bouton Cancel/Retry dans le panneau Work. Cause = le DTO `AgentWorkState` ne portait pas les tâches de fond et `GetProjectWorkState` n'avait pas de dépendance BackgroundTaskStore → `agent.backgroundTasks` toujours vide en live → section masquée (ProjectWorkStatePanel.tsx:638). Front déjà câblé, seule la projection backend manquait. Vitest verts en trompe-l'œil (payload mocké). +Cadrage Architect : mémoire `workstate-background-tasks-projection-fix` (Option A — étendre le read-model unique, best-effort, aucun nouveau port). +Livré : +- BE (DevBackend) : VO `AgentBackgroundTaskState` + champ `background_tasks` sur AgentWorkState + builder `with_background_tasks` + projection union open/undelivered best-effort (workstate/mod.rs) ; DTO Tauri backgroundTasks (dto.rs) ; wiring state.rs. Tests : `cargo test -p application` 24 passed (workstate.rs), `-p app-tauri` vert, build workspace OK. +- FE (DevFrontend) : mapping queued/waiting→pending explicité, test sur vrai shape backend, + correctif tri (finishedAtMs jamais émis → bascule sur updatedAtMs). vitest 460/460, build OK. Verdict : `tickets-t3-frontend-validation-verdict`. +- Dette de contrat tracée en ticket #5 (summary non affiché, owner/project inutiles). Low. + +## RESTE À FAIRE (ordre) +1. Rebuild AppImage `NO_STRIP=true` — EN COURS (background bixa34tht). Voir [[appimage-build-no-strip-relr-dyn-fix]]. +2. Utilisateur relance l'AppImage. +3. QA re-passe T3 LIVE : T3-a apparition running + Cancel actif ; T3-b Cancel agit → cancelled + Retry actif ; T3-c Retry → nouvelle tâche running ; T3-d borne livraison (tâche livrée disparaît = conforme V1, PAS un bug) ; T3-e isolation par agent ; T3-f non-régression live/busy/tickets. Détail dans cadrage Architect. +4. T2 — reconcile après reboot (fin AVANT wake), protocole MANUEL, coordination reboot utilisateur. +5. Si T1+T2+T3 verts → Git commit de TOUS les fix (T1 + T3 BE/FE) + merge feature/background-tasks-first-class → develop (résorbe aussi 4 tests protocole MCP périmés rouges sur develop). + +Liens : [[workstate-background-tasks-projection-fix]], [[tickets-t3-frontend-validation-verdict]], [[b8-in-app-trigger-run-in-background]], [[background-tasks-first-class-design]]. \ No newline at end of file diff --git a/.ideai/memory/develop-realigned-to-cli-ui-baseline-2026-07-02.md b/.ideai/memory/develop-realigned-to-cli-ui-baseline-2026-07-02.md new file mode 100644 index 0000000..b443712 --- /dev/null +++ b/.ideai/memory/develop-realigned-to-cli-ui-baseline-2026-07-02.md @@ -0,0 +1,52 @@ +--- +name: develop-realigned-to-cli-ui-baseline-2026-07-02 +description: memory note develop-realigned-to-cli-ui-baseline-2026-07-02 +metadata: + type: project +--- +# develop réaligné sur la ligne UI CLI (pre-chat-ui-baseline) — 2026-07-02 + +**Type :** décision de topologie, validée utilisateur. + +## Contexte / erreur corrigée + +La feature « annonces inter-agent + fix Final Codex » avait été branchée par erreur sur +`develop` @ `0072aff`, qui portait le chantier **canonical-conversation / chat** (LC1→LC5). +Or l'UI de référence de l'utilisateur est la **ligne CLI** portée par +`feature/pre-chat-ui-baseline` @ `a9653bc` (UI CLI + commit « délégation toujours headless, +jamais d'injection PTY »). Le rebuild sur develop avait régressé l'UI (plus de terminaux CLI). +De plus, `a9653bc` — prémisse runtime de l'overlay — n'était QUE sur pre-chat-ui-baseline. + +## Opération effectuée (Git, réversible, locale, aucune sortante) + +1. Secours du chantier chat : branche `snapshot/develop-canonical-conversation-0072aff` + + tag homonyme → `0072aff` (récupérable à 100%). +2. `git branch -f develop feature/pre-chat-ui-baseline` → **develop == `a9653bc`** (arbre + exactement celui de la ligne CLI). Le chantier chat est SUPERSÉDÉ sur la mainline develop + mais non détruit. +3. Nouvelle branche de fix : **`feature/inter-agent-announcements-v2`** depuis le nouveau + develop (@ a9653bc). + +Retour arrière : `git branch -f develop snapshot/develop-canonical-conversation-0072aff`. + +## État des refs + +- `develop` = `a9653bc` (base de travail canonique désormais = ligne CLI). +- `feature/inter-agent-announcements-v2` = base de travail pour reporter les fix. +- `feature/inter-agent-announcements` = `71d307d` : ancien commit B0-B3 (basé sur l'ancien + develop chat), conservé pour cherry-pick. +- `snapshot/develop-canonical-conversation-0072aff` = chantier chat préservé. + +## Conséquences pour la suite + +- Reporter **B1/B2/B3** sur `feature/inter-agent-announcements-v2` (cherry-pick depuis + 71d307d). **B0 devient probablement REDONDANT** : `a9653bc` (headless alongside/toujours + headless) est déjà la base — à vérifier avant de le rejouer. +- **Re-planifier F1/F2/F3** : le design frontend (preview cellule demandeur + overlay cellule + cible) avait été cadré contre le modèle de cellules canonical-conversation de develop ; sur + la base CLI (`a9653bc`), il faut re-cibler le modèle de cellules CLI/PTY réel. À faire + cadrer par Architect avant dev frontend. +- Rebuild AppImage à faire depuis cette base → restaure l'UI CLI + embarque les fix. + +Liens : [[inter-agent-announcements-feature-and-codex-final-bug]], +[[inter-agent-live-context-shared-per-agent]]. \ No newline at end of file diff --git a/.ideai/memory/inter-agent-announcements-feature-and-codex-final-bug.md b/.ideai/memory/inter-agent-announcements-feature-and-codex-final-bug.md new file mode 100644 index 0000000..1cd2bc2 --- /dev/null +++ b/.ideai/memory/inter-agent-announcements-feature-and-codex-final-bug.md @@ -0,0 +1,67 @@ +--- +name: inter-agent-announcements-feature-and-codex-final-bug +description: memory note inter-agent-announcements-feature-and-codex-final-bug +metadata: + type: project +--- +# Annonces inter-agent (UI live) + fix du Final Codex — DESIGN FIGÉ + +**Type :** design de feature + bug racine. Validé utilisateur le 2026-07-02, cadré par Architect. + +## Bug racine (Codex) + +`crates/infrastructure/src/session/codex.rs` : `parse_event` (~l.86) mappe CHAQUE +`item.completed{item.type=="agent_message"}` vers `ReplyEvent::Final`, et `send` (~l.234) +casse au PREMIER Final (`break 'lines`). Or `codex exec --json` émet plusieurs +`agent_message` par tour (préambule → outils → conclusion → `turn.completed`). IdeA +renvoyait donc le **préambule** au demandeur au lieu de la **conclusion**. Preuve live : +Architect répondait « je vais interroger QA… » au lieu du résultat. **Défaut d'implémentation, +pas du headless Codex** (un `codex exec` brut déroule tout le tour). + +## Feature validée (le bug devient fonctionnalité) + +- **Final** = DERNIER `agent_message` avant `turn.completed` → seule chose renvoyée au + demandeur (résout le ticket). Bords : 1 seul message (préambule=conclusion) ; 0 message + → `TargetReturnedNoReply` (inchangé). +- **Annonces** = `agent_message` intermédiaires → nouvel événement NON terminal + `ReplyEvent::Announcement{text}`, poussé sur le bus vers l'UI, JAMAIS renvoyé au + demandeur, JAMAIS persisté au transcript (éphémère). +- **UI** : (a) cellule DEMANDEUR = petit cadre preview près de la drop-list d'agents, + filtré par `ticket_id` ; (b) cellule CIBLE si visible = overlay d'indisponibilité (texte + haut « un agent est en train de lui parler » + annonces défilantes au centre), monté sur + live-state Working, retiré au Final/idle (reste tant qu'≥1 ticket actif sur la cible). + +## Contrat (Architect) + +- `ReplyEvent::Announcement{text}` (non terminal) vs `Final{text}` (terminal, résout). +- `drain_with_readiness` : Announcement → publie event, ne résout pas, ne passe pas idle ; + Final → résout ticket + idle + fin du drain. +- Nouveau `DomainEvent::AgentAnnouncement{project_id, requester, target, ticket_id, text, + at_ms}` (attribution obligatoire pour router vers la bonne cellule + isoler projet/onglet). +- Overlay cible = état UI piloté par live-state (pas un blocage du PTY : le contact passe par + session headless séparée, `allow_structured_alongside_pty:true`). +- Généralisation Claude : `session/claude.rs` garde son terminal explicite (`result`) comme + Final ; messages complets non terminaux → Announcement ; NE PAS promouvoir les `TextDelta` + tokenisés (bruit) ; tool events restent `ToolActivity`. + +## Découpage dev (Architect) + +- **B1** domaine/appli : `ReplyEvent::Announcement`, `DomainEvent::AgentAnnouncement`, + `structured.rs`, `service.rs` (`ask_structured` ne termine que sur Final). +- **B2** Codex `codex.rs` : bufferiser le dernier `agent_message`, émettre les précédents en + Announcement, le dernier en Final à `turn.completed`. +- **B3** Tauri `app-tauri/{events,dto,state}.rs` : relay + DTO camelCase. +- **F1** front store/gateway annonces (index par ticket/target, borné ~20-50). +- **F2** front preview cellule demandeur. +- **F3** front overlay cellule cible. +- **Q** QA e2e : Main→Architect→QA, préambule+outil+conclusion ; Main ne reçoit que la + conclusion ; preview demandeur + overlay cible pendant le travail ; fermeture au Final. + Critère : reproduire le bug initial et constater la correction. + +## État + +Design figé, cadrage Architect complet. Reste : Git branche → B1/B2/B3 + F1/F2/F3 → QA. +Non démarré côté dev au 2026-07-02. + +Liens : [[inter-agent-live-context-shared-per-agent]], [[rendezvous-no-reply-backstop-design]], +[[headless-interagent-conversation-objective]], [[conversation-viewer-ls7-frontend]]. \ No newline at end of file diff --git a/.ideai/memory/inter-agent-live-context-shared-per-agent.md b/.ideai/memory/inter-agent-live-context-shared-per-agent.md new file mode 100644 index 0000000..8f8f947 --- /dev/null +++ b/.ideai/memory/inter-agent-live-context-shared-per-agent.md @@ -0,0 +1,79 @@ +--- +name: inter-agent-live-context-shared-per-agent +description: memory note inter-agent-live-context-shared-per-agent +metadata: + type: project +--- +# Contexte live inter-agent : PAR AGENT (partagé), pas par paire — DÉCISION FIGÉE + +**Type :** decision d'architecture / produit — validée utilisateur le 2026-07-02. + +## Décision + +Le contexte live d'un agent IdeA est porté **par l'agent cible**, partagé entre TOUS les +demandeurs. Pour un `AgentId` donné, IdeA maintient **au plus une** session +headless/structurée vivante ; tous les `idea_ask_agent(requester, target)` vers le même +`target` réutilisent cette session, quel que soit le demandeur. + +Ce partage est **intentionnel et souhaité**, pas un bug ni une fuite : tous les agents du +projet sont dans le même domaine de confiance (même utilisateur, même projet). Le contexte +live d'un agent = mémoire de travail d'équipe / tableau blanc partagé. On NE veut PAS +d'isolation par paire. + +La paire `(requester, target)` n'est **pas** une frontière d'isolation du contexte moteur : +elle sert uniquement à l'attribution, la corrélation requête/réponse, les vues UI et les +filtres de transcript. + +## Preuve empirique (2026-07-02) + +Main a confié à QA le token `IDEA-TOKEN-7F3A-BLEEDCHECK` ; Architect a ensuite pu le +récupérer auprès de QA **sans que Main le lui transmette**. Cause : `ensure_structured_session` +(`crates/application/src/orchestrator/service.rs:1948`) fait un early-return sur +`session_for_agent(&agent_id)` → une seule session vivante par agent, réutilisée. + +## Modèle de transcript cible (cadré par Architect) + +**Log canonique PAR AGENT + vues par paire dérivées.** Abandon du transcript canonique +par paire comme source de vérité (il promet une isolation qui n'existe pas en live). + +- Arbo cible : `.ideai/conversations/agents//{log.jsonl, handoff.md, providers.json, log.N.jsonl}` +- Chaque tour porte l'attribution : `thread_id=Agent(target)`, `requester`, `target`, + `correlation_id`, `role`, `source`. +- Vues = projections filtrées (`ForAgent`, `ForPair`, `ForCorrelation`), pas des logs séparés. + +Impacts à traiter : +- `resolve_conversation` → `resolve_agent_thread(target)` + `resolve_conversation_view(requester,target)`. +- `bind_conversation_session` → bind sur `targetAgentId` ; réutiliser la session existante. +- `ConversationRegistry` → index primaire `targetAgentId`, secondaires `requester`/`correlation_id` ; + interdire 2 fils live pour le même target. +- `ProviderSessionStore` clé `(agent_thread_id, provider_id)` au lieu de `(pair_id, provider_id)`. +- `LeafCell.conversation_id` = désormais **id de fil agent**, plus « id de paire ». +- Viewer LS7 : défaut = fil agent complet ; vue par paire = vue filtrée/partielle libellée comme telle. + +## Garde-fou efficience (rotation multi-demandeurs) + +Le design LS5/LS6 tient, mais un fil agent partagé **croît plus vite** (agrège les demandes +de plusieurs requesters). Ajustements : la rotation s'applique au fil agent canonique (pas +aux vues) ; le résumé/handoff doit conserver l'attribution minimale (`Objectif courant`, +`Demandes actives par requester`, `Décisions récentes`, `Blocages`, `Dernière réponse +corrélée`) ; transcript brut jamais réinjecté (humain-only) ; handoff borné à +`HANDOFF_SUMMARY_MAX_CHARS=4096`. Risque principal : dilution si demandeurs poussent des +objectifs contradictoires → mitigé par les rubriques stables du handoff. + +## Découpage dev recommandé (Architect) + +1. `ARCHITECTURE.md` §19.7 : remplacer « id de paire » par « fil agent partagé » (+ §16/§17/§21). +2. Introduire `ConversationThreadId` / `ConversationViewId`. +3. Adapter `resolve_conversation`, `bind_conversation_session`, `ConversationRegistry`. +4. Compatibiliser les anciens chemins `for_pair(requester,target)` comme vues historiques. +5. LS7 viewer : fil agent complet + filtres. +6. Test QA : un fait confié à QA par Main est visible quand Architect interroge QA. + +## État + +Décision figée. Cadrage architectural livré par Architect. Reste à : intégrer la proposition +dans `ARCHITECTURE.md`, puis planifier le dev (cycle normal Architect→Git→Dev→QA). + +Liens : [[conversation-rotation-safety-design]], [[handoff-ls5-summary-bound-and-llm-seam]], +[[conversation-log-ls6-rotation-and-paginated-read]], [[conversation-viewer-ls7-frontend]], +[[headless-interagent-conversation-objective]]. \ No newline at end of file diff --git a/.ideai/memory/issue-ticket-system-design.md b/.ideai/memory/issue-ticket-system-design.md new file mode 100644 index 0000000..70bce45 --- /dev/null +++ b/.ideai/memory/issue-ticket-system-design.md @@ -0,0 +1,78 @@ +--- +name: issue-ticket-system-design +description: memory note issue-ticket-system-design +metadata: + type: project +--- +--- +name: issue-ticket-system-design +description: Cadrage figé (Architect+Main, 2026-07-02) du système de tickets IdeA façon Jira — domaine `Issue` par projet, exposé « ticket » côté UI/MCP, Carnet éditable, #N séquentiel. Fait autorité pour les lots T1-T6/F1-F7. +metadata: + type: reference +--- +# Système de tickets IdeA (domaine `Issue`) — CADRAGE FIGÉ 2026-07-02 + +## Décisions produit verrouillées (utilisateur) +- **Portée PAR PROJET** : stockage dans `.ideai/tickets/` du projet (pas de backlog global). +- **Identifiant court `#42`** : compteur séquentiel par projet, parlable à l'oral (poignée + partagée humain↔agent, y compris dans une délégation `idea_ask_agent`). +- **Champ de connaissances éditable = « Carnet »** (PAS « mémoire » — évite collision avec la + mémoire projet). Markdown éditable/réorganisable, scoped ticket, pas append-only. +- **Stockage Markdown + frontmatter**, un DOSSIER par ticket. +- **V1 SANS attachments** : livrer T1-T5 + F1-F5 + F7 ; attachments (T6/F6) en fast-follow. + +## Nommage (collision évitée avec le TicketId de délégation) +Domaine/code = **`Issue`** (`IssueId`, `IssueNumber`, `IssueRef("#42")`, `IssueStore`, +modules `domain::issue`/`application::issues`/`infrastructure::issues`). UI + MCP public = +« ticket » (`idea_ticket_*`, DTO camelCase). Ne JAMAIS nommer `Ticket` en code (réservé à la +délégation inter-agent). + +## Frontière Carnet vs mémoire projet +Carnet = savoir DU ticket (décisions locales, hypothèses, investigation, contraintes). Mémoire +projet = savoir transverse durable. Règle : si l'info doit survivre à la fermeture du ticket et +guider d'autres travaux → mémoire projet ; sinon → carnet. + +## Modèle domaine +`Issue { id(uuid), number(u64), title, description(md), status, priority, carnet, links, +agent_refs, attachments, created_by/updated_by(IssueActor: User|Agent|System), +created_at/updated_at, version(optimistic) }`. +- `IssueStatus`: Open | InProgress | Qa | Closed (DTO: open|inProgress|QA|closed). +- `IssuePriority`: Low | Medium | High | Critical. +- `IssueLinkKind`: RelatesTo | Blocks | BlockedBy | Duplicates | DependsOn. +- `AgentIssueRef { agent_id, role: Assigned|Mentioned|Reviewer|Owner }`. +Invariants : number>0 unique/projet ; IssueRef = `#` ; titre non vide ; pas de self-link ; +assignation → AgentId connu du manifeste ; toute écriture incrémente version ; attachments sous +le dossier du ticket, pas de `..`/symlink/chemin absolu. + +## Ports +`IssueStore` (create/get_by_ref/list(filter)/update(expected_version)/read_carnet/ +write_carnet(expected_version)) ; `IssueNumberAllocator` (allocate_next, atomique via +counter.json + counter.lock + rename, jamais de réutilisation de numéro) ; `IssueAttachmentStore` +(add/remove/list — lot attachments). Adapters : `FsIssueStore`, `FsIssueNumberAllocator`, +`FsIssueAttachmentStore`, `McpIssueTools`, `TauriIssueCommands`, `TauriIssueEventRelay`. + +## Surface MCP (agents) +`idea_ticket_create/read/list/update/update_status/update_priority/read_carnet/update_carnet/ +link/unlink` (+ `idea_ticket_attachment_*` au lot attachments). DTO camelCase ; `TicketRef` = +`#${number}` ; concurrence via `expectedVersion` sur chaque écriture. + +## Events domaine (→ projection UI, même modèle events→front B-series) +IssueCreated/Updated/StatusChanged/PriorityChanged/CarnetUpdated/Linked/Unlinked/ +AgentAssigned/AgentUnassigned/AttachmentAdded/Removed/StorageConflictDetected. + +## Layout stockage +`.ideai/tickets/{counter.json, index.json(projection reconstructible), /{issue.md(frontmatter ++description), carnet.md, attachments/}}`. Attachments versionnés git par défaut (warning >5MiB, +refus configurable >25MiB, pointeur URI pour les gros). + +## Lots +Backend : T1 domaine Issue · T2 store FS Markdown (+allocator) · T3 use cases (Create/Read/List/ +Update/UpdateCarnet/Link/AssignAgent, optimistic concurrency) · T4 surface MCP `idea_ticket_*` · +T5 commands Tauri + relay events · T6 attachments (fast-follow). +Frontend : F1 gateway UI (`IssueGateway`, pas d'invoke direct) · F2 liste filtrable · F3 détail/ +édition (gestion conflits expectedVersion) · F4 éditeur Carnet Markdown · F5 liens & assignations · +F6 attachments (fast-follow) · F7 `#42` cliquable + pré-remplissage délégation. +Réutilise : store FS par projet façon `FsBackgroundTaskStore`/`AppLiveStateProvider`, EventBus + +TauriEventRelay, pattern gateway UI. + +Lien : [[background-tasks-first-class-design]], [[agent-context-memory-and-profile-handoff]]. \ No newline at end of file diff --git a/.ideai/memory/tickets-frontend-v1-done.md b/.ideai/memory/tickets-frontend-v1-done.md new file mode 100644 index 0000000..9a9d6c9 --- /dev/null +++ b/.ideai/memory/tickets-frontend-v1-done.md @@ -0,0 +1,15 @@ +--- +name: tickets-frontend-v1-done +description: État du frontend du système de tickets — livré, vert, décisions de contrat clés. +metadata: + type: project +--- +Frontend V1 du système de tickets livré sur `feature/issue-ticket-system` (non commité, attend QA/Git). Build + 459 tests verts. + +Décisions figées : +- Port nommé `TicketGateway` (pas IssueGateway) — aligné sur le wire `ticket_*`/`TicketDto` et le terme visible « ticket ». +- Statut/priorité côté UI passent par `ticket_update` : `ticket_update_status`/`ticket_update_priority` ne sont PAS des commands Tauri (MCP-only, non enregistrées dans `lib.rs`). +- Conflit de version = `code:"INVALID"` + message contenant `"issue version conflict"` (détection par message, pas de code dédié). Voir `isTicketVersionConflict`. +- F7 = `#ref` cliquables + linkification desc + bouton clipboard « Travaille sur le ticket #N : » (pas d'écriture terminal directe en V1). + +Surface : `features/tickets/` (panel liste + overlay détail/carnet/liens/assign), onglet sidebar « Tickets » dans ProjectsView entre Work et Agents, `MockTicketGateway` émet les events `Issue*`. Voir [[checkpoint-issue-ticket-backend-v1-done]] et [[issue-ticket-system-design]]. \ No newline at end of file diff --git a/.ideai/memory/tickets-t3-frontend-validation-verdict.md b/.ideai/memory/tickets-t3-frontend-validation-verdict.md new file mode 100644 index 0000000..e0bf377 --- /dev/null +++ b/.ideai/memory/tickets-t3-frontend-validation-verdict.md @@ -0,0 +1,15 @@ +--- +name: tickets-t3-frontend-validation-verdict +description: Verdict de validation frontend du fix T3 (projection backgroundTasks par agent) sur feature/background-tasks-first-class — vert, avec 3 désalignements de contrat mineurs. +metadata: + type: reference +--- +Validation frontend du fix T3 (ticket #1), branche `feature/background-tasks-first-class`, 2026-07-03. + +**Verdict : vert.** `npx vitest run` = 460/460, `npm run build` (tsc --noEmit + vite) OK. Le front était déjà câblé pour Cancel/Retry ; contre le vrai payload backend le mapping d'états et l'activation des boutons fonctionnent. + +**Contrat backend réel** (`crates/app-tauri/src/dto.rs::AgentBackgroundTaskStateDto`, camelCase) : `taskId, kind, state, exitCode?, summary?, stdoutTail?, stderrTail?, createdAtMs, updatedAtMs`. `state` ∈ queued|running|waiting|completed|failed|cancelled|expired ; `kind` ∈ command|headlessRendezvous|sessionResume|maintenance. **Pas** de `status`, `ownerAgentId`, `projectId`, ni `finishedAtMs`. + +**Fait :** FE-1 rendu explicite `queued`/`waiting`→`pending` dans `normalizeBackgroundStatus`. FE-2 ajout d'un test sur le vrai shape dans `src/features/workstate/workstate.test.tsx` (le mock passe par le vrai `normalizeProjectWorkState`, donc pas de faux-vert possible). + +**Écarts de contrat à arbitrer (dette, panneau non modifié) :** (1) `ProjectWorkStatePanel.tsx` trie sur `finishedAtMs` jamais émis → tri retombe sur taskId, non chronologique ; (2) `summary` backend non porté par `BackgroundCompletion` → ignoré ; (3) `ownerAgentId`/`projectId` absents du payload par-agent (inoffensif : fallback = agentId). Voir [[workstate-background-tasks-projection-fix]] et [[b8-in-app-trigger-run-in-background]]. \ No newline at end of file diff --git a/.ideai/memory/tickets-v1-e2e-validated-qa.md b/.ideai/memory/tickets-v1-e2e-validated-qa.md new file mode 100644 index 0000000..2f9bc54 --- /dev/null +++ b/.ideai/memory/tickets-v1-e2e-validated-qa.md @@ -0,0 +1,15 @@ +--- +name: tickets-v1-e2e-validated-qa +description: Verdict QA du système de tickets V1 sans attachments sur feature/issue-ticket-system — vert de bout en bout, avec la seule réserve du typage de conflit côté UI. +metadata: + type: reference +--- +Validation QA de bout en bout du système de tickets **V1 (sans attachments)** sur `feature/issue-ticket-system` (backend commité `8de7be0`, frontend non commité). **Verdict : OK.** + +**Preuves réelles :** +- `cargo build --workspace` OK. Tests verts sur domain/application ; infrastructure et app-tauri verts **sauf la dette pré-existante déclarée** (`orchestrator_watcher::ask_request_surfaces_reply_alongside_detail` + `app-tauri state::mcp_e2e_loopback_tests::*`, tous `Elapsed(())` du rendez-vous, sans rapport avec les tickets). Aucun échec imputable aux tickets. +- Frontend : `npm run build` OK (tsc+vite), `npm test` = **459 passed / 49 files**. + +**Invariants prouvés par tests :** `#N` séquentiel jamais réutilisé (`allocator_never_reuses_numbers`) ; create→read round-trip ; carnet remplaçable & scoped `.ideai/tickets/N/carnet.md` ; pas de self-link (`IssueError::SelfLink`, `domain/issue.rs`) ; assignation agent connu seulement (`create_issue_rejects_unknown_assignee`) ; concurrence optimiste (`update_issue_maps_version_conflict`, `IssueStoreError::VersionConflict`) ; énums fermés statut/priorité. + +**Réserve non bloquante (dette technique) :** le conflit de version est détecté côté **front** par fragment de message (`message.includes("version conflict")`, `frontend/src/adapters/ticket.ts`, `useTicketDetail.ts`), s'appuyant sur `domain/ports.rs` `#[error("issue version conflict: …")]`. Un code typé `"versionConflict"` existe DÉJÀ mais uniquement sur la surface MCP (`app-tauri/src/tickets.rs:942`) ; le chemin Tauri command renvoie `INVALID`. Recommandation : propager le code typé dans l'enveloppe d'erreur des `ticket_*` commands et brancher l'UI sur `error.code`. Comportement actuel correct et testé, donc non bloquant. \ No newline at end of file diff --git a/.ideai/memory/workstate-background-tasks-projection-fix.md b/.ideai/memory/workstate-background-tasks-projection-fix.md new file mode 100644 index 0000000..8b3c427 --- /dev/null +++ b/.ideai/memory/workstate-background-tasks-projection-fix.md @@ -0,0 +1,15 @@ +--- +name: workstate-background-tasks-projection-fix +description: Décision d'archi pour rendre visibles Cancel/Retry dans le panneau Work — extension du read-model GetProjectWorkState, contrat DTO et borne de livraison. +metadata: + type: reference +--- +**Défaut** : `AgentWorkState` (`crates/application/src/workstate/mod.rs:140`) ne portait pas les tâches de fond et `GetProjectWorkState` n'avait pas de dépendance `BackgroundTaskStore`. Le panneau Work masque la section (`ProjectWorkStatePanel.tsx:638`, `agent.backgroundTasks.length > 0`) → aucun bouton Cancel/Retry en live. Le frontend était déjà entièrement câblé (`normalizeAgent`/`normalizeBackgroundTask`), il ne manquait que la projection backend. + +**Décision (frontière)** : étendre le read-model unique — ajouter `background_tasks: Vec` à `AgentWorkState` et brancher `BackgroundTaskStore` via un builder best-effort `with_background_tasks(store)` (calqué sur `with_conversation_sources`), `None` ⇒ zéro régression. **Pas** de fetch séparé côté front (garderait la jointure tâche→agent hors du domaine, casserait l'instantané cohérent, dupliquerait les cycles async). Aucun nouveau port/adapter. `list_background_tasks` (B7) reste comme read-model autonome, hors chemin du panneau. + +**Contrat** : `AgentBackgroundTaskState` = miroir de `BackgroundTaskDto` (`dto.rs:2886`) sans owner/project. Projection dans `execute` = union `list_open_for_agent(agent.id)` (per-agent, running/queued/waiting) + `list_undelivered_completions()` (chargé UNE fois avant la boucle, dispatché par `owner_agent_id`, pour failed/cancelled → Retry). Best-effort strict : erreur store ⇒ `Vec::new()` pour l'agent, jamais d'`AppError` (live/busy/tickets intacts). + +**Borne V1 assumée** : une tâche terminale **déjà livrée** (wake tiré, `completion_delivered=true`) n'est plus énumérable (droppée des deux listes) → disparaît du panneau ; Retry seulement dans la fenêtre non-livrée. Conforme à la note « retry session-scoped V1 » de `RetryBackgroundTask` (`background/mod.rs:212`). Historique Retry persistant = V2 (ajouterait `list_recent_terminal_for_agent` au port), ticketable si besoin. + +**Wiring** : `.with_background_tasks(state.background_task_store.clone())` dans `crates/app-tauri/src/state.rs` (ancre `b7-wiring-anchor-state-rs`) ; le store y est déjà managé (utilisé par `list_background_tasks`). Lié à [[b8-in-app-trigger-run-in-background]], [[b8-command-runner-pty-framing]], [[background-tasks-first-class-design]], [[checkpoint-t1-wake-delivery-bug-fixed-rebuild-pending]]. \ No newline at end of file