17 KiB
issueRef, version, updatedBy, updatedAt
| issueRef | version | updatedBy | updatedAt | ||||
|---|---|---|---|---|---|---|---|
| #292 | 5 |
|
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, template07670754-878b-4a37-8e18-5b061c51cd9b, syncedTemplateVersion14. - UnityPlugin:
15e9bbaf-8212-4c6e-a592-9d4f7fb44bf1, même profile/template, syncedTemplateVersion13. - UnitySoundTool:
af772fd8-306e-4ea0-aa20-8f6f537c3377, même profile/template, syncedTemplateVersion14.
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: Gitdone. - SoundTool
.ideai/live-state.json: Gitdone, ticketc5801e75-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.jsonont retournénullpour 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
- Boucle runtime OpenCode/MCP ou launch/reattach propre aux projets externes: les sockets/cwd changent mais le profil est identique.
- Boucle frontend TUI Native: double submit, reattach/render loop ou écriture répétée de l'entrée humaine.
- État projet/layout/session stale non capturé par
live-state.jsonsimple. - 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_agentfixeallow_structured_alongside_pty=falseet ne demande le chemin structuré que pour une celluleChat. La surface courante ignore cecellKindet rend toute cellule agent viaTerminalView: le profil OpenCode est donc lancé une fois comme TUI native dans un PTY.OpenCodeSession::send_innern'est pas sur ce chemin utilisateur.LaunchAgent::executeconsulte 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::subscriberemplace 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.logcorrèle directement SoundTool Gitaf772fd8-306e-4ea0-aa20-8f6f537c3377:front_attached=trueà1788954519395, puis 119 appelswrite_terminalsur la session557f7aff-2ee5-47f3-9e5b-4d268e255eabavant le détachement à1788954524075, sans séquence de saisieHello/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.onDataest installé avant la réattache. Le callback met toute donnée danspendingtant que le handle est absent. Aprèsreattacher(...), le code faitterm.write(scrollback)puisadopt(h); le replay des anciennes requêtes terminal provoque donc de nouvelles réponses xterm, les accumule danspending, puisadoptles 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.tsxdepuisfrontend/— 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.onDatapendantterm.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.tsxfrontend/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 connuHTMLCanvasElement.getContext()non implémenté.npm --prefix frontend run build— vert (tsc --noEmitpuis 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'abonnementterm.onDataest désormais créé paractivateInput(). Sur réattache avec scrollback,term.writereç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étiqueESC[1;1Rpendant le replay deESC[6n; le test vérifie zéro écriture PTY pendant ce replay, puis une seule écritureHello\raprè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
onDatarestait 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
Hellodans IdeA, UnitySoundTool et UnityPlugin reste le seul complément non automatisé.