From 7c71544692ddbe29ec8eac30d66eeefee22bba90 Mon Sep 17 00:00:00 2001 From: Blomios Date: Sat, 20 Jun 2026 19:32:18 +0200 Subject: [PATCH] =?UTF-8?q?chore(wip):=20=C3=A9tat=20runtime=20.ideai=20+?= =?UTF-8?q?=20m=C3=A9moire=20(checkpoint=20Lot=20C=20r=C3=A9sum=C3=A9s=20c?= =?UTF-8?q?onversation)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conversations live, layouts, MEMORY.md et note checkpoint workstate-conversation-summaries-lot-c. Co-Authored-By: Claude Opus 4.8 --- .../handoff.md | 8 +-- .../log.jsonl | 2 + .../handoff.md | 8 +-- .../log.jsonl | 2 + .ideai/layouts.json | 33 ++++++++-- .ideai/memory/MEMORY.md | 1 + ...-workstate-conversation-summaries-lot-c.md | 60 +++++++++++++++++++ 7 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 .ideai/memory/checkpoint-workstate-conversation-summaries-lot-c.md diff --git a/.ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/handoff.md b/.ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/handoff.md index 9a69590..5c7ac61 100644 --- a/.ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/handoff.md +++ b/.ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/handoff.md @@ -1,11 +1,9 @@ --- -upTo: 4a9ff50a-6ad3-410c-89a8-36cfee9adc94 +upTo: c10a7683-5468-472c-adbb-37fb6fe8c470 objective: Nouvelle feature cadrée par Architect, prête à démarrer le développement : « Gestion des limites de session des agents » (détection hiérarchique des limites de session + reprise auto annulable). C'est --- **Objectif :** Nouvelle feature cadrée par Architect, prête à démarrer le développement : « Gestion des limites de session des agents » (détection hiérarchique des limites de session + reprise auto annulable). C'est -- **Response:** Décision Git (aucun commit effectué). ## État constaté - Branche courante : `feature/agent-skill-awareness-v2`, pointée exactement sur `e832af5` = tip de `develop`. `develop` est ancêtre de HEAD → **base saine, aucune divergence** (rebase inutile pour l'instant). - Working tree dirty, deux natures bien distinctes : - **Code (hotfix livraison délégation + logs submit)** — 8 fichiers, +352/-23 : - `crates/app-tauri/src/commands.rs`, `crates/application/src/orchestrator/service.rs`, `crates/infrastructure/src/input/mod.rs` - `frontend/src/adapters/input.ts`, `frontend/src/adapters/terminal.ts`, `frontend/src/domain/index.ts` - `frontend/src/features/terminals/useWritePortal.ts` + `useWritePortal.test.tsx` - **État runtime `.ideai/`** — conversations (handoff/log.jsonl ×3), `layouts.json`, `memory/MEMORY.md`, + nouveau `memory/checkpoint-delivery-submit-logging-fix.md` (non suivi). ## Stratégie de branche **On reste sur `feature/agent-skill-awareness-v2`.** Elle part proprement de `develop`, pas de divergence, c'est le bon support pour reprendre le chantier. Pas de nouvelle branche. (Réserve mineure : le nom de branche parle de « skill-awareness » alors que le diff actuel est un hotfix livraison/logs ; je l'assume car Main reprend ce chantier sous ce nom. Si tu préfères isoler le hotfix sous `fix/delivery-submit-logging`, dis-le, je crée et bascule — sinon on garde.) ## Plan de commit (PLUS TARD, sur ton GO) Deux commits atomiques séparés, par intention : 1. `fix(input): livraison délégation + journalisation submit` → les 8 fichiers code (crates + frontend + test). 2. `chore(wip): état runtime .ideai` → conversations, `layouts.json`, `MEMORY.md` + le nouveau checkpoint mémoire (cohérent avec l'historique `chore(wip)` existant). ## Hors commit / à laisser Rien à exclure ni à ignorer en plus pour l'instant : l'historique du dépôt versionne déjà l'état runtime `.ideai/` via des `chore(wip)`, donc je le committe à part plutôt que de le mélanger au code. (Remarque de fond : ces fichiers runtime sont du bruit de diff ; si tu veux un jour les `.gitignore`, c'est une décision produit séparée à trancher hors de ce ticket.) ## Conditions avant commit / merge - **Avant le commit code** : QA verte — tests Rust (`cargo test` sur les crates touchés) **et** front (`useWritePortal.test.tsx` notamment). Tant que c'est rouge, je ne committe pas le hotfix. - **Avant merge `feature/agent-skill-awareness-v2` → `develop`** : tests verts + rebase sur `develop` uniquement si elle a avancé entre-temps (actuellement non), puis merge `--no-ff`. - **Aucune action sortante** (push/PR) sans ton GO explicite. J'attends ton signal pour committer. -- **Prompt:** QA final vert accepté avec réserve environnementale pour `feature/agent-skill-awareness-v2`. Merci de committer localement selon ta stratégie. État attendu: changements code hotfix délégation/logs submit + nouveau changement `crates/application/src/agent/lifecycle.rs` pour awareness skills; fichiers runtime `.ideai/*` modifiés et nouveau checkpoint mémoire. Conditions QA: application/front/infrastructure/app-tauri check verts; tests loopback socket Unix réels non exécutables dans sandbox (`UnixListener::bind` PermissionDenied), alternatives avec skips vertes. Merci de faire des commits atomiques adaptés (tu avais proposé code puis chore runtime), décider si le changement `lifecycle.rs` doit être dans un commit séparé du hotfix ou groupé selon intention, puis retourner hashes, messages, fichiers inclus/exclus, et état final. Pas de push. - **Response:** Commits effectués localement sur `feature/agent-skill-awareness-v2` (aucun push). Décision : `lifecycle.rs` = intention distincte (la feature réelle de la branche) → commit séparé du hotfix. ## 3 commits atomiques (par intention) **1. `befff76` — feat(skills): injecte un paragraphe d'awareness skills dans le fichier de convention** - Inclus : `crates/application/src/agent/lifecycle.rs` - La feature de la branche : `compose_convention_file` émet l'awareness skills (assigné = contexte opérationnel, pas commande magique/sous-tâche ; capitalisation via `idea_create_skill` en MCP / `skill.create` en protocole fichier), sans injecter les corps des skills non assignés. **2. `018eb1a` — fix(input): fiabilise la livraison de délégation et journalise le submit** - Inclus : `crates/app-tauri/src/commands.rs`, `crates/application/src/orchestrator/service.rs`, `crates/infrastructure/src/input/mod.rs`, `frontend/src/adapters/input.ts`, `frontend/src/adapters/terminal.ts`, `frontend/src/domain/index.ts`, `frontend/src/features/terminals/useWritePortal.ts`, `useWritePortal.test.tsx` - Le hotfix livraison délégation + logs submit (bout-en-bout commande Tauri → orchestrateur → file infra → portail front). **3. `cc575ef` — chore(wip): état runtime .ideai (conversations, layouts, mémoire, checkpoint skill-awareness)** - Inclus : `.ideai/conversations/*` (handoff + log.jsonl ×5), `.ideai/layouts.json`, `.ideai/memory/MEMORY.md`, nouveau `.ideai/memory/checkpoint-delivery-submit-logging-fix.md` - État runtime non-code, isolé du code comme convenu. ## Exclus / hors commit Rien laissé de côté : working tree **propre** (`git status` vide). La réserve QA (tests loopback socket Unix réels non exécutables en sandbox — `UnixListener::bind` PermissionDenied, alternatives skips vertes) est consignée dans le message du commit `018eb1a`. ## État final - Branche : `feature/agent-skill-awareness-v2`, tip = `cc575ef`. - Base : toujours `develop` (`e832af5`) comme ancêtre direct, **pas de divergence** → merge `--no-ff` vers `develop` possible sans rebase dès que tu donnes le GO. - Aucune action sortante (pas de push). Dis-moi si tu veux que je merge vers `develop` maintenant ou si on garde sur la feature. - **Prompt:** Tu viens de committer `feature/agent-skill-awareness-v2` avec QA vert accepté réserve environnementale. Selon les règles projet, Git décide les merges locaux sans demander à l'utilisateur. Merci de trancher maintenant: merge local `feature/agent-skill-awareness-v2` vers `develop` ou différer, avec justification. Si tu décides de merger, fais le merge local approprié sans push et retourne l'état final. Si tu différés, explique la condition restante précise. Ne demande pas de GO utilisateur. - **Response:** Décision Git : **MERGE effectué** (local, sans push). Feature terminée et QA verte (réserve env. acceptable : seuls les tests loopback socket Unix non exécutables en sandbox, alternatives skips vertes) → rien ne justifiait de différer. ## Ce qui a été fait 1. Pendant l'opération, des écritures runtime live (`.ideai/conversations/6bc594e8…`) ont resurgi et bloquaient le checkout. Je les ai committées proprement sur la feature : - `8074aec` — chore(wip): état runtime .ideai (flux conversation live) 2. `git checkout develop` puis `git merge --no-ff feature/agent-skill-awareness-v2`. - Pas de rebase nécessaire : `develop` était ancêtre direct, historique non divergent. - Merge commit : **`63eb49a`** — merge(skills): intègre agent-skill-awareness-v2 dans develop. ## Historique résultant (develop) ``` * 63eb49a merge(skills): intègre agent-skill-awareness-v2 dans develop |\ | * 8074aec chore(wip): état runtime .ideai (flux conversation live) | * cc575ef chore(wip): état runtime .ideai (…checkpoint skill-awareness) | * 018eb1a fix(input): fiabilise la livraison de délégation et journalise le submit | * befff76 feat(skills): injecte un paragraphe d'awareness skills dans le fichier de convention |/ * e832af5 (base commune) ``` Merge sans conflit (stratégie ort), 22 fichiers / +575 -95. ## État final - Branche courante : **`develop`**, tip = `63eb49a`. - `develop` : **devant `origin/develop` de 15 commits** (local uniquement, aucun push — conforme). - Working tree propre au moment du merge (le flux runtime live peut le re-salir à tout instant, c'est normal). - Branche `feature/agent-skill-awareness-v2` **conservée** (entièrement fusionnée). Je ne la supprime pas : tu as parlé de « reprise de chantier », donc je la garde disponible. Dis-moi si tu veux que je la supprime maintenant qu'elle est intégrée. Aucune action sortante effectuée. Pour publier (`push origin develop`), il me faut ta validation explicite. @@ -23,4 +21,6 @@ objective: Nouvelle feature cadrée par Architect, prête à démarrer le dével - **Response:** Lot B `workstate delegation/queue snapshot` intégré. QA re-vérifié vert de mon côté avant tout commit (fmt OK ; domain 6, infra mailbox 13, application workstate 12, app-tauri dto_agents 20 ; vitest 2 fichiers/17 ; tsc clean). **3 commits atomiques** sur `feature/workstate-delegation-queue` (séparation code feature / runtime) : - `cc7d99a` feat(workstate): snapshot des délégations en file par agent (Lot B backend) — domain `QueuedTicketSnapshot` + trait ségrégué `AgentQueueSnapshot` (ISP), infra `InMemoryMailbox` vue lecture, application read-model, DTO camelCase app-tauri + état. - `c600604` feat(workstate): UI des délégations en file par agent (Lot B frontend) — domaine/mock alignés, `ProjectWorkStatePanel` enrichi (requester, aperçu, position FIFO), hook. - `5cb99fd` chore(wip): état runtime .ideai (flux conversation live, layouts) — runtime isolé, jamais mélangé au code feature. **Merge local** : feature TERMINÉE et VERTE → `git merge --no-ff feature/workstate-delegation-queue` dans **develop** → commit de merge `64c2c14`. `develop` n'avait pas divergé (il pointait sur la base de la feature) ; `--no-ff` choisi pour garder la trace d'intégration du lot, cohérent avec l'intégration du Lot A (`3bfb932`). **Pas de push, aucune branche supprimée.** `feature/workstate-delegation-queue` est conservée (sur `cc7d99a→5cb99fd`). **État final** : - Branche courante : `develop` (HEAD = `64c2c14`). - Working tree : propre (`git status` vide). - `feature/workstate-delegation-queue` préservée et entièrement contenue dans `develop`. - Rien en attente côté git. Prêt pour une future release `develop → main` sur ta validation explicite. - **Prompt:** Prépare le dépôt pour le Lot C `workstate conversation summaries` après Lot B mergé dans `develop` @ `64c2c14`. État attendu côté Main: `develop` ahead origin, avec dirty runtime/memory lié au checkpoint récent (`.ideai/conversations/...`, `.ideai/memory/MEMORY.md`, `.ideai/memory/checkpoint-workstate-delegation-queue-lot-b.md`). Merci de: - gérer ce dirty sans perte et sans le mélanger au futur code applicatif (commit runtime/memory si tu juges cohérent avec l'historique), - créer/basculer sur une branche feature propre depuis `develop` pour Lot C, - ne push pas, ne supprime pas de branche, - retourner branche courante, commits éventuels, et `git status` final. Nom de branche suggéré: `feature/workstate-conversation-summaries`. - **Response:** Dépôt prêt pour le Lot C `workstate conversation summaries`. **Dirty traité** : tout le dirty était runtime/mémoire (aucun code applicatif), donc commité tel quel sur `develop`, sans risque de le mélanger au futur code Lot C : - `e5dd4f8` chore(wip): état runtime .ideai + mémoire (checkpoint Lot B délégations/file) — conversations live, `.ideai/memory/MEMORY.md` et nouvelle note `checkpoint-workstate-delegation-queue-lot-b.md` (fichier non suivi désormais versionné). Cohérent avec l'historique des commits runtime. **Branche feature** : créée et basculée depuis `develop` (@ `e5dd4f8`) : - `feature/workstate-conversation-summaries` **Pas de push, aucune branche supprimée.** **État final** : - Branche courante : `feature/workstate-conversation-summaries` (HEAD = `e5dd4f8`, partagé avec `develop`). - Working tree : propre (`git status` vide). - `develop` est ahead de origin (le checkpoint runtime/mémoire est local, non poussé). Le dev Lot C peut démarrer sur cette branche. Reviens vers moi pour committer/merger une fois QA vert. -- **Prompt:** Lot C `workstate conversation summaries` est validé QA vert sur `feature/workstate-conversation-summaries`. Merci de gérer les commits atomiques et la décision de merge local, sans push. Contexte QA final: - `cargo fmt --all -- --check` OK - `cargo test -p application --test workstate` OK, 21 passed, aucun warning Rust - `cargo test -p app-tauri --test dto_agents` OK, 21 passed - `cargo check -p app-tauri` OK (validé avant nettoyage warning; le nettoyage ne touche qu'un test) - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` OK, 2 files / 19 tests (warnings Vite non bloquants) - `cd frontend && npx tsc --noEmit` OK Fichiers attendus: - Backend/Tauri: `crates/application/src/workstate/mod.rs`, `crates/application/src/lib.rs`, `crates/app-tauri/src/dto.rs`, `crates/app-tauri/src/state.rs`, `crates/application/tests/workstate.rs`, `crates/app-tauri/tests/dto_agents.rs`. - Frontend: `frontend/src/domain/index.ts`, `frontend/src/adapters/mock/index.ts`, `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`, `frontend/src/features/workstate/workstate.test.tsx`. - Dirty runtime `.ideai/*` possible; isole-le si présent, ne mélange pas avec feature code. Décide: 1. commits atomiques, 2. merge local éventuel vers `develop`, 3. état final. Ne push pas, ne supprime pas de branche. Retourne commits créés, merge ou non, branche finale, status final. \ No newline at end of file +- **Prompt:** Lot C `workstate conversation summaries` est validé QA vert sur `feature/workstate-conversation-summaries`. Merci de gérer les commits atomiques et la décision de merge local, sans push. Contexte QA final: - `cargo fmt --all -- --check` OK - `cargo test -p application --test workstate` OK, 21 passed, aucun warning Rust - `cargo test -p app-tauri --test dto_agents` OK, 21 passed - `cargo check -p app-tauri` OK (validé avant nettoyage warning; le nettoyage ne touche qu'un test) - `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` OK, 2 files / 19 tests (warnings Vite non bloquants) - `cd frontend && npx tsc --noEmit` OK Fichiers attendus: - Backend/Tauri: `crates/application/src/workstate/mod.rs`, `crates/application/src/lib.rs`, `crates/app-tauri/src/dto.rs`, `crates/app-tauri/src/state.rs`, `crates/application/tests/workstate.rs`, `crates/app-tauri/tests/dto_agents.rs`. - Frontend: `frontend/src/domain/index.ts`, `frontend/src/adapters/mock/index.ts`, `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`, `frontend/src/features/workstate/workstate.test.tsx`. - Dirty runtime `.ideai/*` possible; isole-le si présent, ne mélange pas avec feature code. Décide: 1. commits atomiques, 2. merge local éventuel vers `develop`, 3. état final. Ne push pas, ne supprime pas de branche. Retourne commits créés, merge ou non, branche finale, status final. +- **Response:** Lot C `workstate conversation summaries` intégré. QA re-vérifié vert de mon côté avant tout commit (fmt OK ; application workstate 21, app-tauri dto_agents 21, aucun warning Rust ; vitest 2 fichiers/19 ; tsc clean). **3 commits atomiques** sur `feature/workstate-conversation-summaries` (code feature / runtime séparés) : - `e9edadc` feat(workstate): résumés de conversation dans le read-model (Lot C backend) — assemblage des résumés dans application + DTO camelCase et exposition app-tauri (dto.rs, state.rs). - `c50622e` feat(workstate): UI des résumés de conversation (Lot C frontend) — domaine/mock alignés, `ProjectWorkStatePanel` enrichi. - `78500d8` chore(wip): état runtime .ideai (flux conversation live) — runtime isolé, jamais mêlé au code feature. **Merge local** : feature TERMINÉE et VERTE → `git merge --no-ff feature/workstate-conversation-summaries` dans **develop** → commit de merge `6e1ba7e`. `--no-ff` pour garder la trace d'intégration du lot, cohérent avec Lot A (`3bfb932`) et Lot B (`64c2c14`). **Pas de push, aucune branche supprimée.** `feature/workstate-conversation-summaries` conservée (sur `e9edadc→78500d8`). **État final** : - Branche courante : `develop` (HEAD = `6e1ba7e`). - Working tree : propre (`git status` vide). - `feature/workstate-conversation-summaries` préservée et entièrement contenue dans `develop`. - `develop` en avance sur origin (rien poussé). - Trilogie workstate Lot A→B→C désormais intégrée dans `develop` ; prêt pour une future release `develop → main` sur ta validation explicite. +- **Prompt:** Prépare le dépôt pour le Lot D `workstate controlled actions` après Lot C mergé dans `develop` @ `6e1ba7e`. État probable: dirty runtime/memory lié au checkpoint Lot C (`.ideai/*`, nouvelle note mémoire). Merci de gérer ce dirty sans perte et sans le mélanger au futur code applicatif, puis créer/basculer sur une branche feature propre depuis `develop`. Nom suggéré: `feature/workstate-controlled-actions`. Contraintes: pas de push, pas de suppression de branche. Retourne commits éventuels, branche courante, status final. \ No newline at end of file diff --git a/.ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/log.jsonl b/.ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/log.jsonl index a43782c..77f7ca7 100644 --- a/.ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/log.jsonl +++ b/.ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/log.jsonl @@ -53,3 +53,5 @@ {"id":"1cbd7c12-6608-45b4-aa9c-41dff0fedd51","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781975270116,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Prépare le dépôt pour le Lot C `workstate conversation summaries` après Lot B mergé dans `develop` @ `64c2c14`.\n\nÉtat attendu côté Main: `develop` ahead origin, avec dirty runtime/memory lié au checkpoint récent (`.ideai/conversations/...`, `.ideai/memory/MEMORY.md`, `.ideai/memory/checkpoint-workstate-delegation-queue-lot-b.md`).\n\nMerci de:\n- gérer ce dirty sans perte et sans le mélanger au futur code applicatif (commit runtime/memory si tu juges cohérent avec l'historique),\n- créer/basculer sur une branche feature propre depuis `develop` pour Lot C,\n- ne push pas, ne supprime pas de branche,\n- retourner branche courante, commits éventuels, et `git status` final.\n\nNom de branche suggéré: `feature/workstate-conversation-summaries`."} {"id":"2896e211-78cf-448f-8e78-b559a326ad0a","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781975298096,"source":{"kind":"agent","agentId":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"},"role":"response","text":"Dépôt prêt pour le Lot C `workstate conversation summaries`.\n\n**Dirty traité** : tout le dirty était runtime/mémoire (aucun code applicatif), donc commité tel quel sur `develop`, sans risque de le mélanger au futur code Lot C :\n- `e5dd4f8` chore(wip): état runtime .ideai + mémoire (checkpoint Lot B délégations/file) — conversations live, `.ideai/memory/MEMORY.md` et nouvelle note `checkpoint-workstate-delegation-queue-lot-b.md` (fichier non suivi désormais versionné). Cohérent avec l'historique des commits runtime.\n\n**Branche feature** : créée et basculée depuis `develop` (@ `e5dd4f8`) :\n- `feature/workstate-conversation-summaries`\n\n**Pas de push, aucune branche supprimée.**\n\n**État final** :\n- Branche courante : `feature/workstate-conversation-summaries` (HEAD = `e5dd4f8`, partagé avec `develop`).\n- Working tree : propre (`git status` vide).\n- `develop` est ahead de origin (le checkpoint runtime/mémoire est local, non poussé).\n\nLe dev Lot C peut démarrer sur cette branche. Reviens vers moi pour committer/merger une fois QA vert."} {"id":"4a9ff50a-6ad3-410c-89a8-36cfee9adc94","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781976486735,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Lot C `workstate conversation summaries` est validé QA vert sur `feature/workstate-conversation-summaries`. Merci de gérer les commits atomiques et la décision de merge local, sans push.\n\nContexte QA final:\n- `cargo fmt --all -- --check` OK\n- `cargo test -p application --test workstate` OK, 21 passed, aucun warning Rust\n- `cargo test -p app-tauri --test dto_agents` OK, 21 passed\n- `cargo check -p app-tauri` OK (validé avant nettoyage warning; le nettoyage ne touche qu'un test)\n- `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` OK, 2 files / 19 tests (warnings Vite non bloquants)\n- `cd frontend && npx tsc --noEmit` OK\n\nFichiers attendus:\n- Backend/Tauri: `crates/application/src/workstate/mod.rs`, `crates/application/src/lib.rs`, `crates/app-tauri/src/dto.rs`, `crates/app-tauri/src/state.rs`, `crates/application/tests/workstate.rs`, `crates/app-tauri/tests/dto_agents.rs`.\n- Frontend: `frontend/src/domain/index.ts`, `frontend/src/adapters/mock/index.ts`, `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`, `frontend/src/features/workstate/workstate.test.tsx`.\n- Dirty runtime `.ideai/*` possible; isole-le si présent, ne mélange pas avec feature code.\n\nDécide:\n1. commits atomiques,\n2. merge local éventuel vers `develop`,\n3. état final.\n\nNe push pas, ne supprime pas de branche. Retourne commits créés, merge ou non, branche finale, status final."} +{"id":"923ce31a-d68c-43ca-9781-3d0f3d33f922","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781976561209,"source":{"kind":"agent","agentId":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"},"role":"response","text":"Lot C `workstate conversation summaries` intégré. QA re-vérifié vert de mon côté avant tout commit (fmt OK ; application workstate 21, app-tauri dto_agents 21, aucun warning Rust ; vitest 2 fichiers/19 ; tsc clean).\n\n**3 commits atomiques** sur `feature/workstate-conversation-summaries` (code feature / runtime séparés) :\n- `e9edadc` feat(workstate): résumés de conversation dans le read-model (Lot C backend) — assemblage des résumés dans application + DTO camelCase et exposition app-tauri (dto.rs, state.rs).\n- `c50622e` feat(workstate): UI des résumés de conversation (Lot C frontend) — domaine/mock alignés, `ProjectWorkStatePanel` enrichi.\n- `78500d8` chore(wip): état runtime .ideai (flux conversation live) — runtime isolé, jamais mêlé au code feature.\n\n**Merge local** : feature TERMINÉE et VERTE → `git merge --no-ff feature/workstate-conversation-summaries` dans **develop** → commit de merge `6e1ba7e`. `--no-ff` pour garder la trace d'intégration du lot, cohérent avec Lot A (`3bfb932`) et Lot B (`64c2c14`).\n\n**Pas de push, aucune branche supprimée.** `feature/workstate-conversation-summaries` conservée (sur `e9edadc→78500d8`).\n\n**État final** :\n- Branche courante : `develop` (HEAD = `6e1ba7e`).\n- Working tree : propre (`git status` vide).\n- `feature/workstate-conversation-summaries` préservée et entièrement contenue dans `develop`.\n- `develop` en avance sur origin (rien poussé).\n- Trilogie workstate Lot A→B→C désormais intégrée dans `develop` ; prêt pour une future release `develop → main` sur ta validation explicite."} +{"id":"c10a7683-5468-472c-adbb-37fb6fe8c470","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781976728705,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Prépare le dépôt pour le Lot D `workstate controlled actions` après Lot C mergé dans `develop` @ `6e1ba7e`.\n\nÉtat probable: dirty runtime/memory lié au checkpoint Lot C (`.ideai/*`, nouvelle note mémoire). Merci de gérer ce dirty sans perte et sans le mélanger au futur code applicatif, puis créer/basculer sur une branche feature propre depuis `develop`.\n\nNom suggéré: `feature/workstate-controlled-actions`.\n\nContraintes: pas de push, pas de suppression de branche. Retourne commits éventuels, branche courante, status final."} diff --git a/.ideai/conversations/7a2f446c-2efa-0c56-08dd-07cdc5cac19b/handoff.md b/.ideai/conversations/7a2f446c-2efa-0c56-08dd-07cdc5cac19b/handoff.md index 577cb49..c9262b0 100644 --- a/.ideai/conversations/7a2f446c-2efa-0c56-08dd-07cdc5cac19b/handoff.md +++ b/.ideai/conversations/7a2f446c-2efa-0c56-08dd-07cdc5cac19b/handoff.md @@ -1,11 +1,9 @@ --- -upTo: 998a4f67-a352-4e89-a5ee-71260d89f112 +upTo: e1535768-8ce1-41b7-bc7e-e549fe28ea00 objective: CADRAGE ARCHITECTURE (pas de code) — Support de la délégation inter-agents (idea_ask_agent/idea_reply) pour les profils CODEX. ## Contexte / gap déjà localisé Le round-trip inter-agent fonctionne pour --- **Objectif :** CADRAGE ARCHITECTURE (pas de code) — Support de la délégation inter-agents (idea_ask_agent/idea_reply) pour les profils CODEX. ## Contexte / gap déjà localisé Le round-trip inter-agent fonctionne pour -- **Prompt:** Arbitrage de scope sur le filet humain NIVEAU 3 de la feature limites de session (ARCHITECTURE §21.1 / §21 « niveau 3 »). Contexte : backend (commit 9df5923) + front (en cours, vert) de la gestion des limites de session sont posés. Niveaux 1 (structuré) et 2 (regex PTY) : pleinement fonctionnels (détecter→planifier reprise auto annulable→reprendre). Le front consomme les 5 DOMAIN_EVENT, affiche le badge « limité jusqu'à HH:MM », le compte à rebours + bouton « Annuler la reprise », et appelle la commande Tauri `cancel_resume`. GAP constaté sur le niveau 3 : l'événement `agentRateLimitSuspected` (limite suspectée SANS heure de reset fiable) est bien émis et affiché côté front (note « limité · heure inconnue — reprise à préciser »), MAIS il n'existe AUCUNE commande backend pour que l'utilisateur SAISISSE l'heure de reprise et déclenche une reprise auto à cet instant. DevFrontend a (correctement) refusé de fabriquer une commande inexistante et a posé un TODO. Donc aujourd'hui, sur niveau 3, l'utilisateur est INFORMÉ mais ne peut pas AGIR via le mécanisme de reprise auto — il lui reste seulement le relancement manuel de l'agent par les contrôles existants. Le design verrouillé (mémoire session-limit-handling-design) dit : niveau 3 = « filet humain : IdeA DEMANDE à l'utilisateur. Garantit le 100% même pour un novice : jamais d'inaction silencieuse » ; et « Couverture : les TROIS niveaux d'emblée ». QUESTION (tranche, c'est ton domaine — contrat/frontière) : A) Le niveau 3 est-il considéré COMPLET en l'état (surfacer la suspicion + s'appuyer sur le relancement manuel existant satisfait « jamais d'inaction silencieuse ») → alors la feature est terminée, on merge. OU B) Le niveau 3 EXIGE une boucle d'action : une commande applicative type `set_resume_at(agentId, epochMs)` qui arme le même `SessionLimitService` (planifier une reprise à l'instant saisi, annulable comme les autres) + un formulaire de saisie d'heure côté front. → alors c'est une slice supplémentaire LSx (domaine? non / application: méthode service + port? / app-tauri: commande / front: form) que je dois cadrer avant le merge. Si B), précise le découpage minimal et le contrat exact de la nouvelle entrée (signature commande, où elle branche dans SessionLimitService, événements émis — réutilise-t-on AgentResumeScheduled ?). Réponds de façon décisionnelle. -- **Response:** VERDICT : **B**. Le niveau 3 n'est PAS complet en l'état. Une slice supplémentaire (LS8) est requise AVANT le merge. Le découpage et le contrat exact suivent. ## Pourquoi B (et pas A) Le design verrouillé est sans ambiguïté : niveau 3 = « IdeA **DEMANDE** à l'utilisateur » et « les TROIS niveaux d'emblée ». « Demander » est un acte qui **arme** une reprise, pas une note d'information. La preuve est dans le code lui-même : 1. `domain::session_limit::RateLimitSource::Human` existe déjà, documenté « niveau 3 : confirmée par l'utilisateur », et n'est **produit nulle part** aujourd'hui. C'est du code mort qui attend exactement cette entrée. L'archi a toujours prévu B. 2. La doc de `ResumePlan::HumanFallback` dit : « L'application **demandera l'heure** à l'utilisateur plutôt que d'agir à l'aveugle ». La boucle d'action fait partie du contrat domaine, pas une extension. Surfacer + relancement manuel (chemin `ListResumableAgents`) ne satisfait PAS « jamais d'inaction silencieuse » au sens de la feature : c'est un **autre mécanisme**, qui ne donne ni la reprise auto programmée ni la fenêtre **annulable** que les niveaux 1/2 garantissent. Sur niveau 3, l'utilisateur est aujourd'hui informé mais le mécanisme central de la feature lui est inaccessible. Incohérence de contrat ⇒ non mergeable tel quel. DevFrontend a eu raison de poser le TODO plutôt que d'inventer la commande. ## Découpage minimal — LS8 « filet humain : armement par heure saisie » **Domaine : RIEN à ajouter.** `plan_resume`, `SessionLimit`, `RateLimitSource::Human`, `ResumePlan::Scheduled` couvrent déjà tout. Une heure saisie par l'utilisateur est fonctionnellement une `SessionLimit` de source `Human` avec `resets_at_ms = Some(epoch)`. Le clamp anti-passé (`max(now)`) protège déjà une saisie déjà échue ⇒ reprise immédiate. C'est le payoff de l'hexagonal : zéro nouveau port, zéro nouvel adapter. **Application — `SessionLimitService` : une méthode publique.** N'élargis PAS `on_rate_limited` (sémantique « signal de détection auto »). Ajoute une entrée dédiée, en réutilisant strictement la branche `Scheduled` existante : ```rust /// (d) Filet humain (§21.1 niveau 3). L'utilisateur a saisi l'heure de reset /// pour un agent en limite SUSPECTÉE (AgentRateLimitSuspected, sans heure fiable). /// Construit une SessionLimit source `Human`, calcule le plan et arme la reprise /// EXACTEMENT comme la branche auto : mêmes événements, même dédoublonnage, /// même annulabilité via cancel_resume. pub fn confirm_human_resume( &self, agent_id: AgentId, node_id: NodeId, conversation_id: Option, resets_at_ms: i64, // i64 nu, pas Option : la saisie EST l'heure ) ``` Corps = copie de la branche `ResumePlan::Scheduled` de `on_rate_limited` (publish `AgentRateLimited{Some}` → `disarm` → `scheduler.arm(ResumeAgent)` → mémoriser le `ScheduleId` → publish `AgentResumeScheduled`), avec `SessionLimit::new(Some(resets_at_ms), now, RateLimitSource::Human)`. **Factorise** la branche en un `fn arm_scheduled(&self, agent_id, fire_at_ms, node_id, conversation_id)` privé appelé par les deux chemins, pour ne pas dupliquer le dédoublonnage. `execute_resume` et `cancel_resume` restent **inchangés** : l'armement humain est annulable et s'exécute par les mêmes voies (c'est l'invariant à préserver — un seul mécanisme de reprise). **app-tauri — une commande.** Miroir exact de `cancel_resume` : ```rust #[tauri::command] pub async fn set_resume_at( agent_id: String, resets_at_ms: i64, state: State<'_, AppState>, ) -> Result<(), ErrorDto> ``` Corps : `parse_agent_id` → résoudre `node_id` et `conversation_id` **côté backend** depuis la registry (le front n'a que l'`agent_id` ; `agentRateLimitSuspected` ne porte que ça) : - `node_id` via `TerminalSessions::node_for_agent(&id)` (ou la registry unifiée). Si `None` ⇒ `ErrorDto` INVALID/NOT_FOUND (l'agent n'a plus de cellule vivante — la saisie n'a pas de cible). - `conversation_id` via la session structurée `session_for_agent(&id).map(|s| s.conversation_id())` (best-effort `None` toléré, comme tout le chemin de reprise dégradé). Puis `state.session_limit_service.confirm_human_resume(id, node_id, conv_id, resets_at_ms)`. Enregistre la commande dans `lib.rs` à côté de `cancel_resume`. **Front — un formulaire minimal.** Sur le badge « limité · heure inconnue — reprise à préciser » : un petit input heure (ou datetime) → calcule l'epoch ms → `invoke("set_resume_at", { agentId, resetsAtMs })`. Dès réception de `AgentResumeScheduled` (déjà consommé !), le badge bascule **automatiquement** sur l'état nominal « limité jusqu'à HH:MM » + compte à rebours + bouton « Annuler la reprise » déjà câblés. Aucun nouvel événement, aucune nouvelle vue d'état côté front : la boucle se referme sur l'UI existante. ## Contrat des événements — RÉUTILISE l'existant, n'en crée AUCUN C'est le point clé de cohérence : `confirm_human_resume` émet la **même** paire que la branche auto — `AgentRateLimited { agent_id, resets_at_ms: Some(t) }` puis **`AgentResumeScheduled { agent_id, fire_at_ms }`**. Le front les consomme déjà. La transition « suspecté → programmé » se fait donc sans code de présentation neuf : `AgentResumeScheduled` est le pivot qui retire l'état « heure inconnue » et réutilise le rendu nominal. Annulation ⇒ `AgentResumeCancelled` (inchangé). Reprise ⇒ `AgentResumed` (inchangé). `AgentRateLimitSuspected` reste l'unique signal « il faut demander », rien d'autre. ## Garde-fous QA pour le binôme - Saisie dans le passé : pas un cas d'erreur — `plan_resume` clampe à `now` ⇒ reprise quasi-immédiate. Test à ajouter. - Re-saisie / second suspect : `confirm_human_resume` doit passer par le **même `disarm`** que l'auto ⇒ un seul armement par agent (§21.10-4). Test de dédoublonnage croisé (humain après auto, et inverse). - Agent disparu entre suspicion et saisie : `node_for_agent == None` ⇒ erreur propre, pas d'armement orphelin. - Course cancel-pile-au-tir : inchangée, déjà couverte par `cancel_resume`. Périmètre total : ~1 méthode service (factorisée) + 1 commande + 1 input front + tests. Aucune frontière nouvelle, aucun port, aucun adapter. À cadrer et livrer en LS8 avant que Git n'envisage le merge `feature/* → develop`. - **Prompt:** Diagnostic architecture demandé par Main. Contexte: IdeA intègre Codex CLI. Le profil Codex actuel utilise McpConfigStrategy::TomlConfigHome { target: ".codex/config.toml", home_env: "CODEX_HOME" }, donc au lancement IdeA écrit {runDir}/.codex/config.toml et pousse CODEX_HOME vers ce dossier pour isoler MCP/permissions par agent. Problème produit: chaque agent Codex redemande une connexion ChatGPT/OpenAI, et si l’utilisateur crée beaucoup d’agents il atteint une limite de requests/login. De plus un agent jamais lancé peut échouer lors d’une délégation car il n’est pas authentifié. Questions: 1) quelle solution respecte l’architecture hexagonale/SOLID pour partager l’auth Codex au niveau ordinateur/utilisateur tout en gardant la config MCP/permissions isolée par agent ? 2) faut-il introduire un port Auth/RuntimeHealth/Readiness pour préflight au moment de création/configuration d’agent ? 3) quels changements concrets recommandes-tu dans le domaine/application/infrastructure ? Merci de répondre en français, concis mais actionnable. - **Prompt:** Cadrage architecture obligatoire avant implémentation. Contexte produit : nous voulons ajouter à IdeA une gestion automatique de la croissance des conversations d'agents afin de réduire les coûts tokens. Le système existant a déjà : log canonique `.ideai/conversations/.../log.jsonl`, handoff incrémental `handoff.md`, injection du handoff au lancement/relaunch, conversation_id logique de paire IdeA, provider session store, reprise session provider quand possible. Besoin : mettre en place une compression/rotation automatique des conversations d'agents, mais avec stabilité maximale. Contraintes fortes validées avec l'utilisateur : - Ne jamais couper un agent au milieu du développement d'une feature. - Ne jamais interrompre un tour en cours, outil en cours, terminal occupé, délégation pendante, ou attente de `idea_reply`. - Ne pas casser les contacts inter-agents : les messages doivent cibler l'identité logique AgentId/ConversationId, pas une session physique instable. - Si un autre agent essaie de contacter un agent en rotation, le routage doit être stable : queue, attente ou ancienne session jusqu'à bascule sûre. - La rotation doit être opportuniste, non interruptive : au seuil normal, préparer/planifier au prochain point sûr ; seuil critique = signaler/attendre point sûr, pas tuer au milieu. - Attention au detach/attach de l'agent sur la cellule : la cellule est une vue, pas la session. - Ne jamais faire de rotation automatique si l'utilisateur est en train d'écrire dans la cellule : focus, buffer d'entrée non envoyé, frappe récente, IME/composition, prompt interactif si détectable. - Nouvelle session idéalement démarrée détachée/background, vérifiée prête, puis bascule atomique du routage + attachement cellule. - Checkpoint durable obligatoire avant bascule : log canonique append OK + handoff à jour. Demande : 1. Lire l'architecture et le code existant autour agent lifecycle, terminal sessions, orchestrator routing, conversation log/handoff, provider sessions, layout/cell attach. 2. Proposer un design hexagonal/SOLID précis : domaine, ports éventuels, use cases, adapters, events, UI state. 3. Découper en lots implémentables DevBackend/DevFrontend/QA, avec tests attendus. 4. Identifier les pièces déjà existantes à réutiliser et les risques. 5. Ne pas coder. Rends un rapport utilisable directement par DevBackend/DevFrontend/QA. - **Prompt:** Contexte bug IdeA: une délégation inter-agent longue envoyée à un agent Codex TUI arrive visiblement tronquée/partielle dans le terminal cible. Code actuel: frontend `frontend/src/features/terminals/useWritePortal.ts` écrit `head.text` en un seul `handle.write(encoder.encode(head.text))`, attend `submitDelayMs` puis écrit `\r`; chemin headless `crates/infrastructure/src/input/mod.rs::make_headless_sink` écrit aussi `text.as_bytes()` d'un coup puis submit. `makeTerminalHandle` sérialise déjà les invokes et `PortablePtyAdapter::write` fait `write_all+flush`. Hypothèse: le TUI/paste handling ou IPC/PTY avec gros bloc peut perdre/ignorer une fin avant Enter. Proposition de correction locale: introduire un envoi chunké pour les injections de délégation (front et headless), avec chunks UTF-8 safe, pause courte entre chunks, puis délai anti-paste existant avant submit; ack seulement après tous les chunks + submit. Ajouter tests `useWritePortal` qui vérifient texte long chunké en ordre et ack après submit; tests infra input headless si simple. Est-ce compatible avec l'architecture actuelle (single writer, submit config, hexagonal), et vois-tu un meilleur point de correction ? Réponds court avec risques/contraintes. @@ -23,4 +21,6 @@ objective: CADRAGE ARCHITECTURE (pas de code) — Support de la délégation int - **Prompt:** Lot A `ProjectWorkState` minimal est intégré localement dans `develop` (`3bfb932`). Cadrage demandé pour le Lot B du chantier `UX conversations/délégations + read-model live-state`: queue/delegation snapshot propre. Objectif: exposer dans le read-model les délégations/tickets en attente/en cours de façon read-only, sans nouvelle persistance durable et sans refonte FIFO. Merci de définir précisément: source de vérité backend, éventuelle extension read-only de `AgentMailbox`/`InputMediator`, DTOs, impacts frontend, inclus/exclus, tests attendus. Ne code pas. - **Response:** ## Lot B — Queue / Delegation Snapshot Read-Only Objectif : enrichir `ProjectWorkState` avec les tickets actuellement présents dans la FIFO par agent, en lecture seule, sans nouvelle persistance durable et sans changer la sémantique `enqueue/resolve/cancel`. ### Source de vérité backend La source de vérité des délégations/tickets est **la mailbox existante**, pas le frontend et pas un nouveau store : - `domain::mailbox::Ticket` porte déjà `id`, `source`, `conversation`, `requester`, `task`. - `infrastructure::InMemoryMailbox` possède déjà les `VecDeque` par `AgentId`. - `InputMediator::busy_state(agent)` reste la source de vérité du ticket **en cours**. Donc : - **queue/order** = snapshot de `InMemoryMailbox` ; - **status inProgress/queued** = dérivé en croisant la queue avec `InputMediator::busy_state` ; - **live session** = déjà Lot A via `LiveSessions` ; - **manifest boundary** = toujours `AgentContextStore.load_manifest(project)`. Ne pas reconstruire une deuxième FIFO dans `ProjectWorkState`. ## Extension read-only recommandée Ne pas étendre `InputMediator` : il est l’autorité busy/livraison, pas l’autorité de contenu de queue. Éviter aussi de grossir le port mutateur `AgentMailbox` si possible. Ajouter un port ségrégué read-only dans `domain/src/mailbox.rs` : ```rust #[derive(Debug, Clone, PartialEq, Eq)] pub struct QueuedTicketSnapshot { pub id: TicketId, pub source: InputSource, pub conversation: ConversationId, pub requester: String, pub task: String, pub position: u32, } pub trait AgentQueueSnapshot: Send + Sync { fn queue_for(&self, agent: AgentId) -> Vec; } ``` Implémenter `AgentQueueSnapshot` pour `InMemoryMailbox` en clonant uniquement les données de `Ticket`, jamais les `oneshot::Sender`. Pourquoi ce choix : - ISP : le read-model ne dépend pas de `enqueue/resolve/cancel`. - Aucun changement de comportement FIFO. - Les tests peuvent fournir un fake `AgentQueueSnapshot` sans implémenter tout `AgentMailbox`. Alternative acceptable si l’équipe préfère moins de types : ajouter `fn snapshot_for(&self, agent: AgentId) -> Vec` directement à `AgentMailbox`, avec impl par défaut vide. Mais c’est moins propre : les ports mutateur et lecture restent mélangés. ## Modèle application Fichier principal : `crates/application/src/workstate/mod.rs`. Étendre `GetProjectWorkState` : ```rust pub struct GetProjectWorkState { contexts: Arc, live: Arc, input: Arc, queue: Arc, } ``` Étendre les structs : ```rust pub struct AgentWorkState { pub agent_id: AgentId, pub name: String, pub profile_id: ProfileId, pub live: Option, pub busy: AgentBusyState, pub tickets: Vec, } pub struct AgentTicketState { pub ticket_id: TicketId, pub conversation_id: ConversationId, pub position: u32, pub status: TicketWorkStatus, pub source: TicketWorkSource, pub requester_label: String, pub task_preview: String, pub task_len: usize, } pub enum TicketWorkStatus { InProgress, Queued, } pub enum TicketWorkSource { Human, Agent { agent_id: AgentId }, } ``` Dérivation status : ```rust let busy_ticket = self.input.busy_state(agent.id).ticket(); status = if busy_ticket == Some(ticket.id) { TicketWorkStatus::InProgress } else { TicketWorkStatus::Queued }; ``` `task_preview` : générer côté application pour éviter d’envoyer tout le prompt au panneau. Recommandation : trim + remplacer whitespace runs par espace + couper à 160 caractères. Garder `task_len` pour indiquer qu’il y a plus. Important : conserver l’ordre FIFO via `position`, ne pas retrier les tickets. ## Tauri / DTO Étendre `crates/app-tauri/src/dto.rs`. ```rust #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct AgentWorkStateDto { pub agent_id: String, pub name: String, pub profile_id: String, pub live: Option, pub busy: AgentBusyState, pub tickets: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct AgentTicketStateDto { pub ticket_id: String, pub conversation_id: String, pub position: u32, pub status: TicketWorkStatusDto, pub source: TicketWorkSourceDto, pub requester_label: String, pub task_preview: String, pub task_len: usize, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum TicketWorkStatusDto { InProgress, Queued, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase", tag = "kind")] pub enum TicketWorkSourceDto { Human, Agent { agent_id: String }, } ``` Wire JSON attendu : ```json { "agents": [ { "agentId": "...", "name": "Architect", "profileId": "...", "live": { "nodeId": "...", "sessionId": "...", "kind": "pty" }, "busy": { "state": "busy", "ticket": "...", "sinceMs": 1234 }, "tickets": [ { "ticketId": "...", "conversationId": "...", "position": 0, "status": "inProgress", "source": { "kind": "agent", "agentId": "..." }, "requesterLabel": "Main", "taskPreview": "Analyser...", "taskLen": 842 } ] } ] } ``` La commande Tauri reste la même : ```rust get_project_work_state(project_id) -> ProjectWorkStateDto ``` Aucune nouvelle commande nécessaire. ## Wiring AppState Dans `crates/app-tauri/src/state.rs` : - garder l’`Arc` déjà créé au composition root ; - le passer à `GetProjectWorkState::new(...)` comme `Arc` ; - continuer à passer le même `Arc` au `MediatedInbox` et à l’orchestrateur comme `AgentMailbox`. But : un seul objet `InMemoryMailbox`, deux vues de port : ```rust let inmemory_mailbox = Arc::new(InMemoryMailbox::new()); let mailbox = Arc::clone(&inmemory_mailbox) as Arc; let queue_snapshot = Arc::clone(&inmemory_mailbox) as Arc; ``` Puis : ```rust GetProjectWorkState::new(contexts, live_sessions, input_mediator, queue_snapshot) ``` ## Frontend contracts Étendre `frontend/src/domain/index.ts` : ```ts export type TicketWorkStatus = "inProgress" | "queued"; export type TicketWorkSource = | { kind: "human" } | { kind: "agent"; agentId: string }; export interface AgentTicketState { ticketId: string; conversationId: string; position: number; status: TicketWorkStatus; source: TicketWorkSource; requesterLabel: string; taskPreview: string; taskLen: number; } export interface AgentWorkState { agentId: string; name: string; profileId: string; live?: LiveWorkSession; busy: WorkBusyState; tickets: AgentTicketState[]; } ``` `WorkStateGateway` ne change pas : ```ts getProjectWorkState(projectId: string): Promise; ``` Adapter Tauri inchangé sauf types. Mock : `MockWorkStateGateway` accepte désormais `tickets` dans ses seeds. Pour compat tests, soit mettre `tickets: []` dans les fixtures, soit normaliser dans le mock au retour. ## UI Lot B Fichier principal : `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`. Afficher sous chaque agent : - badge `Live/Offline`, `Busy/Idle` existant ; - si `tickets.length > 0`, une mini-liste compacte : - `#1 In progress` ou `#2 Queued`, - requester : `Human` ou label requester / source agent, - preview du task, - ticket court en monospace. UX minimale : - Agent sans ticket : rien ou texte discret “No queued tickets”. - Ticket `inProgress` : badge warning, cohérent avec `Busy`. - Ticket `queued` : badge neutral. - Pas d’action de résolution/annulation dans Lot B. Refresh : - Garder refresh manuel. - Garder refresh sur événements Lot A : `agentBusyChanged`, `agentLaunched`, `agentExited`, `orchestratorRequestProcessed`. - Ajouter refresh sur `delegationReady`, car c’est le signal existant le plus proche d’un ticket qui démarre physiquement côté UI. Limite connue du Lot B : un ticket ajouté derrière un agent déjà busy peut ne pas émettre de nouvel `agentBusyChanged` ni `delegationReady` immédiatement. Il sera visible au refresh manuel ou au prochain événement. Une vraie notification `agentQueueChanged` est un Lot B+ ou Lot C, pas indispensable pour le snapshot read-only. ## Inclus - Port read-only `AgentQueueSnapshot`. - Impl snapshot de `InMemoryMailbox`, sans mutation. - Extension de `ProjectWorkState` avec `agents[].tickets`. - Statuts `inProgress/queued` dérivés, pas stockés. - Task preview bornée, pas full prompt. - UI compacte des tickets par agent. - Tests de snapshot FIFO et read-model. ## Exclus - Pas de `agentQueueChanged` dans ce lot, sauf si l’équipe juge indispensable pour UX live immédiate. - Pas d’annulation de ticket depuis UI. - Pas de résolution manuelle depuis UI. - Pas de persistance queue dans `.ideai/`. - Pas de lecture `ConversationLog` / `handoff.md`. - Pas d’historique des tickets terminés. - Pas de statut `delivered` : `delegation_delivered` est aujourd’hui un log/ack best-effort, pas une donnée persistée dans la mailbox. Ne pas l’exposer comme état tant qu’il n’a pas de source de vérité. - Pas de refonte des chemins `ask_agent`, `submit_human_input`, `idea_reply`. ## Point de vigilance technique Le snapshot va rendre visibles les tickets réellement restés dans `InMemoryMailbox`. C’est précisément utile, mais cela peut révéler des tickets qui ne sont pas drainés sur certains chemins de succès. À vérifier par tests avant de conclure le lot : - succès `idea_reply` PTY : ticket retiré ; - timeout/cancel : ticket retiré par `BusyTurnGuard` ; - succès structured `Final` : le ticket ne doit pas rester dans la queue ; - submit humain fire-and-forget : décider s’il doit apparaître comme ticket ou être exclu du snapshot. Pour le MVP UX “délégations”, je recommande d’afficher uniquement les tickets dont `source == Agent`. Si l’équipe veut afficher aussi les entrées humaines, le libellé UI doit parler de “tickets d’entrée”, pas seulement de délégations, et il faut clarifier leur drainage. ## Tests attendus ### Domain / infrastructure mailbox Fichiers : - `crates/domain/src/mailbox.rs` - `crates/infrastructure/src/mailbox/mod.rs` Tests : 1. `snapshot_preserves_fifo_order_and_positions` : deux tickets A→B, positions `0`, `1`. 2. `snapshot_carries_ticket_metadata` : source, conversation, requester, task présents. 3. `snapshot_is_read_only` : appeler snapshot ne change pas `pending` ni `head_ticket`. 4. `snapshot_updates_after_resolve_ticket` : le ticket résolu disparaît. 5. `snapshot_updates_after_cancel_head` : le head annulé disparaît, le suivant devient position `0`. ### Application workstate Fichier : `crates/application/tests/workstate.rs`. Tests : 1. Agent sans queue → `tickets == []`. 2. Deux tickets pour un agent manifeste → `tickets` dans l’ordre FIFO. 3. `busy.ticket == first` → premier ticket `InProgress`, second `Queued`. 4. `busy == Idle` → tous tickets `Queued`. 5. Queue pour agent absent du manifeste → ignorée. 6. `task_preview` tronque et `task_len` conserve la longueur originale. 7. Source agent → DTO application contient `TicketWorkSource::Agent { agent_id }`; source human → `Human` si inclus. ### App-tauri DTO / command Fichiers : - `crates/app-tauri/src/dto.rs` - tests existants type `dto_agents.rs` ou nouveau test DTO. Tests : 1. Sérialisation camelCase : `ticketId`, `conversationId`, `requesterLabel`, `taskPreview`, `taskLen`. 2. Enum status sérialisée en `inProgress` / `queued`. 3. Source agent sérialisée en `{ "kind": "agent", "agentId": "..." }`. 4. `get_project_work_state` renvoie `tickets` imbriqués avec les agents. ### Frontend Fichier : `frontend/src/features/workstate/workstate.test.tsx`. Tests : 1. Agent avec ticket `inProgress` affiche badge `In progress`, preview et ticket court. 2. Agent avec deux tickets affiche l’ordre `#1`, `#2`. 3. Agent sans tickets n’affiche pas de liste parasite. 4. Event `delegationReady` déclenche un refresh. 5. Mock gateway accepte et renvoie `tickets`. Commandes de vérification : ```bash cargo test -p infrastructure mailbox --lib cargo test -p application --test workstate cargo test -p app-tauri --test dto_agents npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx npx tsc --noEmit cargo check -p app-tauri ``` ## Résumé d’implémentation Le Lot B est une extension de projection : `InMemoryMailbox` expose une vue snapshot, `GetProjectWorkState` l’agrège par agent manifeste, Tauri sérialise `tickets`, le panneau Work les affiche. Aucun nouveau stockage, aucune nouvelle queue, aucune mutation UX. - **Prompt:** Après Lot A (ProjectWorkState live/busy) et Lot B (tickets FIFO par agent) mergés dans `develop` @ `64c2c14`, l'utilisateur demande d'enchaîner sur tout ce qu'il reste. Cadre le prochain lot prioritaire du chantier UX conversations/délégations. Objectif pressenti: Lot C — conversation summaries depuis log/handoff en lecture best-effort, pour que le panneau Work donne une vue lisible des threads sans charger tout le journal. Merci de définir précisément: - objectif produit et inclus/exclus, - source de vérité backend (ConversationLog/HandoffStore/ConversationRegistry ou autre), - frontières hexagonales: domaine/application/infra/Tauri/frontend, - DTOs exacts et évolution de `ProjectWorkState` ou nouvelle commande si préférable, - stratégie preview/handoff (bornes, erreurs best-effort, pas de mutation), - impacts frontend et UI minimale, - tests attendus et commandes. Contraintes: - Pas de nouvelle persistance durable si les logs/handoff existent déjà. - Ne pas exposer tout `log.jsonl`; previews/summaries seulement. - Ne pas bloquer l’état live si un log est corrompu/absent; best-effort. - Respecter la suite du chantier UX et éviter de mélanger avec actions Lot D ou mémoire automatique Lot E. - Ne code pas. -- **Response:** ## Lot C — Conversation summaries dans le panneau Work ### Objectif produit Objectif : enrichir le panneau `Work` avec une vue lisible des fils de conversation associés aux tickets/live work, sans exposer ni charger tout `log.jsonl`. Inclus : - afficher, pour chaque `conversationId` visible dans le work-state, un résumé compact issu du `handoff.md` quand il existe ; - compléter par un aperçu borné des derniers tours du `ConversationLog` quand le handoff est absent ou vide ; - signaler sobrement l’état de preview : `ready`, `missing`, `partial`, `unavailable` ; - rester strictement read-only et best-effort : aucune écriture, aucun recalcul de handoff, aucun blocage de `live/busy/tickets` si un fichier est absent/corrompu. Exclus : - pas d’action utilisateur sur les conversations ; - pas de lecture complète affichée du journal ; - pas de mutation/réparation des logs/handoff ; - pas de génération LLM de résumé ; - pas de mémoire automatique Lot E ; - pas de changement de FIFO, reprise, cancel, requeue ou actions Lot D. ### Source de vérité backend Source durable primaire : `HandoffStore` (`.ideai/conversations//handoff.md`). Source durable secondaire : `ConversationLog` (`.ideai/conversations//log.jsonl`) uniquement via une lecture bornée (`last(n)`), pour construire une preview courte quand le handoff est absent ou inutilisable. `ConversationRegistry` ne doit pas être la source de vérité du Lot C : il est live/in-memory, utile pour résoudre ou suivre des paires pendant l’orchestration, mais le panneau Work doit refléter les conversations déjà matérialisées par les tickets et leur persistance durable. Source des conversations à inspecter : les `conversation_id` déjà présents dans les `QueuedTicketSnapshot` projetés par `GetProjectWorkState`. En option additive, si le snapshot live expose déjà un `conversation_id` stable ailleurs, on peut l’ajouter plus tard, mais Lot C doit se limiter aux conversations visibles par les tickets pour éviter d’ouvrir un chantier layout/session. ### Frontières hexagonales Domaine : - réutiliser les ports existants `ConversationLog` et `HandoffStore` ; - ajouter seulement des value objects purs de read-model si nécessaire : `ConversationWorkSummary`, `ConversationPreviewStatus`, éventuellement `ConversationTurnPreview` ; - aucun accès FS, aucun Tauri, aucun type UI. Application : - étendre `GetProjectWorkState` avec un collaborateur de lecture de previews, ou extraire un petit service applicatif `ConversationPreviewReader` utilisé par `GetProjectWorkState` ; - dépendances injectées : `Arc` et `Arc`, ou `Option>` si on veut rendre l’enrichissement désactivable dans les tests legacy ; - le use case continue à renvoyer le work-state même si toutes les previews échouent. Infrastructure : - aucune nouvelle persistance ; - réutiliser `FsHandoffStore` et `FsConversationLog` déjà câblés sous `/.ideai/conversations/` ; - si `FsHandoffStore::load` renvoie une erreur de parsing, l’application la convertit en preview `unavailable/partial`, pas en `AppError` global ; - `FsConversationLog::last` est déjà tolérant aux lignes corrompues, donc c’est le bon fallback. Tauri : - garder la commande existante `get_project_work_state` ; - faire évoluer `ProjectWorkStateDto` additivement ; - ne pas créer `get_conversation_log` ni endpoint brut. Frontend : - types TS miroir dans le domaine UI ; - adapter Tauri inchangé côté méthode (`getProjectWorkState`) ; - mock gateway normalise `conversations: []` pour compat tests ; - composant `ProjectWorkStatePanel` mappe `conversationId -> summary` et affiche une ligne compacte par ticket/thread. ### DTO recommandé Préférence : étendre `ProjectWorkState` avec une liste top-level de résumés, référencée par `conversationId`. Ça évite de dupliquer le résumé sur plusieurs tickets du même fil. Rust application : ```rust pub struct ProjectWorkState { pub agents: Vec, pub conversations: Vec, } pub struct ConversationWorkSummary { pub conversation_id: ConversationId, pub status: ConversationPreviewStatus, pub objective_preview: Option, pub summary_preview: Option, pub summary_len: usize, pub up_to: Option, pub recent_turns: Vec, } pub enum ConversationPreviewStatus { Ready, Missing, Partial, Unavailable, } pub struct ConversationTurnWorkPreview { pub role: TurnRole, pub source: TicketWorkSource, pub at_ms: u64, pub text_preview: String, pub text_len: usize, } ``` Wire DTO camelCase : ```ts export interface ProjectWorkState { agents: AgentWorkState[]; conversations: ConversationWorkSummary[]; } export type ConversationPreviewStatus = | "ready" | "missing" | "partial" | "unavailable"; export interface ConversationWorkSummary { conversationId: string; status: ConversationPreviewStatus; objectivePreview?: string; summaryPreview?: string; summaryLen: number; upTo?: string; recentTurns: ConversationTurnWorkPreview[]; } export interface ConversationTurnWorkPreview { role: "prompt" | "response" | "toolActivity"; source: TicketWorkSource; atMs: number; textPreview: string; textLen: number; } ``` `AgentTicketState` ne change pas : il porte déjà `conversationId`, `taskPreview`, `taskLen`, `status`, `requesterLabel`. Le frontend joint via `conversationId`. Compatibilité : les mocks et tests peuvent normaliser `conversations` à `[]` quand absent, comme ils le font déjà pour `tickets`. ### Stratégie preview/handoff Bornes proposées : - `HANDOFF_PREVIEW_MAX_CHARS = 480` ; - `OBJECTIVE_PREVIEW_MAX_CHARS = 160` ; - `RECENT_TURNS_MAX = 3` ; - `TURN_PREVIEW_MAX_CHARS = 220`. Algorithme par `conversationId` unique : 1. tenter `handoffs.load(conversation)` ; 2. si `Ok(Some(handoff))` : - `status = ready` ; - `summaryPreview = normalize_truncate(handoff.summary_md, 480)` ; - `summaryLen = handoff.summary_md.chars().count()` ; - `objectivePreview = handoff.objective.map(truncate 160)` ; - `upTo = Some(handoff.up_to)` ; 3. si `Ok(None)` : `status = missing`, puis tenter `log.last(conversation, 3)` ; 4. si handoff illisible mais `log.last` marche : `status = partial`, `summaryPreview = None`, `recentTurns = ...` ; 5. si handoff et log échouent : `status = unavailable`, champs preview vides ; 6. dans tous les cas, ne jamais propager l’erreur au `ProjectWorkState` global. Normalisation : même règle que `task_preview` Lot B : trim + collapse whitespace + troncature à la borne, jamais mid-codepoint. On peut factoriser un helper applicatif privé dans `workstate`. Important : ne pas appeler `ConversationLog::read(None)` dans Lot C. Le fallback doit être `last(RECENT_TURNS_MAX)` uniquement. ### UI minimale Dans `ProjectWorkStatePanel` : - garder la structure actuelle agent > tickets ; - sous chaque ticket, afficher une ligne thread compacte si un résumé existe : - objectif si présent : `Goal: ` ; - sinon résumé : `` ; - sinon dernier tour : `Last: ` ; - badge discret selon statut : `Summary`, `No summary`, `Partial`, `Unavailable` ; - ne pas ajouter de modal ni bouton “ouvrir log” ; - ne pas afficher les fichiers, chemins, ni JSON brut ; - si `conversationId` n’a pas de summary dans la map, ne pas casser la ligne ticket : afficher seulement l’état Lot B existant. Le panneau doit rester scannable : pas de cartes imbriquées, pas de gros bloc Markdown rendu. Texte simple, clamp/line wrap raisonnable. ### Tests attendus Application (`crates/application/tests/workstate.rs`) : - conserve agents/live/busy/tickets même si le preview reader échoue ; - déduplique les `conversationId` de plusieurs tickets ; - handoff présent produit `ready`, `objectivePreview`, `summaryPreview`, `summaryLen`, `upTo` ; - handoff absent + log présent produit `missing` avec `recentTurns` borné ; - handoff corrompu + log lisible produit `partial` ; - handoff/log indisponibles produit `unavailable` sans `AppError` global ; - previews tronquées et whitespace normalisé. Infrastructure (`crates/infrastructure/tests/conversation_log.rs`) : - normalement peu ou pas de nouveau test requis si `FsConversationLog::last` et `FsHandoffStore::load` sont déjà couverts ; - ajouter seulement un test si un helper d’adapter dédié apparaît, mais éviter d’en créer un. Tauri DTO (`crates/app-tauri/tests/dto_agents.rs`) : - sérialisation camelCase de `conversations` ; - conversion des statuts `ready/missing/partial/unavailable` ; - compat additive avec agents/tickets existants. Frontend (`frontend/src/features/workstate/workstate.test.tsx`) : - mock sans `conversations` normalisé à `[]` ; - ticket avec `conversationId` affiche objectif/résumé ; - status `partial`/`unavailable` ne masque pas le ticket ; - refresh existant sur `agentBusyChanged` / `delegationReady` continue. Commandes : ```bash cargo test -p application workstate cargo test -p app-tauri dto_agents cargo test -p infrastructure conversation_log npm --prefix frontend test -- workstate ``` Selon l’organisation actuelle du workspace, finir par : ```bash cargo test npm --prefix frontend test ``` ### Décision de lot Lot C doit être un enrichissement read-only de `ProjectWorkState`, pas une nouvelle surface d’action. Le backend reste source d’autorité pour les bornes et la robustesse ; le frontend ne fait que joindre `tickets[].conversationId` vers `state.conversations[]` et rendre une preview compacte. Ça respecte les contraintes : aucune nouvelle persistance, aucun log brut exposé, best-effort par conversation, et pas de mélange avec les futures actions de gestion ou la mémoire automatique. \ No newline at end of file +- **Response:** ## Lot C — Conversation summaries dans le panneau Work ### Objectif produit Objectif : enrichir le panneau `Work` avec une vue lisible des fils de conversation associés aux tickets/live work, sans exposer ni charger tout `log.jsonl`. Inclus : - afficher, pour chaque `conversationId` visible dans le work-state, un résumé compact issu du `handoff.md` quand il existe ; - compléter par un aperçu borné des derniers tours du `ConversationLog` quand le handoff est absent ou vide ; - signaler sobrement l’état de preview : `ready`, `missing`, `partial`, `unavailable` ; - rester strictement read-only et best-effort : aucune écriture, aucun recalcul de handoff, aucun blocage de `live/busy/tickets` si un fichier est absent/corrompu. Exclus : - pas d’action utilisateur sur les conversations ; - pas de lecture complète affichée du journal ; - pas de mutation/réparation des logs/handoff ; - pas de génération LLM de résumé ; - pas de mémoire automatique Lot E ; - pas de changement de FIFO, reprise, cancel, requeue ou actions Lot D. ### Source de vérité backend Source durable primaire : `HandoffStore` (`.ideai/conversations//handoff.md`). Source durable secondaire : `ConversationLog` (`.ideai/conversations//log.jsonl`) uniquement via une lecture bornée (`last(n)`), pour construire une preview courte quand le handoff est absent ou inutilisable. `ConversationRegistry` ne doit pas être la source de vérité du Lot C : il est live/in-memory, utile pour résoudre ou suivre des paires pendant l’orchestration, mais le panneau Work doit refléter les conversations déjà matérialisées par les tickets et leur persistance durable. Source des conversations à inspecter : les `conversation_id` déjà présents dans les `QueuedTicketSnapshot` projetés par `GetProjectWorkState`. En option additive, si le snapshot live expose déjà un `conversation_id` stable ailleurs, on peut l’ajouter plus tard, mais Lot C doit se limiter aux conversations visibles par les tickets pour éviter d’ouvrir un chantier layout/session. ### Frontières hexagonales Domaine : - réutiliser les ports existants `ConversationLog` et `HandoffStore` ; - ajouter seulement des value objects purs de read-model si nécessaire : `ConversationWorkSummary`, `ConversationPreviewStatus`, éventuellement `ConversationTurnPreview` ; - aucun accès FS, aucun Tauri, aucun type UI. Application : - étendre `GetProjectWorkState` avec un collaborateur de lecture de previews, ou extraire un petit service applicatif `ConversationPreviewReader` utilisé par `GetProjectWorkState` ; - dépendances injectées : `Arc` et `Arc`, ou `Option>` si on veut rendre l’enrichissement désactivable dans les tests legacy ; - le use case continue à renvoyer le work-state même si toutes les previews échouent. Infrastructure : - aucune nouvelle persistance ; - réutiliser `FsHandoffStore` et `FsConversationLog` déjà câblés sous `/.ideai/conversations/` ; - si `FsHandoffStore::load` renvoie une erreur de parsing, l’application la convertit en preview `unavailable/partial`, pas en `AppError` global ; - `FsConversationLog::last` est déjà tolérant aux lignes corrompues, donc c’est le bon fallback. Tauri : - garder la commande existante `get_project_work_state` ; - faire évoluer `ProjectWorkStateDto` additivement ; - ne pas créer `get_conversation_log` ni endpoint brut. Frontend : - types TS miroir dans le domaine UI ; - adapter Tauri inchangé côté méthode (`getProjectWorkState`) ; - mock gateway normalise `conversations: []` pour compat tests ; - composant `ProjectWorkStatePanel` mappe `conversationId -> summary` et affiche une ligne compacte par ticket/thread. ### DTO recommandé Préférence : étendre `ProjectWorkState` avec une liste top-level de résumés, référencée par `conversationId`. Ça évite de dupliquer le résumé sur plusieurs tickets du même fil. Rust application : ```rust pub struct ProjectWorkState { pub agents: Vec, pub conversations: Vec, } pub struct ConversationWorkSummary { pub conversation_id: ConversationId, pub status: ConversationPreviewStatus, pub objective_preview: Option, pub summary_preview: Option, pub summary_len: usize, pub up_to: Option, pub recent_turns: Vec, } pub enum ConversationPreviewStatus { Ready, Missing, Partial, Unavailable, } pub struct ConversationTurnWorkPreview { pub role: TurnRole, pub source: TicketWorkSource, pub at_ms: u64, pub text_preview: String, pub text_len: usize, } ``` Wire DTO camelCase : ```ts export interface ProjectWorkState { agents: AgentWorkState[]; conversations: ConversationWorkSummary[]; } export type ConversationPreviewStatus = | "ready" | "missing" | "partial" | "unavailable"; export interface ConversationWorkSummary { conversationId: string; status: ConversationPreviewStatus; objectivePreview?: string; summaryPreview?: string; summaryLen: number; upTo?: string; recentTurns: ConversationTurnWorkPreview[]; } export interface ConversationTurnWorkPreview { role: "prompt" | "response" | "toolActivity"; source: TicketWorkSource; atMs: number; textPreview: string; textLen: number; } ``` `AgentTicketState` ne change pas : il porte déjà `conversationId`, `taskPreview`, `taskLen`, `status`, `requesterLabel`. Le frontend joint via `conversationId`. Compatibilité : les mocks et tests peuvent normaliser `conversations` à `[]` quand absent, comme ils le font déjà pour `tickets`. ### Stratégie preview/handoff Bornes proposées : - `HANDOFF_PREVIEW_MAX_CHARS = 480` ; - `OBJECTIVE_PREVIEW_MAX_CHARS = 160` ; - `RECENT_TURNS_MAX = 3` ; - `TURN_PREVIEW_MAX_CHARS = 220`. Algorithme par `conversationId` unique : 1. tenter `handoffs.load(conversation)` ; 2. si `Ok(Some(handoff))` : - `status = ready` ; - `summaryPreview = normalize_truncate(handoff.summary_md, 480)` ; - `summaryLen = handoff.summary_md.chars().count()` ; - `objectivePreview = handoff.objective.map(truncate 160)` ; - `upTo = Some(handoff.up_to)` ; 3. si `Ok(None)` : `status = missing`, puis tenter `log.last(conversation, 3)` ; 4. si handoff illisible mais `log.last` marche : `status = partial`, `summaryPreview = None`, `recentTurns = ...` ; 5. si handoff et log échouent : `status = unavailable`, champs preview vides ; 6. dans tous les cas, ne jamais propager l’erreur au `ProjectWorkState` global. Normalisation : même règle que `task_preview` Lot B : trim + collapse whitespace + troncature à la borne, jamais mid-codepoint. On peut factoriser un helper applicatif privé dans `workstate`. Important : ne pas appeler `ConversationLog::read(None)` dans Lot C. Le fallback doit être `last(RECENT_TURNS_MAX)` uniquement. ### UI minimale Dans `ProjectWorkStatePanel` : - garder la structure actuelle agent > tickets ; - sous chaque ticket, afficher une ligne thread compacte si un résumé existe : - objectif si présent : `Goal: ` ; - sinon résumé : `` ; - sinon dernier tour : `Last: ` ; - badge discret selon statut : `Summary`, `No summary`, `Partial`, `Unavailable` ; - ne pas ajouter de modal ni bouton “ouvrir log” ; - ne pas afficher les fichiers, chemins, ni JSON brut ; - si `conversationId` n’a pas de summary dans la map, ne pas casser la ligne ticket : afficher seulement l’état Lot B existant. Le panneau doit rester scannable : pas de cartes imbriquées, pas de gros bloc Markdown rendu. Texte simple, clamp/line wrap raisonnable. ### Tests attendus Application (`crates/application/tests/workstate.rs`) : - conserve agents/live/busy/tickets même si le preview reader échoue ; - déduplique les `conversationId` de plusieurs tickets ; - handoff présent produit `ready`, `objectivePreview`, `summaryPreview`, `summaryLen`, `upTo` ; - handoff absent + log présent produit `missing` avec `recentTurns` borné ; - handoff corrompu + log lisible produit `partial` ; - handoff/log indisponibles produit `unavailable` sans `AppError` global ; - previews tronquées et whitespace normalisé. Infrastructure (`crates/infrastructure/tests/conversation_log.rs`) : - normalement peu ou pas de nouveau test requis si `FsConversationLog::last` et `FsHandoffStore::load` sont déjà couverts ; - ajouter seulement un test si un helper d’adapter dédié apparaît, mais éviter d’en créer un. Tauri DTO (`crates/app-tauri/tests/dto_agents.rs`) : - sérialisation camelCase de `conversations` ; - conversion des statuts `ready/missing/partial/unavailable` ; - compat additive avec agents/tickets existants. Frontend (`frontend/src/features/workstate/workstate.test.tsx`) : - mock sans `conversations` normalisé à `[]` ; - ticket avec `conversationId` affiche objectif/résumé ; - status `partial`/`unavailable` ne masque pas le ticket ; - refresh existant sur `agentBusyChanged` / `delegationReady` continue. Commandes : ```bash cargo test -p application workstate cargo test -p app-tauri dto_agents cargo test -p infrastructure conversation_log npm --prefix frontend test -- workstate ``` Selon l’organisation actuelle du workspace, finir par : ```bash cargo test npm --prefix frontend test ``` ### Décision de lot Lot C doit être un enrichissement read-only de `ProjectWorkState`, pas une nouvelle surface d’action. Le backend reste source d’autorité pour les bornes et la robustesse ; le frontend ne fait que joindre `tickets[].conversationId` vers `state.conversations[]` et rendre une preview compacte. Ça respecte les contraintes : aucune nouvelle persistance, aucun log brut exposé, best-effort par conversation, et pas de mélange avec les futures actions de gestion ou la mémoire automatique. +- **Prompt:** Après Lots A/B/C `workstate` mergés dans `develop` @ `6e1ba7e`, cadre le prochain lot prioritaire. L'utilisateur demande d'enchaîner sur tout ce qu'il reste. Contexte livré: - Lot A: panneau/read-model live/busy par agent. - Lot B: tickets FIFO par agent, source human/agent, statuts inProgress/queued. - Lot C: summaries de conversations best-effort top-level, jointes via conversationId. Objectif pressenti: Lot D — actions UX contrôlées depuis Work: ouvrir/rattacher l'agent/la cellule, voir la conversation, arrêter un agent, copier résumé. Merci de choisir le périmètre minimal prioritaire et de cadrer précisément. Merci de définir: - objectif produit et inclus/exclus, - actions à livrer dans ce lot, avec priorité, - ports/use cases backend existants à réutiliser ou nouveaux contrats nécessaires, - frontières Tauri/frontend, - contraintes de sécurité/permissions et cohérence avec single-agent/live-session, - impacts UI dans le panneau Work, - tests attendus et commandes. Contraintes: - Ne pas mélanger avec mémoire automatique Lot E. - Ne pas inventer une nouvelle session si une action doit seulement focaliser/rattacher l'existant. - Ne pas casser l'historique conversationnel ni exposer log brut. - Ne code pas. +- **Response:** ## Lot D — actions contrôlées depuis Work ### Objectif produit Rendre le panneau `Work` opérable sans changer sa nature : il reste un tableau de bord live/conversations, mais l’utilisateur peut agir sur l’état déjà visible. Périmètre minimal prioritaire : - retrouver ou afficher la cellule d’un agent live ; - rattacher une session déjà live à une cellule existante et déterminée ; - arrêter explicitement un agent live ; - voir davantage de la preview conversationnelle Lot C ; - copier le résumé affiché. Exclus : - pas de lancement d’un agent neuf depuis Work ; - pas de création automatique de cellule/layout ; - pas de cancel/requeue/priorisation FIFO ; - pas de modification de handoff, mémoire automatique ou Lot E ; - pas de lecture/exposition brute de `log.jsonl` ; - pas de transcript complet. ### Actions à livrer Priorité 1 — `Open` / `Go to cell` - Si `agent.live.nodeId` correspond à une cellule visible dans le layout actif : focus/scroll/highlight de cette cellule. - Aucun appel backend. Aucun spawn. - Libellé UI : `Open` ou `Go to cell` selon la place. Priorité 2 — `Attach` - Si l’agent est live mais sa cellule hôte n’est plus visible, rattacher la session à une cellule existante et non ambiguë. - Cible autorisée pour ce lot : une cellule visible déjà pinnée sur le même agent et sans session, ou une cellule explicitement sélectionnée par l’utilisateur dans le layout. - Si aucune cible déterministe : action désactivée avec message court du type `Pin this agent to a cell first`. - Ne jamais créer une nouvelle session. Ne jamais appeler `launch_agent`. Priorité 3 — `Stop` - Arrête la session live de l’agent après confirmation. - Fonctionne pour PTY et structured. - Ne supprime pas l’agent, ne supprime pas le handoff, ne vide pas `conversationId`. - Après succès : refresh Work + layout/live state via events existants ou refresh explicite. Priorité 4 — `View conversation` - Expansion inline dans Work sur la base de `ProjectWorkState.conversations[]`. - Montre objectif, summary preview, et les `recentTurns` déjà bornés par Lot C. - Pas de nouveau endpoint de transcript. Priorité 5 — `Copy summary` - Frontend-only via clipboard. - Copie uniquement le texte déjà exposé par Lot C : objectif + summary preview + recent turns bornés si summary absent. - Désactivé si aucune preview exploitable. ### Backend Réutiliser : - `GetProjectWorkState` : source unique du panneau, inchangé fonctionnellement ; - `LiveSessions` / `TerminalSessions` / `StructuredSessions` : source de vérité live ; - `LayoutGateway.mutateLayout` avec `setSession` / `setCellAgent` ; - `close_terminal` et `close_agent_session` comme primitives existantes ; - `attach_live_agent` existant, mais à durcir. Contrats backend recommandés : 1. Faire évoluer `attach_live_agent` en vrai use case applicatif `AttachLiveAgent`. Entrée : ```rust pub struct AttachLiveAgentInput { pub project: Project, pub agent_id: AgentId, pub node_id: NodeId, } ``` Sortie : ```rust pub struct AttachLiveAgentOutput { pub agent_id: AgentId, pub node_id: NodeId, pub session_id: SessionId, pub kind: LiveSessionKind, } ``` Comportement : - chercher la session live dans `TerminalSessions`, sinon `StructuredSessions` ; - rebind `node_id` sans spawn ; - retourner `NOT_FOUND` si l’agent n’est plus live ; - idempotent si déjà attaché à cette cellule ; - ne touche pas `ConversationLog`, `HandoffStore`, `ProviderSessionStore`. 2. Ajouter `StopLiveAgent` plutôt que faire deviner le registre au frontend. Entrée : ```rust pub struct StopLiveAgentInput { pub project: Project, pub agent_id: AgentId, } ``` Sortie : ```rust pub struct StopLiveAgentOutput { pub agent_id: AgentId, pub session_id: SessionId, pub kind: LiveSessionKind, } ``` Comportement : - résoudre la session live par agent ; - si PTY : déléguer à `CloseTerminal` / `PtyPort.kill` ; - si structured : `StructuredSessions::remove` puis `AgentSession::shutdown()` ; - publier `DomainEvent::AgentExited { agent_id, code }` ou l’équivalent déjà relayé ; - no-op contrôlé ou `NOT_FOUND` si déjà arrêté. Préférence : `NOT_FOUND` côté API, frontend l’interprète comme refresh. Pourquoi pas seulement `close_terminal(sessionId)` : `WorkState.live.kind` existe, et le modèle a deux registries. Une commande par agent garde la cohérence single-agent côté backend. ### Tauri / DTO Nouvelles/évolutions : ```ts attach_live_agent({ request: { projectId, agentId, nodeId } }) -> { agentId, nodeId, sessionId, kind } stop_live_agent({ request: { projectId, agentId } }) -> { agentId, sessionId, kind } ``` `LiveAgentDto` peut être réutilisé si on lui ajoute/garantit `kind`; sinon créer un DTO dédié. Préférence : enrichir `LiveAgent` additivement avec `kind: "pty" | "structured"`, cohérent avec `LiveWorkSession.kind`. Pas de commande `get_conversation_log`, `open_conversation`, `copy_summary`. ### Frontend Ports UI : - `AgentGateway.attachLiveAgent(projectId, agentId, nodeId): Promise` doit retourner `kind` ; - ajouter `AgentGateway.stopLiveAgent(projectId, agentId): Promise` ; - ne pas faire appeler `close_terminal` directement par `ProjectWorkStatePanel` pour stopper un agent ; - `LayoutGateway.loadLayout/mutateLayout` réutilisés pour trouver/attacher la cellule cible. Coordination UI recommandée : - extraire le helper de focus cellule (`goToCell`) de `LayoutGrid` vers `features/layout/focusCell.ts` ; - ajouter un petit hook `useWorkActions(projectId, activeLayoutId?)` consommé par `ProjectWorkStatePanel` ; - le hook sait charger le layout actif, trouver les feuilles, vérifier si `live.nodeId` est visible, et appeler `attachLiveAgent + mutateLayout(setCellAgent/setSession)` si une cible déterministe existe. ### Sécurité / cohérence Règles obligatoires : - `Open` et `Attach` ne spawnent jamais. Interdit d’appeler `launchAgent` depuis Work dans Lot D. - `Attach` ne doit pas écraser une cellule qui héberge déjà une session différente, sauf remplacement explicite futur hors lot. - `Stop` demande confirmation ; c’est une action destructive sur le process, mais pas sur les fichiers. - `Stop` ne supprime ni agent, ni tickets, ni conversation summary, ni handoff. - `Stop` doit libérer la live registry pour préserver l’invariant “un agent = une session live max”. - Si un ticket est `inProgress`, afficher dans la confirmation que le tour en cours sera interrompu ; ne pas tenter de résoudre le ticket dans ce lot. - Pas de permission filesystem nouvelle. Les seules mutations durables sont les mutations layout déjà existantes (`setSession`/`setCellAgent`) lors d’un attach. ### UI minimale Dans chaque ligne agent : - bouton `Open` si live ; - bouton `Attach` si live mais cellule non visible et une cible existe ; - bouton `Stop` si live ; - état désactivé explicite si action impossible. Dans chaque ticket/summary : - bouton disclosure `View` pour ouvrir/fermer les détails Lot C ; - bouton `Copy` à côté du summary, pas sur toute la ligne ; - garder l’UI dense : pas de drawer global, pas de modal conversation, pas de rendu Markdown lourd. Feedback : - erreurs action en inline dans le panneau ; - après `Stop`/`Attach`, refresh `getProjectWorkState` et layout actif ; - `Copy` affiche un état bref `Copied`. ### Tests attendus Application Rust : - `AttachLiveAgent` rebind PTY sans changer `session_id` ; - `AttachLiveAgent` rebind structured sans changer `session_id` ; - attach agent absent => `NOT_FOUND` ; - attach même node => idempotent ; - `StopLiveAgent` arrête PTY et retire la session live ; - `StopLiveAgent` arrête structured via `shutdown()` et retire la session live ; - stop absent => `NOT_FOUND` ; - stop ne touche pas handoff/log/provider sessions. Tauri DTO/tests : - request camelCase pour `attach_live_agent` et `stop_live_agent` ; - response sérialise `kind`; - erreurs `INVALID` ids et `NOT_FOUND` live session. Frontend tests : - `Open` focus une cellule visible et n’appelle aucun launch ; - `Attach` appelle `attachLiveAgent` puis `mutateLayout(setSession)` sur une cible déterministe ; - `Attach` désactivé sans cible ; - `Stop` demande confirmation puis appelle `stopLiveAgent`, refresh Work ; - `Stop` sur agent busy affiche l’avertissement ; - `View conversation` n’affiche que les champs Lot C ; - `Copy summary` copie uniquement la preview exposée, pas de log brut ; - pas de régression des refresh events Lot A/B/C. Commandes : ```bash cargo test -p application terminal_usecases structured_registry workstate cargo test -p app-tauri dto_agents list_live_agents_r0b npm --prefix frontend test -- workstate npm --prefix frontend test -- LayoutGrid singletonAgent agentAlreadyRunning ``` Puis en validation large : ```bash cargo test npm --prefix frontend test ``` ### Décision de cadrage Lot D doit être “actions sur état existant”, pas “nouveau centre de contrôle”. Le seul ajout backend vraiment justifié est de faire porter `attach/stop` par des contrats agent-level qui respectent PTY + structured et l’invariant single-agent. La conversation reste une preview bornée issue de Lot C ; le log brut et la mémoire automatique restent hors périmètre. \ No newline at end of file diff --git a/.ideai/conversations/7a2f446c-2efa-0c56-08dd-07cdc5cac19b/log.jsonl b/.ideai/conversations/7a2f446c-2efa-0c56-08dd-07cdc5cac19b/log.jsonl index e50659b..1816d15 100644 --- a/.ideai/conversations/7a2f446c-2efa-0c56-08dd-07cdc5cac19b/log.jsonl +++ b/.ideai/conversations/7a2f446c-2efa-0c56-08dd-07cdc5cac19b/log.jsonl @@ -39,3 +39,5 @@ {"id":"001f112c-4ece-4f49-a7cc-140ee76eabec","conversation":"7a2f446c-2efa-0c56-08dd-07cdc5cac19b","atMs":1781971838745,"source":{"kind":"agent","agentId":"dce19c75-9669-4e45-b8de-9950025157da"},"role":"response","text":"## Lot B — Queue / Delegation Snapshot Read-Only\n\nObjectif : enrichir `ProjectWorkState` avec les tickets actuellement présents dans la FIFO par agent, en lecture seule, sans nouvelle persistance durable et sans changer la sémantique `enqueue/resolve/cancel`.\n\n### Source de vérité backend\n\nLa source de vérité des délégations/tickets est **la mailbox existante**, pas le frontend et pas un nouveau store :\n\n- `domain::mailbox::Ticket` porte déjà `id`, `source`, `conversation`, `requester`, `task`.\n- `infrastructure::InMemoryMailbox` possède déjà les `VecDeque` par `AgentId`.\n- `InputMediator::busy_state(agent)` reste la source de vérité du ticket **en cours**.\n\nDonc :\n\n- **queue/order** = snapshot de `InMemoryMailbox` ;\n- **status inProgress/queued** = dérivé en croisant la queue avec `InputMediator::busy_state` ;\n- **live session** = déjà Lot A via `LiveSessions` ;\n- **manifest boundary** = toujours `AgentContextStore.load_manifest(project)`.\n\nNe pas reconstruire une deuxième FIFO dans `ProjectWorkState`.\n\n## Extension read-only recommandée\n\nNe pas étendre `InputMediator` : il est l’autorité busy/livraison, pas l’autorité de contenu de queue.\n\nÉviter aussi de grossir le port mutateur `AgentMailbox` si possible. Ajouter un port ségrégué read-only dans `domain/src/mailbox.rs` :\n\n```rust\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct QueuedTicketSnapshot {\n pub id: TicketId,\n pub source: InputSource,\n pub conversation: ConversationId,\n pub requester: String,\n pub task: String,\n pub position: u32,\n}\n\npub trait AgentQueueSnapshot: Send + Sync {\n fn queue_for(&self, agent: AgentId) -> Vec;\n}\n```\n\nImplémenter `AgentQueueSnapshot` pour `InMemoryMailbox` en clonant uniquement les données de `Ticket`, jamais les `oneshot::Sender`.\n\nPourquoi ce choix :\n\n- ISP : le read-model ne dépend pas de `enqueue/resolve/cancel`.\n- Aucun changement de comportement FIFO.\n- Les tests peuvent fournir un fake `AgentQueueSnapshot` sans implémenter tout `AgentMailbox`.\n\nAlternative acceptable si l’équipe préfère moins de types : ajouter `fn snapshot_for(&self, agent: AgentId) -> Vec` directement à `AgentMailbox`, avec impl par défaut vide. Mais c’est moins propre : les ports mutateur et lecture restent mélangés.\n\n## Modèle application\n\nFichier principal : `crates/application/src/workstate/mod.rs`.\n\nÉtendre `GetProjectWorkState` :\n\n```rust\npub struct GetProjectWorkState {\n contexts: Arc,\n live: Arc,\n input: Arc,\n queue: Arc,\n}\n```\n\nÉtendre les structs :\n\n```rust\npub struct AgentWorkState {\n pub agent_id: AgentId,\n pub name: String,\n pub profile_id: ProfileId,\n pub live: Option,\n pub busy: AgentBusyState,\n pub tickets: Vec,\n}\n\npub struct AgentTicketState {\n pub ticket_id: TicketId,\n pub conversation_id: ConversationId,\n pub position: u32,\n pub status: TicketWorkStatus,\n pub source: TicketWorkSource,\n pub requester_label: String,\n pub task_preview: String,\n pub task_len: usize,\n}\n\npub enum TicketWorkStatus {\n InProgress,\n Queued,\n}\n\npub enum TicketWorkSource {\n Human,\n Agent { agent_id: AgentId },\n}\n```\n\nDérivation status :\n\n```rust\nlet busy_ticket = self.input.busy_state(agent.id).ticket();\nstatus = if busy_ticket == Some(ticket.id) {\n TicketWorkStatus::InProgress\n} else {\n TicketWorkStatus::Queued\n};\n```\n\n`task_preview` : générer côté application pour éviter d’envoyer tout le prompt au panneau. Recommandation : trim + remplacer whitespace runs par espace + couper à 160 caractères. Garder `task_len` pour indiquer qu’il y a plus.\n\nImportant : conserver l’ordre FIFO via `position`, ne pas retrier les tickets.\n\n## Tauri / DTO\n\nÉtendre `crates/app-tauri/src/dto.rs`.\n\n```rust\n#[derive(Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct AgentWorkStateDto {\n pub agent_id: String,\n pub name: String,\n pub profile_id: String,\n pub live: Option,\n pub busy: AgentBusyState,\n pub tickets: Vec,\n}\n\n#[derive(Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct AgentTicketStateDto {\n pub ticket_id: String,\n pub conversation_id: String,\n pub position: u32,\n pub status: TicketWorkStatusDto,\n pub source: TicketWorkSourceDto,\n pub requester_label: String,\n pub task_preview: String,\n pub task_len: usize,\n}\n\n#[derive(Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum TicketWorkStatusDto {\n InProgress,\n Queued,\n}\n\n#[derive(Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\", tag = \"kind\")]\npub enum TicketWorkSourceDto {\n Human,\n Agent { agent_id: String },\n}\n```\n\nWire JSON attendu :\n\n```json\n{\n \"agents\": [\n {\n \"agentId\": \"...\",\n \"name\": \"Architect\",\n \"profileId\": \"...\",\n \"live\": { \"nodeId\": \"...\", \"sessionId\": \"...\", \"kind\": \"pty\" },\n \"busy\": { \"state\": \"busy\", \"ticket\": \"...\", \"sinceMs\": 1234 },\n \"tickets\": [\n {\n \"ticketId\": \"...\",\n \"conversationId\": \"...\",\n \"position\": 0,\n \"status\": \"inProgress\",\n \"source\": { \"kind\": \"agent\", \"agentId\": \"...\" },\n \"requesterLabel\": \"Main\",\n \"taskPreview\": \"Analyser...\",\n \"taskLen\": 842\n }\n ]\n }\n ]\n}\n```\n\nLa commande Tauri reste la même :\n\n```rust\nget_project_work_state(project_id) -> ProjectWorkStateDto\n```\n\nAucune nouvelle commande nécessaire.\n\n## Wiring AppState\n\nDans `crates/app-tauri/src/state.rs` :\n\n- garder l’`Arc` déjà créé au composition root ;\n- le passer à `GetProjectWorkState::new(...)` comme `Arc` ;\n- continuer à passer le même `Arc` au `MediatedInbox` et à l’orchestrateur comme `AgentMailbox`.\n\nBut : un seul objet `InMemoryMailbox`, deux vues de port :\n\n```rust\nlet inmemory_mailbox = Arc::new(InMemoryMailbox::new());\nlet mailbox = Arc::clone(&inmemory_mailbox) as Arc;\nlet queue_snapshot = Arc::clone(&inmemory_mailbox) as Arc;\n```\n\nPuis :\n\n```rust\nGetProjectWorkState::new(contexts, live_sessions, input_mediator, queue_snapshot)\n```\n\n## Frontend contracts\n\nÉtendre `frontend/src/domain/index.ts` :\n\n```ts\nexport type TicketWorkStatus = \"inProgress\" | \"queued\";\n\nexport type TicketWorkSource =\n | { kind: \"human\" }\n | { kind: \"agent\"; agentId: string };\n\nexport interface AgentTicketState {\n ticketId: string;\n conversationId: string;\n position: number;\n status: TicketWorkStatus;\n source: TicketWorkSource;\n requesterLabel: string;\n taskPreview: string;\n taskLen: number;\n}\n\nexport interface AgentWorkState {\n agentId: string;\n name: string;\n profileId: string;\n live?: LiveWorkSession;\n busy: WorkBusyState;\n tickets: AgentTicketState[];\n}\n```\n\n`WorkStateGateway` ne change pas :\n\n```ts\ngetProjectWorkState(projectId: string): Promise;\n```\n\nAdapter Tauri inchangé sauf types.\n\nMock : `MockWorkStateGateway` accepte désormais `tickets` dans ses seeds. Pour compat tests, soit mettre `tickets: []` dans les fixtures, soit normaliser dans le mock au retour.\n\n## UI Lot B\n\nFichier principal : `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`.\n\nAfficher sous chaque agent :\n\n- badge `Live/Offline`, `Busy/Idle` existant ;\n- si `tickets.length > 0`, une mini-liste compacte :\n - `#1 In progress` ou `#2 Queued`,\n - requester : `Human` ou label requester / source agent,\n - preview du task,\n - ticket court en monospace.\n\nUX minimale :\n\n- Agent sans ticket : rien ou texte discret “No queued tickets”.\n- Ticket `inProgress` : badge warning, cohérent avec `Busy`.\n- Ticket `queued` : badge neutral.\n- Pas d’action de résolution/annulation dans Lot B.\n\nRefresh :\n\n- Garder refresh manuel.\n- Garder refresh sur événements Lot A : `agentBusyChanged`, `agentLaunched`, `agentExited`, `orchestratorRequestProcessed`.\n- Ajouter refresh sur `delegationReady`, car c’est le signal existant le plus proche d’un ticket qui démarre physiquement côté UI.\n\nLimite connue du Lot B : un ticket ajouté derrière un agent déjà busy peut ne pas émettre de nouvel `agentBusyChanged` ni `delegationReady` immédiatement. Il sera visible au refresh manuel ou au prochain événement. Une vraie notification `agentQueueChanged` est un Lot B+ ou Lot C, pas indispensable pour le snapshot read-only.\n\n## Inclus\n\n- Port read-only `AgentQueueSnapshot`.\n- Impl snapshot de `InMemoryMailbox`, sans mutation.\n- Extension de `ProjectWorkState` avec `agents[].tickets`.\n- Statuts `inProgress/queued` dérivés, pas stockés.\n- Task preview bornée, pas full prompt.\n- UI compacte des tickets par agent.\n- Tests de snapshot FIFO et read-model.\n\n## Exclus\n\n- Pas de `agentQueueChanged` dans ce lot, sauf si l’équipe juge indispensable pour UX live immédiate.\n- Pas d’annulation de ticket depuis UI.\n- Pas de résolution manuelle depuis UI.\n- Pas de persistance queue dans `.ideai/`.\n- Pas de lecture `ConversationLog` / `handoff.md`.\n- Pas d’historique des tickets terminés.\n- Pas de statut `delivered` : `delegation_delivered` est aujourd’hui un log/ack best-effort, pas une donnée persistée dans la mailbox. Ne pas l’exposer comme état tant qu’il n’a pas de source de vérité.\n- Pas de refonte des chemins `ask_agent`, `submit_human_input`, `idea_reply`.\n\n## Point de vigilance technique\n\nLe snapshot va rendre visibles les tickets réellement restés dans `InMemoryMailbox`. C’est précisément utile, mais cela peut révéler des tickets qui ne sont pas drainés sur certains chemins de succès.\n\nÀ vérifier par tests avant de conclure le lot :\n\n- succès `idea_reply` PTY : ticket retiré ;\n- timeout/cancel : ticket retiré par `BusyTurnGuard` ;\n- succès structured `Final` : le ticket ne doit pas rester dans la queue ;\n- submit humain fire-and-forget : décider s’il doit apparaître comme ticket ou être exclu du snapshot.\n\nPour le MVP UX “délégations”, je recommande d’afficher uniquement les tickets dont `source == Agent`. Si l’équipe veut afficher aussi les entrées humaines, le libellé UI doit parler de “tickets d’entrée”, pas seulement de délégations, et il faut clarifier leur drainage.\n\n## Tests attendus\n\n### Domain / infrastructure mailbox\n\nFichiers :\n\n- `crates/domain/src/mailbox.rs`\n- `crates/infrastructure/src/mailbox/mod.rs`\n\nTests :\n\n1. `snapshot_preserves_fifo_order_and_positions` : deux tickets A→B, positions `0`, `1`.\n2. `snapshot_carries_ticket_metadata` : source, conversation, requester, task présents.\n3. `snapshot_is_read_only` : appeler snapshot ne change pas `pending` ni `head_ticket`.\n4. `snapshot_updates_after_resolve_ticket` : le ticket résolu disparaît.\n5. `snapshot_updates_after_cancel_head` : le head annulé disparaît, le suivant devient position `0`.\n\n### Application workstate\n\nFichier : `crates/application/tests/workstate.rs`.\n\nTests :\n\n1. Agent sans queue → `tickets == []`.\n2. Deux tickets pour un agent manifeste → `tickets` dans l’ordre FIFO.\n3. `busy.ticket == first` → premier ticket `InProgress`, second `Queued`.\n4. `busy == Idle` → tous tickets `Queued`.\n5. Queue pour agent absent du manifeste → ignorée.\n6. `task_preview` tronque et `task_len` conserve la longueur originale.\n7. Source agent → DTO application contient `TicketWorkSource::Agent { agent_id }`; source human → `Human` si inclus.\n\n### App-tauri DTO / command\n\nFichiers :\n\n- `crates/app-tauri/src/dto.rs`\n- tests existants type `dto_agents.rs` ou nouveau test DTO.\n\nTests :\n\n1. Sérialisation camelCase : `ticketId`, `conversationId`, `requesterLabel`, `taskPreview`, `taskLen`.\n2. Enum status sérialisée en `inProgress` / `queued`.\n3. Source agent sérialisée en `{ \"kind\": \"agent\", \"agentId\": \"...\" }`.\n4. `get_project_work_state` renvoie `tickets` imbriqués avec les agents.\n\n### Frontend\n\nFichier : `frontend/src/features/workstate/workstate.test.tsx`.\n\nTests :\n\n1. Agent avec ticket `inProgress` affiche badge `In progress`, preview et ticket court.\n2. Agent avec deux tickets affiche l’ordre `#1`, `#2`.\n3. Agent sans tickets n’affiche pas de liste parasite.\n4. Event `delegationReady` déclenche un refresh.\n5. Mock gateway accepte et renvoie `tickets`.\n\nCommandes de vérification :\n\n```bash\ncargo test -p infrastructure mailbox --lib\ncargo test -p application --test workstate\ncargo test -p app-tauri --test dto_agents\nnpx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx\nnpx tsc --noEmit\ncargo check -p app-tauri\n```\n\n## Résumé d’implémentation\n\nLe Lot B est une extension de projection : `InMemoryMailbox` expose une vue snapshot, `GetProjectWorkState` l’agrège par agent manifeste, Tauri sérialise `tickets`, le panneau Work les affiche. Aucun nouveau stockage, aucune nouvelle queue, aucune mutation UX."} {"id":"8f8bddcd-1d3b-4739-a00f-6457469fa1a2","conversation":"7a2f446c-2efa-0c56-08dd-07cdc5cac19b","atMs":1781975163138,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Après Lot A (ProjectWorkState live/busy) et Lot B (tickets FIFO par agent) mergés dans `develop` @ `64c2c14`, l'utilisateur demande d'enchaîner sur tout ce qu'il reste. Cadre le prochain lot prioritaire du chantier UX conversations/délégations.\n\nObjectif pressenti: Lot C — conversation summaries depuis log/handoff en lecture best-effort, pour que le panneau Work donne une vue lisible des threads sans charger tout le journal.\n\nMerci de définir précisément:\n- objectif produit et inclus/exclus,\n- source de vérité backend (ConversationLog/HandoffStore/ConversationRegistry ou autre),\n- frontières hexagonales: domaine/application/infra/Tauri/frontend,\n- DTOs exacts et évolution de `ProjectWorkState` ou nouvelle commande si préférable,\n- stratégie preview/handoff (bornes, erreurs best-effort, pas de mutation),\n- impacts frontend et UI minimale,\n- tests attendus et commandes.\n\nContraintes:\n- Pas de nouvelle persistance durable si les logs/handoff existent déjà.\n- Ne pas exposer tout `log.jsonl`; previews/summaries seulement.\n- Ne pas bloquer l’état live si un log est corrompu/absent; best-effort.\n- Respecter la suite du chantier UX et éviter de mélanger avec actions Lot D ou mémoire automatique Lot E.\n- Ne code pas."} {"id":"998a4f67-a352-4e89-a5ee-71260d89f112","conversation":"7a2f446c-2efa-0c56-08dd-07cdc5cac19b","atMs":1781975262249,"source":{"kind":"agent","agentId":"dce19c75-9669-4e45-b8de-9950025157da"},"role":"response","text":"## Lot C — Conversation summaries dans le panneau Work\n\n### Objectif produit\n\nObjectif : enrichir le panneau `Work` avec une vue lisible des fils de conversation associés aux tickets/live work, sans exposer ni charger tout `log.jsonl`.\n\nInclus :\n- afficher, pour chaque `conversationId` visible dans le work-state, un résumé compact issu du `handoff.md` quand il existe ;\n- compléter par un aperçu borné des derniers tours du `ConversationLog` quand le handoff est absent ou vide ;\n- signaler sobrement l’état de preview : `ready`, `missing`, `partial`, `unavailable` ;\n- rester strictement read-only et best-effort : aucune écriture, aucun recalcul de handoff, aucun blocage de `live/busy/tickets` si un fichier est absent/corrompu.\n\nExclus :\n- pas d’action utilisateur sur les conversations ;\n- pas de lecture complète affichée du journal ;\n- pas de mutation/réparation des logs/handoff ;\n- pas de génération LLM de résumé ;\n- pas de mémoire automatique Lot E ;\n- pas de changement de FIFO, reprise, cancel, requeue ou actions Lot D.\n\n### Source de vérité backend\n\nSource durable primaire : `HandoffStore` (`.ideai/conversations//handoff.md`).\n\nSource durable secondaire : `ConversationLog` (`.ideai/conversations//log.jsonl`) uniquement via une lecture bornée (`last(n)`), pour construire une preview courte quand le handoff est absent ou inutilisable.\n\n`ConversationRegistry` ne doit pas être la source de vérité du Lot C : il est live/in-memory, utile pour résoudre ou suivre des paires pendant l’orchestration, mais le panneau Work doit refléter les conversations déjà matérialisées par les tickets et leur persistance durable.\n\nSource des conversations à inspecter : les `conversation_id` déjà présents dans les `QueuedTicketSnapshot` projetés par `GetProjectWorkState`. En option additive, si le snapshot live expose déjà un `conversation_id` stable ailleurs, on peut l’ajouter plus tard, mais Lot C doit se limiter aux conversations visibles par les tickets pour éviter d’ouvrir un chantier layout/session.\n\n### Frontières hexagonales\n\nDomaine :\n- réutiliser les ports existants `ConversationLog` et `HandoffStore` ;\n- ajouter seulement des value objects purs de read-model si nécessaire : `ConversationWorkSummary`, `ConversationPreviewStatus`, éventuellement `ConversationTurnPreview` ;\n- aucun accès FS, aucun Tauri, aucun type UI.\n\nApplication :\n- étendre `GetProjectWorkState` avec un collaborateur de lecture de previews, ou extraire un petit service applicatif `ConversationPreviewReader` utilisé par `GetProjectWorkState` ;\n- dépendances injectées : `Arc` et `Arc`, ou `Option>` si on veut rendre l’enrichissement désactivable dans les tests legacy ;\n- le use case continue à renvoyer le work-state même si toutes les previews échouent.\n\nInfrastructure :\n- aucune nouvelle persistance ;\n- réutiliser `FsHandoffStore` et `FsConversationLog` déjà câblés sous `/.ideai/conversations/` ;\n- si `FsHandoffStore::load` renvoie une erreur de parsing, l’application la convertit en preview `unavailable/partial`, pas en `AppError` global ;\n- `FsConversationLog::last` est déjà tolérant aux lignes corrompues, donc c’est le bon fallback.\n\nTauri :\n- garder la commande existante `get_project_work_state` ;\n- faire évoluer `ProjectWorkStateDto` additivement ;\n- ne pas créer `get_conversation_log` ni endpoint brut.\n\nFrontend :\n- types TS miroir dans le domaine UI ;\n- adapter Tauri inchangé côté méthode (`getProjectWorkState`) ;\n- mock gateway normalise `conversations: []` pour compat tests ;\n- composant `ProjectWorkStatePanel` mappe `conversationId -> summary` et affiche une ligne compacte par ticket/thread.\n\n### DTO recommandé\n\nPréférence : étendre `ProjectWorkState` avec une liste top-level de résumés, référencée par `conversationId`. Ça évite de dupliquer le résumé sur plusieurs tickets du même fil.\n\nRust application :\n\n```rust\npub struct ProjectWorkState {\n pub agents: Vec,\n pub conversations: Vec,\n}\n\npub struct ConversationWorkSummary {\n pub conversation_id: ConversationId,\n pub status: ConversationPreviewStatus,\n pub objective_preview: Option,\n pub summary_preview: Option,\n pub summary_len: usize,\n pub up_to: Option,\n pub recent_turns: Vec,\n}\n\npub enum ConversationPreviewStatus {\n Ready,\n Missing,\n Partial,\n Unavailable,\n}\n\npub struct ConversationTurnWorkPreview {\n pub role: TurnRole,\n pub source: TicketWorkSource,\n pub at_ms: u64,\n pub text_preview: String,\n pub text_len: usize,\n}\n```\n\nWire DTO camelCase :\n\n```ts\nexport interface ProjectWorkState {\n agents: AgentWorkState[];\n conversations: ConversationWorkSummary[];\n}\n\nexport type ConversationPreviewStatus =\n | \"ready\"\n | \"missing\"\n | \"partial\"\n | \"unavailable\";\n\nexport interface ConversationWorkSummary {\n conversationId: string;\n status: ConversationPreviewStatus;\n objectivePreview?: string;\n summaryPreview?: string;\n summaryLen: number;\n upTo?: string;\n recentTurns: ConversationTurnWorkPreview[];\n}\n\nexport interface ConversationTurnWorkPreview {\n role: \"prompt\" | \"response\" | \"toolActivity\";\n source: TicketWorkSource;\n atMs: number;\n textPreview: string;\n textLen: number;\n}\n```\n\n`AgentTicketState` ne change pas : il porte déjà `conversationId`, `taskPreview`, `taskLen`, `status`, `requesterLabel`. Le frontend joint via `conversationId`.\n\nCompatibilité : les mocks et tests peuvent normaliser `conversations` à `[]` quand absent, comme ils le font déjà pour `tickets`.\n\n### Stratégie preview/handoff\n\nBornes proposées :\n- `HANDOFF_PREVIEW_MAX_CHARS = 480` ;\n- `OBJECTIVE_PREVIEW_MAX_CHARS = 160` ;\n- `RECENT_TURNS_MAX = 3` ;\n- `TURN_PREVIEW_MAX_CHARS = 220`.\n\nAlgorithme par `conversationId` unique :\n1. tenter `handoffs.load(conversation)` ;\n2. si `Ok(Some(handoff))` :\n - `status = ready` ;\n - `summaryPreview = normalize_truncate(handoff.summary_md, 480)` ;\n - `summaryLen = handoff.summary_md.chars().count()` ;\n - `objectivePreview = handoff.objective.map(truncate 160)` ;\n - `upTo = Some(handoff.up_to)` ;\n3. si `Ok(None)` : `status = missing`, puis tenter `log.last(conversation, 3)` ;\n4. si handoff illisible mais `log.last` marche : `status = partial`, `summaryPreview = None`, `recentTurns = ...` ;\n5. si handoff et log échouent : `status = unavailable`, champs preview vides ;\n6. dans tous les cas, ne jamais propager l’erreur au `ProjectWorkState` global.\n\nNormalisation : même règle que `task_preview` Lot B : trim + collapse whitespace + troncature à la borne, jamais mid-codepoint. On peut factoriser un helper applicatif privé dans `workstate`.\n\nImportant : ne pas appeler `ConversationLog::read(None)` dans Lot C. Le fallback doit être `last(RECENT_TURNS_MAX)` uniquement.\n\n### UI minimale\n\nDans `ProjectWorkStatePanel` :\n- garder la structure actuelle agent > tickets ;\n- sous chaque ticket, afficher une ligne thread compacte si un résumé existe :\n - objectif si présent : `Goal: ` ;\n - sinon résumé : `` ;\n - sinon dernier tour : `Last: ` ;\n - badge discret selon statut : `Summary`, `No summary`, `Partial`, `Unavailable` ;\n- ne pas ajouter de modal ni bouton “ouvrir log” ;\n- ne pas afficher les fichiers, chemins, ni JSON brut ;\n- si `conversationId` n’a pas de summary dans la map, ne pas casser la ligne ticket : afficher seulement l’état Lot B existant.\n\nLe panneau doit rester scannable : pas de cartes imbriquées, pas de gros bloc Markdown rendu. Texte simple, clamp/line wrap raisonnable.\n\n### Tests attendus\n\nApplication (`crates/application/tests/workstate.rs`) :\n- conserve agents/live/busy/tickets même si le preview reader échoue ;\n- déduplique les `conversationId` de plusieurs tickets ;\n- handoff présent produit `ready`, `objectivePreview`, `summaryPreview`, `summaryLen`, `upTo` ;\n- handoff absent + log présent produit `missing` avec `recentTurns` borné ;\n- handoff corrompu + log lisible produit `partial` ;\n- handoff/log indisponibles produit `unavailable` sans `AppError` global ;\n- previews tronquées et whitespace normalisé.\n\nInfrastructure (`crates/infrastructure/tests/conversation_log.rs`) :\n- normalement peu ou pas de nouveau test requis si `FsConversationLog::last` et `FsHandoffStore::load` sont déjà couverts ;\n- ajouter seulement un test si un helper d’adapter dédié apparaît, mais éviter d’en créer un.\n\nTauri DTO (`crates/app-tauri/tests/dto_agents.rs`) :\n- sérialisation camelCase de `conversations` ;\n- conversion des statuts `ready/missing/partial/unavailable` ;\n- compat additive avec agents/tickets existants.\n\nFrontend (`frontend/src/features/workstate/workstate.test.tsx`) :\n- mock sans `conversations` normalisé à `[]` ;\n- ticket avec `conversationId` affiche objectif/résumé ;\n- status `partial`/`unavailable` ne masque pas le ticket ;\n- refresh existant sur `agentBusyChanged` / `delegationReady` continue.\n\nCommandes :\n\n```bash\ncargo test -p application workstate\ncargo test -p app-tauri dto_agents\ncargo test -p infrastructure conversation_log\nnpm --prefix frontend test -- workstate\n```\n\nSelon l’organisation actuelle du workspace, finir par :\n\n```bash\ncargo test\nnpm --prefix frontend test\n```\n\n### Décision de lot\n\nLot C doit être un enrichissement read-only de `ProjectWorkState`, pas une nouvelle surface d’action. Le backend reste source d’autorité pour les bornes et la robustesse ; le frontend ne fait que joindre `tickets[].conversationId` vers `state.conversations[]` et rendre une preview compacte. Ça respecte les contraintes : aucune nouvelle persistance, aucun log brut exposé, best-effort par conversation, et pas de mélange avec les futures actions de gestion ou la mémoire automatique."} +{"id":"d3798b0f-d037-44be-b407-976e8e250da6","conversation":"7a2f446c-2efa-0c56-08dd-07cdc5cac19b","atMs":1781976583977,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Après Lots A/B/C `workstate` mergés dans `develop` @ `6e1ba7e`, cadre le prochain lot prioritaire. L'utilisateur demande d'enchaîner sur tout ce qu'il reste.\n\nContexte livré:\n- Lot A: panneau/read-model live/busy par agent.\n- Lot B: tickets FIFO par agent, source human/agent, statuts inProgress/queued.\n- Lot C: summaries de conversations best-effort top-level, jointes via conversationId.\n\nObjectif pressenti: Lot D — actions UX contrôlées depuis Work: ouvrir/rattacher l'agent/la cellule, voir la conversation, arrêter un agent, copier résumé. Merci de choisir le périmètre minimal prioritaire et de cadrer précisément.\n\nMerci de définir:\n- objectif produit et inclus/exclus,\n- actions à livrer dans ce lot, avec priorité,\n- ports/use cases backend existants à réutiliser ou nouveaux contrats nécessaires,\n- frontières Tauri/frontend,\n- contraintes de sécurité/permissions et cohérence avec single-agent/live-session,\n- impacts UI dans le panneau Work,\n- tests attendus et commandes.\n\nContraintes:\n- Ne pas mélanger avec mémoire automatique Lot E.\n- Ne pas inventer une nouvelle session si une action doit seulement focaliser/rattacher l'existant.\n- Ne pas casser l'historique conversationnel ni exposer log brut.\n- Ne code pas."} +{"id":"e1535768-8ce1-41b7-bc7e-e549fe28ea00","conversation":"7a2f446c-2efa-0c56-08dd-07cdc5cac19b","atMs":1781976715720,"source":{"kind":"agent","agentId":"dce19c75-9669-4e45-b8de-9950025157da"},"role":"response","text":"## Lot D — actions contrôlées depuis Work\n\n### Objectif produit\n\nRendre le panneau `Work` opérable sans changer sa nature : il reste un tableau de bord live/conversations, mais l’utilisateur peut agir sur l’état déjà visible.\n\nPérimètre minimal prioritaire :\n- retrouver ou afficher la cellule d’un agent live ;\n- rattacher une session déjà live à une cellule existante et déterminée ;\n- arrêter explicitement un agent live ;\n- voir davantage de la preview conversationnelle Lot C ;\n- copier le résumé affiché.\n\nExclus :\n- pas de lancement d’un agent neuf depuis Work ;\n- pas de création automatique de cellule/layout ;\n- pas de cancel/requeue/priorisation FIFO ;\n- pas de modification de handoff, mémoire automatique ou Lot E ;\n- pas de lecture/exposition brute de `log.jsonl` ;\n- pas de transcript complet.\n\n### Actions à livrer\n\nPriorité 1 — `Open` / `Go to cell`\n- Si `agent.live.nodeId` correspond à une cellule visible dans le layout actif : focus/scroll/highlight de cette cellule.\n- Aucun appel backend. Aucun spawn.\n- Libellé UI : `Open` ou `Go to cell` selon la place.\n\nPriorité 2 — `Attach`\n- Si l’agent est live mais sa cellule hôte n’est plus visible, rattacher la session à une cellule existante et non ambiguë.\n- Cible autorisée pour ce lot : une cellule visible déjà pinnée sur le même agent et sans session, ou une cellule explicitement sélectionnée par l’utilisateur dans le layout.\n- Si aucune cible déterministe : action désactivée avec message court du type `Pin this agent to a cell first`.\n- Ne jamais créer une nouvelle session. Ne jamais appeler `launch_agent`.\n\nPriorité 3 — `Stop`\n- Arrête la session live de l’agent après confirmation.\n- Fonctionne pour PTY et structured.\n- Ne supprime pas l’agent, ne supprime pas le handoff, ne vide pas `conversationId`.\n- Après succès : refresh Work + layout/live state via events existants ou refresh explicite.\n\nPriorité 4 — `View conversation`\n- Expansion inline dans Work sur la base de `ProjectWorkState.conversations[]`.\n- Montre objectif, summary preview, et les `recentTurns` déjà bornés par Lot C.\n- Pas de nouveau endpoint de transcript.\n\nPriorité 5 — `Copy summary`\n- Frontend-only via clipboard.\n- Copie uniquement le texte déjà exposé par Lot C : objectif + summary preview + recent turns bornés si summary absent.\n- Désactivé si aucune preview exploitable.\n\n### Backend\n\nRéutiliser :\n- `GetProjectWorkState` : source unique du panneau, inchangé fonctionnellement ;\n- `LiveSessions` / `TerminalSessions` / `StructuredSessions` : source de vérité live ;\n- `LayoutGateway.mutateLayout` avec `setSession` / `setCellAgent` ;\n- `close_terminal` et `close_agent_session` comme primitives existantes ;\n- `attach_live_agent` existant, mais à durcir.\n\nContrats backend recommandés :\n\n1. Faire évoluer `attach_live_agent` en vrai use case applicatif `AttachLiveAgent`.\n\nEntrée :\n```rust\npub struct AttachLiveAgentInput {\n pub project: Project,\n pub agent_id: AgentId,\n pub node_id: NodeId,\n}\n```\n\nSortie :\n```rust\npub struct AttachLiveAgentOutput {\n pub agent_id: AgentId,\n pub node_id: NodeId,\n pub session_id: SessionId,\n pub kind: LiveSessionKind,\n}\n```\n\nComportement :\n- chercher la session live dans `TerminalSessions`, sinon `StructuredSessions` ;\n- rebind `node_id` sans spawn ;\n- retourner `NOT_FOUND` si l’agent n’est plus live ;\n- idempotent si déjà attaché à cette cellule ;\n- ne touche pas `ConversationLog`, `HandoffStore`, `ProviderSessionStore`.\n\n2. Ajouter `StopLiveAgent` plutôt que faire deviner le registre au frontend.\n\nEntrée :\n```rust\npub struct StopLiveAgentInput {\n pub project: Project,\n pub agent_id: AgentId,\n}\n```\n\nSortie :\n```rust\npub struct StopLiveAgentOutput {\n pub agent_id: AgentId,\n pub session_id: SessionId,\n pub kind: LiveSessionKind,\n}\n```\n\nComportement :\n- résoudre la session live par agent ;\n- si PTY : déléguer à `CloseTerminal` / `PtyPort.kill` ;\n- si structured : `StructuredSessions::remove` puis `AgentSession::shutdown()` ;\n- publier `DomainEvent::AgentExited { agent_id, code }` ou l’équivalent déjà relayé ;\n- no-op contrôlé ou `NOT_FOUND` si déjà arrêté. Préférence : `NOT_FOUND` côté API, frontend l’interprète comme refresh.\n\nPourquoi pas seulement `close_terminal(sessionId)` : `WorkState.live.kind` existe, et le modèle a deux registries. Une commande par agent garde la cohérence single-agent côté backend.\n\n### Tauri / DTO\n\nNouvelles/évolutions :\n\n```ts\nattach_live_agent({ request: { projectId, agentId, nodeId } })\n -> { agentId, nodeId, sessionId, kind }\n\nstop_live_agent({ request: { projectId, agentId } })\n -> { agentId, sessionId, kind }\n```\n\n`LiveAgentDto` peut être réutilisé si on lui ajoute/garantit `kind`; sinon créer un DTO dédié. Préférence : enrichir `LiveAgent` additivement avec `kind: \"pty\" | \"structured\"`, cohérent avec `LiveWorkSession.kind`.\n\nPas de commande `get_conversation_log`, `open_conversation`, `copy_summary`.\n\n### Frontend\n\nPorts UI :\n- `AgentGateway.attachLiveAgent(projectId, agentId, nodeId): Promise` doit retourner `kind` ;\n- ajouter `AgentGateway.stopLiveAgent(projectId, agentId): Promise` ;\n- ne pas faire appeler `close_terminal` directement par `ProjectWorkStatePanel` pour stopper un agent ;\n- `LayoutGateway.loadLayout/mutateLayout` réutilisés pour trouver/attacher la cellule cible.\n\nCoordination UI recommandée :\n- extraire le helper de focus cellule (`goToCell`) de `LayoutGrid` vers `features/layout/focusCell.ts` ;\n- ajouter un petit hook `useWorkActions(projectId, activeLayoutId?)` consommé par `ProjectWorkStatePanel` ;\n- le hook sait charger le layout actif, trouver les feuilles, vérifier si `live.nodeId` est visible, et appeler `attachLiveAgent + mutateLayout(setCellAgent/setSession)` si une cible déterministe existe.\n\n### Sécurité / cohérence\n\nRègles obligatoires :\n- `Open` et `Attach` ne spawnent jamais. Interdit d’appeler `launchAgent` depuis Work dans Lot D.\n- `Attach` ne doit pas écraser une cellule qui héberge déjà une session différente, sauf remplacement explicite futur hors lot.\n- `Stop` demande confirmation ; c’est une action destructive sur le process, mais pas sur les fichiers.\n- `Stop` ne supprime ni agent, ni tickets, ni conversation summary, ni handoff.\n- `Stop` doit libérer la live registry pour préserver l’invariant “un agent = une session live max”.\n- Si un ticket est `inProgress`, afficher dans la confirmation que le tour en cours sera interrompu ; ne pas tenter de résoudre le ticket dans ce lot.\n- Pas de permission filesystem nouvelle. Les seules mutations durables sont les mutations layout déjà existantes (`setSession`/`setCellAgent`) lors d’un attach.\n\n### UI minimale\n\nDans chaque ligne agent :\n- bouton `Open` si live ;\n- bouton `Attach` si live mais cellule non visible et une cible existe ;\n- bouton `Stop` si live ;\n- état désactivé explicite si action impossible.\n\nDans chaque ticket/summary :\n- bouton disclosure `View` pour ouvrir/fermer les détails Lot C ;\n- bouton `Copy` à côté du summary, pas sur toute la ligne ;\n- garder l’UI dense : pas de drawer global, pas de modal conversation, pas de rendu Markdown lourd.\n\nFeedback :\n- erreurs action en inline dans le panneau ;\n- après `Stop`/`Attach`, refresh `getProjectWorkState` et layout actif ;\n- `Copy` affiche un état bref `Copied`.\n\n### Tests attendus\n\nApplication Rust :\n- `AttachLiveAgent` rebind PTY sans changer `session_id` ;\n- `AttachLiveAgent` rebind structured sans changer `session_id` ;\n- attach agent absent => `NOT_FOUND` ;\n- attach même node => idempotent ;\n- `StopLiveAgent` arrête PTY et retire la session live ;\n- `StopLiveAgent` arrête structured via `shutdown()` et retire la session live ;\n- stop absent => `NOT_FOUND` ;\n- stop ne touche pas handoff/log/provider sessions.\n\nTauri DTO/tests :\n- request camelCase pour `attach_live_agent` et `stop_live_agent` ;\n- response sérialise `kind`; \n- erreurs `INVALID` ids et `NOT_FOUND` live session.\n\nFrontend tests :\n- `Open` focus une cellule visible et n’appelle aucun launch ;\n- `Attach` appelle `attachLiveAgent` puis `mutateLayout(setSession)` sur une cible déterministe ;\n- `Attach` désactivé sans cible ;\n- `Stop` demande confirmation puis appelle `stopLiveAgent`, refresh Work ;\n- `Stop` sur agent busy affiche l’avertissement ;\n- `View conversation` n’affiche que les champs Lot C ;\n- `Copy summary` copie uniquement la preview exposée, pas de log brut ;\n- pas de régression des refresh events Lot A/B/C.\n\nCommandes :\n\n```bash\ncargo test -p application terminal_usecases structured_registry workstate\ncargo test -p app-tauri dto_agents list_live_agents_r0b\nnpm --prefix frontend test -- workstate\nnpm --prefix frontend test -- LayoutGrid singletonAgent agentAlreadyRunning\n```\n\nPuis en validation large :\n\n```bash\ncargo test\nnpm --prefix frontend test\n```\n\n### Décision de cadrage\n\nLot D doit être “actions sur état existant”, pas “nouveau centre de contrôle”. Le seul ajout backend vraiment justifié est de faire porter `attach/stop` par des contrats agent-level qui respectent PTY + structured et l’invariant single-agent. La conversation reste une preview bornée issue de Lot C ; le log brut et la mémoire automatique restent hors périmètre."} diff --git a/.ideai/layouts.json b/.ideai/layouts.json index 1f6a499..8c79c73 100644 --- a/.ideai/layouts.json +++ b/.ideai/layouts.json @@ -15,12 +15,35 @@ "children": [ { "node": { - "type": "leaf", + "type": "split", "node": { - "id": "d4b8c0d1-a44a-4c45-bbe9-26991f79b465", - "session": "813f17c2-d056-4875-b9a0-3c18e4e40776", - "agent": "a6ced819-b893-4213-b003-9e9dc79b9641", - "agentWasRunning": true + "id": "652a0a78-e342-473d-9725-ebac04556cbf", + "direction": "column", + "children": [ + { + "node": { + "type": "leaf", + "node": { + "id": "d4b8c0d1-a44a-4c45-bbe9-26991f79b465", + "session": "813f17c2-d056-4875-b9a0-3c18e4e40776", + "agent": "a6ced819-b893-4213-b003-9e9dc79b9641", + "agentWasRunning": true + } + }, + "weight": 1.0760044 + }, + { + "node": { + "type": "leaf", + "node": { + "id": "b7fc564a-7673-4970-acdc-1b8cf23dee8e", + "session": "3a6c9dc2-9919-4c17-8f0f-48439151fd27", + "agent": "dce19c75-9669-4e45-b8de-9950025157da" + } + }, + "weight": 0.9239957 + } + ] } }, "weight": 1.0 diff --git a/.ideai/memory/MEMORY.md b/.ideai/memory/MEMORY.md index 14f6f65..b488c00 100644 --- a/.ideai/memory/MEMORY.md +++ b/.ideai/memory/MEMORY.md @@ -15,3 +15,4 @@ - [checkpoint-blocked-until-appimage-030-restart](checkpoint-blocked-until-appimage-030-restart.md) — memory note checkpoint-blocked-until-appimage-030-restart - [checkpoint-delivery-submit-logging-fix](checkpoint-delivery-submit-logging-fix.md) — memory note checkpoint-delivery-submit-logging-fix - [checkpoint-workstate-delegation-queue-lot-b](checkpoint-workstate-delegation-queue-lot-b.md) — memory note checkpoint-workstate-delegation-queue-lot-b +- [checkpoint-workstate-conversation-summaries-lot-c](checkpoint-workstate-conversation-summaries-lot-c.md) — memory note checkpoint-workstate-conversation-summaries-lot-c diff --git a/.ideai/memory/checkpoint-workstate-conversation-summaries-lot-c.md b/.ideai/memory/checkpoint-workstate-conversation-summaries-lot-c.md new file mode 100644 index 0000000..b8b3680 --- /dev/null +++ b/.ideai/memory/checkpoint-workstate-conversation-summaries-lot-c.md @@ -0,0 +1,60 @@ +--- +name: checkpoint-workstate-conversation-summaries-lot-c +description: memory note checkpoint-workstate-conversation-summaries-lot-c +metadata: + type: project +--- +# Checkpoint — Workstate conversation summaries Lot C + +Date: 2026-06-20 + +## État final + +Lot C `workstate conversation summaries` terminé, validé QA et mergé localement dans `develop`. + +Branche finale: +- `develop` @ `6e1ba7e` — merge local `feature/workstate-conversation-summaries`. +- `feature/workstate-conversation-summaries` conservée. +- Aucun push, aucune branche supprimée. +- Working tree propre après décision Git. + +## Commits créés + +- `e9edadc` — `feat(workstate): résumés de conversation dans le read-model (Lot C backend)` + - `ProjectWorkState.conversations` top-level. + - Résumés best-effort depuis `HandoffStore`, fallback `ConversationLog::last(3)`. + - Déduplication des conversation ids issus des tickets, ordre first-seen. + - DTO camelCase et câblage Tauri. + +- `c50622e` — `feat(workstate): UI des résumés de conversation (Lot C frontend)` + - Types TS `ConversationWorkSummary` et previews. + - Mock normalise `conversations: []`. + - Panneau Work joint `tickets[].conversationId` vers `conversations[]` et affiche badge + preview compacte. + +- `78500d8` — `chore(wip): état runtime .ideai (flux conversation live)` + - Runtime isolé des commits feature. + +- `6e1ba7e` — merge local dans `develop`. + +## QA verte + +Commandes QA finales validées: +- `cargo fmt --all -- --check` OK. +- `cargo test -p application --test workstate` OK, 21 passed, aucun warning Rust. +- `cargo test -p app-tauri --test dto_agents` OK, 21 passed. +- `cargo check -p app-tauri` OK. +- `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx` OK, 2 files / 19 tests. +- `cd frontend && npx tsc --noEmit` OK. + +Warnings restants uniquement côté Vite sur options `esbuild` dépréciées/ignorées au profit de `oxc`, non bloquants. + +## Décisions produit/techniques + +- Source primaire: handoff conversationnel existant. +- Fallback: derniers tours bornés du log, jamais le log brut complet. +- Read-only et best-effort: les erreurs de preview ne bloquent pas `live/busy/tickets`. +- Pas de nouvelle persistance, pas de mutation/réparation, pas de nouvelle action UX. + +## Suite probable + +Le prochain lot logique est Lot D: actions UX read/write contrôlées autour du panneau Work (ouvrir/rattacher cellule, voir conversation, arrêter agent, éventuellement copier résumé), à cadrer par Architect avant implémentation. \ No newline at end of file