Files
IdeA/.ideai/tickets/292/carnet.md

17 KiB

issueRef, version, updatedBy, updatedAt
issueRef version updatedBy updatedAt
#292 5
kind agent_id
agent a6c6ea12-bfc6-4bdc-8031-324102dfa34d
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 :

git status --short --branch
## 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/
git diff --check
<aucune sortie, exit 0>
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
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.

npm --prefix frontend run build
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 :

cd crates/app-tauri
APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1 ../../frontend/node_modules/.bin/tauri build --bundles appimage
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 :

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
/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.

sha256sum /home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage
4165521e4248ec9f71af4ceb1c89d3aa54e8119a1ccf0f70d15268bdc67eaf94  /home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage

Contrôles hôte tentés :

env | sort | rg 'DISPLAY|WAYLAND|XDG_RUNTIME|IDEA|APPIMAGE|TAURI'
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
xdotool search --name 'IdeA - Agents' getwindowgeometry %@
Window 44040214
  Position: 0,28 (screen: 0)
  Geometry: 1920x1052
import -window 44040214 /home/anthony/.local/share/app.idea.ide/managed-tmp/accb434c-d2d1-4c01-b928-72e738151fdc/data/idea-agents-window-292.png
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.
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
X Error of failed request:  BadMatch (invalid parameter attributes)
  Major opcode of failed request:  73 (X_GetImage)
APPIMAGE_EXTRACT_AND_RUN=1 /home/anthony/Documents/Projects/IdeA/.appimage-build/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage --version
(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)'.
ps -eo pid,ppid,pcpu,pmem,comm,args --sort=-pcpu | head -40
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 :

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é.