--- issueRef: "#292" version: 5 updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} updatedAt: 1788956518862 --- # Carnet #292 — Agents Git TUI Native UnitySoundTool/UnityPlugin ## Symptôme utilisateur Le 2026-09-09, l'utilisateur signale que les agents `Git` des projets `UnitySoundTool` (`/home/anthony/Documents/Projects/SoundTool`) et `UnityPlugin` (`/home/anthony/Documents/Projects/UnityPlugin`) bouclent sans répondre avec une entrée simple type `Hello` en TUI Native, et le CPU monte à 100%. L'agent `Git` du projet IdeA courant marche avec une configuration apparemment identique. ## Cadrage Architect Architect confirme que les trois agents `Git` utilisent le même profil `5ef495f8-4e6c-58e5-b3c9-f32ec8ffac55` (`GLM5.2`), `command: opencode`, `structuredAdapter: openCode`, config MCP via `opencode.json`. Le libellé `TUI Native` décrit la surface visible, pas un profil Codex brut. Contrat attendu si fix runtime: une entrée humaine dans une TUI Native/OpenCode ne peut produire qu'un seul write utilisateur, un seul tour actif par agent, et aucune relance automatique sans action explicite ou erreur terminale exposée. ## Données collectées Agents Git: - IdeA: `1ff94b51-3e17-4a39-8543-7715d1ff0f80`, template `07670754-878b-4a37-8e18-5b061c51cd9b`, syncedTemplateVersion `14`. - UnityPlugin: `15e9bbaf-8212-4c6e-a592-9d4f7fb44bf1`, même profile/template, syncedTemplateVersion `13`. - UnitySoundTool: `af772fd8-306e-4ea0-aa20-8f6f537c3377`, même profile/template, syncedTemplateVersion `14`. `opencode.json` des trois run dirs est identique hors `--endpoint`, `--project`, `--requester` et `cwd`. Tous pointent vers `/home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage mcp-server` et le modèle `zai-coding-plan/glm-5.3`. Run dirs Git: - IdeA: `.ideai/run/1ff94b51-3e17-4a39-8543-7715d1ff0f80/{AGENTS.md,opencode.json,.mcp.json}`. - UnityPlugin: `.ideai/run/15e9bbaf-8212-4c6e-a592-9d4f7fb44bf1/{AGENTS.md,opencode.json}`. - UnitySoundTool: `.ideai/run/af772fd8-306e-4ea0-aa20-8f6f537c3377/{AGENTS.md,opencode.json}`. `AGENTS.md` diff: les projets Unity ont naturellement un contexte projet différent; SoundTool a plusieurs skills Unity dans le contexte. Ce n'est pas censé expliquer une boucle CPU au simple `Hello`. États persistés: - IdeA `.ideai/live-state.json`: Git `done`. - SoundTool `.ideai/live-state.json`: Git `done`, ticket `c5801e75-14f7-4564-9702-c9667174457e`. - UnityPlugin `.ideai/live-state.json`: pas d'entrée Git. - `chat-drafts.json`: drafts vides pour UnityPlugin et SoundTool. - Les commandes `jq '.tabs, .nodes // empty' .ideai/layouts.json` ont retourné `null` pour UnityPlugin et SoundTool; format à inspecter plus directement si nécessaire. Limite de diagnostic: depuis le sandbox Main, `ps` ne voit que les processus du sandbox, donc l'identification du process à 100% (`IdeA`, `opencode`, `node`, `mcp-server`) doit être faite par QA hors sandbox ou via logs applicatifs. ## Hypothèses actuelles 1. Boucle runtime OpenCode/MCP ou launch/reattach propre aux projets externes: les sockets/cwd changent mais le profil est identique. 2. Boucle frontend TUI Native: double submit, reattach/render loop ou écriture répétée de l'entrée humaine. 3. État projet/layout/session stale non capturé par `live-state.json` simple. 4. UnityPlugin a un template Git v13 au lieu de v14, mais SoundTool échoue aussi en v14, donc ce n'est pas la cause unique. ## Suite attendue Demander à Git la branche locale, puis DevBackend pour investigation runtime/PTY/OpenCode/MCP/reconciliation. DevFrontend seulement si DevBackend trouve un double-submit ou une boucle de reattach côté surface. QA devra reproduire sur les trois projets avec le vrai binaire AppImage. ## Diagnostic DevBackend — 2026-09-09 Cause localisée dans la réattache frontend de la TUI native, pas dans le runtime OpenCode structuré, le lancement PTY ni la réconciliation backend. Preuves : - `launch_agent` fixe `allow_structured_alongside_pty=false` et ne demande le chemin structuré que pour une cellule `Chat`. La surface courante ignore ce `cellKind` et rend toute cellule agent via `TerminalView` : le profil OpenCode est donc lancé une fois comme TUI native dans un PTY. `OpenCodeSession::send_inner` n'est pas sur ce chemin utilisateur. - `LaunchAgent::execute` consulte les registres avant tout I/O et rend la session existante sur réattache/idempotence. Les tests ciblés de non-respawn sont verts. - `PortablePtyAdapter::Broadcast::subscribe` remplace le subscriber précédent : il n'existe qu'un seul pump de sortie actif par PTY après réattache. Le test ciblé est vert. - Le log hôte `/home/anthony/.local/share/app.idea.ide/logs/idea.log` corrèle directement SoundTool Git `af772fd8-306e-4ea0-aa20-8f6f537c3377` : `front_attached=true` à `1788954519395`, puis 119 appels `write_terminal` sur la session `557f7aff-2ee5-47f3-9e5b-4d268e255eab` avant le détachement à `1788954524075`, sans séquence de saisie `Hello`/Entrée dans cette fenêtre. Le burst commence par des réponses terminal synthétiques xterm : `ESC[1;1R`, `ESC[?1016;2$y`, `ESC[?2027;0$y`, `ESC[?2031;0$y`, `ESC[?1004;2$y`, `ESC[?2004;2$y`, `ESC[?2026;2$y`, device attributes et réponses focus. - Dans `frontend/src/features/terminals/TerminalView.tsx`, `term.onData` est installé avant la réattache. Le callback met toute donnée dans `pending` tant que le handle est absent. Après `reattacher(...)`, le code fait `term.write(scrollback)` puis `adopt(h)` ; le replay des anciennes requêtes terminal provoque donc de nouvelles réponses xterm, les accumule dans `pending`, puis `adopt` les réinjecte physiquement dans le PTY. C'est une boucle de replay/réponse propre aux sessions TUI riches en séquences de contrôle, ce qui explique l'écart entre projets malgré le même profil. Le correctif appartient à DevFrontend : pendant le replay du scrollback de réattache, les données synthétiques produites par xterm ne doivent pas être relayées au handle ni ajoutées à `pending`. Le relais `term.onData` doit devenir actif seulement après le callback de fin de `term.write(scrollback)` (ou être explicitement suspendu pendant ce replay), tout en conservant le chemin nominal de frappe utilisateur ensuite. Test de régression attendu dans `TerminalView.test.tsx` : un scrollback contenant des requêtes terminal déclenche des réponses xterm simulées mais zéro `handle.write`; une saisie `Hello` + Entrée après replay produit exactement les writes utilisateur attendus. Aucun correctif backend n'a été appliqué : filtrer les réponses terminal dans `write_terminal` casserait le protocole PTY normal et masquerait l'origine frontend. Aucun fichier des projets Unity n'a été modifié. ## Validations DevBackend - `cargo test -p infrastructure pty --lib` — vert, 26 tests passés, 352 filtrés. - `cargo test -p application --test agent_lifecycle launch_same_node_is_idempotent_no_respawn` — vert, 1 passé. - `cargo test -p application --test agent_lifecycle launch_existing_agent_with_no_node_is_background_noop` — vert, 1 passé. - `cargo test -p application --test orchestrator_service agent_run_visible_same_cell_rebinds_existing_session` — vert, 1 passé. - `npm test -- --run src/features/terminals/TerminalView.test.tsx` depuis `frontend/` — vert, 21 tests existants ; la couverture actuelle ne simule pas les réponses xterm émises pendant le replay et doit être complétée par DevFrontend. Suite : DevFrontend implémente et teste la suspension du relais pendant le replay ; QA rebuild l'AppImage puis reproduit sur SoundTool, UnityPlugin et IdeA, en contrôlant dans `idea.log` qu'une saisie humaine ne produit qu'une seule séquence utilisateur et aucun burst de réponses historiques à la réattache. Pour identifier le consommateur CPU hors sandbox, QA peut utiliser `ps -eo pid,ppid,pcpu,comm,args --sort=-pcpu` puis corréler le PID OpenCode/renderer avec l'heure du burst `pty-write`. ## Correctif DevFrontend — 2026-09-09 Le relais xterm → PTY est désormais activé selon le cycle de montage : - ouverture fraîche : activation immédiate, afin de conserver le buffering des frappes humaines pendant la résolution de l'opener ; - réattache avec scrollback : aucun abonnement `term.onData` pendant `term.write(scrollback)` ; activation seulement dans le callback de fin du replay, puis adoption du handle ; - réattache sans scrollback et fallback vers une ouverture fraîche : activation avant adoption/ouverture selon le même invariant ; - cleanup : disposal conditionnel de l'abonnement, puisqu'il peut ne pas encore exister si la vue est démontée pendant le replay. Le test de non-régression simule une requête de position curseur (`ESC[6n`) dans le scrollback et une réponse xterm synthétique (`ESC[1;1R`) émise par `term.write`. Il vérifie zéro appel à `handle.write` pendant le replay, puis exactement un appel contenant `Hello\r` après la fin du replay. Fichiers modifiés : - `frontend/src/features/terminals/TerminalView.tsx` - `frontend/src/features/terminals/TerminalView.scrollback.test.tsx` - `.ideai/tickets/292/carnet.md` Validations réelles : - `git diff --check` — vert. - `npm --prefix frontend test -- --run src/features/terminals/TerminalView.scrollback.test.tsx src/features/terminals/TerminalView.portal.test.tsx src/features/terminals/TerminalView.test.tsx` — vert, 3 fichiers et 27 tests passés. jsdom signale seulement son avertissement connu `HTMLCanvasElement.getContext()` non implémenté. - `npm --prefix frontend run build` — vert (`tsc --noEmit` puis Vite, 515 modules transformés). Seul l'avertissement existant de chunk supérieur à 500 kB reste présent. Reste à QA : rebuild de l'AppImage et reproduction hôte sur UnitySoundTool, UnityPlugin et IdeA avec contrôle de l'absence de burst `pty-write` à la réattache. ## Validation QA — 2026-09-09 Verdict QA : VERT sur le contrat automatisé et l'artefact AppImage. Limite explicite : la reproduction hôte complète par interaction graphique dans les trois projets n'a pas pu être conduite depuis ce sandbox QA sans envoyer des frappes à l'aveugle dans l'application visible. Diff relu : - `frontend/src/features/terminals/TerminalView.tsx` : l'abonnement `term.onData` est désormais créé par `activateInput()`. Sur réattache avec scrollback, `term.write` reçoit un callback final qui active seulement ensuite le relais xterm -> PTY et adopte le handle. Le chemin ouverture fraîche garde l'activation immédiate pour conserver le buffering des frappes humaines. - `frontend/src/features/terminals/TerminalView.scrollback.test.tsx` : le stub xterm émet une réponse synthétique `ESC[1;1R` pendant le replay de `ESC[6n`; le test vérifie zéro écriture PTY pendant ce replay, puis une seule écriture `Hello\r` après activation. Commandes réelles exécutées depuis `/home/anthony/Documents/Projects/IdeA` : ```bash git status --short --branch ``` ```text ## fix/292-tui-native-git-agents-loop M .ideai/tickets/counter.json M .ideai/tickets/index.json M frontend/src/features/terminals/TerminalView.scrollback.test.tsx M frontend/src/features/terminals/TerminalView.tsx ?? .ideai/tickets/292/ ?? scripts/ ``` ```bash git diff --check ``` ```text ``` ```bash npm --prefix frontend test -- --run src/features/terminals/TerminalView.scrollback.test.tsx src/features/terminals/TerminalView.portal.test.tsx src/features/terminals/TerminalView.test.tsx ``` ```text Test Files 3 passed (3) Tests 27 passed (27) Duration 2.82s ``` La sortie contient aussi l'avertissement jsdom connu : `Not implemented: HTMLCanvasElement's getContext() method: without installing the canvas npm package`. ```bash npm --prefix frontend run build ``` ```text tsc --noEmit && vite build 515 modules transformed. dist/assets/index-Cce2ycJb.js 1,261.08 kB ✓ built in 2.60s ``` Vite signale seulement l'avertissement existant de chunk supérieur à 500 kB. Rebuild AppImage réel : ```bash cd crates/app-tauri APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1 ../../frontend/node_modules/.bin/tauri build --bundles appimage ``` ```text Running beforeBuildCommand `npm --prefix ../frontend run build:bundle` Finished `release` profile [optimized] target(s) in 2m 26s Built application at: /home/anthony/Documents/Projects/IdeA/target/release/app-tauri Bundling IdeA_0.3.0_amd64.AppImage (/home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage) Finished 1 bundle at: /home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage ``` Vérification d'artefact : ```bash stat -c '%n %s %Y %i' /home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage /home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage ``` ```text /home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage 119478776 1788956753 12345211 /home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage 119478776 1788956753 12345211 ``` Les deux chemins sont le même artefact hardlinké, ce qui couvre les `opencode.json` des agents Git affectés qui pointent vers `.appimage-build/.../IdeA_0.3.0_amd64.AppImage`. ```bash sha256sum /home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage ``` ```text 4165521e4248ec9f71af4ceb1c89d3aa54e8119a1ccf0f70d15268bdc67eaf94 /home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage ``` Contrôles hôte tentés : ```bash env | sort | rg 'DISPLAY|WAYLAND|XDG_RUNTIME|IDEA|APPIMAGE|TAURI' ``` ```text APPIMAGE=/home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage DISPLAY=:0 IDEA_MANAGED_TMP=/home/anthony/.local/share/app.idea.ide/managed-tmp/accb434c-d2d1-4c01-b928-72e738151fdc/data QT_WAYLAND_RECONNECT=1 WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000 ``` ```bash xdotool search --name 'IdeA - Agents' getwindowgeometry %@ ``` ```text Window 44040214 Position: 0,28 (screen: 0) Geometry: 1920x1052 ``` ```bash import -window 44040214 /home/anthony/.local/share/app.idea.ide/managed-tmp/accb434c-d2d1-4c01-b928-72e738151fdc/data/idea-agents-window-292.png ``` ```text import: missing an image filename `/home/anthony/.local/share/app.idea.ide/managed-tmp/accb434c-d2d1-4c01-b928-72e738151fdc/data/idea-agents-window-292.png' @ error/import.c/ImportImageCommand/1291. ``` ```bash xwd -id 44040214 -silent -out /home/anthony/.local/share/app.idea.ide/managed-tmp/accb434c-d2d1-4c01-b928-72e738151fdc/data/idea-agents-window-292.xwd && convert /home/anthony/.local/share/app.idea.ide/managed-tmp/accb434c-d2d1-4c01-b928-72e738151fdc/data/idea-agents-window-292.xwd /home/anthony/.local/share/app.idea.ide/managed-tmp/accb434c-d2d1-4c01-b928-72e738151fdc/data/idea-agents-window-292.png ``` ```text X Error of failed request: BadMatch (invalid parameter attributes) Major opcode of failed request: 73 (X_GetImage) ``` ```bash APPIMAGE_EXTRACT_AND_RUN=1 /home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage --version ``` ```text (app-tauri:12): Gdk-WARNING **: The program 'app-tauri' received an X Window System error. The error was 'BadAccess (attempt to access private resource denied)'. ``` ```bash ps -eo pid,ppid,pcpu,pmem,comm,args --sort=-pcpu | head -40 ``` ```text PID PPID %CPU %MEM COMMAND COMMAND 1 0 20.0 0.0 codex codex-linux-sandbox ... ``` Le sandbox PID ne voit que ses propres processus ; il ne permet donc pas de mesurer fiablement le CPU des processus hôtes IdeA/OpenCode. Contrôle log : ```bash tail -80 /home/anthony/.local/share/app.idea.ide/logs/idea.log ``` Le log actuel confirme surtout la signature pré-fix déjà collectée avant relance : bursts `pty-write` de réponses synthétiques historiques (`\x1b[1;1R`, `\x1b[?1;2c`, bulk writes) autour des sessions Git. Après le rebuild, aucun scénario graphique nouveau n'a été exécuté depuis QA ; je ne conclus donc pas sur une absence live de burst dans UnitySoundTool/UnityPlugin/IdeA. Conclusion QA : - VERT pour le correctif frontend ciblé : le test ajouté couvre la cause racine et échouerait si `onData` restait actif pendant le replay. - VERT pour les suites ciblées et le build frontend. - VERT pour le rebuild AppImage avec `APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1`, artefact disponible sur le chemin utilisé par les agents. - Validation live hôte des trois Git TUI Native non conclue depuis ce sandbox faute de pilotage UI fiable et de visibilité PID hôte. Une relance d'IdeA sur l'AppImage rebuildée puis une saisie manuelle `Hello` dans IdeA, UnitySoundTool et UnityPlugin reste le seul complément non automatisé.