Conversations live, MEMORY.md et note checkpoint workstate-delegation-queue-lot-b. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
82 KiB
82 KiB
upTo, objective
| upTo | objective |
|---|---|
| 998a4f67-a352-4e89-a5ee-71260d89f112 | CADRAGE ARCHITECTURE (pas de code) — Support de la délégation inter-agents (idea_ask_agent/idea_reply) pour les profils CODEX. |
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 Tauricancel_resume. GAP constaté sur le niveau 3 : l'événementagentRateLimitSuspected(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 typeset_resume_at(agentId, epochMs)qui arme le mêmeSessionLimitService(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::Humanexiste 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 deResumePlan::HumanFallbackdit : « 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 (cheminListResumableAgents) 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::Scheduledcouvrent déjà tout. Une heure saisie par l'utilisateur est fonctionnellement uneSessionLimitde sourceHumanavecresets_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 PASon_rate_limited(sémantique « signal de détection auto »). Ajoute une entrée dédiée, en réutilisant strictement la brancheScheduledexistante :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<String>, resets_at_ms: i64, // i64 nu, pas Option : la saisie EST l'heure )Corps = copie de la brancheResumePlan::Scheduleddeon_rate_limited(publishAgentRateLimited{Some}→disarm→scheduler.arm(ResumeAgent)→ mémoriser leScheduleId→ publishAgentResumeScheduled), avecSessionLimit::new(Some(resets_at_ms), now, RateLimitSource::Human). Factorise la branche en unfn 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_resumeetcancel_resumerestent 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 decancel_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ésoudrenode_idetconversation_idcôté backend depuis la registry (le front n'a que l'agent_id;agentRateLimitSuspectedne porte que ça) : -node_idviaTerminalSessions::node_for_agent(&id)(ou la registry unifiée). SiNone⇒ErrorDtoINVALID/NOT_FOUND (l'agent n'a plus de cellule vivante — la saisie n'a pas de cible). -conversation_idvia la session structuréesession_for_agent(&id).map(|s| s.conversation_id())(best-effortNonetoléré, comme tout le chemin de reprise dégradé). Puisstate.session_limit_service.confirm_human_resume(id, node_id, conv_id, resets_at_ms). Enregistre la commande danslib.rsà côté decancel_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 deAgentResumeScheduled(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) }puisAgentResumeScheduled { agent_id, fire_at_ms }. Le front les consomme déjà. La transition « suspecté → programmé » se fait donc sans code de présentation neuf :AgentResumeScheduledest le pivot qui retire l'état « heure inconnue » et réutilise le rendu nominal. Annulation ⇒AgentResumeCancelled(inchangé). Reprise ⇒AgentResumed(inchangé).AgentRateLimitSuspectedreste 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_resumeclampe ànow⇒ reprise quasi-immédiate. Test à ajouter. - Re-saisie / second suspect :confirm_human_resumedoit passer par le mêmedisarmque 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 parcancel_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 mergefeature/* → 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émentalhandoff.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 deidea_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écrithead.texten un seulhandle.write(encoder.encode(head.text)), attendsubmitDelayMspuis écrit\r; chemin headlesscrates/infrastructure/src/input/mod.rs::make_headless_sinkécrit aussitext.as_bytes()d'un coup puis submit.makeTerminalHandlesérialise déjà les invokes etPortablePtyAdapter::writefaitwrite_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 testsuseWritePortalqui 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 execdepuis le run dir isolé et doit permettre l’écriture dans le project root quand les permissions résolues donnent workspace-write. Proposition: faire transiterPreparedContext.project_root/équivalent depuisStructuredSessionFactory::startversCodexExecSession, puis ajouter--add-dir <projectRoot>à la lignecodex execen workspace-write, sans ajouter--ask-for-approvalcarcodex execle 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_rootdans un DTO/VO de contexte préparé (PreparedContext.project_rootou é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::startpeut 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 <projectRoot>dansCodexExecSessionest au bon endroit siCodexExecSessionest l’adapter infrastructure/presentation-backend spécifique à Codex CLI. Le flag--add-direst un détail technique de l’adapter Codex, donc il ne doit pas remonter dans le domaine. - Conditionner cet ajout àworkspace-writeest 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-approvalest correct sicodex execle 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 decodex exec,--add-dir,--sandbox, ni des noms de flags Codex. Ces détails restent dans l’adapter Codex. - Ne pas laisserCodexExecSessionrecalculer 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-dirdoit être ajouté uniquement pour leprojectRootvalidé/résolu du projet et uniquement quand le mode effectif estworkspace-write. - Ne pas confondrecwdet périmètre d’écriture : le cwd peut rester le run dir isolé pour l’exécution/session, tandis queproject_rootdevient 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 commeworkspace_roots,writable_roots,project_root, ouExecutionScope { cwd, project_root, sandbox_mode }. Puis l’adapter Codex traduitExecutionScope + workspace-writeencodex exec --add-dir <projectRoot>. - 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 diagnosticcrates/application/src/diag.rspour le rendez-vous inter-agents. - Ne pas stash/switch. Premier chantier logique: finirorchestrator-designationsur 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 courantorchestrator-designationa 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 jourarchitect.mdmaintenant ou attendre la fin de la stabilisation. Ne code pas. Reponds avecidea_replysi 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-designationest intégré localement surdevelop(develop ahead origin, AppImage 0.3.0 produite). Le prochain chantier recommandé par Git estfeature/agent-skill-awareness, mais il faut d'abord trancher son chevauchement avec une branche/initiativefeature/agent-skillsmentionnée dans les notes. Merci de cadrer précisément: 1) objectif produit/technique deagent-skill-awareness, 2) différence avecagent-skillssi 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é :developest ahead origin et contient déjà L12agent-skillsdans le code courant (Skill,SkillStore, CRUD, assignation agent↔skill, injection des bodies dans le convention file). La branchefeature/agent-skill-awarenessdiverge avant l’intégrationorchestrator-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 depuisdevelop. ## 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 parname + description; - outil MCP read-onlyidea_skill_read(name)pour charger le body complet à la demande ; - champ optionneldescriptionsurSkill, avec fallback déterministe viaeffective_description(); - rétrocompat des anciensskills/index.jsonsansdescription; - conserver un mode non-MCP compatible. Point à trancher avec Architecture : soit garder l’ancien dump complet pourmcp == None, soit intégrer cette découverte au futur binaire universelidea/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” danscompose_convention_file. Attention :ARCHITECTURE.mdrécent pousse une décision plus structurante : remplacer la prose libre d’orchestration par un skill built-inOrchestration 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 deagent-skill-awarenessdoit rester : “découverte et lecture des skills assignés”. ## 2) Différence avecfeature/agent-skillsagent-skillsvisible dans le repo = L12, déjà intégré fonctionnellement : - domaine :Skill,SkillScope::{Global, Project},SkillRef,Agent.skills/ManifestEntry.skills; - port :SkillStore; - infra :FsSkillStoreglobal + projet ; - application :CreateSkill,UpdateSkill,ListSkills,DeleteSkill,AssignSkillToAgent,UnassignSkillFromAgent; - backend Tauri + DTO ; - frontend :features/skills,SkillGateway, assignation dansAgentsPanel; - lancement :LaunchAgentrésout les skills assignés et injecte leurs bodies danscompose_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 viaidea_skill_read; - ajoutedescriptioncomme méta courte, pas une nouvelle famille de skills. Donc ce n’est pas un doublon deagent-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 : - ajouterdescription: Option<String>àSkillavec#[serde(default)]; - ajouterSkill::with_descriptionetSkill::effective_description(); - préserverwith_content()en conservant la description ; - éventuellement ajouterOrchestratorCommand::ReadSkill { name, requester? }et actionskill.readdansOrchestratorRequest::validatesi on garde le routage MCP via modèle orchestrator. Backend Application : -CreateSkillInputreçoitdescription: Option<String>; -UpdateSkilldoit idéalement pouvoir modifierdescriptionaussi, pas seulementcontent, sinon le frontend ne peut pas éditer la méta ; - créerReadSkilluse case read-only sur le port existantSkillStore, 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 : - modifiercompose_convention_filepour les profils MCP : section# Skills disponiblesen amont, lignes déterministes**name** — description, mentionnantidea_skill_read(name=...); - garder ordre déterministe des skills assignés selon le manifest ; - décider comportement non-MCP. Option conservatrice : garder le dump# Skillscomplet seulement en non-MCP pour zéro régression. Option cible architecture : passer par skill built-in + futur CLIidea, mais c’est un autre lot. Infrastructure / MCP : - ajouteridea_skill_readau catalogue MCP et au mapping tool →OrchestratorCommand::ReadSkill; - dispatcher dansOrchestratorServiceversReadSkill, retour inline Markdown ; - câbler dansstate.rs/composition root ; - mettre à jour les tests de compteur/catalogue MCP, actuellement sensibles aux nombres fixes. App-Tauri DTO/commands : -SkillDtobénéficie du champ automatiquement siSkillsérialise camelCase ; -CreateSkillRequestDtoetUpdateSkillRequestDtodoivent porterdescription?: string | null; - commandes UI existantescreate_skill/update_skillrestent les mêmes noms. Frontend : -domain Skillajoutedescription?: string | null; -CreateSkillInputajoutedescription?: string;updateSkilldoit permettre de passer description + content, pas seulement content ; -SkillEditorajoute un champ court “Description” ; en edit, description éditable ; -SkillsPanelpeut 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.mdou note dédiée : préciser que L12 crée/assigne/injecte, et queagent-skill-awarenessajoute description + affordance + read tool. ## 4) Contrats / ports / DTO À créer/modifier : -domain::Skill { description: Option<String> }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 implslist/get/savetransportent simplement le champ. -FsSkillStoreindex : ajouterdescriptiondansIndexEntry,#[serde(default)], écriture camelCase. Le body reste dansmd/<id>.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 completdescription + contentpour rester aligné UI. - nouveau use caseReadSkill { SkillStore }: input{ name, project_root }, outputMarkdownDocou DTO{ name, scope, contentMd }si on veut plus de traçabilité. Branche historique renvoyait seulementMarkdownDoc; acceptable pour MCP. -OrchestratorRequest: action/typeskill.read, champnamerequis. -OrchestratorCommand::ReadSkill { name, requester? }. Lerequesterest utile si la couche MCP veut garder la symétrie avec context/memory, mais le use case n’en a pas besoin. - MCP toolidea_skill_read: input{ name: string }, output contenu Markdown inline. - DTO Tauri/TS :Skill.description?: string | null, create/update requests. À ne pas créer : - pas deSkillAwarenessStore; - 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 deorchestrator-designation; merge brut très risqué ; - conflit conceptuel avec la décision architectureSkillScope::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 depuisdevelop, par exemplefeature/agent-skill-awareness-v2; ne cherry-pick que si les hunks sont triviaux, sinon réimplémenter depuis la note. Garderfeature/agent-skill-awarenesscomme référence de design/test, pas comme base de merge. 2. DevBackend T1 : domaine + serde + tests purs (description, fallback,with_contentconserve description, legacy JSON). 3. DevBackend T2 :FsSkillStore, use cases create/update/list, DTO Tauri. Tests store legacy + roundtrip. 4. DevBackend T3 :ReadSkill+OrchestratorCommand::ReadSkill+ validationskill.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_fileawareness. Tests : MCP avec skills affiche# Skills disponibles, descriptions, appelidea_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 futurSkillScope::Builtin. 9. QA T8 :cargo test --workspace,pnpm test, puis e2e AppImage : agent neuf + skill assigné + profil MCP voit# Skills disponibles, appelleidea_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 depuisdevelop, avec périmètre serré et sans avaler le vieux diff. - Prompt: Reprise chantier
feature/agent-skill-awareness-v2après hotfix livraison délégation/logs submit. Contexte: branche actuellefeature/agent-skill-awareness-v2; dirty attendu avec fichiers runtime.ideai/*et changements code du hotfix listés dans la mémoirecheckpoint-delivery-submit-logging-fix. Le checkpoint précédent disait de cadreragent-skill-awarenessvsagent-skillsavant 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, scopesGlobal|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# Skillssont 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_skillcôté MCP, ouskill.createcô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 flouawarenessvsskills: 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 deProject root, orchestration, contexte projet, persona, skills, mémoire, handoff. - Ajouter une sous-section courte, stable et testable, par exemple dans# Orchestration IdeAou 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# Skillssi 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 outilsidea_create_skill/ outils natifs IdeA. -mcp_enabled=false: mentionner le protocole fichierskill.create. À ne pas toucher pour le MVP : - Pas de nouveauSkillStore. - Pas de nouveau port domaine. - Pas de nouveau DTO Tauri. - Pas de mutation du manifeste. - Pas de changement des invariantsSkill,SkillRef,Agent.skills. - Pas de scopeBuiltintant 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éintroduireBuiltinserait 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/deletereste la seule abstraction de persistance. -LaunchAgent::resolve_skillsreste best-effort pour les dangling refs : un skill supprimé ne bloque pas le launch. -compose_convention_filereste 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<SkillScope> }ouListSkillsavec scope requis. - Alias protocole :skill.list. - Outil MCP :idea_list_skills(scope?). -OrchestratorServiceréutilise leSkillStoreexistant et renvoie une liste compacte{ id, name, scope }, pascontentMdpar défaut. Ce serait un lot séparé, parce que cela touchedomain/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. Modifiercompose_convention_fileuniquement - Ajouter un helper pur si utile, par exemplecompose_skill_awareness(mcp_enabled: bool) -> &'static str. - Ne pas déplacer la section# Skillsni 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 applicationagent/lifecycle. - Puiscargo test -p application agentou 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émenterskill.list/idea_list_skillscomme nouvelle entrée orchestrateur compacte. ### Point d’attention Le fichierARCHITECTURE.mdcontient encore des sections historiques sur un vieux planBuiltin skill + idea CLI + inbox/outbox, mais l’état réel du code est déjà surAskAgent,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-ideliste 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-v2Contexte observé : la branche locale estdevelop...origin/develop [ahead 15], avec seulement des fichiers runtime.ideai/conversations/*dirty. Je considère doncagent-skill-awareness-v2inté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égateurLiveSessions,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_agentetreplyexistent, avec conversation par paire. - Statut : livré applicativement, UX encore perfectible. 3. MCP IdeA-only en flux backend - Livré : bridgeidea mcp-server, endpoint app-tauri, serveur MCP, outilsidea_*, runtime MCP injecté au launch, badge sourcemcp/filecô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.rsdéfinitConversationLog,HandoffStore,HandoffSummarizer,ProviderSessionStore. - Infra livrée :FsConversationLog,FsHandoffStore,FsProviderSessionStore,HeuristicHandoffSummarizer. - App livrée :RecordTurn, injection handoff auLaunchAgent, 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, gardeguard_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-skillsreste L12,awarenessreste 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, - étatidle/busy/limited/startingsi disponible, - délégations en cours et en attente par agent, - dernière requête/réponse utile, - conversationUser↔AgentouAgent↔Agentassocié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<LiveAgentState>, conversations: Vec<ConversationThreadSummary>, delegations: Vec<DelegationState>, }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, -AgentMailboxsi une méthode d’inspection propre est ajoutée, -ConversationLog/HandoffStoreen lecture best-effort pour les résumés. Si inspection mailbox nécessaire, ne pas exposer l’implInMemoryMailbox: ajouter un petit port ségrégué, par exempleAgentQueueSnapshot, ou étendre prudemmentAgentMailboxavec 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) -> ProjectWorkStateDtoDTOs 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 toutlog.jsonldans ce lot. Préviews seulement. ### Frontière frontend Ajouter une feature dédiée, sans mélanger avecTerminalView: -frontend/src/features/workstate/oufeatures/conversations/- hookuseProjectWorkState(projectId)consommant un nouveauWorkStateGateway- panneau dansProjectsViewou extension du sidebar Agents : “Work” / “Conversations” Le frontend doit continuer à consommer des ports TS, pasinvoke()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 commandget_project_work_statequi 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 - LireConversationLog::lastetHandoffStore::loadbest-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 —
ProjectWorkStateminimal, 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, - indiqueridle/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-onlyget_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 ongletWork. - Tests backend DTO/commande + tests frontend hook/panel. ## Exclus - Aucune nouvelle persistance.ideai/. - Pas de lecturelog.jsonl/handoff.mddans 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 delist_live_agentsexistant. - Pas de refonte des panneaux Agents/Terminal/Chat. ## Contrat backend application Le plus minimal peut rester dansapp-taurien 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 danscrates/application/src/lib.rs### Types application proposésrust pub struct GetProjectWorkState { contexts: Arc<dyn AgentContextStore>, live: Arc<LiveSessions>, input: Arc<dyn InputMediator>, } pub struct GetProjectWorkStateInput { pub project: Project, } pub struct ProjectWorkState { pub agents: Vec<AgentWorkState>, } pub struct AgentWorkState { pub agent_id: AgentId, pub name: String, pub profile_id: ProfileId, pub live: Option<LiveWorkSession>, pub busy: AgentBusyState, } pub struct LiveWorkSession { pub node_id: NodeId, pub session_id: SessionId, pub kind: LiveSessionKind, } pub enum LiveSessionKind { Pty, Structured, }### Important surkindLiveSessions::live_agents()agrège déjà PTY + structured mais ne porte pas le kind. Pour Lot A, deux options : 1. Option minimale : omettrekinddu 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<LiveAgentEntry> pub struct LiveAgentEntry { pub agent_id: AgentId, pub node_id: NodeId, pub session_id: SessionId, pub kind: LiveSessionKind, }Garderlive_agents()existant pour compatibilité aveclist_live_agents. ### Algorithme use case 1.manifest = contexts.load_manifest(&project).await?2. Convertir chaque entry enAgentviaentry.to_agent(). 3. Construire une mapagent_id -> live entrydepuisLiveSessions. 4. Pour chaque agent : -busy = input.busy_state(agent.id)-live = live_map.get(agent.id)- produireAgentWorkState5. Trier parnameou conserver l’ordre manifeste. Recommandation : conserver ordre manifeste pour stabilité aveclist_agents. ## Contrat Tauri ### Nouveau DTO danscrates/app-tauri/src/dto.rsrust #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct ProjectWorkStateDto { pub agents: Vec<AgentWorkStateDto>, } #[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<LiveWorkSessionDto>, 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 : supprimerkindetLiveSessionKindDto. ### Nouvelle commande danscrates/app-tauri/src/commands.rsrust #[tauri::command] pub async fn get_project_work_state( project_id: String, state: State<'_, AppState>, ) -> Result<ProjectWorkStateDto, ErrorDto>Comportement : -resolve_project(&project_id, &state).await?- appelerstate.get_project_work_state.execute(...)- mapper en DTO Erreurs : -INVALIDsiprojectIdinvalide, -NOT_FOUNDsi projet inconnu, -STOREsi manifeste illisible. ### WiringAppStateFichiers probables : -crates/app-tauri/src/state.rs- ajouterpub get_project_work_state: Arc<GetProjectWorkState>. - construire avecIdeaiContextStore,LiveSessions::new(terminal_sessions, structured_sessions),input_mediator. -crates/app-tauri/src/lib.rs- enregistrercommands::get_project_work_statedansinvoke_handler. ## Contrat frontend ### Types dansfrontend/src/domain/index.tsts 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 ometkind, retirerkindici aussi. ### Port dansfrontend/src/ports/index.tsts export interface WorkStateGateway { getProjectWorkState(projectId: string): Promise<ProjectWorkState>; } export interface Gateways { // existants... workState: WorkStateGateway; }### Adapter Tauri Nouveau fichier :frontend/src/adapters/workState.tsts export class TauriWorkStateGateway implements WorkStateGateway { getProjectWorkState(projectId: string): Promise<ProjectWorkState> { return invoke<ProjectWorkState>("get_project_work_state", { projectId }); } }Puis wiring : -frontend/src/adapters/index.ts: instancierworkState: new TauriWorkStateGateway(). -frontend/src/adapters/mock/index.ts: ajouterMockWorkStateGateway. ### 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.tsxHook :ts export interface ProjectWorkStateViewModel { state: ProjectWorkState | null; busy: boolean; error: string | null; refresh: () => Promise<void>; }Refresh initial + refresh sur events existants : -agentLaunched-agentExited-agentBusyChanged-orchestratorRequestProcessed- éventuellementagentProfileChanged### UI minimale DansProjectsView.tsx: - ajouterSidebarTab = ... | "work"- ajouter{ id: "work", label: "Work" }- afficherProjectWorkStatePanel projectId={active.id}si actif. Contenu minimal du panneau : - ligne par agent : nom, live/offline, busy/idle. - si live : affichersessionIdcourt ounodeIdcourt. - 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.rsnouveau -crates/application/src/lib.rs-crates/application/src/terminal/registry.rssi ajoutlive_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.rsFrontend : -frontend/src/domain/index.ts-frontend/src/ports/index.ts-frontend/src/adapters/workState.tsnouveau -frontend/src/adapters/index.ts-frontend/src/adapters/mock/index.ts-frontend/src/features/workstate/useProjectWorkState.tsnouveau -frontend/src/features/workstate/ProjectWorkStatePanel.tsxnouveau -frontend/src/features/workstate/index.tsnouveau -frontend/src/features/workstate/workstate.test.tsxnouveau -frontend/src/features/projects/ProjectsView.tsx## Tests attendus ### Rust application Nouveau test application, selon conventions existantes : -cargo test -p application workstateCas : 1. Projet avec deux agents, aucun live, aucun busy → deux rowslive=None,busy=Idle. 2. Agent live PTY → row portelive.session_id,live.node_id,kind=Ptysi kind inclus. 3. Agent busy → row porteBusy { 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 dansinvoke_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 Nouveauworkstate.test.tsx: 1. Le panneau afficheNo agents yet.quand liste vide. 2. Il affiche agent idle/offline. 3. Il affiche agent live + busy avec ticket court. 4. Un eventagentBusyChangeddéclenche refresh. 5.ProjectsViewexpose l’ongletWorket 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### Typecheckbash 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 lireConversationLogdans Lot A : dès qu’on affiche des previews de conversation, on bascule vers Lot C. - Ne pas ajouter une persistancelive_state.json: le live-state est dérivé des registres runtime existants. - Ne pas dupliquerlist_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. - SiLiveSessionsest difficile à injecter tel quel, injecter les deux registres et agréger dans le use case, mais garder l’agrégation dansapplication, pas dans Tauri ni frontend. - Prompt: Lot A
ProjectWorkStateminimal est intégré localement dansdevelop(3bfb932). Cadrage demandé pour le Lot B du chantierUX 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 deAgentMailbox/InputMediator, DTOs, impacts frontend, inclus/exclus, tests attendus. Ne code pas. - Response: ## Lot B — Queue / Delegation Snapshot Read-Only Objectif : enrichir
ProjectWorkStateavec les tickets actuellement présents dans la FIFO par agent, en lecture seule, sans nouvelle persistance durable et sans changer la sémantiqueenqueue/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::Ticketporte déjàid,source,conversation,requester,task. -infrastructure::InMemoryMailboxpossède déjà lesVecDeque<Slot>parAgentId. -InputMediator::busy_state(agent)reste la source de vérité du ticket en cours. Donc : - queue/order = snapshot deInMemoryMailbox; - status inProgress/queued = dérivé en croisant la queue avecInputMediator::busy_state; - live session = déjà Lot A viaLiveSessions; - manifest boundary = toujoursAgentContextStore.load_manifest(project). Ne pas reconstruire une deuxième FIFO dansProjectWorkState. ## Extension read-only recommandée Ne pas étendreInputMediator: il est l’autorité busy/livraison, pas l’autorité de contenu de queue. Éviter aussi de grossir le port mutateurAgentMailboxsi possible. Ajouter un port ségrégué read-only dansdomain/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<QueuedTicketSnapshot>; }ImplémenterAgentQueueSnapshotpourInMemoryMailboxen clonant uniquement les données deTicket, jamais lesoneshot::Sender. Pourquoi ce choix : - ISP : le read-model ne dépend pas deenqueue/resolve/cancel. - Aucun changement de comportement FIFO. - Les tests peuvent fournir un fakeAgentQueueSnapshotsans implémenter toutAgentMailbox. Alternative acceptable si l’équipe préfère moins de types : ajouterfn snapshot_for(&self, agent: AgentId) -> Vec<QueuedTicketSnapshot>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. ÉtendreGetProjectWorkState:rust pub struct GetProjectWorkState { contexts: Arc<dyn AgentContextStore>, live: Arc<LiveSessions>, input: Arc<dyn InputMediator>, queue: Arc<dyn AgentQueueSnapshot>, }Étendre les structs :rust pub struct AgentWorkState { pub agent_id: AgentId, pub name: String, pub profile_id: ProfileId, pub live: Option<LiveWorkSession>, pub busy: AgentBusyState, pub tickets: Vec<AgentTicketState>, } 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. Gardertask_lenpour indiquer qu’il y a plus. Important : conserver l’ordre FIFO viaposition, ne pas retrier les tickets. ## Tauri / DTO Étendrecrates/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<LiveWorkSessionDto>, pub busy: AgentBusyState, pub tickets: Vec<AgentTicketStateDto>, } #[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) -> ProjectWorkStateDtoAucune nouvelle commande nécessaire. ## Wiring AppState Danscrates/app-tauri/src/state.rs: - garder l’Arc<InMemoryMailbox>déjà créé au composition root ; - le passer àGetProjectWorkState::new(...)commeArc<dyn AgentQueueSnapshot>; - continuer à passer le mêmeArc<InMemoryMailbox>auMediatedInboxet à l’orchestrateur commeAgentMailbox. But : un seul objetInMemoryMailbox, deux vues de port :rust let inmemory_mailbox = Arc::new(InMemoryMailbox::new()); let mailbox = Arc::clone(&inmemory_mailbox) as Arc<dyn AgentMailbox>; let queue_snapshot = Arc::clone(&inmemory_mailbox) as Arc<dyn AgentQueueSnapshot>;Puis :rust GetProjectWorkState::new(contexts, live_sessions, input_mediator, queue_snapshot)## Frontend contracts Étendrefrontend/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[]; }WorkStateGatewayne change pas :ts getProjectWorkState(projectId: string): Promise<ProjectWorkState>;Adapter Tauri inchangé sauf types. Mock :MockWorkStateGatewayaccepte désormaisticketsdans ses seeds. Pour compat tests, soit mettretickets: []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 : - badgeLive/Offline,Busy/Idleexistant ; - sitickets.length > 0, une mini-liste compacte : -#1 In progressou#2 Queued, - requester :Humanou label requester / source agent, - preview du task, - ticket court en monospace. UX minimale : - Agent sans ticket : rien ou texte discret “No queued tickets”. - TicketinProgress: badge warning, cohérent avecBusy. - Ticketqueued: 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 surdelegationReady, 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 nouvelagentBusyChangednidelegationReadyimmédiatement. Il sera visible au refresh manuel ou au prochain événement. Une vraie notificationagentQueueChangedest un Lot B+ ou Lot C, pas indispensable pour le snapshot read-only. ## Inclus - Port read-onlyAgentQueueSnapshot. - Impl snapshot deInMemoryMailbox, sans mutation. - Extension deProjectWorkStateavecagents[].tickets. - StatutsinProgress/queueddé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 deagentQueueChangeddans 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 lectureConversationLog/handoff.md. - Pas d’historique des tickets terminés. - Pas de statutdelivered:delegation_deliveredest 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 cheminsask_agent,submit_human_input,idea_reply. ## Point de vigilance technique Le snapshot va rendre visibles les tickets réellement restés dansInMemoryMailbox. 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èsidea_replyPTY : ticket retiré ; - timeout/cancel : ticket retiré parBusyTurnGuard; - succès structuredFinal: 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 dontsource == 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.rsTests : 1.snapshot_preserves_fifo_order_and_positions: deux tickets A→B, positions0,1. 2.snapshot_carries_ticket_metadata: source, conversation, requester, task présents. 3.snapshot_is_read_only: appeler snapshot ne change paspendingnihead_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 position0. ### Application workstate Fichier :crates/application/tests/workstate.rs. Tests : 1. Agent sans queue →tickets == []. 2. Deux tickets pour un agent manifeste →ticketsdans l’ordre FIFO. 3.busy.ticket == first→ premier ticketInProgress, secondQueued. 4.busy == Idle→ tous ticketsQueued. 5. Queue pour agent absent du manifeste → ignorée. 6.task_previewtronque ettask_lenconserve la longueur originale. 7. Source agent → DTO application contientTicketWorkSource::Agent { agent_id }; source human →Humansi inclus. ### App-tauri DTO / command Fichiers : -crates/app-tauri/src/dto.rs- tests existants typedto_agents.rsou nouveau test DTO. Tests : 1. Sérialisation camelCase :ticketId,conversationId,requesterLabel,taskPreview,taskLen. 2. Enum status sérialisée eninProgress/queued. 3. Source agent sérialisée en{ "kind": "agent", "agentId": "..." }. 4.get_project_work_staterenvoieticketsimbriqués avec les agents. ### Frontend Fichier :frontend/src/features/workstate/workstate.test.tsx. Tests : 1. Agent avec ticketinProgressaffiche badgeIn 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. EventdelegationReadydéclenche un refresh. 5. Mock gateway accepte et renvoietickets. 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 :InMemoryMailboxexpose une vue snapshot,GetProjectWorkStatel’agrège par agent manifeste, Tauri sérialisetickets, 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 deProjectWorkStateou 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 toutlog.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
Workavec une vue lisible des fils de conversation associés aux tickets/live work, sans exposer ni charger toutlog.jsonl. Inclus : - afficher, pour chaqueconversationIdvisible dans le work-state, un résumé compact issu duhandoff.mdquand il existe ; - compléter par un aperçu borné des derniers tours duConversationLogquand 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 delive/busy/ticketssi 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/<conversationId>/handoff.md). Source durable secondaire :ConversationLog(.ideai/conversations/<conversationId>/log.jsonl) uniquement via une lecture bornée (last(n)), pour construire une preview courte quand le handoff est absent ou inutilisable.ConversationRegistryne 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 : lesconversation_iddéjà présents dans lesQueuedTicketSnapshotprojetés parGetProjectWorkState. En option additive, si le snapshot live expose déjà unconversation_idstable 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 existantsConversationLogetHandoffStore; - ajouter seulement des value objects purs de read-model si nécessaire :ConversationWorkSummary,ConversationPreviewStatus, éventuellementConversationTurnPreview; - aucun accès FS, aucun Tauri, aucun type UI. Application : - étendreGetProjectWorkStateavec un collaborateur de lecture de previews, ou extraire un petit service applicatifConversationPreviewReaderutilisé parGetProjectWorkState; - dépendances injectées :Arc<dyn HandoffStore>etArc<dyn ConversationLog>, ouOption<Arc<dyn ConversationPreviewPort>>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éutiliserFsHandoffStoreetFsConversationLogdéjà câblés sous<project_root>/.ideai/conversations/; - siFsHandoffStore::loadrenvoie une erreur de parsing, l’application la convertit en previewunavailable/partial, pas enAppErrorglobal ; -FsConversationLog::lastest déjà tolérant aux lignes corrompues, donc c’est le bon fallback. Tauri : - garder la commande existanteget_project_work_state; - faire évoluerProjectWorkStateDtoadditivement ; - ne pas créerget_conversation_logni endpoint brut. Frontend : - types TS miroir dans le domaine UI ; - adapter Tauri inchangé côté méthode (getProjectWorkState) ; - mock gateway normaliseconversations: []pour compat tests ; - composantProjectWorkStatePanelmappeconversationId -> summaryet affiche une ligne compacte par ticket/thread. ### DTO recommandé Préférence : étendreProjectWorkStateavec une liste top-level de résumés, référencée parconversationId. Ça évite de dupliquer le résumé sur plusieurs tickets du même fil. Rust application :rust pub struct ProjectWorkState { pub agents: Vec<AgentWorkState>, pub conversations: Vec<ConversationWorkSummary>, } pub struct ConversationWorkSummary { pub conversation_id: ConversationId, pub status: ConversationPreviewStatus, pub objective_preview: Option<String>, pub summary_preview: Option<String>, pub summary_len: usize, pub up_to: Option<TurnId>, pub recent_turns: Vec<ConversationTurnWorkPreview>, } 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; }AgentTicketStatene change pas : il porte déjàconversationId,taskPreview,taskLen,status,requesterLabel. Le frontend joint viaconversationId. Compatibilité : les mocks et tests peuvent normaliserconversationsà[]quand absent, comme ils le font déjà pourtickets. ### 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 parconversationIdunique : 1. tenterhandoffs.load(conversation); 2. siOk(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. siOk(None):status = missing, puis tenterlog.last(conversation, 3); 4. si handoff illisible maislog.lastmarche :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 auProjectWorkStateglobal. Normalisation : même règle quetask_previewLot B : trim + collapse whitespace + troncature à la borne, jamais mid-codepoint. On peut factoriser un helper applicatif privé dansworkstate. Important : ne pas appelerConversationLog::read(None)dans Lot C. Le fallback doit êtrelast(RECENT_TURNS_MAX)uniquement. ### UI minimale DansProjectWorkStatePanel: - garder la structure actuelle agent > tickets ; - sous chaque ticket, afficher une ligne thread compacte si un résumé existe : - objectif si présent :Goal: <objectivePreview>; - sinon résumé :<summaryPreview>; - sinon dernier tour :Last: <textPreview>; - 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 ; - siconversationIdn’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 lesconversationIdde plusieurs tickets ; - handoff présent produitready,objectivePreview,summaryPreview,summaryLen,upTo; - handoff absent + log présent produitmissingavecrecentTurnsborné ; - handoff corrompu + log lisible produitpartial; - handoff/log indisponibles produitunavailablesansAppErrorglobal ; - previews tronquées et whitespace normalisé. Infrastructure (crates/infrastructure/tests/conversation_log.rs) : - normalement peu ou pas de nouveau test requis siFsConversationLog::lastetFsHandoffStore::loadsont 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 deconversations; - conversion des statutsready/missing/partial/unavailable; - compat additive avec agents/tickets existants. Frontend (frontend/src/features/workstate/workstate.test.tsx) : - mock sansconversationsnormalisé à[]; - ticket avecconversationIdaffiche objectif/résumé ; - statuspartial/unavailablene masque pas le ticket ; - refresh existant suragentBusyChanged/delegationReadycontinue. Commandes :bash cargo test -p application workstate cargo test -p app-tauri dto_agents cargo test -p infrastructure conversation_log npm --prefix frontend test -- workstateSelon 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 deProjectWorkState, pas une nouvelle surface d’action. Le backend reste source d’autorité pour les bornes et la robustesse ; le frontend ne fait que joindretickets[].conversationIdversstate.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.