Files
IdeA/.ideai/memory/mcp-bridge-and-delegation-runtime-notes.md
Blomios 27597eb64e feat(permissions): voie projection CLI (LP0→LP3) + checkpoint Codex/input
Jalon vert regroupant deux chantiers entrelacés dans le working tree,
indissociables au niveau fichier mais tous deux verts (cargo test
--workspace + tests frontend permissions au vert).

Permissions — voie « projection CLI » (advisory), complète :
- LP0 domaine pur : modèle PermissionSet/EffectivePermissions, resolve
  deny-wins + postures Allow<Ask<Deny (crates/domain/src/permission.rs).
- LP1 store : FsPermissionStore (.ideai/permissions.json).
- LP2 use cases : Get/Update project, Update agent override, Resolve.
- LP3 projecteurs Claude/Codex (settings.local.json / config.toml),
  câblage launch-path + PermissionProjectorRegistry, nettoyage des
  fichiers Replace orphelins au swap de profil (LP3-4), composition root
  + commandes Tauri, UI PermissionsPanel (projet + override agent).
- ports.rs : PermissionStore + FileSystem::remove_file (cleanup au swap).

Reste ouvert (hors scope, marqué dans le code) : LP4 enforcement OS
airtight (Landlock fichiers) + résumé de permissions injecté.

Inclut aussi le chantier Codex/input/sessions structurées en cours
(McpConfigStrategy, StructuredAdapter, gestion d'input) partageant les
mêmes fichiers (lifecycle.rs, commands.rs, dto.rs, state.rs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:39:18 +02:00

17 KiB

name, description, metadata
name description metadata
mcp-bridge-and-delegation-runtime-notes Pieges runtime du pont MCP et de la delegation inter-agents IdeA, et la regle de rebuild de l'AppImage.
type
project

Pont MCP & delegation : pieges runtime

Deux bugs trouves le 2026-06-13 en testant la conversation inter-agents (idea_ask_agent vers TestConversation), tous deux corriges. Notes utiles pour ne pas reperdre du temps :

Le binaire qui tourne = AppImage installee, pas les sources

L'IdeA en cours d'utilisation est /home/anthony/Documents/IdeA_0.1.0_amd64.AppImage. Ce meme binaire sert a la fois de serveur orchestrateur (il tient OrchestratorService/InputMediator et compose les evenements) ET de binaire-pont (<exe> mcp-server … declare dans chaque .ideai/run/<id>/.mcp.json). Donc tout correctif cote serveur ou cote pont n'est actif dans l'app que apres rebuild + reinstall de l'AppImage et relance d'IdeA. Un binaire target/debug fraichement compile ne valide que le pont (qui parle au serveur via le socket) ; il ne valide pas la composition cote serveur. Comment appliquer : apres une correction backend, rebuild AppImage (npm --prefix frontend run build puis, depuis crates/app-tauri/, ../../frontend/node_modules/.bin/tauri build --bundles appimage), remplacer l'AppImage, relancer IdeA, puis retester. Exclure NSIS (--bundles appimage) sur Linux. Piege FUSE (2026-06-14) : l'etape finale linuxdeploy echoue avec failed to run linuxdeploy (linuxdeploy est une AppImage qui se monte via FUSE). Workaround obligatoire : prefixer APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1. Le compile Rust reussit avant ce point ; seul le bundling casse, et l'AppDir est genere mais pas le .AppImage. L'artefact final = target/release/bundle/appimage/IdeA_0.1.0_amd64.AppImage.

Env AppImage pollue le shell

La session shell herite des variables de l'AppImage montee (APPDIR, LD_LIBRARY_PATH, PYTHONHOME -> /tmp/.mount_IdeA_*). Consequences : python3 casse (No module named 'encodings') et lancer un binaire app-tauri fraichement compile tente de booter WebKit et crash. Workaround : lancer avec un env propre (env -i PATH=/usr/bin:/bin HOME=$HOME XDG_RUNTIME_DIR=/run/user/1000 ...), utiliser jq plutot que python.

Bug 1 — pont MCP en lockstep (corrige)

mcp_bridge.rs::relay lisait 1 ligne client -> attendait 1 reponse loopback, en boucle. MCP n'est pas 1:1 : notifications/initialized n'a pas de reponse => deadlock juste apres initialize, et tools/list n'etait jamais relaye => les outils idea_* ne se chargeaient jamais (claude mcp list affichait quand meme "Connected" car initialize repond). Corrige en relay full-duplex (deux pompes concurrentes) + drain borne (DRAIN_GRACE) a la fermeture stdin. Symptome cote utilisateur : 3 jours de "outils MCP pas charges".

Bug 2 — prefixe de delegation perdu (corrige)

Le signal [IdeA · tâche de <demandeur> · ticket <id>] qui dit a l'agent cible "reponds via idea_reply, jamais en texte" n'etait plus compose nulle part : supprime du backend (service.rs C3 §5.1) lors du passage de l'ecriture PTY au frontend, mais le frontend (useWritePortal.ts) ecrit head.text verbatim et ne l'ajoutait pas. La cible recevait la tache brute, repondait en texte => idea_ask_agent timeout. Corrige en composant le prefixe dans infrastructure/src/input/mod.rs (delegation_preamble) a l'emission de DomainEvent::DelegationReady ; la tache brute reste dans le Ticket/historique. Rappel : la correlation idea_reply marche par ticket OU par tete de FIFO (fallback), donc le ticket echo est recommande mais pas strictement requis.

Bug 3 — cold-launch race : 1er tour perdu (corrige 2026-06-14)

Deleguer a un agent froid (pas encore lance) via idea_ask_agent echouait silencieusement : terminal cible vide, ask bloque jusqu'au timeout 300s ; un agent chaud (deja a son prompt, lance manuellement) marchait. Cause : avec with_structured non cable sur l'orchestrateur (regression assumee aa2f67a), tous les ask passent par le chemin PTY ensure_live_pty (application/src/orchestrator/service.rs). Un agent jamais vu etait initialise Idle (BusyTracker::start_turn -> or_insert(Idle), infrastructure/src/input/mod.rs), donc le 1er enqueue publiait DelegationReady immediatement et ecrivait la tache dans le PTY avant que le CLI ait affiche son prompt -> tache perdue. Le prompt-ready watcher (lot C5) ne gardait que les tours suivants. Fix : ensure_live_pty renvoie un flag cold_launch ; si cold ET prompt_ready_pattern non vide, l'orchestrateur appelle mark_starting(agent) -> la DelegationReady du 1er tour est differee puis publiee par le watcher a l'apparition du prompt. Sans pattern ou agent chaud -> livraison immediate (zero regression). Touche domain/src/input.rs (port mark_starting), infrastructure/src/input/mod.rs, service.rs.

Bug 4 — le fix cold-launch ne s'armait jamais en prod (corrige 2026-06-15)

Le « fix Bug 3 corrige 2026-06-14 » etait trop optimiste : il ne s'arme que si gate_cold_start = cold_launch && prompt_ready_pattern non vide (service.rs). Or le profil Claude Code (664cc20c, partage par TOUS les agents) dans ~/.local/share/app.idea.ide/profiles.json n'a aucun prompt_ready_pattern. Donc mark_starting n'etait jamais appele -> enqueue publiait DelegationReady immediatement -> tache ecrite dans le PTY avant le prompt de claude -> 1er tour perdu -> idea_ask_agent vers un agent froid bloque jusqu'au timeout (un agent chaud marche). Le Bug 3 n'etait valide que par des tests unitaires qui injectent un pattern a la main : le gap d'integration (profil sans pattern) n'avait pas ete vu. Fix (option B, signal MCP, sans sniff de prompt TUI) : on gate le cold-launch des qu'un MCP est configure sur le profil (gate_cold_start = cold_launch && (pattern non vide || profil.mcp.is_some())), et on libere le tour differe quand le pont MCP de l'agent froid se connecte (= son CLI est up + outils charges) : nouveau port InputMediator::release_cold_start(agent) (drain du deferred, sans mark_idle car c'est un signal de DEMARRAGE, idempotent et OR-safe avec prompt_ready), McpServer fire un ready_sink: Fn(&str) sur initialize avec le requester du handshake, et la composition root (state.rs::ensure_mcp_server) parse le requester en AgentId -> OrchestratorService::release_agent_cold_start. Touche domain/src/input.rs, infrastructure/src/input/mod.rs, infrastructure/src/orchestrator/mcp/server.rs, application/src/orchestrator/service.rs, app-tauri/src/state.rs. Tests unitaires verts ; validation live = rebuild AppImage + relance IdeA (cf. section binaire qui tourne). Filet OR : si un jour un profil porte un prompt_ready_pattern, les deux signaux coexistent. Methodo : ne PAS deleguer la reparation du systeme inter-agents via le systeme inter-agents (casse) — utiliser les subagents natifs (outil Agent).

Bug 5 — la boucle serve du serveur MCP est en lockstep, un ask sans reponse wedge TOUTE la connexion (corrige 2026-06-15)

Symptome : 1er idea_ask_agent vers un agent qui repond -> OK ; puis ask vers un agent qui ne rappelle JAMAIS idea_reply (ex. QA lance mais qui ne repond pas) -> ensuite tout appel suivant du MEME demandeur (meme idea_list_agents, sans rendezvous) se bloque. Cote utilisateur : "DevBackend ne marche plus" alors qu'il repondait 11s avant — en realite c'est la connexion du DEMANDEUR (Main) qui est morte, pas la cible. Annuler l'appel cote client ne deparke rien. Cause : infrastructure/src/orchestrator/mcp/server.rs::serve lisait 1 requete puis awaitait handle_raw en entier avant de relire. Pour idea_ask_agent, handle_raw -> dispatch -> service.dispatch attend le idea_reply de la cible : pendant cette attente la boucle ne relit plus rien -> pipeline de la connexion fige. C'est l'analogue COTE SERVEUR du Bug 1 (lockstep) qui n'avait ete corrige que cote pont (mcp_bridge.rs). NB : la couche application bornait deja le rendezvous (service.rs::ask_agent via tokio::time::timeout), donc l'attente n'etait pas infinie — le vrai coupable etait bien la serialisation de serve, pas l'absence de timeout. Fix : serve reecrite full-duplex non bloquante — tokio::select! entre transport.recv() et un canal mpsc::unbounded::<Option<Vec<u8>>> ; chaque message entrant traite dans une tache tokio::spawn qui possede un clone cheap self.for_requester(self.requester.clone()) ('static) + un clone du tx ; reponses (Some) ou notifications (None) renvoyees par le canal et ecrites par la meme boucle ; arret gracieux via compteur in_flight (on draine les taches en vol apres EOF). Les reponses MCP portent l'id -> ordre indifferent. Le trait Transport (&mut self recv/send) et les signatures serve/serve_as restent intacts. + filet de securite : timeout serveur isole au seul idea_ask_agent (ASK_RENDEZVOUS_TIMEOUT 24h, finie ; setter with_ask_rendezvous_timeout #[doc(hidden)] pour les tests). Tests : infrastructure/tests/mcp_server.rs -> pending_ask_does_not_wedge_the_connection_concurrent_call_still_answered (anti-wedge, echoue sur l'ancien code) + ask_agent_rendezvous_times_out_with_a_jsonrpc_error. Verts : cargo test -p infrastructure (20 mcp_server), cargo test -p app-tauri --lib mcp_e2e_loopback_tests (6). Validation live = rebuild AppImage + relance IdeA (la connexion wedgee ne se deparke pas de l'interieur : il FAUT relancer IdeA). AppImage du 2026-06-15 10:58 contient le fix ; backup ~/Documents/IdeA_0.1.0_amd64.AppImage.old-prewedgefix. Reste a investiguer (separe, non bloquant) : pourquoi QA lance ne repond pas du tout a une delegation (son claude n'appelle pas idea_reply) — impossible a creuser tant que la connexion du demandeur est wedgee ; le fix Bug 5 permet desormais de le diagnostiquer sans tout figer.

Bug 6 — la tache n'est JAMAIS ecrite dans le PTY d'un agent delegue en arriere-plan (corrige 2026-06-15)

C'EST la cause racine du « reste a investiguer » du Bug 5. Symptome : un agent lance a la main (cellule visible, ex. DevBackend) repond ; un agent froid auto-lance par idea_ask_agent (ex. QA) ne repond JAMAIS → ask bloque jusqu'au timeout (24h, ASK_AGENT_TIMEOUT). Reproduction sure et non bloquante : idea_stop_agent puis idea_launch_agent(task=…, visibility=background), et observer le dossier de session claude de la cible (~/.claude/projects/<run-dir>/*.jsonl) : aucune nouvelle session en 28 s = la tache n'a jamais ete soumise (le pont MCP de la cible, lui, se connecte bien). Cause : depuis ARCHITECTURE §20, l'ecriture physique du PTV est faite par le write-portal FRONTEND (useWritePortal), qui n'est monte que pour une cellule de layout (leaf) visible. Or ensure_live_pty cold-lance la cible en background avec node_id: None (service.rs) → aucune cellule montee → useWritePortal n'existe pas → l'event DelegationReady n'a aucun consommateur → tache perdue. Les fix Bug 3/4 (differer/liberer la DelegationReady) ne pouvaient donc jamais marcher pour un agent background : ils publient un event que personne n'ecoute cote UI. Fix (option « writer PTY backend ») : le mediateur ecrit lui-meme la tache dans le PTY quand aucune cellule frontend n'est attachee. Registre front_owned dans MediatedInbox, alimente par le front via bindHandle/unbindHandle → commande Tauri set_front_attachedOrchestratorService::set_agent_front_attachedInputMediator::set_front_attached. Point de livraison unique = BusyTracker::publish_deferred (chemin chaud immediat ET drains froids prompt_ready/release_cold_start), qui passe par un HeadlessSink optionnel cable par MediatedInbox::with_pty/with_events : agent dans front_ownedSome(d) (publie l'event, le front ecrit, inchange) ; sinon ⇒ ecrit texte puis (apres submit_delay_ms, defaut 60ms, anti-paste-detection) la submit_sequence dans le handle PTY bound, sur un std::thread detache (le watcher prompt-ready tourne sur un std::thread, PAS tokio — ne pas utiliser tokio::spawn). Touche domain/src/input.rs (port set_front_attached), infrastructure/src/input/mod.rs (HeadlessSink + front_owned + handles en Arc<Mutex>), application/src/orchestrator/service.rs, app-tauri/src/{dto,commands,lib}.rs, frontend/src/{ports,adapters/input,adapters/mock,features/terminals/useWritePortal}. Tests verts : cargo test -p infrastructure (input 34, dont headless_agent_without_front_cell_is_written_by_the_backend + front_attached_agent_is_delivered_via_event_not_backend_write), app-tauri lib 42, useWritePortal 9, tsc. Validation live = rebuild AppImage + relance IdeA (build 2026-06-15 11:42 ; backup ~/Documents/IdeA_0.1.0_amd64.AppImage.old-prefrontwriterfix). NB diagnostic : ~/.claude/projects/<encoded-run-dir>/*.jsonl = transcript de session claude de l'agent ; pas de nouveau fichier apres une delegation = tache jamais soumise. ss -xp | grep idea-mcp = ponts MCP connectes cote serveur.

Bug 7 — une delegation interrompue/annulee laisse la cible Busy a vie (corrige 2026-06-15, sources ; pas encore en AppImage)

Symptome : un agent qui repondait (ex. DevFrontend a « 123x4=492 », QA pendant LP3) ne repond plus du tout aux delegations suivantes ; idea_ask_agent bloque jusqu'au timeout. DevBackend, lui, continue de marcher. Diagnostic ecarte 2 fausses pistes : (a) PAS le socket MCP — les 6 ponts sont ESTAB (ss -xp | grep idea-mcp), pont vivant ; (b) PAS « lance a la main vs par IdeA » — DevBackend est aussi auto-lance et marche. Le vrai discriminant : une delegation vers cet agent a-t-elle ete interrompue/annulee cote demandeur ? DevFrontend coince par l'interruption d'une tache LP3 ; QA coince par un ping diag rejete ; DevBackend jamais annule => OK. Confirmation cote transcript : la tache figure dans le log.jsonl de la conversation (donc enqueue cote serveur a eu lieu) mais PAS dans le transcript claude de la cible (~/.claude/projects/<run-dir>/*.jsonl) => jamais ecrite dans son PTY. Cause (lue dans application/src/orchestrator/service.rs, chemins ask PTY ~l.847-911 ET ask_structured ~l.927-1011) : l'agent passe Idle→Busy des input.enqueue (~l.978). Il ne redevient Idle que sur la branche succes (input.mark_idle, ~l.1001). Les branches erreur/annulation/timeout (~l.901-910 et ~l.1004-1009) appellent mailbox.cancel_head mais jamais mark_idle. Pire : quand le demandeur interrompt l'appel, le futur ask_agent est dropped => AUCUNE branche du select! ne s'execute => l'agent reste Busy pour toujours. Une cible Busy met les delegations suivantes en file derriere un tour fantome, jamais livrees au PTY. L'etat Busy vit en memoire dans le process serveur et ne se deparke pas de l'interieur : deblocage immediat = relancer IdeA (comme le wedge du Bug 5). Fix (corrige cote sources, service.rs) : garde RAII BusyTurnGuard (Arc clones de InputMediator + mailbox, agent_id, ticket_id, flag armed) cree juste apres l'enqueue dans les DEUX chemins (ask PTY et ask_structured) ; son Drop (si arme) appelle cancel_head(agent,ticket) puis mark_idle(agent) ; disarm() sur la branche succes (le mark_idle propre existant reste, pas de double cancel). Les cancel_head redondants des branches erreur/_cancelled/_elapsed ont ete retires au profit du garde (cancel_head est positionnel/idempotent). Indispensable que ce soit un garde et pas un mark_idle dans les branches : le cas reel est un futur DROPPED, aucune branche du select! ne s'execute. Tests verts : cargo test --workspace 80 suites 0 echec, dont dropped_ask_future_frees_busy_target, second_delegation_delivered_after_dropped_ask, cancelled_ask_marks_target_idle (tests/orchestrator_service.rs) + 2 tests du garde dans le mod tests de service.rs. VERDICT sweep_stalled (infra/input/mod.rs) : purement advisory — bascule Alive→Stalled et emet AgentLivenessChanged mais n'appelle JAMAIS mark_idle (par conception : « la FIFO et le tour continuent »). Ce n'est donc PAS un filet pour ce bug ; le garde RAII est la seule correction. Non recable (changement de semantique hors perimetre). Pas encore actif live : rebuild AppImage requis (npm --prefix frontend run build puis depuis crates/app-tauri/ APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1 ../../frontend/node_modules/.bin/tauri build --bundles appimage, remplacer l'AppImage, relancer IdeA). Le relancement d'IdeA debloque aussi DevFrontend/QA actuellement coinces Busy (etat en memoire). Methodo (rappel Bug 4) : NE PAS reparer le systeme inter-agent via idea_ask_agent (casse) — utiliser les subagents natifs (outil Agent).

See also remaining-work-idea-agent-control-ide, agent-context-memory-and-profile-handoff.