Files
IdeA/.ideai/conversations/7a2f446c-2efa-0c56-08dd-07cdc5cac19b/handoff.md

85 KiB
Raw Blame History

upTo, objective
upTo objective
fc80726d-b0ec-4747-bea9-a5287ed9526e 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: Cadrage architecture — nouvelle feature « Gestion des limites de session des agents » (détection + reprise auto). Étape 1 du cycle §3, AVANT tout code. Produis la cartographie : frontières domaine/application/infra, ports/contrats à créer ou étendre, et l'arborescence des fichiers touchés. Mets aussi à jour ARCHITECTURE.md. CONTEXTE PRODUIT (verrouillé avec l'utilisateur le 2026-06-16) : Besoin : IdeA doit savoir quand un agent est en limite de session ET jusqu'à quelle heure, puis lui demander de reprendre où il en était une fois la limite levée. Priorités : (1) SOLIDE — pas de bidouille, marche dans ~100% des cas même pour un novice ; (2) si possible sans dépendance au modèle de l'agent. CONSTAT DUR à intégrer : l'heure exacte de reset n'existe nulle part de façon universelle (ni OS, ni code de sortie, ni API inter-modèles). Elle est fabriquée par le fournisseur et seulement exposée dans le flux de sa CLI. Donc « 100% fiable + zéro dépendance modèle + heure exacte » sont incompatibles simultanément. SOLUTION RETENUE — détecteur HIÉRARCHIQUE calqué sur la hiérarchie de readiness existante (domain/readiness.rs) : - Niveau 1 (solide, structuré) : l'adapter structuré extrait limite + reset du flux machine. Pour Claude, rate_limit_event.rate_limit_info est DÉJÀ parsé dans infrastructure/session/claude.rs (~ligne 90) mais jeté (réduit à Heartbeat) — il suffit de lire le timestamp de reset (resetsAt) au lieu de le dropper. - Niveau 2 (déclaratif, configurable) : champ de profil rate_limit_pattern (regex + groupe de capture pour l'heure) pour agents PTY/TUI sans adapter structuré, dans la lignée des profils déclaratifs §9 (domain/profile.rs). - Niveau 3 (filet humain) : si rien ne matche mais agent Stalled (variante DÉJÀ prévue dans domain/readiness.rs), IdeA DEMANDE à l'utilisateur. Garantit le « 100% même pour un novice » : jamais d'inaction silencieuse. MODEL-AGNOSTIC tenu AU DOMAINE : le domaine ne connaît que quelque chose comme RateLimited { until: Option<Instant> }. Tout le savoir spécifique modèle reste confiné aux adapters/profils. REPRISE (model-agnostique, briques existantes) : pivot sur le conversation_id du moteur + --resume natif, déjà câblés (session/claude.rs build_spawn_line + application/agent/resume.rs ListResumableAgents). Le --resume porte tout l'historique → pas de reconstruction manuelle. Le SessionInspector (infrastructure/inspector/claude.rs) fournit le « dernier sujet » pour l'UI. Un planificateur arme un réveil sur le port Clock (infrastructure/clock/) à resets_at puis relance via SessionPlan::Resume + prompt de reprise court. DÉCISIONS PRODUIT VERROUILLÉES : - Reprise : AUTOMATIQUE à l'heure de reset, ANNULABLE (fenêtre + notification UI). - Couverture : les TROIS niveaux d'emblée (y compris le repli regex niveau 2). - État : EN MÉMOIRE uniquement — PAS de persistance de SessionLimit. Conséquence assumée : le réveil auto ne joue que tant qu'IdeA reste ouvert ; si l'IDE est fermé/rouvert après le reset, le chemin existant ListResumableAgents (agent_was_running/conversation_id) prend le relais. DÉCOUPAGE PRESSENTI (à valider/affiner par toi) : 1) domaine (variante ReplyEvent::RateLimited, ReadinessSignal::RateLimited, type SessionLimit, état/statut agent RateLimited) ; 2) adapter Claude (extraire resetsAt) ; 3) profil (champ rate_limit_pattern) ; 4) application (service de planification de reprise sur le port Clock, annulable) ; 5) UI front (badge « limité jusqu'à HH:MM » + filet humain de confirmation). Livrable attendu : la cartographie complète (ports à créer/étendre avec leurs signatures, où vit chaque pièce, fichiers à créer/modifier, lots ordonnés pour le cycle dev/test), + ARCHITECTURE.md mis à jour. Signale tout point où ma solution entre en tension avec l'archi hexagonale existante.
  • Response: # Cadrage architecture validé — « Gestion des limites de session des agents » Cartographie produite et ARCHITECTURE.md mis à jour (nouvelle §21). J'ai vérifié ta solution contre le code réel (readiness.rs, session/claude.rs, profile.rs, ports.rs, input.rs, resume.rs, clock, events.rs). Elle est architecturalement saine ; je valide les 5 niveaux, avec 5 tensions hexagonales corrigées (à respecter au dev). ## Frontières (où vit quoi) - Domaine (pur) : le fait neutre « limité, reset à T (peut-être) ». Rien de spécifique modèle. - Infra (adapters) : forme du rate_limit_event Claude, regex d'une TUI, parsing d'heure locale, minuterie. - Application : orchestration détecter→planifier→reprendre. - Présentation : badge + countdown + Annuler + dialogue filet humain. ## ⚠️ 5 tensions avec l'hexagonal (corrigées dans §21.2 — à lire avant de coder) 1. T1 — Instant rejeté. Le domaine parle i64 époche-ms (via Clock::now_millis), Instant est non sérialisable/monotone/absent. ⇒ RateLimited { resets_at_ms: Option<i64> }. Déviation assumée de ta proposition. 2. T2 — pas de regex dans le domaine. prompt_ready_pattern a été choisi littéral exprès pour éviter la dép regex. ⇒ le domaine stocke la donnée (RateLimitPattern{pattern,…}), le moteur regex + parsing d'heure vivent en infra (regex ajouté au seul Cargo.toml d'infrastructure). 3. T3 — Clock ne réveille pas. Il donne l'heure, pas un timer. ⇒ 1 seul nouveau port Scheduler (arm/cancel), tout le reste réutilise l'existant. 4. T4 — RateLimited non terminal. Le contrat ReplyStream dit « seul Final est terminal » et claude.rs rompt sur Final. ⇒ RateLimited s'intercale comme Heartbeat. Point d'intégration : un tour clos sans Final parce que limité ne doit pas devenir AgentSessionError::Iodrain_bounded doit le traiter en fin gracieuse. 5. T5 — niveau 3 dépend du lot 2. ReadinessSignal::Stalled est réservé/non produit aujourd'hui. ⇒ le filet humain (LS6) est gated sur la livraison du lot 2 (stagnation). Niveaux 1+2 couvrent déjà structuré + PTY entre-temps. ## Ports à créer / étendre - NOUVEAUScheduler (domaine, ports.rs) : arm(deadline_ms, ScheduledTask) -> ScheduleId + cancel(id) -> bool, minuterie one-shot annulable in-memory ; adapter TokioScheduler (infra). ScheduledTask::ResumeAgent{agent_id,node_id,conversation_id} = donnée pure (pas de closure traversant la frontière), drain côté application — même motif que le dispatch orchestrateur §14.3. - ÉTENDUS (aucun autre port neuf) : ReplyEvent +RateLimited ; ReadinessSignal +RateLimited (+classify) ; AgentProfile +rate_limit_pattern ; EventBus/DomainEvent +5 variantes. Reprise = réutilise AgentSessionFactory::start/LaunchAgent + SessionPlan::Resume + conversation_id (déjà câblés) — zéro reconstruction. ## Fichiers touchés - domaine : ports.rs, readiness.rs, profile.rs, events.rs, lib.rs, session_limit.rs (NEW : VO SessionLimit + fn pure plan_resume). - infra : session/claude.rs (lire resetsAt→époche-ms au lieu de jeter en Heartbeat, ligne ~90), session/codex.rs (si signal Codex), ratelimit/ (NEW : RateLimitParser regex niveau 2), scheduler/ (NEW : TokioScheduler), clock/ inchangé. - application : agent/session_limit.rs (NEW : SessionLimitService), agent/structured.rs (réagir au signal + réconcilier T4), agent/mod.rs. - app-tauri : composition root (wire scheduler+service+drain), commandes cancel_agent_resume/resume_agent_now/confirm_agent_rate_limit, relais events camelCase. - frontend : features/{agents,terminals} (badge « limité jusqu'à HH:MM » + countdown + Annuler + dialogue filet humain), gateway TS + adapter + mocks. ## Lots ordonnés (cycle dev/test §3) LS1 domaine (variantes + VO + plan_resume + profil, tests purs) → LS2 adapter Claude niv.1 ∥ LS3 port Scheduler+TokioSchedulerLS4 SessionLimitService + réconciliation T4 → LS5 parser regex niv.2 → LS6 filet humain niv.3 (gate : lot 2 stagnation livré)LS7 app-tauri → LS8 frontend. LS1+LS2+LS4 = niveau 1 Claude de bout en bout : valeur atteinte tôt. ## Confirmé sain - In-mémoire seul : aucun store, aucun schéma .ideai/ modifié. IDE rouvert après reset ⇒ le chemin existant ListResumableAgents reprend (popup, pas d'auto). Cohérent. - Auto + annulable = Scheduler::arm/cancel + events AgentResumeScheduled/AgentResumeCancelled. ## Spikes (§21.10) Format réel de resetsAt (époch/ISO/relatif) ; heure locale→époche + passage de minuit (niv.2) ; signal de limite côté Codex ; dédoublonnage si niv.1 et niv.2 matchent le même épisode (1 SessionLimit vivante/agent). Prochaine étape du cycle : passer la main à Git pour décider de la branche (feature/session-limits ?) avant que le binôme dev/test attaque LS1.
  • Prompt: Feature session-limits — cadrage du lot LS7 (câblage au composition root). Project root: /home/anthony/Documents/Projects/IdeA. État : LS1→LS6 committés. Tout le mécanisme existe mais n'est PAS branché dans app-tauri : application::agent::session_limit::SessionLimitService (ports injectés : Clock, Scheduler, EventBus, AgentResumer) n'est référencé nulle part dans le composition root → aucun DomainEvent::AgentRateLimited/ResumeScheduled/Resumed/RateLimitSuspected n'est jamais émis, aucune reprise armée. LS7 doit câbler au composition root (app-tauri), conformément à ARCHITECTURE §21. J'ai besoin d'une carte de câblage précise (PAS de code), répondant à ces points, en nommant les fichiers/structs/fonctions exacts du repo où chaque tap se branche : 1. Instanciation du service : où, dans app-tauri (state.rs ? di/composition root ?), instancier SessionLimitService::new(clock, scheduler, events, resumer). Quel Scheduler concret (TokioScheduler déjà en infra), quel EventBus (TokioBroadcastEventBus partagé), quel Clock. Cycle de vie/partage (Arc) cohérent avec l'existant. 2. Port AgentResumer → LaunchAgent : comment implémenter AgentResumer::resume(agent_id, node_id, conversation_id, resume_prompt) par-dessus le mécanisme de lancement existant (LaunchAgent + AgentSessionFactory + SessionPlan::Resume). Où vit ce code (un adapter app-tauri ?), et quelles dépendances il capture. Référence : les passerelles voisines HandoffProvider/ProviderSessionProvider mentionnées dans session_limit.rs. 3. Tap détection niveau 1 (structuré) : où le flux ReplyStream/ReplyEvent des sessions structurées est consommé aujourd'hui (le « pump » qui draine les tours — cf. chat.rs chunk_from_event), et comment y intercepter ReplyEvent::RateLimited{resets_at_ms} pour appeler service.on_rate_limited(agent_id, node_id, conversation_id, resets_at_ms). Comment récupérer node_id + conversation_id à ce point. 4. Tap détection niveau 2 (PTY) : où la sortie PTY des agents non-structurés est drainée, et où instancier un infrastructure::RateLimitParser (règle de sélection ratelimit::applies(profile)) pour le nourrir des fragments et router un SessionLimit détecté vers le service. Respect de l'anti-double-détection §21.10-4. 5. Drain du Scheduler : comment TokioScheduler remet les ScheduledTask::ResumeAgent échus (canal de remise) et où câbler le récepteur qui appelle service.execute_resume(task) sur le runtime Tokio. 6. Commande Tauri d'annulation : exposer cancel_resume(agent_id) comme #[tauri::command] (la fenêtre annulable côté UI l'appellera en LS8) — où l'enregistrer dans le generate_handler!. Donne aussi : ordre d'implémentation recommandé, points de fragilité/race à surveiller pour QA, et confirme que rien ne viole l'hexagonal (domaine pur, regex/parse confinés infra). Rends via idea_reply.
  • Response: # LS7 — Carte de câblage « session-limits » au composition root Tous les chemins sont relatifs au project root. Aucun code ici : seulement les points d'ancrage exacts (fichier · struct/fn · ligne). Faits vérifiés en amont : - Service : crates/application/src/agent/session_limit.rsSessionLimitService::new(clock, scheduler, events, resumer) ; API publique : on_rate_limited(agent_id, node_id, conversation_id, resets_at_ms), execute_resume(task).await -> Result<(),AppError>, cancel_resume(agent_id) -> bool. Port à implémenter : AgentResumer::resume(agent_id, node_id, conversation_id, resume_prompt) (async). - Scheduler : crates/infrastructure/src/scheduler/mod.rsTokioScheduler::new(tx: UnboundedSender<ScheduledTask>, clock: Arc<dyn Clock>). Réexporté infrastructure::TokioScheduler. Il pousse la ScheduledTask échue dans tx ; le drain détient rx. - Parser N2 : crates/infrastructure/src/ratelimit/mod.rsRateLimitParser::new(&RateLimitPattern) -> Option<Self>, detect(text, now_ms) -> Option<SessionLimit>, et la règle de sélection ratelimit::applies(profile) -> bool (= structured_adapter.is_none() && rate_limit_pattern.is_some()). Réexportés infrastructure::RateLimitParser / infrastructure::ratelimit::applies. - LS6 a déjà câblé les 6 variantes DomainEvent::Agent{RateLimited,ResumeScheduled,ResumeCancelled,Resumed,RateLimitSuspected} → DTO → relay (crates/app-tauri/src/events.rs:208-253, 426-452). Rien à faire côté event wire. --- ## 1. Instanciation du service (composition root) Fichier : crates/app-tauri/src/state.rs, dans AppState::build (à partir de la ligne 355). Réutiliser les adapters déjà construits en tête de build : - clock (l.358, SystemClock, implémente Clock::now_millis) → caster en Arc<dyn Clock> comme partout (cf. Arc::clone(&clock) as Arc<dyn Clock>). - event_bus (l.357) → events_port (l.369, Arc<dyn EventBus>), le même bus partagé que tout le reste (donc l'AgentRateLimited émis passera par spawn_relay). Séquence d'instanciation (à placer après la construction de launch_agent l.668-700 et de project_store l.750, car le resumer en dépend ; idéalement juste avant le bloc orchestrateur l.917) : 1. let (resume_tx, resume_rx) = tokio::sync::mpsc::unbounded_channel::<ScheduledTask>(); 2. let scheduler = Arc::new(TokioScheduler::new(resume_tx, Arc::clone(&clock) as Arc<dyn Clock>)) as Arc<dyn Scheduler>; 3. let resumer = Arc::new(AppAgentResumer::new(Arc::clone(&launch_agent), Arc::clone(&store_port), <resume_ctx_registry>)) as Arc<dyn application::AgentResumer>; (cf. §2). 4. let session_limit_service = Arc::new(SessionLimitService::new(Arc::clone(&clock) as Arc<dyn Clock>, scheduler, Arc::clone(&events_port), resumer)); Cycle de vie / partage : ajouter un champ pub session_limit_service: Arc<SessionLimitService> à la struct AppState (déclaration vers l.326, à côté de orchestrator_service) et le renvoyer dans le littéral final (l.967-1055). Le Arc est partagé par : (a) la commande cancel_resume (§6), (b) la tâche de drain (§5), (c) les taps de détection N1/N2 (§3/§4) qui appellent on_rate_limited. Le resume_rx n'entre pas dans AppState : il est moved dans la tâche de drain spawné à l'intérieur de build (§5), exactement comme sweep_stalled (l.899-911). Imports à ajouter en tête de state.rs : application::{SessionLimitService, AgentResumer}, domain::ports::{Scheduler, ScheduledTask}, infrastructure::TokioScheduler. --- ## 2. Port AgentResumerLaunchAgent (adapter app-tauri) Nouvel adapter dans crates/app-tauri/src/state.rs, à côté des passerelles stateless existantes AppHandoffProvider (l.100-109) / AppProviderSessionProvider (l.119-128) / AppRecordTurnProvider (l.81-90) — même patron impl application::Trait for AppXxx. impl application::AgentResumer for AppAgentResumer { async fn resume(agent_id, node_id, conversation_id, resume_prompt) -> Result<(), AppError> } recompose un LaunchAgentInput et appelle self.launch_agent.execute(...) (le même Arc<LaunchAgent> que la commande launch_agent, l.1140). C'est LaunchAgent qui, via with_handoff_provider / with_provider_session_provider (l.687-695) et le routage §17.4, applique déjà SessionPlan::Resume quand un conversation_id est présent (chemin de reprise P7/P8b/§15). Le resume_prompt (constante application::RESUME_PROMPT) est le premier tour — pour le chemin PTY natif (composition B-2, voir l.645-653), il devra être écrit dans le PTY après spawn ; pour le chemin structuré (dormant), passé en premier send. À cadrer dev : où injecter ce premier tour. La voie la plus cohérente avec l'existant est de réutiliser le médiateur d'entrée (MediatedInbox/portail d'écriture PTY, l.883-892) plutôt qu'un write direct. ⚠️ Point dur de conception (à trancher, c'est LE risque du lot) : AgentResumer::resume et ScheduledTask::ResumeAgent ne portent pas de project_id, alors que LaunchAgentInput exige un Project complet + rows/cols + mcp_runtime (cf. commande l.1126-1152). Le service est volontairement model/projet-agnostique. Il faut donc que l'adapter résolve le Project à partir de l'agent_id. Recommandation : AppAgentResumer détient un Arc<Mutex<HashMap<AgentId, ResumeContext>>> (ResumeContext = { project: Project, rows: u16, cols: u16 }) alimenté par le chemin de lancement (commande launch_agent, l.1140, où project/rows/cols sont en main) et lu au moment du resume. Le mcp_runtime est recalculé dans resume à partir de project.id via crate::mcp_endpoint::{idea_exe_path, mcp_endpoint} (recette identique à l.1126-1133). Évite un scan coûteux de project_store.list_projects + manifestes. store_port reste injecté en repli (résolution si le contexte est absent après un restart — cohérent avec « état en mémoire only » : après restart c'est ListResumableAgents qui reprend, pas le scheduler). Dépendances capturées par l'adapter : Arc<LaunchAgent>, Arc<dyn ProjectStore> (repli), le registre ResumeContext partagé. --- ## 3. Tap détection niveau 1 (structuré) Fichier : crates/app-tauri/src/commands.rs, fn agent_send, boucle de pump l.1283-1294. C'est le seul drain actif d'un ReplyStream structuré (for event in stream { ... }). Aujourd'hui chunk_from_event (crates/app-tauri/src/chat.rs:186-193) mappe ReplyEvent::RateLimited → None (jeté). Le tap : avant d'appeler chunk_from_event, faire un if let ReplyEvent::RateLimited { resets_at_ms } = &event { service.on_rate_limited(agent_id, node_id, conversation_id, *resets_at_ms); }, puis continuer le drain normalement (l'event reste non-terminal, le tour continue jusqu'au Final). Récupération de node_id + agent_id : le pump ne connaît que sid: SessionId. Le registre state.structured_sessions (crates/application/src/terminal/registry.rs:297) mappe SessionId → (agent_id, node_id) — mais il manque un accès par session_id. Ajouter une petite méthode meta_for_session(&SessionId) -> Option<(AgentId, NodeId)> sur StructuredSessions (jumeau trivial de live_agents l.388, lookup direct dans entries). conversation_id : StructuredEntry ne le porte pas ; passer None (le resume dégrade proprement sans id, contrat ScheduledTask.conversation_id: Option) ou, plus précis, le lire via providers.json (passerelle AppProviderSessionProvider) — optionnel, None est acceptable pour LS7. État actuel : ce tap est DORMANT. En composition B-2 la fabrique structurée est décâblée (launch_agent retombe toujours sur PTY, l.645-653 ; orchestrateur sans .with_structured, l.947-958). Aucun agent n'a de session structurée vivante ⇒ agent_send n'est jamais drainé. Le câbler quand même = robustesse forward (réactivation structurée). La détection réelle aujourd'hui passe exclusivement par le niveau 2. Passage du service au pump : agent_send a state: State<AppState>Arc::clone(&state.session_limit_service) avant le thread::spawn (l.1282) et le move dans le thread. --- ## 4. Tap détection niveau 2 (PTY) Fichier : crates/app-tauri/src/commands.rs, fn launch_agent, branche PTY l.1165-1190 (le if output.structured.is_none() + le thread::spawn du pump d'octets l.1176-1183). C'est le drain de la sortie des agents non-structurés — donc le chemin actif en B-2. Câblage : 1. Sélection (anti-double-détection §21.10-4) : avant d'armer un parser, résoudre le AgentProfile de l'agent et appeler infrastructure::ratelimit::applies(&profile). applies impose déjà structured_adapter.is_none() (or on est dans la branche output.structured.is_none() ⇒ cohérent) et rate_limit_pattern.is_some(). Besoin de wiring : la commande launch_agent ne charge pas le profil. Deux options — (a) exposer le AgentProfile (ou au minimum le RateLimitPattern) résolu sur LaunchAgentOutput (LaunchAgent::execute le résout déjà en interne — le plus propre, zéro I/O en plus) ; (b) le relire via le profile store. Recommandation : (a). 2. Instanciation : RateLimitParser::new(&pattern) (retourne Option ⇒ regex invalide = pas de détecteur, jamais de panique). À construire une fois par lancement, déplacé dans le thread de pump. 3. Alimentation : dans la boucle for chunk in stream (l.1177), après send_output, décoder le fragment (String::from_utf8_lossy) et appeler parser.detect(&text, clock.now_millis()). Sur Some(SessionLimit)service.on_rate_limited(agent_id, node_id, conversation_id, limit.resets_at_ms). Ici agent_id, node_id (l.1115) et conversation_id (request.conversation_id, l.1150) sont déjà en main dans la commande ⇒ les cloner avant le thread::spawn. Idem Arc::clone(&state.session_limit_service) et un Arc<dyn Clock> (ajouter un champ clock à AppState, ou réutiliser SystemMillisClock). Anti-double-détection : garantie par construction — applies ne renvoie true que pour les agents sans adapter structuré ; un agent structuré (N1) n'arme jamais de parser N2. Un seul tap actif par agent. Fragilité fragmentation : detect reçoit des fragments PTY ; un motif peut être coupé entre deux chunks. Pour LS7, accepter la détection best-effort par fragment (les bannières de limite des CLI arrivent en général d'un bloc). Si QA observe des ratés, prévoir un petit buffer glissant borné (dernières ~4 Kio) côté thread — note pour QA, pas bloquant. --- ## 5. Drain du Scheduler TokioScheduler (crates/infrastructure/src/scheduler/mod.rs:62-85) pousse la ScheduledTask échue dans son tx (canal mpsc non borné). Le récepteur resume_rx (créé en §1) est drainé dans une tâche détachée spawné à l'intérieur de AppState::build, sur le patron exact du sweeper sweep_stalled (crates/app-tauri/src/state.rs:899-911) — tauri::async_runtime::spawn (et pas tokio::spawn : build tourne dans le hook setup sans runtime ambiant, cf. commentaire l.901-903). Boucle : while let Some(task) = resume_rx.recv().await { if let Err(e) = service.execute_resume(task).await { /* log best-effort */ } }. Le Arc<SessionLimitService> et resume_rx sont moved dans la closure. execute_resume désarme l'entrée puis appelle AgentResumer::resume puis publie AgentResumed (déjà relayé par LS6). La tâche vit autant que l'app (le canal se ferme au drop du TokioScheduler/AppState). --- ## 6. Commande Tauri cancel_resume Déclaration : crates/app-tauri/src/commands.rs — nouvelle #[tauri::command] pub async fn cancel_resume(agent_id: String, state: State<'_, AppState>) -> Result<bool, ErrorDto> : let id = parse_agent_id(&agent_id)?; Ok(state.session_limit_service.cancel_resume(id)). (parse_agent_id existe déjà, cf. l.1111/1215.) Retourne le bool (true = reprise effectivement annulée ; false = rien d'armé ou « cancel pile au tir » — voir doc service l.188-213). Enregistrement : crates/app-tauri/src/lib.rs, dans tauri::generate_handler![ … ] (l.122) — ajouter commands::cancel_resume, à côté des commandes agent (p.ex. après commands::change_agent_profile, l.166). LS8 (UI fenêtre annulable) l'appellera via invoke("cancel_resume", { agentId }). --- ## Ordre d'implémentation recommandé 1. Service + scheduler + drain (§1 + §5) avec un AgentResumer stub (renvoie Ok(())) ⇒ valide que on_rate_limited arme, que le canal remet la tâche, que execute_resume/AgentResumed partent. Testable sans toucher aux pumps. 2. cancel_resume (§6) ⇒ boucle détecter→annuler bout-en-bout vérifiable manuellement (events au front via LS6). 3. Tap N2 PTY (§4) — chemin actif ⇒ première vraie détection. Implique d'exposer le RateLimitPattern/AgentProfile sur LaunchAgentOutput. 4. AppAgentResumer réel (§2) + registre ResumeContext alimenté par launch_agent ⇒ reprise réelle. 5. Tap N1 structuré (§3) + meta_for_session ⇒ forward-compat (dormant), à câbler en dernier. ## Points de fragilité / race pour QA - Résolution agent_id → Project au resume (§2) : le gros risque. Vérifier qu'un resume après fermeture/réouverture de projet ne plante pas (repli None/erreur propre, jamais de panique). Tester resume quand le ResumeContext est absent. - Cancel « pile au tir » (service l.188-213, scheduler cancel l.87-100) : sous runtime multi-thread, cancel peut renvoyer false car la tâche vient de tirer ⇒ pas d'AgentResumeCancelled, la reprise suit son cours. UI LS8 doit tolérer un false (la reprise arrive quand même). Test de course explicite recommandé. - Rafraîchissement (dédoublonnage §21.10-4) : deux signaux de limite rapprochés pour le même agent ⇒ disarm puis ré-arm ; vérifier qu'on n'empile pas deux ScheduledTask et qu'un seul AgentResumed sort. - Fragmentation PTY (§4) : motif coupé entre chunks (best-effort en LS7, buffer glissant en repli si raté). - Injection du resume_prompt en PTY (§2) : timing d'écriture après spawn (la CLI doit être prête) — réutiliser le portail d'écriture médié plutôt qu'un write brut. - Premier tour différé / cold-start : interaction avec release_agent_cold_start (l.1132-1137) si le resume relance un agent dont le pont MCP n'est pas encore connecté. ## Conformité hexagonale — confirmée - Domaine pur : SessionLimit/plan_resume/RateLimitPattern/ScheduledTask/Scheduler/Clock/EventBus ne portent aucune I/O ni regex. - Regex + parsing d'heure confinés infra : RateLimitParser et timeparse vivent dans crates/infrastructure/src/ratelimit|timeparse ; ils produisent une valeur domaine (SessionLimit). Aucune regex ne franchit la frontière. - Service applicatif pur-ports : SessionLimitService ne dépend que de traits (Clock/Scheduler/EventBus/AgentResumer). - Composition root seul à connaître le concret : TokioScheduler, RateLimitParser, AppAgentResumer, le canal mpsc et les taps sont tous dans app-tauri (state.rs/commands.rs/lib.rs), jamais ailleurs. AgentResumer est un port applicatif implémenté à la racine, exactement comme HandoffProvider/ProviderSessionProvider. Aucun nouveau port domaine, aucun nouvel adapter infra : LS7 est purement du câblage (composition de l'existant), conforme à ARCHITECTURE §21 et au principe « zéro nouveau port/adapter » du lot.
  • Prompt: Arbitrage de scope sur le filet humain NIVEAU 3 de la feature limites de session (ARCHITECTURE §21.1 / §21 « niveau 3 »). Contexte : backend (commit 9df5923) + front (en cours, vert) de la gestion des limites de session sont posés. Niveaux 1 (structuré) et 2 (regex PTY) : pleinement fonctionnels (détecter→planifier reprise auto annulable→reprendre). Le front consomme les 5 DOMAIN_EVENT, affiche le badge « limité jusqu'à HH:MM », le compte à rebours + bouton « Annuler la reprise », et appelle la commande Tauri cancel_resume. GAP constaté sur le niveau 3 : l'événement agentRateLimitSuspected (limite suspectée SANS heure de reset fiable) est bien émis et affiché côté front (note « limité · heure inconnue — reprise à préciser »), MAIS il n'existe AUCUNE commande backend pour que l'utilisateur SAISISSE l'heure de reprise et déclenche une reprise auto à cet instant. DevFrontend a (correctement) refusé de fabriquer une commande inexistante et a posé un TODO. Donc aujourd'hui, sur niveau 3, l'utilisateur est INFORMÉ mais ne peut pas AGIR via le mécanisme de reprise auto — il lui reste seulement le relancement manuel de l'agent par les contrôles existants. Le design verrouillé (mémoire session-limit-handling-design) dit : niveau 3 = « filet humain : IdeA DEMANDE à l'utilisateur. Garantit le 100% même pour un novice : jamais d'inaction silencieuse » ; et « Couverture : les TROIS niveaux d'emblée ». QUESTION (tranche, c'est ton domaine — contrat/frontière) : A) Le niveau 3 est-il considéré COMPLET en l'état (surfacer la suspicion + s'appuyer sur le relancement manuel existant satisfait « jamais d'inaction silencieuse ») → alors la feature est terminée, on merge. OU B) Le niveau 3 EXIGE une boucle d'action : une commande applicative type set_resume_at(agentId, epochMs) qui arme le même SessionLimitService (planifier une reprise à l'instant saisi, annulable comme les autres) + un formulaire de saisie d'heure côté front. → alors c'est une slice supplémentaire LSx (domaine? non / application: méthode service + port? / app-tauri: commande / front: form) que je dois cadrer avant le merge. Si B), précise le découpage minimal et le contrat exact de la nouvelle entrée (signature commande, où elle branche dans SessionLimitService, événements émis — réutilise-t-on AgentResumeScheduled ?). Réponds de façon décisionnelle.
  • Response: VERDICT : B. Le niveau 3 n'est PAS complet en l'état. Une slice supplémentaire (LS8) est requise AVANT le merge. Le découpage et le contrat exact suivent. ## Pourquoi B (et pas A) Le design verrouillé est sans ambiguïté : niveau 3 = « IdeA DEMANDE à l'utilisateur » et « les TROIS niveaux d'emblée ». « Demander » est un acte qui arme une reprise, pas une note d'information. La preuve est dans le code lui-même : 1. domain::session_limit::RateLimitSource::Human existe déjà, documenté « niveau 3 : confirmée par l'utilisateur », et n'est produit nulle part aujourd'hui. C'est du code mort qui attend exactement cette entrée. L'archi a toujours prévu B. 2. La doc de ResumePlan::HumanFallback dit : « L'application demandera l'heure à l'utilisateur plutôt que d'agir à l'aveugle ». La boucle d'action fait partie du contrat domaine, pas une extension. Surfacer + relancement manuel (chemin ListResumableAgents) ne satisfait PAS « jamais d'inaction silencieuse » au sens de la feature : c'est un autre mécanisme, qui ne donne ni la reprise auto programmée ni la fenêtre annulable que les niveaux 1/2 garantissent. Sur niveau 3, l'utilisateur est aujourd'hui informé mais le mécanisme central de la feature lui est inaccessible. Incohérence de contrat ⇒ non mergeable tel quel. DevFrontend a eu raison de poser le TODO plutôt que d'inventer la commande. ## Découpage minimal — LS8 « filet humain : armement par heure saisie » Domaine : RIEN à ajouter. plan_resume, SessionLimit, RateLimitSource::Human, ResumePlan::Scheduled couvrent déjà tout. Une heure saisie par l'utilisateur est fonctionnellement une SessionLimit de source Human avec resets_at_ms = Some(epoch). Le clamp anti-passé (max(now)) protège déjà une saisie déjà échue ⇒ reprise immédiate. C'est le payoff de l'hexagonal : zéro nouveau port, zéro nouvel adapter. Application — SessionLimitService : une méthode publique. N'élargis PAS on_rate_limited (sémantique « signal de détection auto »). Ajoute une entrée dédiée, en réutilisant strictement la branche Scheduled existante : rust /// (d) Filet humain (§21.1 niveau 3). L'utilisateur a saisi l'heure de reset /// pour un agent en limite SUSPECTÉE (AgentRateLimitSuspected, sans heure fiable). /// Construit une SessionLimit source `Human`, calcule le plan et arme la reprise /// EXACTEMENT comme la branche auto : mêmes événements, même dédoublonnage, /// même annulabilité via cancel_resume. pub fn confirm_human_resume( &self, agent_id: AgentId, node_id: NodeId, conversation_id: Option<String>, resets_at_ms: i64, // i64 nu, pas Option : la saisie EST l'heure ) Corps = copie de la branche ResumePlan::Scheduled de on_rate_limited (publish AgentRateLimited{Some}disarmscheduler.arm(ResumeAgent) → mémoriser le ScheduleId → publish AgentResumeScheduled), avec SessionLimit::new(Some(resets_at_ms), now, RateLimitSource::Human). Factorise la branche en un fn arm_scheduled(&self, agent_id, fire_at_ms, node_id, conversation_id) privé appelé par les deux chemins, pour ne pas dupliquer le dédoublonnage. execute_resume et cancel_resume restent inchangés : l'armement humain est annulable et s'exécute par les mêmes voies (c'est l'invariant à préserver — un seul mécanisme de reprise). app-tauri — une commande. Miroir exact de cancel_resume : rust #[tauri::command] pub async fn set_resume_at( agent_id: String, resets_at_ms: i64, state: State<'_, AppState>, ) -> Result<(), ErrorDto> Corps : parse_agent_id → résoudre node_id et conversation_id côté backend depuis la registry (le front n'a que l'agent_id ; agentRateLimitSuspected ne porte que ça) : - node_id via TerminalSessions::node_for_agent(&id) (ou la registry unifiée). Si NoneErrorDto INVALID/NOT_FOUND (l'agent n'a plus de cellule vivante — la saisie n'a pas de cible). - conversation_id via la session structurée session_for_agent(&id).map(|s| s.conversation_id()) (best-effort None toléré, comme tout le chemin de reprise dégradé). Puis state.session_limit_service.confirm_human_resume(id, node_id, conv_id, resets_at_ms). Enregistre la commande dans lib.rs à côté de cancel_resume. Front — un formulaire minimal. Sur le badge « limité · heure inconnue — reprise à préciser » : un petit input heure (ou datetime) → calcule l'epoch ms → invoke("set_resume_at", { agentId, resetsAtMs }). Dès réception de AgentResumeScheduled (déjà consommé !), le badge bascule automatiquement sur l'état nominal « limité jusqu'à HH:MM » + compte à rebours + bouton « Annuler la reprise » déjà câblés. Aucun nouvel événement, aucune nouvelle vue d'état côté front : la boucle se referme sur l'UI existante. ## Contrat des événements — RÉUTILISE l'existant, n'en crée AUCUN C'est le point clé de cohérence : confirm_human_resume émet la même paire que la branche auto — AgentRateLimited { agent_id, resets_at_ms: Some(t) } puis AgentResumeScheduled { agent_id, fire_at_ms }. Le front les consomme déjà. La transition « suspecté → programmé » se fait donc sans code de présentation neuf : AgentResumeScheduled est le pivot qui retire l'état « heure inconnue » et réutilise le rendu nominal. Annulation ⇒ AgentResumeCancelled (inchangé). Reprise ⇒ AgentResumed (inchangé). AgentRateLimitSuspected reste l'unique signal « il faut demander », rien d'autre. ## Garde-fous QA pour le binôme - Saisie dans le passé : pas un cas d'erreur — plan_resume clampe à now ⇒ reprise quasi-immédiate. Test à ajouter. - Re-saisie / second suspect : confirm_human_resume doit passer par le même disarm que l'auto ⇒ un seul armement par agent (§21.10-4). Test de dédoublonnage croisé (humain après auto, et inverse). - Agent disparu entre suspicion et saisie : node_for_agent == None ⇒ erreur propre, pas d'armement orphelin. - Course cancel-pile-au-tir : inchangée, déjà couverte par cancel_resume. Périmètre total : ~1 méthode service (factorisée) + 1 commande + 1 input front + tests. Aucune frontière nouvelle, aucun port, aucun adapter. À cadrer et livrer en LS8 avant que Git n'envisage le merge feature/* → develop.
  • Prompt: Diagnostic architecture demandé par Main. Contexte: IdeA intègre Codex CLI. Le profil Codex actuel utilise McpConfigStrategy::TomlConfigHome { target: ".codex/config.toml", home_env: "CODEX_HOME" }, donc au lancement IdeA écrit {runDir}/.codex/config.toml et pousse CODEX_HOME vers ce dossier pour isoler MCP/permissions par agent. Problème produit: chaque agent Codex redemande une connexion ChatGPT/OpenAI, et si lutilisateur crée beaucoup dagents il atteint une limite de requests/login. De plus un agent jamais lancé peut échouer lors dune délégation car il nest pas authentifié. Questions: 1) quelle solution respecte larchitecture hexagonale/SOLID pour partager lauth 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 dagent ? 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 <projectRoot> à 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, cest conforme à larchitecture 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 dexécution du projet, pas un détail Tauri/Codex. - StructuredSessionFactory::start peut transmettre cette donnée vers ladapter runtime concret : cest précisément le rôle de la couche application/composition dorchestrer les ports avec le contexte résolu. - Ajouter --add-dir <projectRoot> dans CodexExecSession est au bon endroit si CodexExecSession est ladapter infrastructure/presentation-backend spécifique à Codex CLI. Le flag --add-dir est un détail technique de ladapter Codex, donc il ne doit pas remonter dans le domaine. - Conditionner cet ajout à workspace-write est cohérent : lintention métier est “la session peut écrire dans le workspace résolu”, limplé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 ladapter 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. Ladapter 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 lexé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. LUI demande un lancement dagent/session ; la politique de sandbox reste dans les services/adapters backend. Formulation recommandée côté modèle : plutôt quun champ trop Codex-spécifique, utiliser un nom neutre comme workspace_roots, writable_roots, project_root, ou ExecutionScope { cwd, project_root, sandbox_mode }. Puis ladapter Codex traduit ExecutionScope + workspace-write en codex 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 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 linté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 quil possède des skills assignés et comment les utiliser, sans que lutilisateur doive le lui rappeler. Aujourdhui L12 injecte le contenu complet des skills dans # Skills, mais cest bas dans le contexte, lu comme documentation, et pas présenté comme affordance/action disponible. Objectif technique minimal : transformer linjection “dump de contenu” en modèle “à la MCP” pour les profils MCP : - section haute altitude # Skills disponibles, juste après lorchestration/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 lancien 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 dorchestration 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 nest pas un doublon de agent-skills; cest une amélioration dergonomie 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<String> à 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<String> ; - 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 dabord, 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 cest 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 laligner 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<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 impls list/get/save transportent simplement le champ. - FsSkillStore index : ajouter description dans IndexEntry, #[serde(default)], écriture camelCase. Le body reste dans md/<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 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 nen 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 lUI. ## 5) Risques et ordre dimplé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 quaprè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 à lexé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 à lactivation. - agent-skill-awareness = rendre lagent 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é nest pas un nouveau store, ni une nouvelle UI CRUD, ni un mécanisme de slash-commands fournisseur. Cest 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 dagent, 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 lagent identifie un workflow réutilisable, il doit passer par lorchestration 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 : lassignation 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 lagent 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 lordre 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 : larchitecture historique en parle, mais le code courant a déjà une prose dorchestration 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. LUI 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 lagent”. 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 lagent/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<SkillScope> } 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, ladapter MCP / watcher, et éventuellement les DTO de réponse. Ce nest pas nécessaire pour corriger lawareness des skills assignés. ### Ordre dimplé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 linterdiction 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 lordre. - 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 dattention 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 lancien plan. Le bon axe est : améliorer la conscience contextuelle des agents sur les skills existants, pas refaire lorchestration.
  • 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 nexiste 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 dun 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 dabord 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 dempiler de linvisible. Le prochain verrou produit est de rendre lorchestration compréhensible et opérable : lutilisateur doit voir ses “employés IA” travailler, attendre, déléguer, répondre et reprendre. Ce chantier peut aussi servir daudit dintégration : sil manque un événement ou une donnée backend, on lajoute 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 dun 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 nest 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 quon lit les registres existants. Le read-model peut composer : - LiveSessions / TerminalSessions / StructuredSessions, - InputMediator::busy_state, - ConversationRegistry, - AgentMailbox si une méthode dinspection propre est ajoutée, - ConversationLog / HandoffStore en lecture best-effort pour les résumés. Si inspection mailbox nécessaire, ne pas exposer limpl InMemoryMailbox : ajouter un petit port ségrégué, par exemple AgentQueueSnapshot, ou étendre prudemment AgentMailbox avec une méthode read-only si limpact 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 dimplé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 linspection 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 lordre. 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 lagent”, “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 nest plus dinventer 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 lagent tourne, - indiquer idle / busy à partir du médiateur dentrée, - exposer le ticket courant si busy, - permettre au frontend dafficher un panneau “Work” simple et scannable. Ce lot ne cherche pas encore à afficher lhistorique, 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 dinspection 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<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 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 lexistant : 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, } 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 lordre 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<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 : 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<ProjectWorkStateDto, ErrorDto> 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<GetProjectWorkState>. - 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<ProjectWorkState>; } 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<ProjectWorkState> { return invoke<ProjectWorkState>("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<void>; } 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 dactions 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 longlet 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 quon 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 lutiliser 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 lagrégation dans application, pas dans Tauri ni frontend.