--- 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:** 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. - **Prompt:** Validation rapide architecture pour correctif IdeA: le runtime structuré Codex lance `codex exec` depuis le run dir isolé et doit permettre l’écriture dans le project root quand les permissions résolues donnent workspace-write. Proposition: faire transiter `PreparedContext.project_root`/équivalent depuis `StructuredSessionFactory::start` vers `CodexExecSession`, puis ajouter `--add-dir ` à la ligne `codex exec` en workspace-write, sans ajouter `--ask-for-approval` car `codex exec` le refuse. Est-ce conforme Ports & Adapters/hexagonal ? Signale toute frontière à ne pas franchir. - **Response:** Oui, c’est conforme à l’architecture Ports & Adapters, avec une réserve de frontière importante. Validation : - Faire porter `project_root` dans un DTO/VO de contexte préparé (`PreparedContext.project_root` ou équivalent) est acceptable si cette donnée représente le périmètre métier d’exécution du projet, pas un détail Tauri/Codex. - `StructuredSessionFactory::start` peut transmettre cette donnée vers l’adapter runtime concret : c’est précisément le rôle de la couche application/composition d’orchestrer les ports avec le contexte résolu. - Ajouter `--add-dir ` dans `CodexExecSession` est au bon endroit si `CodexExecSession` est l’adapter infrastructure/presentation-backend spécifique à Codex CLI. Le flag `--add-dir` est un détail technique de l’adapter Codex, donc il ne doit pas remonter dans le domaine. - Conditionner cet ajout à `workspace-write` est cohérent : l’intention métier est “la session peut écrire dans le workspace résolu”, l’implémentation Codex est “ajouter le root autorisé à la sandbox CLI”. - Ne pas ajouter `--ask-for-approval` est correct si `codex exec` le refuse. Il ne faut pas forcer une option non supportée au niveau adapter. Frontières à ne pas franchir : - Ne pas faire dépendre le domaine/application de `codex exec`, `--add-dir`, `--sandbox`, ni des noms de flags Codex. Ces détails restent dans l’adapter Codex. - Ne pas laisser `CodexExecSession` recalculer seul le project root à partir du run dir isolé si le project root a déjà été résolu par les couches supérieures. L’adapter doit consommer un contexte préparé, pas réinterpréter l’état global. - Ne pas élargir systématiquement les permissions : `--add-dir` doit être ajouté uniquement pour le `projectRoot` validé/résolu du projet et uniquement quand le mode effectif est `workspace-write`. - Ne pas confondre `cwd` et périmètre d’écriture : le cwd peut rester le run dir isolé pour l’exécution/session, tandis que `project_root` devient une racine autorisée explicite. - Ne pas exposer ce comportement côté React/Tauri commands comme une option utilisateur brute. L’UI demande un lancement d’agent/session ; la politique de sandbox reste dans les services/adapters backend. Formulation recommandée côté modèle : plutôt qu’un champ trop Codex-spécifique, utiliser un nom neutre comme `workspace_roots`, `writable_roots`, `project_root`, ou `ExecutionScope { cwd, project_root, sandbox_mode }`. Puis l’adapter Codex traduit `ExecutionScope + workspace-write` en `codex exec --add-dir `. - **Prompt:** Contexte: l'utilisateur veut terminer les chantiers ouverts d'IdeA, en autonomie, via le cycle Main -> Architect -> Git -> Dev -> QA -> Git. Main ne code pas. Decision Git deja obtenue: - Branche courante: `feature/orchestrator-designation`. - Worktree dirty important (~75 fichiers) mais analyse Git: mono-theme = chantier orchestrateur/designation + nouveau sink diagnostic `crates/application/src/diag.rs` pour le rendez-vous inter-agents. - Ne pas stash/switch. Premier chantier logique: finir `orchestrator-designation` sur cette branche, puis QA, puis Git commit/merge develop. Etat memoire projet pertinent: - Chantiers restants: agent=session vivante coherente, persistance conversationnelle, handoff cross-profile, live-state partage, refresh memoire/contexte a chaud, UX delegations/conversations, documentation architecture. - Le chantier actuel semble etre une brique d'orchestration/designation, avec modifications dans domain/application/infrastructure/app-tauri/frontend terminal write portal et permissions/sandbox/session structured. Ta mission Architect: 1. Cadre le chantier courant `orchestrator-designation` a partir de son nom et de l'etat Git ci-dessus: objectif architectural probable, frontieres de couches, contrats/ports/DTO a verifier, invariants attendus. 2. Donne a Main un plan de delegation concret: ce que DevBackend doit verifier/finaliser, ce que DevFrontend doit verifier/finaliser, ce que QA doit tester. 3. Donne des criteres de fin stricts pour considerer ce chantier ferme. 4. Indique s'il faut mettre a jour `architect.md` maintenant ou attendre la fin de la stabilisation. Ne code pas. Reponds avec `idea_reply` si cette tache est deleguee via IdeA. - **Prompt:** Tu es sollicité par Main pour reprendre après le checkpoint AppImage 0.3.0. Contexte utile: le chantier `orchestrator-designation` est intégré localement sur `develop` (develop ahead origin, AppImage 0.3.0 produite). Le prochain chantier recommandé par Git est `feature/agent-skill-awareness`, mais il faut d'abord trancher son chevauchement avec une branche/initiative `feature/agent-skills` mentionnée dans les notes. Merci de cadrer précisément: 1) objectif produit/technique de `agent-skill-awareness`, 2) différence avec `agent-skills` si visible depuis le repo ou les notes, 3) frontières backend/frontend/docs, 4) contrats/ports/DTO à créer ou modifier, 5) risques et ordre d'implémentation. Ne code pas; donne un cadrage actionnable pour Git puis DevBackend/DevFrontend/QA. - **Response:** ## Cadrage `feature/agent-skill-awareness` État repo constaté : `develop` est ahead origin et contient déjà L12 `agent-skills` dans le code courant (`Skill`, `SkillStore`, CRUD, assignation agent↔skill, injection des bodies dans le convention file). La branche `feature/agent-skill-awareness` diverge avant l’intégration `orchestrator-designation`; son diff est très large et mêlé à des changements runtime/anciens. Recommandation Git : **ne pas merger la branche brute**. Rejouer sélectivement les contrats utiles sur une branche neuve depuis `develop`. ## 1) Objectif produit/technique Objectif produit : un agent doit savoir qu’il possède des skills assignés et comment les utiliser, sans que l’utilisateur doive le lui rappeler. Aujourd’hui L12 injecte le contenu complet des skills dans `# Skills`, mais c’est bas dans le contexte, lu comme documentation, et pas présenté comme affordance/action disponible. Objectif technique minimal : transformer l’injection “dump de contenu” en modèle “à la MCP” pour les profils MCP : - section haute altitude `# Skills disponibles`, juste après l’orchestration/capacités IdeA, listant les skills assignés par `name + description` ; - outil MCP read-only `idea_skill_read(name)` pour charger le body complet à la demande ; - champ optionnel `description` sur `Skill`, avec fallback déterministe via `effective_description()` ; - rétrocompat des anciens `skills/index.json` sans `description` ; - conserver un mode non-MCP compatible. Point à trancher avec Architecture : soit garder l’ancien dump complet pour `mcp == None`, soit intégrer cette découverte au futur binaire universel `idea`/skill built-in. Ne pas mélanger avec la refonte C-univ complète. La note de branche ajoute aussi un brief inconditionnel “capacités IdeA” dans `compose_convention_file`. Attention : `ARCHITECTURE.md` récent pousse une décision plus structurante : remplacer la prose libre d’orchestration par un **skill built-in `Orchestration IdeA`** (`SkillScope::Builtin`). Donc, pour ce chantier, je recommande de **ne pas reprendre tel quel le brief inconditionnel** sauf comme correctif transitoire validé par Architecture. Le périmètre propre de `agent-skill-awareness` doit rester : “découverte et lecture des skills assignés”. ## 2) Différence avec `feature/agent-skills` `agent-skills` visible dans le repo = L12, déjà intégré fonctionnellement : - domaine : `Skill`, `SkillScope::{Global, Project}`, `SkillRef`, `Agent.skills`/`ManifestEntry.skills` ; - port : `SkillStore` ; - infra : `FsSkillStore` global + projet ; - application : `CreateSkill`, `UpdateSkill`, `ListSkills`, `DeleteSkill`, `AssignSkillToAgent`, `UnassignSkillFromAgent` ; - backend Tauri + DTO ; - frontend : `features/skills`, `SkillGateway`, assignation dans `AgentsPanel` ; - lancement : `LaunchAgent` résout les skills assignés et injecte leurs bodies dans `compose_convention_file`. `agent-skill-awareness` = couche par-dessus L12 : - rend les skills visibles comme capacités nommées ; - évite de forcer le body complet en contexte MCP ; - expose une lecture explicite du body via `idea_skill_read`; - ajoute `description` comme méta courte, pas une nouvelle famille de skills. Donc ce n’est pas un doublon de `agent-skills`; c’est une amélioration d’ergonomie runtime. Mais son ancienne branche embarque des changements qui chevauchent des zones modifiées depuis, donc reprise manuelle. ## 3) Frontières backend/frontend/docs Backend Domaine : - ajouter `description: Option` à `Skill` avec `#[serde(default)]` ; - ajouter `Skill::with_description` et `Skill::effective_description()` ; - préserver `with_content()` en conservant la description ; - éventuellement ajouter `OrchestratorCommand::ReadSkill { name, requester? }` et action `skill.read` dans `OrchestratorRequest::validate` si on garde le routage MCP via modèle orchestrator. Backend Application : - `CreateSkillInput` reçoit `description: Option` ; - `UpdateSkill` doit idéalement pouvoir modifier `description` aussi, pas seulement `content`, sinon le frontend ne peut pas éditer la méta ; - créer `ReadSkill` use case read-only sur le port existant `SkillStore`, pas de nouveau port ; - résolution par nom : project scope d’abord, global ensuite ; ambiguïté dans un même scope = erreur typée ; absent = not found ; case-insensitive. Backend Lifecycle : - modifier `compose_convention_file` pour les profils MCP : section `# Skills disponibles` en amont, lignes déterministes `**name** — description`, mentionnant `idea_skill_read(name=...)` ; - garder ordre déterministe des skills assignés selon le manifest ; - décider comportement non-MCP. Option conservatrice : garder le dump `# Skills` complet seulement en non-MCP pour zéro régression. Option cible architecture : passer par skill built-in + futur CLI `idea`, mais c’est un autre lot. Infrastructure / MCP : - ajouter `idea_skill_read` au catalogue MCP et au mapping tool → `OrchestratorCommand::ReadSkill` ; - dispatcher dans `OrchestratorService` vers `ReadSkill`, retour inline Markdown ; - câbler dans `state.rs`/composition root ; - mettre à jour les tests de compteur/catalogue MCP, actuellement sensibles aux nombres fixes. App-Tauri DTO/commands : - `SkillDto` bénéficie du champ automatiquement si `Skill` sérialise camelCase ; - `CreateSkillRequestDto` et `UpdateSkillRequestDto` doivent porter `description?: string | null` ; - commandes UI existantes `create_skill`/`update_skill` restent les mêmes noms. Frontend : - `domain Skill` ajoute `description?: string | null` ; - `CreateSkillInput` ajoute `description?: string`; `updateSkill` doit permettre de passer description + content, pas seulement content ; - `SkillEditor` ajoute un champ court “Description” ; en edit, description éditable ; - `SkillsPanel` peut afficher description sous le nom ; - mocks + tests RTL à adapter. Docs : - `ARCHITECTURE.md` : ajouter la décision “awareness MCP” sous §14.2 ou l’aligner avec §16 si le skill built-in devient la voie cible ; - `agents-dev/L12-skills.md` ou note dédiée : préciser que L12 crée/assigne/injecte, et que `agent-skill-awareness` ajoute description + affordance + read tool. ## 4) Contrats / ports / DTO À créer/modifier : - `domain::Skill { description: Option }` avec serde default. - `Skill::effective_description() -> String` : description non vide trimée, sinon première ligne non vide du body sans `#` initial. - `SkillStore` : **pas de nouveau port**. Les impls `list/get/save` transportent simplement le champ. - `FsSkillStore` index : ajouter `description` dans `IndexEntry`, `#[serde(default)]`, écriture camelCase. Le body reste dans `md/.md`. - `CreateSkillInput { name, description, content, scope, project_root }`. - `UpdateSkillInput` à faire évoluer vers `{ scope, skill_id, description, content, project_root }` ou variante patch explicite. Je conseille simple remplacement complet `description + content` pour rester aligné UI. - nouveau use case `ReadSkill { SkillStore }` : input `{ name, project_root }`, output `MarkdownDoc` ou DTO `{ name, scope, contentMd }` si on veut plus de traçabilité. Branche historique renvoyait seulement `MarkdownDoc`; acceptable pour MCP. - `OrchestratorRequest`: action/type `skill.read`, champ `name` requis. - `OrchestratorCommand::ReadSkill { name, requester? }`. Le `requester` est utile si la couche MCP veut garder la symétrie avec context/memory, mais le use case n’en a pas besoin. - MCP tool `idea_skill_read`: input `{ name: string }`, output contenu Markdown inline. - DTO Tauri/TS : `Skill.description?: string | null`, create/update requests. À ne pas créer : - pas de `SkillAwarenessStore` ; - pas de nouveau port dédié ; - pas de mécanisme CLI propriétaire ; - pas de lancement/exécution de skill depuis l’UI. ## 5) Risques et ordre d’implémentation Risques : - la branche historique est obsolète vis-à-vis de `orchestrator-designation`; merge brut très risqué ; - conflit conceptuel avec la décision architecture `SkillScope::Builtin` / “Orchestration IdeA” : ne pas figer de prose libre qui sera supprimée juste après ; - double noms de skills : project shadow global doit être documenté ; doublons intra-scope doivent produire une erreur claire ; - assignation à chaud : un skill assigné pendant une session ne devient visible qu’après relaunch/régénération du convention file, sauf mécanisme futur ; - ancien index sans description : serde default obligatoire ; - tests MCP avec compteurs hardcodés fragiles ; préférer assertion par présence de tool. Ordre recommandé : 1. **Git** : créer une branche neuve depuis `develop`, par exemple `feature/agent-skill-awareness-v2`; ne cherry-pick que si les hunks sont triviaux, sinon réimplémenter depuis la note. Garder `feature/agent-skill-awareness` comme référence de design/test, pas comme base de merge. 2. **DevBackend T1** : domaine + serde + tests purs (`description`, fallback, `with_content` conserve description, legacy JSON). 3. **DevBackend T2** : `FsSkillStore`, use cases create/update/list, DTO Tauri. Tests store legacy + roundtrip. 4. **DevBackend T3** : `ReadSkill` + `OrchestratorCommand::ReadSkill` + validation `skill.read`. Tests résolution project/global/ambiguous/not found. 5. **DevBackend T4** : MCP catalogue/mapping/service/state. Tests tool map + dispatch + catalogue par nom. 6. **DevBackend T5** : `compose_convention_file` awareness. Tests : MCP avec skills affiche `# Skills disponibles`, descriptions, appel `idea_skill_read`, pas de body complet si décision retenue ; non-MCP conserve baseline/dump complet ; zéro skill omet la section ; ordre avant persona ou juste après orchestration selon décision. 7. **DevFrontend T6** : champ description dans domain/ports/adapters/mock/SkillEditor/SkillsPanel/tests. Ne pas toucher à l’exécution de skills. 8. **Docs T7** : architecture + agents-dev, en explicitant la frontière avec futur `SkillScope::Builtin`. 9. **QA T8** : `cargo test --workspace`, `pnpm test`, puis e2e AppImage : agent neuf + skill assigné + profil MCP voit `# Skills disponibles`, appelle `idea_skill_read`, reçoit le body ; profil non-MCP ne régresse pas. Verdict court : `agent-skills` = socle déjà intégré ; `agent-skill-awareness` = affordance + lecture à la demande. Reprendre le chantier, oui, mais en branche neuve depuis `develop`, avec périmètre serré et sans avaler le vieux diff. - **Prompt:** Reprise chantier `feature/agent-skill-awareness-v2` après hotfix livraison délégation/logs submit. Contexte: branche actuelle `feature/agent-skill-awareness-v2`; dirty attendu avec fichiers runtime `.ideai/*` et changements code du hotfix listés dans la mémoire `checkpoint-delivery-submit-logging-fix`. Le checkpoint précédent disait de cadrer `agent-skill-awareness` vs `agent-skills` avant implémentation. Merci de fournir le cadrage architecture: objectif exact, frontières backend/frontend, contrats/DTO/ports à toucher, et ordre d'implémentation recommandé. Ne code pas. - **Response:** ## Cadrage architecture — `feature/agent-skill-awareness-v2` ### Verdict de cadrage Il faut distinguer strictement deux sujets : - **`agent-skills` / L12** = fonctionnalité déjà existante : entité `Skill`, scopes `Global|Project`, CRUD, assignation agent↔skill dans le manifeste, injection des bodies assignés dans le convention file à l’activation. - **`agent-skill-awareness`** = rendre l’agent conscient du système de skills IdeA et de la manière correcte de les utiliser/contribuer, sans réimplémenter L12. Donc le chantier recommandé n’est pas un nouveau store, ni une nouvelle UI CRUD, ni un mécanisme de slash-commands fournisseur. C’est une évolution de **composition de contexte agent** + éventuellement une petite surface de découverte orchestrateur, gardée séparée. ### Objectif exact MVP recommandé : à chaque activation d’agent, le convention file doit expliquer explicitement : 1. Les sections sous `# Skills` sont les workflows assignés à cet agent, utilisables quand pertinents. 2. Un skill assigné est du contexte opérationnel, pas une commande magique ni une sous-tâche fournisseur. 3. Si l’agent identifie un workflow réutilisable, il doit passer par l’orchestration IdeA (`idea_create_skill` côté MCP, ou `skill.create` côté protocole fichier), jamais écrire directement dans `.ideai/skills/`. 4. Les skills non assignés ne doivent pas être injectés intégralement à tous les agents : l’assignation reste la frontière de contexte. Ce MVP ferme le flou `awareness` vs `skills` : on garde L12 comme source métier, on améliore la compréhension de l’agent au moment où son contexte est généré. ### Frontières backend À toucher : - `crates/application/src/agent/lifecycle.rs` - `compose_convention_file(...)` est le point naturel : fonction pure, déjà responsable de `Project root`, orchestration, contexte projet, persona, skills, mémoire, handoff. - Ajouter une sous-section courte, stable et testable, par exemple dans `# Orchestration IdeA` ou juste avant `# Skills` : `## Usage des skills IdeA`. - Garder l’ordre actuel : project root → orchestration/awareness → contexte projet → persona → skills → mémoire → handoff. - Tests application de composition dans le même fichier ou suite existante : - agent sans skills : la consigne awareness peut exister, mais pas de section `# Skills` si la liste est vide, pour préserver le contrat actuel. - agent avec skills : la consigne précède les bodies et les bodies restent en ordre manifeste. - `mcp_enabled=true` : mentionner les outils `idea_create_skill` / outils natifs IdeA. - `mcp_enabled=false` : mentionner le protocole fichier `skill.create`. À ne pas toucher pour le MVP : - Pas de nouveau `SkillStore`. - Pas de nouveau port domaine. - Pas de nouveau DTO Tauri. - Pas de mutation du manifeste. - Pas de changement des invariants `Skill`, `SkillRef`, `Agent.skills`. - Pas de scope `Builtin` tant que le projet reste sur l’état réel actuel : l’architecture historique en parle, mais le code courant a déjà une prose d’orchestration dynamique et le pivot structuré/MCP est livré différemment. Réintroduire `Builtin` serait un autre chantier. ### Frontières frontend MVP : **aucune frontière frontend obligatoire**. L’UI skills existe déjà via : - `frontend/src/domain/index.ts` : `Skill`, `SkillRef`, `SkillScope`. - `frontend/src/ports/index.ts` : `SkillGateway`. - `frontend/src/adapters/skill.ts` : `list/create/update/delete/assign/unassign`. - `frontend/src/features/skills/*` : panneau et view-model L12. Éventuel ajustement UI, à garder non bloquant : afficher dans le panneau skills une phrase sobre du type “Les skills assignés sont injectés au lancement de l’agent”. Pas de nouveau gateway pour ça. ### Contrats/DTO/ports à toucher MVP recommandé : - **Domaine Rust** : aucun nouveau type requis. - **Ports Rust** : aucun nouveau port. - **Application** : seulement la fonction pure de composition du convention file et ses tests. - **Infrastructure** : aucun changement. - **Tauri DTO/commands** : aucun changement. - **Frontend DTO/ports** : aucun changement. Contrats existants à respecter : - `domain::Skill { id, name, content_md, scope }`. - `domain::SkillRef { skill_id, scope }` stocké sur l’agent/manifeste. - `SkillStore::list/get/save/delete` reste la seule abstraction de persistance. - `LaunchAgent::resolve_skills` reste best-effort pour les dangling refs : un skill supprimé ne bloque pas le launch. - `compose_convention_file` reste pure/I-O free. ### Option séparée : découverte typée des skills par agent À ne faire que si le produit veut que les agents découvrent les skills non assignés. Dans ce cas, ne pas injecter tous les bodies. Ajouter plutôt une surface compacte : - `OrchestratorCommand::ListSkills { scope: Option }` ou `ListSkills` avec scope requis. - Alias protocole : `skill.list`. - Outil MCP : `idea_list_skills(scope?)`. - `OrchestratorService` réutilise le `SkillStore` existant et renvoie une liste compacte `{ id, name, scope }`, pas `contentMd` par défaut. Ce serait un lot séparé, parce que cela touche `domain/src/orchestrator.rs`, `application/src/orchestrator/service.rs`, l’adapter MCP / watcher, et éventuellement les DTO de réponse. Ce n’est pas nécessaire pour corriger l’awareness des skills assignés. ### Ordre d’implémentation recommandé 1. **Stabiliser le texte de contrat** - Rédiger une section courte “Usage des skills IdeA”. - Deux variantes : MCP (`idea_create_skill`) et fichier (`skill.create`). - Garder l’interdiction des subagents natifs dans la section orchestration existante. 2. **Modifier `compose_convention_file` uniquement** - Ajouter un helper pur si utile, par exemple `compose_skill_awareness(mcp_enabled: bool) -> &'static str`. - Ne pas déplacer la section `# Skills` ni changer la forme des bodies. 3. **Ajouter les tests de composition** - No-skills : pas de `# Skills`, awareness présente. - With-skills : awareness + bodies dans l’ordre. - MCP vs file : bonne consigne de création/contribution. 4. **Vérifier non-régression launch** - Tests ciblés application `agent/lifecycle`. - Puis `cargo test -p application agent` ou le subset existant pertinent. - Pas besoin de Vitest sauf si un texte UI est ajouté. 5. **Lot optionnel discovery** seulement après validation du MVP - Décider explicitement si un agent doit pouvoir lister les skills non assignés. - Si oui, implémenter `skill.list` / `idea_list_skills` comme nouvelle entrée orchestrateur compacte. ### Point d’attention Le fichier `ARCHITECTURE.md` contient encore des sections historiques sur un vieux plan `Builtin skill + idea CLI + inbox/outbox`, mais l’état réel du code est déjà sur `AskAgent`, `Reply`, sessions structurées, MCP bridge, mailbox/input mediator. Pour ce chantier, il faut suivre l’état réel du code et ne pas rouvrir l’ancien plan. Le bon axe est : **améliorer la conscience contextuelle des agents sur les skills existants**, pas refaire l’orchestration. - **Prompt:** Inventaire architecture des chantiers applicatifs restant en cours après intégration locale de `agent-skill-awareness-v2`. Références mémoire: `remaining-work-idea-agent-control-ide` liste comme suites possibles: registre session/singleton agent, live-state partagé projet, handoff/canonical conversation log cross-session/cross-profile, UX conversations/délégations, MCP IdeA-only en flux réel, mise à jour mémoire/contexte automatique, restrictions profils supportés. Merci de classer ce qui est déjà livré vs encore actif, choisir le prochain chantier à reprendre en premier, et fournir un cadrage architectural initial. Ne code pas. - **Response:** ## Inventaire post `agent-skill-awareness-v2` Contexte observé : la branche locale est `develop...origin/develop [ahead 15]`, avec seulement des fichiers runtime `.ideai/conversations/*` dirty. Je considère donc `agent-skill-awareness-v2` intégré localement et je reclasse depuis le code actuel, pas depuis la mémoire seule. ### Déjà livré / à considérer fondation acquise 1. **Registre session / singleton agent** - Livré côté application : `TerminalSessions`, `StructuredSessions`, agrégateur `LiveSessions`, `session_for_agent`, `node_for_agent`, `rebind_agent_node`. - Livré côté Tauri/UI : `list_live_agents`, `attach_live_agent`, guards frontend de lancement singleton. - Statut : **fondation livrée**, à durcir uniquement par tests de flux réel. 2. **Messagerie inter-agents / FIFO / réponse synchrone** - Livré : `AgentMailbox`, `InputMediator`, `AgentBusyChanged`, tickets, `idea_reply`, résolution par ticket, timeout/cancel. - `OrchestratorService::ask_agent` et `reply` existent, avec conversation par paire. - Statut : **livré applicativement**, UX encore perfectible. 3. **MCP IdeA-only en flux backend** - Livré : bridge `idea mcp-server`, endpoint app-tauri, serveur MCP, outils `idea_*`, runtime MCP injecté au launch, badge source `mcp/file` côté UI. - Statut : **livré côté infrastructure/app**, reste validation produit en flux réel et polish observabilité. 4. **Handoff / canonical conversation log cross-session / cross-profile** - Plus avancé que la mémoire ne le dit : `domain/src/conversation_log.rs` définit `ConversationLog`, `HandoffStore`, `HandoffSummarizer`, `ProviderSessionStore`. - Infra livrée : `FsConversationLog`, `FsHandoffStore`, `FsProviderSessionStore`, `HeuristicHandoffSummarizer`. - App livrée : `RecordTurn`, injection handoff au `LaunchAgent`, persistance provider session, séparation pair id IdeA vs engine session id. - Statut : **architecture et première implémentation livrées** ; reste qualité de résumé, couverture UX, audit de complétude de tous les chemins de record. 5. **Restrictions profils supportés** - Livré partiellement : profils structurés Claude/Codex, `structured_adapter`, `materializes_idea_bridge`, garde `guard_mcp_bridge_supported`, profils sélectionnables. - Statut : **règle technique présente**, reste formulation produit/UI des capacités et fallbacks. 6. **Agent skill awareness** - Après intégration locale : **livré comme couche de contexte**, sans nouveau store ni DTO. `agent-skills` reste L12, `awareness` reste composition de convention file. ### Encore actif / pas complètement produit 1. **UX conversations / délégations** - Backend riche, frontend encore dispersé entre agents/layout/terminal/chat/events. - Manque une lecture utilisateur claire : qui a demandé quoi, à qui, état FIFO, réponse reçue, conversation par paire, reprise. - Statut : **actif, prochain meilleur chantier**. 2. **Live-state partagé projet** - Il existe un live-state runtime : sessions, busy, mailbox, events. - Il n’existe pas encore un modèle applicatif unifié, consultable et éventuellement persistable, du “travail en cours” projet. - Statut : **partiellement livré en runtime, pas encore comme read-model produit**. 3. **Mise à jour mémoire/contexte automatique pendant la vie d’un agent** - Injection au launch : livrée. - Outils contexte/mémoire et FileGuard : présents. - Promotion automatique, signal/bruit, mise à jour continue : pas livrés. - Statut : **actif mais à repousser après UX**, car il faut d’abord rendre les fils et décisions visibles. 4. **Handoff/canonical log qualité produit** - Socle livré, mais pas encore “expérience de conversation retrouvée” garantie dans tous les cas. - Résumeur heuristique seulement ; UX de consultation/reprise à clarifier. - Statut : **fondation livrée, produit actif**. ## Prochain chantier recommandé Je recommande de reprendre en premier : **UX conversations & délégations, avec un read-model live-state minimal**. Raison : les briques backend existent maintenant. Continuer par mémoire automatique ou nouveaux mécanismes MCP risque d’empiler de l’invisible. Le prochain verrou produit est de rendre l’orchestration compréhensible et opérable : l’utilisateur doit voir ses “employés IA” travailler, attendre, déléguer, répondre et reprendre. Ce chantier peut aussi servir d’audit d’intégration : s’il manque un événement ou une donnée backend, on l’ajoute proprement comme read-model, au lieu de découvrir les trous plus tard. ## Cadrage architectural initial ### Objectif Fournir une vue produit unifiée des conversations et délégations d’un projet : - agents vivants et cellule hôte, - état `idle/busy/limited/starting` si disponible, - délégations en cours et en attente par agent, - dernière requête/réponse utile, - conversation `User↔Agent` ou `Agent↔Agent` associée, - capacité à ouvrir/rattacher la cellule concernée. Le but n’est pas encore de créer un “journal complet” frontend. Le journal canonique existe côté `.ideai/conversations`; le premier lot doit exposer un **état opérationnel scannable**. ### Frontière backend Préférer un use case de lecture applicatif plutôt que de faire recomposer le frontend depuis dix events. Nouveau read-model applicatif proposé : ```rust ProjectWorkState { live_agents: Vec, conversations: Vec, delegations: Vec, } ``` Port/domain à éviter au départ : pas de nouveau store durable tant qu’on lit les registres existants. Le read-model peut composer : - `LiveSessions` / `TerminalSessions` / `StructuredSessions`, - `InputMediator::busy_state`, - `ConversationRegistry`, - `AgentMailbox` si une méthode d’inspection propre est ajoutée, - `ConversationLog` / `HandoffStore` en lecture best-effort pour les résumés. Si inspection mailbox nécessaire, ne pas exposer l’impl `InMemoryMailbox` : ajouter un petit port ségrégué, par exemple `AgentQueueSnapshot`, ou étendre prudemment `AgentMailbox` avec une méthode read-only si l’impact reste faible. ### Frontière Tauri / DTO Ajouter une commande de lecture, pas une mutation : - `get_project_work_state(projectId) -> ProjectWorkStateDto` DTOs camelCase, stables et tolérants aux champs absents : - `LiveAgentStateDto { agentId, name, nodeId?, sessionId?, kind: "pty"|"structured", busy, limited? }` - `DelegationStateDto { ticketId, requesterAgentId?, targetAgentId, taskPreview, status: "queued"|"delivered"|"awaitingReply"|"replied"|"timedOut", source: "mcp"|"file"|"user"?, createdAtMs? }` - `ConversationThreadSummaryDto { conversationId, left, right, lastTurnPreview?, handoffPreview?, updatedAtMs? }` Ne pas exposer tout `log.jsonl` dans ce lot. Préviews seulement. ### Frontière frontend Ajouter une feature dédiée, sans mélanger avec `TerminalView` : - `frontend/src/features/workstate/` ou `features/conversations/` - hook `useProjectWorkState(projectId)` consommant un nouveau `WorkStateGateway` - panneau dans `ProjectsView` ou extension du sidebar Agents : “Work” / “Conversations” Le frontend doit continuer à consommer des ports TS, pas `invoke()` directement : - `domain/index.ts` : types miroirs DTO. - `ports/index.ts` : `WorkStateGateway`. - `adapters/workState.ts` : Tauri invoke. - mock gateway + tests Vitest. ### Ordre d’implémentation recommandé 1. **Lot A — read-model minimal sans nouvelle persistance** - Backend command `get_project_work_state` qui agrège live agents + busy state + conversations connues. - Frontend panneau simple affichant agents live/busy et dernières délégations connues. 2. **Lot B — queue/delegation snapshot propre** - Ajouter l’inspection read-only de mailbox/FIFO si le Lot A ne peut pas montrer les tickets en attente. - Tester FIFO visible : deux demandes vers le même agent apparaissent dans l’ordre. 3. **Lot C — conversation summaries depuis log/handoff** - Lire `ConversationLog::last` et `HandoffStore::load` best-effort. - Afficher preview et reprise sans charger tout le journal. 4. **Lot D — actions UX** - “Ouvrir/rattacher l’agent”, “voir la conversation”, “arrêter”, “copier résumé”, selon capacités déjà existantes. 5. **Lot E — mémoire/contexte automatique** - Seulement après visibilité des fils : proposer/persister les apprentissages utiles depuis les conversations vers mémoire/contexte via FileGuard. ### Contraintes à respecter - Ne pas faire du frontend la source de vérité du live-state. - Ne pas polluer `.ideai/memory/` avec le journal conversationnel. - Ne pas dupliquer les registres session existants. - Garder PTY brut et structured sessions substituables dans le read-model. - Toute écriture mémoire/contexte doit rester derrière les use cases/FileGuard existants. ## Synthèse Le cœur “agent control IDE” est maintenant largement livré côté backend : singleton, mailbox/FIFO, MCP IdeA-only, log/handoff/provider sessions. Le chantier prioritaire n’est plus d’inventer ces mécanismes, mais de les rendre visibles et pilotables. Je reprendrais donc par **UX conversations/délégations + read-model live-state projet minimal**, puis seulement ensuite la mise à jour automatique mémoire/contexte. - **Prompt:** Nous passons à l'implémentation du chantier `UX conversations/délégations + read-model live-state minimal`. Merci de transformer ton cadrage en périmètre de Lot A implémentable maintenant. Donne les contrats exacts backend/Tauri/frontend, les fichiers probables, ce qui est inclus/exclu, et les tests attendus. Objectif: premier lot utile, minimal, sans nouvelle persistance durable si possible. Ne code pas. - **Response:** ## Lot A — `ProjectWorkState` minimal, implémentable maintenant ### Objectif du lot Livrer une première vue utile et read-only du travail agent en cours dans un projet, sans nouvelle persistance durable : - lister les agents du projet avec leur état live, - indiquer cellule/session hôte quand l’agent tourne, - indiquer `idle` / `busy` à partir du médiateur d’entrée, - exposer le ticket courant si busy, - permettre au frontend d’afficher un panneau “Work” simple et scannable. Ce lot ne cherche pas encore à afficher l’historique, les previews de log, ni la file FIFO complète. Il pose le read-model minimal qui rend l’état agent visible. ## Inclus - Nouvelle commande Tauri read-only `get_project_work_state(projectId)`. - Agrégation backend depuis les stores/registres existants : manifest agents, `LiveSessions`, `InputMediator::busy_state`. - DTO frontend miroir + gateway TS. - Panneau frontend minimal dans la sidebar, probablement onglet `Work`. - Tests backend DTO/commande + tests frontend hook/panel. ## Exclus - Aucune nouvelle persistance `.ideai/`. - Pas de lecture `log.jsonl` / `handoff.md` dans le Lot A. - Pas d’inspection complète de la mailbox/FIFO. - Pas de mutation : pas de stop/reply/attach depuis ce panneau dans le premier lot. - Pas de migration de `list_live_agents` existant. - Pas de refonte des panneaux Agents/Terminal/Chat. ## Contrat backend application Le plus minimal peut rester dans `app-tauri` en composition de DTO, mais je recommande un petit use case application pour garder Tauri adapter mince. ### Nouveau module probable - `crates/application/src/workstate/mod.rs` - export dans `crates/application/src/lib.rs` ### Types application proposés ```rust pub struct GetProjectWorkState { contexts: Arc, live: Arc, input: Arc, } pub struct GetProjectWorkStateInput { pub project: Project, } pub struct ProjectWorkState { pub agents: Vec, } pub struct AgentWorkState { pub agent_id: AgentId, pub name: String, pub profile_id: ProfileId, pub live: Option, pub busy: AgentBusyState, } pub struct LiveWorkSession { pub node_id: NodeId, pub session_id: SessionId, pub kind: LiveSessionKind, } pub enum LiveSessionKind { Pty, Structured, } ``` ### Important sur `kind` `LiveSessions::live_agents()` agrège déjà PTY + structured mais ne porte pas le kind. Pour Lot A, deux options : 1. **Option minimale** : omettre `kind` du DTO. Suffisant pour afficher “live”. 2. **Option préférable** : ajouter une méthode read-only à `LiveSessions`, sans casser l’existant : ```rust pub fn live_agent_entries(&self) -> Vec pub struct LiveAgentEntry { pub agent_id: AgentId, pub node_id: NodeId, pub session_id: SessionId, pub kind: LiveSessionKind, } ``` Garder `live_agents()` existant pour compatibilité avec `list_live_agents`. ### Algorithme use case 1. `manifest = contexts.load_manifest(&project).await?` 2. Convertir chaque entry en `Agent` via `entry.to_agent()`. 3. Construire une map `agent_id -> live entry` depuis `LiveSessions`. 4. Pour chaque agent : - `busy = input.busy_state(agent.id)` - `live = live_map.get(agent.id)` - produire `AgentWorkState` 5. Trier par `name` ou conserver l’ordre manifeste. Recommandation : conserver ordre manifeste pour stabilité avec `list_agents`. ## Contrat Tauri ### Nouveau DTO dans `crates/app-tauri/src/dto.rs` ```rust #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct ProjectWorkStateDto { pub agents: Vec, } #[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: BusyStateDto, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct LiveWorkSessionDto { pub node_id: String, pub session_id: String, pub kind: LiveSessionKindDto, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum LiveSessionKindDto { Pty, Structured, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase", tag = "state")] pub enum BusyStateDto { Idle, Busy { ticket: String, since_ms: u64 }, } ``` Si l’équipe veut réduire encore le lot : supprimer `kind` et `LiveSessionKindDto`. ### Nouvelle commande dans `crates/app-tauri/src/commands.rs` ```rust #[tauri::command] pub async fn get_project_work_state( project_id: String, state: State<'_, AppState>, ) -> Result ``` Comportement : - `resolve_project(&project_id, &state).await?` - appeler `state.get_project_work_state.execute(...)` - mapper en DTO Erreurs : - `INVALID` si `projectId` invalide, - `NOT_FOUND` si projet inconnu, - `STORE` si manifeste illisible. ### Wiring `AppState` Fichiers probables : - `crates/app-tauri/src/state.rs` - ajouter `pub get_project_work_state: Arc`. - construire avec `IdeaiContextStore`, `LiveSessions::new(terminal_sessions, structured_sessions)`, `input_mediator`. - `crates/app-tauri/src/lib.rs` - enregistrer `commands::get_project_work_state` dans `invoke_handler`. ## Contrat frontend ### Types dans `frontend/src/domain/index.ts` ```ts export interface ProjectWorkState { agents: AgentWorkState[]; } export interface AgentWorkState { agentId: string; name: string; profileId: string; live?: LiveWorkSession | null; busy: WorkBusyState; } export interface LiveWorkSession { nodeId: string; sessionId: string; kind: "pty" | "structured"; } export type WorkBusyState = | { state: "idle" } | { state: "busy"; ticket: string; sinceMs: number }; ``` Si backend omet `kind`, retirer `kind` ici aussi. ### Port dans `frontend/src/ports/index.ts` ```ts export interface WorkStateGateway { getProjectWorkState(projectId: string): Promise; } export interface Gateways { // existants... workState: WorkStateGateway; } ``` ### Adapter Tauri Nouveau fichier : `frontend/src/adapters/workState.ts` ```ts export class TauriWorkStateGateway implements WorkStateGateway { getProjectWorkState(projectId: string): Promise { return invoke("get_project_work_state", { projectId }); } } ``` Puis wiring : - `frontend/src/adapters/index.ts` : instancier `workState: new TauriWorkStateGateway()`. - `frontend/src/adapters/mock/index.ts` : ajouter `MockWorkStateGateway`. ### Feature frontend Nouveau dossier recommandé : - `frontend/src/features/workstate/useProjectWorkState.ts` - `frontend/src/features/workstate/ProjectWorkStatePanel.tsx` - `frontend/src/features/workstate/index.ts` - `frontend/src/features/workstate/workstate.test.tsx` Hook : ```ts export interface ProjectWorkStateViewModel { state: ProjectWorkState | null; busy: boolean; error: string | null; refresh: () => Promise; } ``` Refresh initial + refresh sur events existants : - `agentLaunched` - `agentExited` - `agentBusyChanged` - `orchestratorRequestProcessed` - éventuellement `agentProfileChanged` ### UI minimale Dans `ProjectsView.tsx` : - ajouter `SidebarTab = ... | "work"` - ajouter `{ id: "work", label: "Work" }` - afficher `ProjectWorkStatePanel projectId={active.id}` si actif. Contenu minimal du panneau : - ligne par agent : nom, live/offline, busy/idle. - si live : afficher `sessionId` court ou `nodeId` court. - si busy : afficher ticket court + âge approximatif facultatif. - empty state : “No agents yet.” - erreur et bouton refresh. Pas d’actions dans Lot A. Le panneau est observabilité seulement. ## Fichiers probables Backend : - `crates/application/src/workstate/mod.rs` nouveau - `crates/application/src/lib.rs` - `crates/application/src/terminal/registry.rs` si ajout `live_agent_entries()` avec kind - `crates/app-tauri/src/dto.rs` - `crates/app-tauri/src/commands.rs` - `crates/app-tauri/src/state.rs` - `crates/app-tauri/src/lib.rs` Frontend : - `frontend/src/domain/index.ts` - `frontend/src/ports/index.ts` - `frontend/src/adapters/workState.ts` nouveau - `frontend/src/adapters/index.ts` - `frontend/src/adapters/mock/index.ts` - `frontend/src/features/workstate/useProjectWorkState.ts` nouveau - `frontend/src/features/workstate/ProjectWorkStatePanel.tsx` nouveau - `frontend/src/features/workstate/index.ts` nouveau - `frontend/src/features/workstate/workstate.test.tsx` nouveau - `frontend/src/features/projects/ProjectsView.tsx` ## Tests attendus ### Rust application Nouveau test application, selon conventions existantes : - `cargo test -p application workstate` Cas : 1. Projet avec deux agents, aucun live, aucun busy → deux rows `live=None`, `busy=Idle`. 2. Agent live PTY → row porte `live.session_id`, `live.node_id`, `kind=Pty` si kind inclus. 3. Agent busy → row porte `Busy { ticket, since_ms }`. 4. Agent absent du manifeste mais présent dans registry → ignoré dans le read-model projet. ### Rust app-tauri Selon structure actuelle, ajouter dans tests de wiring/commands : - commande enregistrée dans `invoke_handler`, - DTO sérialise en camelCase : `agentId`, `profileId`, `sinceMs`, `sessionId`, `nodeId`, `kind`. Commandes de vérification probables : - `cargo test -p app-tauri --test orchestrator_wiring` - ou test dédié si les commands sont déjà testées ailleurs. ### Frontend Vitest Nouveau `workstate.test.tsx` : 1. Le panneau affiche `No agents yet.` quand liste vide. 2. Il affiche agent idle/offline. 3. Il affiche agent live + busy avec ticket court. 4. Un event `agentBusyChanged` déclenche refresh. 5. `ProjectsView` expose l’onglet `Work` et rend le panneau pour le projet actif. Commande ciblée : ```bash npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx ``` ### Typecheck ```bash npx tsc --noEmit cargo check -p app-tauri ``` ## Notes de rigueur - Le Lot A doit rester read-only. Les actions “ouvrir/rattacher”, “stop”, “voir conversation” sont Lot D. - Ne pas lire `ConversationLog` dans Lot A : dès qu’on affiche des previews de conversation, on bascule vers Lot C. - Ne pas ajouter une persistance `live_state.json` : le live-state est dérivé des registres runtime existants. - Ne pas dupliquer `list_live_agents`; le nouveau read-model peut l’utiliser conceptuellement, mais il doit ajouter le busy state et le nom agent, donc il mérite sa commande dédiée. - Si `LiveSessions` est difficile à injecter tel quel, injecter les deux registres et agréger dans le use case, mais garder l’agrégation dans `application`, pas dans Tauri ni frontend. - **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. - **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.