merge(skills): intègre agent-skill-awareness-v2 dans develop
Awareness skills dans le fichier de convention (featbefff76), hotfix livraison délégation + logs submit (fix018eb1a) et état runtime associé. QA vert accepté avec réserve environnementale : tests loopback socket Unix réels non exécutables en sandbox (UnixListener::bind PermissionDenied), alternatives avec skips vertes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,15 +1,9 @@
|
||||
---
|
||||
upTo: abf3985b-d4d6-44b3-9451-b96a72129c88
|
||||
upTo: 30bb2b67-c173-43d1-9776-48f2f597e90b
|
||||
objective: Nouvelle feature cadrée par Architect, prête à démarrer le développement : « Gestion des limites de session des agents » (détection hiérarchique des limites de session + reprise auto annulable). C'est
|
||||
---
|
||||
**Objectif :** Nouvelle feature cadrée par Architect, prête à démarrer le développement : « Gestion des limites de session des agents » (détection hiérarchique des limites de session + reprise auto annulable). C'est
|
||||
|
||||
- **Prompt:** Feature session-limits, suite. Project root: /home/anthony/Documents/Projects/IdeA. Deux lots sont terminés et VERTS (workspace compile, `cargo test --workspace` tout vert, clippy propre sur le nouveau code) mais NON committés : - LS5 — détecteur niveau 2 déclaratif + parsing temps partagé. Fichiers : nouveau `crates/infrastructure/src/ratelimit/mod.rs` (RateLimitParser, regex confiné infra), nouveau `crates/infrastructure/src/timeparse.rs` (parsing d'heures pur, partagé niveau 1/2), modifs `crates/infrastructure/src/session/claude.rs` (refactor vers timeparse, ~-121 lignes), `crates/infrastructure/src/scheduler/mod.rs`, `crates/infrastructure/src/lib.rs` (exports), `crates/infrastructure/Cargo.toml` (dép `regex`), `Cargo.lock`. - LS6 — câblage des 5 nouvelles variantes DomainEvent (AgentRateLimited/ResumeScheduled/ResumeCancelled/Resumed/RateLimitSuspected) + ReplyEvent::RateLimited vers le front. Fichiers : `crates/app-tauri/src/events.rs` (DTO miroir + bras From + test relais), `crates/app-tauri/src/chat.rs` (chunk_from_event). Note : l'arbre contient aussi des fichiers runtime .ideai/ modifiés (conversations/*/handoff.md, log.jsonl, layouts.json, memory/) et un nouveau memory `git-owns-commit-merge-decisions.md` — à toi de juger ce qui entre ou non dans le(s) commit(s) applicatif(s) (rappel : les LS précédents committaient uniquement le code applicatif, pas le bruit runtime). La branche courante est détachée (HEAD), pas sur develop. À toi de décider : topologie de branche, découpage en un ou deux commits (LS5 puis LS6, ou groupés), messages dans le style des commits feat(session-limits) existants, et si un merge feature/* → develop doit suivre. Périmètre LOCAL uniquement (aucun push). Exécute ta décision et rends via idea_reply le détail (branche, sha, messages, état final de `git status`).
|
||||
- **Response:** Fait. Décisions et exécution : **Branche** — Contrairement à ce qui était annoncé, HEAD n'était pas détachée : on était bien sur `feature/agent-session-limits` (la branche de la feature). Aucune création/bascule nécessaire, le dev se poursuit dessus. Rien à corriger. **Découpage — 2 commits atomiques** (LS5 puis LS6, séparés car deux intentions distinctes : couche infra de détection vs câblage événementiel vers le front) : 1. `98bfcf4` — feat(session-limits): LS5 — détecteur niveau 2 déclaratif + parsing temps partagé Fichiers : `crates/infrastructure/src/ratelimit/mod.rs` (nouv.), `crates/infrastructure/src/timeparse.rs` (nouv.), `crates/infrastructure/src/session/claude.rs`, `crates/infrastructure/src/scheduler/mod.rs`, `crates/infrastructure/src/lib.rs`, `crates/infrastructure/Cargo.toml`, `Cargo.lock`. 2. `ea94e75` — feat(session-limits): LS6 — câblage des événements de limite vers le front Fichiers : `crates/app-tauri/src/events.rs`, `crates/app-tauri/src/chat.rs`. Messages en français, style Conventional Commits cohérent avec LS1→LS4, corps expliquant le pourquoi, co-author inclus. **Bruit runtime exclu** — Non committé, comme pour les LS précédents : `.ideai/conversations/*` (handoff.md, log.jsonl), `.ideai/layouts.json`, `.ideai/memory/` (dont MEMORY.md et le nouveau `git-owns-commit-merge-decisions.md`), et la nouvelle conversation `6bc594e8-…`. Ce sont des artefacts d'exécution, pas du code applicatif. **Merge feature/* → develop : NON.** La feature session-limits est livrée par incréments (série LS en cours) ; LS5/LS6 sont verts mais internes (détecteur niveau 2 + relais d'événements) sans surface produit complète. On reste sur `feature/agent-session-limits`. Le merge vers `develop` se fera quand la feature sera terminée et toujours verte. **Périmètre** — 100% local, aucun push. **État final `git status`** : working tree propre côté applicatif ; ne subsistent que les fichiers runtime `.ideai/` modifiés/non suivis (volontairement hors commit). Derniers commits : ea94e75 LS6 / 98bfcf4 LS5 / 9000b4d LS4.
|
||||
- **Prompt:** Slice LS7 (backend / câblage app-tauri) de la feature session-limits terminée et verte. On est sur la branche feature/agent-session-limits (HEAD détaché ? `git status` montre HEAD), dernier commit ea94e75 (LS6). Contenu à committer (tout est vert, aucun warning) : - crates/application/src/agent/lifecycle.rs : `LaunchAgentOutput.profile` exposé (None sur réattache/idempotent, Some sur lancement effectif). - crates/application/src/terminal/registry.rs : `StructuredSessions::meta_for_session()` (lookup agent/node par SessionId pour le tap niveau 1). - crates/app-tauri/src/state.rs : `ResumeContext`/`ResumeContexts`, `AppAgentResumer` (impl port `AgentResumer` au-dessus de LaunchAgent), instanciation+câblage du `SessionLimitService` (TokioScheduler + drain des réveils) dans `AppState::build`. - crates/app-tauri/src/commands.rs : taps niveau 1 (agent_send) et niveau 2 (launch_agent, parser regex confiné), alimentation de `resume_contexts`, nouvelle commande `cancel_resume`. - crates/app-tauri/src/lib.rs : enregistrement de `cancel_resume` dans le handler. - crates/app-tauri/Cargo.toml : dépendance `async-trait`. - crates/app-tauri/tests/session_limit_wiring.rs : 2 tests d'intégration (composition) — verts. - crates/app-tauri/tests/dto_agents.rs + dto_chat.rs : ajustement `profile: None` dans les fixtures. - crates/application/tests/structured_registry_d1.rs : test de `meta_for_session`. Hors périmètre code (à NE PAS committer dans ce commit applicatif) : .ideai/conversations/**, .ideai/layouts.json, .ideai/memory/** — ce sont des artefacts runtime/mémoire. Questions : 1) Faut-il committer cette slice LS7-backend maintenant, et sous quel message ? 2) Reste-t-on sur feature/agent-session-limits ? 3) Décides-tu d'un merge vers develop, ou on attend LS7-front (React/TS, badge + compte à rebours + filet humain) qui complète la surface produit ? Tranche et exécute le commit applicatif si tu juges que oui.
|
||||
- **Response:** Décision Git — LS7 backend committée, on reste en feature, pas de merge maintenant. État initial : on était bien **sur la branche** `feature/agent-session-limits` (pas de HEAD détaché — le « HEAD » de ton `git status` était juste l'en-tête francisé), HEAD = ea94e75 (LS6). 1) ✅ Commit fait. Hash **9df5923** : `feat(session-limits): LS7 — câblage backend app-tauri (taps niveaux 1&2 + reprise annulable)` - 10 fichiers, +406/−3, dont la création de `tests/session_limit_wiring.rs`. - Périmètre strictement applicatif : lifecycle.rs, registry.rs, state.rs, commands.rs, lib.rs, Cargo.toml + les 4 fichiers de tests. - **Exclus comme demandé** (restés non-stagés, ce sont des artefacts runtime/mémoire) : `.ideai/conversations/**`, `.ideai/layouts.json`, `.ideai/memory/**` (dont le nouveau `git-owns-commit-merge-decisions.md` et le `6bc594e8…/` non suivi). Commit atomique = code seulement. 2) ✅ On **reste sur `feature/agent-session-limits`**. La feature n'est pas terminée côté produit (il manque LS7-front), donc on continue dessus. 3) ❌ **Pas de merge vers `develop` maintenant.** Raison : la slice est verte mais la surface produit session-limits est **incomplète sans LS7-front** (badge + compte à rebours + filet humain). Merger le backend seul livrerait dans `develop` une mécanique de reprise annulable sans son UI de contrôle/annulation visible — état intermédiaire que je ne veux pas figer dans l'intégration. **On attend LS7-front.** Une fois le front implémenté et la suite complète verte, reviens vers moi : je rebase si besoin sur `develop`, puis merge `--no-ff` la feature entière d'un coup (backend + front cohérents). Prochaine étape côté toi : enchaîner LS7-front (React/TS) sur cette même branche. Quand c'est vert, ping-moi pour le merge d'intégration.
|
||||
- **Prompt:** Slice LS7-front (React/TS, UI limites de session) terminée et VERTE sur `feature/agent-session-limits`. État tests : `npx vitest run src/features/agents/` → 4 fichiers, 63 tests passés (39 existants + 24 nouveaux), 0 régression. Typecheck `tsc --noEmit` propre. Fichiers à committer (frontend/ uniquement) : - src/domain/index.ts — 5 variantes ajoutées au union DomainEvent (agentRateLimited/ResumeScheduled/ResumeCancelled/Resumed/RateLimitSuspected). - src/ports/index.ts — `cancelResume(agentId)` ajouté à InputGateway. - src/adapters/input.ts — TauriInputGateway.cancelResume → invoke("cancel_resume", { agentId }). - src/adapters/mock/index.ts — MockInputGateway.cancelResume (cancelledResumes/cancelResumeResult). - src/features/agents/useAgents.ts — état limitByAgent + action cancelResume. - src/features/agents/AgentLimitBadge.tsx — NOUVEAU (badge + compte à rebours + bouton Annuler + helpers purs). - src/features/agents/AgentsPanel.tsx — câblage du badge. - src/features/agents/useAgentsLimits.test.tsx — NOUVEAU (13 tests, QA). - src/features/agents/AgentLimitBadge.test.tsx — NOUVEAU (11 tests, QA). Hors périmètre (NE PAS committer) : .ideai/** (artefacts runtime/mémoire). Aucun fichier backend touché. NOTE : il existe un test PRÉEXISTANT rouge SANS rapport avec cette slice — `src/adapters/mock/mock.test.ts:16` asserte « thirteen gateways » alors qu'il y en a 14 (gateway `permission` déjà présente dans HEAD committé eca2ba9). Compteur périmé, pas causé par LS7-front. À toi de voir si tu veux que je fasse corriger ce compteur (tâche séparée DevFrontend) avant/après — dis-moi. Questions : 1) Committe la slice LS7-front maintenant ? Sous quel message ? 2) Le merge --no-ff de la feature complète vers develop : on attend que je tranche le dernier point de scope (filet humain niveau 3 — voir consultation Architect en cours : peut nécessiter une slice backend+front supplémentaire `set_resume_at`), donc NE merge pas encore vers develop — confirme que tu te limites au commit de cette slice et qu'on garde le merge pour quand je te recontacte « feature complète ». Tranche et exécute le commit.
|
||||
- **Response:** Décision Git — LS7-front committée, pas de merge develop (j'attends ton « feature complète »). 1) ✅ Commit fait. Hash **4fad042** : `feat(session-limits): LS7-front — UI limites de session (badge + compte à rebours + filet humain)` - 9 fichiers, +702/−1, dont 3 créations (AgentLimitBadge.tsx + ses 2 fichiers de tests). - Périmètre strictement frontend : domain/index.ts, ports/index.ts, adapters/input.ts, adapters/mock/index.ts, useAgents.ts, AgentLimitBadge.tsx, AgentsPanel.tsx + les 2 tests. - **Exclus comme demandé** (non-stagés) : `.ideai/**` (conversations, layouts, memory) et `node_modules/` (non suivi — au passage, il traîne en untracked ; à gitignorer dans une tâche d'hygiène séparée si ce n'est pas déjà couvert, mais hors périmètre ici). Aucun fichier backend touché. Commit atomique = frontend seulement. 2) ✅ Confirmé : **je me limite à ce commit, pas de merge vers `develop`.** Je garde le `--no-ff` d'intégration de la feature complète pour quand tu me recontactes « feature complète », une fois tranché le scope du filet humain niveau 3 (slice `set_resume_at` éventuelle, cf. consultation Architect). Concernant le **test préexistant rouge** `mock.test.ts:16` (« thirteen gateways » → 14 réelles, gateway `permission` déjà dans HEAD eca2ba9) : compteur périmé sans rapport avec LS7. Mon avis Git : à corriger **avant le merge d'intégration**, en **commit séparé** (`fix(test): …` ou `chore(test): …`), pour ne pas merger `develop` avec une suite rouge. Lance une tâche DevFrontend dédiée quand tu veux ; tu peux l'enchaîner maintenant ou la grouper avec l'éventuelle slice `set_resume_at` — dans tous les cas elle aura son propre commit, et je ne mergerai pas tant que toute la suite n'est pas verte. État branche : `feature/agent-session-limits` → … ea94e75 (LS6) · 9df5923 (LS7 back) · **4fad042 (LS7 front, HEAD)**.
|
||||
- **Prompt:** Feature session-limits COMPLÈTE (3 niveaux) et verte. Reste à committer 2 slices, puis on peut envisager le merge d'intégration `feature/agent-session-limits → develop` que tu gardais en attente. État branche actuel : … 9df5923 (LS7 back) · 4fad042 (LS7 front, HEAD). Travail NON committé depuis, à committer maintenant en 2 commits séparés : ═══ COMMIT A — LS8-backend (filet humain niveau 3, backend) ═══ Implémenté par DevBackend, testé par QA (application: 15 tests session_limit_service / app-tauri: 4 wiring, + régressions vertes, 0 failed). Fichiers : - crates/application/src/agent/session_limit.rs — refactor privé `arm_scheduled` (param `resets_at_ms` brut ajouté) partagé par `on_rate_limited` + nouvelle `pub fn confirm_human_resume(agent_id, node_id, conversation_id, resets_at_ms: i64)` (source Human, réutilise la branche Scheduled, annulable). - crates/app-tauri/src/commands.rs — nouvelle commande `set_resume_at(agent_id, resets_at_ms) -> Result<(), ErrorDto>` (résout node_id via node_for_agent + conversation_id best-effort, NOT_FOUND si pas de cellule vivante). - crates/app-tauri/src/lib.rs — `set_resume_at` enregistrée après `cancel_resume`. - crates/application/tests/session_limit_service.rs — +6 tests (QA). - crates/app-tauri/tests/session_limit_wiring.rs — +2 tests (QA). Aucun événement nouveau (réutilise AgentRateLimited + AgentResumeScheduled). ═══ COMMIT B — LS8-front + fix test (DevFrontend a demandé 2 commits ; à toi de voir si tu sépares ou regroupes) ═══ LS8-front (typecheck propre, 109 tests verts) : - frontend/src/ports/index.ts — `setResumeAt(agentId, resetsAtMs)` sur InputGateway. - frontend/src/adapters/input.ts — `setResumeAt` → invoke("set_resume_at", { agentId, resetsAtMs }). - frontend/src/adapters/mock/index.ts — MockInputGateway.setResumeAt (resumeArmings[]). - frontend/src/features/agents/useAgents.ts — action setResumeAt (sans mutation optimiste). - frontend/src/features/agents/AgentLimitBadge.tsx — formulaire de saisie d'heure sur l'état suspected sans heure + helper pur `timeInputToEpochMs` ; TODO LS7 retiré. - frontend/src/features/agents/AgentsPanel.tsx — câblage onSetResumeAt. - frontend/src/features/agents/AgentLimitBadge.test.tsx — tests mis à jour au nouveau contrat + couverture LS8. Fix test hygiène (DevFrontend recommande commit SÉPARÉ) : - frontend/src/adapters/mock/mock.test.ts — compteur gateways 13→14 (« thirteen »→« fourteen » + `permission` dans l'array). Vérifié : 14 gateways réelles. Hors périmètre (NE PAS committer) : .ideai/** (runtime/mémoire), node_modules/ (untracked — DevFrontend signalait qu'il traîne, à gitignorer dans une tâche d'hygiène séparée, hors périmètre ici). QUESTIONS : 1) Committe COMMIT A puis les commits front (LS8-front + fix-test, séparés ou groupés selon ton jugement). Donne-moi les hashes + messages. 2) La suite complète est maintenant verte (Rust: domain/application/app-tauri ; front: agents + adapters/mock 109 tests) et la surface produit session-limits est complète sur les 3 niveaux. Décides-tu MAINTENANT le merge `--no-ff` vers develop (rebase si besoin) ? Si oui, exécute-le et donne-moi le résultat. Si tu veux un dernier garde-fou (ex. run de la suite complète des deux côtés avant merge), dis-le et je le fais lancer.
|
||||
- **Response:** ✅ 3 commits + merge d'intégration faits. La feature session-limits est intégrée dans `develop`. ═══ 1) Les 3 commits (sur feature/agent-session-limits) ═══ J'ai séparé le fix-test de la feature (hygiène isolée, comme recommandé par DevFrontend) → 3 commits : - **c480d28** `feat(session-limits): LS8-backend — filet humain niveau 3 (set_resume_at)` (session_limit.rs + commands.rs + lib.rs + tests service/wiring ; 5 fichiers, +394/−18) - **5d9dd32** `feat(session-limits): LS8-front — filet humain niveau 3 (saisie d'heure de reprise)` (ports/input/mock + useAgents + AgentLimitBadge(.tsx/.test) + AgentsPanel ; 7 fichiers, +222/−26) - **3f3504e** `fix(test): corrige le compteur de gateways du mock (13 → 14)` (mock.test.ts isolé ; 1 fichier, +2/−1) Artefacts `.ideai/**` et `node_modules/` exclus comme demandé. ═══ 2) Merge `--no-ff` → develop : OUI, exécuté ═══ Décision : la surface produit est complète sur les 3 niveaux et tout est vert des deux côtés → je merge. Pas de rebase nécessaire : `feature` était **12 ahead / 0 behind** `develop` (aucune divergence, base linéaire). - Merge commit : **d7041c5** `merge(session-limits): intégration de la feature limites de session (3 niveaux)` — stratégie ort, **0 conflit**, 47 fichiers / +5157/−45 (cumul LS2→LS8). - `develop` est maintenant à **d7041c5**, contenant toute la chaîne LS2 (a1755e5) → LS8 (3f3504e). - La branche `feature/agent-session-limits` est **conservée** (pas supprimée) : je la garde tant que tu n'as pas confirmé que l'intégration est définitivement validée. Dis-moi si tu veux que je la supprime maintenant. Note d'exécution : les artefacts runtime `.ideai/**` non committés bloquaient le switch de branche → je les ai **stashés le temps du merge puis restaurés** (stash poppé, working tree intact, rien perdu). Aucun de ces artefacts n'est entré dans les commits applicatifs ni dans le merge. ⚠️ Périmètre **strictement local** : aucun `push`. `develop` local est en avance de 58 commits sur `origin/develop` — je ne pousse rien sans ta validation explicite. Prochaines options possibles, à ta main : suppression de `feature/agent-session-limits`, et/ou push de `develop` vers origin (action sortante → nécessite ton feu vert).
|
||||
- **Response:** Topologie tranchée et opération git exécutée. Le dev démarre sur **`feature/orchestrator-designation`**. **Décision : branche dédiée créée depuis `develop`.** Pourquoi cette base : - HEAD était bien sur `feature/agent-skill-awareness` (pas détaché — le statut initial trompait). - La feature « orchestrateur du projet » est **fonctionnellement indépendante** de skill-awareness. Vérifié au niveau fichiers : leur seul recoupement est `lifecycle.rs`. Les fichiers cœur de ta feature (`context_guard.rs`, `domain/fileguard.rs`, `agent.rs`, `events.rs`, infra `fileguard/mod.rs`) ne sont **pas** touchés par skill-awareness, et `develop` contient déjà le câblage du ContextGuard (commit 8452333) sur lequel ta feature s'appuie. - `feature/agent-skill-awareness` a 4 commits non mergés dans `develop` et **n'est pas confirmé vert** de ton côté → règle d'or : je ne le merge pas et je ne le prends pas comme base (cela tirerait du travail non vérifié dans la nouvelle feature). Branche pure depuis l'intégration = feature mergeable indépendamment. Ce que j'ai fait : 1. Commit de l'état runtime qui traînait dans le working tree (`.ideai/` : conversations, layouts, mémoire + note design non suivie) **sur `feature/agent-skill-awareness`** pour partir d'un arbre propre — `5be8987 chore(wip): état runtime .ideai …`. 2. `checkout develop` → `checkout -b feature/orchestrator-designation`. Base = `8452333` (HEAD de develop). Arbre propre, branche active = `feature/orchestrator-designation`. DevBackend/DevFrontend + QA peuvent attaquer T1..T8 dessus. ⚠️ Point d'attention pour le futur merge : `lifecycle.rs` sera modifié à la fois par cette feature et par skill-awareness (non mergé). À l'intégration de l'une puis l'autre dans `develop`, un conflit sur `lifecycle.rs` est probable — résoluble au merge, rien à anticiper côté dev. Reviens vers moi une fois T1..T8 verts pour le merge `feature/orchestrator-designation → develop`. Note locale uniquement : aucune action sortante (`push`) effectuée.
|
||||
@ -24,3 +18,9 @@ objective: Nouvelle feature cadrée par Architect, prête à démarrer le dével
|
||||
- **Prompt:** Préparation du prochain chantier après fermeture de `orchestrator-designation`. Contexte: - Branche courante propre: `develop` @ `9c71a5b`, en avance de 9 sur `origin/develop`, aucun push. - `orchestrator-designation` est intégré et sa branche supprimée. - L'utilisateur a donné autonomie pour terminer les chantiers restants. Branches anciennes à statuer selon tes rappels: - `feature/agent-session-limits`: tu l'as qualifiée comme intégrée/supprimable. - `feature/agent-skills`: 1 commit non intégré. - `feature/agent-skill-awareness`: 5 commits non intégrés. - `fix/cold-start-delivery-race`: 3 commits non intégrés, probable sous-ensemble de skill-awareness. Ta mission Git: 1. Inspecte ces branches et leur relation à `develop`. 2. Prends la décision git la plus saine en autonomie: supprimer les branches déjà intégrées, conserver celles qui représentent un chantier réel, identifier les doublons/sous-ensembles. 3. Ne push pas. Pas d'action sortante. 4. Si une branche non intégrée doit être reprise comme prochain chantier, dis à Main laquelle et pourquoi. Si elle doit passer par Architect/Dev/QA avant merge, indique-le. 5. Rends compte des actions effectuées et de l'état final. Réponds via `idea_reply`.
|
||||
- **Response:** Ménage des branches fait + cadrage du prochain chantier. Aucune action sortante, aucune suppression risquée. ## Action exécutée - **Supprimé `feature/agent-session-limits`** (`git branch -d`, refus auto si non mergée) : 0 commit propre, entièrement intégrée dans develop. Suppression sûre. ## Analyse des 3 branches restantes (relations git vérifiées) - **`feature/agent-skill-awareness`** — base `8452333`, 5 commits propres, NON intégrée. Le chantier le plus riche et récent : `ab34363` (manifeste de skills + outil MCP `idea_skill_read`), `e93a2c1` (fix cold-start delivery), `1a10d67` (test compteur outils MCP 11→12), `566bff4` (brief « capacités IdeA »), `5be8987` (runtime). **À conserver.** - **`feature/agent-skills`** — base `9736c42`, 1 commit `ef101db` (skills domain/usecases/FS store, L12), NON intégrée, **divergente** de skill-awareness (pas ancêtre). ⚠️ Elle touche **les mêmes fichiers** que skill-awareness (`domain/src/skill.rs`, `application/src/skill/*`, `infrastructure/src/store/skill.rs`) → ce sont très probablement **deux itérations concurrentes de la même couche skill**, pas des compléments. **Conservée**, mais l'arbitrage « laquelle fait foi » est une décision d'**architecture, pas git**. - **`fix/cold-start-delivery-race`** — base `6969dc7` (ligne main : elle traîne les commits de release 0.1.0/0.2.0). Son **seul contenu réel** est `9590eac` (fix cold-start), dont le **jumeau logique `e93a2c1` est déjà dans skill-awareness** (message identique). → **doublon superseded**. `git cherry` confirme qu'aucun de ces patchs n'est encore dans develop. ## Décisions / recommandations 1. **Prochain chantier = `feature/agent-skill-awareness`.** Pourquoi : c'est le travail le plus avancé et non intégré, et il englobe le fix cold-start de la branche `fix/cold-start-delivery-race`. 2. **Il DOIT passer par le cycle avant tout merge**, dans cet ordre : - **Architect d'abord** : trancher le chevauchement skill-awareness ⟷ `feature/agent-skills` (couche skill implémentée sur les mêmes fichiers). Décider qui est la source de vérité (probablement skill-awareness subsume agent-skills) avant de figer quoi que ce soit. - **Dev/QA** : revalider vert, en particulier `1a10d67` (compteur d'outils MCP, qui a déjà bougé 11→12 puis 12→13 dans state.rs côté develop) et le fix cold-start. 3. ⚠️ **Le rebase de skill-awareness sur develop VA conflicter** : la branche modifie `crates/app-tauri/src/state.rs` et `crates/infrastructure/src/input/mod.rs`, tous deux lourdement réécrits par orchestrator-designation déjà mergé dans develop. Ce n'est **pas un rebase aveugle** : je le ferai avec l'appui de Dev pour résoudre les conflits, puis QA revalide avant mon merge `--no-ff`. 4. **`fix/cold-start-delivery-race` : je recommande sa suppression** (force `-D`, car non mergée), son contenu unique étant préservé dans skill-awareness et le reste n'étant que des commits de release déjà sur main. Je ne l'ai PAS forcée en autonomie (force-delete = action plus difficile à annuler). **Donne-moi le feu vert et je la supprime**, idéalement une fois skill-awareness intégré dans develop. ## État final - Branche courante : **`develop`** @ `9c71a5b` (+9 vs origin, non poussé). - Branches : `develop`, `main` (@ `29232dd`), `feature/agent-skill-awareness`, `feature/agent-skills`, `fix/cold-start-delivery-race`. - Worktree : **propre**. Dis-moi : (a) je lance le prochain chantier sur skill-awareness — confie d'abord à Architect l'arbitrage du chevauchement avec agent-skills ; (b) feu vert ou non pour supprimer `fix/cold-start-delivery-race`.
|
||||
- **Prompt:** Main reprend le cycle après cadrage Architect. État connu: repo `/home/anthony/Documents/Projects/IdeA`, branche actuelle `develop`, `develop...origin/develop [ahead 9]`, dirty: `.ideai/conversations/.../handoff.md`, `.ideai/conversations/.../log.jsonl`, `.ideai/layouts.json`, `.ideai/memory/MEMORY.md`, nouveau `.ideai/memory/checkpoint-blocked-until-appimage-030-restart.md`. Architect recommande de ne pas merger la vieille branche `feature/agent-skill-awareness`, mais de créer une branche neuve depuis `develop`, par ex. `feature/agent-skill-awareness-v2`. Merci de décider et effectuer uniquement les actions Git/locales nécessaires: gestion de branche, éventuellement commit/ignore/stash des fichiers runtime si tu le juges nécessaire selon ton rôle. Pas de push. Retourne: branche active, actions effectuées, état `git status --short --branch`, et consignes pour DevBackend/DevFrontend/QA.
|
||||
- **Response:** Décision Git appliquée (local uniquement, aucun push). **Branche active : `feature/agent-skill-awareness-v2`** (créée depuis `develop`, working tree propre). **Actions effectuées :** 1. Les 7 fichiers dirty étaient tous de l'état runtime `.ideai/` (conversations handoff+log, layouts.json, MEMORY.md, nouveau checkpoint-blocked-until-appimage-030-restart.md). Conformément à mon rôle (séparer code de feature et état runtime), je les ai committés sur `develop` : - `c... ` chore(wip): état runtime .ideai (conversations, layouts, mémoire, checkpoint blocage AppImage 0.3.0) 2. Création de la branche neuve `feature/agent-skill-awareness-v2` depuis `develop` (tip = ce commit). J'ai suivi la reco Architect : la vieille `feature/agent-skill-awareness` (5be8987) n'est PAS mergée et reste en place comme archive — repart d'une base develop propre, pas de dette d'historique divergent (develop avait 7 commits d'avance / la feature 5 d'écart). **`git status --short --branch` :** ``` ## feature/agent-skill-awareness-v2 ``` (working tree propre) **Contexte branches :** - `develop` : +9 puis +1 (le commit runtime) vs origin → désormais ahead 10, non poussé. - `feature/agent-skill-awareness` : intacte (5be8987), abandonnée, conservée comme archive. - `feature/agent-skill-awareness-v2` : NOUVELLE base de travail. **Consignes pour DevBackend / DevFrontend / QA :** - Travaillez tous sur **`feature/agent-skill-awareness-v2`**. Ne touchez pas à `develop` ni à la vieille `feature/agent-skill-awareness`. - Ne committez pas vous-mêmes : signalez-moi (via Main) quand l'implémentation est prête, je fais les commits atomiques (code feature séparé de l'état runtime `.ideai/`). - Rappel checkpoint mémoire : un blocage de validation e2e attendait un restart sur AppImage 0.3.0 — vérifier que l'AppImage qui tourne est bien rebuildée avant de conclure une validation (le binaire qui tourne = AppImage, pas les sources). - Quand QA est vert avec sortie réelle, revenez vers moi : je rebase si besoin puis merge `--no-ff` v2 → develop.
|
||||
- **Prompt:** Reprise chantier `feature/agent-skill-awareness-v2`. État observé: branche actuelle `feature/agent-skill-awareness-v2`, dirty avec changements code du hotfix livraison délégation/logs submit et fichiers runtime `.ideai/conversations/*`, `.ideai/layouts.json`, `.ideai/memory/MEMORY.md`, plus nouvelle mémoire `checkpoint-delivery-submit-logging-fix.md`. Merci de décider la stratégie locale: rester sur cette branche ou ajuster, quoi committer plus tard, quoi laisser hors commit/ignorer, et conditions avant commit/merge. Ne fais pas de commit pour l'instant.
|
||||
- **Response:** Décision Git (aucun commit effectué). ## État constaté - Branche courante : `feature/agent-skill-awareness-v2`, pointée exactement sur `e832af5` = tip de `develop`. `develop` est ancêtre de HEAD → **base saine, aucune divergence** (rebase inutile pour l'instant). - Working tree dirty, deux natures bien distinctes : - **Code (hotfix livraison délégation + logs submit)** — 8 fichiers, +352/-23 : - `crates/app-tauri/src/commands.rs`, `crates/application/src/orchestrator/service.rs`, `crates/infrastructure/src/input/mod.rs` - `frontend/src/adapters/input.ts`, `frontend/src/adapters/terminal.ts`, `frontend/src/domain/index.ts` - `frontend/src/features/terminals/useWritePortal.ts` + `useWritePortal.test.tsx` - **État runtime `.ideai/`** — conversations (handoff/log.jsonl ×3), `layouts.json`, `memory/MEMORY.md`, + nouveau `memory/checkpoint-delivery-submit-logging-fix.md` (non suivi). ## Stratégie de branche **On reste sur `feature/agent-skill-awareness-v2`.** Elle part proprement de `develop`, pas de divergence, c'est le bon support pour reprendre le chantier. Pas de nouvelle branche. (Réserve mineure : le nom de branche parle de « skill-awareness » alors que le diff actuel est un hotfix livraison/logs ; je l'assume car Main reprend ce chantier sous ce nom. Si tu préfères isoler le hotfix sous `fix/delivery-submit-logging`, dis-le, je crée et bascule — sinon on garde.) ## Plan de commit (PLUS TARD, sur ton GO) Deux commits atomiques séparés, par intention : 1. `fix(input): livraison délégation + journalisation submit` → les 8 fichiers code (crates + frontend + test). 2. `chore(wip): état runtime .ideai` → conversations, `layouts.json`, `MEMORY.md` + le nouveau checkpoint mémoire (cohérent avec l'historique `chore(wip)` existant). ## Hors commit / à laisser Rien à exclure ni à ignorer en plus pour l'instant : l'historique du dépôt versionne déjà l'état runtime `.ideai/` via des `chore(wip)`, donc je le committe à part plutôt que de le mélanger au code. (Remarque de fond : ces fichiers runtime sont du bruit de diff ; si tu veux un jour les `.gitignore`, c'est une décision produit séparée à trancher hors de ce ticket.) ## Conditions avant commit / merge - **Avant le commit code** : QA verte — tests Rust (`cargo test` sur les crates touchés) **et** front (`useWritePortal.test.tsx` notamment). Tant que c'est rouge, je ne committe pas le hotfix. - **Avant merge `feature/agent-skill-awareness-v2` → `develop`** : tests verts + rebase sur `develop` uniquement si elle a avancé entre-temps (actuellement non), puis merge `--no-ff`. - **Aucune action sortante** (push/PR) sans ton GO explicite. J'attends ton signal pour committer.
|
||||
- **Prompt:** QA final vert accepté avec réserve environnementale pour `feature/agent-skill-awareness-v2`. Merci de committer localement selon ta stratégie. État attendu: changements code hotfix délégation/logs submit + nouveau changement `crates/application/src/agent/lifecycle.rs` pour awareness skills; fichiers runtime `.ideai/*` modifiés et nouveau checkpoint mémoire. Conditions QA: application/front/infrastructure/app-tauri check verts; tests loopback socket Unix réels non exécutables dans sandbox (`UnixListener::bind` PermissionDenied), alternatives avec skips vertes. Merci de faire des commits atomiques adaptés (tu avais proposé code puis chore runtime), décider si le changement `lifecycle.rs` doit être dans un commit séparé du hotfix ou groupé selon intention, puis retourner hashes, messages, fichiers inclus/exclus, et état final. Pas de push.
|
||||
- **Response:** Commits effectués localement sur `feature/agent-skill-awareness-v2` (aucun push). Décision : `lifecycle.rs` = intention distincte (la feature réelle de la branche) → commit séparé du hotfix. ## 3 commits atomiques (par intention) **1. `befff76` — feat(skills): injecte un paragraphe d'awareness skills dans le fichier de convention** - Inclus : `crates/application/src/agent/lifecycle.rs` - La feature de la branche : `compose_convention_file` émet l'awareness skills (assigné = contexte opérationnel, pas commande magique/sous-tâche ; capitalisation via `idea_create_skill` en MCP / `skill.create` en protocole fichier), sans injecter les corps des skills non assignés. **2. `018eb1a` — fix(input): fiabilise la livraison de délégation et journalise le submit** - Inclus : `crates/app-tauri/src/commands.rs`, `crates/application/src/orchestrator/service.rs`, `crates/infrastructure/src/input/mod.rs`, `frontend/src/adapters/input.ts`, `frontend/src/adapters/terminal.ts`, `frontend/src/domain/index.ts`, `frontend/src/features/terminals/useWritePortal.ts`, `useWritePortal.test.tsx` - Le hotfix livraison délégation + logs submit (bout-en-bout commande Tauri → orchestrateur → file infra → portail front). **3. `cc575ef` — chore(wip): état runtime .ideai (conversations, layouts, mémoire, checkpoint skill-awareness)** - Inclus : `.ideai/conversations/*` (handoff + log.jsonl ×5), `.ideai/layouts.json`, `.ideai/memory/MEMORY.md`, nouveau `.ideai/memory/checkpoint-delivery-submit-logging-fix.md` - État runtime non-code, isolé du code comme convenu. ## Exclus / hors commit Rien laissé de côté : working tree **propre** (`git status` vide). La réserve QA (tests loopback socket Unix réels non exécutables en sandbox — `UnixListener::bind` PermissionDenied, alternatives skips vertes) est consignée dans le message du commit `018eb1a`. ## État final - Branche : `feature/agent-skill-awareness-v2`, tip = `cc575ef`. - Base : toujours `develop` (`e832af5`) comme ancêtre direct, **pas de divergence** → merge `--no-ff` vers `develop` possible sans rebase dès que tu donnes le GO. - Aucune action sortante (pas de push). Dis-moi si tu veux que je merge vers `develop` maintenant ou si on garde sur la feature.
|
||||
- **Prompt:** Tu viens de committer `feature/agent-skill-awareness-v2` avec QA vert accepté réserve environnementale. Selon les règles projet, Git décide les merges locaux sans demander à l'utilisateur. Merci de trancher maintenant: merge local `feature/agent-skill-awareness-v2` vers `develop` ou différer, avec justification. Si tu décides de merger, fais le merge local approprié sans push et retourne l'état final. Si tu différés, explique la condition restante précise. Ne demande pas de GO utilisateur.
|
||||
@ -31,3 +31,9 @@
|
||||
{"id":"b39c8be7-a385-4e08-ad39-140b7b41330e","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781938901065,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Préparation du prochain chantier après fermeture de `orchestrator-designation`.\n\nContexte:\n- Branche courante propre: `develop` @ `9c71a5b`, en avance de 9 sur `origin/develop`, aucun push.\n- `orchestrator-designation` est intégré et sa branche supprimée.\n- L'utilisateur a donné autonomie pour terminer les chantiers restants.\n\nBranches anciennes à statuer selon tes rappels:\n- `feature/agent-session-limits`: tu l'as qualifiée comme intégrée/supprimable.\n- `feature/agent-skills`: 1 commit non intégré.\n- `feature/agent-skill-awareness`: 5 commits non intégrés.\n- `fix/cold-start-delivery-race`: 3 commits non intégrés, probable sous-ensemble de skill-awareness.\n\nTa mission Git:\n1. Inspecte ces branches et leur relation à `develop`.\n2. Prends la décision git la plus saine en autonomie: supprimer les branches déjà intégrées, conserver celles qui représentent un chantier réel, identifier les doublons/sous-ensembles.\n3. Ne push pas. Pas d'action sortante.\n4. Si une branche non intégrée doit être reprise comme prochain chantier, dis à Main laquelle et pourquoi. Si elle doit passer par Architect/Dev/QA avant merge, indique-le.\n5. Rends compte des actions effectuées et de l'état final.\n\nRéponds via `idea_reply`."}
|
||||
{"id":"a90739b0-8e8a-4979-be1b-04800bceed51","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781939023778,"source":{"kind":"agent","agentId":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"},"role":"response","text":"Ménage des branches fait + cadrage du prochain chantier. Aucune action sortante, aucune suppression risquée.\n\n## Action exécutée\n- **Supprimé `feature/agent-session-limits`** (`git branch -d`, refus auto si non mergée) : 0 commit propre, entièrement intégrée dans develop. Suppression sûre.\n\n## Analyse des 3 branches restantes (relations git vérifiées)\n- **`feature/agent-skill-awareness`** — base `8452333`, 5 commits propres, NON intégrée. Le chantier le plus riche et récent : `ab34363` (manifeste de skills + outil MCP `idea_skill_read`), `e93a2c1` (fix cold-start delivery), `1a10d67` (test compteur outils MCP 11→12), `566bff4` (brief « capacités IdeA »), `5be8987` (runtime). **À conserver.**\n- **`feature/agent-skills`** — base `9736c42`, 1 commit `ef101db` (skills domain/usecases/FS store, L12), NON intégrée, **divergente** de skill-awareness (pas ancêtre). ⚠️ Elle touche **les mêmes fichiers** que skill-awareness (`domain/src/skill.rs`, `application/src/skill/*`, `infrastructure/src/store/skill.rs`) → ce sont très probablement **deux itérations concurrentes de la même couche skill**, pas des compléments. **Conservée**, mais l'arbitrage « laquelle fait foi » est une décision d'**architecture, pas git**.\n- **`fix/cold-start-delivery-race`** — base `6969dc7` (ligne main : elle traîne les commits de release 0.1.0/0.2.0). Son **seul contenu réel** est `9590eac` (fix cold-start), dont le **jumeau logique `e93a2c1` est déjà dans skill-awareness** (message identique). → **doublon superseded**. `git cherry` confirme qu'aucun de ces patchs n'est encore dans develop.\n\n## Décisions / recommandations\n1. **Prochain chantier = `feature/agent-skill-awareness`.** Pourquoi : c'est le travail le plus avancé et non intégré, et il englobe le fix cold-start de la branche `fix/cold-start-delivery-race`.\n2. **Il DOIT passer par le cycle avant tout merge**, dans cet ordre :\n - **Architect d'abord** : trancher le chevauchement skill-awareness ⟷ `feature/agent-skills` (couche skill implémentée sur les mêmes fichiers). Décider qui est la source de vérité (probablement skill-awareness subsume agent-skills) avant de figer quoi que ce soit.\n - **Dev/QA** : revalider vert, en particulier `1a10d67` (compteur d'outils MCP, qui a déjà bougé 11→12 puis 12→13 dans state.rs côté develop) et le fix cold-start.\n3. ⚠️ **Le rebase de skill-awareness sur develop VA conflicter** : la branche modifie `crates/app-tauri/src/state.rs` et `crates/infrastructure/src/input/mod.rs`, tous deux lourdement réécrits par orchestrator-designation déjà mergé dans develop. Ce n'est **pas un rebase aveugle** : je le ferai avec l'appui de Dev pour résoudre les conflits, puis QA revalide avant mon merge `--no-ff`.\n4. **`fix/cold-start-delivery-race` : je recommande sa suppression** (force `-D`, car non mergée), son contenu unique étant préservé dans skill-awareness et le reste n'étant que des commits de release déjà sur main. Je ne l'ai PAS forcée en autonomie (force-delete = action plus difficile à annuler). **Donne-moi le feu vert et je la supprime**, idéalement une fois skill-awareness intégré dans develop.\n\n## État final\n- Branche courante : **`develop`** @ `9c71a5b` (+9 vs origin, non poussé).\n- Branches : `develop`, `main` (@ `29232dd`), `feature/agent-skill-awareness`, `feature/agent-skills`, `fix/cold-start-delivery-race`.\n- Worktree : **propre**.\n\nDis-moi : (a) je lance le prochain chantier sur skill-awareness — confie d'abord à Architect l'arbitrage du chevauchement avec agent-skills ; (b) feu vert ou non pour supprimer `fix/cold-start-delivery-race`."}
|
||||
{"id":"abf3985b-d4d6-44b3-9451-b96a72129c88","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781939505436,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Main reprend le cycle après cadrage Architect. État connu: repo `/home/anthony/Documents/Projects/IdeA`, branche actuelle `develop`, `develop...origin/develop [ahead 9]`, dirty: `.ideai/conversations/.../handoff.md`, `.ideai/conversations/.../log.jsonl`, `.ideai/layouts.json`, `.ideai/memory/MEMORY.md`, nouveau `.ideai/memory/checkpoint-blocked-until-appimage-030-restart.md`. Architect recommande de ne pas merger la vieille branche `feature/agent-skill-awareness`, mais de créer une branche neuve depuis `develop`, par ex. `feature/agent-skill-awareness-v2`. Merci de décider et effectuer uniquement les actions Git/locales nécessaires: gestion de branche, éventuellement commit/ignore/stash des fichiers runtime si tu le juges nécessaire selon ton rôle. Pas de push. Retourne: branche active, actions effectuées, état `git status --short --branch`, et consignes pour DevBackend/DevFrontend/QA."}
|
||||
{"id":"73d95d25-9a00-4b31-912c-a9ccf4a93acc","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781939540668,"source":{"kind":"agent","agentId":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"},"role":"response","text":"Décision Git appliquée (local uniquement, aucun push).\n\n**Branche active : `feature/agent-skill-awareness-v2`** (créée depuis `develop`, working tree propre).\n\n**Actions effectuées :**\n1. Les 7 fichiers dirty étaient tous de l'état runtime `.ideai/` (conversations handoff+log, layouts.json, MEMORY.md, nouveau checkpoint-blocked-until-appimage-030-restart.md). Conformément à mon rôle (séparer code de feature et état runtime), je les ai committés sur `develop` :\n - `c... ` chore(wip): état runtime .ideai (conversations, layouts, mémoire, checkpoint blocage AppImage 0.3.0)\n2. Création de la branche neuve `feature/agent-skill-awareness-v2` depuis `develop` (tip = ce commit). J'ai suivi la reco Architect : la vieille `feature/agent-skill-awareness` (5be8987) n'est PAS mergée et reste en place comme archive — repart d'une base develop propre, pas de dette d'historique divergent (develop avait 7 commits d'avance / la feature 5 d'écart).\n\n**`git status --short --branch` :**\n```\n## feature/agent-skill-awareness-v2\n```\n(working tree propre)\n\n**Contexte branches :**\n- `develop` : +9 puis +1 (le commit runtime) vs origin → désormais ahead 10, non poussé.\n- `feature/agent-skill-awareness` : intacte (5be8987), abandonnée, conservée comme archive.\n- `feature/agent-skill-awareness-v2` : NOUVELLE base de travail.\n\n**Consignes pour DevBackend / DevFrontend / QA :**\n- Travaillez tous sur **`feature/agent-skill-awareness-v2`**. Ne touchez pas à `develop` ni à la vieille `feature/agent-skill-awareness`.\n- Ne committez pas vous-mêmes : signalez-moi (via Main) quand l'implémentation est prête, je fais les commits atomiques (code feature séparé de l'état runtime `.ideai/`).\n- Rappel checkpoint mémoire : un blocage de validation e2e attendait un restart sur AppImage 0.3.0 — vérifier que l'AppImage qui tourne est bien rebuildée avant de conclure une validation (le binaire qui tourne = AppImage, pas les sources).\n- Quand QA est vert avec sortie réelle, revenez vers moi : je rebase si besoin puis merge `--no-ff` v2 → develop."}
|
||||
{"id":"9f681540-d319-4ba7-af49-c5f09e46e7c2","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781945248461,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Reprise chantier `feature/agent-skill-awareness-v2`. État observé: branche actuelle `feature/agent-skill-awareness-v2`, dirty avec changements code du hotfix livraison délégation/logs submit et fichiers runtime `.ideai/conversations/*`, `.ideai/layouts.json`, `.ideai/memory/MEMORY.md`, plus nouvelle mémoire `checkpoint-delivery-submit-logging-fix.md`. Merci de décider la stratégie locale: rester sur cette branche ou ajuster, quoi committer plus tard, quoi laisser hors commit/ignorer, et conditions avant commit/merge. Ne fais pas de commit pour l'instant."}
|
||||
{"id":"361850dc-348a-4b6b-bfd5-100809bce59f","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781945295907,"source":{"kind":"agent","agentId":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"},"role":"response","text":"Décision Git (aucun commit effectué).\n\n## État constaté\n- Branche courante : `feature/agent-skill-awareness-v2`, pointée exactement sur `e832af5` = tip de `develop`. `develop` est ancêtre de HEAD → **base saine, aucune divergence** (rebase inutile pour l'instant).\n- Working tree dirty, deux natures bien distinctes :\n - **Code (hotfix livraison délégation + logs submit)** — 8 fichiers, +352/-23 :\n - `crates/app-tauri/src/commands.rs`, `crates/application/src/orchestrator/service.rs`, `crates/infrastructure/src/input/mod.rs`\n - `frontend/src/adapters/input.ts`, `frontend/src/adapters/terminal.ts`, `frontend/src/domain/index.ts`\n - `frontend/src/features/terminals/useWritePortal.ts` + `useWritePortal.test.tsx`\n - **État runtime `.ideai/`** — conversations (handoff/log.jsonl ×3), `layouts.json`, `memory/MEMORY.md`, + nouveau `memory/checkpoint-delivery-submit-logging-fix.md` (non suivi).\n\n## Stratégie de branche\n**On reste sur `feature/agent-skill-awareness-v2`.** Elle part proprement de `develop`, pas de divergence, c'est le bon support pour reprendre le chantier. Pas de nouvelle branche. (Réserve mineure : le nom de branche parle de « skill-awareness » alors que le diff actuel est un hotfix livraison/logs ; je l'assume car Main reprend ce chantier sous ce nom. Si tu préfères isoler le hotfix sous `fix/delivery-submit-logging`, dis-le, je crée et bascule — sinon on garde.)\n\n## Plan de commit (PLUS TARD, sur ton GO)\nDeux commits atomiques séparés, par intention :\n1. `fix(input): livraison délégation + journalisation submit` → les 8 fichiers code (crates + frontend + test).\n2. `chore(wip): état runtime .ideai` → conversations, `layouts.json`, `MEMORY.md` + le nouveau checkpoint mémoire (cohérent avec l'historique `chore(wip)` existant).\n\n## Hors commit / à laisser\nRien à exclure ni à ignorer en plus pour l'instant : l'historique du dépôt versionne déjà l'état runtime `.ideai/` via des `chore(wip)`, donc je le committe à part plutôt que de le mélanger au code. (Remarque de fond : ces fichiers runtime sont du bruit de diff ; si tu veux un jour les `.gitignore`, c'est une décision produit séparée à trancher hors de ce ticket.)\n\n## Conditions avant commit / merge\n- **Avant le commit code** : QA verte — tests Rust (`cargo test` sur les crates touchés) **et** front (`useWritePortal.test.tsx` notamment). Tant que c'est rouge, je ne committe pas le hotfix.\n- **Avant merge `feature/agent-skill-awareness-v2` → `develop`** : tests verts + rebase sur `develop` uniquement si elle a avancé entre-temps (actuellement non), puis merge `--no-ff`.\n- **Aucune action sortante** (push/PR) sans ton GO explicite.\n\nJ'attends ton signal pour committer."}
|
||||
{"id":"c554b89b-b920-4d50-b387-a315bb494c5a","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781945808651,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"QA final vert accepté avec réserve environnementale pour `feature/agent-skill-awareness-v2`. Merci de committer localement selon ta stratégie. État attendu: changements code hotfix délégation/logs submit + nouveau changement `crates/application/src/agent/lifecycle.rs` pour awareness skills; fichiers runtime `.ideai/*` modifiés et nouveau checkpoint mémoire. Conditions QA: application/front/infrastructure/app-tauri check verts; tests loopback socket Unix réels non exécutables dans sandbox (`UnixListener::bind` PermissionDenied), alternatives avec skips vertes. Merci de faire des commits atomiques adaptés (tu avais proposé code puis chore runtime), décider si le changement `lifecycle.rs` doit être dans un commit séparé du hotfix ou groupé selon intention, puis retourner hashes, messages, fichiers inclus/exclus, et état final. Pas de push."}
|
||||
{"id":"601fabf2-4a39-45aa-a153-81637732f09c","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781945860170,"source":{"kind":"agent","agentId":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"},"role":"response","text":"Commits effectués localement sur `feature/agent-skill-awareness-v2` (aucun push). Décision : `lifecycle.rs` = intention distincte (la feature réelle de la branche) → commit séparé du hotfix.\n\n## 3 commits atomiques (par intention)\n\n**1. `befff76` — feat(skills): injecte un paragraphe d'awareness skills dans le fichier de convention**\n- Inclus : `crates/application/src/agent/lifecycle.rs`\n- La feature de la branche : `compose_convention_file` émet l'awareness skills (assigné = contexte opérationnel, pas commande magique/sous-tâche ; capitalisation via `idea_create_skill` en MCP / `skill.create` en protocole fichier), sans injecter les corps des skills non assignés.\n\n**2. `018eb1a` — fix(input): fiabilise la livraison de délégation et journalise le submit**\n- Inclus : `crates/app-tauri/src/commands.rs`, `crates/application/src/orchestrator/service.rs`, `crates/infrastructure/src/input/mod.rs`, `frontend/src/adapters/input.ts`, `frontend/src/adapters/terminal.ts`, `frontend/src/domain/index.ts`, `frontend/src/features/terminals/useWritePortal.ts`, `useWritePortal.test.tsx`\n- Le hotfix livraison délégation + logs submit (bout-en-bout commande Tauri → orchestrateur → file infra → portail front).\n\n**3. `cc575ef` — chore(wip): état runtime .ideai (conversations, layouts, mémoire, checkpoint skill-awareness)**\n- Inclus : `.ideai/conversations/*` (handoff + log.jsonl ×5), `.ideai/layouts.json`, `.ideai/memory/MEMORY.md`, nouveau `.ideai/memory/checkpoint-delivery-submit-logging-fix.md`\n- État runtime non-code, isolé du code comme convenu.\n\n## Exclus / hors commit\nRien laissé de côté : working tree **propre** (`git status` vide). La réserve QA (tests loopback socket Unix réels non exécutables en sandbox — `UnixListener::bind` PermissionDenied, alternatives skips vertes) est consignée dans le message du commit `018eb1a`.\n\n## État final\n- Branche : `feature/agent-skill-awareness-v2`, tip = `cc575ef`.\n- Base : toujours `develop` (`e832af5`) comme ancêtre direct, **pas de divergence** → merge `--no-ff` vers `develop` possible sans rebase dès que tu donnes le GO.\n- Aucune action sortante (pas de push).\n\nDis-moi si tu veux que je merge vers `develop` maintenant ou si on garde sur la feature."}
|
||||
{"id":"30bb2b67-c173-43d1-9776-48f2f597e90b","conversation":"6bc594e8-a37c-0dbd-1de6-6e3b73002cb4","atMs":1781945865963,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Tu viens de committer `feature/agent-skill-awareness-v2` avec QA vert accepté réserve environnementale. Selon les règles projet, Git décide les merges locaux sans demander à l'utilisateur. Merci de trancher maintenant: merge local `feature/agent-skill-awareness-v2` vers `develop` ou différer, avec justification. Si tu décides de merger, fais le merge local approprié sans push et retourne l'état final. Si tu différés, explique la condition restante précise. Ne demande pas de GO utilisateur."}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -62,3 +62,8 @@
|
||||
{"id":"96a4277d-ea90-4170-9eb1-52cf399408e3","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781736062933,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Suite de la feature orchestrateur, branche `feature/orchestrator-designation`. T1 (domaine) est vert. Implémente maintenant **T2 → T5** (infra + application backend) pour rétablir un workspace qui compile et tester. Suis le cadrage Architect, notamment l'**Alternative A** retenue.\n\n### T2 — Infra : guard = verrou pur + serde rétro-compatible (dépend T1)\n- **Alternative A (RETENUE)** : `RwFileGuard::acquire_write` (`crates/infrastructure/src/fileguard/mod.rs`) NE fait plus le check d'identité orchestrateur. Il redevient un **verrou pur** (sérialise les writers, comme un rwlock). Donc retire l'appel `may_write_directly` côté guard (la ligne ~93). L'autorisation single-writer remonte dans `ProposeContext` (T3). `GuardError::Forbidden` n'est plus émis par le guard — vérifie ce que ça implique pour le port/les tests du guard (déplace/retire le test « Forbidden » qui n'a plus lieu d'être à ce niveau, documente que le guard est désormais un lock pur). Si la signature du port `FileGuard::acquire_write` portait un paramètre lié à l'identité, garde-la cohérente.\n- **Serde** : corrige tous les littéraux exhaustifs `AgentManifest { version, entries }` cassés par le nouveau champ (ajoute `orchestrator: None` ou `..Default::default()`). Test round-trip : un `agents.json` legacy SANS le champ `orchestrator` se désérialise → `None` → `effective_orchestrator()` = plus ancien.\n\n### T3 — Application : autorisation propose (cœur MCP) (dépend T1, T2)\nDans `ProposeContext` (`crates/application/.../context_guard.rs`), branche globale (target = None) :\n```\nlet manifest = contexts.load_manifest(project).await?;\nlet d = manifest.orchestrator_designation();\nif may_write_directly(requester, &GuardedResource::ProjectContext, &d) {\n let _lease = guard.acquire_write(requester, ProjectContext).await?; // sérialise\n fs.write(project_context_file, content) -> Written\n} else {\n file_proposal(...) -> Proposed { path } // inchangé\n}\n```\nConséquence : quand l'appelant `idea_context_propose` (sans target) EST l'orchestrateur désigné, l'écriture devient DIRECTE ; sinon proposition ; l'humain écrit toujours direct. Tests : agent désigné → write direct ; agent non-désigné → proposition ; humain → direct.\n\n### T4 — Application : défaut + succession (dépend T1)\n- `DeleteAgent::execute` (`lifecycle.rs`) : après filtrage des entrées, appelle `manifest.on_agent_deleted(removed)` avant `save_manifest` ; émets `DomainEvent::OrchestratorChanged` si `effective_orchestrator()` a changé.\n- Défaut (Create scratch + template) : pas de logique à ajouter (modèle paresseux), mais ajoute les tests qui figent « 1er agent créé = orchestrateur effectif » et l'émission de `OrchestratorChanged` si l'effectif passe de None→1er.\n- Tests succession : désigné explicite supprimé → retombe sur plus ancien ; non-désigné supprimé → repointage paresseux.\n\n### T5 — Application : `SetOrchestrator` + ListAgents output (dépend T1)\n- Nouveau use case `SetOrchestrator { project, agent_id }` : `load_manifest` → `manifest.designate(agent_id)` (radio, écrase) → `save_manifest` → publie `OrchestratorChanged`. `NotFound`/erreur domaine si l'agent n'existe pas.\n- Étends `ListAgentsOutput` avec `orchestrator: Option<AgentId>` (= `effective_orchestrator()`).\n- Tests (store mock) : écrasement radio, agent inconnu → erreur, output porte l'effectif.\n\nRespecte l'archi hexagonale, SOLID, le style existant, aucun nouveau port (compose FileGuard / AgentContextStore / EventBus existants). Compile et teste les crates infra + application (`cargo test -p infrastructure -p application` ou vrais noms). Quand vert, réponds via `idea_reply` : fichiers touchés, signatures publiques finales de `SetOrchestrator` et du champ ajouté à `ListAgentsOutput` (accroche pour T6), et la sortie réelle des tests. Si rouge, donne l'erreur réelle."}
|
||||
{"id":"d45770fd-efa2-40b6-8dae-2bd93716b496","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781938190834,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Tâche ciblée sur la branche courante `feature/orchestrator-designation`.\n\nContexte:\n- Git a décidé de finir le chantier courant `orchestrator-designation` sur cette branche, sans switch/stash.\n- Main ne code pas; tu es chargé de la correction backend Rust.\n- Tests ciblés verts:\n - `cargo test -p infrastructure input --lib`: 35 passed.\n - `cargo test -p application --test orchestrator_service`: 45 passed.\n- Frontend vert:\n - `cd frontend && npx vitest run`: 41 files, 384 tests passed.\n - `cd frontend && npx tsc --noEmit`: OK.\n\nProblème à corriger:\n`cargo test --workspace` échoue à la compilation sur `crates/application/src/orchestrator/context_guard.rs`.\n\nSortie réelle:\n```text\nerror[E0061]: this function takes 3 arguments but 2 arguments were supplied\n --> crates/application/src/orchestrator/context_guard.rs:449:17\n |\n449 | if !may_write_directly(who, &res) {\n | ^^^^^^^^^^^^^^^^^^----------- argument #3 of type `&OrchestratorDesignation` is missing\n\nerror[E0560]: struct `ManifestEntry` has no field named `orchestrator`\n --> crates/application/src/orchestrator/context_guard.rs:616:21\n |\n616 | orchestrator: None,\n | ^^^^^^^^^^^^ `ManifestEntry` does not have this field\n\nerror[E0063]: missing field `orchestrator` in initializer of `AgentManifest`\n --> crates/application/src/orchestrator/context_guard.rs:613:23\n |\n613 | manifest: AgentManifest {\n | ^^^^^^^^^^^^^ missing `orchestrator`\n```\n\nAttendu:\n- Adapter `context_guard.rs` au nouveau modèle `AgentManifest { version, entries, orchestrator }` et à `may_write_directly(resource, designation)` sans contourner l'architecture.\n- Supprimer l'import inutilisé si nécessaire.\n- Garder le domaine propriétaire des règles de désignation orchestrateur.\n- Ne touche pas aux fichiers `.ideai/*` runtime.\n\nValidation minimale à exécuter après correction:\n1. `cargo test -p application --test orchestrator_service`\n2. `cargo test --workspace` si raisonnable.\n\nRéponds avec les fichiers modifiés et les commandes/test outputs réels. Si cette tâche arrive via IdeA, utilise impérativement `idea_reply`."}
|
||||
{"id":"edf1ef36-9b9f-41f8-9fde-dfddf09366f9","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781938335787,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"response","text":"Correction backend appliquée sur `crates/application/src/orchestrator/context_guard.rs`.\n\nModifications:\n- `ProposeContext` charge maintenant `AgentManifest`, récupère `manifest.orchestrator_designation()`, puis appelle la règle domaine `may_write_directly(requester, &GuardedResource::ProjectContext, &designation)` avant toute écriture globale.\n- Le `FileGuard` reste un verrou de sérialisation: la décision orchestrateur/proposition reste dans le domaine via `may_write_directly`.\n- Le commentaire obsolète qui disait que le guard renvoyait `Forbidden` pour les non-orchestrateurs a été mis à jour.\n- Les tests locaux de `context_guard.rs` ont été adaptés au modèle `AgentManifest { version, entries, orchestrator }`.\n- Le fake `TestGuard` ne réimplémente plus l’autorisation; il reflète `infrastructure::RwFileGuard` comme verrou pur.\n\nFichier modifié par moi:\n- `crates/application/src/orchestrator/context_guard.rs`\n\nValidations exécutées:\n1. `cargo fmt --all && cargo test -p application --test orchestrator_service`\n - Résultat: OK\n - Sortie utile: `45 passed; 0 failed; 0 ignored`\n - Warning existant: `method writes is never used` dans `crates/application/tests/orchestrator_service.rs:1642`.\n\n2. `cargo test --workspace`\n - Résultat: compilation passée jusqu’à l’exécution des tests, puis échec dans `app-tauri` sur tests loopback Unix socket sous le sandbox courant.\n - Sortie utile: `app-tauri` lib: `39 passed; 8 failed`.\n - Échecs: `mcp_bridge::tests::end_to_end_over_real_loopback`, `state::bind_endpoint_d1_tests::rebind_after_corpse_socket_succeeds`, et les tests `state::mcp_e2e_loopback_tests::*`.\n - Cause observée: `PermissionDenied` / `Operation not permitted` lors du bind ou de la pose de socket sous `/run/user/1000/idea-mcp/*.sock`, par exemple `bind test listener: Os { code: 1, kind: PermissionDenied, message: \"Operation not permitted\" }`.\n\n3. `cargo test -p application`\n - Résultat: OK\n - Sortie utile: tous les tests application sont verts, dont `orchestrator::context_guard`.\n - Extraits: lib `41 passed`; `orchestrator_service` `45 passed`; suite application complète terminée avec succès.\n\nNote: je n’ai pas touché aux fichiers `.ideai/*` runtime. Le `git status` montre beaucoup de fichiers déjà modifiés dans le workspace, dont `.ideai/*`; je les ai laissés tels quels."}
|
||||
{"id":"f609c293-be61-4e83-bfad-70cdcd2c815d","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781939555870,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Implémente le backend du chantier `feature/agent-skill-awareness-v2` sur `/home/anthony/Documents/Projects/IdeA`, branche active préparée par Git. Respecte le cadrage Architect ci-dessous et ne commit pas.\n\nPérimètre backend attendu:\n1. Domaine Skill: ajouter `description: Option<String>` avec serde default/rétrocompat, helpers `with_description`, `effective_description`, et préserver la description dans `with_content`.\n2. Persistance FsSkillStore/index: roundtrip description, legacy index sans description OK.\n3. Use cases/DTO Tauri: `CreateSkillInput` et update skill doivent pouvoir porter `description`; les DTO exposent `description`.\n4. Read-only skill body: ajouter use case/read path pour `idea_skill_read(name)` via port existant `SkillStore`; résolution case-insensitive, project scope d'abord puis global, erreur claire si absent/ambigu intra-scope.\n5. Orchestrator/MCP: ajouter commande/action `skill.read` ou équivalent local selon patterns existants, outil MCP `idea_skill_read` avec input `{ name: string }`, mapping + dispatch service/state.\n6. Convention file/lifecycle: pour profils MCP avec skills assignés, injecter une section haute `# Skills disponibles` listant `name + description` et mentionnant `idea_skill_read(name=...)`; ne pas inclure le body complet dans ce mode. Pour non-MCP, conserver le comportement existant de dump complet pour éviter régression. Ordre déterministe selon assignation; section omise si zéro skill.\n7. Tests backend ciblés à ajouter/adapter autant que possible. Évite les compteurs MCP hardcodés; assert par nom de tool.\n\nNe reprends pas la vieille branche brute. Si une zone est ambiguë, suis les patterns existants. Retourne: fichiers modifiés, commandes de test exécutées et résultats, risques/points à passer à DevFrontend/QA."}
|
||||
{"id":"531c2206-e233-4ae7-b76e-dd25ece10805","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781945306231,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Implémente le MVP `agent-skill-awareness-v2` cadré par Architect. Périmètre strict: application Rust, pas de frontend, pas de nouveaux ports/DTO/stores. À modifier principalement `crates/application/src/agent/lifecycle.rs` dans `compose_convention_file(...)` ou helper pur.\n\nObjectif: à chaque activation d'agent, le convention file doit inclure une section courte et testable expliquant l'usage des skills IdeA:\n- les sections sous `# Skills` sont des workflows assignés utilisables quand pertinents;\n- un skill assigné est du contexte opérationnel, pas une commande magique ni une sous-tâche fournisseur;\n- si l'agent identifie un workflow réutilisable, il doit passer par l'orchestration IdeA: `idea_create_skill` quand `mcp_enabled=true`, sinon protocole fichier `skill.create`;\n- les skills non assignés ne doivent pas être injectés intégralement à tous les agents, l'assignation reste la frontière.\n\nContraintes: préserver l'ordre existant project root -> orchestration/awareness -> contexte projet -> persona -> skills -> mémoire -> handoff; ne pas créer `# Skills` quand aucun skill n'est assigné si le contrat actuel le fait déjà; préserver l'ordre des bodies de skills.\n\nAjoute/ajuste les tests de composition: no-skills awareness présente mais pas `# Skills`; with-skills awareness avant bodies et ordre manifeste conservé; variantes MCP vs fichier pour `idea_create_skill` / `skill.create`.\n\nNe committe pas. Réponds avec fichiers modifiés et commandes de vérification exécutées."}
|
||||
{"id":"812e96b5-8340-48c7-bbff-a0466d84eacd","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781945488377,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"response","text":"Implémentation MVP `agent-skill-awareness-v2` faite, sans commit.\n\nFichier code modifié :\n- `crates/application/src/agent/lifecycle.rs`\n\nChangements :\n- Ajout d’un helper pur `skill_awareness(mcp_enabled)` injecté dans `compose_convention_file(...)` dans le bloc orchestration/awareness, avant `# Contexte projet`, persona, skills, mémoire et handoff.\n- Awareness toujours présente à l’activation, sans ouvrir de section `# Skills` quand aucun skill n’est assigné.\n- Variante MCP : mention de `idea_create_skill`.\n- Variante fichier : mention du protocole `skill.create`.\n- Les bodies des skills assignés restent injectés sous `# Skills`, dans l’ordre existant fourni par l’appelant.\n- Aucun port/DTO/store/frontend ajouté ou modifié.\n\nTests/format exécutés :\n- `cargo test -p application compose_convention_file --no-fail-fast` : OK, 15 passed.\n- `cargo fmt -p application` : OK.\n- `cargo test -p application` : OK, toute la crate application verte. Seule alerte observée, préexistante/non liée : `method writes is never used` dans `crates/application/tests/orchestrator_service.rs:1642`.\n\nValidation QA : vert. QA a relu le diff ciblé et réexécuté :\n- `cargo test -p application compose_convention_file --no-fail-fast` : OK.\n- `cargo test -p application` : OK.\n\nNote état repo : `git status` contient d’autres fichiers déjà modifiés/non liés au périmètre (`.ideai/*`, app-tauri, infrastructure, frontend, etc.). Je ne les ai pas revert ni intégrés au changement backend demandé."}
|
||||
{"id":"e9caa4cd-a4ca-4aef-9ecb-74be3a95df26","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781945649980,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"QA bloque sur `cargo test -p app-tauri --test orchestrator_wiring`: 4 tests loopback socket échouent car socket `/run/user/1000/idea-mcp/<project>.sock` non bindé. Compléments QA: `cargo test -p app-tauri bind_endpoint -- --nocapture` échoue avec `PermissionDenied` sur `lay corpse socket`; `cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` échoue car `bind_endpoint returned None`. Merci de diagnostiquer si c'est une contrainte d'environnement/sandbox ou une régression code liée aux changements récents. Si correction applicative/test nécessaire, implémente-la sans toucher aux fichiers runtime `.ideai`. Sinon, fournis une justification précise et une commande de vérification alternative. Ne committe pas."}
|
||||
{"id":"00aa406e-b715-4f9b-9978-f52b9a37e27f","conversation":"d5068bc8-786e-0428-1d14-83b93961a15e","atMs":1781945759965,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"response","text":"Diagnostic : blocage environnement/sandbox, pas régression applicative liée au MVP `agent-skill-awareness-v2` ni aux changements récents.\n\nConstats :\n- `cargo test -p app-tauri bind_endpoint -- --nocapture` échoue exactement comme QA : `lay corpse socket: Os { code: 1, kind: PermissionDenied, message: \"Operation not permitted\" }` sur `std::os::unix::net::UnixListener::bind`.\n- `cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` échoue car `bind_endpoint` retourne `None` pour `/run/user/1000/idea-mcp/<project>.sock`.\n- Même en forçant `XDG_RUNTIME_DIR=/tmp`, les tests loopback échouent : `bind_endpoint returned None for \"/tmp/idea-mcp/<project>.sock\"`.\n- Smoke test Rust minimal hors repo, compilé dans `/tmp`, confirme que le sandbox interdit `AF_UNIX` path sockets partout :\n - `/tmp/idea-socket-smoke-*.sock` => `PermissionDenied / Operation not permitted`\n - `/run/user/1000/idea-socket-smoke-*.sock` => `PermissionDenied / Operation not permitted`\n\nInterprétation :\n- Les tests qui échouent nécessitent un vrai listener Unix domain socket. L’échec se produit avant la logique IdeA, au niveau syscall de bind socket.\n- Le code récent `agent-skill-awareness-v2` ne touche pas `app-tauri`, `state.rs`, `mcp_endpoint.rs`, `mcp_bridge.rs`, ni la dérivation/binding des endpoints.\n- `mcp_endpoint` choisit correctement `$XDG_RUNTIME_DIR` puis `/tmp`; le fait que `/tmp` échoue aussi exclut un simple problème de permissions de `/run/user/1000`.\n\nCommandes de vérification exécutées :\n- `cargo test -p app-tauri bind_endpoint -- --nocapture` => échoue `PermissionDenied`, reproduit QA.\n- `cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` => échoue `bind_endpoint returned None`, reproduit QA.\n- `XDG_RUNTIME_DIR=/tmp cargo test -p app-tauri bind_endpoint -- --nocapture` => échoue aussi `PermissionDenied`.\n- `XDG_RUNTIME_DIR=/tmp cargo test -p app-tauri mcp_e2e_loopback_tests -- --nocapture` => échoue aussi `bind_endpoint returned None`.\n- Smoke Rust minimal avec `UnixListener::bind` dans `/tmp` et `/run/user/1000` => échoue `PermissionDenied` dans les deux cas.\n\nCommandes alternatives vertes dans ce sandbox, en excluant uniquement les tests qui nécessitent un vrai socket Unix :\n```text\ncargo test -p app-tauri --test orchestrator_wiring -- --skip open_binds_the_project_loopback_endpoint --skip double_open_keeps_a_single_endpoint_no_address_in_use --skip close_cleans_up_the_endpoint_socket_file --skip file_watcher_and_loopback_endpoint_live_together\n```\nRésultat : OK, 9 passed, 4 filtered out.\n\n```text\ncargo test -p app-tauri --lib -- --skip state::mcp_e2e_loopback_tests --skip state::bind_endpoint_d1_tests --skip mcp_bridge::tests::end_to_end_over_real_loopback\n```\nRésultat : OK, 39 passed, 8 filtered out.\n\nAucune correction code/test appliquée. Je n’ai pas touché aux fichiers runtime `.ideai`."}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
---
|
||||
upTo: 419c60a5-2aca-4711-ad83-0a185bb3214e
|
||||
upTo: 49eb022e-28c5-4704-a629-9a28aa8901ea
|
||||
objective: Ping de test inter-agent depuis DevBackend — réponds avec un court PONG et l'heure que tu vois.
|
||||
---
|
||||
**Objectif :** Ping de test inter-agent depuis DevBackend — réponds avec un court PONG et l'heure que tu vois.
|
||||
|
||||
- **Prompt:** Ping de test inter-agent depuis DevBackend — réponds avec un court PONG et l'heure que tu vois.
|
||||
- **Prompt:** Validation ciblée du MVP `agent-skill-awareness-v2` côté application Rust. Périmètre modifié: uniquement `crates/application/src/agent/lifecycle.rs`, dans `compose_convention_file(...)` + helper pur `skill_awareness`. Changement: injection d'un paragraphe `**Skills IdeA**` dans le bloc orchestration/awareness à chaque activation, avant contexte projet/persona; variante MCP mentionne `idea_create_skill`, variante non-MCP mentionne protocole fichier `skill.create`; aucun `# Skills` section ajouté quand aucun skill assigné; bodies de skills assignés restent sous `# Skills` dans l'ordre existant. Tests ajoutés/ajustés dans les tests unitaires internes de `lifecycle.rs`: awareness présente sans section Skills, awareness avant contexte projet/persona, bodies sous `# Skills` dans l'ordre, variantes MCP/fichier. Commandes déjà exécutées par DevBackend: - `cargo test -p application compose_convention_file --no-fail-fast` => 15 tests passés - `cargo fmt -p application` => OK - `cargo test -p application` => OK, 43 unit + tous tests intégration application + doctests, seule alerte existante `method writes is never used` dans `tests/orchestrator_service.rs`. Merci de relire/exécuter ce que tu juges nécessaire et répondre verdict QA: vert ou corrections requises.
|
||||
- **Response:** Verdict QA : vert. Relu le diff ciblé de `crates/application/src/agent/lifecycle.rs` : l’injection `**Skills IdeA**` est placée dans le bloc orchestration avant le contexte projet/persona, les variantes MCP/fichier pointent respectivement vers `idea_create_skill` et `skill.create`, aucun `# Skills` n’est ouvert sans skill assigné, et les bodies assignés restent sous `# Skills` dans l’ordre. Commandes exécutées : ```text cargo test -p application compose_convention_file --no-fail-fast ``` Sortie pertinente : ```text running 15 tests ... test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 28 filtered out; finished in 0.00s ``` Alerte existante observée : ```text warning: method `writes` is never used --> crates/application/tests/orchestrator_service.rs:1642:8 ``` Commande de régression crate : ```text cargo test -p application ``` Sortie pertinente : ```text running 43 tests ... test result: ok. 43 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.10s ... Doc-tests application running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` Tous les tests d’intégration `application` listés dans la suite sont passés également. Aucune correction requise pour ce MVP.
|
||||
@ -1 +1,3 @@
|
||||
{"id":"419c60a5-2aca-4711-ad83-0a185bb3214e","conversation":"dd35eeb0-2329-0dfa-3255-df7dba90a0aa","atMs":1781427081894,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"prompt","text":"Ping de test inter-agent depuis DevBackend — réponds avec un court PONG et l'heure que tu vois."}
|
||||
{"id":"558b7648-c032-4f11-b2cf-dc763888fb1c","conversation":"dd35eeb0-2329-0dfa-3255-df7dba90a0aa","atMs":1781945435488,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"prompt","text":"Validation ciblée du MVP `agent-skill-awareness-v2` côté application Rust. Périmètre modifié: uniquement `crates/application/src/agent/lifecycle.rs`, dans `compose_convention_file(...)` + helper pur `skill_awareness`. Changement: injection d'un paragraphe `**Skills IdeA**` dans le bloc orchestration/awareness à chaque activation, avant contexte projet/persona; variante MCP mentionne `idea_create_skill`, variante non-MCP mentionne protocole fichier `skill.create`; aucun `# Skills` section ajouté quand aucun skill assigné; bodies de skills assignés restent sous `# Skills` dans l'ordre existant.\n\nTests ajoutés/ajustés dans les tests unitaires internes de `lifecycle.rs`: awareness présente sans section Skills, awareness avant contexte projet/persona, bodies sous `# Skills` dans l'ordre, variantes MCP/fichier.\n\nCommandes déjà exécutées par DevBackend:\n- `cargo test -p application compose_convention_file --no-fail-fast` => 15 tests passés\n- `cargo fmt -p application` => OK\n- `cargo test -p application` => OK, 43 unit + tous tests intégration application + doctests, seule alerte existante `method writes is never used` dans `tests/orchestrator_service.rs`.\n\nMerci de relire/exécuter ce que tu juges nécessaire et répondre verdict QA: vert ou corrections requises."}
|
||||
{"id":"49eb022e-28c5-4704-a629-9a28aa8901ea","conversation":"dd35eeb0-2329-0dfa-3255-df7dba90a0aa","atMs":1781945461642,"source":{"kind":"agent","agentId":"aefdbd61-e3d4-4bc1-9f42-c259446a97b5"},"role":"response","text":"Verdict QA : vert.\n\nRelu le diff ciblé de `crates/application/src/agent/lifecycle.rs` : l’injection `**Skills IdeA**` est placée dans le bloc orchestration avant le contexte projet/persona, les variantes MCP/fichier pointent respectivement vers `idea_create_skill` et `skill.create`, aucun `# Skills` n’est ouvert sans skill assigné, et les bodies assignés restent sous `# Skills` dans l’ordre.\n\nCommandes exécutées :\n\n```text\ncargo test -p application compose_convention_file --no-fail-fast\n```\n\nSortie pertinente :\n\n```text\nrunning 15 tests\n...\ntest result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 28 filtered out; finished in 0.00s\n```\n\nAlerte existante observée :\n\n```text\nwarning: method `writes` is never used\n --> crates/application/tests/orchestrator_service.rs:1642:8\n```\n\nCommande de régression crate :\n\n```text\ncargo test -p application\n```\n\nSortie pertinente :\n\n```text\nrunning 43 tests\n...\ntest result: ok. 43 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.10s\n...\nDoc-tests application\nrunning 0 tests\n\ntest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\n```\n\nTous les tests d’intégration `application` listés dans la suite sont passés également. Aucune correction requise pour ce MVP."}
|
||||
|
||||
@ -1,56 +1,18 @@
|
||||
{
|
||||
"version": 1,
|
||||
"activeId": "c44601af-8100-4553-8bf2-e660dbc9309c",
|
||||
"activeId": "0ac56658-4f42-47fe-ad03-c088c832460a",
|
||||
"layouts": [
|
||||
{
|
||||
"id": "c44601af-8100-4553-8bf2-e660dbc9309c",
|
||||
"id": "0ac56658-4f42-47fe-ad03-c088c832460a",
|
||||
"name": "Default",
|
||||
"kind": "terminal",
|
||||
"tree": {
|
||||
"root": {
|
||||
"type": "split",
|
||||
"node": {
|
||||
"id": "ab32e1cf-0e12-4d52-b2fb-2e751d5ca96b",
|
||||
"direction": "row",
|
||||
"children": [
|
||||
{
|
||||
"node": {
|
||||
"type": "leaf",
|
||||
"node": {
|
||||
"id": "fb3a974b-8054-46b4-afb1-f7a0d8c54308",
|
||||
"session": "8912d5e9-921f-4d20-84af-6a68ad4ace9f",
|
||||
"agent": "a6ced819-b893-4213-b003-9e9dc79b9641",
|
||||
"agentWasRunning": true
|
||||
}
|
||||
},
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"node": {
|
||||
"type": "leaf",
|
||||
"node": {
|
||||
"id": "997ad931-ee93-4430-991d-e82996b2d87d",
|
||||
"session": "07cd76a1-e6c0-49fc-a320-cca8c0d010aa",
|
||||
"agent": "dce19c75-9669-4e45-b8de-9950025157da",
|
||||
"agentWasRunning": true
|
||||
}
|
||||
},
|
||||
"weight": 1.0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "478acc7a-1afc-4cc0-9b1e-108eb83c5f5c",
|
||||
"name": "Git Graph",
|
||||
"kind": "gitGraph",
|
||||
"tree": {
|
||||
"root": {
|
||||
"type": "leaf",
|
||||
"node": {
|
||||
"id": "c74eb1dc-0831-4f62-88dc-c08ec1854274"
|
||||
"id": "d4b8c0d1-a44a-4c45-bbe9-26991f79b465",
|
||||
"session": "d5e00a5c-591e-4061-9104-c9ea7c9e01c3",
|
||||
"agent": "a6ced819-b893-4213-b003-9e9dc79b9641"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,3 +13,4 @@
|
||||
- [checkpoint-orchestrator-designation-qa-verdict](checkpoint-orchestrator-designation-qa-verdict.md) — memory note checkpoint-orchestrator-designation-qa-verdict
|
||||
- [checkpoint-orchestrator-designation-appimage-build](checkpoint-orchestrator-designation-appimage-build.md) — memory note checkpoint-orchestrator-designation-appimage-build
|
||||
- [checkpoint-blocked-until-appimage-030-restart](checkpoint-blocked-until-appimage-030-restart.md) — memory note checkpoint-blocked-until-appimage-030-restart
|
||||
- [checkpoint-delivery-submit-logging-fix](checkpoint-delivery-submit-logging-fix.md) — memory note checkpoint-delivery-submit-logging-fix
|
||||
|
||||
88
.ideai/memory/checkpoint-delivery-submit-logging-fix.md
Normal file
88
.ideai/memory/checkpoint-delivery-submit-logging-fix.md
Normal file
@ -0,0 +1,88 @@
|
||||
---
|
||||
name: checkpoint-delivery-submit-logging-fix
|
||||
description: memory note checkpoint-delivery-submit-logging-fix
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
# Checkpoint — hotfix livraison délégation + logs submit
|
||||
|
||||
Date: 2026-06-20
|
||||
|
||||
## Pourquoi ce checkpoint existe
|
||||
|
||||
Après le fix AppImage 0.3.0 précédent, la délégation `Main -> DevBackend` a reproduit le même symptôme que `Main -> Architect` avant redémarrage : le message de délégation apparaît dans l'input de la cellule cible, mais n'est pas soumis.
|
||||
|
||||
Le chantier `feature/agent-skill-awareness-v2` est donc suspendu tant que le canal de délégation n'est pas fiable.
|
||||
|
||||
## Branche et état
|
||||
|
||||
Branche active pendant le hotfix : `feature/agent-skill-awareness-v2`.
|
||||
|
||||
Dirty attendu hors code : fichiers runtime `.ideai/conversations/*`, `.ideai/layouts.json` produits par la session live.
|
||||
|
||||
## Changements code effectués
|
||||
|
||||
Frontend :
|
||||
- `frontend/src/features/terminals/useWritePortal.ts`
|
||||
- Logs détaillés `[write-portal]` : abonnement, attachement front, réception `delegationReady`, chunks de texte, délai avant submit, submit, ack, erreurs.
|
||||
- Fix défensif : `submitSequence: ""` est normalisée vers `"\r"` au lieu d'écrire zéro octet.
|
||||
- En cas d'erreur d'injection, arrêt du retry immédiat infini.
|
||||
- `frontend/src/adapters/terminal.ts`
|
||||
- Logs `[terminal-write]` sur writes non triviaux ou contrôles (`\r`, `\n`, bulk, etc.).
|
||||
- `frontend/src/adapters/input.ts`
|
||||
- Logs `[input-gateway]` pour `delegationDelivered` et `setFrontAttached`.
|
||||
- `frontend/src/domain/index.ts`
|
||||
- Commentaire défaut submit aligné sur ~350 ms.
|
||||
- `frontend/src/features/terminals/useWritePortal.test.tsx`
|
||||
- Test ajouté : `submitSequence: ""` doit envoyer `"\r"`.
|
||||
|
||||
Backend/application/Tauri :
|
||||
- `crates/application/src/orchestrator/service.rs`
|
||||
- `note_delegation_delivered` passe par `application::diag!` persistant.
|
||||
- `set_agent_front_attached` logge les changements d'attachement et le cas médiateur absent.
|
||||
- `crates/infrastructure/src/input/mod.rs`
|
||||
- Logs détaillés `[input-mediator]` : start turn, gate cold start, publish `DelegationReady`, choix front-owned vs headless, bind handle, front attach/detach, headless text/submit start/ok/failure.
|
||||
- Défaut headless `DEFAULT_SUBMIT_DELAY_MS` corrigé de 60 ms à 350 ms pour être aligné avec le write-portal.
|
||||
- Headless normalise aussi une submit sequence vide vers `"\r"`.
|
||||
- `crates/app-tauri/src/commands.rs`
|
||||
- Logs persistants `[pty-write]` autour de `write_terminal` avec session, bytes, contrôle sans contenu bulk.
|
||||
- Logs persistants `[delivery]` pour `delegation_delivered` et `set_front_attached`.
|
||||
|
||||
## Vérifications passées
|
||||
|
||||
- `cargo fmt --all -- --check` : OK.
|
||||
- `npx vitest run src/features/terminals/useWritePortal.test.tsx src/adapters/terminal.test.ts src/features/terminals/TerminalView.portal.test.tsx` : OK, 3 files / 20 tests.
|
||||
- `npx tsc --noEmit` : OK.
|
||||
- `cargo test -p infrastructure input --lib` : OK, 35 tests.
|
||||
- `cargo check -p app-tauri` : OK.
|
||||
- `cargo test -p application --test orchestrator_service` : OK, 45 tests (warning existant `CapturingFs::writes` unused).
|
||||
- `cargo test -p app-tauri --test orchestrator_wiring` : OK, 13 tests.
|
||||
|
||||
## Build AppImage
|
||||
|
||||
Commande Tauri standard : frontend build OK, Rust release OK, bundling Tauri KO avec `failed to run linuxdeploy` comme précédemment.
|
||||
|
||||
Contournement réussi :
|
||||
|
||||
```bash
|
||||
ARCH=x86_64 APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1 \
|
||||
/tmp/appimage_extracted_02f96dbd6ecc47f616b5a57fb8b7aa60/usr/bin/appimagetool \
|
||||
--runtime-file /tmp/idea-appimage-runtime-x86_64 \
|
||||
/home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA.AppDir \
|
||||
/home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage
|
||||
```
|
||||
|
||||
Artefact :
|
||||
- `/home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage`
|
||||
- taille : 106M
|
||||
- `--appimage-offset` : `944632`
|
||||
|
||||
## Reprise recommandée
|
||||
|
||||
1. Relancer IdeA avec l'AppImage ci-dessus.
|
||||
2. Reproduire une délégation simple `Main -> DevBackend` ou `Main -> Architect`.
|
||||
3. Si le message reste dans l'input sans être soumis, récupérer les traces :
|
||||
- logs persistants `application::diag!` (chemin configuré au startup, typiquement app-data logs/idea.log), chercher `[delivery]`, `[pty-write]`, `[rendezvous]`.
|
||||
- console frontend, chercher `[write-portal]`, `[terminal-write]`, `[input-gateway]`.
|
||||
- stderr si lancé depuis terminal, chercher `[input-mediator]`.
|
||||
4. Une fois délégation fiable, reprendre `feature/agent-skill-awareness-v2` depuis le cadrage Architect déjà obtenu.
|
||||
@ -364,7 +364,46 @@ pub fn write_terminal(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
let input = request.into_input()?;
|
||||
state.write_terminal.execute(input).map_err(ErrorDto::from)
|
||||
let session_id = input.session_id;
|
||||
let bytes = input.data.len();
|
||||
let control = describe_terminal_write(&input.data);
|
||||
application::diag!(
|
||||
"[pty-write] write_terminal start: session={session_id} bytes={bytes} control={control}"
|
||||
);
|
||||
match state.write_terminal.execute(input) {
|
||||
Ok(()) => {
|
||||
application::diag!(
|
||||
"[pty-write] write_terminal ok: session={session_id} bytes={bytes} control={control}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
application::diag!(
|
||||
"[pty-write] write_terminal failed: session={session_id} bytes={bytes} \
|
||||
control={control} error={e}"
|
||||
);
|
||||
Err(ErrorDto::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn describe_terminal_write(data: &[u8]) -> String {
|
||||
if data.is_empty() {
|
||||
return "<empty>".to_owned();
|
||||
}
|
||||
if data.len() > 16 {
|
||||
return "<bulk>".to_owned();
|
||||
}
|
||||
data.iter()
|
||||
.map(|byte| match *byte {
|
||||
b'\r' => "\\r".to_owned(),
|
||||
b'\n' => "\\n".to_owned(),
|
||||
0x7f => "\\x7f".to_owned(),
|
||||
byte if byte < 0x20 => format!("\\x{byte:02x}"),
|
||||
byte => char::from(byte).to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
/// `resize_terminal` — resize a live PTY.
|
||||
@ -1479,6 +1518,10 @@ pub async fn delegation_delivered(
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
let agent_id = parse_agent_id(&request.agent_id)?;
|
||||
let ticket = parse_ticket_id(&request.ticket)?;
|
||||
application::diag!(
|
||||
"[delivery] delegation_delivered command: project={} agent={agent_id} ticket={ticket}",
|
||||
project.id
|
||||
);
|
||||
state
|
||||
.orchestrator_service
|
||||
.note_delegation_delivered(&project, agent_id, ticket);
|
||||
@ -1502,6 +1545,10 @@ pub async fn set_front_attached(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
let agent_id = parse_agent_id(&request.agent_id)?;
|
||||
application::diag!(
|
||||
"[delivery] set_front_attached command: agent={agent_id} attached={}",
|
||||
request.attached
|
||||
);
|
||||
state
|
||||
.orchestrator_service
|
||||
.set_agent_front_attached(agent_id, request.attached);
|
||||
|
||||
@ -2541,6 +2541,13 @@ fn append_block(input: &str, block: &str) -> String {
|
||||
/// agent's persona `.md`, then the bodies of its assigned `skills` under a
|
||||
/// `# Skills` section (ARCHITECTURE §14.2).
|
||||
///
|
||||
/// A short skill-awareness paragraph is always injected in the orchestration
|
||||
/// block: it explains that assigned skills are operational workflow context, not
|
||||
/// magic commands or provider subagents, and points reusable workflow creation to
|
||||
/// the active IdeA orchestration surface (`idea_create_skill` for MCP profiles,
|
||||
/// `skill.create` for the file protocol). This awareness deliberately does not
|
||||
/// inject unassigned skill bodies; assignment remains the context boundary.
|
||||
///
|
||||
/// Skills are emitted in the order given (the caller passes them in manifest
|
||||
/// order, making the output deterministic); each is introduced by a `##` header
|
||||
/// carrying its name. When `skills` is empty the section is omitted entirely, so
|
||||
@ -2616,6 +2623,7 @@ pub(crate) fn compose_convention_file(
|
||||
avec son propre AI Profile, son contexte et sa mémoire.\n\n",
|
||||
);
|
||||
}
|
||||
out.push_str(skill_awareness(mcp_enabled));
|
||||
out.push_str("---\n\n");
|
||||
|
||||
if !project_context.trim().is_empty() {
|
||||
@ -2671,6 +2679,24 @@ pub(crate) fn compose_convention_file(
|
||||
out
|
||||
}
|
||||
|
||||
fn skill_awareness(mcp_enabled: bool) -> &'static str {
|
||||
if mcp_enabled {
|
||||
"**Skills IdeA** : les workflows assignés apparaissent plus bas dans ce fichier sous la \
|
||||
section Skills quand il y en a. Un skill assigné est du contexte opérationnel utilisable \
|
||||
quand pertinent : ce n'est ni une commande magique, ni une sous-tâche fournisseur. Si tu \
|
||||
identifies un workflow réutilisable à capitaliser, passe par l'orchestration IdeA avec \
|
||||
`idea_create_skill`. Les skills non assignés ne sont pas injectés intégralement à tous les \
|
||||
agents ; l'assignation reste la frontière.\n\n"
|
||||
} else {
|
||||
"**Skills IdeA** : les workflows assignés apparaissent plus bas dans ce fichier sous la \
|
||||
section Skills quand il y en a. Un skill assigné est du contexte opérationnel utilisable \
|
||||
quand pertinent : ce n'est ni une commande magique, ni une sous-tâche fournisseur. Si tu \
|
||||
identifies un workflow réutilisable à capitaliser, passe par l'orchestration IdeA avec le \
|
||||
protocole fichier `skill.create`. Les skills non assignés ne sont pas injectés \
|
||||
intégralement à tous les agents ; l'assignation reste la frontière.\n\n"
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a [`MemoryType`] as its stable lowercase label for the convention-file
|
||||
/// memory section (`user`/`feedback`/`project`/`reference`).
|
||||
#[must_use]
|
||||
@ -2759,8 +2785,13 @@ mod tests {
|
||||
let root_at = doc.find("/abs/project/root").unwrap();
|
||||
let persona_at = doc.find("# Persona").unwrap();
|
||||
assert!(root_at < persona_at, "root header must precede the persona");
|
||||
// No skills ⇒ no Skills section.
|
||||
assert!(!doc.contains("# Skills"));
|
||||
// No skills ⇒ awareness is present, but no Skills section is opened.
|
||||
assert!(doc.contains("**Skills IdeA**"));
|
||||
assert!(
|
||||
!doc.contains("\n# Skills\n"),
|
||||
"no assigned skills ⇒ no Skills section: {doc}"
|
||||
);
|
||||
assert!(doc.contains("skill.create"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -2812,9 +2843,19 @@ mod tests {
|
||||
// Both skill bodies present, after the persona.
|
||||
assert!(doc.contains("REFAC_BODY"));
|
||||
assert!(doc.contains("REVIEW_BODY"));
|
||||
let awareness_at = doc.find("**Skills IdeA**").unwrap();
|
||||
let persona_at = doc.find("# Persona").unwrap();
|
||||
let skills_at = doc.find("\n# Skills\n").unwrap();
|
||||
let refac_at = doc.find("REFAC_BODY").unwrap();
|
||||
let review_at = doc.find("REVIEW_BODY").unwrap();
|
||||
assert!(
|
||||
awareness_at < persona_at,
|
||||
"skill awareness belongs to orchestration, before persona"
|
||||
);
|
||||
assert!(
|
||||
persona_at < skills_at && skills_at < refac_at,
|
||||
"assigned skill bodies come under the Skills section after persona"
|
||||
);
|
||||
assert!(persona_at < refac_at, "skills come after the persona");
|
||||
// Deterministic order: first assigned skill precedes the second.
|
||||
assert!(refac_at < review_at, "skills emitted in the given order");
|
||||
@ -2823,6 +2864,44 @@ mod tests {
|
||||
assert!(doc.contains("## review"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compose_convention_file_skill_awareness_precedes_project_context() {
|
||||
let doc = compose_convention_file(
|
||||
"/root",
|
||||
"# Shared project context\n\nUse pnpm.",
|
||||
"# Persona\n\nDo X.",
|
||||
&[],
|
||||
&[],
|
||||
None,
|
||||
false,
|
||||
);
|
||||
|
||||
let awareness_at = doc.find("**Skills IdeA**").unwrap();
|
||||
let context_at = doc.find("# Contexte projet").unwrap();
|
||||
let persona_at = doc.find("# Persona").unwrap();
|
||||
assert!(
|
||||
awareness_at < context_at && context_at < persona_at,
|
||||
"order must stay root -> orchestration/awareness -> project context -> persona"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compose_convention_file_skill_awareness_uses_mcp_or_file_creation_path() {
|
||||
let mcp_doc = compose_convention_file("/root", "", "# Persona", &[], &[], None, true);
|
||||
assert!(mcp_doc.contains("idea_create_skill"));
|
||||
assert!(
|
||||
!mcp_doc.contains("skill.create"),
|
||||
"MCP awareness must point to the native IdeA tool"
|
||||
);
|
||||
|
||||
let file_doc = compose_convention_file("/root", "", "# Persona", &[], &[], None, false);
|
||||
assert!(file_doc.contains("skill.create"));
|
||||
assert!(
|
||||
!file_doc.contains("idea_create_skill"),
|
||||
"file-protocol awareness must point to skill.create"
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds a memory index entry for the convention-file composition tests.
|
||||
fn mem(slug_str: &str, title: &str, hook: &str, kind: MemoryType) -> MemoryIndexEntry {
|
||||
MemoryIndexEntry {
|
||||
|
||||
@ -1288,13 +1288,11 @@ impl OrchestratorService {
|
||||
// Observability beacon only: never mutates the mailbox/busy state nor resolves
|
||||
// the ticket (that stays `idea_reply`-driven). Kept best-effort by construction —
|
||||
// a pure, infallible hook so a missing mediator/registry never breaks the
|
||||
// rendezvous. The application crate carries no logging framework (zero new dep);
|
||||
// the ack is materialised as an `eprintln!` trace, the same lightweight channel
|
||||
// the orchestrator already uses for best-effort diagnostics.
|
||||
// rendezvous.
|
||||
let _ = project;
|
||||
eprintln!(
|
||||
"[orchestrator] delegation delivered into agent {agent_id}'s native terminal \
|
||||
(front ack, ticket {ticket})"
|
||||
crate::diag!(
|
||||
"[delivery] front ack received: agent={agent_id} ticket={ticket} \
|
||||
(write-portal reports text+submit writes completed)"
|
||||
);
|
||||
}
|
||||
|
||||
@ -1313,8 +1311,14 @@ impl OrchestratorService {
|
||||
/// agent **headless** (délégué en arrière-plan, sans cellule) voit le médiateur écrire
|
||||
/// lui-même la tâche dans son PTY — sinon le tour est perdu. No-op sans médiateur.
|
||||
pub fn set_agent_front_attached(&self, agent: domain::AgentId, attached: bool) {
|
||||
crate::diag!("[delivery] front attachment changed: agent={agent} attached={attached}");
|
||||
if let Some(input) = &self.input {
|
||||
input.set_front_attached(agent, attached);
|
||||
} else {
|
||||
crate::diag!(
|
||||
"[delivery] front attachment ignored because input mediator is not wired: \
|
||||
agent={agent} attached={attached}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -93,7 +93,7 @@ type HeadlessSink =
|
||||
/// Aligné sur le défaut du write-portal frontend (`DEFAULT_SUBMIT_DELAY_MS`) : sépare
|
||||
/// la soumission du collage pour esquiver la paste-detection des CLI. Utilisé seulement
|
||||
/// quand le profil de la cible ne fournit pas de `submit_delay_ms`.
|
||||
const DEFAULT_SUBMIT_DELAY_MS: u32 = 60;
|
||||
const DEFAULT_SUBMIT_DELAY_MS: u32 = 350;
|
||||
/// Taille maximale d'un fragment de consigne écrit dans le PTY en mode headless.
|
||||
const DELEGATION_WRITE_CHUNK_BYTES: usize = 512;
|
||||
/// Pause entre deux fragments de consigne en mode headless.
|
||||
@ -147,6 +147,7 @@ impl BusyTracker {
|
||||
/// appel, l'`enqueue` publie la `DelegationReady` immédiatement (chemin chaud,
|
||||
/// zéro régression).
|
||||
fn mark_starting(&self, agent: AgentId) {
|
||||
eprintln!("[input-mediator] cold-start gate armed agent={agent}");
|
||||
self.lock_starting().insert(agent);
|
||||
}
|
||||
|
||||
@ -263,8 +264,10 @@ impl BusyTracker {
|
||||
let mut busy = self.lock();
|
||||
let entry = busy.entry(agent).or_insert(AgentBusyState::Idle);
|
||||
if entry.is_busy() {
|
||||
eprintln!("[input-mediator] enqueue queued behind busy agent={agent}");
|
||||
false
|
||||
} else {
|
||||
eprintln!("[input-mediator] turn started agent={agent} state={state:?}");
|
||||
*entry = state;
|
||||
true
|
||||
}
|
||||
@ -281,11 +284,21 @@ impl BusyTracker {
|
||||
let d = match &self.headless_sink {
|
||||
Some(sink) => match sink(agent, d) {
|
||||
Some(d) => d,
|
||||
None => return,
|
||||
None => {
|
||||
eprintln!("[input-mediator] delegation handled by headless sink agent={agent}");
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => d,
|
||||
};
|
||||
if let Some(events) = &self.events {
|
||||
eprintln!(
|
||||
"[input-mediator] publishing DelegationReady agent={agent} ticket={} text_bytes={} submit_sequence={} submit_delay_ms={:?}",
|
||||
d.ticket,
|
||||
d.text.as_bytes().len(),
|
||||
describe_submit_sequence(d.submit_sequence.as_deref()),
|
||||
d.submit_delay_ms,
|
||||
);
|
||||
events.publish(DomainEvent::DelegationReady {
|
||||
agent_id: agent,
|
||||
ticket: d.ticket,
|
||||
@ -309,7 +322,13 @@ impl BusyTracker {
|
||||
self.lock_starting().remove(&agent);
|
||||
let deferred = self.lock_deferred().remove(&agent);
|
||||
match deferred {
|
||||
Some(d) => self.publish_deferred(agent, d),
|
||||
Some(d) => {
|
||||
eprintln!(
|
||||
"[input-mediator] prompt-ready released deferred delegation agent={agent} ticket={}",
|
||||
d.ticket
|
||||
);
|
||||
self.publish_deferred(agent, d);
|
||||
}
|
||||
None => self.mark_idle(agent),
|
||||
}
|
||||
}
|
||||
@ -322,6 +341,10 @@ impl BusyTracker {
|
||||
fn release_cold_start(&self, agent: AgentId) {
|
||||
self.lock_starting().remove(&agent);
|
||||
if let Some(d) = self.lock_deferred().remove(&agent) {
|
||||
eprintln!(
|
||||
"[input-mediator] mcp-ready released deferred delegation agent={agent} ticket={}",
|
||||
d.ticket
|
||||
);
|
||||
self.publish_deferred(agent, d);
|
||||
}
|
||||
}
|
||||
@ -336,6 +359,7 @@ impl BusyTracker {
|
||||
.is_some_and(|s| s.is_busy())
|
||||
};
|
||||
if was_busy {
|
||||
eprintln!("[input-mediator] turn marked idle agent={agent}");
|
||||
if let Some(events) = &self.events {
|
||||
events.publish(DomainEvent::AgentBusyChanged {
|
||||
agent_id: agent,
|
||||
@ -470,6 +494,10 @@ impl MediatedInbox {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.contains(&agent)
|
||||
{
|
||||
eprintln!(
|
||||
"[input-mediator] front-owned delivery selected agent={agent} ticket={}",
|
||||
d.ticket
|
||||
);
|
||||
return Some(d);
|
||||
}
|
||||
// Agent headless : récupérer son handle PTY. Absent ⇒ repli sur l'événement
|
||||
@ -480,11 +508,19 @@ impl MediatedInbox {
|
||||
.get(&agent)
|
||||
.cloned();
|
||||
let Some(handle) = handle else {
|
||||
eprintln!(
|
||||
"[input-mediator] no PTY handle for headless delivery agent={agent} ticket={}",
|
||||
d.ticket
|
||||
);
|
||||
return Some(d);
|
||||
};
|
||||
let pty = Arc::clone(&pty);
|
||||
let text = d.text;
|
||||
let submit = d.submit_sequence.unwrap_or_else(|| "\r".to_owned());
|
||||
let ticket = d.ticket;
|
||||
let submit = d
|
||||
.submit_sequence
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_else(|| "\r".to_owned());
|
||||
let delay = u64::from(d.submit_delay_ms.unwrap_or(DEFAULT_SUBMIT_DELAY_MS));
|
||||
// Écriture sur un thread détaché : ne JAMAIS bloquer l'appelant (thread du
|
||||
// watcher prompt-ready, tâche tokio du serveur MCP, ou le fil de l'enqueue).
|
||||
@ -493,11 +529,43 @@ impl MediatedInbox {
|
||||
// La consigne est fragmentée : les TUI (Codex surtout) peuvent traiter un
|
||||
// gros write unique comme un paste et garder/perdre la fin au démarrage.
|
||||
std::thread::spawn(move || {
|
||||
let _ = write_delegation_chunks(pty.as_ref(), &handle, &text);
|
||||
eprintln!(
|
||||
"[input-mediator] headless write text start agent={agent} ticket={ticket} handle={} text_bytes={} submit_sequence={} submit_delay_ms={delay}",
|
||||
handle.session_id,
|
||||
text.as_bytes().len(),
|
||||
describe_submit_sequence(Some(&submit)),
|
||||
);
|
||||
match write_delegation_chunks(pty.as_ref(), &handle, &text) {
|
||||
Ok(()) => eprintln!(
|
||||
"[input-mediator] headless write text ok agent={agent} ticket={ticket} handle={}",
|
||||
handle.session_id
|
||||
),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"[input-mediator] headless write text failed agent={agent} ticket={ticket} handle={} error={e}",
|
||||
handle.session_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if delay > 0 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(delay));
|
||||
}
|
||||
let _ = pty.write(&handle, submit.as_bytes());
|
||||
eprintln!(
|
||||
"[input-mediator] headless submit start agent={agent} ticket={ticket} handle={} submit_sequence={}",
|
||||
handle.session_id,
|
||||
describe_submit_sequence(Some(&submit)),
|
||||
);
|
||||
match pty.write(&handle, submit.as_bytes()) {
|
||||
Ok(()) => eprintln!(
|
||||
"[input-mediator] headless submit ok agent={agent} ticket={ticket} handle={}",
|
||||
handle.session_id
|
||||
),
|
||||
Err(e) => eprintln!(
|
||||
"[input-mediator] headless submit failed agent={agent} ticket={ticket} handle={} error={e}",
|
||||
handle.session_id
|
||||
),
|
||||
}
|
||||
});
|
||||
None
|
||||
})
|
||||
@ -692,6 +760,24 @@ fn write_delegation_chunks(
|
||||
pty.write(handle, text[start..].as_bytes())
|
||||
}
|
||||
|
||||
fn describe_submit_sequence(value: Option<&str>) -> String {
|
||||
match value {
|
||||
None => "<default>".to_owned(),
|
||||
Some("") => "<empty>".to_owned(),
|
||||
Some(value) => value
|
||||
.chars()
|
||||
.map(|ch| match ch {
|
||||
'\r' => "\\r".to_owned(),
|
||||
'\n' => "\\n".to_owned(),
|
||||
'\u{7f}' => "\\x7f".to_owned(),
|
||||
ch if ch.is_control() => format!("\\x{:02x}", ch as u32),
|
||||
ch => ch.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(""),
|
||||
}
|
||||
}
|
||||
|
||||
impl InputMediator for MediatedInbox {
|
||||
fn enqueue(&self, agent: AgentId, ticket: Ticket) -> PendingReply {
|
||||
let ticket_id = ticket.id;
|
||||
@ -761,6 +847,10 @@ impl InputMediator for MediatedInbox {
|
||||
}
|
||||
|
||||
fn bind_handle(&self, agent: AgentId, handle: PtyHandle) {
|
||||
eprintln!(
|
||||
"[input-mediator] bind handle agent={agent} handle={}",
|
||||
handle.session_id
|
||||
);
|
||||
self.handles().insert(agent, handle);
|
||||
}
|
||||
|
||||
@ -776,6 +866,13 @@ impl InputMediator for MediatedInbox {
|
||||
// then arm the prompt-ready watcher when the profile declares a literal marker
|
||||
// (C5). A `None`/empty pattern arms nothing: Idle then comes only from the
|
||||
// explicit signal or the per-turn timeout (safe fallback, never a false Idle).
|
||||
eprintln!(
|
||||
"[input-mediator] bind handle with prompt agent={agent} handle={} prompt_ready={} submit_sequence={} submit_delay_ms={:?}",
|
||||
handle.session_id,
|
||||
prompt_ready_pattern.as_deref().unwrap_or("<none>"),
|
||||
describe_submit_sequence(submit.sequence.as_deref()),
|
||||
submit.delay_ms,
|
||||
);
|
||||
self.handles().insert(agent, handle.clone());
|
||||
self.submit().insert(agent, submit);
|
||||
if let Some(pattern) = prompt_ready_pattern {
|
||||
@ -805,8 +902,10 @@ impl InputMediator for MediatedInbox {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if attached {
|
||||
eprintln!("[input-mediator] front attached agent={agent}");
|
||||
front.insert(agent);
|
||||
} else {
|
||||
eprintln!("[input-mediator] front detached agent={agent}");
|
||||
front.remove(&agent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,15 +28,27 @@ export class TauriInputGateway implements InputGateway {
|
||||
agentId: string,
|
||||
ticket: string,
|
||||
): Promise<void> {
|
||||
console.info("[input-gateway] delegationDelivered:start", {
|
||||
projectId,
|
||||
agentId,
|
||||
ticket,
|
||||
});
|
||||
await invoke("delegation_delivered", {
|
||||
request: { projectId, agentId, ticket },
|
||||
});
|
||||
console.info("[input-gateway] delegationDelivered:ok", {
|
||||
projectId,
|
||||
agentId,
|
||||
ticket,
|
||||
});
|
||||
}
|
||||
|
||||
async setFrontAttached(agentId: string, attached: boolean): Promise<void> {
|
||||
console.info("[input-gateway] setFrontAttached:start", { agentId, attached });
|
||||
await invoke("set_front_attached", {
|
||||
request: { agentId, attached },
|
||||
});
|
||||
console.info("[input-gateway] setFrontAttached:ok", { agentId, attached });
|
||||
}
|
||||
|
||||
async cancelResume(agentId: string): Promise<boolean> {
|
||||
|
||||
@ -64,11 +64,13 @@ export function makeTerminalHandle(
|
||||
return {
|
||||
sessionId,
|
||||
write(data: Uint8Array): Promise<void> {
|
||||
const run = chain.then(() =>
|
||||
invoke<void>("write_terminal", {
|
||||
const run = chain.then(async () => {
|
||||
logTerminalWrite("start", sessionId, data);
|
||||
await invoke<void>("write_terminal", {
|
||||
request: { sessionId, data: Array.from(data) },
|
||||
}),
|
||||
);
|
||||
});
|
||||
logTerminalWrite("ok", sessionId, data);
|
||||
});
|
||||
// Keep the chain alive even if this write rejects: the next write must
|
||||
// still run. Swallow the error on the *chain* copy only — `run` keeps the
|
||||
// rejection so the caller can observe it.
|
||||
@ -91,6 +93,35 @@ export function makeTerminalHandle(
|
||||
};
|
||||
}
|
||||
|
||||
function logTerminalWrite(phase: "start" | "ok", sessionId: string, data: Uint8Array): void {
|
||||
if (data.length === 1 && data[0] >= 0x20 && data[0] !== 0x7f) return;
|
||||
console.info("[terminal-write]", phase, {
|
||||
sessionId,
|
||||
bytes: data.length,
|
||||
control: describeBytes(data),
|
||||
});
|
||||
}
|
||||
|
||||
function describeBytes(data: Uint8Array): string {
|
||||
if (data.length === 0) return "<empty>";
|
||||
if (data.length > 16) return "<bulk>";
|
||||
return Array.from(data)
|
||||
.map((byte) => {
|
||||
switch (byte) {
|
||||
case 0x0d:
|
||||
return "\\r";
|
||||
case 0x0a:
|
||||
return "\\n";
|
||||
case 0x7f:
|
||||
return "\\x7f";
|
||||
default:
|
||||
if (byte < 0x20) return `\\x${byte.toString(16).padStart(2, "0")}`;
|
||||
return String.fromCharCode(byte);
|
||||
}
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
export class TauriTerminalGateway implements TerminalGateway {
|
||||
async openTerminal(
|
||||
options: OpenTerminalOptions,
|
||||
|
||||
@ -27,7 +27,7 @@ export type DomainEvent =
|
||||
* PTY-writes the turn: the frontend write-portal runs the handshake (b→e)
|
||||
* and writes `text` + `submitSequence`. `submitSequence`/`submitDelayMs`
|
||||
* come from the target's profile; absent ⇒ the portal applies its defaults
|
||||
* (`"\r"`, ~60 ms).
|
||||
* (`"\r"`, ~350 ms).
|
||||
*/
|
||||
type: "delegationReady";
|
||||
agentId: string;
|
||||
|
||||
@ -235,6 +235,26 @@ describe("useWritePortal (§20)", () => {
|
||||
expect(writes).toEqual(["msg", "\n"]);
|
||||
});
|
||||
|
||||
it("defaults an empty profile submitSequence to Enter", async () => {
|
||||
const { system, view } = setup();
|
||||
const { handle, writes } = makeHandle();
|
||||
act(() => view.result.current.portal.bindHandle(handle));
|
||||
|
||||
await act(async () => {
|
||||
system.emit({
|
||||
type: "delegationReady",
|
||||
agentId: "ag1",
|
||||
ticket: "t1",
|
||||
text: "msg",
|
||||
submitSequence: "",
|
||||
submitDelayMs: 0,
|
||||
});
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(writes).toEqual(["msg", "\r"]);
|
||||
});
|
||||
|
||||
it("ignores delegationReady for a different agent", async () => {
|
||||
const { system, view } = setup();
|
||||
const { handle, writes } = makeHandle();
|
||||
|
||||
@ -58,6 +58,8 @@ const DELEGATION_WRITE_CHUNK_BYTES = 512;
|
||||
/** Delay between delegation chunks, to avoid TUI paste/drop behaviour on cold agents. */
|
||||
const DELEGATION_WRITE_CHUNK_DELAY_MS = 8;
|
||||
|
||||
const LOG_PREFIX = "[write-portal]";
|
||||
|
||||
/** A pending delegation kept in the local FIFO. */
|
||||
interface PendingDelegation {
|
||||
ticket: string;
|
||||
@ -135,10 +137,24 @@ export function useWritePortal(
|
||||
if (previous === desired) return;
|
||||
|
||||
if (previous) {
|
||||
void inputRef.current?.setFrontAttached(previous, false).catch(() => {});
|
||||
portalLog("front-attached:set", { agent: previous, attached: false });
|
||||
void inputRef.current?.setFrontAttached(previous, false).catch((error) => {
|
||||
portalLog("front-attached:set-failed", {
|
||||
agent: previous,
|
||||
attached: false,
|
||||
error: describeError(error),
|
||||
});
|
||||
});
|
||||
}
|
||||
if (desired) {
|
||||
void inputRef.current?.setFrontAttached(desired, true).catch(() => {});
|
||||
portalLog("front-attached:set", { agent: desired, attached: true });
|
||||
void inputRef.current?.setFrontAttached(desired, true).catch((error) => {
|
||||
portalLog("front-attached:set-failed", {
|
||||
agent: desired,
|
||||
attached: true,
|
||||
error: describeError(error),
|
||||
});
|
||||
});
|
||||
}
|
||||
frontAttachedAgentRef.current = desired;
|
||||
};
|
||||
@ -164,7 +180,16 @@ export function useWritePortal(
|
||||
setOverlay(true);
|
||||
|
||||
void (async () => {
|
||||
let drainNext = false;
|
||||
try {
|
||||
portalLog("inject:start", {
|
||||
agent,
|
||||
ticket: head.ticket,
|
||||
textBytes: encoder.encode(head.text).byteLength,
|
||||
queue: queueRef.current.length,
|
||||
submitSequence: describeControl(head.submitSequence),
|
||||
submitDelayMs: head.submitDelayMs ?? DEFAULT_SUBMIT_DELAY_MS,
|
||||
});
|
||||
// Yield once so any keystroke that landed in the micro-window between
|
||||
// "empty observed" (a) and the suspension taking hold (b) is counted
|
||||
// before we re-check K at step (c). Without this the race window would
|
||||
@ -174,6 +199,7 @@ export function useWritePortal(
|
||||
// (backspace) — never Ctrl-U (which could clear more than the human typed).
|
||||
const k = counterRef.current;
|
||||
if (k > 0) {
|
||||
portalLog("inject:erase-human-window", { agent, ticket: head.ticket, count: k });
|
||||
await handle.write(encoder.encode("\x7f".repeat(k)));
|
||||
counterRef.current = 0;
|
||||
}
|
||||
@ -183,19 +209,37 @@ export function useWritePortal(
|
||||
// Long delegated prompts are chunked: Codex's TUI can otherwise treat a
|
||||
// large single write like a paste and occasionally keep/drop the tail on
|
||||
// cold-started agents before Enter is received.
|
||||
await writeDelegationText(handle, head.text);
|
||||
await writeDelegationText(handle, head.text, head.ticket);
|
||||
const delay = head.submitDelayMs ?? DEFAULT_SUBMIT_DELAY_MS;
|
||||
portalLog("inject:before-submit-delay", { agent, ticket: head.ticket, delay });
|
||||
await sleep(delay);
|
||||
const submit = head.submitSequence ?? DEFAULT_SUBMIT_SEQUENCE;
|
||||
const submit = normalizeSubmitSequence(head.submitSequence, head.ticket);
|
||||
portalLog("inject:submit-write:start", {
|
||||
agent,
|
||||
ticket: head.ticket,
|
||||
submitSequence: describeControl(submit),
|
||||
submitBytes: encoder.encode(submit).byteLength,
|
||||
});
|
||||
await handle.write(encoder.encode(submit));
|
||||
portalLog("inject:submit-write:ok", { agent, ticket: head.ticket });
|
||||
|
||||
// Dequeue + ack exactly once (best-effort; never throws the handshake).
|
||||
queueRef.current.shift();
|
||||
drainNext = true;
|
||||
try {
|
||||
portalLog("inject:ack:start", { agent, ticket: head.ticket });
|
||||
await inputRef.current.delegationDelivered(project, agent, head.ticket);
|
||||
portalLog("inject:ack:ok", { agent, ticket: head.ticket });
|
||||
} catch {
|
||||
portalLog("inject:ack:failed", { agent, ticket: head.ticket });
|
||||
/* ack is observability-only; never block the portal */
|
||||
}
|
||||
} catch (error) {
|
||||
portalLog("inject:failed", {
|
||||
agent,
|
||||
ticket: head.ticket,
|
||||
error: describeError(error),
|
||||
});
|
||||
} finally {
|
||||
// (e) lower the overlay + resume the relay, with the 2 s anti-flash floor.
|
||||
const elapsed = Date.now() - startedAt;
|
||||
@ -205,7 +249,7 @@ export function useWritePortal(
|
||||
setOverlay(false);
|
||||
injectingRef.current = false;
|
||||
// A boundary may now be open for the next queued delegation.
|
||||
if (queueRef.current.length > 0) tryInject.current();
|
||||
if (queueRef.current.length > 0 && drainNext) tryInject.current();
|
||||
}
|
||||
})();
|
||||
};
|
||||
@ -231,6 +275,14 @@ export function useWritePortal(
|
||||
submitSequence: event.submitSequence,
|
||||
submitDelayMs: event.submitDelayMs,
|
||||
});
|
||||
portalLog("event:delegation-ready", {
|
||||
agent: subscribedAgent,
|
||||
ticket: event.ticket,
|
||||
textBytes: encoder.encode(event.text).byteLength,
|
||||
queue: queueRef.current.length,
|
||||
submitSequence: describeControl(event.submitSequence),
|
||||
submitDelayMs: event.submitDelayMs,
|
||||
});
|
||||
// A delegation may already be at a clean boundary (line empty) — try now.
|
||||
tryInject.current();
|
||||
})
|
||||
@ -239,6 +291,7 @@ export function useWritePortal(
|
||||
else {
|
||||
unsubscribe = un;
|
||||
subscribedAgentRef.current = subscribedAgent;
|
||||
portalLog("event:subscribed", { agent: subscribedAgent });
|
||||
reconcileFrontAttachment.current();
|
||||
}
|
||||
});
|
||||
@ -248,6 +301,7 @@ export function useWritePortal(
|
||||
unsubscribe?.();
|
||||
if (subscribedAgentRef.current === subscribedAgent) {
|
||||
subscribedAgentRef.current = null;
|
||||
portalLog("event:unsubscribed", { agent: subscribedAgent });
|
||||
}
|
||||
if (frontAttachedAgentRef.current === subscribedAgent) {
|
||||
void inputRef.current?.setFrontAttached(subscribedAgent, false).catch(() => {});
|
||||
@ -279,6 +333,7 @@ export function useWritePortal(
|
||||
},
|
||||
bindHandle(handle: TerminalHandle) {
|
||||
handleRef.current = handle;
|
||||
portalLog("handle:bound", { agent: agentIdRef.current, sessionId: handle.sessionId });
|
||||
// Tell the backend a frontend cell is mounted only after the event
|
||||
// subscription is ready. Until then, the backend keeps the headless PTY
|
||||
// write fallback, so a delegation cannot disappear during mount.
|
||||
@ -287,6 +342,10 @@ export function useWritePortal(
|
||||
tryInject.current();
|
||||
},
|
||||
unbindHandle() {
|
||||
portalLog("handle:unbound", {
|
||||
agent: agentIdRef.current,
|
||||
sessionId: handleRef.current?.sessionId,
|
||||
});
|
||||
handleRef.current = null;
|
||||
// The cell is gone — let the backend fall back to headless delivery so a
|
||||
// delegation arriving while this agent has no live cell is not lost.
|
||||
@ -307,16 +366,40 @@ function sleep(ms: number): Promise<void> {
|
||||
async function writeDelegationText(
|
||||
handle: TerminalHandle,
|
||||
text: string,
|
||||
ticket: string,
|
||||
): Promise<void> {
|
||||
const chunks = splitUtf8Chunks(text, DELEGATION_WRITE_CHUNK_BYTES);
|
||||
for (let i = 0; i < chunks.length; i += 1) {
|
||||
portalLog("inject:text-chunk:start", {
|
||||
ticket,
|
||||
sessionId: handle.sessionId,
|
||||
chunk: i + 1,
|
||||
chunks: chunks.length,
|
||||
bytes: encoder.encode(chunks[i]).byteLength,
|
||||
});
|
||||
await handle.write(encoder.encode(chunks[i]));
|
||||
portalLog("inject:text-chunk:ok", {
|
||||
ticket,
|
||||
sessionId: handle.sessionId,
|
||||
chunk: i + 1,
|
||||
chunks: chunks.length,
|
||||
});
|
||||
if (i + 1 < chunks.length) {
|
||||
await sleep(DELEGATION_WRITE_CHUNK_DELAY_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeSubmitSequence(sequence: string | undefined, ticket: string): string {
|
||||
if (sequence == null) return DEFAULT_SUBMIT_SEQUENCE;
|
||||
if (sequence.length > 0) return sequence;
|
||||
portalLog("inject:empty-submit-sequence-defaulted", {
|
||||
ticket,
|
||||
defaultSubmitSequence: describeControl(DEFAULT_SUBMIT_SEQUENCE),
|
||||
});
|
||||
return DEFAULT_SUBMIT_SEQUENCE;
|
||||
}
|
||||
|
||||
function splitUtf8Chunks(text: string, maxBytes: number): string[] {
|
||||
if (text.length === 0) return [""];
|
||||
const chunks: string[] = [];
|
||||
@ -337,3 +420,36 @@ function splitUtf8Chunks(text: string, maxBytes: number): string[] {
|
||||
if (current.length > 0) chunks.push(current);
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function describeControl(value: string | undefined): string {
|
||||
if (value == null) return "<default>";
|
||||
if (value.length === 0) return "<empty>";
|
||||
return [...value]
|
||||
.map((ch) => {
|
||||
switch (ch) {
|
||||
case "\r":
|
||||
return "\\r";
|
||||
case "\n":
|
||||
return "\\n";
|
||||
case "\x7f":
|
||||
return "\\x7f";
|
||||
default: {
|
||||
const code = ch.codePointAt(0)!;
|
||||
if (code < 0x20) return `\\x${code.toString(16).padStart(2, "0")}`;
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
function describeError(error: unknown): string {
|
||||
if (error && typeof error === "object" && "message" in error) {
|
||||
return String((error as { message: unknown }).message);
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
||||
function portalLog(event: string, fields: Record<string, unknown>): void {
|
||||
console.info(LOG_PREFIX, event, fields);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user