Le corps d'une leaf (layout-leaf-body) crée désormais son propre stacking context (z-index 0). Les vues CLI custom peuvent comporter des enfants à haut z local (toolbar z-20) qui restaient au-dessus des contrôles de cellule (CELL_Z 5) et captaient les clics ; ils restent désormais sous les contrôles. Test de layering dédié ajouté dans LayoutGrid.cellControlsLayering.test.tsx.
#165 (seam plugin slash-commands) et #166 (dispatch e2e du callback plugin)
fermés après merge de feature/165-plugin-slash-commands dans develop.
L'épic #161 (commandes slash custom CLI) est entièrement terminé : tous ses
sous-tickets (#162 fondation, #163 autocomplete, #164 /profile, #165 seam
plugin) plus le dispatch e2e (#166) sont intégrés.
Sync du miroir local + counter (#166 créé).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clôture l'épic slash-commands #161 : avec le seam plugin (#165) et le dispatch
end-to-end du callback (#166), tous les sous-tickets natifs + plugin sont livrés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boucle le gap end-to-end de #165 : à l'exécution d'une commande slash plugin
depuis le composer custom, l'UI traite l'effet pluginCallback et le dispatche
au PluginRuntimeRegistry pour que la callback du plugin s'exécute réellement.
- runtime/registry: runCommandStrict (échec explicite si handler absent) +
propagation de la valeur de retour ; élargit les types de retour (unknown).
- runtime/loader: chargement de contributes.slashCommands au registre +
propagation du retour du handler.
- features/plugins/usePluginMenus: runCommand retourne unknown.
- features/agents/CustomAgentChatView: handler de l'effet pluginCallback ->
pluginRuntime.registry.runCommandStrict, avec retour utilisateur en cas
d'échec (commande indisponible / callback absente).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Permet à un plugin de contribuer des commandes slash adossées à une callback,
transitant par le registry/contrat unifié (#162) :
- sdk/IdeaSDK: manifeste contributes.slashCommands (déclaratif) — pointer bumpé.
- domain: source Plugin étendue + effet PluginCallback (la commande ne
s'exécute pas directement : elle renvoie une référence à la callback du plugin).
- application: registration des commandes plugin dans le registry + exécution
renvoyant l'effet PluginCallback ; métadonnées UI suffisantes pour l'autocomplete.
- backend + app-tauri: DTO + commandes Tauri listant/invoquant les commandes plugin.
- frontend (contrat): types adapters/domain/ports/mock pour pluginCallback et
l'invocation avec arguments.
L'exécution effective de la callback côté runtime UI est livrée par #166.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#164 (/profile + reset de session confirmé) fermé après merge de
feature/164-profile-command dans develop. Sync du miroir local.
Reste #165 (seam plugins) pour clore l'épic #161.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Commande /profile first-class avec reset de session explicite et confirmé,
séparée de la palette slash générique. Dernier sous-ticket de l'épic #161
avant #165 (seam plugins).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute /profile comme commande slash first-class dont la sémantique de reset
de session reste explicite et testable, séparée de la palette générique :
- domain + application: /profile est enregistrée et renvoie un effet
SlashCommandEffect::ProfileSwitch { session_id }. La commande ne reset pas
directement : elle délègue l'effet au frontend pour imposer une confirmation.
- frontend (CustomAgentChatView): à l'exécution de /profile, ouverture d'un
popup de sélection de profil informant que la validation reset la session,
avec confirmation explicite (confirmedReset) requise avant changeAgentProfile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#163 (UX autocomplete slash-commands) fermé après merge de
feature/163-slash-command-autocomplete dans develop. Sync du miroir local.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UX d'autocomplétion slash dans le composer custom, consommant le contrat
unifié de #162 (aucune liste codée en dur). Débloque #164 (/profile).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
À la saisie d'un « / » en début de draft (sans espace), ouverture d'un menu
contextuel listant les commandes matchant le préfixe, via le contrat unifié
listSlashCommands — aucune liste codée en dur côté UI. Rafinement incrémental
des suggestions à la frappe, navigation clavier + sélection, exécution par
executeSlashCommand.
- adapters/agent: listSlashCommands(prefix?) + executeSlashCommand (invoke Tauri).
- domain + ports: types SlashCommand / ExecuteSlashCommandResult.
- mock: données de test.
- CustomAgentChatView: détection de préfixe (/ sans espace), fetch avec séquence
anti-retard, gestion ouverture/active index, navigation clavier, fermeture
quand le préfixe cesse d'être éligible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#162 (fondation slash-commands) fermé après merge de
feature/162-slash-commands-foundation dans develop. Sync du miroir local.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduit le contrat unifié des commandes slash consommable par le frontend,
indépendant de la source (native, puis plugin plus tard) :
- domain: modèle pur SlashCommand / SlashCommandSource / SlashCommandAvailability,
métadonnées UI (nom, description, disponibilité, confirmation requise) et
validation des noms/préfixes.
- application: registry + list/filter par préfixe + plan d'exécution natif ;
commandes natives /help et /clean (/clean = nettoyage de la vue de session
courante ; pas de /reset distinct tant qu'aucune utilité produit ne le justifie).
- backend + app-tauri: exposition du contrat via DTO/transport + tests DTO.
Source neutre : les commandes contribuées par plugins (#165) transiteront par
le même registry sans changer le contrat frontend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#151 (z-order bouton Cancel) et #157 (arrêt spinner Progress) fermés
après merge du Cluster A (fix/batch-151-157-cli-ui) dans develop.
Sync du miroir local .ideai/tickets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#151: z-order du bouton Cancel en état busy (LayoutGrid).
#157: arrêt du spinner Progress à l'état terminal (CustomAgentChatView).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute completeRunningProgress() qui passe les stages started/delta à
completed lors des événements final/error. La ligne Progress conserve son
contenu statique utile mais n'affiche plus l'animation de chargement une
fois le tour terminé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extrait le bouton Cancel du banner inline et le rend en position absolue
(top:24, right:20) avec zIndex CELL_Z.turnActions. Le banner de statut est
décalé (right:76 en état busy) et rétrogradé en CELL_Z.banner, afin que
Cancel reste au premier plan et cliquable sans recouvrement en état busy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- frontend: onPaste sur CustomAgentChatView, détection MIME image, chip/preview d'attachment, envoi via le contrat #154 (adapters/agent, ports)
- backend: import d'image par bytes dans le store attachments (commands, chat_attachments app+infra, dto, ports) + tests
- #151: chevauchement des boutons dans la barre supérieure pendant une conversation agent
- #158: popup de reprise de conversation intempestive (ajout cellule / changement layout / projet)
- #159: pastille d'activité de projet
- Met à jour le carnet avec la preuve runtime returned cellKind=pty; expected chat
- Documente les hypothèses invalidées et la nouvelle cible de correction
- Met à jour issue.md avec le titre et le diagnostic consolidés
- Incrémente le compteur et ajoute les tickets 150-158 à l'index
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Ajoute cellKind dans AgentChatHandle et vérifie que launchAgentChat reçoit cellKind:chat
- Rejette avec erreur STRUCTURED_ROUTED_TO_PTY si le backend route vers PTY
- Ajoute cellKind:chat dans MockAgentGateway.launchAgentChat
- Corrige l'overflow du layout CustomAgentChatView (bounded shell avec scroll + composer fixe)
- Dédouble les prompts utilisateur quand le stream les echo
- Ajoute les tests launchAgentChat returns handle only when confirms chat et launchAgentChat rejects PTY-routed launch
- Ajoute les tests does not duplicate user prompt when echoed et keeps chat shell bounded
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Ajoute le champ cell_kind dans LaunchAgentRequestDto pour distinguer les demandes chat (custom CLI) des lancements PTY historiques
- Convertit cellKind:Chat en require_structured:true dans commands.rs et web-server/src/lib.rs
- Implémente la logique de routing structuré dans LaunchAgent::execute :
- valide que require_structured implique un profil avec structured_adapter
- remplace une session PTY existante quand require_structured est vrai
- route vers structured seulement quand wants_structured est vrai
- Met à jour tous les appels historiques avec require_structured:false pour préserver le contrat PTY
- Ajoute les tests unitaires human_launcher_with_chat_intent_routes_structured_profile_to_chat_session et chat_intent_replaces_existing_pty_instead_of_returning_pty_session
- Wire les ports structured dans BackendCore pour le launcher humain
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Elargit isNotFound() pour reconnaitre aussi la forme brute
'not found: structured session <uuid>' que Tauri peut retourner,
en plus de la forme typée {code: 'NOT_FOUND'}.
Ajoute deux tests couvrant:
- fallback sur reattach avec erreur brute
- retry prompt sur send avec erreur brute
QA: tests passés sur cette correction.
Ajoute des points de traçage ciblés sur le cycle de vie de session
CustomAgentChatView/LayoutGrid pour isoler la cause du fallback
silencieux CLI custom avant tentative de fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consigne Architecte: le fallback custom→TUI persiste après 3 correctifs sur
LayoutGrid.tsx. Nouvelle piste non explorée: boucle d'effet sessionId dans
CustomAgentChatView.tsx (openOrAttach déclenché par son propre callback
onSessionId). Carnet mis à jour avec l'historique complet des tentatives et
la règle de traçabilité demandée par l'utilisateur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le fallback custom→TUI se redéclenchait après le premier chargement dès que
refreshProfiles() renvoyait un instant un profil pinné sans structuredAdapter
(refetch en vol), coupant la CLI custom ~0.5s après son ouverture. Introduit
un binding "trusted" (agent+profil validés) qui reste valide tant qu'aucun
changement légitime de profil n'est observé via agentProfileChanged, et ne
retombe en TUI que sur perte réelle du CLI custom.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Retest utilisateur du 2026-08-05: la CLI custom s'ouvre ~0.5s puis retombe
sur Plain/TUI malgré le fix précédent. Statut repassé à open pour relancer
le cycle de diagnostic/correction.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cause racine restante après le fix de la course au premier chargement : le
garde-fou de LayoutGrid retombait sur `tui` dès que agents/profiles semblaient
indisponibles, y compris quand le cache était simplement stale (refresh en
cours), écrasant silencieusement la préférence custom. Le fallback n'agit
désormais qu'une fois le catalogue confirmé à jour.
QA verte : LayoutGrid.chat.test.tsx (7 tests), src/features/layout (16
fichiers, 130 tests), cas ciblé "stale catalog refresh".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rattrapage de l'état runtime .ideai/ (tickets #119-#139/#147 clôturés,
compteur→149, ticket #148 clos et QA verte, métadonnées plugin android) —
séparé du code de feature avant d'ouvrir le travail sur le nouveau bug
de lancement CLI custom.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sur reattach NOT_FOUND, le composant retentait implicitement l'attach sur le
même sessionId mort au lieu de nettoyer l'état avant de relancer, ce qui
produisait l'erreur "not found: structured session …" côté utilisateur.
recoverStructuredSession() nettoie désormais sessionId/conversationId avant
de relancer et retente une seule fois le reattach sur NOT_FOUND post-launch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
reattachAgentChat pouvait échouer avec NOT_FOUND (session structurée fermée
ou jamais vivante) et laisser fuiter l'erreur brute jusqu'à l'utilisateur
("not found: structured session ... not found") au lieu de relancer une
session fraîche. Le fallback ne s'applique qu'au code NOT_FOUND ; toute
autre erreur de reattach continue de remonter telle quelle.
QA vert 2026-08-05 : npx vitest run CustomAgentChatView.test.tsx,
npx vitest run agents.test.tsx CustomAgentChatView.test.tsx, npm run typecheck.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Livraison complète : toggle TUI/CLI custom par cellule, socle backend
AgentSession/ChatBridge, et correctif du bouton Cancel (interruption du
tour courant au lieu de fermer la session). QA a validé en vert après
le correctif frontend ciblé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implémente la vue chat structurée par cellule agent (toggle TUI/CLI custom,
préférence persistée `preferred_view`, reattach live, composer + pièces
jointes) avec le socle backend AgentSession/ChatBridge (UserPrompt,
cancel_current_turn, routage interrupt_agent, commande cancel_agent_chat).
Corrige le bug bloquant relevé par QA : le bouton Cancel de
CustomAgentChatView interrompait tout le tour via closeAgentChat au lieu
de n'annuler que le tour courant via cancelAgentChat, ce qui tuait la
session contrairement au contrat produit validé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebase de l'état ticket (réouverture #102, clôtures #141-146, ouverture
#147) et ajout du contexte agent glmopencode avant démarrage du cycle #147.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Le store de tâches d'arrière-plan produisait un état incohérent lors de la
délégation multi-agent via idea_ask_agents. Tests ciblés ajoutés/étendus,
suite verte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La detection d'absence de reponse structuree finale (structured_no_reply_error)
ne couvrait pas AgentSessionError::Decode ni le message "aucun final textuel
exploitable" emis par l'adaptateur OpenCode, ce qui laissait idea_ask_agent
planter silencieusement sur les profils GLM/OpenCode au lieu de retomber sur
le chemin de secours prevu.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajoute la vue window host qui charge le layout plugin dans une fenêtre OS
détachée, expose react/react-dom/jsx-runtime hébergés sous
frontend/public/plugin-host pour que le runtime SDK y résolve React, et
câble services.windows.open() côté runtime plugin (loader/services/registry).
npm test -- --run src/adapters/window.test.ts src/app/ViewWindow.test.tsx
src/plugins/runtime/loader.test.ts src/plugins/runtime/services.test.ts : 39/39
npm run typecheck : OK
npm run build : OK
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Étend le port/usecases window et layout.customPluginLayout pour ouvrir et
piloter des fenêtres OS hébergeant un layout plugin, en réutilisant
contributes.layouts plutôt qu'un système de fenêtres parallèle. Câble la
commande Tauri et l'état app-tauri correspondants.
Cargo test -p domain --test window : 5/5
Cargo test -p application --test window_usecases : 7/7
Cargo test -p infrastructure --test window_state_store : 1/1
Cargo test -p app-tauri view_window_tests : 8/8
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enregistre le cadrage du programme fenêtres plugin-hébergées (#142 backend,
#143 frontend host + windows.open, #144 runtime React partagé, #145 docs SDK,
#146 QA) et met à jour le contexte de l'agent Git avec le modèle de branches
git-flow simplifié.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Étend le flux de création de layout backend (DTO, usecases, store) et le
frontend (sélecteur, adaptateurs, LayoutTabs/LayoutGrid) pour permettre
d'ouvrir un layout déclaré par un plugin installé (ex. Android Health),
sans passer par le message bloquant "extension backend pas encore livrée".
Ticket #141 — QA vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
La lecture du contexte projet global via MCP (idea_context_read sans target)
lisait <root>/CLAUDE.md au lieu de la source de vérité canonique
<root>/.ideai/CONTEXT.md, désynchronisant la lecture MCP de
crates/application/src/project/context.rs. ReadContext, UpdateProjectContext
et ProposeContext partagent désormais project_context_path/project_context_dir,
avec création du dossier .ideai/ à l'écriture et lecture tolérante à l'absence
du fichier (FsError::NotFound -> contenu vide).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plugins can now declare activationScope ("app" | "project") in their
manifest; loader/runtime honor it to defer activation of project-scoped
plugins until a project is focused instead of activating everything at
app bootstrap. Bumps sdk/IdeaSDK to the commit that adds the field.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Le contexte projet d'un agent s'affichait "[object Object]" et se
vidait à la réouverture du panneau : read_agent_context renvoie déjà
{content} côté backend, mais les gateways front traitaient la réponse
comme une string brute. Les deux gateways (Tauri + HTTP) unwrap
maintenant .content ; type AgentContextDocument ajouté au domaine.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
État d'intégration confiné à la branche batch. Les tickets #119 (skills →
capacités agent découvrables), #122 (override permissions par défaut), #131
(effort par agent/presets) et #132 (outil MCP d'édition du contexte projet)
sont verts en périmètre. Le sprint plugins multi-fichiers ESM / persistance
plugin-owned (#135/#136/#139) est co-implémenté dans les MÊMES fichiers de
câblage (frontend/src/ports/index.ts, backend/src/lib.rs, domain/ports.rs,
backend/dto.rs), inséparable sans staging interactif (indisponible ici).
Commit unique volontaire : préserve l'état vert QA sans découpe hunk risquée.
NON mergé vers develop tant que #137 (QA e2e plugins) n'est pas vert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les espaces et autres frappes étaient interceptés par un handler parent ;
stopPropagation sur le onKeyDown de l'input args laisse la saisie intacte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implémente §22.1 : asset_allowed autorise tout chemin relatif dès lors que
les gardes déjà en place sont satisfaites (entrée registre active,
content_hash du package, confinement canonicalize en aval), au lieu de
restreindre au triplet main/icon/assets. Débloque les imports ESM
multi-fichiers (./constants.js, ./core/x.js) sans affaiblir le modèle de
menace fixé à l'install (#135).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cadrage architecture du ticket #138 : séparation project-owned/plugin-owned,
API canonique ctx.storage seule, stockage sous app_data/plugins/data/<id>/
(frère de installed/), purge intégrale à l'uninstall. Débloque #139.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cadrage architecture du ticket #133 : asset_allowed autorise tout chemin
relatif confiné dès lors que registre actif + content_hash + confinement
canonicalize sont satisfaits, sans gater fichier par fichier. Débloque
#134/#135.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- injecte model et model_reasoning_effort du profil via -c dans argv
- remplace le modèle TUI stale en config.toml
- aligne PTY/interactif sur le comportement déjà garanti par CodexExecSession
- test unitaire codex_pty_launch_forwards_profile_model_as_config_override
- Add model_reasoning_effort field to AgentProfile (domain layer)
- Remove model parameter from codex_config_toml, stop rewriting model in TOML
- Pass model and model_reasoning_effort via Codex CLI -c overrides on every exec
- Update CodexExecSession with new_with_policy_and_overrides factory
- Cover new conversation and resume flows with tests
- Convertit sdk/IdeaSDK en git submodule pointant vers IdeaSDK dédié
- Nettoie les artefacts du dépôt git imbriqué (.git.disabled-empty-nested-repo)
- Le SDK TypeScript plugin est maintenant dans son propre repo
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(sdk,plugins): ajoute capability tooling et services runtime publics (workspace/terminal/tasks)
- capability manifeste tooling supportée backend + transport runtime catalog
- ctx.services publique côté SDK/runtime, gated par tooling
- surface honnête: workspace, terminal, et tasks en observation/contrôle uniquement
- docs/exemple/tests mis à jour
- QA PASS
- capability manifeste tooling supportée backend + transport runtime catalog
- ctx.services publique côté SDK/runtime, gated par tooling
- surface honnête: workspace, terminal, et tasks en observation/contrôle uniquement
- docs/exemple/tests mis à jour
- QA PASS sur feature/sdk-plugin-tooling-build-debug-surface
- carnet.md: enregistre la livraison CORS et la décision Git du 2026-08-01
- issue.md + index.json: mise à jour version/timestamp
- plugins.test.tsx: tests d'intégration frontend liés
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- plugin.ts: normalizeReview() garantit issues/installable définis même si backend omet ces champs
- mock/index.ts: MockPluginGateway.uninstall() retourne removalOutcome pour cohérence avec le domaine
- domain/index.ts: PluginUninstallResult ajoute removalOutcome optionnel
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- plugin_install_load.rs: test uninstalls_then_reinstalls_sdk_hello_plugin_without_runtime_residue()
- application/src/plugin/mod.rs: refactor FakePackages pour supporter plusieurs staged packages et nettoyer les manifests sur uninstall
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- usePlugins.ts: remplace les mises à jour optimistes du state par des appels listPlugins() après chaque opération backend
- PluginsPanel.tsx: ajoute la normalisation isInstallable() pour éviter les crashes si installable est undefined
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause racine de la 4e rechute #120 corrigee : plugin_asset_response
posait aucun header CORS, bloquant l'import() dynamique du bundle plugin
sous WebKitGTK. Valide via cargo test -p app-tauri (test CORS cible +
plugin_install_load) et tests frontend runtime/plugins, tous verts.
Reserve QA non levee : pas de preuve visuelle e2e dans un AppImage reel
(fuse absent dans cet environnement) que le bandeau disparait apres
redemarrage. Cloture definitive du ticket #120 a conditionner a cette
validation AppImage reelle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
plugin_asset_response ne posait aucun header Access-Control-Allow-Origin,
ce qui bloquait l'import() dynamique du bundle plugin (origine distincte
du document) sous WebKitGTK — cause racine confirmee de la 4e rechute #120
(bandeau « Cross-origin script load denied »). Ajoute Access-Control-Allow-
Origin/-Methods sur toutes les reponses du protocole (succes et erreurs) et
un test backend dedie pour eviter une rechute silencieuse.
QA 2026-08-01 : PASS avec reserve — cargo test -p app-tauri (test CORS
cible + plugin_install_load) et tests frontend runtime/plugins verts,
mais pas de preuve visuelle e2e AppImage dans cet environnement (fuse
absent, AppImage non montable ici). Validation AppImage reelle a faire
avant cloture definitive du ticket #120.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Requalification Architect du 2026-08-01: le crash INTERFACE INTERROMPUE au
chargement d'un plugin (menu + item) remonte via ProjectsView -> usePluginMenus
-> MenuBar jusqu'à RootErrorBoundary, quel que soit le plugin (ancien ou
reconstruit via SDK).
- usePluginMenus isole la résolution/conversion des contributions plugin :
toute erreur retombe sur [] au lieu de propager.
- ProjectsView sépare les menus natifs des menus enrichis par plugin et rend
MenuBar derrière une error boundary locale (fallback menus natifs seuls).
- hello-plugin (SDK) et les tests d'installation associés durcis en cohérence.
Bookkeeping ticket #120 uniquement (issue.md, carnet.md) ; les fichiers
counter.json/index.json et le dossier tickets/122/ restent hors commit car
une collision de numérotation #122 existe entre cette base et
feature/ticket120-hello-plugin-install-path-audit (deux tickets différents
revendiquent #122) — à arbitrer avant de committer le bookkeeping global.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Trois correctifs déjà mergés sur le même symptôme sans que #120 se ferme ;
note de mémoire projet pour que le prochain agent ne reparte pas d'un
patch symptomatique de plus sans avoir lu l'historique des tentatives.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complète le diagnostic backend (#120) côté frontend : un crash React non
capturé (ex. plugin cassant le rendu) laissait un écran noir sans trace, et
une activation de plugin qui ne se résout jamais (promesse infinie) bloquait
le chargement sans échouer. Ajoute RootErrorBoundary + logging d'erreurs
globales autour de l'arbre React, et un timeout sur l'import/l'activation de
chaque plugin dans loadPlugins pour transformer un hang silencieux en échec
explicite et diagnosticable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Écran noir hello-plugin (#120) récidivant après 3 fixes déjà mergés sans
capturer la cause réelle : on ne pouvait pas savoir si le crash venait de
l'install, du reconcile MCP ou d'un panic silencieux. Ajoute un panic hook
qui logge thread/location/backtrace, trace les étapes install/reconcile/
asset-protocol dans idea.log, et remplace les `.expect()` du superviseur MCP
plugin par une erreur typée au lieu d'un panic sur mutex empoisonné.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute SkillKind (Workflow/Reference) sur Skill, extrait le use case
ResolveAgentCapabilities à partir des SkillRef assignés, et enrichit
idea_list_agents d'un champ additif capabilities (name, description,
kind) — remplace l'exposition de SkillRef opaques par un inventaire de
capacités interrogeable, source commune avec le bloc « Skills
disponibles » injecté au lancement (#119).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le champ contrôlé de ModelServersPanel/FirstRunWizard perdait les
espaces saisis dans les arguments llama.cpp (trim/split prématuré sur
chaque frappe au lieu de la seule sérialisation finale) (#113).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le loader accepte désormais aussi la forme export default { activate }
en plus de l'export nommé, les subscriptions partiellement établies
sont nettoyées en cas d'échec d'activation, et une erreur de
runtime-catalog est remontée visuellement dans PluginsPanel avec un
badge Invalide/Isolé sur les plugins concernés (#116/#120).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Un bundle_url invalide ou un plugin dont l'asset n'est pas réellement
servable passe désormais en état Invalid isolé au lieu de faire
échouer globalement la réconciliation MCP au démarrage — cause racine
de la perte d'affichage à l'installation de hello-plugin (#116/#120).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Point de départ de la réinvestigation #120 : commandIdsFromContributes
et layoutTypesFromContributes typaient implicitement leurs Set en
unknown, masquant les valeurs vides potentielles issues des
contributions de plugin (piste du crash d'affichage à l'installation).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Ajoute block_on_protocol_future() pour éviter l'erreur 'Cannot start a runtime from within a runtime'
- Ajoute test unitaire pour vérifier la compatibilité avec le runtime Tauri
- Ajoute test d'intégration pour le hello-plugin du SDK
Met à jour l'index/compteur de tickets, les tickets #111/#113/#114 et le
contexte de Main suite au traitement du ticket #114.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduit SmallDropdown comme composant partagé et le fait adopter par
AgentsPanel, ModelServerSelect, OpenCodeModeFields, TemplateEditor et
TicketViewportSelect, pour que toutes les listes déroulantes dynamiques
(agent, modèle, template, viewport) partagent la même apparence que le
sélecteur d'agent de la création de ticket.
Refs #114
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
QA: verdict VERT (suite automatisée), réserve manuelle : vérification desktop/Tauri (comportement réel du fit terminal dans l'app packagée) non exécutée.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
L'ancien correctif (traîne de refits à nombre de frames fixe,
MOUNT_SETTLE_REFIT_FRAMES) restait insuffisant : une cellule CLI pouvait
rester mal dimensionnée après un changement de projet/layout si l'agent
écrivait en arrière-plan pendant la fenêtre de stabilisation, nécessitant
un resize manuel pour corriger l'affichage. TerminalView détecte
désormais la stabilité réelle du fit (dimensions inchangées sur des
mesures successives) au lieu de s'appuyer sur un nombre de frames fixe
avant de rendre la main au ResizeObserver.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
QA: verdict VERT (suite automatisée), réserve manuelle : vérification desktop (chargement réel de l'archive dans l'app packagée) non exécutée.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Le chargement de l'archive hello-plugin (build/hello-plugin-0.1.0.zip)
vidait la fenêtre principale : une contribution plugin fautive
remontait jusqu'au rendu global au lieu de rester locale à la cellule.
Ajoute un boundary local dans PluginLayoutCellView/PluginLayoutSelectorSection
et durcit menus.ts/loader.ts/registry.ts contre les entrées de menu ou
contributions malformées.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codex Code Mode pouvait appeler n'importe quel outil MCP directement,
contournant la médiation d'approbation. On force
[features.code_mode].direct_only_tool_namespaces = ["mcp__idea"] sur
chaque surface qui écrit le config.toml Codex (permission projector,
lifecycle, migration run-dir, assistant de ticket), et on ajoute
initialize.instructions côté serveur MCP pour orienter Codex vers le
bon outil idea_* dès la connexion, sans dépendre de la recherche
sémantique différée.
QA : domain 283/0, application 126/0 + agent_lifecycle 73/0 +
change_agent_profile 19/0 + ticket_assistant 5/0, infrastructure 339/0
dont mcp_server 37/0, backend 68/0 (7 ignored). Les échecs web-server
observés sur cargo test --workspace (Too many open files, cookies)
sont une contamination de ressources inter-tests ; les deux tests
concernés repassent isolément.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Permet de sélectionner plusieurs tickets dans TicketsPanel et de leur
assigner un sprint en une seule action, au lieu d'un ticket à la fois.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Les permissions déclarées ne reflétaient pas toujours les tools
effectivement exposés aux agents (contexte requester/projet lié après
coup). Ajoute ToolInvoker::tools_for_bound_context/tools_for_context
pour exposer la liste effective au moment de l'injection dans une
requête OpenAI-compatible, et propage le calcul dans la policy MCP,
le serveur, la factory de session et l'adapter openai_compat.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Met à jour les carnets/issues existants et ajoute les tickets #108,
#109, #111, #112 créés durant le cycle. État runtime sans rapport
avec le lot de code #82, isolé dans son propre commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Les tickets legacy sans champ createdBy faisaient noircir la fenêtre
tickets côté frontend. Les adapters (ticket.ts, streamGateways.ts)
normalisent désormais ce champ absent, avec couverture de test dédiée.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les lignes de ticket persistées avant l'introduction de ces champs peuvent
arriver sans assignedAgentIds (ni links/attachments), ce que TicketsPanel
déréférence sans garde côté rendu : la fenêtre de liste rend quelques lignes
puis devient noire dès qu'une ligne legacy est itérée. Les deux gateways
(Tauri et HTTP/stream) normalisent désormais chaque ticket et chaque item de
liste avant de les retourner à l'UI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Levée de la réserve QA sur #108 : aucun test ciblé ne matchait réellement
sur web-server et app-tauri malgré le câblage attachments tickets.
- web-server: test fonctionnel via /api/invoke pour ticket_create,
ticket_attachment_add, ticket_attachment_read,
ticket_attachment_mark_summarized.
- app-tauri: test fonctionnel via la surface publique tool/provider pour
idea_ticket_create, idea_ticket_attachment_add,
idea_ticket_attachment_read, idea_ticket_attachment_mark_summarized.
Pas de changement de wiring produit ; assertions alignées sur le contrat
public réel (contentBase64 sans padding =, summarizedBy.kind = user côté
app-tauri).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stockage flat côté ticket + métadonnées d'attachments, lecture exposée côté
backend/MCP, et UI minimale de liste/ajout dans TicketDetail. Traverse le
domaine (Issue, ports), l'application (usecases + assistant de ticket), les
adaptateurs infra/MCP (issues store, orchestrateur), les DTO backend/web-
server/app-tauri, et le frontend (domain/ports/adapters/hooks/UI).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
createdBy était déjà présent dans le frontmatter des tickets mais ni exposé
ni exploitable côté UI. Ajout du DTO (domain/ports/mock), affichage dans
TicketDetail, colonne/filtre créateur dans TicketsPanel, et persistance du
filtre. ticketActor.ts introduit pour porter la logique de filtrage côté
frontend, sans changement backend nécessaire (lot frontend pur).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le manifeste hello-plugin était généré par un SDK obsolète/désaligné avec le
schéma backend actuel (champ ideaPluginManifestVersion manquant, displayName,
trustLevel, shape de contributes), ce qui faisait échouer l'installation avec
« invalid input: missing field ideaPluginManifestVersion ».
- SDK (manifest.ts/js, index.ts) aligné sur le schéma backend courant.
- hello-plugin (exemple + fixture) régénéré avec le manifeste valide.
- Base de tests fonctionnels plugins : fixture réelle versionnée
(reference-minimal) + test d'installation/chargement bout en bout
(plugin_install_load.rs), pour couvrir install/load/catalog depuis un
dossier fixture réel.
Le sous-repo git imbriqué et vide (sdk/IdeaSDK/.git, 0 commit) a été
neutralisé pour que les sources du SDK soient suivies normalement dans ce
dépôt plutôt que comme gitlink vide.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Panneau Permissions: idea_ask_agents est câblé côté backend MCP mais absent
du groupe « Délégation agents » du panneau — ajout de l'outil et de son
label, catalogue mock MCP aligné en conséquence.
Terminaux: ProjectsView remonte LayoutGrid via une key au changement de
projet, donc refitEpoch n'aide pas ; le montage de TerminalView ne
maintenait pas assez longtemps le fit tant que la nouvelle cellule ne
s'était pas stabilisée. Le chemin de montage garde désormais une traîne de
refits plus longue (MOUNT_SETTLE_REFIT_FRAMES) avant de laisser la main au
ResizeObserver.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le popup viewport-positionné utilisait zIndex.menuDropdown (40), inférieur à
zIndex.floatingWindow (50) : il s'affichait derrière une fenêtre flottante
ouverte. Ajout de zIndex.transientPopup (65), au-dessus de floatingWindow et
floatingWindowNested, pour tout popup transitoire porté par document.body
depuis une FloatingWindow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Restaure .ideai/tickets/ depuis 851f1f8^ (167 tickets + index.json + counter.json)
- Enlève les ignore rules .ideai/tickets/ dans .gitignore (ligne 51 et 75)
- Permet le suivi durable du store tickets futur
- Ne touche pas sdk/ (reste untracked)
Ticket #3 vert (995/995 suite complète, tsc propre) : les dropdowns de la
vue Tickets ne sont plus rognées en bas de fenêtre.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Remplace les <select> natifs de la vue Tickets (statut, priorité, tri,
lien, agent assigné/assistant, sprint) par TicketViewportSelect, qui
repositionne intelligemment son overlay selon l'espace disponible dans
la fenêtre au lieu de toujours ouvrir vers le bas.
QA vert : 80/80 (frontend/src/features/tickets), tsc --noEmit propre.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajoute created_by sur IssueIndexEntry/TicketSummaryDto + filtre createdBy
(#5), et les use cases BulkUpdateIssueStatus/Priority + BulkDeleteIssues
avec les DTO/outils MCP idea_ticket_bulk_* associés (#6).
NON VERT : BulkDeleteIssues::execute référence BatchIssueResult::deleted(),
constructeur pas encore ajouté à BatchIssueResult (seuls ok/err existent) ->
`cargo check` échoue (E0599 dans application::issues::mod). Reste à
DevBackend avant toute QA/merge.
Branché sur feature/sdk-integration (dépend du commit #2 dans
orchestrator/mcp/server.rs et tools.rs) ; à rebaser sur develop une fois
feature/sdk-integration mergé.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wrapper MCP backend pur : dispatche N requêtes idea_ask_agent en parallèle
et retourne un résultat par requête, dans l'ordre, en rejetant les cibles
dupliquées avant dispatch. Aucun changement de domaine/application — pose
la primitive nécessaire au SDK (chantier feature/sdk-integration).
QA vert (ticket #2).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Feature terminée et verte (Architect/DevBackend/DevFrontend/QA au vert) :
durcissement de la reprise headless inter-projets et persistance des
profils Opencode.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #91 — libellé explicite « Appel X -> Y terminé/en échec/annulé »
pour les notifications de fin de background task issues d'un rendez-vous
inter-agent. Frontend pur, tests annoncés verts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Le toast de fin de tâche de fond liée à un rendez-vous inter-agent passe
de « Main -> DevBackend completed » à « Appel Main -> DevBackend terminé »
(idem en échec / annulé), pour nommer explicitement l'appel plutôt qu'un
état brut. Fallback générique conservé quand requester/target sont absents.
Validations obtenues avant commit : tests frontend annoncés verts pour #91.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Câble un canal attachable au flux de sortie d'une tâche de fond (runner
infrastructure + commande app-tauri + port/adaptateurs frontend) et le
panneau ProjectWorkStatePanel s'y abonne pour un rendu live au lieu d'un
état figé au dernier snapshot.
Validations obtenues avant commit :
- cargo test -p infrastructure --test background_task_runner : vert
- cargo check -p backend -p app-tauri : vert
- npx vitest run src/features/workstate/workstate.test.tsx : vert
- npm run typecheck : vert
- verdict QA #58 : vert
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Un tour OpenCode sans texte mais terminé proprement (step_finish, ex.
tool-call-only) échouait en Decode alors qu'aucune erreur réelle ne
s'était produite : parse_jsonl_turn émet désormais un Final vide dans
ce cas au lieu d'un hard-fail, réservé aux flux vraiment vides,
ignorés ou seulement démarrés.
send() inversait aussi l'ordre de décision : un statut de sortie non
nul faisait échouer la délégation même quand stdout contenait un
événement terminal structuré exploitable (Final/Error). Le parse est
maintenant tenté avant l'évaluation du statut, et un événement
terminal structuré prévaut sur un exit code non nul.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Résout le conflit d'imports/helpers de tests dans
crates/application/tests/model_server.rs par union des deux côtés
(imports application/domain + helpers aid/nid/sess).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implémentation du garde ModelServerProjectUseGuard pour séparer
les retours des agents entre projets concurrents.
- Garde RAII par LocalModelServerId partagé entre projets
- Refus inter-projets concurrent via model_server_in_use
- Partage intra-projet conservé avec refcount
- Libération automatique à la fermeture/erreur de session
Tests de validation: rejet projet distinct, compteur refs, libération retrait
Ajout du catalogue enrichi pour les modèles Codex et Claude avec:
- Compatibilité estimée avec la version CLI locale détectée
- Source d'origine (catalogue/Provider) pour chaque entrée
- Support du catalogue Provider API externe
- Matrice de compatibilité embarquée dans l'application
Frontend:
- UI de configuration des modèles avec affichage des états de compatibilité
- Suggestions dynamiques avec badges de compatibilité
- Messages d'aide contextuels (compatible/unknown/likelyTooRecent)
- Alertes non-bloquantes pour les modèles trop récents
- Gestion des échecs de catalogue avec saisie manuelle conservée
Backend:
- Ports CliVersionReader, ProviderModelCatalogue, CompatibilityMatrixSource
- Implémentations: ProcessCliVersionReader, HttpProviderModelCatalogue, EmbeddedCompatibilityMatrix
- Enrichissement des DTOs avec compatibility, cli_version, warnings
- Tests unitaires complets pour le resolver de catalogue
CloneOpenCodeProfileFromSeed::execute rejetait auparavant tout profil persisté squattant l'UUID déterministe du seed canonique opencode-llamacpp avec « internal error: canonical OpenCode seed is not an OpenCode profile ».
Root cause: un profil persisté converti en backend cloud GLM5.2 (ticket #92, opencodeProvider) conserve l'UUID du seed ; le find le matchait et le garde-fou seed.opencode.is_none() — non mis à jour pour le backend cloud — déclenchait une erreur Internal bloquante.
Correctif:
- Le find exige désormais un seed OpenCode valide (structured_adapter OpenCode + au moins un backend opencode local OU opencode_provider cloud), et retombe sur le seed catalogue sinon au lieu d'errer sur un état de store inattendu.
- L'override opencode local remet opencode_provider = None pour honorer l'invariant #97 opencode_backend_is_consistent (miroir de AgentProfile::with_opencode).
- 2 tests de régression (fallback catalogue, seed cloud accepté) + 1 test de symétrie save ajoutés.
Vérification: cargo test -p application --test profile_usecases -> 25 passed.
Les builders `with_opencode` / `with_opencode_provider` s'évacuent
réciproquement (dernier appel gagnant) pour honorer l'invariant
`opencode_backend_is_consistent`. Le use case SaveOpenCodeProviderProfile
route désormais via le builder au lieu de muter le champ directement — c'est
ce qui dupliquait `opencode` + `opencodeProvider` et forçait un repli sur
llamacpp même quand l'utilisateur choisissait un provider cloud.
Défense en profondeur côté store :
- `read_doc` répare les profils corrompus sur disque (drop du stale `opencode`)
- `save` refuse de persister un profil violant l'invariant via le nouveau
variant `StoreError::Invalid` (mappé vers `AppError::Invalid`)
Tests verts : builders last-wins (domain), save rejets + read repair
(infra, profile_store 10/10).
Refs #97
Le timeout hardcoded 15000ms (15s) dans le bloc mcp.idea de l'opencode.json
généré par IdeA était far too short pour idea_ask_agent, qui bloque pendant
que l'agent cible travaille. OpenCode tuait la requête à 15s avec l'erreur
-32001 « Request timed out », même si la cible répondait correctement
(visible dans le workstate). Claude/Codex n'étaient pas affectés car leur
config MCP n'a pas de champ timeout.
Désormais le timeout est résolu par resolve_opencode_mcp_timeout_ms() :
- défaut 4h (14_400_000 ms), aligné sur DEFAULT_RENDEZVOUS_CEILING ;
- overridable via IDEA_OPENCODE_MCP_TIMEOUT_MS ;
- fallback sûr sur 0/parse-error (jamais d'expiration instantanée).
Les 4 occurrences (lifecycle.rs x2 + assistant/mod.rs x2) utilisent la même
fonction exportée depuis application::agent. Aucune modification des configs
MCP Claude/Codex.
Les 4 générateurs opencode.json/opencode_provider.json codaient en dur
{"bash":"ask","edit":"ask"}, ignorant les permissions configurées côté
IdeA pour l'agent. Claude et Codex appliquaient déjà PermissionProjector,
seul OpenCode passait à côté.
Ajoute domain::opencode_permission_block(eff: Option<&EffectivePermissions>)
qui mappe bash ← posture bash effective, edit ← posture Write effective
(Read/Delete non exprimables dans le schéma OpenCode, déjà enforcées par
le sandbox Landlock). eff == None omet la clé permission entièrement,
préservant le prompting natif OpenCode — même invariant que Claude/Codex.
Câble eff jusqu'aux 4 sites d'appel (lifecycle.rs + assistant/mod.rs,
variantes llamacpp et provider cloud). Le chemin ticket-assistant
(assistant/mod.rs) n'a pas de PermissionStore par agent pour l'instant,
donc eff y reste None (comportement inchangé, pas de régression).
Réf ticket #94.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le parseur lisait value.text/value.content à la racine alors que
`opencode run --format json` niche le texte réel sous value.part.text
(cf. packages/opencode/src/cli/cmd/run.ts). Conséquence : idea_ask_agent
vers un profil OpenCode échouait systématiquement avec « OpenCode n'a
produit aucun final textuel » malgré une réponse CLI normale.
Ajoute aussi la gestion des événements type "error" (ParsedEvent::Error,
ReplyEvent::Error) pour distinguer un échec explicite d'un final vide,
et des tests de conformité sur le format JSONL réel confirmé.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix backend de sélection GGUF (fichier fusionné prioritaire sur les
shards partiels) — QA vert : cargo test -p infrastructure/application,
cargo check --workspace, 3 nouveaux tests unitaires.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quand un repo HuggingFace héberge à la fois le fichier GGUF fusionné et
ses shards pour une même quantisation, select_gguf_file (tri alphabétique
naïf) pouvait retenir un shard partiel plutôt que le fichier fusionné ou
l'ensemble complet des shards, causant un échec immédiat au démarrage du
serveur llama.cpp (agent Context).
select_gguf_files renvoie désormais soit le fichier fusionné seul (priorité),
soit tous les shards correspondants triés par index — jamais un shard isolé.
Le téléchargement, le cache et la reprise gèrent le cas multi-fichiers
(manifest de shards, chemins locaux préservant le nom distant pour
l'autoload de llama.cpp).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le picker de provider OpenCode du first-run wizard consomme le
catalogue dynamique exposé par le backend et propose une option
« provider personnalisé » avec un formulaire de saisie libre
(id + clé API) quand le provider souhaité n'y figure pas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le catalogue de providers OpenCode lit désormais le cache local
~/.cache/opencode/models.json pour refléter les providers réellement
disponibles, avec repli garanti sur le catalogue statique en cas
d'absence ou d'erreur de lecture du cache. Ajout d'un champ additif
`custom` sur OpenCodeProviderConfig pour permettre à l'utilisateur de
déclarer un provider hors catalogue (id + clé API en saisie libre).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ticket #92 — support des providers OpenCode cloud, backend + frontend,
QA vert des deux côtés (cargo test workspace + 952 tests frontend).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute l'UI de configuration des providers OpenCode cloud dans le wizard
first-run (sélection, édition, sauvegarde, suppression), le domaine et
les ports associés, ainsi que les adapters HTTP/mock correspondants.
Build + 952 tests verts, comportements clés vérifiés en exécution réelle,
y compris un test de couverture ajouté pour le parcours d'édition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute le catalogue statique de providers OpenCode (lot B3), le stockage
sécurisé des secrets (SecretStore + adapter infrastructure), et les
use cases SaveOpenCodeProviderProfile/DeleteProfile câblés en composition
root. Couvre le fix B1 et les tests de régression demandés par QA.
cargo build --workspace propre, cargo test --workspace -- --test-threads=1
intégralement vert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
État d'exécution accumulé (tickets créés/mis à jour hors #43, notes de
mémoire, tâche de fond) capturé au moment du commit de la feature.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lots F1-F4 : runtime de chargement/registre plugin, extension des menus
existants, panneau de gestion des plugins, types de layout custom
(sélecteur, fallback, cellule dédiée) branchés sur le port plugin.
Suite npm typecheck/test verte (947/947).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lots B1-B4 : modèle de domaine des plugins (manifeste, capacités menus/layouts/MCP),
port et registre applicatif, chargement/validation en infrastructure, exposition DTO
et commandes Tauri. Tests cargo verts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrige la régression persistante en version web du refit des
cellules terminal (#61 rouvert) : le rafraîchissement du scaling
CLI après ajout/suppression de cellule ne se déclenchait pas dans
le workspace web, forçant un redimensionnement manuel.
QA : tsc propre, 909/909 tests vitest, aucune régression desktop
(LayoutGrid/useLayout/TerminalView).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The web shell never adopted the desktop's #61 fix: WebAgentCell/WebWorkspace
didn't pass any refitSignal to TerminalView, so a cell opened after another
kept a stale xterm scaling — desktop "fixed" it via an incidental window
resize, but mobile has no such escape hatch.
- TerminalView: a refit landing on a transient 0x0 container now reschedules
on the next few frames instead of giving up for good (bounded retries),
closing the independent timing gap Architect identified. refitSignal stays
the single explicit-refit mechanism; the terminal/PTY is never recreated,
and resize is still pushed to the PTY only when rows/cols actually change.
- WebAgentCell: new optional refitSignal prop, forwarded to TerminalView
as-is (no key change, no remount).
- WebWorkspace/LiveProjectPanel: new cellLayoutVersion counter, the web
equivalent of desktop useLayout.layoutVersion, bumped on every cell
open/close and forwarded as refitSignal to the visible WebAgentCell.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajoute l'option de lancement automatique du serveur web au démarrage
d'IdeA (#89) : déclenchement backend Tauri au boot selon la
préférence utilisateur persistée, réglage frontend dans les
paramètres de déploiement avec erreur port occupé actionnable.
QA : 57 tests backend app-tauri verts (embedded_server/auto_start
compris), 895/895 tests frontend, tsc propre.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajoute les menus Panneaux/Paramètres manquants à la version web
(#90), avec élargissement de l'allowlist /api/invoke côté
web-server pour router les commandes correspondantes.
QA : 895/895 tests frontend, tsc propre, tests backend
web_invoke_routes verts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Étend ServerExposureSettings avec autoStart (défaut false, mock aligné sur le
backend). useDeployment expose setAutoStart, qui persiste immédiatement le
draft sans jamais appeler start() ni toucher le comportement de stop(), et ne
met à jour l'état local qu'après confirmation de la sauvegarde (même piste de
validation que start/save). DeploymentSettings ajoute la case à cocher sous
le panneau Serveur, et des actions « Modifier le port »/« Réessayer » quand le
statut échoue avec le code PORT_IN_USE stabilisé côté backend.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WebWorkspace gagne un menu « Panneaux » qui route la surface principale du
projet ouvert vers les panneaux transport-neutres déjà réutilisables
(contexte, agents, templates, skills, permissions, mémoire, git) en plus des
surfaces web existantes (travail live, tickets, sprints), ainsi qu'un variant
web du panneau Projets (création par chemin serveur saisi manuellement, sans
browse natif). WebApp gagne un menu « Paramètres » (Profils IA, Appareils,
Déploiement désactivé "Desktop uniquement") qui remplace l'ancien bouton
unique "Appareils".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajoute le déclenchement du serveur embarqué dès le démarrage d'IdeA
selon la préférence utilisateur, sans action manuelle requise.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Permet aux commandes Tauri des menus manquants (#90) d'être invoquées
depuis la version web via le proxy /api/invoke.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Repliage par défaut des background tasks par agent dans le WebWorkspace
(#87), fix frontend pur, tests verts (887 passants, tsc clean).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WebWorkspace replie désormais par défaut la liste des background tasks
par agent dans le panneau Work ; QA a ajouté les cas de test associés
dans WebWorkspaceLive.test.tsx (887 tests verts, tsc --noEmit clean).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #86, lot 2 : surface tickets/sprints pour le workspace web, avec fix
QA sur le changement/retrait de sprint sur un ticket. QA vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
QA-flagged gap: the web surface could only ADD a ticket to a sprint, never
change or clear it, despite the backend already exposing both
ticket_assign_sprint and ticket_unassign_sprint.
- useTicketDetail: new setSprint(sprintId | null) method (additive, also
available to the desktop TicketDetail — unused there today, no behaviour
change), routing through the existing TicketGateway.setTicketSprint.
- WebTicketDetail: Sprint selector in "Statut et priorité", immediate save
like status/priority; empty value clears back to "Sans sprint".
- WebSprintsView: each sprint card now shows its tickets (compact list,
resolved client-side like the desktop SprintManager) with a "Retirer du
sprint" action per ticket, calling assignSprint(ref, null) — symmetric
with "Ajouter tickets". Extracted the card into a SprintCard subcomponent
to keep the growing card readable.
- Tests: WebTicketsSprints.test.tsx now has 16 tests (was 10) — added
sprint change/clear, sprint removal from the Sprints tab, agent
assign/unassign, ticket link/unlink, carnet save, and free-text search
filtering.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a Live/Tickets/Sprints tab navigation to WebWorkspace after a project
is opened — a mobile-first adaptation, not a port of the desktop docks/
floating windows, per carnet #86. The web-server transport (17 ticket_*/
sprint_* commands) and the HttpTicketGateway were already wired (lot 1 +
pre-existing gateway code); this lot is UI only.
- Tickets tab: search/filters, list grouped by sprint, create screen,
detail view with stacked accordion sections (Résumé/Statut et priorité/
Carnet open by default; Agents assignés/Liens/Zone dangereuse collapsed),
delete confirmation, optimistic-concurrency conflict banner.
- Sprints tab: create/rename/reorder (Monter/Descendre)/delete with
confirmation, add tickets via a mobile full-screen ticket picker (never
a small desktop modal), "Voir tickets" filters the Tickets tab to one
sprint.
- Reuses the transport-neutral hooks as-is (useTickets, useTicketDetail,
useTicketSearch) — only presentation and copy are web-specific, in
French per decision #78 (new local label module, not a reuse of the
English desktop ticketMeta labels).
- Workaround: `list_agents` isn't in the web-server allowlist, so agent
names/assignment options are derived from `get_project_work_state`
(already used by the Live tab) instead of the desktop's useProjectAgents.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #86, lot 1 : commandes tickets/sprints exposées sur le transport web
(web-server), DTO tickets factorisés entre Tauri et web (ticket_dto.rs).
QA vert. Le lot 2 (frontend) suit sur une nouvelle branche.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expose les commandes tickets/sprints sur le transport web (web-server/lib.rs),
avec factorisation des DTO tickets partagés entre Tauri et web dans un
nouveau module backend/src/ticket_dto.rs (app-tauri/src/tickets.rs
consomme désormais ce module commun).
Lot 1 du ticket #86, backend/web-server. Le lot 2 (frontend) suit sur une
nouvelle branche depuis develop à jour.
QA vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #83 : popup de confirmation à la fermeture d'IdeA si travail en
cours — guard backend (agents busy + tâches d'arrière-plan actives,
GetAppExitWorkGuardState) + popup frontend. QA vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expose l'état du guard de sortie applicative (GetAppExitWorkGuardState) :
agents busy + tâches d'arrière-plan actives à travers tous les projets
ouverts, avec détails compacts pour la popup de confirmation. Le handler
CloseRequested d'app-tauri interroge ce guard avant de laisser la fenêtre
se fermer, et respecte la confirmation explicite de l'utilisateur
(EXIT_GUARD_CONFIRMED) pour ne pas la redemander en boucle.
QA vert (backend + frontend).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Écoute l'event Tauri `app-exit-work-guard` (émis par le backend quand la
fermeture de la fenêtre main est interceptée) et affiche une popup modale
"Du travail est encore en cours" avec le résumé pluralisé exact du carnet
#83, le détail agents/tâches capé à 5 lignes, et les deux actions Annuler
(focus par défaut, no-op local) / Quitter quand même (danger, appelle
confirm_app_exit). Pas d'option "ne plus demander".
- domain/ports/adapters (Tauri listen+invoke, HTTP desktop-only stub, mock
avec helpers de test) : onAppExitWorkGuard/confirmAppExit sur
SystemGateway, suivant le patron déjà utilisé pour focused-project et les
domain events.
- AppExitConfirmDialog : mounted une fois près de la racine (App.tsx), à
côté d'AnnouncementsProvider — role="alertdialog", focus trap, Échap =
Annuler, pas de fermeture au clic extérieur, ne se referme jamais
automatiquement (un event pendant l'ouverture rafraîchit juste le résumé).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajoute le MCP dédié à l'édition de templates : catalogue et classification
des templates (mcp/templates.rs infrastructure + app-tauri), use cases et
provider (application/template), enforcement de la policy des tools (mod.rs,
server.rs, tools.rs), avec la parité côté chemin OpenAI-compatible
(openai_tools.rs x2).
Lots B1 (catalogue/classification), B2 (use cases/provider) et B3
(enforcement policy) livrés en un seul commit cohérent.
QA vert (seul l'échec de bind loopback #80, connu et non-régression, écarté).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #82, lot UX/Frontend : surface de gestion des permissions des tools
MCP par agent (Permissions panel), consommant l'API Tauri livrée en B4. QA
vert (858/858, tsc propre, stable sur shuffle hormis le flake #85, hors
périmètre).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the "Tools MCP IdeA" tab to the project Permissions panel, alongside
the existing "Système" (file/command) tab. Lets the user grant/revoke MCP
tool capabilities per agent or project-wide default, grouped by domain
(Lecture projet, Lecture tickets, Délégation agents, Contexte et mémoire,
Tickets, Travail et exécution, Skills) instead of a flat 25-checkbox list,
per the UX conception in carnet #82.
- domain/ports/adapters (Tauri, HTTP, mock): wire get_mcp_tool_permissions,
update_project_mcp_tool_permissions, update_agent_mcp_tool_permissions
(already merged backend API, #82 lots B1-B4) onto PermissionGateway.
- useMcpToolPermissions: view-model owning the durable MCP tool policy
document, distinct from the file/command permissions in usePermissions.
- McpToolPermissionsPanel: target selector (Défaut projet + agents with
Hérité/Override badges) and grouped editor — inherited agents are
read-only until "Créer un override" (prefilled with the effective
allowlist), per-row Ajouté/Retiré diffing against the project default,
inline confirmation before granting a write tool at project-default
level, and unsaved-draft protection on target change.
- mcpToolGroups.ts: presentational-only domain grouping and short French
labels — the read/write classification itself always comes from the
backend-provided catalogue, never hardcoded here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deux reformatages sans changement de comportement (device.rs, web-server/lib.rs),
laissés de côté hors périmètre au fil de plusieurs tickets précédents.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #82 : catalogue et permissions des tools MCP, backend complet en 4
lots — B1 domaine/store durable, B2 enforcement au serveur MCP stdio, B3
parité enforcement sur le chemin OpenAI-compatible, B4 API Tauri pour la
future UI de gestion. QA vert sur chaque lot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expose au niveau application/DTO/commandes Tauri le catalogue et les
permissions des tools MCP (application/mcp_tool_permissions.rs, dto.rs,
commands.rs) pour une future UI de gestion.
Lot B4 du ticket #82, dernier lot backend : ferme la boucle sur B1
(domaine/store) + B2 (enforcement MCP stdio) + B3 (parité
OpenAI-compatible).
QA vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
backend/src/openai_tools.rs et app-tauri/src/openai_tools.rs appliquent
désormais les mêmes règles de permission du domaine mcp_tool_permissions
(B1) et le même enforcement que le serveur MCP natif (B2), pour fermer
l'écart de parité sur le chemin OpenAI-compatible.
Lot B3 du ticket #82 : parité posée sur B1+B2, l'API backend pour la future
UI suit en B4 sur la même branche.
QA vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
orchestrator/mcp/server.rs applique désormais les règles de permission du
domaine mcp_tool_permissions (lot B1) à l'invocation d'un tool, y compris
le cas requester vide.
Lot B2 du ticket #82 : ferme la boucle enforcement sur le socle B1, la
parité OpenAI-compatible suit en B3 sur la même branche.
QA vert (32 tests dont le nouveau cas requester vide).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduit le domaine mcp_tool_permissions (règles de permission par tool
MCP) et étend le port de store correspondant. Le catalogue read/write des
tools MCP (orchestrator/mcp/tools.rs) s'appuie désormais sur ces règles, et
un store durable (mcp_tool_permission.rs) persiste les permissions au-delà
d'une session.
Lot B1 du ticket #82 : pose le socle domaine/store, l'enforcement suit en
B2 sur la même branche.
QA vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #62 : identité requester explicite pour les sessions structurées et
policy des tools OpenAI-compatible (ToolPolicyRegistry branché sur
AppOpenAiToolInvoker). QA vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AgentSessionFactory::start propage désormais l'identité du requester aux
sessions structurées ; OpenTicketAssistant et LaunchAgent la portent
correctement de bout en bout. ToolPolicyRegistry est branché sur
AppOpenAiToolInvoker pour combler le trou de parité : TicketToolProvider
n'appliquait pas la policy des tools sur le chemin OpenAI-compatible,
contrairement au chemin structuré natif.
QA vert (échecs de bind loopback écartés comme non-régression préexistante,
tracés en #80).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #79 : fix du flake PermissionsPanel > saves project defaults
(PermissionEditor draft resync clobbering in-flight edits). QA vert sur le
périmètre du ticket (2/2 stable + suite complète non-shuffled) ; le rouge
observé en shuffle vient d'un autre test préexistant sans rapport
(setCellAgent.test.tsx, tracé séparément en #85).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #78 : alignement des libellés Settings desktop sur le français, QA
vert (850/850 tests, tsc propre).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
UX decision (carnet #78): the human UI of IdeA is French by default,
uniform per surface — proper nouns and technical acronyms (URL, LAN,
IP/CIDR, HTTPS, API, CLI…) stay as-is. Settings mixed English (AI
Profiles, Deployment) with French (Appareils, frozen by UX for #77).
Renames the Settings menu/nav, the Profils IA and Déploiement panels
(titles, actions, states, help text) to French, per the carnet's
exhaustive list. Updates the affected tests accordingly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The draft-resync useEffect fired on every new `draft` object (initial load,
refresh, another save), even when its content matched `local`. If it flushed
after the user started editing, it silently discarded the edit — flaky in
tests where a passive effect can settle after a synchronous fireEvent
sequence, and a real risk in production if a fetch lands mid-edit. Now it
only resyncs when `local` still matches the last-adopted draft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ticket #61 : refit différé des cellules terminal après split/merge, QA vert
(850/850 tests, tsc propre, test refitSignal dédié).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Le ResizeObserver de TerminalView ne déclenche pas toujours un événement
utile quand une cellule voisine apparaît/disparaît (split/merge), forçant
l'utilisateur à redimensionner la fenêtre pour rafraîchir le scaling xterm.
useLayout expose un layoutVersion bumpé à chaque commit d'arbre (chargement
initial inclus), relayé par LayoutGrid comme refitSignal à chaque
TerminalView survivant pour déclencher fit.fit() sans rouvrir le PTY.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Intègre le ticket #77 : l'appairage repose désormais sur des appareils
persistants, nommés et révocables, derrière un code éphémère à usage
unique (TTL 10 min) au lieu d'un code permanent imprimé sur stdout.
Ferme aussi la dette #76 : la normalisation du code passe côté serveur,
là où #75 ne pouvait que la masquer depuis le frontend.
Suites rejouées avant merge, toutes vertes :
- cargo test -p domain -p application -p infrastructure -p web-server :
exit 0, 89 suites, 1686 tests.
- frontend : 91 fichiers, 848 tests, tsc exit 0.
- garde-fou de bundle : dist = tauri, dist-web = http.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enregistre l'état du registre de tickets : #77 entre en QA, #78 ouvre la
dette UX de langue de l'écran Settings et #79 le test flaky relevé en
cours de route.
État runtime uniquement : aucun code de feature n'est touché.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expose la gestion des appareils appairés introduite en B1-B4, sur une
surface unique partagée par le web et le desktop.
- Écran Appareils : liste, renommage, révocation unitaire ou globale,
activité formatée, panneau de code éphémère.
- Le parcours d'appairage demande un nom d'appareil, pour qu'une
révocation porte sur quelque chose d'identifiable par l'utilisateur.
- Gateways DeviceGateway en trois adapters (Tauri, HTTP, Mock), le port
restant le seul contrat connu de la feature.
Les erreurs sont mappées localement et le message du serveur n'est jamais
affiché tel quel : un échec d'appairage ne doit pas devenir un oracle pour
qui teste des codes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L'appairage ne survivait pas au redémarrage et son code, permanent, était
imprimé sur la sortie standard. Un appareil appairé devient une entité
persistante, nommée et révocable, derrière un code désormais éphémère.
- B1 : port DeviceSessionStore et adapter FsDeviceSessionStore, entités de
domaine (PairedDevice, DeviceId, SessionTokenHash, DeviceName). Les tokens
sont hachés en SHA-256 et comparés en temps constant (subtle) : le store
ne peut pas rejouer une session qu'il a servie. Cookie Max-Age 400 j à
renouvellement glissant, lastSeenAtMs throttlé.
- B2 : code éphémère en mémoire, TTL 10 min et usage unique, toute
génération invalidant la précédente. POST /api/pairing-code authentifiée,
flag --new-code. Le code est retiré du boot et l'eprintln! qui l'imprimait
est supprimé.
- B3 : endpoints devices (list/rename/revoke/revoke-all/logout), event
DeviceRevoked et ActiveConnectionRegistry par device_id, fermant sans
délai les WebSockets d'un appareil révoqué.
- B4 : port PairAttemptLimiter et adapter mémoire, rate-limit par origine et
global sur horloge injectée, donc testable sans attente réelle.
La normalisation du code passe côté serveur : elle absorbe la dette #76, que
la seule normalisation frontend de #75 ne faisait que masquer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre le ticket #75 : l'écran d'appairage web appelait le pavé
numérique des mobiles alors que le code d'appairage est de
l'hexadécimal majuscule, rendant les lettres A-F non saisissables.
Le champ passe en clavier texte, annonce le format réel et normalise
la saisie avant envoi. Le correctif revient sur le choix de #69, qui
avait supposé un code purement numérique.
Suite verte rejouée avant merge : 88 fichiers, 801 tests, tsc exit 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enregistre l'état du registre de tickets : #74 et #68 passent en closed
après la validation live de l'intégration web, #75 (ce fix) entre en QA
et #76 ouvre la dette de casse du code d'appairage côté serveur.
État runtime uniquement : aucun code de feature n'est touché.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le code d'appairage est de l'hexadécimal majuscule (p. ex. 3F7A9C21),
pas une suite de chiffres. Le champ appelait pourtant le pavé numérique
des mobiles, rendant les lettres A-F non saisissables.
Le champ passe donc en inputMode="text" avec autoCapitalize="characters",
ce qui revient sur le choix inverse fait en #69 : ce ticket avait supposé
un code purement numérique. Le placeholder et l'aide du champ décrivent
désormais le format réel.
La saisie est normalisée (majuscules, espaces retirés) avant pair() car
le serveur compare le code strictement ; la dette correspondante côté
backend est suivie en #76.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre les tickets #68 (packaging des assets web en ressource Tauri) et
#74 (le serveur embarqué servait le bundle desktop au navigateur).
Le frontend produit désormais deux artefacts Vite distincts : dist
(transport tauri, build.frontendDist) et dist-web (transport http,
packagé en ressource « web/ »). Le garde-fou de build échoue si un
bundle résout le mauvais transport.
Les deux tickets ferment ensemble : le packaging #68 servait le bundle
cassé, il ne pouvait pas être déclaré vert sans le fix#74. Validation
live utilisateur verte derrière reverse proxy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enregistre le passage de #74 en statut QA (issue, carnet, index) et
l'accusé de complétion des rendez-vous headless du lot.
État runtime uniquement : aucun code de feature n'est touché.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vérifie sur les artefacts réels que `dist` résout `tauri` et `dist-web`
`http`, en lisant le marqueur `__IDEA_TRANSPORT__` des assets JS émis. La
régression de #74 était invisible aux tests unitaires : elle vivait dans la
conf de build et de packaging, pas dans le code testé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`bundle.resources` packageait `frontend/dist` — le bundle desktop — sous
`web/`, que `resolve_web_root()` sert au navigateur. La ressource pointe
désormais sur `frontend/dist-web` (transport http), et `beforeBuildCommand`
appelle `build:bundle` pour que les deux bundles existent au packaging.
`frontendDist` reste sur `dist` : le desktop est inchangé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le transport est figé au build par Vite : un seul `dist` ne peut pas servir
à la fois le desktop (IPC Tauri) et le navigateur (HTTP+WS). Le serveur
embarqué servait donc un bundle desktop, d'où `__TAURI_INTERNALS__ is
undefined` côté navigateur.
- `vite.config.ts` devient une factory `({ mode })` : le mode par défaut émet
le bundle desktop (`dist`), `--mode web` lit `.env.web` et émet le bundle
navigateur (`dist-web`).
- `.env.web` porte `VITE_TRANSPORT=http` dans un fichier de mode plutôt qu'en
préfixe de commande, syntaxe qui n'existe pas sous Windows (bundle NSIS).
- `transport.ts` extrait le prédicat `transportFromEnv()`, partagé par l'app et
la config de build : le constant `__IDEA_TRANSPORT__` et le transport résolu
dérivent de la même variable via le même prédicat, ils ne peuvent pas diverger.
- `main.tsx` publie `__IDEA_TRANSPORT__` sur `window` : l'affectation est un
effet de bord, elle survit à la minification et rend un bundle identifiable
sans grep d'un symbole minifié. Les deux jeux d'adapters étant présents dans
les deux bundles, leur présence ne prouve rien.
Le chemin desktop est inchangé : le web reste opt-in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L'AppImage ne pouvait pas servir d'assets web : `resolve_web_root` ne
connaissait que `IDEA_WEB_ROOT`, le dossier de l'exécutable et le cwd —
aucun ne pointe vers un bundle packagé.
- `bundle.resources` embarque les assets sous `web/`, `beforeBuildCommand`
déclenche le build du frontend.
- Le resource dir Tauri descend de `lib.rs` jusqu'à `EmbeddedServerController`
via `AppState::build_with_resource_dir`, en gardant les constructeurs
historiques comme façade.
- `resolve_web_root` insère le candidat packagé juste après `IDEA_WEB_ROOT`,
qui garde donc la priorité pour le développement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dernier état `.ideai/` du sprint serveur embarqué, séparé du code applicatif
conformément aux précédents `f965f64` / `7fbaa8e` / `5ee25d1`.
- #68 passé en `qa`, pas en `closed` : mergé dans `develop` (`b30c9c7`) et vert,
mais RIEN n'a été cliqué dans l'app réelle. « Mergé » ne veut pas dire
« validé » — même distinction que pour #69 et son lot 3.
- Carnet de #68 : périmètre livré, réserve « aucune validation live », et la
dette identifiée (ordre de `preview_settings` qui verrouille la liste des
candidats LAN, warning `missingTrustedProxy` inatteignable).
- Mémoire projet : note `sandbox-eperm-bind-false-green-web-server`. Elle
capitalise le piège qui s'est refermé DEUX FOIS le 2026-07-16 : les sandboxes
de DevBackend et QA refusent `TcpListener::bind`, donc tout `cargo test` sur
`web-server`/`app-tauri` y rend un vert qui ne prouve rien. La première fois,
ce faux vert masquait un vrai bug produit — un serveur embarqué sur port
éphémère rejetait toutes les requêtes API en 403. La règle qui en sort : aucun
repli EPERM silencieux, et tout vert sur ces crates doit être produit hors
sandbox.
- Journal des tâches de fond : complétions enregistrées.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#72 passé en `closed` : mergé dans `develop` via `ae01297`, vert hors sandbox,
branche supprimée. Carnet complété avec le périmètre livré et les arbitrages.
État `.ideai/` indépendant du code de #68, committé directement sur `develop`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ferme #68 : le serveur web s'active désormais depuis l'app desktop, via un
panneau Settings > Deployment. C'est la fonctionnalité demandée à l'origine du
ticket, et l'aboutissement de la chaîne #65 (extraction d'idea-serve) → #68 B1
(seam shared-core) → #72 (durcissement proxy) → ce lot.
Backend : `EmbeddedServerController` sur `AppState`, commandes start/stop/status,
store `<app-data-dir>/deployment/server-exposure.json` (config de sécurité, pas
préférence d'UI), arrêt câblé sur la sortie d'app. Le serveur embarqué consomme
`run_embedded_with_core` avec le `BackendCore` du desktop — une seule
composition root, ce pour quoi le seam de B1 existait.
Frontend : `features/settings/` avec `SettingsView`/`DeploymentSettings`,
gateway `DesktopServerGateway`, et `ProjectsView` dont le booléen `showSettings`
devient une vraie navigation `AI Profiles` / `Deployment`.
Vert avant merge, ré-exécuté par Git HORS SANDBOX — impératif sur ces crates,
le sandbox bloque `bind` et a déjà produit un faux vert sur #68 B1 :
`cargo test -p app-tauri` 43 passed / 1 ignored · `-p web-server` 66 passed ·
`npm run typecheck` exit 0 · `npx vitest run` 87 files / 789 passed. Le test
ignoré est une garde d'environnement préexistante, vérifiée passante hors
sandbox avec `--ignored`.
Invariants vérifiés par Git avant merge : `run_embedded_with_core` bien
l'appelant, aucune fuite des types d'exposition dans `domain`/`application`,
`0600` posé avant le `rename`, `stop()` appelé à la fermeture.
Dette consignée, non bloquante : `preview_settings` valide avant de
prévisualiser (verrouille la liste des candidats LAN, contourné côté frontend),
et le warning `missingTrustedProxy` est inatteignable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Donne à l'utilisateur la surface pour activer le serveur depuis l'app.
- `features/settings/` : `SettingsView`, `DeploymentSettings`, `useDeployment`.
- `adapters/desktopServer.ts` + port `DesktopServerGateway` et DTO associés ;
adapters mock et http/unsupported alignés (le mode web n'expose pas le
contrôle du serveur qui l'héberge).
- `ProjectsView` : le `showSettings: boolean` devient une navigation interne
`AI Profiles` / `Deployment`. Le libellé alternant « Close AI Profiles »
disparaît — Settings existait déjà dans cette vue, la surface évolue au lieu
d'ajouter un `PanelId`.
CORRECTION D'UN BRIEF FAUX, remontée spontanément par DevFrontend et qui mérite
de survivre : le cadrage décrivait le mode `remoteProxyOtherMachine` avec deux
champs. `validate_settings` en exige un troisième, `lanBindAddress`, et rejette
loopback comme unspecified. Construit selon la spec, chaque save et chaque start
en mode 3 aurait échoué — le lot serait parti vert et cassé. Le panneau expose
donc un select alimenté par `candidateLanAddresses` fourni par le backend :
la règle « le frontend n'invente jamais une IP » tient.
QA ré-exécutée par Git avant merge : `npm run typecheck` exit 0 ·
`npx vitest run` 87 files / 789 passed, 0 échec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Permet d'activer le serveur web depuis l'app desktop — la fonctionnalité
demandée à l'origine du ticket.
- `embedded_server.rs` : `EmbeddedServerController` porté par `AppState`, avec
les commandes `embedded_server_start` / `stop` / `status` et
`get` / `save` / `preview_server_exposure_settings`.
- `FsServerExposureSettingsStore` : écrit `<app-data-dir>/deployment/server-exposure.json`.
C'est de la config de sécurité, pas une préférence d'UI — d'où l'app-data-dir
global plutôt que `localStorage`. Écriture atomique : temporaire, `0600` posé
AVANT le `rename`, donc le fichier final n'est jamais brièvement lisible par
tous.
- Arrêt du serveur câblé sur la sortie de l'app (`lib.rs`), aux côtés des autres
nettoyages.
Le serveur embarqué appelle `run_embedded_with_core` avec le `BackendCore`
partagé du desktop, jamais `run_embedded` — c'est tout l'intérêt du seam ouvert
par B1 : une seule composition root dans le processus, pas deux.
DETTE CONNUE, consignée au carnet, non bloquante :
- `preview_settings` valide avant de prévisualiser (`embedded_server.rs:211`),
ce qui verrouille la liste des candidats : un brouillon en mode
`remoteProxyOtherMachine` ne peut pas être prévisualisé tant qu'il n'a pas le
`lanBindAddress` que cette liste doit justement fournir. Contourné côté
frontend par une sonde `localOnly` toujours valide. Défaut d'ordonnancement,
pas une fatalité.
- Le warning `missingTrustedProxy` (`embedded_server.rs:452`) est du code mort :
`validate_settings` rejette les `trustedProxies` vides en mode 3 avant que
`preview_settings` ne puisse le produire.
QA ré-exécutée par Git HORS SANDBOX (DevBackend et QA sont bloqués par EPERM sur
`bind` ; un vert sandboxé ne prouve rien sur ces crates) : `cargo test -p
app-tauri` 43 passed, 1 ignored · `-p web-server` 66 passed, 0 échec.
Le test ignoré `mcp_bridge::tests::end_to_end_over_real_loopback` est
PRÉEXISTANT sur `develop` et non touché par ce lot ; lancé explicitement hors
sandbox avec `--ignored`, il passe. C'est une garde d'environnement, pas un faux
vert — distinction qui nous a déjà coûté deux fois aujourd'hui.
Invariants vérifiés par Git : appelant `run_embedded_with_core` confirmé, aucune
fuite d'`EmbeddedServer*`/`ServerExposure*` dans `domain` ni `application`,
ordre chmod/rename correct, `stop()` bien appelé à la fermeture.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#73 ouvert en priorité haute : intégrer TLS à `idea-serve` pour supprimer la
cause racine de la cérémonie reverse proxy, dont #72 vient de durcir les
garde-fous. Lié à #72, #66, #68 et #71.
État `.ideai/` indépendant du code de #72, committé directement sur `develop`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rend réelle la confiance accordée au reverse proxy. Jusqu'ici
`--trust-reverse-proxy` n'était lu que pour exiger sa propre présence : le
serveur ne vérifiait ni qui se connectait, ni ce que le proxy annonçait. Le pair
TCP est désormais vérifié avant toute confiance aux headers, `X-Forwarded-Proto:
https` est exigé en mode proxy, et `X-Forwarded-For` n'autorise jamais rien.
Corrige aussi B0, le pendant CLI du bug de port de #68 B1 : `idea-serve --listen
127.0.0.1:0` ne compare plus l'origine à `http://127.0.0.1:0`.
CHANGEMENT DE COMPORTEMENT ASSUMÉ : un bind non-loopback distant sans
`--trusted-proxy` est refusé au démarrage. Les configurations existantes de cette
forme cassent volontairement, avec un message qui dit quoi ajouter.
Vert avant merge, ré-exécuté par Git HORS SANDBOX — impératif sur ce crate, le
sandbox interdit `TcpListener::bind` et a déjà produit un faux vert sur #68 B1 :
`cargo test -p web-server` 66 passed · `-p backend` · `-p app-tauri` verts.
Revue de sécurité : guard câblé sur les 3 surfaces (statique, /api/*, /api/ws),
vrai `peer_addr` propagé sur tout le chemin de production.
Étape 2 du plan d'intégration séquentiel. Suit : #68 (panneau Settings
Deployment), sur arbitrage utilisateur qui a refusé de le reléguer derrière #71
et #73.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aligne la doc du mode distant sur le durcissement de #72 : exemple complet du
cas proxy sur une autre machine avec `--trusted-proxy`, mention que tout bind
non-loopback exige au moins un proxy autorisé, et obligation faite au proxy
d'envoyer `X-Forwarded-Proto: https`.
Précise que transmettre le host public est recommandé pour le diagnostic mais
n'est pas la vérification d'accès : celle-ci reste l'égalité stricte d'`Origin`
contre `--public-origin`.
Bascule les IP d'exemple vers la plage de documentation RFC 5737 (`192.0.2.0/24`)
au lieu d'un réseau privé plausible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`--trust-reverse-proxy` était un drapeau creux : il n'était lu que dans
`ServerConfig::validate()` pour exiger sa propre présence. Le serveur n'a jamais
vérifié qui se connectait ni ce que le proxy annonçait. Ce lot lui donne un
effet.
- `trusted_proxies: Vec<TrustedProxy>` dans `ServerConfig` + `--trusted-proxy`
(répétable, IP ou CIDR v4/v6). `validate()` refuse désormais au démarrage un
bind non-loopback en mode remote sans au moins un proxy autorisé.
- Guard runtime sur les trois surfaces (statique, `/api/*`, `/api/ws`) : le
`peer_addr` réel est vérifié AVANT toute confiance accordée aux headers. Sur
bind loopback, seul un pair loopback est accepté ; sinon le pair doit tomber
dans un `--trusted-proxy`.
- `X-Forwarded-Proto: https` obligatoire en mode proxy, avec un message de refus
actionnable (la commande nginx exacte à ajouter).
- `X-Forwarded-For` n'autorise jamais rien : il n'est que journalisé. Un header
ne donne aucun droit, seul le pair TCP en donne.
- Diagnostics : `UntrustedProxyPeer`, `ForwardedProtoRejected`,
`ForwardedHostMismatch`.
B0 — même bug de port que celui corrigé dans #68 B1, sur le chemin CLI cette
fois : `run_server` construisait son `ServerState` AVANT le bind, donc
`idea-serve --listen 127.0.0.1:0` comparait l'origine à `http://127.0.0.1:0` et
rejetait tout en 403. La réconciliation du port effectif est factorisée dans
`config_with_effective_listen`, partagée avec le chemin embarqué.
`X-Forwarded-Host` ne provoque PAS de rejet : Architect l'a jugé redondant avec
la vérification stricte d'`Origin`, et il cassait nginx en configuration par
défaut. Il reste un warning de diagnostic. La vérification d'accès stricte
demeure l'égalité d'`Origin` contre `--public-origin`.
CHANGEMENT DE COMPORTEMENT ASSUMÉ — ce lot casse volontairement les
configurations existantes : un bind non-loopback distant sans `--trusted-proxy`
est désormais refusé au démarrage. C'est le prix d'un drapeau qui ne mentait
plus. Le message d'erreur dit quoi ajouter.
Origine du ticket : #72 est né d'une trouvaille de l'agent Git à la revue de la
doc de #65 — c'est en vérifiant une phrase de sécurité qu'il a établi que
`--trust-reverse-proxy` n'avait aucun effet runtime.
QA ré-exécutée par Git HORS SANDBOX avant merge (DevBackend et QA sont tous deux
bloqués par EPERM sur `TcpListener::bind` ; un vert sandboxé ne vaut rien ici, on
s'est déjà fait avoir sur #68 B1) : `cargo test -p web-server` 66 passed, 0
échec · `-p backend` · `-p app-tauri` verts.
Revue de sécurité par Git : guard câblé sur les 3 routes, chemin de production
propageant toujours le vrai `peer_addr` (le repli `listen.ip()` est
`#[cfg(test)]`, inatteignable en production), CIDR correct y compris `prefix 0`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Corrige une documentation qui a réellement induit l'utilisateur en erreur :
l'exemple du cas « Remote HTTPS » bindait `127.0.0.1` en supposant sans le dire
que le reverse proxy est co-localisé. Suivi tel quel avec un proxy sur une autre
machine, il produit un serveur injoignable.
Précise le cas co-localisé vs le cas distant, la configuration exigée pour tout
bind non-loopback, l'égalité stricte de `--public-origin` contre l'`Origin`
(ouvrir l'UI par le domaine, pas par l'IP, sinon 403), et le pare-feu hôte comme
cause de timeout depuis les autres machines.
Conserve l'obligation normative « ne pas exposer sans proxy TLS » que la
première rédaction avait perdue au profit d'une garantie que le code n'offre
pas : `ServerConfig::validate()` valide la configuration, pas la présence réelle
d'un proxy.
Indépendant de #68 et #71 : n'a attendu aucun de leurs lots.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La réécriture précédente avait remplacé « Do not expose the backend on a public
non-loopback address without TLS proxying » par une formulation adossée à
`ServerConfig::validate()`. Littéralement vraie, mais pas équivalente : elle
troque une OBLIGATION faite à l'opérateur contre une garantie automatique qui
n'existe pas.
`validate()` valide une configuration, pas la réalité. Rien ne vérifie qu'un
proxy est réellement devant le serveur : passer les trois drapeaux en bindant
`0.0.0.0` sans aucun proxy satisfait `validate()` et sert du HTTP en clair.
La phrase normative est donc rétablie, avec cette limite dite explicitement.
Voir #72 : `--trust-reverse-proxy` n'a par ailleurs aucun effet à l'exécution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- #72 ouvert en priorité haute, bloquant #68 : `--trust-reverse-proxy` n'est lu
que dans `ServerConfig::validate()` pour exiger sa propre présence — le serveur
ne parse jamais `X-Forwarded-*`. C'est un drapeau d'intention pur, qui donne un
faux sentiment de protection. Le ticket porte aussi B0 (le bug de port effectif
sur le chemin CLI `run_server`), `trusted_proxies` + `--trusted-proxy`, le
guard runtime et les événements de diagnostic.
- Journal des tâches de fond : complétions enregistrées.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Déclare UX comme propriétaire de la conception des surfaces (navigation,
libellés, hiérarchie de l'information, formulation des erreurs, parcours) et
l'insère dans le cycle : avant Architect quand la forme conditionne les contrats,
avec lui quand une contrainte technique borne la conception. UX ne bloque pas les
lots sans surface utilisateur.
Ajoute aussi l'avertissement que la liste des rôles de ce document n'est pas la
source de vérité des agents déclarés : `idea_list_agents` fait autorité.
Contexte projet, indépendant des chantiers en cours — committé directement sur
`develop`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Livre la fondation backend du serveur embarqué desktop : le seam
`run_embedded_with_core(config, Arc<BackendCore>)`, qui permet à l'app Tauri de
faire tourner le serveur web en partageant son composition root plutôt qu'en
dupliquant un backend dans le même processus.
Apporte aussi le fix du port effectif : un serveur embarqué sur port éphémère
rejetait toutes les requêtes API en 403 (origine comparée à un `listen` resté à
port 0). Et comble le trou de couverture sur `run_embedded().stop()` signalé au
merge de #65.
Vert avant merge, ré-exécuté par Git HORS SANDBOX — impératif ici : le sandbox
interdit `TcpListener::bind` et avait produit un faux vert sur ce lot précis.
`cargo test -p web-server` 57 passed (les 2 tests embedded réellement exécutés) ·
`-p backend` 41 · `-p app-tauri` 35, 0 échec.
Étape 1 du plan d'intégration séquentiel. Suit : #72 (durcissement bind/origine/
proxy, dont B0 = le même bug de port sur le chemin CLI `run_server`), puis #71
lot 1 (diagnostics), puis le Settings Deployment de #68.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ouvre le point d'intégration du serveur embarqué desktop : `run_embedded_with_core`
reçoit un `Arc<BackendCore>` déjà construit, pour que l'adapter HTTP partage le
composition root de l'adapter Tauri au lieu d'en bâtir un second dans le même
processus. `ServerState` porte désormais `Arc<BackendCore>` ; `run_embedded` est
conservé en compat et délègue en construisant son propre core.
Corrige au passage un vrai bug produit, découvert en rejouant les tests hors
sandbox : `run_embedded` ne réconciliait pas le port effectif avec la config. Sur
un bind éphémère (`127.0.0.1:0`), l'état du serveur gardait `listen` à port 0,
donc `origin_allowed` comparait l'Origin entrante à `http://127.0.0.1:0` et le
serveur embarqué rejetait **toutes** les requêtes API en 403. La config reçoit
maintenant `local_addr` lu sur le listener **avant** la construction du state.
Comble le trou signalé au merge de #65 : `run_embedded().stop()` est enfin
couvert.
ATTENTION — le premier « 57 passed » de ce lot était un FAUX VERT. Le sandbox
d'exécution interdit `TcpListener::bind` ; les tests sortaient en silence sur
EPERM, dont précisément le test `stop()`. Les replis EPERM sont supprimés : un
bind refusé fait désormais échouer le test au lieu de le peindre en vert.
QA ré-exécutée par Git HORS SANDBOX avant merge (sinon on reproduit le faux
vert) : `cargo test -p web-server` 57 passed, 0 échec, avec
`run_embedded_stop_shuts_down_accept_loop` et
`run_embedded_with_core_uses_injected_core_for_http_invokes` réellement exécutés
sur le vrai chemin réseau. `-p backend` 41 · `-p app-tauri` 35, 0 échec.
DETTE CONNUE, tracée en #72 B0 : le chemin CLI standalone `run_server` garde la
même hypothèse fausse — il reçoit un `ServerState` construit AVANT le bind, donc
`idea-serve --listen 127.0.0.1:0` reste cassé à l'identique.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L'exemple du cas « Remote HTTPS » bindait `127.0.0.1` en supposant sans le dire
que le reverse proxy tourne sur la même machine que le serveur. Suivi tel quel
avec un proxy sur une autre machine, il produit un serveur injoignable — ce qui
a réellement induit l'utilisateur en erreur aujourd'hui.
Précise donc : le cas co-localisé vs le cas proxy distant (binder une adresse
joignable par le proxy, lien proxy→serveur en HTTP clair sur le LAN), la
configuration exigée pour tout bind non-loopback, l'égalité stricte de
`--public-origin` contre l'`Origin` de la requête (ouvrir l'UI par le domaine et
non par l'IP, sinon 403), et le pare-feu hôte comme cause de timeout depuis les
autres machines.
Indépendant de #68 et #71 : valeur immédiate, n'attend aucun de leurs lots.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
État `.ideai/` indépendant des branches de feature, committé directement sur
`develop` (précédents `2fa226e`, `56757c7`).
- #65 « serveur headless idea-serve » passé en `closed` par Main : mergé dans
`develop` via `fe0e53e`, vert, branche supprimée.
- #71 « diagnostic d'accessibilité du serveur » ouvert, lié à #68 et #65.
- Journal des tâches de fond : complétions enregistrées.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rend le client web utilisable sur téléphone : `100dvh` + `viewport-fit=cover` et
safe-area insets (la barre d'URL rétractable masquait le bas), rangées qui
s'empilent sous 360px, et surtout une `TerminalKeyBar` (Esc/Tab/Ctrl-C/Ctrl-D/
flèches) qui rend le terminal pilotable au doigt — le clavier virtuel n'offre
aucune de ces touches, on pouvait donc parler à un agent CLI mais pas
l'interrompre.
Surface frontend-pure : aucun DTO, use case ni `LayoutTree` touché. Le chemin
desktop est inchangé (`LayoutGrid` ne passe pas `onReady`, `onReady` est
optionnel).
Vert avant merge, ré-exécuté par Git sur la base rebasée (les chiffres du carnet
ne valaient que sur `506d589`) : `npm run typecheck` exit 0 · `npx vitest run`
85 files / 774 passed, 0 échec · `VITE_TRANSPORT=http npx vite build` exit 0.
RÉSERVE — le lot 3 est livré mais NON PROUVÉ en conditions réelles.
La validation live utilisateur du 2026-07-16 (téléphone réel, via le reverse
proxy) est PARTIELLE. Couvert : le terminal s'ouvre, ce qui implique appairage +
workspace traversés, donc les lots 1 et 2 (`100dvh`, `60dvh`) validés en réel.
NON couvert : la `TerminalKeyBar` n'a pas été utilisée, et l'invariant de focus
au tap (« chaque tap annule le déplacement de focus puis refocalise xterm », sans
lequel le clavier virtuel se referme à chaque touche) n'a pas été observé sur
navigateur mobile réel. C'est le cœur du ticket. Le test manquant tient en 30 s :
lancer une commande longue, taper Ctrl-C dans la barre, vérifier que la commande
s'interrompt ET que le clavier reste ouvert.
Merge autorisé par Main sur ce constat : risque résiduel borné (frontend-pur,
desktop non atteint, non-régression prouvée). Si le test Ctrl-C échoue, il ouvre
un bug, il ne réverte pas ce merge. Jusque-là, « mergé » ne veut pas dire
« validé ».
Trou de couverture connu et assumé : jsdom n'évalue pas les media queries, les
tests sont structurels et ne prouvent ni les breakpoints, ni `dvh`, ni les safe
areas, ni le clavier virtuel. La décision d'ajouter un vrai navigateur
(Playwright) appartient à #63 « Système de test de l'UI ».
Rebase préalable sur `develop@fe0e53e` (#65 avait fait avancer la base) :
sans conflit, aucun recouvrement de fichiers, arbre `frontend/` identique bit
pour bit à l'original `6ed0087`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
État `.ideai/` du chantier client téléphone, séparé du code applicatif
conformément aux précédents `5ee25d1` / `8e481ae`.
- Carnet #69 : périmètre exact de la validation live utilisateur sur téléphone
réel (lots 1 et 2 validés en réel via l'appairage puis le workspace ; lot 3 —
`TerminalKeyBar` et invariant de focus — NON exercé), arbitrage Main
autorisant le merge avec réserve écrite, et report de l'écart Playwright
vers #63 plutôt que vers un ticket neuf.
- Faits de topologie mis à jour après rebase sur `develop@fe0e53e` : les SHA
cités (`e22ea5b`/`8f15ad6`/`6ed0087`) n'existaient plus sur aucune branche.
- Journal des tâches de fond : complétions enregistrées.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`npm run build` est `tsc --noEmit && vite build` : un import non utilisé
casse le build (TS6133), pas seulement le lint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lots 3 et 4 du ticket #69. Sans ça un agent CLI est indriveable depuis un
téléphone : le clavier virtuel n'a ni Esc, ni Tab, ni Ctrl, ni flèches, donc
on peut taper un prompt mais pas l'interrompre, compléter un chemin, sortir
d'un éditeur ou rappeler l'historique.
- `TerminalView` expose un `onReady(api)` optionnel (donc desktop inchangé,
et inerte quand xterm ne monte pas). `api.send` passe par `term.input()` :
le *même* chemin qu'une frappe réelle, donc le relais PTY, le comptage de
lignes et la suspension du write-portal s'appliquent à l'identique. Écrire
sur le handle aurait court-circuité le portal.
- `TerminalKeyBar` (web-only) : Esc/Tab/Ctrl-C/Ctrl-D/flèches en chips
tactiles 44px, scroll horizontal. Chaque tap annule le déplacement de
focus et refocalise xterm — sinon le clavier virtuel se referme à chaque
touche.
- La cellule agent passe de `h-64` fixe (une letterbox d'~20 lignes) à
`h-[60dvh]` sur téléphone, `sm:h-80` au-delà.
- Tests : séquences d'octets émises, préservation du focus, état désactivé
avant montage de xterm, et garde de non-régression sur la frontière —
le client web ne monte aucun layout-grid/split/dock desktop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lots 1 et 2 du ticket #69 : rendre le client web (#13) utilisable sur un
téléphone. Le client web n'a jamais eu de docks ni de fenêtres flottantes —
il est déjà une colonne verticale unique — donc le lot 2 se réduit aux
rangées qui débordaient réellement à 360px.
- `100dvh` sur html/body/#root : `height: 100%` se résout sur le *large*
viewport, donc la barre d'URL rétractable d'un navigateur mobile masquait
le bas de l'app. Desktop inchangé (dynamic == large viewport).
- `viewport-fit=cover` + padding `env(safe-area-inset-*)` sur le header et
le workspace : l'app peut peindre sous une encoche sans perdre ses
contrôles. Le zoom reste libre (accessibilité).
- Padding responsive (`p-4 sm:p-6`) : 48px de gouttière sur 360px, c'était
13% de la largeur.
- `AgentLiveRow` et la rangée de tâche de fond empilent leurs contrôles sur
téléphone (nom / badges / actions) et retrouvent leur ligne unique dès
`sm` — à 360px les boutons Cancel+Retry ne tenaient pas.
- Code d'appairage : `inputMode="numeric"`, pas d'autocapitalisation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre le serveur headless `idea-serve` : le cœur (use cases, DTO, events)
devient partagé entre deux driving adapters, le desktop Tauri et le crate
`web-server` autonome, sans dépendance GUI.
Vert avant merge : `cargo test -p backend` 41 · `-p web-server` 55 ·
`-p app-tauri` 35, 0 échec, ré-exécuté sur la branche. Invariant headless
vérifié (`ldd idea-serve` sans webkit/gtk/tauri) et validation live utilisateur
du serveur (démarrage, SPA servi, contrôle d'origine strict, accès distant).
Débloque #68 (activer le serveur depuis le desktop, via `run_embedded`) et #66
(Docker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
État durable de `.ideai/` accumulé pendant le chantier `idea-serve`, séparé du
code applicatif conformément aux précédents `8e481ae` / `ad1f225`.
- Tickets : #13 clôturé (server/client mode livré), #65 passé en QA avec son
carnet de chantier complet (arbitrages Architect sur le propriétaire canonique
des DTO, structure livrée, vérif QA, incident de topologie et sa leçon).
#64/#66/#67 rattachés au sprint. Nouveaux tickets #68 (activer le serveur
depuis le desktop, dépend de #65), #69 (adaptabilité client téléphone, en QA)
et #70 (gestion des modèles locaux llama.cpp).
- Mémoire : note `web-client-is-single-column-no-desktop-shell` — le client web
n'a pas de shell desktop, à lire avant tout cadrage responsive sur cette
surface.
- Journal des tâches de fond : complétions enregistrées.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le build web (`VITE_TRANSPORT=http`) que sert `idea-serve` produit
`frontend/dist-web/`, remonté comme non suivi car la règle existante n'ancrait
que `frontend/dist/`. C'est de la sortie de build (~1 Mo, rebuildable depuis les
sources) : même classe que `dist/`, elle n'a pas sa place dans l'historique.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sort le serveur HTTP/WS de `app-tauri` vers un crate `web-server` autonome,
exposant le binaire `idea-serve` qui sert le client web sans aucune dépendance
GUI. Le cœur (use cases, DTO, events) devient partagé entre deux driving
adapters : le desktop Tauri et le serveur headless.
Structure :
- `crates/web-server` : lib + bin `idea-serve`, consomme `backend::{dto, events}`.
- `crates/backend` : `dto.rs` et `events.rs` deviennent le propriétaire canonique
des DTO/events transport-neutres (arbitrage Architect : pas de crate contrat
dédié, `backend` est déjà le composition root transport-neutre).
- `crates/app-tauri` : `dto.rs` réduit à un shim de ré-export, `events.rs` réduit
au relais Tauri (souscription au bus + emit), `server.rs` vidé au profit du
crate partagé.
- `Cargo.toml`/`Cargo.lock` : `web-server` ajouté aux membres du workspace.
Frontière respectée : les DTO de `backend` ne tirent ni Tauri, ni HTTP/WS, ni
Axum/Hyper, ni UI ; `domain`/`application` n'en dépendent pas. Le frontend est
inchangé (`git diff 506d589...HEAD -- frontend` vide).
`web_server::run_embedded(...)` est le point d'extension prévu pour #68 (le
desktop consommera `web-server` comme lib plutôt que d'en forker le serveur).
QA (exécution réelle, re-vérifiée avant merge) :
- `cargo test -p backend` 41 passed · `-p web-server` 55 passed · `-p app-tauri`
35 passed, 0 échec.
- Invariant headless : `ldd target/debug/idea-serve` ne tire aucun
webkit/javascriptcore/gtk/gdk/soup/tauri/wry, là où `app-tauri` les tire bien.
- Validation live utilisateur : `idea-serve` démarre, sert le SPA, le contrôle
d'origine strict tient, accès distant OK via reverse proxy.
- Les 10 échecs `openai_compat` de `cargo test --workspace` sont préexistants sur
`506d589` (bind réseau interdit par la sandbox), hors périmètre.
Squash de `82e8e77` (commit de sûreté « état intermédiaire non figé », qui
rapatriait le travail réalisé par erreur sur la branche de #69) et de `ddbea7b`
(déduplication des DTO events, comblant l'écart annoncé par le premier). Les deux
n'avaient de sens qu'ensemble : ce commit fige le contrat que le premier laissait
explicitement ouvert. Arbre identique bit pour bit à `ddbea7b` ; historique
d'origine conservé sur `backup/ticket65-pre-squash-ddbea7b`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Transport HTTP/WS et surface web du ticket #13, validé par l'utilisateur en
lancement Desktop (comportement conforme).
Contenu : endpoint PTY WebSocket authentifié et relais live-state/background
côté serveur (idea --serve), client web read-only, xterm.js sur WebSocket avec
reconnexion et scrollback, surfaces agent et live web, service same-origin de
dist et durcissement (logout, logs de sécurité, static hardening).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Versionne l'état runtime IdeA produit pendant le sprint #13 : store de
tickets (dont les nouveaux #55 à #67), compteur et index, notes de mémoire
projet des lots F0 à F5, et journal des tâches de fond.
Séparé du code applicatif conformément à la convention du dépôt
(cf. ad1f225, a244f32) : métadonnée d'orchestration last-writer-wins,
sans impact sur le build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le client live visait /ws/live alors que le serveur expose /api/ws : la
connexion échouait et la reconnexion bouclait en permanence. Chemin corrigé
et extrait en constante WS_PATH, avec deux tests de non-régression sur
l'URL construite.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Firefox exige que fetch soit appelé sur Window : passer la référence nue
`fetch` en dépendance déclenchait « 'fetch' called on an object that does
not implement interface Window » au pairing. Nouveau helper defaultFetch()
qui binde globalThis.fetch, utilisé par webSession.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
default_app_data_dir aligné sur l'identifier tauri app.idea.ide, avec
précédence IDEA_APP_DATA_DIR > XDG_DATA_HOME > HOME. Log stderr
`idea --serve: app data dir = <path>` au démarrage et test de précédence.
Doc build web corrigée (VITE_TRANSPORT=http npx vite build + npm au lieu
de pnpm), section app-data-dir et avertissement double-writer desktop/serve.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Flow logout : POST /api/logout puis retour à l'écran de pairing et
déconnexion du live. 401 renvoie au pairing sans boucle. Bannière de
reconnexion couvrant le cas live-only et le serveur indisponible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sert le bundle web (dist/) en same-origin via --web-root / IDEA_WEB_ROOT
ou le web/ packagé. serve_static durci : anti path-traversal, typage MIME,
X-Content-Type-Options nosniff, CSP, fallback SPA. Ajoute POST /api/logout
qui révoque la session HTTP + WS, et une journalisation sécurité via
SecurityLogger. Documente le déploiement remote (dev local, packaging web/,
reverse proxy HTTPS).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot F5 du chantier server/client mode : le client web consomme les frames
event.domain (B7) pour animer ses surfaces live.
- webLive.ts : consommation du flux event.domain (workstate, background,
inbox).
- useLiveReconnect.ts : reconnexion du flux live.
- wsLiveClient.ts / index.ts : câblage du transport live.
- WebWorkspace.tsx : surfaces live branchées.
- Tests : WebWorkspaceLive.test.tsx, wsLiveClientReconnect.test.ts,
WebApp.test.tsx.
Validé : frontend 753 tests verts, desktop non régressé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot B7 du chantier server/client mode : le serveur --serve relaie l'état
live et les tâches de fond au client web.
- Relais du bus domaine en frames event.domain, queue bornée avec drop,
exclusion de PtyOutput.
- Allowlist étendue : list_background_tasks (read) + cancel_background_task
/ retry_background_task (actions), sous auth.
- 49 tests server (fan-out, drop sur queue pleine, relais de complétion
background, exclusion PtyOutput, actions allowlist + auth). Clippy propre.
Validé : app-tauri 292+ tests verts, backend 28, cœur backend agnostique,
desktop non régressé. Réserve connue : le fan-out socket réel relève d'une
validation live hors sandbox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot F4 du chantier server/client mode : le client web expose une cellule
agent branchée sur le PTY WebSocket, en face de agent.launch (B6).
- wsLiveClient.ts : launchAgent sur le transport WebSocket.
- streamGateways.ts : gateway agent alignée sur le contrat serveur B6.
- WebAgentCell.tsx : cellule agent web, câblée dans WebWorkspace et index.
- Tests : agentGateway.test.ts, WebApp.test.tsx.
Validé : frontend 749 tests verts, contrat B6↔F4 aligné (aucun écart de
frame), desktop non régressé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot F3 du chantier server/client mode : le client web branche xterm.js sur
le terminal distant via WebSocket, en face de l'endpoint PTY B5.
- wsLiveClient.ts : transport WebSocket du terminal avec reconnexion.
- streamGateways.ts : gateway terminal (open/attach/data/resize/close).
- frames.ts : frames PTY alignées sur le contrat serveur B5.
- Tests : terminalGateway.test.ts, wsLiveClientReconnect.test.ts.
Validé : frontend 743 tests verts (F3 ciblé 12), cohérence des frames
B5↔F3 confirmée, desktop non régressé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot B5 du chantier server/client mode : le serveur --serve expose le
terminal distant via un endpoint WebSocket, préalable au client xterm (F3).
- Handshake WebSocket RFC 6455 fait main, sans nouvelle dépendance.
- Auth de l'upgrade par cookie de session + Origin strict.
- Frames PTY (open/attach/close/ping), réattache, scrollback, backpressure.
- 37 tests server dont les refus de sécurité et les handlers
open/attach/close/ping. Clippy propre (result_large_err corrigé).
Validé : app-tauri 277 tests verts, backend 28, cohérence des frames
B5↔F3 confirmée, desktop non régressé. Réserve connue : le round-trip
socket réel relève d'une validation live hors sandbox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Premier incrément livrable du chantier server/client mode (B0→B4/F2) :
cœur backend commun extrait hors Tauri, sink de stream agnostique, serveur
HTTP sécurisé idea --serve (pairing + cookie de session, allowlist
read-only), et client web read-only (pairing, liste, ouverture, snapshot).
Desktop inchangé ; mode web opt-in via VITE_TRANSPORT="http". PTY WebSocket
(B5) + xterm (F3) reportés à la 2e vague. Incrément VERT de bout en bout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot F2 du chantier server/client mode : client web read-only complétant le
premier incrément livrable — pairing, liste des projets, ouverture et
snapshot de l'état, sans PTY.
- frontend/src/adapters/http/webSession.ts : session web (pairing/cookie).
- frontend/src/features/web : PairingScreen, WebWorkspace, WebApp, index.
- Câblage main.tsx et adaptations httpInvoker.ts / index.ts (cas 401).
- Tests : webSession.test.ts, WebApp.test.tsx, cas 401 dans
httpInvoker.test.ts.
Validé : frontend 736 tests verts, build vert, garde no-direct-invoke
verte, contrat B4↔F2 aligné, desktop non régressé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot B4 du chantier server/client mode : extension read-only de l'allowlist
du serveur --serve pour clôturer le premier incrément livrable (sans PTY).
- open_project exposé en read-only via resolve_project_readonly.
- get_project_work_state ajouté à l'allowlist.
- 4 nouveaux tests.
Validé : cargo check --workspace vert, app-tauri 265 tests verts (dont les
4 tests B4), contrat B4↔F2 aligné (list_projects/open_project/
get_project_work_state), desktop non régressé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot F1 du chantier server/client mode : nouvel adaptateur web branché
derrière les ports d'invocation et de flux live, permettant au frontend de
dialoguer avec le backend via HTTP + WebSocket en mode client/serveur. Le
mode desktop (Tauri IPC) reste inchangé.
- frontend/src/adapters/http : invoker HTTP, client live WebSocket, gateways
request/response et stream, frames, garde unsupported (7 fichiers + 2 tests).
- frontend/src/app : câblage DI (di.tsx) et son test, typage vite-env.d.ts.
Validé : build vert, garde no-direct-invoke verte, 724 tests verts,
desktop inchangé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot B2 du chantier server/client mode : le cœur backend émet désormais ses
flux via une abstraction de sink agnostique, sans dépendre directement de
tauri::ipc::Channel, préalable au futur serveur web + PTY WebSocket.
- crates/backend : abstraction de sink (stream.rs) câblée dans lib.rs.
- crates/app-tauri : implémentation Tauri du sink (stream.rs) et adaptation
des surfaces lib.rs, pty.rs, chat.rs.
- Nettoyage clippy des 2 warnings B2.
Validé : cargo check --workspace vert, tests backend/app-tauri verts,
cœur agnostique Tauri, clippy B2 propre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot B1 du chantier server/client mode : création de la crate `backend`
qui héberge le cœur commun (endpoint MCP, outils OpenAI) indépendant de
Tauri, préalable au futur serveur web + PTY WebSocket.
- crates/backend : nouvelle crate (lib.rs, mcp_endpoint.rs, openai_tools.rs).
- crates/app-tauri : câblage sur la crate backend (state.rs, mcp_bridge.rs,
Cargo.toml, tests/orchestrator_wiring.rs).
- Cargo.toml / Cargo.lock racine : ajout de la crate au workspace.
Validé : cargo check --workspace vert, tests backend/app-tauri verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot B0 du chantier server/client mode : cartographie des surfaces de
transport existantes, préalable à l'extraction du cœur backend commun.
- docs/ticket13-b0-backend-transport-inventory.md : inventaire backend.
- docs/ticket13-f0-frontend-transport-inventory.md : inventaire frontend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rendre visible l'échec « aucune réponse » de l'assistant de ticket :
stream sans Final ou Final vide → ReplyChunk::Error terminal visible,
plus jamais de tour muet. Backend + frontend, tests verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deadline de warmup configurable pour le cold-start llama.cpp.
Tests verts (domain/application/infrastructure + dto app-tauri).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Corrige la régression high « Error on loading local model » : le premier chargement
du serveur modèle local (cold-start llama.cpp) dépassait la fenêtre de readiness et
échouait. Le warmup dispose désormais d'un deadline par défaut de 600 s, surchargable
par config optionnelle `warmup_deadline_secs` (validée dans [30, 1800]).
- domain: champ `warmup_deadline_secs: Option<u64>` + validation de borne
- application: policy de readiness effective (défaut 600 s, override par config)
- app-tauri: DTO `warmupDeadlineSecs`
- infrastructure: application de la deadline effective au warmup
Verdict QA (vert) : domain 252, application 81 + 22 model_server, app-tauri
dto_model_servers 6, infrastructure model_server 2, build OK. Contrat readiness
couvert sur ports mockés.
Caveat : le cold-start end-to-end réel (llama-server) sort du sandbox de test ->
vérification manuelle utilisateur restante, non couverte par les tests unitaires.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduit un variant terminal `ReplyEvent::Error { message }` (domain/ports)
propagé jusqu'au chat : l'adapter OpenAI-compat récupère `reasoning_content`
quand `content` est vide, et un `Final` vide est converti en `Error` visible
plutôt qu'un tour silencieux qui pend. Le front (`ReplyChunk` + useTicketAssistant)
rend ce message d'erreur dans la cellule.
- domain: variant `ReplyEvent::Error`, bras non terminal (readiness, structured drain)
- infra/session: parse `reasoning_content` (delta/completion), fallback visible
- app-tauri: mapping ReplyEvent::Error -> ReplyChunk::Error, Final vide -> Error
- frontend: ReplyChunk `error`, rendu dans useTicketAssistant (+ test .test.tsx)
NB: commit sur la feature branch, PAS un merge. #60 reste inProgress.
Les 10 tests `cargo test -p infrastructure --lib session` échouent SOUS SANDBOX
uniquement (bind loopback 127.0.0.1:0 interdit -> Os PermissionDenied), pas une
régression : revérification hors-sandbox requise avant tout merge vers develop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PtyPort::wait/try_wait ajoutés au port figé (exit status mémorisé, idempotent) ;
le runner de tâches de fond détecte la fin via wait au lieu de l'EOF du drain
output, découplant cycle de vie du process et flux de sortie. Fakes PtyPort du
workspace complétés. Tee live UI hors périmètre (#58). QA vert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le runner de tâches de fond détectait la fin d'un process via l'EOF du drain de
sortie PTY, couplant le cycle de vie du process au flux d'output. Un output
encore ouvert (ou drainé ailleurs) pouvait masquer ou retarder la détection de
fin.
Ajoute wait/try_wait au port figé PtyPort :
- wait : bloquant, rend un ExitStatus idempotent ;
- try_wait : non bloquant, rend Option<ExitStatus> ;
- exit status mémorisé pour garder wait/try_wait/kill cohérents.
Implémentation dans PortablePtyAdapter (état d'exit mémorisé). Le
CommandBackgroundRunner détecte désormais la fin via pty.wait (select sur
cancel/deadline) au lieu de l'EOF du drain. Tous les fakes PtyPort du workspace
sont complétés en conséquence.
Le tee live UI reste hors périmètre (traité en #58). Aucun breaking IPC/front.
Tests : infrastructure/tests/background_task_runner.rs (nouveau) + pty_adapter.rs
verts, non-régression application/app-tauri OK (hors échecs réseau du sandbox).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sondes de DetectProfiles::execute parallélisées (une tâche Tokio par candidat,
JoinHandle attendus dans l'ordre) : coût total ~1×timeout au lieu de N×800ms,
ordre de sortie déterministe préservé. QA vert 20/20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les sondes de détection de profils étaient exécutées séquentiellement, portant
le coût total au pire à N×800 ms (N × timeout par candidat).
Chaque candidat est désormais sondé dans une tâche Tokio dédiée (tokio::spawn),
les JoinHandle étant attendus dans l'ordre de création : le coût total tombe à
~1×timeout tout en préservant un ordre de sortie déterministe. Aucune dépendance
ajoutée, aucun changement de contrat.
Test : crates/application/tests/profile_usecases.rs (concurrence multi-thread +
ordre déterministe). profile_usecases 20/20, suite application verte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scrollback de ChatBridge borné (double cap chunks + octets, drop par la tête)
pour stopper la croissance mémoire du buffer de replay. Aucun changement DTO.
QA vert 19/19.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le scrollback de ChatBridge croissait sans limite, faisant enfler la mémoire sur
les conversations longues. Il sert de buffer de replay transport (reattach), pas
d'historique durable.
Introduit un double cap : MAX_CHAT_SCROLLBACK_CHUNKS=2000 et
MAX_CHAT_SCROLLBACK_BYTES=512 KiB. Un helper trim_scrollback, appelé après chaque
send_output, drop des chunks entiers par la tête tout en conservant l'ordre et
les chunks les plus récents. Doc de chat.rs corrigée pour refléter cette nature
de buffer borné.
Aucun changement de ReplyChunk / reattach_agent_chat / DTO. Tests : suite
chat_bridge 19/19.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ticket_list : matching exact du numéro/#ref dans la recherche texte + curseur
de pagination opaque et stable (anchor-based v1, rejet explicite des curseurs
invalides). DTO cursor inchangé (non-breaking UI). Tests #20 verts 7/7.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Épurement de la dette de ticket_list sur deux axes :
Recherche texte — matching exact du numéro/#ref via parse_issue_search_ref,
court-circuité avant load_issue, sans matching par substring numérique
(« 1 » ne remonte plus « 12 », « 123 »…).
Pagination — curseur opaque et stable anchor-based au lieu d'un offset fragile :
token v1.<base64url-no-pad-json> encodant le tri + l'ancre {number, sortKey},
reprise strictement après l'ancre. Curseur legacy / invalide / de version
inconnue / avec sort divergent rejeté par une erreur explicite « Invalid
cursor ». Le DTO cursor reste String → non-breaking côté UI.
Fichiers : infrastructure/src/issues.rs, app-tauri/src/tickets.rs,
app-tauri/Cargo.toml (+ base64 0.22). Tests : issue_store_text_filter,
ticket_list_ 7/7 (anchor-based + rejets).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Routage structuré vs PTY rendu explicite dans LaunchAgent
(StructuredRoutingMode) : erreur explicite au lieu du fallthrough silencieux
quand un profil structuré rencontre une factory non câblée. QA vert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Un profil structuré rencontrant une factory de session non câblée retombait
silencieusement sur pty.spawn (fallthrough du `if let`), masquant une erreur de
configuration au lieu de la signaler.
Remplace ce fallthrough par une intention explicite :
- nouvel enum StructuredRoutingMode { HumanPtyFallback, RequireStructured } sur
LaunchAgent, posé via le builder with_structured_routing_mode ;
- le `if let` devient un `match` explicite (agent/lifecycle.rs) ; en mode
RequireStructured, un profil structuré sans factory câblée retourne
AppError::Process("structured profile requires structured session factory")
au lieu de tomber sur pty.spawn.
Composition root (app-tauri/src/state.rs) : launcher humain = HumanPtyFallback,
launcher orchestrateur = RequireStructured, wake background rebranché sur
orchestrator_launch_agent.
Tests : agent_lifecycle.rs (4 branches de routage) + non-régression
agent_wake/structured_launch_d3. Crate application verte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le sélecteur d'agent de LayoutGrid dérive « visible ailleurs » du layout
courant : un agent live non réellement affiché redevient sélectionnable, sans
tuer la session. QA vert 706/706.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le sélecteur d'agent de LayoutGrid désactivait un agent en se fondant sur le
seul liveAgents.nodeId : un agent live dont l'ancienne cellule affiche désormais
un autre agent restait grisé (« visible ailleurs ») alors qu'il n'était plus
affiché nulle part, donc impossible à resélectionner.
La dérivation « visible ailleurs » s'appuie maintenant sur le layout courant
(feuille visible ≠ cellule courante ET leaf.agent === candidate). Un agent live
dont l'ancienne cellule montre un autre agent redevient sélectionnable dans une
autre cellule ; la session n'est jamais tuée (attachLiveAgent conservé). Le prop
mort visibleNodeIds est retiré du threading.
Tests : frontend/src/features/layout/singletonAgent.test.tsx. Suite frontend
verte 706/706.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fiabilisation de la readiness au démarrage d'un serveur modèle local
(llama.cpp) : deadline de warmup longue, distinction vivant-en-warmup /
process mort. QA vert 20/20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le serveur modèle local (llama.cpp) était tué après ~5 s par un timeout de
readiness prématuré, alors que le modèle était encore en warmup (chargement en
RAM/VRAM). Résultat : « Error on loading local model » alors que le process
était vivant et en train de démarrer normalement.
Introduit une deadline de warmup longue configurable (~120 s via
ReadinessPolicy) qui distingue un process « vivant en warmup » d'un process
« mort » :
- Ready → succès immédiat
- Unreachable + Running → continuer d'attendre (warmup en cours)
- Unreachable + Exited → échec rapide (process mort, inutile d'attendre)
- deadline atteinte → stop + Timeout
Tests (crates/application/tests/model_server.rs) : warmup lent, exit pendant le
warmup, deadline atteinte, plus la régression adaptée. 20/20 verts, crate
application verte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stretch B2/F2 : progression fine du téléchargement du modèle llamacpp,
par-dessus le MVP (B1/F1) déjà intégré. Tests verts (application +
infrastructure + vitest, tsc clean).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Métadonnée runtime IdeA : tickets, sprints, mémoire et background-tasks
mis à jour au fil du sprint de progression fine du téléchargement (#54).
Séparé du code de feature (last-writer-wins, état non applicatif).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stretch B2/F2 de #54, par-dessus le MVP déjà mergé (B1/F1).
Backend : le port de téléchargement HF publie une progression débouncée
(bytes reçus / total, pourcentage) via le stream de statut du serveur
modèle, avec gestion du total inconnu (pas de faux %), du cache hit,
de l'annulation et du timeout.
Frontend : l'overlay plein-cellule de préparation du serveur affiche la
progression réelle (barre, %, octets, source) en mappant le fil de
statut, avec la règle « pas de faux % » quand le total est inconnu.
Tests : application + infrastructure (téléchargement débouncé, cancel,
timeout, cache hit, total inconnu) et vitest (overlay + formatage pur).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute un handle du téléchargement des modèles lors du démarrage de
llamacpp : le domaine et l'application émettent la progression de
téléchargement du modèle, relayée en événement côté app-tauri, et l'UI
l'affiche via un badge de lancement et un overlay de cellule pendant que
le serveur de modèle démarre.
Backend (B1) : progression de téléchargement dans domain/application,
relais d'événement app-tauri, couverture de tests.
Frontend (F1) : modelServerLaunch, badge et overlay LayoutGrid, tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extrait la logique de dérivation des entrées du wizard dans un module
dédié `wizardEntries` afin de préserver les informations du profil
courant lors de l'ouverture de l'affichage d'édition des profils, au
lieu de repartir d'un état vide. Couvre le comportement par des tests
unitaires (wizardEntries + FirstRunWizard).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ticket #50 : commande backend `list_open_view_windows` + consommateur
frontend qui réconcilie l'état du menu Panneaux avec les fenêtres
détachées réellement ouvertes. Validé QA vert (frontend
typecheck+build+677 tests ; backend cargo check/build + 7 tests
view_window).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Branche le consommateur frontend sur la commande backend
`list_open_view_windows` : le port window expose la liste des fenêtres
de panneaux détachées réellement ouvertes côté OS, l'adaptateur Tauri
(et son double mock) l'implémente, et `ProjectsView` réconcilie le
placement des vues au démarrage à partir de ce snapshot au lieu de
supposer un état. Couvre `viewPlacement` et la réconciliation par des
tests dédiés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ticket #52 : le bouton « + » d'ajout de projet est collé aux onglets
(retrait des `flex-1`). Validé QA vert (typecheck + build + 665 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retire les utilitaires `flex-1` sur la liste d'onglets et sur le
placeholder « No open tabs. » : le conteneur ne pousse plus la zone
d'onglets sur toute la largeur, si bien que le bouton d'ajout de projet
« + » vient directement à la suite des onglets au lieu d'être repoussé à
l'extrémité droite de la barre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Applique rustfmt sur agent_lifecycle.rs pour que develop repasse
`cargo fmt --check`. Fix de formatage pur, aucun changement de comportement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reformate `launch_opencode_omits_api_key_when_profile_has_none` selon
rustfmt (le fichier committé échouait `cargo fmt --check`). Aucun
changement de comportement, purement du formatage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute la commande Tauri `list_open_view_windows` qui retourne un
`ViewWindowSnapshot` (panel, label, visible) par fenêtre de panneau
détachée, avec le parseur `view_panel_from_window_label` (labels stables
et legacy suffixés du project id). Enregistre la commande dans le
handler. Backend seul pour le ticket #50 ; pas encore de consommateur
frontend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Suppression de 31 notes de mémoire projet devenues transitoires
(checkpoints, plans de test datés, findings résolus) ou supersédées, et
remise en parité de l'index MEMORY.md (52 entrées = 52 fichiers).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix du handle de limite de session sur le chemin direct (#30) + émission
AgentRateLimited sur le chemin délégué (#7/F2). QA verte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sur le rendez-vous délégué (idea_ask_agent), quand la cible touchait sa
limite de session, l'événement AgentRateLimited n'était pas émis : l'écart
backend documenté (ticket #7/F2) laissait la surface UI sans signal de
limite pour la cible déléguée.
Le service orchestrateur relaie désormais la limite de la cible vers le
service de limite de session, fermant l'écart en cohérence avec le chemin
direct (#30).
Couverture QA (sortie réelle) : orchestrator_service 63 passed,
session_limit_service 15 passed, session_limit_t4 7 passed,
cargo test -p application 0 failed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le handle de limite de session ne se déclenchait pas quand un agent
directement adressé (dont Main) touchait sa propre limite : le ReplyEvent
::RateLimited du chemin structuré direct n'était pas relayé au service de
limite.
On tap désormais ReplyEvent::RateLimited dans le registre terminal vers
SessionLimitService::on_rate_limited, qui émet AgentRateLimited puis
AgentResumeScheduled et arme la reprise auto annulable, exactement comme le
chemin délégué.
Couverture QA (sortie réelle) : structured_registry_d1 12 passed,
session_limit_wiring 6 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(projects): rendre visible le bouton + d'ajout d'onglet projet (#46)
Dernier ticket du sprint « Gestion des bugs » (#48, #45, #27, #47, #46).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le bouton + de la barre d'onglets projets était invisible (pas de variant
d'affichage). Il est désormais rendu en permanence via un variant secondary,
avec aria-pressed et title pour l'accessibilité, permettant d'ajouter un
projet en onglet.
Couvert par un nouveau test (ProjectTabs.test.tsx).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(windows): restauration panel-only des fenêtres détachées suivant le projet en focus (#47)
Backend + frontend : fenêtres/panneaux détachés restaurés en mode panel-only
(sans project_id figé), suivant le projet en focus de la fenêtre principale
via l'event focused-project.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Partie frontend. Ajoute un adaptateur focusedProject et un port dédié : la
ViewWindow détachée n'est plus liée à un project_id figé, elle s'abonne à
l'event focused-project émis par la fenêtre principale et affiche le panneau
du projet courant. ProjectsView propage le focus ; le détachement crée une
fenêtre panel-only. Couvert par les tests window/ViewWindow/focusedProject/
ProjectsView.focus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Partie backend. Les fenêtres/panneaux détachés étaient restaurés avec un
project_id figé au moment du détachement, si bien qu'ils restaient collés à
un projet mort ou incohérent après redémarrage. Ils sont désormais restaurés
en mode panel-only, sans project_id figé, et suivent le projet en focus de la
fenêtre principale via un event focused-project exposé par la couche fenêtre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L'assistant IA d'édition de ticket éditait les fichiers du ticket en direct,
hors de tout contrôle. Il passe désormais par les tools MCP idea_ticket_* :
préparation d'un environnement structuré dédié et policy d'enforcement scopée
au ticket courant, de sorte que l'assistant ne peut agir que sur son ticket
via la surface MCP plutôt que sur le système de fichiers.
Couvert par de nouveaux tests QA (mcp_server, assistant_context_store).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Corrige la race « port_occupied:8080 » au démarrage OpenCode/model server :
plusieurs demandes concurrentes tentaient chacune de lancer le serveur de
modèle local, provoquant un conflit de port. Le démarrage est désormais
sérialisé en singleflight — une seule tentative de lancement partagée entre
les appelants concurrents.
Couvert par un nouveau test de concurrence (cargo test -p application vert :
81 unit + 9 model_server).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Corrige la superposition z-index dans LeafView : le bandeau d'erreur de
cellule passait sous les boutons de contrôle, rendant le message illisible.
Le voile d'annonces ciblées est réaligné en conséquence.
Couvert par un nouveau test de layering (128/128 frontend au vert QA).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persiste l'état des fenêtres (layout/position) et le restaure au
relancement de l'application. Découpage hexagonal :
- domaine : modèle et port d'état des fenêtres (layout, ports)
- application : use cases de persistance/restauration
- infrastructure : store window_state (adapter de persistance)
- présentation : câblage app-tauri (state, commands, lib)
Couvert par des tests ciblés domaine/application/infrastructure.
Depend de #39 (fermeture des fenêtres auxiliaires).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduit le port UiPreferencesGateway et son adapter uiPreferences,
avec le module ticketFilterPersistence qui sauvegarde/restaure les
filtres (recherche, statut, sprint, agents) via useTickets,
useTicketSearch et useProjectAgents. Couvert par tests unitaires et
un test d'intégration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute close_non_main_webview_windows sur l'event de fermeture de la
fenêtre principale, avec le prédicat should_close_with_main_window qui
exclut la fenêtre "main" et cible toutes les fenêtres auxiliaires
(vues détachées, settings…). Couvert par 3 tests unitaires.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Attribution d'un sprint à la création de ticket via popup SprintPicker
(#38). QA vert (tsc + 634 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nouveau composant SprintPicker permettant de choisir un sprint lors de
la création d'un ticket depuis TicketsPanel ; export ajouté à l'index
des features tickets. Frontend-pur.
QA vert : tsc --noEmit exit 0, vitest 63 fichiers / 634 tests passés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les tickets restent rattachés à leur sprint ; SprintManager affiche le
statut et propose des filtres via useTicketSearch. Frontend / read-query.
QA vert : tsc --noEmit exit 0, vitest 62 fichiers / 623 tests passés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le TicketPicker permet la sélection multiple de tickets ; SprintManager
consomme la sélection multiple. Frontend-pur.
QA vert : tsc --noEmit exit 0, vitest 62 fichiers / 620 tests passés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refinement UX DockControls (#42) suite #26 : clarification des
contrôles d'en-tête. QA vert (tsc + 616 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refinement UX suite #26 : clarifie les contrôles DockControls dans
l'en-tête des panneaux. Frontend-pur.
QA vert : tsc --noEmit exit 0, vitest 62 fichiers / 616 tests passés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refonte menus & fenêtres (#26) : menu unique Panneaux, onglets projet
avec bouton +, sous-menus flyout. QA vert (tsc + 616 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sous-menus flyout dans MenuBar ; ProjectsView expose un menu unique
Panneaux et supprime les menus View/Window/File ainsi que le sélecteur
de projet ; ProjectTabs gère les onglets et le bouton +. Tests migrés
en conséquence.
QA vert : tsc --noEmit exit 0, vitest 62 fichiers / 616 tests passés,
invariants préservés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Support natif d'une source modèle Hugging Face (-hf) en alternative au
chemin .gguf local pour le serveur llama.cpp intégré, options structurées
llama.cpp (host/gpu_layers/context_size/jinja), preview de commande, et
refonte UX du wizard V2. Migration store model-servers.json V1 -> V2.
QA : suites ciblées vertes (domain 7, application 8, infra model_server 4,
app-tauri dto 5, Vitest 22, tsc/build OK).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refonte UX du wizard de configuration des modèles locaux du serveur
llama.cpp intégré, alignée sur la source Hugging Face et les options backend.
- choix de source sans jargon (chemin local .gguf vs dépôt Hugging Face) ;
- champs structurés -ngl (gpu_layers), -c (context_size), --jinja, --host ;
- zone d'arguments libres avec alerte sur les flags réservés ;
- preview de la commande via le backend (previewModelServerCommand, debounced) ;
- gateway/ports et mock adaptés au contrat V2.
QA : Vitest 22 verts, tsc/build OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend du support natif d'une source modèle Hugging Face en alternative au
chemin .gguf local pour le serveur llama.cpp intégré, et refonte des options.
- domaine : ModelSource {LocalPath|HuggingFace} + HfModelRef,
LlamaCppOptions {host,gpu_layers,context_size,jinja}, invariant auto_start
exigeant une source, validate_free_args (rejet des flags réservés).
- infra : build_argv partagé avec build_spawn_spec, émission -hf vs --model,
ordre argv figé ; migration model-servers.json V1 -> V2.
- app-tauri : DTO V2 (modelSource, compat modelPath, conflit = INVALID),
commande preview_model_server_command.
QA : cargo test ciblés verts (domain 7, application 8, infra model_server 4,
app-tauri dto 5). Échecs des suites complètes infra/app-tauri environnementaux
(sandbox bind/socket), hors périmètre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace le provider Ollama par llama.cpp dans l'intégration OpenCode
(tool-calling local). QA vert, réserve E2E live non bloquante.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le tool-calling local ne fonctionnait jamais via Ollama. Refonte du
support local d'OpenCode autour de llama.cpp: profil, catalogue,
matérialisation de la config OpenCode et surface first-run alignés sur
llama-server (backend + frontend).
QA vert (commandes réelles): domain 244, application 81+64, infra 263
(10 échecs = bind-port sandbox identiques sur develop, non-régression),
frontend 574, tsc propre. Réserve E2E live non bloquante faute de
llama-server joignable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`.ideai/tickets/` et `.ideai/sprints/` sont le registre durable du projet (les
tickets sont déjà référencés par les messages de commit : #14, #21..#25, #28),
au même titre que `.ideai/memory/`. Ils entrent donc dans le dépôt.
À l'inverse, `.ideai/background-tasks/` (snapshots de rendez-vous headless) et
`.ideai/proposals/` (amendements de contexte en attente d'arbitrage) restent de
l'état d'exécution transitoire, de la même classe que `live-state.json` et
`.ideai/requests/` : ils doivent être ignorés (patch `.gitignore` à venir).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute les contextes des deux nouveaux agents et enregistre leurs règles dans
`.ideai/permissions.json`. Aucun impact applicatif : état de configuration
projet, isolé du code de feature.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capitalise les notes durables produites pendant le sprint UI rework et les
tickets livrés depuis. Commit séparé du code : `.ideai/memory/` est le store
durable versionné, il ne doit jamais être mélangé aux commits de feature.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First-run wizard figé : boutons « Save and continue » et « Detect installed
CLIs » grisés à vie.
Cause racine à deux étages, corrigée aux deux :
- backend : `AgentRuntime::detect` était un `fn` synchrone pilotant un
spawner async via un `block_on` imbriqué — panique sous le runtime Tauri,
commande IPC sans réponse. Le port passe en `async fn`, la sonde est bornée
par un timeout, et les profils OpenAI-compatible sont sondés en HTTP.
- frontend : `busy` awaitait `detectProfiles`, si bien qu'une promesse
pendante grisait les actions sans issue. La détection devient best-effort,
suivie par un drapeau `detecting` séparé qui n'inhibe rien.
QA vert (exécution réelle) : domain 241, application 81, infrastructure 269,
app-tauri 63+15 ; vitest 569 tests / 59 fichiers ; tsc --noEmit exit 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Même backend réparé, le wizard restait à la merci d'une détection qui ne
répond pas : `reload()` et `detect()` awaitaient `detectProfiles` sous le
même drapeau `busy` qui grise « Save and continue » et « Detect installed
CLIs ». Une promesse IPC jamais résolue laissait donc les deux boutons
grisés à vie — sans issue pour l'utilisateur.
La détection redevient ce qu'elle est : une étape best-effort, jamais
bloquante.
- `busy` ne garde plus que ce dont le wizard ne peut pas se passer
(`firstRunState`) ou qui mute l'état (`configureProfiles`). Il ne dépend
plus jamais de `detectProfiles`. L'invariant est documenté en tête de
module.
- Un drapeau `detecting` distinct suit la sonde et n'inhibe aucune action.
Il est relâché par un timer (`DETECT_TIMEOUT_MS`), jamais par la seule
promesse : celle-ci peut rester pendante indéfiniment.
- Un identifiant de tour monotone (`detectRun`) invalide les tours périmés
et ceux qui survivent au démontage, évitant un `setState` hors montage.
- `reload()` rend les lignes immédiatement puis lance la détection en tâche
détachée. Si elle échoue, les lignes restent cochables à la main ; seul le
bouton explicite remonte l'erreur.
Tests: vitest 59 fichiers / 569 tests verts, tsc --noEmit exit 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le first-run wizard restait figé : `CliAgentRuntime::detect` construisait un
runtime tokio courant-thread via `futures_block_on` pour piloter un
`ProcessSpawner` async. Appelé depuis le runtime async de Tauri, ce
`block_on` imbriqué panique ; la commande IPC ne répond alors jamais, la
promesse `detectProfiles` reste pendante et `busy` ne redescend plus.
Ce commit s'attaque à la cause côté backend :
- `AgentRuntime::detect` devient `async fn` (`#[async_trait]`) : le port cesse
de mentir sur sa nature. Il pilote un spawner async, il est async. Le
`futures_block_on` disparaît, et avec lui le runtime imbriqué.
- La sonde CLI est bornée par `tokio::time::timeout(DETECTION_TIMEOUT)` :
un binaire qui ne rend jamais la main dégrade la détection en `Err`, il
ne gèle plus l'appelant.
- Les profils `StructuredAdapter::OpenAiCompatible` n'ont pas de CLI à
spawner : les sonder revenait à tester un binaire inexistant. Ils sont
désormais sondés par un GET HTTP sur l'endpoint `/models` dérivé de
`chat_http.endpoint`, borné par le même timeout.
`DetectProfiles` séquence les `await` sur les candidats. Les fakes
`AgentRuntime` des crates application/infrastructure/app-tauri suivent la
nouvelle signature.
Tests: cargo test -p domain (241), -p application (81), -p infrastructure
(269), -p app-tauri (63+15) — verts. `rg futures_block_on crates` sans match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre les profils IA locaux/LAN OpenAI-compatible comme profils IdeA
canoniques (adapter HTTP additif, parité tool-calling/MCP).
Backend (aab4bca) + frontend (d89380c), validés GO par QA de bout en bout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Câble la surface frontend des profils IA locaux/LAN OpenAI-compatible,
en parité avec l'adapter backend additif (aab4bca).
- domain: types de profil OpenAI-compatible
- first-run: édition/validation du profil dans le FirstRunWizard
- adapters/mock: mock de profil pour les tests
- terminals: rendu des round-trips et erreurs endpoint (role=alert)
Validé QA (frontend GO): typecheck exit 0, vitest 59 fichiers / 566 tests,
0 echec ; couverture timeouts round-trip + erreur endpoint role=alert
prouvees ; pas de regression sur les tests de bail headless.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(assistant): retire le plan sandbox project_read_only qui provoquait
l'EACCES/os error 13 au spawn de la CLI de l'assistant de ticket.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La surface « assistant de ticket » imposait un SessionPlan Landlock
restrictif (project_read_only) qui gouvernait la classe exec de la
sandbox OS et empêchait le spawn de la CLI (EACCES / os error 13).
factory.start reçoit désormais SessionPlan::None + sandbox absent :
l'assistant n'impose plus ce plan OS-sandbox. La classe exec n'est
plus verrouillée, le spawn de la CLI aboutit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Détache les vues dans de vraies fenêtres système Tauri (multi-écran,
fullscreen). Backend : commandes de gestion de WebviewWindow, capabilities
et composition root. Frontend : nouveau port WindowGateway et son adaptateur
window, entrée panel-only ViewWindow/ViewPanelBody, détachement câblé dans
ProjectsView. QA vert : app-tauri 63, frontend typecheck + vitest 546/546.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anchor de views — primitive de docking DockRegion + modèle ViewPlacement,
câblés dans ProjectsView. QA vert : frontend typecheck + vitest 537/537.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tri de la liste de tickets — backend (TicketListQuery/ticket_list, MCP)
+ frontend (contrôle « Trier par… » dans TicketFacetsBar). QA vert :
app-tauri 60+15, infrastructure 254, frontend typecheck + vitest 533/533.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute le sélecteur de tri dans TicketFacetsBar et propage le critère
via useTicketSearch jusqu'à TicketsPanel et TicketPicker, sur le contrat
de tri backend (e523f44). Couvre le tri par les tests tickets et
TicketPicker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute le paramètre de tri dans TicketListQuery et l'expose via
ticket_list (surface app-tauri + MCP). Ports et adaptateur mock
frontend alignés sur le contrat de tri. La partie UI (contrôle de tri
dans TicketFacetsBar) suivra dans un commit frontend dédié.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduit la primitive de docking DockRegion et le modèle ViewPlacement
pour ancrer les vues dans le chrome, câblés dans ProjectsView. Tests
unitaires DockRegion et test d'intégration docking de ProjectsView.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(tickets): perte de focus dans l'édition d'un ticket (#17) — focus-trap
FloatingWindow passé en mount-only via onCloseRef, + test de régression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le focus-trap de FloatingWindow avait un useEffect dépendant de `[onClose]`.
`onClose` (ex. TicketDetail.requestClose) est une closure recréée à chaque
render : chaque frappe ré-armait l'effet et renvoyait le focus au premier
élément focusable, ne laissant saisir qu'une lettre à la fois dans le
formulaire d'édition.
Correctif : conserver le dernier `onClose` dans un `onCloseRef` et passer
l'effet focus-trap en mount-only (`[]`). Le handler Escape lit la valeur
courante via la ref. Ajout d'un test de régression prouvé rouge-sans /
vert-avec le fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot C du sprint UI rework : fenêtre dédiée de gestion des tickets (#17).
Clôt le sprint UI rework (#16, #18, #19, #17 intégrés).
Tests verts (tsc clean, suite complète 530/530).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot C du sprint UI rework : fenêtre dédiée de gestion des tickets,
appuyée sur la primitive FloatingWindow (#16) et la popup TicketPicker (#18).
- TicketDetail : gestion des liens de ticket via la popup TicketPicker,
assistant IA en fenêtre flottante
- FloatingWindow : ajustements pour l'usage fenêtre dédiée
Tests : tsc --noEmit clean, suite complète 530/530, suites tickets +
FloatingWindow 50/50.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot D du sprint UI rework : TicketPicker branché dans la gestion de sprint (#19).
Tests verts (tsc clean, suite complète 528/528, tickets 38/38).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot D du sprint UI rework : branche la popup réutilisable TicketPicker (#18)
dans la sélection de ticket de la création/édition de sprint.
- SprintManager : sélection de ticket déléguée à la popup TicketPicker
- TicketsPanel : ajustements liés à l'intégration
Tests : tsc --noEmit clean, suite complète 528/528, suite tickets 38/38
(dont 2 nouveaux tests #19).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot A du sprint UI rework : menus déroulants, FloatingWindow, barre unique
et sélecteur de projet permanent (#16). Débloque le lot C #17.
Tests verts (tsc clean, suite complète 527/527).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot A du sprint UI rework : réorganisation de la navigation racine.
- shared/ui/MenuBar.tsx : barre de menus unique avec menus déroulants
- shared/ui/FloatingWindow.tsx : primitive de fenêtre flottante réutilisable
(socle du lot C #17 fenêtre dédiée gestion tickets)
- shared/ui/zIndex.ts : échelle z-index étendue pour les fenêtres flottantes
- App : intègre la barre unique et le sélecteur de projet permanent
- ProjectsView : sélecteur de projet permanent
Tests : tsc --noEmit clean, suite complète 527/527, suites impactées 33/33.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot B du sprint UI rework : popup réutilisable TicketPicker (#18).
Tests verts (TicketPicker 8/8, suite tickets 37/37, tsc clean).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute TicketPicker, popup modale réutilisable de sélection de ticket,
socle du chemin critique du sprint UI rework (#17 et #19 en dépendent).
- TicketPicker.tsx : popup de recherche/sélection réutilisable
- useTicketSearch.ts : hook de recherche/filtrage extrait et partagé
- TicketFacetsBar.tsx : barre de facettes extraite de TicketsPanel
pour réutilisation (listing principal + picker)
- shared/ui/zIndex.ts : échelle z-index centralisée
- TicketsPanel : consomme la barre de facettes extraite
Tests : TicketPicker.test.tsx 8/8, suite tickets 37/37, tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dette de contrat DTO work-state (#5, frontend) : summary sur BackgroundCompletion
mappé depuis task.summary, affichage discret sur tâche terminale, suppression du
chemin de merge top-level mort. Tests verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aligne le contrat DTO du panneau work-state (#5).
- ajout summary: string|null à BackgroundCompletion, mappé depuis task.summary
- affichage discret du résumé sur une tâche terminale
- suppression du chemin de merge top-level mort des background tasks
Tests verts : vitest workstate.test.tsx (21), suite complète (507),
npm run build (exit 0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Filtres multi-critères par cases à cocher (#12) : backend (IssueListFilter en Vec
statuses/priorities, filter_matches OR/AND, DTO en tableaux, MCP idea_ticket_list
aligné) et frontend (TicketListQuery multi-valeurs, UI checkboxes, toggle/clear).
Tests verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Passe les filtres de la liste des tickets en sélection multiple (#12).
Backend Rust :
- IssueListFilter : statuses/priorities en Vec, filter_matches en OR intra-champ
et AND inter-champs
- TicketListRequestDto en tableaux + from_request (parse/déduplication)
- MCP idea_ticket_list aligné sur le nouveau contrat
Frontend :
- TicketListQuery.statuses/priorities
- UI de cases à cocher, toggle/clear des filtres
Tests verts : issue_store (7), app-tauri --lib (59), mcp_server (24),
issue_usecases (6), sprint_usecases (6), frontend vitest (505), npm run build (exit 0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Suppression d'un ticket (#6) : backend (IssueStore::delete, FsIssueStore::delete
sous lock, use case DeleteIssue, event IssueDeleted, commande ticket_delete) et
frontend (gateway delete, retrait de liste/fermeture détail via event). Tests verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute la suppression complète d'un ticket (#6).
Backend Rust :
- port IssueStore::delete (NotFound si absent) + event IssueDeleted{freed_sprint}
- FsIssueStore::delete : supprime .ideai/tickets/<N>/ et l'index sous lock
- use case DeleteIssue + adaptations des tests sprint/ticket_assistant au port
- commande Tauri ticket_delete + câblage events/state/lib
Frontend :
- gateway delete (ports, adapter ticket + mock, domain)
- useTicketDetail : retrait de la liste et fermeture du détail via event issueDeleted
- intégration TicketDetail + tests
Tests verts : application/issue_usecases (6), infrastructure/issue_store (7),
app-tauri --lib (56), frontend vitest (503), npm run build (exit 0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assistant IA de ticket (#8) : backend (use cases open/close, policy d'outils
par agent, store de contexte assistant, events, commandes Tauri) et frontend
(gateway, hook, panneau, intégration TicketDetail). Tests verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ticket #11 : interface de création et gestion des sprints (frontend pur) —
SprintManager, useTickets, ports/adaptateurs et TicketsPanel. Suite frontend
verte (typecheck + vitest 497+ tests + build).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ticket #11 — surface UI de gestion des sprints (frontend pur).
- Nouveau composant SprintManager : création (nom optionnel), édition et
gestion du cycle de vie d'un sprint, contrôles accessibles (pas de DnD).
- useTickets étendu aux opérations de gestion de sprint ; ports, adaptateurs
(ticket.ts, mock/index.ts) et TicketsPanel câblés en conséquence.
- Tests Vitest associés (tickets.test.tsx).
Typecheck / vitest (497+ tests) / build verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ticket #10 — introduction du modèle de sprints côté backend.
- Domaine : nouvel agrégat Sprint (sprint.rs), IDs, événements et invariants ;
rattachement des issues à un sprint (issue.rs) et ports associés.
- Application : use-cases sprints (application/src/sprints) + erreurs dédiées.
- Infrastructure : store de sprints (infrastructure/src/sprints.rs), adaptation
du store d'issues et exposition MCP via orchestrator/mcp/tickets.rs.
- app-tauri : commandes, state et events pour piloter les sprints depuis l'UI.
Tests domaine/application/infra/app-tauri verts (sprint_usecases, sprint_store,
issue_store, mcp_server).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ticket #9 : persistance de l'édition d'un ticket côté frontend
(useTicketDetail + TicketDetail), tests Vitest tickets verts (13/13).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ticket #9 — l'édition d'un ticket (titre, corps, champs) est désormais
persistée depuis le détail : useTicketDetail porte l'état d'édition et le
flux de sauvegarde, TicketDetail expose l'UI d'édition/validation.
Tests Vitest tickets verts (13/13), typecheck et build OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre les annonces live inter-agent (ticket #4) : backend du tap
mpsc live (B0-B3), surface frontend des annonces sur les cellules
(F1-F3), et les notes projet associées.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Notes du chantier #4 relancé sur develop, commit séparé du code
(convention d'usage) :
- ticket4-restart-on-develop-ebd992e: baseline de redémarrage.
- ticket4-overlay-composition-leafview: décision de composition de l'overlay
dans le layout (LeafView).
- ticket4-announcements-frontend-f1f2f3: cadrage de la surface frontend F1-F3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Affiche les annonces d'agent en direct au-dessus des cellules, sur la base
develop :
- announcements: nouveau feature module — store réactif, provider
d'abonnement aux événements, overlay par cible et aperçu, avec tests
(announcementsStore + provider).
- App.tsx: montage du provider dans l'arbre applicatif.
- domain/index.ts: types partagés de l'événement d'annonce côté front.
- layout: composition de l'overlay dans LayoutGrid + règle d'exclusion
couverte par overlayExclusion.test.ts.
- AgentsPanel: intègre l'aperçu des annonces dans la surface existante.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Redémarre le ticket #4 sur la base develop. Émission d'annonces live d'un
agent vers l'UI, distinctes du Final de délégation :
- domain: ReplyEvent::Announcement/Final et DomainEvent::AgentAnnouncement
(events.rs), port d'émission (ports.rs), gating de readiness (readiness.rs).
- application: mapping des événements structurés en annonces
(agent/structured.rs, agent/mod.rs, lib.rs) et relais côté orchestrateur
(orchestrator/service.rs).
- infrastructure/session: parse des annonces + fix du Final pour Claude et
Codex, propagé aux adaptateurs et à la conformance
(claude.rs, codex.rs, conformance.rs, mod.rs, process.rs, sandbox_e2e.rs).
- app-tauri: relais Tauri des annonces vers le front (events.rs, chat.rs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capitalise la mémoire projet accumulée pendant les chantiers B7/B8
(tâches de fond first-class), le système de tickets V1 et le ticket #1 :
design, cadrages d'archi, checkpoints d'avancement et verdicts QA/frontend.
Mise à jour de l'index MEMORY.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
T1 — Wake automatique du propriétaire à la complétion.
La complétion d'une tâche de fond est désormais livrée à l'agent
propriétaire dès que la session accepte l'envoi (wake.rs :
mark_completion_delivered au send accepté), via un drain de flux dédié
(structured.rs : drain_reply_stream_with_readiness). L'inbox médiée
enfile l'item sans démarrer de tour ni marquer l'agent busy
(input/mod.rs : enqueue FIFO silencieux). Régression couverte
(tests/agent_wake.rs, tests input/mod.rs).
T3 — Tâches de fond projetées dans le read-model du panneau Work.
AgentWorkState porte désormais background_tasks
(VO AgentBackgroundTaskState), alimenté par un builder best-effort
with_background_tasks(store) : union list_open_for_agent + dispatch des
completions non livrées par owner_agent_id, erreur store => Vec vide
(aucune régression live/busy/tickets). DTO Tauri backgroundTasks et
wiring du BackgroundTaskStore côté state.rs. Le frontend, déjà câblé,
affiche Cancel/Retry (mapping queued/waiting -> pending, tri sur
updatedAtMs). Borne V1 : une tâche terminale déjà livrée n'est plus
énumérable (Retry limité à la fenêtre non livrée).
Tests : cargo build --workspace OK ; cargo test -p application /
-p app-tauri / -p infrastructure verts ; frontend build + vitest verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre le système de tickets/issues V1 (validé QA vert de bout en bout,
mémoire tickets-v1-e2e-validated-qa) dans la branche des tâches de fond.
Conflits résolus : app-tauri/state.rs, domain/events.rs, domain/lib.rs, domain/ports.rs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Câble l'action utilisateur sur les commandes Tauri B8 :
- port WorkStateGateway : ajout de cancelBackgroundTask/retryBackgroundTask
(le read-model se rafraîchit via l'événement domaine backgroundTaskChanged ;
retry rejoue sous un NOUVEL id de tâche).
- adapter Tauri : invoke cancel_background_task/retry_background_task.
- adapter mock : no-ops (le refresh réel est piloté par les événements).
- ProjectWorkStatePanel (BackgroundTaskRow) : état busy + affichage d'erreur
+ refresh après action.
- test ProjectsView.ls7 : stub de gateway complété.
Build vert : npm run build (tsc --noEmit + vite build), vitest 449 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Livre le lot backend B8 des tâches de fond :
- infrastructure : runner de commandes concret (CommandBackgroundRunner)
sur le port BackgroundTaskRunner, tail borné (bounded_tail/BoundedTail),
éclatement du module background_task en sous-modules (mod/runner/tail,
sink extrait de l'ancien background_task.rs).
- application : nouveau module background exposant les cas d'usage
SpawnBackgroundCommand, CancelBackgroundTask, RetryBackgroundTask et le
port BackgroundCommandArchive.
- domain : refactor point-2 de l'arbitrage Architect — sortie du trait
BackgroundCommandArchive de la couche domaine vers application.
- app-tauri : câblage runtime (commands, dto, state, lib) des commandes
spawn/cancel/retry et de la boucle de complétion sink fermée en
composition root.
Build workspace + tests application/infrastructure verts (QA).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expose les tâches de fond côté front : modèle domaine et
normalisation du work state, panneau ProjectWorkStatePanel,
intégration dans LayoutGrid et ProjectsView, et abonnement aux
événements backgroundTaskChanged / agentInboxChanged /
agentWakeChanged pour rafraîchir l'état. npm run build + npm test
(449) verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Branche le store de tâches de fond dans le runtime : store per-root
routé par project_id, AppReconcileBackgroundTasks au boot,
AppWakeSessionProvider et boucle ready→MediatedInbox→AgentWakeService,
avec .with_background_tasks(store, clock) au builder OrchestratorService.
open_project appelle reconcile_background_tasks au démarrage.
Aligne aussi 4 tests MCP périmés de l'ancien protocole : idea_reply
retiré (erreur JSON-RPC -32601), idea_ask_agent exposé (13 tools),
rendez-vous capture le Final inline. cargo test -p app-tauri --lib
= 52 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Câblage des événements de complétion/mailbox/réveil vers la couche
app-tauri pour consommation par le frontend (lots F1-F4 à venir).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Orchestration du réveil (wake) d'un agent sur complétion/message et
traitement du rendez-vous inter-agent comme tâche de fond de 1re classe.
Couvert par agent_wake (vert).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adapters de persistance des BackgroundTask (store), sink de récupération
de complétion post-tour et boîte de réception (mailbox) pour les messages
concurrents, câblés sur l'entrée. Couvert par background_task_store,
background_completion_sink, agent_inbox et orchestrator_watcher (verts).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entités BackgroundTask et Inbox, identifiants, événements et ports du
domaine pour la récupération de complétion post-tour et la réception de
messages concurrents. Fondations des lots infra/application suivants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La conversation inter-agent (idea_ask_agent) doit rester strictement
headless : la cible répond via une session structured capturée par IdeA,
sans jamais écrire dans le PTY d'une cellule visible ni fermer le terminal
que l'utilisateur observe.
- lifecycle: flag `allow_structured_alongside_pty` — ouvre une session
structured pour la délégation sans fermer le PTY visible (coexistence).
- orchestrator/service: `ensure_structured_session` ne ferme plus le PTY
visible ; mapping typé de l'erreur no-reply ; coexistence PTY/structured.
- infrastructure/input: garantit zéro `DelegationReady` et zéro write PTY
pour une délégation headless, même quand l'entrée est `front_owned`.
- app-tauri (commands/state): câblage du flag de coexistence.
- tests: fixtures portées vers le modèle structured/headless, assertions
cibles mises à jour (aucun #[ignore] ajouté, aucun test retiré).
Validé réel : cargo build OK ; orchestrator_service 60/0, agent_lifecycle
62/0, structured_launch_d3 22/0, infrastructure input 35/0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 10:22:06 +02:00
928 changed files with 158949 additions and 12861 deletions
- Tu **décides** : quand Main t'annonce une nouvelle feature (après cadrage Architect),
c'est **toi** qui tranches s'il faut une nouvelle branche, un checkout/switch, ou rien.
Après chaque implémentation, Main revient vers toi pour que tu décides si un **merge**
doit avoir lieu, ou non.
doit avoir lieu quelque part, ou non.
**Hors périmètre / garde-fous :**
**Hors périmètre :**
- Tu **n'écris pas de code de feature** (c'est DevBackend/DevFrontend).
- Tu ne fais **aucune action sortante** (`push`, publication, création de PR distante)
sans validation explicite de Main / de l'utilisateur. Ton terrain est **local**.
- Tu ne prends pas de décision produit/archi : si un choix dépend de l'architecture,
tu remontes à Main.
- **Aucune action sortante** : tu ne **pousses pas** vers un remote (pas de `git push`),
tu ne crées pas de PR distante, tu ne publies pas de tags. La synchronisation avec un
remote n'est pas dans ton périmètre. Tu restes **strictement local**.
- **Jamais** de réécriture destructive de l'historique sans validation explicite de Main.
---
@ -55,6 +52,7 @@ feature/* ← une branche PAR nouvelle feature. C'est là que le dev se fait.
> Si le dépôt ne possède pas encore `main`/`develop`, c'est à toi de les établir
> proprement (création de `develop` depuis `main`) lors de ta première sollicitation.
> A chaque feature ou fix demandé, vérifier qu'il n'y a pas une branche non mergée dans develop qui peut être intéressante de laquelle repartir ou a merge sur la nouvelle branche créée depuis develop.
---
@ -109,11 +107,19 @@ tu le dis.
## 5. Délégation & collaboration
-Quand Main te délègue une tâche via IdeA, tu la traites puis tu termines ton tour avec
ta réponse normale. IdeA capture automatiquement ta réponse finale ; tu ne gères pas
de ticket et tu n'appelles pas d'outil de remise de résultat.
-Tu réponds à Main via le protocole d'orchestration IdeA (`idea_reply`). Quand Main te
délègue une tâche (message `[IdeA · tâche de … · ticket …]`), tu traites puis tu
@ -109,3 +109,16 @@ Si la demande utilisateur contredit le cycle, rappelle brièvement la règle et
---
*Dernière mise à jour : 2026-06-20*
---
## Découverte exhaustive des outils MCP IdeA
Codex charge les outils MCP de façon différée : une recherche sémantique peut ne retourner qu'un sous-ensemble des outils IdeA disponibles. Avant toute opération d'orchestration, de tickets, de mémoire, de contexte, de skills, de templates, de sprint, de workstate ou de tâche en arrière-plan :
1. Inspecte le registre complet des outils disponibles et filtre le préfixe `mcp__idea__`.
2. Choisis l'outil natif IdeA le plus spécifique dans cet inventaire exhaustif.
3. N'utilise pas l'absence d'un outil dans les résultats partiels de recherche comme preuve de son indisponibilité.
4. Appelle les outils différés par leur nom exact via le registre lorsqu'ils ne sont pas exposés directement.
Cette vérification de découverte est obligatoire au début de chaque workflow IdeA, afin que les outils natifs soient utilisés spontanément et pas seulement lorsqu'un utilisateur en rappelle le nom.
-`crates/web-server` : handlers / Web API / mapping requête-réponse / erreurs.
- Commandes : `cargo test -p <crate>` ciblé, `cargo test --workspace` global.
**Frontend (TS/React)** :
@ -42,6 +43,7 @@ tu le signales tel quel.
- **Round-trip de sérialisation** (DTO ↔ domaine, fichiers `.ideai/*.json`).
- **Régressions** : avant de valider un lot, relance la suite complète des crates touchées.
- **Pas de faux vert** : un test tautologique ou qui ne s'exécute pas n'est pas un test.
- **Tests fonctionnels Web API** : dès qu'une feature passe par `web-server` ou une surface serveur/HTTP/JSON-RPC analogue, tu dois chercher une preuve fonctionnelle réelle sur les requêtes/réponses du serveur, pas seulement des tests de store/use case. Si le câblage serveur existe, ton objectif par défaut est d'avoir au moins un test qui exerce la requête publique correspondante et qui aurait échoué si le handler/DTO/route était cassé.
## 4. Format du rapport d'erreurs
@ -75,4 +77,4 @@ Trois chantiers (cadence **A+B ensemble, puis C**). Points de vigilance test :
fichier quand un profil ne supporte pas MCP, la non-régression du protocole `.ideai/requests`.
Tu interviens **après** le cadrage d'`Architect`, en binôme avec le dev du lot concerné, jusqu'au
- [session-limit-handling-design](session-limit-handling-design.md) — Design valide (detecteur hierarchique + reprise auto annulable) pour les limites de session des agents.
- [git-owns-commit-merge-decisions](git-owns-commit-merge-decisions.md) — Ne jamais demander a l'utilisateur s'il faut committer/merger/brancher : l'agent Git tranche toute la topologie du depot.
- [skills-integration-canonical-foundation](skills-integration-canonical-foundation.md) — Topologie d'intégration du chantier skills agent et décision d'abandon de feature/agent-skills.
- [idea-program-surface-separation-and-livestate](idea-program-surface-separation-and-livestate.md) — Cadrage du programme post-skills : ce qui existe vs reste, et la règle de frontière surface-agent/humaine.
- [handoff-ls5-summary-bound-and-llm-seam](handoff-ls5-summary-bound-and-llm-seam.md) — Contrat figé de la borne de handoff (perf) et du seam résumeur LLM non activé.
- [conversation-log-ls6-rotation-and-paginated-read](conversation-log-ls6-rotation-and-paginated-read.md) — Contrat figé de la rétention/rotation du transcript et de la lecture paginée riche (surface humaine).
- [conversation-viewer-ls7-frontend](conversation-viewer-ls7-frontend.md) — Contrat figé du viewer de transcript paginé (frontend pur, lecture seule, surface humaine).
- [live-state-persistence-program-closed-ls8](live-state-persistence-program-closed-ls8.md) — Le programme live-state + persistance conversationnelle est livré et documenté ; pointe la doc de clôture et les points restés ouverts.
- [rendezvous-no-reply-backstop-design](rendezvous-no-reply-backstop-design.md) — Décision d'architecture sur la fin-de-tour et le backstop no-reply du rendez-vous idea_ask_agent ⇄ idea_reply, après échec live du fix Finding A (77e62e5).
- [tickets-frontend-v1-done](tickets-frontend-v1-done.md) — État du frontend du système de tickets — livré, vert, décisions de contrat clés.
- [tickets-v1-e2e-validated-qa](tickets-v1-e2e-validated-qa.md) — Verdict QA du système de tickets V1 sans attachments sur feature/issue-ticket-system — vert de bout en bout, avec la seule réserve du typage de conflit côté UI.
- [b8-command-runner-pty-framing](b8-command-runner-pty-framing.md) — Cadrage hexagonal figé du lot B8 : runner concret sur le port BackgroundTaskRunner, fermeture de la boucle sink en composition root, contrat de complétion, commandes cancel/retry.
- [b8-arbitration-outcomes](b8-arbitration-outcomes.md) — Décisions d'arbitrage Architecture sur les 5 écarts B8 remontés par DevBackend : ce qui reste dans B8 vs part en dette/ticket.
- [b8-in-app-trigger-run-in-background](b8-in-app-trigger-run-in-background.md) — Arbitrage de l'écart de périmètre #1 — la surface agent MCP idea_run_in_background ferme la boucle B8, contrat et lots figés.
- [workstate-background-tasks-projection-fix](workstate-background-tasks-projection-fix.md) — Décision d'archi pour rendre visibles Cancel/Retry dans le panneau Work — extension du read-model GetProjectWorkState, contrat DTO et borne de livraison.
- [tickets-t3-frontend-validation-verdict](tickets-t3-frontend-validation-verdict.md) — Verdict de validation frontend du fix T3 (projection backgroundTasks par agent) sur feature/background-tasks-first-class — vert, avec 3 désalignements de contrat mineurs.
- [ticket4-restart-on-develop-ebd992e](ticket4-restart-on-develop-ebd992e.md) — Cadrage du redémarrage des annonces inter-agent sur la base pré-canonique develop ebd992e — ce qui existe, l'écart live, la réutilisabilité du backup et le découpage B/F.
- [ticket4-overlay-composition-leafview](ticket4-overlay-composition-leafview.md) — Règle de composition des deux voiles plein-cellule de LeafView (write-portal injection PTY vs F3 annonces busy) — exclusion mutuelle avec priorité F3, arbitrée pour éviter le double voile.
- [ticket7-f2-delegated-limit-no-agentratelimited](ticket7-f2-delegated-limit-no-agentratelimited.md) — Écart backend ticket #7/F2 : le rendez-vous délégué n'émet pas AgentRateLimited pour la cible limitée.
- [ui-rework-sprint-scoping-contracts](ui-rework-sprint-scoping-contracts.md) — Frontières hexagonales et contrats figés du sprint UI rework — les 4 tickets sont frontend-purs, la popup TicketPicker #18, l'échelle z-index et le curseur opaque.
- [floatingwindow-focus-trap-mount-only](floatingwindow-focus-trap-mount-only.md) — Le focus-trap de FloatingWindow ne doit dépendre que du mount ; sinon un onClose instable vole le focus à chaque frappe.
- [ticket17-focus-trap-fix-merged-develop](ticket17-focus-trap-fix-merged-develop.md) — Correctif de la perte de focus dans l'édition de ticket (#17) livré sur develop via feature branch dédiée.
- [ticket25-assistant-sandbox-eacces-rootcause](ticket25-assistant-sandbox-eacces-rootcause.md) — Root cause et contrat de fix du EACCES (os error 13) au spawn de la CLI d'un assistant de ticket — le preset SandboxPlan::project_read_only handle la classe exec Landlock.
- [ticket28-firstrun-detect-hang-scoping](ticket28-firstrun-detect-hang-scoping.md) — Cadrage hexagonal du bug bloquant first-run (#28) — panic block_on imbriqué dans detect + découplage busy/détection, contrats et point de vérité QA.
- [ticket28-f1-frontend-delivered](ticket28-f1-frontend-delivered.md) — Lot F1 du ticket #28 livré sur feature/ticket28-firstrun-detect-hang — invariant busy/detectProfiles, flag detecting, timeout UI, test de non-régression.
- [f36-multi-opencode-profiles-frontend](f36-multi-opencode-profiles-frontend.md) — Multiplicité des profils OpenCode locaux côté UI first-run wizard — livrée, verte, sans gap backend.
- [f35-config-crud-delivered](f35-config-crud-delivered.md) — F35.2 (config serveur local + sélection dans les profils OpenCode) livrée et verte sur feature/modeles-locaux ; clôt le sprint modèles locaux F34-F36.
- [develop-realigned-to-cli-ui-baseline-2026-07-02](develop-realigned-to-cli-ui-baseline-2026-07-02.md) — Décision de topologie (validée utilisateur) : develop réaligné sur la ligne UI CLI (pre-chat-ui-baseline).
- [feature-agent-skill-awareness-design](feature-agent-skill-awareness-design.md) — Design validé + découpage de la feature « surfacer les skills assignés à un agent à la manière MCP » (manifeste + idea_skill_read).
- [inter-agent-announcements-feature-and-codex-final-bug](inter-agent-announcements-feature-and-codex-final-bug.md) — Design figé des annonces inter-agent (UI live) + cause racine du bug Final Codex, validé utilisateur le 2026-07-02.
- [inter-agent-live-context-shared-per-agent](inter-agent-live-context-shared-per-agent.md) — Décision d'architecture figée : le contexte live inter-agent est PAR AGENT (partagé), pas par paire (validée utilisateur 2026-07-02).
- [ticket4-announcements-frontend-f1f2f3](ticket4-announcements-frontend-f1f2f3.md) — Topologie du frontend des annonces inter-agent (store borné, preview requester filtré, overlay cible piloté par le busy/idle par agent) — réintégré sur base develop.
- [ticket54-f1-model-download-overlay-frontend](ticket54-f1-model-download-overlay-frontend.md) — Overlay plein-cellule de préparation du serveur modèle local + progression (barre/%/bytes/source), corrélation factorisée, priorité de voile.
- [ticket56-visibleelsewhere-derives-from-layout](ticket56-visibleelsewhere-derives-from-layout.md) — Fix frontend du sélecteur d'agent : la désactivation « visible ailleurs » doit venir du layout courant, pas du dernier nodeId du live registry.
- [ticket13-f0-frontend-transport-inventory](ticket13-f0-frontend-transport-inventory.md) — Résultat de l'inventaire B0/F0 frontend du chantier client/serveur #13 — la frontière gateways transport-neutres existe déjà, F1 = ajout d'adapters HTTP+WS.
- [ticket13-f1-http-ws-adapter-delivered](ticket13-f1-http-ws-adapter-delivered.md) — Le 3e jeu d'adapters frontend (HTTP request/response + squelette WebSocket) du chantier client/serveur #13 est livré et vert, derrière les ports inchangés.
- [ticket13-f2-web-readonly-client-delivered](ticket13-f2-web-readonly-client-delivered.md) — Le client web read-only (pairing cookie, liste projets, ouverture read-only + snapshot work-state) du chantier client/serveur #13 est livré et vert.
- [ticket13-f3-xterm-websocket-delivered](ticket13-f3-xterm-websocket-delivered.md) — L'adapter WS terminal (round-trip xterm, reconnexion+replay, états connexion) du chantier client/serveur #13 est finalisé et vert côté frontend.
- [ticket13-f4-web-agent-surface-delivered](ticket13-f4-web-agent-surface-delivered.md) — L'agent CLI web (agent.launch → terminal.attached, réattache sans relance, cellule agent réutilisant TerminalView) du chantier client/serveur #13 est livré et vert côté frontend.
- [ticket13-f5-web-live-surfaces-delivered](ticket13-f5-web-live-surfaces-delivered.md) — Les surfaces live web (workstate live via event.domain, background tasks cancel/retry, inbox, re-synchro au reconnect) du chantier client/serveur #13 sont livrées et vertes.
- [ticket74-f1-web-bundle-transport-seam](ticket74-f1-web-bundle-transport-seam.md) — Deux artefacts Vite (dist/dist-web), mode `web` portable Windows, et le câblage anti-divergence de la constante de transport.
- [model-catalogue-compat-cadrage](model-catalogue-compat-cadrage.md) — Frontières hexagonales, ports, DTO, fallback et matrice de compatibilité versionnée pour l'évolution du catalogue de modèles des profils structurés Codex/Claude.
description: Le build AppImage échoue sur cette machine (CachyOS/Arch) à l'étape linuxdeploy/strip (.relr.dyn) ; correctif = NO_STRIP=true. Séquence de build mise à jour après #74 (beforeBuildCommand = build:bundle).
description: Ancre runtime précise pour câbler B7 (tâches de fond) dans app-tauri/state.rs — repérée par Main pour dé-risquer la délégation DevBackend.
metadata:
type: reference
---
# B7 — Ancre de câblage runtime (crates/app-tauri/src/state.rs)
Composition root = builder `AppState` dans `crates/app-tauri/src/state.rs`. Repères exacts
(lecture Main 2026-07-02, base `feature/background-tasks-first-class`) :
description: Décisions d'arbitrage Architecture sur les 5 écarts B8 remontés par DevBackend : ce qui reste dans B8 vs part en dette/ticket.
metadata:
type: reference
---
# B8 — Arbitrage des écarts (Architecture, 2026-07-03)
Suite de [[b8-command-runner-pty-framing]]. Build vert (sink 6/6, tail 5/5, b7 1/1).
1.**Tee PTY live** : ACCEPTER B8 sans rendu live (bloqueur levé). Le cœur complétion→wake fonctionne ; tee = orthogonal. NE PAS toucher PtyPort ni broadcast multi-conso dans B8. Dette = ticket A : ajouter `PtyPort::wait`/`try_wait` pour découpler détection d'exit de la consommation d'output (l'EOF-comme-proxy-de-fin est fragile), puis tee UI. Pas de broadcast multi-consommateur.
2.**BackgroundCommandArchive** : REFACTORER — retirer le trait DOMAINE. Retry = registre runtime in-memory (app/runner), NON persisté (SpawnSpec porte des secrets, ne pas écrire dans .ideai/background-tasks/*.json qui voyage avec le projet). Conséquence : retry SESSION-SCOPED en V1, pas après reboot. Reboot-retry = dette ticket B (persistance sûre = redaction/store machine-local hors projet).
3.**spawn_background_command** : ACCEPTER, nécessaire (point d'entrée manquant du cadrage). B8 a donc 4 commandes : spawn/cancel/retry/list.
5.**list sans agentId dégradé** : ACCEPTER V1 (F2 centré agent). Limite : store n'énumère pas les tâches terminales/ouvertes par projet ; historique completed/failed partiel. Enrichir BackgroundTaskStore = dette ticket B.
## Tickets de suivi à ouvrir
- A : PtyPort::wait + tee live + robustesse détection de fin.
- B : persistance sûre de l'invocation (retry-after-reboot, secrets) + énumération terminale/projet du store.
B8 clôturable une fois le point 2 refactoré + points 1/5 tracés en dette.
description: Cadrage hexagonal figé du lot B8 : runner concret sur le port BackgroundTaskRunner, fermeture de la boucle sink en composition root, contrat de complétion, commandes cancel/retry.
Base `feature/background-tasks-first-class`. Fait suite à [[background-tasks-first-class-design]] et [[b7-wiring-anchor-state-rs]].
## Trou constaté
`BackgroundCompletionSink::new`/`start_from_runner` n'existent QUE dans les tests. En prod `state.rs`, `background_ready_tx` n'est alimenté que par le reconcile boot ; aucun `impl BackgroundTaskRunner` concret. `LocalProcessSpawner.run` = one-shot Output, non couplé.
## Décisions
- **Le spawner ne crée pas la task.** Concepts owner/project/wake_policy = application. On implémente le port FIGÉ `BackgroundTaskRunner` (domain/ports.rs:1349) via nouvel adapter infra `CommandBackgroundRunner` composant `Arc<dyn PtyPort>` (résolu via RemoteHost → Liskov SSH/WSL), PAS un PortablePtyAdapter en dur.
- Application = use case `SpawnBackgroundCommand` : alloue TaskId, store.create(Queued)→Running, runner.spawn(BackgroundTaskSpec). Le spec (ports.rs:207) porte déjà task_id/project_id/owner_agent_id/kind/wake_policy/command:Option<SpawnSpec>/deadline.
- Le runner n'écrit NI store NI inbox : il émet 1 `BackgroundTaskCompletion` sur subscribe_completions(). Le sink (single-writer) fait store.save PUIS ready_tx.send (persist-avant-signal).
- Composition root ferme la boucle : construire runner + `BackgroundCompletionSink::new(store_port, background_ready_tx)` + `sink.start_from_runner(runner)` sur `tauri::async_runtime::spawn` (pas de tokio ambiant). Ancre state.rs ~1540-1660.
- Frontière : seuls SpawnSpec/BackgroundTaskSpec/BackgroundTaskCompletion (domaine) franchissent ; portable-pty reste en infra. Rendu xterm = tee du même spawn vers PtyBridge, orthogonal au tracking.
## Contrat complétion
BackgroundTaskResult Success/Failure { finished_at_ms, exit_code:Some, summary, stdout_tail, stderr_tail bornés (ring UTF-8-safe, cap IDEA_BG_TAIL_BYTES ~8-16KiB) }. Cancel→runner kill→Cancelled{reason}, pas de wake succès. Invariants tenus par le sink (dédup task_id, IgnoredAlreadyTerminal, mark_completion_delivered) + pont enqueue_message (jamais busy) + wake si idle. Final reste terminal-de-tour, la complétion est un InboxItem::BackgroundCompletion.
## Commandes Tauri (dépend B8)
cancel_background_task({taskId}); retry_background_task({taskId})→BackgroundTaskDto (NOUVEAU task_id, jamais réutilisé); list_background_tasks({projectId,agentId?}). DTO camelCase {taskId,ownerAgentId,projectId,kind,state,exitCode?,summary?,stdoutTail?,stderrTail?,createdAtMs,updatedAtMs}.
## Sous-tâches DevBackend (ordre)
1 util tail borné (infra). 2 CommandBackgroundRunner (infra/background_task.rs + lib.rs). 3 use cases SpawnBackgroundCommand/Cancel/Retry (application). 4 fermer boucle sink en composition root (app-tauri/state.rs). 5 handlers+DTO (commands.rs, events.rs). 6 tee PTY live (pty.rs). 7 tests (réutiliser tests/background_completion_sink.rs).
Suite de [[b8-command-runner-pty-framing]] et [[b8-arbitration-outcomes]]. Constat : le consommateur B8 (runner PTY → sink → inbox → wake) est livré (8cac147) mais SANS producteur — `spawn_background_command` (commands.rs:2586) n'est appelé par personne. #1 n'a donc aucun déclencheur réel.
## Décision
- **Retenu : outil MCP agent `idea_run_in_background`** (surface principale, fermante). Chemin humain UI (bouton panel F2) = secondaire optionnel, `wake_policy=RecordOnly`. **Promotion auto d'une commande PTY longue : REJETÉE** (pas d'owner/intention, heuristique fragile, change la sémantique de write_terminal).
- Pourquoi (a) : toute la machinerie (WakeOwner, inbox, AgentWakePort) est agent-centrée ; seul un agent déclarant explicitement une tâche de fond exerce la boucle bout-en-bout.
## Contrat `idea_run_in_background`
Params : label(req), command(req), args[], cwd?(déf=project root), deadline_ms?. **owner = identité handshake du demandeur (non paramètre, non usurpable, rejet si absente)** ; project = contexte du demandeur ; record_only NON exposé côté agent → **WakeOwner forcé**. Retour SYNCHRONE {taskId,state} ; le résultat arrive plus tard en InboxItem::BackgroundCompletion (fire-and-forget-avec-tracking, distinct d'idea_ask_agent synchrone).
- Exécution : handler route vers use case EXISTANT application::SpawnBackgroundCommand (déjà en composition root state.rs), owner=requester, WakeOwner. Suivre le MÊME dispatch qu'AskAgent pour atteindre la couche app ; ne pas ré-router par commande Tauri front.
- FE-1 (secondaire) : formulaire création dans panel F2 → spawn_background_command record_only.
- QA T1 : agent appelle idea_run_in_background(`sh -c 'sleep 8; echo done'`), finit son tour → owner ré-invoqué avec exit+résumé. T3 cancel/retry sur ce taskId (retry session-scoped, pas reboot — dette ticket B).
## Branche
Tient sur feature/background-tasks-first-class (additif, réutilise la boucle figée). Rien à préparer côté Git.
description: Cadrage hexagonal du modèle BackgroundTask + mailbox bornée par agent + wake owner après complétion post-tour. Fait autorité pour les lots B1-B7 / F1-F4.
metadata:
type: reference
---
# Tâches de fond de 1re classe — CADRAGE FIGÉ (Architect, 2026-07-02)
Base : `feature/background-tasks-first-class` (@ fccc1e2, empilée sur v2 `62915ee`+`fccc1e2`,
au-dessus de develop `a9653bc`, ligne CLI/PTY « toujours headless »).
## Objectif (2 défauts à corriger)
1.**Complétion post-tour perdue** : une tâche de fond (ex. build `run_in_background`) finit
après la fin du tour de l'agent → IdeA ne ré-invoque pas le propriétaire avec le résultat.
2.**Pas de mailbox** : message concurrent pendant working/waiting rejeté « still busy » au
lieu d'être mis en file et drainé au tour suivant.
title: CAUSE RACINE — le backstop tire sur chaque tour interne (turn_duration ≠ retour-au-prompt)
type: project
description: Le turn-watcher fait tirer le backstop no-reply au PREMIER turn_duration, soit le 1er pas d'agent interne — il casse toute délégation multi-étapes (libération prématurée + rapport perdu). Le test QA pong passait par chance (tâche mono-tour).
---
# CAUSE RACINE (2026-06-23, prouvée live) — backstop tire sur les tours INTERNES
## Preuve
Cadrage délégué à Architect (tâche multi-étapes, lecture de code) :
```
562100 [rendezvous] ask started -> Architect turn_timeout_ms=600000
- Tiré à **2843 ms** : impossible qu'Architect ait fini un cadrage en 2,8 s.
- Au même instant, transcript Architect **toujours en écriture** (Bash/Read en boucle), `idea_reply` count = **0** → Architect **continuait à travailler**.
- Transcript = **61 `turn_duration`** : Claude écrit un `turn_duration`**par tour d'agent interne** (chaque appel LLM de la boucle agentique), PAS seulement au retour-au-prompt final.
## Diagnostic
`claude_turn_watcher.rs` fait tirer `turn_ended` au **premier incrément** de `turn_duration` au-dessus de la baseline. Or cet incrément survient dès le **1er pas interne** de l'agent (~secondes). Donc le backstop confond « l'agent a fait un pas de raisonnement » avec « l'agent est retourné au prompt sans répondre ». Pour **toute tâche multi-étapes** : libération prématurée du demandeur « sans réponse » pendant que la cible bosse encore → `idea_reply` final → « no pending request » → **rapport perdu** (travail survit sur disque/branche).
Le test QA « pong » (backstop-no-reply-live-test-failed-2026-06-23, devenu vert) passait **par chance** : tâche à **un seul tour** ⇒ premier tour = dernier tour. Le fix `encode_cwd` a fait tirer le watcher correctement, mais a **révélé que la CONDITION de tir est fausse**.
## Conséquence opérationnelle
Avec le binaire courant, **aucune délégation substantielle (multi-tours) ne remonte son rapport** : elle est coupée à ~3 s. Workaround Main en attendant le fix : déléguer → surveiller le transcript de la cible jusqu'à inactivité (mtime stable) → récupérer le résultat sur disque/branche ou re-solliciter en léger.
## Implication pour la refonte (seam liveness/fin-de-tour, validée utilisateur)
« Retourné au prompt idle » NE peut PAS être « un turn_duration est apparu ». Le bon signal = **process vivant (`is_agent_live`) + transcript NE grossit PLUS depuis stall_after_ms** (battement de cœur). Tant que le transcript grossit ⇒ l'agent travaille ⇒ ne pas libérer. Le 1er lot du fix devrait **neutraliser ce tir prématuré**. Lié à [[rendezvous-600s-cap-too-short-heavy-tasks]], [[rendezvous-no-reply-backstop-design]], [[backstop-no-reply-live-test-failed-2026-06-23]].
title: Backstop no-reply — RE-VALIDATION LIVE VERTE (wedge levé) après fix encode_cwd
type: project
description: Le backstop no-reply du rendez-vous est validé en live le 2026-06-23 (session post-rebuild) : demandeur libéré ~grâce 2s au lieu de 600s. Reste un point de finition (résultat véhiculé via canal d'erreur).
Remplace le 1er test (ÉCHEC) du même jour. La cause racine était l'encodage du slug projet Claude (`.` non encodé) corrigé dans `crates/infrastructure/src/inspector/claude_paths.rs` (`encode_cwd` → `replace(/[^a-zA-Z0-9]/g,'-')`, double-tiret sur `/.ideai/`). Voir [[checkpoint-rendezvous-redesign-devbackend-todo]] pour la cause racine.
## Preuves live (idea.log, session a6ced819)
Scénario : DevBackend → idea_ask_agent vers QA « pong en prose, SANS idea_reply (volontaire) ».
Contraste avec l'échec précédent : `turn_ended` = 0 avant le fix, ici il FIRE ; `arm … dir=…IdeA--ideai-run-…` sur dossier EXISTANT ; grâce 2s exacte (917707→919707) ; libération du demandeur via `complete_without_reply outcome=sent`. DevBackend a repris la main en ~13 s mesuré (pas 600 s). Aucun wedge, aucun cascade-timeout.
## Point de finition résiduel (NON bloquant, follow-up)
Le résultat rendu au demandeur passe par le **canal d'erreur** MCP : « agent QA returned to its prompt without calling idea_reply (no answer was rendered); retry the request and ensure the agent replies via idea_reply ». Ce n'est pas un résultat no-reply synthétique structuré (design item c de [[rendezvous-no-reply-backstop-design]]) ; pour un orchestrateur c'est mal distinguable d'un échec transitoire « retry ». À transformer en verdict consommable (follow-up, hors blocage merge).
## Suite
Wedge prouvé levé en live ⇒ garde-fou Git satisfait. Délégué à Git : commit du fix `encode_cwd` (+ test de non-régression `encode_cwd_encodes_dot_in_run_dir_to_double_dash`) sur la branche du backstop, puis décision de merge develop.
-`cargo test --workspace` brut rouge uniquement sur tests AF_UNIX/MCP bloqués par sandbox `Operation not permitted`; workspace filtré strictement sur ces tests environnementaux OK.
## Décisions produit/techniques
- Harvest strictement best-effort: jamais bloquant pour réponse, délégation, handoff ou ticket.
- Mémoire durable seulement via directive explicite `idea-memory`, plafonnée et validée; pas de log brut, pas de live-state, pas de handoff dans ce mécanisme.
-`title` est exigé par directive mais non persisté car l'entité `Memory` n'a pas de champ titre; accepté pour E1 afin de ne pas élargir le lot.
- Les fichiers runtime `.ideai/*` générés pendant la session restent hors commits applicatifs.
## Suite probable
Prochains chantiers à cadrer par Architect avant implémentation:
Deux tentatives de délégation vers Architect ont affiché le texte dans le chat/terminal d'Architect sans le soumettre réellement.
L'utilisateur a interrompu et confirmé: « Le message n'arrive pas jusqu'à Architecte visiblement ».
Ce symptôme correspond au bug live de soumission write-portal/headless delivery corrigé dans `orchestrator-designation`, mais l'application en cours tourne encore avec l'ancien AppImage. Le nouvel artefact n'est pas chargé tant qu'IdeA n'est pas relancé avec l'AppImage 0.3.0.
## Action effectuée
Architect stoppé pour éviter un terminal avec tâche fantôme.
- Architect doit d'abord trancher le chevauchement avec `feature/agent-skills`.
-`fix/cold-start-delivery-race` est probablement obsolète/superseded par `feature/agent-skill-awareness`, mais à supprimer seulement après intégration ou décision Git finale.
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.
# Checkpoint — Finding A : fix confirmé EN CODE + AppImage clean DÉPLOYÉE → reste la validation e2e LIVE (MAJ 2026-06-23 09:33)
Suite de [[mcp-e2e-findings-reply-wedge-phantom-busy]] (le défaut) et [[resume-after-appimage-rebuild-mcp-e2e]] (méthode).
## État du code (OK, vert)
- Fix Finding A **mergé dans `develop @ 77e62e5`** (par-dessus feat `c5e5493`).
- 3 lots : **Lot 1** préambule `idea_reply` impératif (`infrastructure/src/input/mod.rs` + `application/src/agent/lifecycle.rs`) ; **Lot 2 = LE fix**`with_prompt_ready_pattern("? for shortcuts")` sur le profil Claude (`application/src/agent/catalogue.rs:80`) + overlay `overlay_reference_defaults` ; **Lot 3** backstop timeout typé `TARGET_RETURNED_NO_REPLY` / env `IDEA_ASK_RENDEZVOUS_TIMEOUT_MS` (`infrastructure/src/orchestrator/mcp/server.rs`).
- Tests lib qui PROUVENT le seed (re-passés vert le 2026-06-23) : `application::agent::catalogue::mcp_tests::claude_seed_carries_the_measured_prompt_ready_pattern` ✓ et `codex_seed_leaves_prompt_ready_pattern_empty_for_now` ✓.
## LEÇON D'OUTILLAGE (important, éviter de refaire l'erreur)
**Ne PAS utiliser `strings`/`grep` sur le binaire comme oracle de présence du fix.** Le compilateur élimine/inline les littéraux uniques de `catalogue.rs` : `? for shortcuts` ET `codex --version` sont ABSENTS du binaire alors que le code et les tests les portent (les littéraux aussi présents ailleurs — `claude --version`, `.mcp.json`, `CLAUDE.md` — n'apparaissent que par co-référence). Oracle fiable = **tests** + **comportement live**, pas le grep binaire. (Une fausse alarme « build incrémental incohérent » a été levée puis invalidée ainsi.)
- ⚠️ **L'instance IdeA qui tourne encore est l'ANCIENNE** (FUSE déjà monté). Il faut que **l'utilisateur RELANCE IdeA** pour charger le nouveau binaire — cela redémarre tous les agents, dont Main (la session courante meurt).
## PROCHAINE ÉTAPE après redémarrage : test e2e LIVE
Méthode (rappel) : **NE PAS valider le système inter-agents VIA `idea_ask_agent`**. Piloter via subagents natifs / l'utilisateur. Diag EXTERNE : `ss -xp | grep idea-mcp` (ponts) ; transcripts `~/.claude/projects/<run-dir-encodé>/*.jsonl` (horodatages UTC ; reply réel = `tool_use` idea_reply ; wedge = transcript demandeur figé sur `tool_use` idea_ask_agent sans `tool_result`).
2. ATTENDU avec le fix : si QA répond en prose sans `idea_reply`, dans la grâce `PROMPT_READY_GRACE` le retour au prompt idle (`? for shortcuts`) déclenche `complete_without_reply` → **DevBackend reçoit `TargetReturnedNoReply` (retryable) et REPREND la main** (plus de wedge) ; `idea_workstate` QA repasse `working`→`idle`/`done` (plus de Busy fantôme). Lot 1 devrait en plus pousser QA à appeler `idea_reply` (chemin positif → DevBackend reçoit pong).
3. Non-régression : chemin positif normal (QA appelle `idea_reply`) → demandeur reçoit le résultat, workstate→done ; livraison 1er tour à froid intacte.
4. Calibrer `PROMPT_READY_GRACE` si la fenêtre est trop courte/longue en réel (défaut conservé).
## Après validation verte
Merge `develop` déjà fait. Reste éventuelle release (0.4.0 ?) = décision utilisateur. NB : **Git est de nouveau strictement local, PLUS d'accès push** (la note `git-agent-push-access-gitea-ssh` a été corrigée en ce sens). `develop` est ~69 commits devant `origin/develop` (sync remote hors périmètre Git).
## Follow-ups différés (hors PR)
Capture `prompt_ready_pattern` Codex ; généralisation merge profils de référence ; e2e délégation background (node_id None) / profils MCP défaut / observabilité UI des délégations ; durcissement doc `idea_reply` dans `CLAUDE.md` versionné (router à Git).
description: T1→T6 verts (non-régression). T7 ROUGE diagnostiqué (fix sur mauvaise couche) PUIS RE-FIX implémenté dans le working tree + vert en tests unitaires. Reste : rebuild AppImage en cours → relance IdeA → re-valider T1→T7 en live → commit Git.
metadata:
type: project
---
# Checkpoint tests fonctionnels MCP — run a6ced819 (2026-06-24)
Plan via skill **mcp-rendezvous-functional-test**, Main = demandeur. Setup chauds : Main/QA/DevFrontend ; froids : Architect/DevBackend/Git.
## T1→T6 : VERTS sur AppImage 09:21 (aucune régression)
T1 visibilité, T2 chaude `pong`, T3 réveil froid Git (1er tour intact), T4 background node_id None, T5 no-reply backstop (live-state purgée), T6 multi-étapes == oracle. Détails dans l'historique. NB outillage : `pgrep -af mcp-server | grep <uuid>` = FAUX positifs (le shell de mesure se matche) → filtrer `ps -eo cmd | grep 'app-tauri mcp-server'`.
## T7 : défaut trouvé EN LIVE puis CORRIGÉ
**Défaut (T7e live, 621 s ≈ 600 s)** : cible en long tour unique > 600 s (transcript en croissance prouvée) coupée par un timeout PLAT `process error: agent session reply timed out`, pas d'extension. Le watchdog du fix précédent vivait côté requester (`server.rs`) mais le seam qui coupe vraiment est `application/src/orchestrator/service.rs` (`ASK_AGENT_TIMEOUT`=600s plat appliqué au drain de tour délégué, ~L1564). **Fix posé sur la mauvaise couche.**
**RE-FIX implémenté (subagent natif Architect→DevBackend→QA, worktree, ramené dans le main working tree ; HEAD toujours `1efe2f1`, NON committé)** :
- Découverte clé : `ClaudeSdkSession::send().await` rend TOUS les `ReplyEvent` en bloc À LA FIN du tour (rien mid-turn) → la vivacité par-événement est inutilisable ; seul signe de vie réel = **sonde transcript** (octets `.jsonl`), comme `server.rs`.
- Un seul `run_inactivity_watchdog` générique dans **`crates/application/src/orchestrator/rendezvous.rs`** (NOUVEAU, pur, testé), réutilisé par `service.rs` (le vrai seam) ET `server.rs` (copie privée supprimée → dé-duplication).
- Nouveau port `AskLivenessProbe` injecté au composition root `app-tauri/state.rs` → `transcript_activity_token` (+ `with_ask_ceiling`, env `IDEA_ASK_RENDEZVOUS_CEILING_MS`, défaut 4 h). Sans injection ⇒ fallback fenêtre plate = zéro régression.
-**Preuveréelle(re-vérifiéeparMaindansleMAINTREE)**:`cargo test -p application --lib`75/0(dontprogressing_target_extends_then_resolves,_hits_ceiling_distinctly,silent_target_expires_no_reply,no_probe_falls_back_to_flat_window,ceiling_active_code);`cargo test -p infrastructure --lib`247/0;clippy0nouveauwarning.
## PROCHAINE ÉTAPE (reprise après relance IdeA)
1.**Rebuild AppImage EN COURS**(recette:`npm --prefix frontend run build`puisdepuis`crates/app-tauri/``APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1 ../../frontend/node_modules/.bin/tauri build --bundles appimage`;artefact`target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage`→remplace`/home/anthony/Documents/IdeA_0.3.0_amd64.AppImage`,backupl'ancienne).Logbuild:scratchpad/rebuild.log.
2.**Utilisateur relance IdeA**(tueMain).
3.**Reprendre le test depuis T1**(skill).Lebutimmédiat =**re-valider T7 EN LIVE**:déléguerunechargeréelle> 600 s en un long tour (la machine est rapide : `cargo clean && cargo build --release --workspace` en boucle jusqu'à >13 min, transcript en croissance) ⇒ ATTENDU = PAS de timeout à 600 s, extension, reply final livré. Puis enchaîner T8 (parallèles), T9 (transitif A→B→C, Architect+DevBackend froids), T10 (interruption + reconcile reboot).
4.**Si live vert : demander à Git de committer** le fix (cf. [[git-owns-commit-merge-decisions]]) ; rendez-vous MCP court marche (T2-T6), donc commit délégable à Git OU subagent natif.
Lié à [[rendezvous-600s-cap-too-short-heavy-tasks]], [[mcp-functional-test-plan-2026-06-24]], [[mcp-bridge-and-delegation-runtime-notes]].
description: Run a6ced819 — relance complète T1→T6 du test fonctionnel MCP sur AppImage 08:41 (fix T5). T5 (live-state purgée sur no-reply) VALIDÉ EN LIVE. Blocage à T7 = défaut plafond 600s connu, non corrigé.
metadata:
type: project
---
# Checkpoint tests fonctionnels MCP — run a6ced819 (2026-06-24, relance depuis T1)
Binaire validé : **AppImage 2026-06-24 08:41:30** (fix live-state no-reply), process en cours = cette AppImage, git HEAD `1efe2f1`. Plan via skill **mcp-rendezvous-functional-test** (Main = demandeur). Voir [[mcp-functional-test-plan-2026-06-24]], [[rendezvous-600s-cap-too-short-heavy-tasks]], [[backstop-fires-on-intra-task-turn-rootcause]].
- **T2** cible chaude (QA) ✅ — `pong` inline borné, workstate `done`, pas de busy fantôme.
- **T3** réveil à froid (Git) ✅ — nouveau pont (26012/26016), 1er tour `[IdeA·tâche…]``parentUuid:null` intact, `delivered`, `pong-cold`.
- **T4** background node_id None (Git hors layouts.json) ✅ — `bg-step-ok`, multi-`delivered`, pont stable sans dup.
- **T5** no-reply backstop ✅ **VALIDÉ EN LIVE (le fix marche)** — Main libéré borné avec `TargetReturnedNoReply` (« returned to its prompt without calling idea_reply », retryable) ET **QA `status:"done"` purgée** (plus de busy fantôme `working`). Défaut historique #1 fermé.
- **T6** multi-étapes (QA lit fichier + `find` puis reply) ✅ — pas de libération prématurée, reply réel `header=… ; rs_count=55` (concorde avec `find` indépendant). Piège turn-watcher non déclenché.
## Blocage actuel : T7 (NON lancé — défaut connu non corrigé)
T7 = tâche lourde > plafond 600s. C'est le finding [[rendezvous-600s-cap-too-short-heavy-tasks]] : dépassement du plafond `IDEA_ASK_RENDEZVOUS_TIMEOUT_MS` (défaut 600000) → faux `-32001` + canal de report mort (idea_reply final non livrable), alors que la cible travaille et finit sur la branche. Les pistes (extension sur signe de vie / message « cible active, plafond atteint » distinct du retryable / plafond configurable / pas de cascade) sont **à cadrer Architect, pas encore implémentées**. Lancer T7 en live = figer Main ~10 min pour re-confirmer un défaut déjà documenté.
## Prochaine étape (décision utilisateur en attente)
Trois options sur T7 : (a) le lancer quand même pour re-confirmer (wedge ~10 min) ; (b) ouvrir le cycle de fix du plafond maintenant (Architect→Git→DevBackend→QA, rebuild, reprise T1) ; (c) sauter T7 et continuer T8 (parallèles), T9 (transitif A→B→C), T10 (interruption + reconcile reboot) qui ne dépendent pas du plafond. Architect/DevBackend restent froids pour T9.
M .ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/handoff.md
M .ideai/conversations/6bc594e8-a37c-0dbd-1de6-6e3b73002cb4/log.jsonl
M .ideai/layouts.json
```
Ne pas nettoyer arbitrairement. Demander à Git de décider si ces fichiers runtime doivent être commités/ignorés au prochain passage.
## Important live
L'AppImage en cours d'exécution ne charge pas automatiquement ce nouvel artefact. Pour utiliser les correctifs d'orchestration/write-portal, lancer l'artefact 0.3.0 produit ci-dessus ou remplacer l'AppImage installée hors sandbox si nécessaire.
Finir le chantier courant sur cette branche. Pas de stash, pas de switch. Après tests verts ou résidu qualifié: retour Git pour commits atomiques puis merge local vers `develop`.
## Incident live
La délégation vers Architect a été affichée dans son chat mais non soumise. Architect a été stoppé. Symptôme cohérent avec l'AppImage live qui n'intègre pas encore les correctifs WIP write-portal/headless delivery.
## Vérifications Main avant correction DevBackend
Verts:
-`cargo test -p infrastructure input --lib`: 35 passed.
-`cargo test -p application --test orchestrator_service`: 45 passed.
-`cargo test --workspace`: compile maintenant plus loin mais échoue dans `app-tauri` sur tests loopback Unix socket sous sandbox (`PermissionDenied`/`Operation not permitted` lors du bind `/run/user/1000/idea-mcp/*.sock`).
## Prochaine étape
QA doit qualifier le résidu app-tauri:
- confirmer que les suites métier/orchestration sont vertes,
- confirmer si les échecs app-tauri sont environnement/sandbox et non régression,
- proposer la commande de validation acceptable ou le besoin de correction test/env.
Cause qualifiée par QA: contrainte environnement/sandbox. Une sonde Node minimale échoue aussi à `listen()` sur Unix socket avec `EPERM` sous `/run/user/1000` et sous `/tmp`. Donc les tests qui exigent un vrai socket Unix ne sont pas exécutables dans ce sandbox.
## Prochaine étape
Retour à Git pour commits atomiques sur `feature/orchestrator-designation`, puis décision de merge local vers `develop` selon sa stratégie. Aucun push.
- Worktree dirty important mais analysé comme mono-thème: chantier orchestrateur/designation + diagnostic inter-agents.
- Ne pas stash, ne pas switch, ne pas ouvrir une nouvelle branche maintenant.
- Premier chantier à fermer: `orchestrator-designation` sur la branche actuelle.
- Après stabilisation + tests verts: retour à Git pour commits atomiques puis merge local vers `develop`.
- Ensuite seulement ouvrir des branches dédiées pour les autres chantiers.
## Incident de délégation
Main a tenté de demander à Architect de cadrer `orchestrator-designation` via `idea_ask_agent`.
L'utilisateur a interrompu et signalé qu'Architect était bloqué avec le texte de la tâche affiché dans son chat mais non envoyé.
Interprétation: symptôme cohérent avec les problèmes actuels de write portal / soumission physique PTY / délégation visible-background. Ne pas empiler de nouvelles délégations tant que cette zone n'est pas stabilisée.
## Prochaine reprise recommandée
1. Nettoyer/arrêter l'agent Architect bloqué si nécessaire.
2. Reprendre le chantier courant localement en lecture seule pour identifier exactement les changements WIP liés à la soumission de délégations.
3. Comme Main ne code pas, utiliser la délégation seulement vers un agent dont la soumission est confirmée fonctionnelle, ou lancer les agents en visible et vérifier qu'ils reçoivent réellement la tâche.
4. Priorité technique immédiate: fermer `orchestrator-designation`, car la délégation inter-agents fiable conditionne tous les autres chantiers.
# Checkpoint — Backstop no-reply : 1ère validation live ÉCHOUÉE → cause racine corrigée → RE-VALIDATION LIVE EN ATTENTE (MAJ 2026-06-23 ~19:40)
## Résultat du 1er test live (AppImage 0.3.0 du commit e87c05f) : ÉCHEC reproduisant le wedge
Scénario joué : DevBackend→QA `ping (test backstop)`, QA répond `pong` EN PROSE sans `idea_reply`.
- QA a bien émis un vrai `turn_duration durationMs=2218` (transcript, ts 17:23:40.161Z).
- MAIS `[turn-watcher] turn_ended` JAMAIS émis (`grep -c turn_ended` sur tout le log = **0**) → DevBackend resté coincé → rendez-vous Main→DevBackend a timeout 600s (MCP error -32001).
## CAUSE RACINE (prouvée) — encodage du slug projet Claude faux
Traces `[turn-watcher] arm` : toutes `conversation=<none>` + `baseline … count=0`. Le dossier transcript calculé n'existait pas :
`encode_cwd` (crates/infrastructure/src/inspector/claude_paths.rs) ne remplaçait que `/`/`\\` et gardait le `.`. Claude Code encode `replace(/[^a-zA-Z0-9]/g,'-')`. Dir introuvable → conversation non résolue → baseline 0 → `turn_duration` jamais vu → backstop mort.
Log de référence : `/home/anthony/.local/share/app.idea.ide/logs/idea.log`.
## FIX LIVRÉ par DevBackend (NON commité — Git tranche)
-`encode_cwd` : `.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })` (algo exact Claude Code). `/IdeA/.ideai/run` → `-IdeA--ideai-run`.
- Test non-régression ajouté `encode_cwd_encodes_dot_in_run_dir_to_double_dash` (PASS). Test Windows maj `C:\\Users\\me`→`C--Users-me`.
- DevBackend précise : `count_turn_ends` agrège déjà tous les `.jsonl` du dossier ⇒ pas besoin de choisir un .jsonl ; `conversation=<none>` à l'armement est normal et sans impact une fois le dir correct.
## QA PRÉ-VALIDATION : VERTE
`cargo test --workspace` = **1616 passed, 0 failed** ; `cargo clippy --workspace --all-targets` = 0 erreur. Nouveau test + 5 tests turn-watcher tous PASS.
## EN COURS : rebuild AppImage (par Main, perms project-root OK)
Recette : `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`. Logs scratchpad fe-build.log / appimage-build.log. Sortie : `/home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage`.
## SEUL RESTE À FAIRE = RE-VALIDATION LIVE (garde-fou Git : pas de merge sans wedge prouvé levé EN LIVE)
1. Utilisateur remplace l'AppImage installée par la nouvelle + relance IdeA (coupe la session).
2. Session fraîche : rejouer DevBackend→QA pong-sans-`idea_reply`. ATTENDU cette fois : trace `arm … dir=…IdeA--ideai-run-… conversation=…` sur dossier EXISTANT + baseline NON nulle + `[turn-watcher] turn_duration detected … -> turn_ended` ⇒ libération du demandeur ~grâce (≈2s) avec résultat no-reply synthétique, et non-régression du chemin propre `idea_reply`.
3. Si VERT → demander à Git d'amender/committer le fix sur `feature/rendezvous-no-reply-backstop` puis décider du merge develop. Si KO → relayer trace réelle à DevBackend.
## Trou produit confirmé (hors scope, à planifier)
Au reboot, `.ideai/live-state.json` non réconcilié : agents restent `working` fantôme (process morts). Fichier file-guardé, `idea_workstate_set` = self only, aucun reset cross-agent. Manque : réconciliation au boot OU outil orchestrateur de reset.
Correctif frontend de l'onglet Work vide terminé, validé QA (VERT) et mergé localement dans `develop`.
-`develop` @ `a66881d` — merge local `--no-ff` du fix.
- Commit de feature intégré : `3e1e553` (anciennement `9d2f186`, message amendé pour retirer le marqueur `(WIP, QA en attente)`).
- Branche `fix/work-tab-blank-page` supprimée après merge.
- Aucun push, aucune PR. `develop` en avance de 45 commits sur `origin/develop` (local-only).
## Contenu du fix
Normalisation défensive du work-state (`frontend/src/adapters/workStateNormalization.ts` nouveau) + adaptation panneau/mock/tests, pour qu'un work-state partiel/inattendu ne rende plus une page entièrement vide. 5 fichiers, +259/-47.
Warnings restants uniquement côté Vite sur options `esbuild` dépréciées/ignorées au profit de `oxc`, non bloquants.
## Décisions produit/techniques
- Actions Lot D opèrent seulement sur l'état existant; pas de lancement d'agent neuf depuis Work.
- Attach ne crée ni session ni cellule; cible déterministe = cellule visible vide côté UI.
- Stop ne supprime ni agent, ni tickets, ni handoff/conversation summary.
- View/Copy restent limités aux previews bornées Lot C; aucun log brut exposé.
## Suite probable
La quadrilogie Work A/B/C/D est intégrée. Prochains chantiers restants à cadrer: persistance conversationnelle/cross-profile plus profonde, mise à jour automatique mémoire/contexte pendant la vie des agents, ou synchronisation documentaire architecture selon priorité Architect/Main.
- Fallback: derniers tours bornés du log, jamais le log brut complet.
- Read-only et best-effort: les erreurs de preview ne bloquent pas `live/busy/tickets`.
- Pas de nouvelle persistance, pas de mutation/réparation, pas de nouvelle action UX.
## Suite probable
Le prochain lot logique est Lot D: actions UX read/write contrôlées autour du panneau Work (ouvrir/rattacher cellule, voir conversation, arrêter agent, éventuellement copier résumé), à cadrer par Architect avant implémentation.
Warnings observés uniquement côté Vite sur options `esbuild` dépréciées/ignorées au profit de `oxc`, non bloquants.
## Décisions produit/techniques
- Les tickets `human` et `agent` sont inclus dans le snapshot; l'UI distingue explicitement `Human` vs agent/requester au lieu de tout appeler délégation agent.
- Le statut est dérivé à la lecture: `inProgress` si le ticket courant correspond au `busy_state`, sinon `queued`.
- Pas de nouvelle persistance, pas de lecture `log.jsonl`/handoff, pas d'événement `agentQueueChanged`, pas d'action UX d'annulation/résolution.
## Suite probable
Le prochain lot logique du chantier UX conversations/délégations est Lot C: résumés de conversations depuis log/handoff en lecture best-effort, ou Lot D actions UX selon priorité produit. Suivre le cycle Main: Architect -> Git -> DevBackend/DevFrontend -> QA -> Git.
# Correctif accès réseau Codex via `sandbox_workspace_write.network_access`
Le 2026-07-26, le diagnostic live a montré qu'un agent Codex lancé par IdeA échouait sur `curl` même avec IP forcée. La cause n'était pas seulement DNS ni une session à relancer : Codex CLI 0.145 attend la configuration officielle `sandbox_workspace_write.network_access=true` pour autoriser le réseau dans `workspace-write`.
Correctif implémenté et validé par QA dans les sources :
- Projection Codex `$CODEX_HOME/config.toml` : gérer `[sandbox_workspace_write] network_access = true/false` via `MergeToml`, pour éviter un stale `true`.
-`codex exec` structuré : passer `-c sandbox_workspace_write.network_access=<bool>` quand la policy est connue.
- La permission système IdeA reste séparée des permissions fichiers/bash : seul `NetworkPolicy::Allow` active `network_access=true`; `Deny`, `Ask` et `None` donnent `false`.
- L'env `CODEX_SANDBOX_NETWORK_DISABLED` est encore upserté comme garde anti-héritage stale, mais ce n'est plus le mécanisme principal.
Fichiers principaux :
-`crates/infrastructure/src/permission/codex.rs`
-`crates/domain/src/ports.rs`
-`crates/application/src/agent/lifecycle.rs`
-`crates/infrastructure/src/session/codex.rs`
-`crates/application/src/ticket_assistant.rs`
Validation QA verte :
-`cargo test -p infrastructure permission::codex`
-`cargo test -p infrastructure codex_`
-`cargo test -p application --test agent_lifecycle codex_`
-`cargo test -p application --test ticket_assistant codex_`
-`cargo test -p domain`
Build réalisé :
-`npm --prefix frontend run build` : vert.
-`tauri build --bundles appimage` : compilation release OK, mais bundling linuxdeploy a échoué car `appimagetool` tentait de télécharger le runtime sans réseau.
Validation live restante obligatoire : relancer IdeA depuis cette nouvelle AppImage, lancer un agent Codex frais avec `network: allow`, puis exécuter un vrai `curl`. La session Codex déjà active ne peut pas prouver le fix car elle a été lancée avant ces nouveaux arguments/config.
- **#167 (ordre événements avant Final) : CLOSED** — mergé dans develop, QA verte (backend `codex_send_with_tap...ok` + frontend 38/38 confirmés sur develop).
- **#155 (clipboard paste) : OUVERT** — partiel. Logique+tests verts, mais e2e presse-papiers natif Tauri/AppImage non validable dans l'env. Code NON mergé dans develop.
## Commits
-`3d4ea685` chore(tickets): rouverture #151#155 + création #167 (sur develop, début cycle)
-`develop` : intègre #151#167 (pas #155). En avance sur origin/develop, **pas de push**.
-`fix/cycle-151-167-155-custom-cli` : ramenée à C1 (b45deabe), mergée dans develop.
-`wip/155-clipboard-paste-e2e` : préserve C1+C2 (388de4b8) → porte le code #155 pour le cycle de clôture e2e AppImage.
## Point de clôture #155
Valider le collage presse-papiers image/fichier sur l'AppImage réelle. Reprendre depuis `wip/155-clipboard-paste-e2e`.
## Notes
-`reset --hard` a révoqué hors-cycle les modifs miroir disque non committées (carnets QA, `.ideai/idea-android-plugin.json`) ; le store IdeA reste source de vérité, miroirs resyncés à la clôture. La modif 1-ligne `idea-android-plugin.json` (propriétaire inconnu) a été perdue du working tree — à refaire si besoin.
- Le toolchain Rust du shell n'a pas de default ; backend confirmé via cargo nightly sous `.ideai/run/<agent>/.opencode/.rustup`.
description: F35.2 (config serveur local + sélection dans les profils OpenCode) livrée et verte sur feature/modeles-locaux ; clôt le sprint modèles locaux F34-F36.
metadata:
type: project
---
Ticket #35 F35.2 LIVRÉ (vert) sur `feature/modeles-locaux`. Débloqué par le contrat PLAT d'Architect.
**Contrat backend** : `list_model_servers` / `save_model_server({request:{config}})` / `delete_model_server({serverId})`. DTO plat camelCase `LocalModelServerConfig { id, kind:"llamaCpp", name, baseURL, port, modelPath?, servedModelName, binaryPath?, args, autoStart, stopPolicy }`. PIÈGE intégré : `save_model_server` exige un UUID valide dans `config.id` même à la création (backend ne génère PAS l'id serveur, seulement le model.id interne caché) → le front minte l'UUID client-side (`newModelServerId`). Erreur suppression référencée = `code:"model_server_in_use"`.
**Livré** : `ModelServerGateway` (port + adapter Tauri `adapters/modelServer.ts` + `MockModelServerGateway` avec `markInUse`). Feature `features/model-servers/` : `useModelServers` (CRUD, traduit in-use en message FR actionnable), `ModelServersPanel` (déclarer/éditer/supprimer), `ModelServerSelect` (dropdown binding avec « None » = serveur externe + préserve un id orphelin « unknown/removed »). Dans `FirstRunWizard`, le champ texte `localModelServerId` (F36) est remplacé par `ModelServerSelect`.
Cohérent avec F35.1 (badge corrèle via localModelServerId). tsc 0 ; suite 608/608. Sprint modèles locaux (F34 backend, F35, F36) désormais complet côté frontend.
description: Multiplicité des profils OpenCode locaux côté UI first-run wizard — livrée, verte, sans gap backend.
metadata:
type: project
---
Ticket #36 (F36.1) livré côté frontend sur `feature/modeles-locaux`.
**Constat clé** : aucun gap backend. Le backend supporte déjà N profils OpenCode (identité = ProfileId) via `clone_opencode_profile_from_seed`, `list_profiles`, `save_profile`, `delete_profile`, `configure_profiles`. `OpenCodeConfig` DTO expose `localModelServerId` (camelCase).
**Ce qui manquait, purement front** : la commande clone n'était câblée dans aucun gateway, et le miroir TS `OpenCodeConfig` n'avait pas `localModelServerId`.
**Livré** : `ProfileGateway.cloneOpenCodeProfileFromSeed` (port + adapter Tauri + mock) ; le first-run wizard (`FirstRunWizard.tsx` + `useFirstRun.ts`) gère désormais une LISTE de profils OpenCode — bouton "Add OpenCode profile" + "Duplicate" par ligne, name éditable, cases reasoning/attachment, champ localModelServerId (saisie simple).
**Reste** : lot F35 = UI riche de config serveur local (remplacera la saisie simple de `localModelServerId`). Le wizard reste la surface de gestion de profils (`Settings ▸ Configure profiles` = `forceOpen`) ; il lit `firstRunState().referenceProfiles`, pas `list_profiles` — à vérifier au lot F35 pour l'édition de profils OpenCode déjà persistés.
description: Le focus-trap de FloatingWindow ne doit dépendre que du mount ; sinon un onClose instable vole le focus à chaque frappe.
metadata:
type: reference
---
Le primitif `frontend/src/shared/ui/FloatingWindow.tsx` a un `useEffect` focus-trap qui appelle `(first ?? node)?.focus()`. Sa dépendance doit rester `[]` (mount-only), et `onClose` doit être lu via un `onCloseRef` maintenu à jour.
**Pourquoi** : les appelants (ex. `TicketDetail.requestClose`) passent souvent un `onClose` recréé à chaque render. Si l'effet dépend de `[onClose]`, chaque frappe dans un input contrôlé de la fenêtre → re-render → nouvelle identité `onClose` → l'effet se ré-arme → focus renvoyé au premier focusable → l'utilisateur ne peut taper qu'UNE lettre à la fois (bug #17, corrigé 2026-07-07).
**Comment l'appliquer** : ne jamais remettre `onClose` (ni une autre prop changeante) dans les deps de cet effet ; router les callbacks via une ref. Régression couverte par le test « #17 focus-loss bug » dans FloatingWindow.test.tsx. Voir [[ticket16-menus-floating-delivered-devfrontend]].
Le dossier `frontend/` s'installe et se build avec **npm**, jamais pnpm (lockfile `package-lock.json`).
**Ne jamais lancer `pnpm` (ni `corepack pnpm build`, ni le wrapper `pnpm --dir frontend build`) dans `frontend/`** :
-`pnpm --dir frontend build` échoue d'abord sur un pré-check `pnpm install` (`ERR_PNPM_IGNORED_BUILDS` sur esbuild).
- Surtout, `pnpm install`**clobber** le `node_modules` npm : pnpm aplatit `vite@5.4.21` au top-level alors que `vitest@4.1.x` exige `vite ^6||^7||^8`. Résultat : `ERR_PACKAGE_PATH_NOT_EXPORTED: './module-runner'` dès que vitest spawn son worker pool (>3 fichiers de test). Avec npm, `vite@8` est imbriqué sous `node_modules/vitest/node_modules/vite`, donc tout marche.
- Réparation si le clobber arrive : `rm -f frontend/pnpm-lock.yaml frontend/pnpm-workspace.yaml && cd frontend && npm install`, puis `git checkout -- frontend/package-lock.json`.
Commandes correctes sans le wrapper cassé : `cd frontend && npx tsc --noEmit && npx vite build` (build) et `npx vitest run` (tests). Le script `pnpm build` du package.json ne doit pas être utilisé tel quel dans cet environnement.
Stratégie de distribution IdeA validée utilisateur (2026-07-16, suite #13) : DEUX offres au-dessus du MÊME cœur backend (pas de fork métier).
**Offre 1 — Full desktop** : binaire Tauri `app-tauri` actuel. AppImage Linux (existe, inchangée). Windows explicitement REPORTÉ (garder la portabilité à l'esprit, ne rien introduire de non-portable ; PTY = ConPTY à retester le jour venu). L'AppImage reste desktop PUR — elle ne bundle pas les assets web.
**Offre 2 — Serveur/client** : image Docker, bâtie sur un binaire serveur HEADLESS `idea-serve` SANS dépendance Tauri/WebKit (pas de dockerisation du binaire Tauri qui traînerait GTK/WebKit pour rien). Cadré faisable en hexagonal par Architect.
**Point pivot technique** : `server.rs` vit dans `crates/app-tauri` et réutilise indirectement la présentation Tauri via `crate::state::AppState`, `crate::dto::*`, `crate::events::DomainEventDto`, `crate::pty::PtyChunk`, `crate::mcp_endpoint::*`, `ResumeContext`. Le protocole HTTP/WS lui est déjà autonome. Le vrai travail = découpler les DTO (→ crate partagé `presentation-dto`/`backend-api`) puis extraire `crates/web-server` (sur `BackendCore`, pas `AppState`) puis le bin `idea-serve`.
**Topologie de branches (décision utilisateur)** : chantier packaging sur `feature/server-client-packaging` (créée depuis `c246875`, tête de `feature/ticket13-pty-websocket`). PAS de merge dans develop tant que l'utilisateur n'a pas validé lui-même la NON-RÉGRESSION desktop-only. Flux final : `feature/server-client-packaging` → `feature/ticket13-pty-websocket` → `develop`. Git rebase la branche packaging si #13 avance.
Invariants : desktop AppImage ne perd RIEN ; aucun import Tauri dans le bin headless ; même cœur/use cases/stores ; contrat HTTP/WS inchangé sauf lot versionné. Voir [[frontend-uses-npm-not-pnpm]], [[appimage-build-no-strip-relr-dyn-fix]].
description: Cadrage figé (Architect+Main, 2026-07-02) du système de tickets IdeA façon Jira — domaine `Issue` par projet, exposé « ticket » côté UI/MCP, Carnet éditable, #N séquentiel. Fait autorité pour les lots T1-T6/F1-F7.
metadata:
type: reference
---
# Système de tickets IdeA (domaine `Issue`) — CADRAGE FIGÉ 2026-07-02
## Décisions produit verrouillées (utilisateur)
- **Portée PAR PROJET** : stockage dans `.ideai/tickets/` du projet (pas de backlog global).
- **Identifiant court `#42`** : compteur séquentiel par projet, parlable à l'oral (poignée
partagée humain↔agent, y compris dans une délégation `idea_ask_agent`).
- **Champ de connaissances éditable = « Carnet »** (PAS « mémoire » — évite collision avec la
mémoire projet). Markdown éditable/réorganisable, scoped ticket, pas append-only.
- **Stockage Markdown + frontmatter**, un DOSSIER par ticket.
- **V1 SANS attachments** : livrer T1-T5 + F1-F5 + F7 ; attachments (T6/F6) en fast-follow.
## Nommage (collision évitée avec le TicketId de délégation)
description: Findings validation MCP e2e live (AppImage 0.3.0) — transport/rendez-vous SAINS ; un seul vrai défaut = wedge si l'agent délégué n'appelle pas idea_reply (pas de timeout serveur).
Contexte : validation réelle du parcours inter-agents (cf. [[resume-after-appimage-rebuild-mcp-e2e]]). Méthode = Main observe en externe (Bash sur `ss -xp` + transcripts `~/.claude/projects/<run-dir-encodé>/*.jsonl`), l'utilisateur déclenche dans l'UI ; on ne valide PAS via `idea_ask_agent`.
## Scénario 1 — délégation vers agent FROID + cycle complet : VERDICT
- ✅ **Réveil à froid** : déléguer vers un agent arrêté relance son pont (`mcp-server --requester <id>` réapparaît, socket ESTAB recompte).
- ✅ **1er tour non perdu** : tâche livrée intacte = 1er `user``[IdeA · tâche de <agent> · ticket <uuid>] …`.
- ✅ **Chemin positif `idea_reply`** : quand l'agent cible appelle `idea_reply(ticket,result)`, le bridge répond `reply ... delivered`, le **demandeur reçoit le `tool_result` et reprend la main** (vérifié : DevBackend reçoit `pong` puis continue).
- ✅ **Purge live-state** : dès l'`idea_reply`, `idea_workstate` repasse le cible de `working` → `done`, intent vidé.
➡️ **Transport + protocole de rendez-vous = SAINS.** Bugs historiques 3/4/5 OK sur ce chemin.
## LE défaut restant (racine unique) — wedge sans `idea_reply`
Si l'agent délégué **termine son tour sans appeler `idea_reply`** (ex. tâche triviale « réponds pong » → il répond `pong` en **prose** et s'arrête) :
- le **demandeur wedge indéfiniment** dans `idea_ask_agent` (transcript demandeur figé sur le `tool_use` idea_ask_agent, aucun `tool_result`) ;
- la **cible reste `working`** sur le ticket périmé dans la live-state ;
- **aucun timeout ni récupération** côté serveur. Une interruption utilisateur du `ask` libère le demandeur mais **ne purge pas** le `working` de la cible (purge seulement à l'`idea_reply`).
➡️ Le « Busy fantôme » n'est PAS un bug d'état indépendant : c'est la **conséquence** de l'absence d'`idea_reply`. Une seule cause à traiter.
### Pistes correctif (à arbitrer Architect)
1.**Durcir contextes agents** : « toute tâche déléguée se termine IMPÉRATIVEMENT par `idea_reply`, même triviale » (cheap, attaque la cause comportementale).
2.**Garde-fou serveur (rendez-vous)** : timeout sur l'attente `idea_ask_agent` → renvoyer au demandeur un résultat d'échec/relance explicite + purger le `working` de la cible (idempotent). Touche le contrat rendez-vous + cycle de vie live-state = domaine Architect. Voir [[idea-program-surface-separation-and-livestate]], [[mcp-bridge-and-delegation-runtime-notes]].
- Reply réel = `tool_use``idea_reply` dans le transcript cible → tool_result `reply ... delivered` (⚠️ ne pas confondre avec les mentions `idea_reply` du CLAUDE.md injecté via attachments).
- Wedge demandeur = transcript demandeur s'arrête sur `tool_use``idea_ask_agent` sans `tool_result`.
- ⚠️ Horodatages transcripts = **UTC** ; `ls %H:%M:%S` masque la date → comparer avec `date` + mtime réel (`ls -t`).
## Restant à tester (scénarios e2e suivants)
- Délégation vers agent BACKGROUND (node_id None) — même protocole, vérifier pas de 1er tour perdu.
-`ask` sans réponse ne wedge pas durablement la CONNEXION du demandeur (vs juste l'appel) — Bug 5 côté transport.
description: Plan de test FONCTIONNEL du rendez-vous inter-agents MCP (trivial→tordu), à exécuter après rebuild develop HEAD ; méthode, ordre, setup IdeA et critères de réussite.
metadata:
type: project
---
# Plan de test fonctionnel MCP inter-agents (2026-06-24, run a6ced819)
Objectif : état des lieux par tests **fonctionnels** (pas unitaires) du rendez-vous `idea_ask_agent` ⇄ `idea_reply`, du trivial au plus tordu. On lance dans l'ordre, on **s'arrête au 1er blocage**, on corrige (cycle Architect→Git→Dev→QA), rebuild+relance, puis on **reprend de T1**. Contexte/historique : [[mcp-e2e-findings-reply-wedge-phantom-busy]], [[backstop-fires-on-intra-task-turn-rootcause]], [[rendezvous-no-reply-backstop-design]], [[rendezvous-600s-cap-too-short-heavy-tasks]], [[reconcile-live-state-implemented-feature-branch]].
## Contexte binaire au moment du plan
- AppImage qui tournait = build **23/06 19:42**, PÉRIMÉ (antérieur à `c6f0f86` encode_cwd, `744de20` backstop no-reply, `886bb0d`/`1efe2f1` reconcile). Décision utilisateur : **rebuild develop HEAD `1efe2f1` puis relance** avant de tester.
- Règle structurelle : **binaire qui tourne = AppImage**. Tout fix backend ⇒ rebuild + relance d'IdeA (tue Main). Les tests s'enchaînent SANS relance *à l'intérieur d'une même version* ; chaque cycle de fix = 1 relance.
## Méthode
- **Main est le demandeur** (`idea_ask_agent`) = test fidèle de « si je donne un message à un agent, il me répond ».
- Risque résiduel : un wedge non borné fige le tour de Main → l'utilisateur peut interrompre.
## Setup IdeA à mettre en place avant T1 (consigne utilisateur)
1. Relancer IdeA, ouvrir le projet IdeA, **Main visible** (le demandeur).
2. Garder **2–3 cellules libres visibles** pour qu'on observe à l'œil les délégations (Main y placera des cibles visibles, ou lance-toi QA + DevFrontend dans des cellules visibles).
3.**Laisser Architect, DevBackend, Git ARRÊTÉS (froids)** : T3 testera le réveil à froid ; T9 utilisera Architect+DevBackend froids.
4. Cibles chaudes pour T1/T2 : Main les réchauffe lui-même (launch) — pas d'action requise.
## Ordre des tests (trivial → tordu)
- **T1 — Visibilité outils MCP.** Un agent lancé par IdeA voit ses outils sans action manuelle. Vérif : pont ESTAB (`ss`) + 2 process mcp-server par requester.
- **T3 — Réveil à FROID + idea_reply.** Cible arrêtée. Attendu : pont relancé (nouveau mcp-server), **1er tour non perdu** (1er user `[IdeA · tâche…]` intact), reply remonte.
- **T4 — Cible BACKGROUND (node_id None).** Délégation vers agent sans cellule visible. Attendu : même protocole, pas de 1er tour perdu (historiquement non testé).
- **T5 — NO-REPLY (cible répond en prose, pas d'idea_reply).** Attendu backstop : demandeur libéré en temps borné avec `TargetReturnedNoReply` (retryable), **pas de Busy fantôme** (workstate purgé). DÉFAUT historique #1.
- **T6 — Délégation MULTI-ÉTAPES puis idea_reply.** Cible lit plusieurs fichiers / lance une cmd PUIS idea_reply. Attendu : **pas de libération prématurée** pendant le travail, rapport final livré. PIÈGE turn-watcher (cause racine : backstop tirait au 1er tour interne ~3 s).
- **T7 — Tâche LOURDE > plafond** (impl + `cargo test`/clippy). Attendu : extension sur signe de vie OU message clair « cible active, plafond atteint » (≠ faux `-32001`) ; idea_reply tardif non perdu. Plafond `IDEA_ASK_RENDEZVOUS_TIMEOUT_MS` (défaut 600000).
- **T9 — TRANSITIVE A→B→C** (Main→Architect, Architect délègue à DevBackend puis reply à Main). Attendu : rendez-vous imbriqués, 2 attentes simultanées sans deadlock. Cas le plus tordu.
- **T10 — INTERRUPTION/annulation + reconcile reboot.** (a) Main interrompt un ask en vol → `working` cible purgé, pas de cascade, idea_reply tardif non-livrable proprement. (b) Au reboot, orphelins status∈{working,waiting,blocked} & session morte → `idle` + marker `STALE_AT_RESTART_MARKER` (use case ReconcileLiveState, cf. [[reconcile-live-state-implemented-feature-branch]]).
## Critère de fin
Tous les T1→T10 verts avec preuve réelle (réponse inline + observation externe cohérente). Sinon : 1er rouge = blocage à corriger, puis reprise de T1.
title: MCP tests fonctionnels T1→T9 VERTS en live (2026-06-24, AppImage 12:53 HEAD 1efe2f1 + fix T7 non committé)
type: project
description: Passe complète T1→T9 verte en conditions réelles sur AppImage du 24/06 12:53 ; T5 (backstop no-reply) et T7 (watchdog inactivité >600s) validés live ; reste T10 (interruption + reconcile reboot) qui exige action utilisateur.
---
# Tests fonctionnels MCP rendez-vous — T1→T9 VERTS live (2026-06-24)
Run a6ced819, plan [[mcp-functional-test-plan-2026-06-24]]. AppImage **rebuild 12:53 depuis l'arbre de travail** (HEAD `1efe2f1` + le fix T7 watchdog **non committé** mais présent dans les 11 fichiers modifiés du git status → embarqué dans le binaire). Méthode : Main demandeur + observation externe (ss/pgrep/transcripts UTC/workstate).
- **T5 NO-REPLY backstop** ✅ (défaut historique #1 résolu) : cible répond en prose sans idea_reply → demandeur libéré en ~20 s avec erreur retryable « returned to its prompt without calling idea_reply », workstate cible done (PAS de Busy fantôme).
- **T6 multi-étapes** ✅ (piège turn-watcher résolu) : DevFrontend lit fichiers + git log PUIS idea_reply ; pas de libération prématurée ; données réelles (142 fichiers, hashes corrects).
- **T7 tâche LOURDE >600s** ✅ (défaut historique #2 résolu, validation live du fix watchdog) : QA charge réelle continue 11:00:46→11:12:44 UTC = **~11m58s**, cargo clean+build release+test (1632 passed/0)+clippy+9 relances ; rendez-vous **NON expiré à 600s**, idea_reply accepté au-delà du plafond (aucun « no pending request »), reply delivered 11:13:21.
- **T8 PARALLÈLE** ✅ : 2 asks même tour (DevFrontend `alpha-8` + Git `beta-8`), réponses distinctes, multiplexage pont OK.
- **T10a interruption** : Main interrompt un ask en vol → attendu : workstate cible purgé, pas de cascade, idea_reply tardif proprement non-livrable. NÉCESSITE que l'utilisateur appuie sur interrupt pendant un ask wedgé.
- **T10b reconcile reboot** : orphelin status∈{working,waiting,blocked} + session morte → au reboot, use case ReconcileLiveState le passe à idle + `STALE_AT_RESTART_MARKER` (cf. [[reconcile-live-state-implemented-feature-branch]]). NÉCESSITE un reboot d'IdeA (ferme Main).
## Suite recommandée
1. Faire committer le fix T7 par Git (encore non committé, cf. [[git-owns-commit-merge-decisions]] + [[rendezvous-600s-cap-too-short-heavy-tasks]]).
2. Exécuter T10 avec l'utilisateur (interrupt + reboot).
title: T10a — l'interrupt harness du demandeur n'annule PAS le rendez-vous backend (reply tardif accepté dans le vide)
type: reference
description: Finding T10a (2026-06-24) — interrompre Main (demandeur) au niveau Claude Code ne propage aucune annulation à IdeA ; la cible finit, son idea_reply est « delivered » dans un demandeur abandonné, et la live-state du demandeur reste working.
---
# Finding T10a (2026-06-24, run a6ced819)
## Setup
Main → `idea_ask_agent(DevFrontend, "travaille ~90s puis idea_reply late-10a")`. Pendant l'attente, l'utilisateur **interrompt Main** (Échap harness Claude Code). Le tool use `idea_ask_agent` est rejeté côté harness.
## Observé
- DevFrontend a fini ses ~95 s de travail réel (11:20:39→11:22:14 UTC) puis `idea_reply("late-10a")` → **ACCEPTÉ « delivered »** à 11:22:18 (PAS « no pending request »).
- DevFrontend workstate = **done** (pas d'orphelin côté cible). Pas de cascade.
- **Main (demandeur) workstate resté `working`** : l'interrupt a coupé le tour de Main avant son nettoyage ; le chemin interrupt ne réconcilie pas la live-state du demandeur.
## Interprétation
L'interrupt Claude Code annule le **tool call local** mais le **process Main survit** → la socket MCP (mcp-server du demandeur) reste connectée → IdeA ne reçoit aucun signal d'annulation → le rendez-vous reste pending → le reply tardif de la cible est livré dans un demandeur qui a déjà abandonné (perdu pour Main mais rapporté « delivered »).
## Conséquence
- Le T10a du plan (« annulation en vol → reply tardif non-livrable proprement ») **n'est pas exerçable via interrupt harness** tant que le process demandeur survit. Une vraie annulation exigerait : tuer le process demandeur (drop socket) OU un cancel protocolaire JSON-RPC.
- Comportement néanmoins bénin : pas de wedge, pas de cascade, cible réconciliée (done). Seuls écarts : (a) reply tardif « delivered » au lieu de rejeté ; (b) live-state du demandeur laissée working par le chemin interrupt (mais reconcile-au-reboot la rattraperait, cf. [[reconcile-live-state-implemented-feature-branch]]).
Lié à [[mcp-functional-tests-t1-t9-green-live-2026-06-24]], [[mcp-functional-test-plan-2026-06-24]].
description: T10b validé live après reboot. ReconcileLiveState ramène l'orphelin Main working→idle en conservant l'intent et purgeant ticket/lastDelegation. Couvre l'orchestrateur. Reste : faire committer par Git les fix non committés (T7 watchdog + reconcile).
---
# T10b — PASS (reboot reconcile vérifié)
## Verdict
Orphelin armé = Main `working` intent "T10b-ORPHAN-MAIN". Après reboot (run a6ced819), `idea_workstate_read` montre Main :
-`status: idle` ✅ (était working)
-`intent` conservé ✅
-`ticket` purgé ✅ / `lastDelegation` purgé ✅
Signature `STALE_AT_RESTART_MARKER` complète. **ReconcileLiveState valide ET inclut l'orchestrateur** (Main n'a PAS été exclu malgré la crainte du design open_project). Champ `progress` non exposé par workstate_read mais transition working→idle+intent retenu = concluante.
## Suite du plan
T1→T10 du plan [[mcp-functional-test-plan-2026-06-24]] terminés (T1→T9 verts cf. [[mcp-functional-tests-t1-t9-green-live-2026-06-24]], T10a finding [[mcp-t10a-harness-interrupt-does-not-cancel-rendezvous]], T10b PASS ici).
**RESTE : faire committer par Git les fix non committés (T7 backstop/watchdog + reconcile live-state).** cf. [[git-owns-commit-merge-decisions]].
title: MCP T7b/T7e — backstop 600s rejette le reply tardif et laisse le live-state worker non réconcilié
type: reference
description: Finding fonctionnel des tests MCP T7b/T7e (2026-06-24) — un idea_reply après dépassement de la fenêtre 600s est refusé « no pending request », et le ticket du worker reste working dans le live-state.
---
# Finding — tests MCP T7 (2026-06-24, QA)
## Contexte
Série de tests du rendez-vous `idea_ask_agent ⇄ idea_reply` et du backstop no-reply (cap ~600s), demandés par Main :
-**T7e**(releasebuildrépété,avant-plan,sanssleepnibg):7cycles,cumulréel**08:01:14 → 08:14:21 = 13 min 07 s**,franchitlargement600s.
## Défaut observé
ÀlafindeT7e(etdeT7b,terminéaumêmetour,après>600s d'occupation continue), les DEUX `idea_reply` (tickets `4613262f…` T7b et `d2398b22…` T7e) sont refusés :
```
invalid input: no pending request to reply to for agent aefdbd61-…
```
→ Le backstop a libéré le **demandeur** (Main) côté rendez-vous après dépassement du cap, ce qui est le comportement voulu. MAIS :
1. Le **résultat réel** (builds OK, horodatages non falsifiés) ne peut **plus être délivré** : il est perdu pour le demandeur.
2.`idea_workstate_read` montre encore le worker QA `status=working ticket=d2398b22…`**après** le rejet du reply → le live-state du **worker** n'est PAS réconcilié quand le backstop ferme le rendez-vous côté demandeur. Incohérence demandeur (libéré) vs worker (toujours working).
## Conséquence / piste
- Le cap 600s du rendez-vous reste trop court pour des tâches lourdes légitimes (cf. [[rendezvous-600s-cap-too-short-heavy-tasks]]) : ici un travail réel et honnête >13 min est « jeté ».
- À corriger : quand le backstop ferme un rendez-vous, soit (a) accepter encore un reply tardif (le router vers le demandeur même après timeout / le persister), soit (b) réconcilier le live-state du worker (passer son ticket à done/abandon) pour ne pas laisser un `working` fantôme. Voir [[backstop-fires-on-intra-task-turn-rootcause]] et [[rendezvous-no-reply-backstop-design]].
## Reproductible
Charge avant-plan réelle (`cargo build --release --workspace` en boucle clean) dépassant 600s, puis tenter `idea_reply` sur le ticket d'origine → rejet systématique.
description: Frontières hexagonales, ports, DTO, fallback et matrice de compatibilité versionnée pour l'évolution du catalogue de modèles des profils structurés Codex/Claude.
metadata:
type: reference
---
Évolution de `ListClaudeModels`/`ListCodexModels` (catalogue statique `application/src/agent/model_catalogue.rs`) vers un catalogue enrichi par compatibilité CLI.
**Décisions tranchées :**
- JAMAIS scraper les TUI `/model`, JAMAIS exécuter les CLIs pour énumérer les modèles. Seule exécution CLI autorisée : `<cli> --version` (pattern existant `infrastructure/runtime::detection_spec` + port `ProcessSpawner`). Saisie libre toujours ouverte.
- 3 sources non bloquantes à dégradation indépendante : API provider `/v1/models` (best-effort, uniquement si clé env/SecretStore présente, sinon skip), catalogue statique seed, matrice de compat.
**Hexagonal :**
- Domaine (pur) : VO `CliVersion` (Ord), enum `ModelCompatibility {Compatible|Unknown|LikelyTooRecent}` (miroir des 3 états produit), VO `CompatibilityMatrix` (forme seule), fonction pure `evaluate_compatibility(matrix, adapter, model_id, Option<CliVersion>)` — version None ⇒ Unknown, modèle absent ⇒ Unknown, min<=ver ⇒ Compatible, min>ver ⇒ LikelyTooRecent.
- Nouveaux ports : `CliVersionReader`, `ProviderModelCatalogue` (Ok(vec![]) si pas de clé), `CompatibilityMatrixSource` (infaillible).
- Application : use case unique `ResolveModelCatalogue{adapter}` async, jamais de hard-error sur échec source (warnings + fallback). `ListClaude/CodexModels` deviennent des façades.
- Infra : `ProcessCliVersionReader`, `HttpProviderModelCatalogue` (reqwest), `EmbeddedCompatibilityMatrix`.
**Matrice de compat = DONNÉE, pas code** : JSON versionné maintenu dans IdeA, bundlé via `include_str!` (seed infaillible) + override optionnel `app_data_dir/IdeA/model-compat.json`. Ajouter un modèle = éditer le JSON, zéro code (Open/Closed).
**DTO (rupture front)** : `ProfileModelCatalogDto` passe de `transparent Vec` à `{ models:[{...,compatibility,source}], cliVersion:string|null, warnings:string[] }`. Répercuter ports TS + 2 adapters + mock + ProfilesSettings.
**Découpage** : B1 domaine pur, B2 use case ports mockés, B3 infra ; F1 contrat, F2 ProfilesSettings 3 badges. UX passe avant F2 (libellés des 3 états, warnings, cliVersion null).
**Point ouvert produit** : récup clé provider — proposé best-effort sur clé env/SecretStore existante, pas de prompt dédié.
title: OpenCode local provider — llama.cpp remplace Ollama (chantier livré)
type: decision
description: Le provider local d'OpenCode passe d'Ollama à llama.cpp (llama-server OpenAI-compatible). Profil éditable baseURL/apiKey/model. Livré sur develop (merge 2e98f1f). Clôt le diagnostic tool-calling Ollama.
---
# OpenCode local : llama.cpp remplace Ollama (2026-07-11)
## Pourquoi
Le tool-calling des modèles locaux via **Ollama** ne s'est jamais déclenché nativement en live : OpenCode fuyait les appels d'outils en texte `<function=...>` (émulation par prompt), jamais parsés. Diagnostic utilisateur : la faute est dans la relation **OpenCode↔Ollama**. Décision : abandonner Ollama comme provider OpenCode et passer à **llama.cpp** (`llama-server`, endpoint OpenAI-compatible). Voir historique : [[opencode-config-model-agnostic-tool-diagnostic]], [[opencode-ollama-native-tool-call-flag]], [[opencode-ollama-tool-calling-surface-overload]] (contexte Ollama, désormais superseded).
## Contrat livré
`OpenCodeConfig` (domaine + DTO app-tauri + type frontend) :
`opencode_config_json` (crates/application/src/agent/lifecycle.rs) régénère un opencode.json MINIMAL : provider `llamacpp` (`npm=@ai-sdk/openai-compatible`, `options.baseURL/apiKey` du profil, def modèle `llamacpp/<model>` avec `tool_call:true,reasoning,attachment`), MCP `idea` inchangé, `permission bash/edit=ask`, `disabled_providers=["anthropic","openai","gemini","ollama"]`. `apiKey` omis (pas `null`) si absent. Plus aucun résidu `ollama`/préfixe `ollama/`/allow-list diagnostic.
Frontend : wizard profil OpenCode = 3 champs éditables **Base URL / Model / API key(optionnel)** (`FirstRunWizard.tsx`, `profile.ts`, mocks). L'embedder Ollama (`ollamaDetected`, mémoire vectorielle) est un AUTRE usage, NON touché.
- QA VERT : domain 244, application 81+64, infra 263/10 (les 10 = bind-port sandbox, identiques sur develop = non-régression), frontend 574, tsc propre. Claude/Codex non régressés.
- Git : commit `4e70631`, merge `--no-ff``2e98f1f` sur `develop`. Base branche = `eaba05d` (profil OpenCode process-backed). WIP diagnostic Ollama capsulé dans `3cdedf2` sur `feature/opencode-glm47-flash-tool-diagnostic`. PAS de push distant.
E2E live llama.cpp jamais exécuté (aucun `llama-server` joignable sur :8080 au moment QA ; `llama-server` 9859 + `opencode` 1.17.18 installés). À valider côté utilisateur : lancer llama-server, relancer l'Appointe rebuild, créer un agent profil llama.cpp, vérifier un vrai `tool_use` MCP (pas de `<function=` texte). Rebuild AppImage requis pour le live (binaire qui tourne = AppImage). Build via `NO_STRIP=true` cf [[appimage-build-no-strip-relr-dyn-fix]].
# Contrats plugins #133/#138 : service d'assets multi-fichiers + persistance plugin-owned hors projet
Décisions figées dans `ARCHITECTURE.md` §22 (2026-08-02).
## #133 — service des assets `idea-plugin://`
`asset_allowed` (`crates/app-tauri/src/plugins.rs:504-536`) doit servir tout chemin relatif confiné dès lors que les gardes déjà présentes tiennent : registre actif (`lifecycle_state.is_runtime_active()`) + `content_hash` du package matché + confinement canonicalize (déjà en place lignes 467-483). Fin de l'allowlist `declared_main || declared_icon || starts_with("assets/")` qui cassait tout import ESM relatif secondaire (`./constants.js`, `./core/x.js`) → "Importing a module script failed.". Pas de résolution `node_modules`/bare specifiers — hors scope, figé. Débloque #134 (implémentation) et #135 (audit confinement install + désinstallation 100%).
## #138 — persistance plugin-owned
`ctx.storage` (déjà typé dans `sdk/IdeaSDK/src/runtime.ts`, jamais câblé côté `frontend/src/plugins/runtime/loader.ts` ni implémenté côté Rust — vérifié : zéro port/commande/répertoire) devient l'API canonique unique pour l'état interne du plugin (prefs/cache/index). Nouveau répertoire `app_data/plugins/data/<pluginId>/`, frère de `plugins/installed/<pluginId>/` (jamais dedans, pour survivre aux réinstalls et donner une racine univoque à purger). `plugin_uninstall` doit purger les deux répertoires. `ctx.services.config`/`workspace` restent réservés au project-owned (fichiers réels du projet, jamais l'état interne du plugin). L'exemple `hello-plugin` doit migrer ses compteurs internes de `.ideai/hello-plugin.json` vers `ctx.storage`. Débloque #139.
Détail complet et rationale : `ARCHITECTURE.md` §22. Tickets liés : #133/#134/#135 (assets), #138/#139 (storage). Les deux tickets #133/#138 sont passés en `QA` avec carnet détaillé.
title: Réconciliation du live-state au reboot — implémentée (feature/reconcile-live-state)
type: project
description: Le chantier ReconcileLiveState (3 lots) est implémenté et vert (1623 tests), non committé ; détail des fichiers et de l'arbitrage project_id/provider.
---
Implémenté le 2026-06-23 sur `feature/reconcile-live-state` (ticket Main a7fcb5e4, dont le rendez-vous `idea_reply` a expiré pendant le build — résultat consigné ici). **Non committé** (Git tranche). Cadrage : [[live-state-reboot-reconciliation-design]].
**Résultats réels** : `cargo test --workspace` = **1623 passed, 0 failed**. `cargo clippy --workspace --all-targets` = 0 erreur, aucun warning sur le code ajouté (warnings restants tous pré-existants).
**Lot 1 (domaine, `crates/domain/src/live_state.rs`)** : `LiveState::reconcile_orphans(is_live, now_ms) -> Vec<LiveEntry>` pure (ne mute pas self). Orphelin = status ∈ {Working,Waiting,Blocked} ET !is_live → réécrit status=Idle, intent gardé, ticket=None, last_delegation=None, progress=Some(`STALE_AT_RESTART_MARKER`), updated_at_ms=now. Const pub `STALE_AT_RESTART_MARKER = "(stale — session not running at restart)"`. +4 tests.
**Lot 2 (application, nouveau `crates/application/src/workstate/reconcile.rs`)** : use case `ReconcileLiveState` sur `LiveStateStore`+`LiveAgentRegistry`(ISP)+`Clock` ; `execute(ReconcileLiveStateInput{project_id})` charge → reconcile_orphans → upsert par ligne. +3 tests. Exporté via workstate/mod.rs + application/lib.rs.
**Lot 3 (app-tauri, `state.rs`+`commands.rs`)** : provider par-root `AppReconcileLiveState` (résout root via `ProjectStore::load_project`, `FsLiveStateStore::new(&root)`, registre = `LiveSessions` existant). Champ `reconcile_live_state` sur AppState. Hook best-effort dans `open_project` après `reconcile_layouts`. **Acte système** : écrit le port `LiveStateStore` directement, jamais via `OrchestratorCommand::SetWorkState` → self-only de `idea_workstate_set` préservé par construction.
**Arbitrage à valider QA/Architect** : la résolution project_id→root se fait au composition root (le store est root-bound), donc `ReconcileLiveState::execute` reçoit bien `ReconcileLiveStateInput{project_id}` (conforme Lot 2 + hook Lot 3 à la lettre) mais ne relit pas project_id dans le flux pur (`_input`). Variante sans ce param redondant = ajustement trivial si souhaité.
- **AppImage rebuildée + tournante** : `IdeA_0.3.0_amd64.AppImage` (le nom n'est plus 0.1.0). Le binaire live embarque tout le programme ; 6 ponts MCP connectés/ESTAB observés → point 1 du chantier e2e (agent voit ses outils MCP sans action manuelle) déjà satisfait en réel. Recette rebuild (rappel) : `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 installée → relancer.
- **Discordance .gitignore ↔ D19-4 : TRANCHÉE** (par l'utilisateur) et exécutée par Git → commit `47b3806` sur `develop` (`chore(gitignore): désuivre .ideai/conversations/`). Périmètre réel : seul `.ideai/conversations/` était discordant (16 fichiers suivis, 8 handoff.md + 8 log.jsonl) ; `.ideai/run/` et `.ideai/live-state.json` étaient déjà ignorés/propres. Désuivis (rm --cached, conservés sur disque) + règle ajoutée `.gitignore:58`. Plus une décision en attente.
Objectif = valider/durcir en situation RÉELLE le parcours inter-agents (le code est probablement bon, Bugs 1→7 corrigés cf. [[mcp-bridge-and-delegation-runtime-notes]], mais ces bugs ne sont apparus qu'en live). À vérifier sur l'AppImage 0.3.0 :
1. Un agent lancé par IdeA voit ses outils MCP sans action manuelle. (déjà OK observé)
2. Délégation vers agent FROID et vers agent BACKGROUND (node_id None) : tâche bien écrite dans le PTY, pas de 1er tour perdu, pas de `Busy` fantôme après interruption/annulation (Bugs 3/4/6/7).
3. Un `ask` sans réponse ne wedge pas la connexion du demandeur (Bug 5).
5. Observabilité UI des délégations/replies (aujourd'hui opaque) — sujet UX.
**CONTRAINTE MÉTHODO** : NE PAS réparer/valider le système inter-agents VIA le système inter-agents (`idea_ask_agent`). Piloter via les **subagents natifs (outil Agent)**, et demander à l'utilisateur de faire tourner l'AppImage + observer (Main ne peut pas valider seul depuis l'intérieur). Diag live : `ss -xp | grep idea-mcp` (ponts connectés) ; `~/.claude/projects/<encoded-run-dir>/*.jsonl` (transcript ; pas de nouveau fichier = tâche jamais soumise).
## Autres chantiers restants (depuis [[remaining-work-idea-agent-control-ide]])
- **Auto-update mémoire/contexte EN COURS de session** (aujourd'hui injection au lancement seulement) — priorité 1.
- **Multi-fenêtres / déplacement d'onglet** du registre de sessions — priorité 2 (UI).
- Optionnels différés : activation réelle du seam LLM de handoff (contrat figé ADR LS5, défaut heuristique) ; sweep périodique de rotation (idempotent, non câblé).
## Friction outillage notée (par Git, sans incidence résultat)
Le `settings.local.json` du run dir a un `deny` qui bloque Edit/Write et certaines redirections Bash pour les agents en run dir isolé ; Git a dû éditer `.gitignore` via `tee -a`. À assouplir si on veut des éditions de fichiers propres côté run dir.
## Contrainte d'écriture (rappel)
Main + agents en run-dir isolé ne peuvent PAS écrire l'arbre du project root (docs/, ARCHITECTURE.md, .ideai/memory/*.md fichiers) — permissions déniées. Mémoire IdeA : outil `idea_memory_write` (create/replace par slug ; pas de delete). Suppression fichiers mémoire / édition MEMORY.md / CLAUDE.md / .gitignore du project root : passer par **Git**.
description: Les sandboxes de DevBackend et QA bloquent TcpListener::bind (EPERM) ; tout `cargo test` sur web-server/app-tauri y rend un VERT QUI NE PROUVE RIEN. Vérifier hors sandbox.
metadata:
type: reference
---
# Faux vert : `TcpListener::bind` interdit dans les sandboxes agents
## Le fait
Les environnements d'exécution de **DevBackend et de QA** refusent `TcpListener::bind("127.0.0.1:0")` avec `Operation not permitted (os error 1)`. Tout test qui ouvre un socket y est structurellement inexécutable.
Le 2026-07-16, ce piège s'est refermé **deux fois dans la même journée** :
1.**#68 B1** — DevBackend annonce « 57 passed ». En réalité le test `run_embedded_stop_shuts_down_accept_loop` sortait en silence sur EPERM via un repli, et le test du core partagé basculait sur un dispatch in-process. Rejoué **hors sandbox** : échec réel, révélant un **vrai bug produit** — `run_embedded` ne réconciliait pas le port effectif avec la config, donc `origin_allowed` comparait l'origine à `http://127.0.0.1:0` et un serveur embarqué sur port éphémère **rejetait toutes les requêtes API en 403**. Le trou n'avait jamais été vu parce que rien ne consommait `run_embedded`.
2.**#72** — même schéma, cette fois signalé honnêtement par DevBackend (« 63 passed; 2 failed » sur EPERM) après consigne explicite.
Un repli silencieux sur EPERM transforme un test en décoration : il passe sans rien exercer, et masque la classe de bug qu'il était censé attraper.
## La règle
- **Aucun repli EPERM silencieux.** Un test qui ne peut pas s'exécuter doit **échouer visiblement** ou être `#[ignore]` explicite avec sa raison — jamais « passer ».
- **Tout vert sur `web-server`/`app-tauri` doit être produit hors sandbox** (`dangerouslyDisableSandbox: true` côté Main, qui n'a pas la restriction). Ne jamais accepter un vert sandboxé comme preuve sur ces crates.
- DevBackend et QA doivent **dire ce qu'ils ont pu exécuter et ce qu'ils n'ont pas pu**, plutôt que de rendre un chiffre global.
- Les `#[ignore = "requires local socket bind permission"]` légitimes (ex. `mcp_bridge::tests::end_to_end_over_real_loopback`) se vérifient en les **lançant** hors sandbox avec `--ignored`, pas en raisonnant dessus.
## Principe général
Ne pas confondre « les tests sont verts » et « le code marche ». Le vert d'un environnement contraint ne dit rien du produit. Cette leçon vaut au-delà du bind : un environnement qui ne peut pas exercer un chemin ne peut pas le valider.
Lien : [[appimage-build-no-strip-relr-dyn-fix]] (autre piège d'environnement de cette machine),
description: Cause racine du cross-talk entre projets ouverts simultanément (branche hybride wear-os + notification de tâche backend perdue) : les stores disque IdeA sont scoppés par projet, mais plusieurs registres mémoire runtime critiques restent indexés par AgentId seul, sans re-mint des UUID à l'ouverture. Plan de correction figé en 6 lots (clé RuntimeAgentKey).
- **A — contamination du contexte d'inférence** : un agent a créé une pseudo-branche `feature/ticket91-wear-os-watch-sync` (ticket #91 purement front) où le suffixe `wear-os-watch-sync` appartient à l'AUTRE projet de l'utilisateur. Le nom n'apparaît dans aucun fichier `.ideai/` du projet IdeA → contamination au niveau du contexte d'inférence runtime (conversation injectée à l'agent Git mélangeait les projets). Preuve topo préservée : `main` divergé de `develop` (da907b8 vs 6a87c46), `feature/ticket99-agent-model-configuration` créée depuis main au lieu de develop.
- **B — notification de tâche backend perdue / agent qui s'arrête** (sujet original #101) : un agent OpenCode lance `idea_run_in_background` puis s'arrête, sans retour de notification garanti.
## Cause racine (Architect, 2026-07-25)
Stores disque **correctement scoppés par projet** (mémoire, contexte, handoff, live-state lus depuis `input.project.root` ; agents persistés dans `.ideai/agents.json` par root projet). MAIS les agents **ne re-mint pas leurs UUID à l'ouverture** → deux projets (surtout si l'un est une copie) peuvent porter les **mêmes `AgentId`**.
Plusieurs **registres mémoire runtime restent globaux, indexés par `AgentId` seul** :
- ConversationRegistry global + `ConversationId` dérivé des UUID agents seuls — `crates/domain/src/conversation.rs:71`, `crates/infrastructure/src/conversation/mod.rs:41`, orchestrateur `crates/application/src/orchestrator/service.rs:2192`.
- Sessions PTY/structured retrouvées par `agent_id` seul — `crates/application/src/terminal/registry.rs:182,437` ; réutilisation session `service.rs:2293,2368`.
- Verrous ask, busy-state, liveness, délégations différées par `AgentId` seul — `service.rs:418`, `crates/infrastructure/src/input/mod.rs:47`.
- Inbox/mailbox par `AgentId` seul — `crates/domain/src/inbox.rs:68,156`, `input/mod.rs:1052`, `crates/infrastructure/src/mailbox/mod.rs:44`.
- Wake par `AgentId` seul — `crates/application/src/orchestrator/wake.rs:87`, provider `crates/backend/src/lib.rs:687`.
→ Un agent du projet courant peut se rattacher à la conversation/session/inbox d'un autre projet (collision d'UUID) : contamination d'inférence (A) ET complétion livrable/bloquée/réveillant la mauvaise session (B).
## Pour B : runtime vs modèle
La chaîne sink→`project_id`→wake est correcte jusqu'au bridge (`crates/infrastructure/src/background_task/sink.rs:23,142` ; `crates/backend/src/lib.rs:2228` recharge le projet par `project_id` puis `wake_agent`). Le défaut réapparaît juste après (inbox/mailbox/busy/session par AgentId seul). **Runtime IdeA suspecté en premier.** Le modèle GLM 5.2 n'est l'hypothèse principale que si les logs prouvent (pour le bon `project_id`) : enqueue + wake + `BackgroundTaskCompletionDelivered` émis sans reprise utile. Traces : `lib.rs:2238`, `wake.rs:93`.
## Plan de correction (6 lots, figé)
1. Introduire `RuntimeAgentKey { project_id, agent_id }` ; interdire toute map app-wide indexée par `AgentId` seul.
2. Propager la clé dans `AgentInbox`, `InputMediator`, `AgentMailbox`, busy/liveness/deferred, wake, session lookup, ask-locks. **Correctif structurel commun à A et B.**
3. Qualifier les conversations par projet : `ConversationId` + `ConversationRegistry` intègrent `ProjectId`.
4. Requalifier `TerminalSessions`/`StructuredSessions` par `(project_id, agent_id)` ; `AppWakeSessionProvider` refuse toute session vivante d'un autre projet.
5. Audit des autres états mémoire similaires (`session_limit`, tables de reprise/verrous par agent) pour éviter une demi-correction.
6. Télémétrie `project_id` explicite sur les logs diag critiques de wake/routage.
## Invariants
- Sources de contexte sur disque restent root-scopées (ne pas régresser).
- Templates/profils globaux restent globaux produit — ne doivent pas devenir vecteurs de session/conversation partagée.
- Un projet non-actif doit continuer à recevoir ses wakes et complétions.
- Pas de régression OpenCode/Codex/Claude (correctif transverse runtime, pas moteur-spécifique).
- Aucun nouveau DTO frontend pour la correction minimale.
## QA — critère de vérité
Test d'intégration **non-cross-talk** : 2 projets ouverts simultanément contenant **volontairement les mêmes `AgentId`** :
- délégation dans projet A ne réutilise jamais une session/conversation vivante de projet B ;
- complétion `(project A, agent X)` n'entre ni dans l'inbox ni dans la session de `(project B, agent X)` ;
- le wake d'un projet non-actif fonctionne quand même ;
- une collision d'ids qui échouait avant devient verte sur PTY, structured et background wake.
## Hors périmètre
-#91 (popup de notification) : purement front, indépendant du défaut runtime.
- Remint systématique des AgentId à l'ouverture : piste complémentaire non retenue dans le fix minimal (la clé runtime scellée par projet rend la collision inoffensive même sans remint).
## Topologie
- Branche de travail à créer pour le fix (Git décidera). Base `develop` (6a87c46). `main`/`feature/ticket99-agent-model-configuration` divergent sur da907b8 (preuves préservées, à nettoyer après enquête).
## Lié
-#91 (relatesTo) : popup de notification — front pur.
- Mémoire `background-tasks-first-class-design` + `b8-command-runner-pty-framing` : design du flux de complétion (le défaut est en aval du sink).
- Mémoire `mcp-bridge-and-delegation-runtime-notes` : règle rebuild AppImage (le binaire qui tourne = AppImage, pas les sources).
description: Cause racine identifiée du bug ticket #113 (espaces impossibles dans le champ Arguments supplémentaires llamacpp) et niveau de fix attendu.
metadata:
type: project
---
Root cause confirmée dans le code (2026-07-31) : `frontend/src/features/model-servers/ModelServersPanel.tsx:645-647` affiche `value={draft.args.join(" ")}` et parse via `parseArgs` (`modelServer.ts:133`, `split(/\s+/).filter(non-vide)`) à chaque `onChange`. Le state du champ est un `string[]` reconstruit en texte à chaque frappe, donc tout espace en fin de saisie ou espace double est immédiatement absorbé avant le prochain re-render : l'utilisateur ne peut jamais laisser un espace « en attente ».
Le même pattern existe à l'identique dans `frontend/src/features/first-run/FirstRunWizard.tsx:264` (même `parseArgs` importé depuis `first-run/profile.ts`) — probablement le même bug latent, non signalé par l'utilisateur mais à couvrir dans le même correctif.
**Why:** classique piège de champ contrôlé dont le state est un type dérivé (array) plutôt que la chaîne brute tapée — le round-trip parse→join efface la saisie en cours.
**How to apply:** le fix attendu est purement frontend/local : garder une valeur de saisie brute en state local (string) pendant la frappe, ne convertir vers `args: string[]` qu'à la sortie du champ (blur/submit), sans reconstruire `value` depuis `args.join(" ")` à chaque `onChange`. Aucun changement domaine/backend/DTO nécessaire — `args: string[]` reste le contrat de sortie inchangé.
Le bug "écran noir à l'installation de hello-plugin" (#120, encore open) a déjà survécu à 3 correctifs mergés dans develop — traiter le prochain lot comme recherche de root cause, pas comme patch symptomatique de plus.
## Constat
Le ticket #120 ("Réinvestiguer l'installation de hello-plugin: écran noir / perte d'affichage IdeA") est toujours **`open`** au 2026-08-01, alors que **trois** correctifs distincts ont déjà été mergés dans `develop` sur le même symptôme :
Et le bug revient une 4e fois, ce qui a motivé la relance de #120 le 2026-08-01 sur la branche `feature/ticket120-hello-plugin-blackscreen-crash-diagnostics`.
**Why** : chaque fix précédent a visiblement traité un symptôme observable (crash isolation, manifeste, réconciliation MCP, loader export) sans que la cause racine soit couverte — sinon le bug ne reviendrait pas. Fermer #120 à chaque merge sans validation e2e réelle post-merge semble être le trou du cycle.
**How to apply** :
- Avant tout nouveau fix sur ce symptôme, lire l'historique des 3 tentatives ci-dessus pour ne pas répéter une piste déjà explorée et retombée.
- Ne pas fermer #120 au merge — seulement après validation réelle de l'installation du plugin de bout en bout (voir [[git-owns-commit-merge-decisions]] pour la règle générale de non-merge sans tests verts, qui s'applique ici avec une vigilance renforcée).
- La dette de diagnostic crash/logs est traitée dans le même lot que ce 4e essai (branche `feature/ticket120-hello-plugin-blackscreen-crash-diagnostics`), précisément pour éviter un 5e patch aveugle.
description: Angle d'investigation pour le ticket #120 (récidive du black screen hello-plugin après 3 vagues de correctifs sur #116 et antérieurs) — la cause probable est en amont de la couche déjà blindée.
metadata:
type: project
---
Historique des correctifs déjà appliqués au symptôme « installer hello-plugin fait perdre l'affichage IdeA » : `fe5fe7c` (crash asset protocol Tauri), `aa85037`/`c100a03` (isolation des contributions plugin en erreur + durcissement `menus.ts`), `d4e61a8`/`f6685aa` (ticket #116, fermé). Constat utilisateur live au 2026-07-31 (ticket #120) : le black screen est **toujours observé** malgré ces trois vagues.
**Why:** trois correctifs successifs ciblant tous la couche « rendu des contributions plugin déjà chargées » sans faire disparaître le symptôme est un signal fort que la cause racine réelle est ailleurs — probablement en amont (flux d'installation backend : validation manifeste, copie assets, écriture manifeste projet — un panic/erreur non catché ici peut tuer le process Tauri entier, ce qu'aucune isolation frontend ne peut rattraper) ou dans le remount/reload post-install côté frontend, pas dans le rendu des contributions lui-même qui a déjà été isolé trois fois.
**How to apply:** pour #120, prioriser l'audit du chemin d'installation backend et de la frontière install→reload frontend AVANT de retoucher l'isolation des contributions (déjà traitée). Reproduire dans l'AppImage réellement rebuild (pas `pnpm dev`), pas seulement en dev — voir [[mcp-bridge-and-delegation-runtime-notes]] sur le piège du binaire qui tourne. Ne pas re-fermer le ticket sans preuve live post-fix dans le binaire réel, comme #116 l'a probablement fait à tort.
description: Résultat de l'inventaire B0/F0 frontend du chantier client/serveur #13 — la frontière gateways transport-neutres existe déjà, F1 = ajout d'adapters HTTP+WS.
**Constat clé** : la frontière « gateways TS transport-neutres » du plan est DÉJÀ réalisée et testée.
-`src/ports/index.ts` = 21 gateways sans Tauri ; toute la couche `features/`+`app/` en dépend via DI (`useGateways()`).
-`src/adapters/*` = seul lieu important `@tauri-apps/api`.
- Garde L1 `src/app/no-direct-invoke.test.ts` casse la CI si un fichier hors `adapters` importe Tauri / appelle `invoke(`.
-`src/app/di.tsx``resolveGateways()` bifurque déjà Tauri vs mock → F1 = ajouter `createHttpWsGateways()` (3ᵉ impl).
**Donc F1 = écrire un 2ᵉ jeu d'adapters derrière des ports inchangés, aucun composant métier à réécrire.**
**Flux Channel (→ WebSocket)** : (1) `terminal.ts` PTY, (2) `agent.ts` PTY agent (réutilise `makeTerminalHandle`), (3) `ticket.ts``sendTicketChat``Channel<ReplyChunk>`. Tous modélisés côté port par callback `onData`/`onChunk`. Le contrat PTY WS du carnet mappe 1-pour-1 sur `TerminalHandle` (write/resize/detach/close, detach≠close, scrollback au reattach) → port inchangé pour F3. `TerminalView.tsx` ne connaît que le port.
description: Le 3e jeu d'adapters frontend (HTTP request/response + squelette WebSocket) du chantier client/serveur #13 est livré et vert, derrière les ports inchangés.
metadata:
type: reference
---
Ticket #13 lot F1 livré sur feature/ticket13-server-client-mode. Nouveau dossier `frontend/src/adapters/http/` = 3e implémentation des gateways, à côté de Tauri (desktop) et mock.
**Seam DI** : `app/di.tsx` → `resolveTransport()` 3-way (mock > http > tauri), web sélectionné par `VITE_TRANSPORT="http"`. Tauri reste défaut (desktop inchangé).
**Décision de contrat clé (à confirmer DevBackend)** : transport RPC générique `POST /api/invoke {command,args}` choisi PLUTÔT que l'arbre REST du brouillon B0 — préserve 1:1 tous les DTO Tauri, zéro divergence, bascule REST future ne touche que httpInvoker.ts. Autres points à trancher : placement token WS (pas en URL ; navigateur ne peut pas fixer d'en-tête upgrade), forme réponse terminal.open/agent.launch (ack terminal.attached avec session.sessionId), projectId absent du port openTerminal.
**Reporté F3/B5/B6** (TODO(F3/B5)) : round-trip xterm réel, reconnexion/backpressure, replay seq/gap, sink chat par-session pour sendTicketChat, agents structurés. Pas de serveur avant B3/B4.
description: Le client web read-only (pairing cookie, liste projets, ouverture read-only + snapshot work-state) du chantier client/serveur #13 est livré et vert.
metadata:
type: reference
---
Ticket #13 lot F2 livré sur feature/ticket13-server-client-mode. Clôture frontend du premier incrément livrable (B0→B4/F2).
**Modifié** : httpInvoker (credentials same-origin + callback onUnauthorized sur 401), http/index.ts (câble onUnauthorized→webSession), app/main.tsx (monte <WebApp/> si resolveTransport()==="http", desktop inchangé).
**Flux** : non-paired→PairingScreen ; pair OK (cookie HttpOnly posé serveur)→WebWorkspace ; 401 d'un /api/invoke→retour pairing ; « se déconnecter »=clear flag local (révocation serveur=B8). Cookie jamais lisible en JS (HttpOnly) : on se fie au 200 + flag de routage.
**Read-only** : WebWorkspace n'appelle QUE list_projects, open_project, get_project_work_state (via gateways DI). N'appelle PAS onDomainEvent/health/firstRunState → aucune commande hors-allowlist, pas de WS (live update = F3/B5, snapshot ponctuel pour l'instant).
**À confirmer B4** : get_project_work_state dans l'allowlist ; open_project sans effet de bord dangereux en read-only ; 401 (pas 403) sur cookie manquant ; code HTTP mauvais code /api/pair (401/403 → « Code d'appairage invalide »).
Build vert, garde no-direct-invoke verte, 79 fichiers/736 tests verts, desktop inchangé. Suite de [[ticket13-f1-http-ws-adapter-delivered]] ; inventaire [[ticket13-f0-frontend-transport-inventory]].
description: L'adapter WS terminal (round-trip xterm, reconnexion+replay, états connexion) du chantier client/serveur #13 est finalisé et vert côté frontend.
metadata:
type: reference
---
Ticket #13 lot F3 livré sur feature/ticket13-pty-websocket. Finalise l'adapter WS terminal depuis le squelette F1. Travail 100% dans frontend/src/adapters/http/ ; port TerminalGateway/TerminalHandle et TerminalView INCHANGÉS.
**wsLiveClient.ts** : machine d'état connexion (connecting/connected/reconnecting/closed, getConnectionState()+onConnectionStateChange), reconnexion auto (backoff, setTimeout injectable) → re-attach_terminal avec lastSeq + repaint scrollback borné + notices « déconnecté »/« reconnecté » écrites dans xterm via le sink. Suivi des sessions terminales (map `terminals`) pour le replay. Routage terminal.status exited → notice + onStatus + untrack. openTerminal/attachTerminal/detachTerminal/closeTerminalSession haut-niveau. API bas-niveau F1 (setOutputSink/send/domain events) conservée pour agent/system gateways.
**Écarts B5 à arbitrer** : (1) pas de replay delta — le serveur rejoue TOUT le scrollback, lastSeq ne sert qu'au flag gap ⇒ duplication possible à la reconnexion (conforme « V1 scrollback borné »). (2) multi-onglets « dernier gagne » SILENCIEUX — l'attachement évincé ne reçoit aucune frame (pas de crash, mais pas d'indication). (3) indication d'état = notices dans xterm (port inchangé).
**Bloqueur run live** (dette carnet, hors F3) : `idea --serve` ne sert pas les assets web same-origin ⇒ app web pas lançable en navigateur tant que le lot « servir dist/ » n'est pas fait. Suite de [[ticket13-f1-http-ws-adapter-delivered]], [[ticket13-f2-web-readonly-client-delivered]].
description: L'agent CLI web (agent.launch → terminal.attached, réattache sans relance, cellule agent réutilisant TerminalView) du chantier client/serveur #13 est livré et vert côté frontend.
metadata:
type: reference
---
Ticket #13 lot F4 livré sur feature/ticket13-pty-websocket. Rend la surface agent fonctionnelle en mode web via les gateways DI ; CLI serveur, web = affichage.
**Adapter** : wsLiveClient.launchAgent(params) réutilise attachInternal de F3 → frame agent.launch, ack unifié terminal.attached, session trackée dans la map `terminals` (reconnexion/replay/exited comme un terminal), renvoie {sessionId, scrollback, assignedConversationId, status}. HttpAgentGateway.launchAgent → ws.launchAgent (pose assignedConversationId sur le handle) ; HttpAgentGateway.reattach → ws.attachTerminal (frame terminal.attach, PAS de relance). Chemin bas-niveau F1 dupliqué supprimé.
**UI (câblage, pas de nouveau composant terminal)** : features/web/WebAgentCell.tsx réutilise TerminalView (agentMode) avec agent gateway DI comme open=launchAgent/reattach=reattach, persiste sessionId. WebWorkspace : affordance « Ouvrir » par agent du snapshot work-state → monte WebAgentCell.
**Contrat B6 (server.rs) confirmé, aucun écart** : agent.launch payload plat camelCase {projectId,agentId,nodeId,rows,cols,conversationId} → ack terminal.attached{assignedConversationId}. Agent structuré → erreur UNSUPPORTED (canal PTY-only) surfacée par la bannière TerminalView. Réattache = terminal.attach (no respawn). Singleton guard AGENT_ALREADY_RUNNING déjà géré par TerminalView.
**Points UI à signaler** : (1) l'affordance liste les agents du snapshot work-state ; lister tous les agents exigerait list_agents sur l'allowlist B4. (2) write-portal (injection délégation) NON câblé en web V1 (affichage + frappe seulement). (3) WebAgentCell ne gère pas de nœud layout.
**Bloqueur run live** (dette carnet, hors F4) : `idea --serve` ne sert pas les assets web same-origin ⇒ round-trip navigateur pas validable tant que le lot « servir dist/ » n'est pas fait. Suite de [[ticket13-f3-xterm-websocket-delivered]], [[ticket13-f1-http-ws-adapter-delivered]], [[ticket13-f2-web-readonly-client-delivered]].
description: Les surfaces live web (workstate live via event.domain, background tasks cancel/retry, inbox, re-synchro au reconnect) du chantier client/serveur #13 sont livrées et vertes.
metadata:
type: reference
---
Ticket #13 lot F5 livré sur feature/ticket13-pty-websocket. Rend les surfaces live fonctionnelles en mode web.
**Réutilisation clé** : le hook desktop transport-neutre `features/workstate/useProjectWorkState` (refresh du read-model sur event.domain) est réutilisé TEL QUEL — c'est lui qui donne la parité live. PAS de réutilisation de ProjectWorkStatePanel (dépend de useLayout + attach/stop-vers-cellule, spécifiques au grid desktop, hors read-only). WebWorkspace rend une vue lean : agents live/idle/busy, background tasks (Cancel/Retry via workState gateway), inbox par agent, + cellule agent F4.
**Adapter** : wsLiveClient.needsReconnect() = terminals.size>0 OU domainEventHandler!=null → la reconnexion marche aussi pour un abonnement live sans terminal (le handler domaine persiste à travers la reconnexion). Nouveau webLive.ts = singleton getWebLiveClient()/setWebLiveClient() (createHttpWsGateways enregistre le ws) exposant l'état de connexion au feature web SANS le mettre dans le port SystemGateway. Nouveau hook features/web/useLiveReconnect.ts : re-refresh du read-model sur transition reconnecting→connected (events manqués pendant coupure ; récupération = re-fetch complet du snapshot, pas de replay serveur).
**À confirmer B7** : (1) cancel_background_task/retry_background_task sur l'allowlist web write. (2) inbox surfacée via le read-model workstate, PAS via un flux notifications distinct (si B7 en a un, non câblé). (3) re-synchro = re-fetch complet (workstate = snapshot complet), pas de delta par event.
**Bloqueur run live** (dette carnet, hors F5) : `idea --serve` ne sert pas les assets web same-origin. Suite de [[ticket13-f4-web-agent-surface-delivered]], [[ticket13-f3-xterm-websocket-delivered]], [[ticket13-f2-web-readonly-client-delivered]].
| Boucle agentique | conduite par la CLI | conduite par **IdeA** |
| Rôle MCP d'IdeA | serveur ; CLI = client | pas de client ⇒ IdeA = orchestrateur d'outils |
| Contexte .md | la CLI lit son convention file | injecté en **system message** par l'adapter |
| Conversation | id moteur (session_id/thread_id) | API **stateless** ⇒ transcript possédé par IdeA |
Conséquence : le nouvel adapter **n'utilise PAS**`session/process.rs` (run_turn/spawn/drain). Il ne partage avec claude.rs/codex.rs que les **types de port** (`AgentSession`, `ReplyEvent`). Cette absence de code partagé mutable EST la garantie d'isolation.
- Nouveau VO validé (parse-don't-validate) `HttpChatConfig { endpoint:String (http/https non vide), model:String (non vide), api_key_env:Option<String> (nom de var env valide, JAMAIS la clé), request_timeout_ms:Option<u32>, connect_timeout_ms:Option<u32>, max_tool_iterations:Option<u16> (garde-fou boucle, défaut ~16) }`. Le domaine ne résout jamais la clé (pas d'I/O env), il porte le **nom** de variable.
- Rattachement `AgentProfile.chat_http: Option<HttpChatConfig>` avec `#[serde(default, skip_serializing_if="Option::is_none")]` (miroir exact de `mcp`/`liveness`/`rate_limit_pattern`, profile.rs:570-604) ⇒ **zéro régression de sérialisation** (profils Claude/Codex bit-identiques). Test round-trip obligatoire (miroir profile_without_mcp_round_trips_identically).
**Nouveau port tool-calling (le seam clé — le pont MCP .mcp.json/config.toml + bridge `idea mcp-server` NE s'applique pas, pas de client CLI)**
Implémenté en **app-tauri** en déléguant à la **même**`OrchestratorService::dispatch` (orchestrator/service.rs:1082) que le endpoint MCP ⇒ parité délégation/rendez-vous (idea_ask_agent/idea_reply), gating permissions/sandbox conservé. Injecté dans la factory via `with_tool_invoker(Arc<dyn ToolInvoker>)` (jumeau de `with_sandbox_enforcer`). `None` ⇒ tool-calling désactivé proprement (chat nu).
**Infra** : nouveau fichier `crates/infrastructure/src/session/openai_compat.rs` (jumeau structurel de codex.rs SANS process). Client HTTP **reqwest + rustls-tls** (jamais native-tls — spike AppImage §13-2, confiné au crate infra). Parsing isolé pur `parse_chat_delta`/`parse_completion` (miroir de parse_event) — seul endroit qui connaît le schéma OpenAI (choices[].message, tool_calls[], SSE data:).
**Routing — seul contact avec l'existant** : `StructuredSessionFactory::start` (factory.rs:111) = **UN bras de match ajouté** ; bras Claude/Codex INTOUCHÉS ; `supports()` reste `profile.is_selectable()` (=structured_adapter.is_some(), profile.rs:857) ⇒ sélectionnable sans changer le prédicat.
## 2. Contrat de session headless
`send(prompt)` : 1) transcript.push(user) ; 2) émettre Heartbeat ; 3) POST {endpoint}/chat/completions {model, messages:transcript, tools?:invoker.tools(), stream:true} ; 4) si tool_calls ⇒ pour chaque: ToolActivity{label=name} + result=invoker.call(name,args) + push(assistant tool_call)+push(tool result), reboucler (borné max_tool_iterations) ; 5) sinon streamer chunks ⇒ TextDelta (+tap→Announcement, cf claude.rs:337) ; 6) réponse complète ⇒ push(assistant) + **Final{content}** ; 7) persister transcript run dir. Contrat de flux universel respecté (un seul Final terminal) ⇒ send_blocking/drain_with_readiness marchent tels quels.
**Erreurs (jamais de corps HTTP brut propagé)** : endpoint injoignable 1er contact/probe ⇒ `AgentSessionError::Start` (UI erreur, IdeA non bloqué) ; réseau/coupure en tour ⇒ `Io` ; JSON illisible/schéma ⇒ `Decode` (diagnostic court) ; timeout ⇒ `Timeout` via send_blocking, session non tuée ; modèle absent (404/400) ⇒ `Start` dédié.
**Reprise sans mélange d'ids (invariant du ticket)** : /chat/completions **stateless** ⇒ AUCUN id provider ⇒ `conversation_id()` retourne **None** (rien à mélanger). État = transcript provider-shaped (roles+tool_calls) **possédé par IdeA**, persisté dans le **run dir stable**`.ideai/run/<agent-id>/chat-transcript.json` (clé = agent, jamais id provider). Reprise = ré-instanciation factory ⇒ rechargement transcript ; `seed_conversation_id` (factory.rs:63) **ignoré** par cet adapter (documenter). DISTINCT du conversation-log LS6 (log.jsonl, dérivé ReplyEvent, surface humaine, inchangé) — deux artefacts, aucun mélange.
**Contexte** : `start` reçoit `ctx:&PreparedContext` ; `ctx.content` (ports.rs:120) = Markdown rendu ⇒ injecté comme premier message **system** (le serveur HTTP ne lit pas de convention file).
## 3. Seam tool-calling / dégradation
Parité : ToolInvoker câblé ⇒ modèle local voit idea_* via la même OrchestratorService (délégation/rendez-vous/contexte/mémoire/tickets identiques, gating conservé). Dégradation 3 niveaux : (1) ToolInvoker None ⇒ chat nu ; (2) profil opte sans outils ⇒ idem ; (3) endpoint rejette param `tools` ⇒ détecter, retirer tools, rejouer chat nu, 1 diagnostic — l'agent converse toujours, sans déléguer. Garde-fou max_tool_iterations (modèles locaux moins fiables) ⇒ au plafond, Final + note.
## 4. Lots B/F
Backend : **B1** domaine (variante+provider_key, VO HttpChatConfig, champ chat_http, port ToolInvoker/ToolSpec/ToolInvocationError ; tests sérialisation zéro-régression, round-trip, invariants ; zéro I/O). **B2** adapter infra openai_compat (reqwest/rustls, parse_* purs, mapping ReplyEvent+erreurs, résolution clé env ; tests fixtures OpenAI/Ollama, erreurs via wiremock, pas de fuite payload). **B3** boucle outils bornée + persistance/rechargement transcript run-dir (reprise) + dégradation tools non supporté (tests ToolInvoker fake, reprise=rechargement, plafond). **B4** routing (bras match) + composition root (with_tool_invoker, impl ToolInvoker→OrchestratorService) + profil de référence "Ollama / OpenAI-compatible local model" éditable (tests factory route, supports true, probe indispo→Start).
Frontend : **F1** types TS HttpChatConfig + champ chatHttp? + adapter "openAiCompatible". **F2** wizard/settings : sélectionnable comme Claude/Codex, form endpoint/model/apiKeyEnv/timeouts, validation miroir backend (first-run/profile.ts isValidEnvVar, URL, non-vide), apiKeyEnv = nom de var jamais clé. **F3** cellule = chemin chat structuré existant (dérivé de is_selectable, "gratuit" si DTO respecté) + erreur "endpoint indisponible" propre sans bloquer UI (tests RTL gateways mock).
Frontière B↔F : **DTO AgentProfile** (déjà le pont IPC) porte structuredAdapter:"openAiCompatible" + chatHttp{endpoint,model,apiKeyEnv?,requestTimeoutMs?,connectTimeoutMs?,maxToolIterations?}. Sélectionnabilité + rendu chat dérivés du DTO existant, pas de nouveau canal ni commande Tauri au cœur (le seed du profil de référence peut passer par le CRUD profils existant — à confirmer B4/F3).
## 5. Vigilance régression / invariants
1. Zéro régression sérialisation (skip_serializing_if=Option::is_none ; round-trip Claude/Codex bit-identique — BLOQUANT). 2. Isolation code : nouvel adapter = fichier neuf ; INTERDIT de toucher claude.rs/codex.rs/process.rs ; seul diff existant = 1 bras match factory.rs + champs optionnels domaine. 3. Contrat flux : exactement un Final terminal ; Heartbeat/ToolActivity/TextDelta/RateLimited non terminaux (conformance.rs doit couvrir le nouvel adapter). 4. Frontière domaine : reqwest UNIQUEMENT infra ; aucun type HTTP en domaine/application ; ToolInvoker port pur. 5. Nouvelle dépendance AppImage : reqwest rustls-tls (pas OpenSSL, spike §13-2). 6. Non-mélange ids : conversation_id()=None ; transcript clé-agent ; distinct de LS6. 7. Sandbox : pas de process spawné ⇒ SandboxEnforcer OS = no-op pour le tour (pas une régression) mais les outils via ToolInvoker gardent le gating orchestrateur.
## 6. Décisions produit remontées à Main (NON tranchées par Architect)
1. Surface d'outils v1 : parité totale immédiate vs sous-ensemble curaté (fiabilité tool-calling des modèles locaux). Reco : parité + garde-fou max_tool_iterations.
2. Streaming SSE dès v1 (reco, parité observabilité live) vs non-streaming (repli si endpoint ne stream pas).
3. Nom canonique variante : `OpenAiCompatible` (reco, décrit le protocole ; provider_key figé) vs `LocalChat` (formulation ticket).
-`ReplyEvent::Progress { progress }` est explicitement non terminal ; `ReadinessPolicy` et les drains applicatifs continuent de donner autorite uniquement a `ReplyEvent::Final` pour la fin de tour. `RateLimited` reste orthogonal/non terminal.
- DTO transport : `ReplyChunk::Progress { progress }` avec shape JSON camelCase, additive par rapport aux anciens chunks.
-`agent_send` utilise maintenant `AgentSession::send_with_tap` pour projeter best-effort les progress pendant que `send` tourne ; le `ReplyStream` retourne par l'adapter reste le chemin autoritaire draine jusqu'au `Final`.
- Mapping adapters : Claude/Codex/OpenCode emettent des progress `ProviderNative` pour les evenements natifs observables (turn/system/step/tool item). OpenAI-compatible emet des progress `IdeaLocal`/`Mcp` autour des appels d'outils orchestries par IdeA.
Garde-fous : ne pas promettre le streaming du raisonnement interne ; ne jamais parser les metadonnees provider comme autorite metier ; #157 doit consommer `ReplyChunk::Progress` cote UI pour affichage distinct et degrade proprement si absent.
- **Barre unique** : plus de MenuBar séparée dans App.tsx. Une seule MenuBar dans `ProjectsView` regroupant `[ Projet: <actif> ▾ ] File ▾ View ▾ Settings ▾`.
- **Sélecteur de projet permanent** : premier menu de la barre, label = projet actif, items = tous les projets connus (`vm.projects`) → `selectProject(id)` (active l'onglet si ouvert, sinon `openProject`). Accessible même sous projet actif, sans ouvrir File→Projects….
- **Settings → AI Profiles** déplacé dans la barre unique : toggle `showSettings` LOCAL à ProjectsView ; le main area swap vers `<ProfilesSettings/>` pendant que la barre reste visible (permet de refermer). App.tsx ne gère plus showSettings ni ProfilesSettings (juste firstRun ? wizard : ProjectsView).
## Fichiers (état final)
- **Créés (lot A initial)** : `shared/ui/FloatingWindow.tsx`, `shared/ui/MenuBar.tsx`, `shared/ui/FloatingWindow.test.tsx`.
- ProfilesSettings rendu DANS le main de ProjectsView (barre + onglets projet restent visibles au-dessus) au lieu du swap plein-écran App précédent — meilleure continuité, barre unique toujours accessible.
- Pas de run live Tauri (couverture RTL mock-driven). DoD vitest satisfaite.
description: Correctif de la perte de focus dans l'édition de ticket (#17) livré sur develop via feature branch dédiée.
metadata:
type: reference
---
Bug #17 : saisie une lettre à la fois dans le formulaire d'édition de ticket, cause = focus-trap `FloatingWindow` avec `useEffect` dépendant de `[onClose]` (closure recréée à chaque render). Fix DevFrontend : `onCloseRef` + effet focus-trap mount-only (`[]`).
Topologie Git : branche `feature/ticket17-focus-trap-fix` depuis `develop`, commit `4cccd40` (2 fichiers frontend seulement), merge `--no-ff` → `develop` = `2f8467e`, branche supprimée. Vert : tsc clean, 531/531 + test de régression. Voir [[ticket16-menus-floating-delivered-devfrontend]], [[ui-rework-sprint-scoping-contracts]].
1.**Emplacement TicketPicker** : placé sous `features/tickets/`, PAS `shared/ui`, car il dépend du domaine ticket + gateways DI (shared/ui = design system pur). Conforme à tous les autres overlays (SprintManager, MemoryEditor…). Seul `zIndex.ts` (niveau design-system) est dans shared/ui.
2.**`TicketPickerResult.id`/`number`** : `ticket_list`/`TicketSummary` ne portent NI `id` NI `number`. Résolus par un `ticket_read` unique **à la sélection** (`useTicketSearch.resolve`). `number` serait dérivable du ref mais `id` exige la lecture. → contrat `{ref,id,number,title,status,priority}` respecté sans nouveau endpoint. Écart backend candidat : ajouter id/number au summary (dette basse).
3.**`zIndex.ts` provisoire** : lot A (#16) introduit le canonique en parallèle. Valeurs alignées sur l'échelle figée (menuDropdown=40, floatingWindow=50, floatingWindowNested=60, toast=70). **À réconcilier au merge** (garder un seul module). Picker monté à `floatingWindowNested=60`.
## Notes d'impl
- G3-bis respecté : `cursor` traité en token opaque (jamais construit/incrémenté ; relayé verbatim via `loadMore`).
-`excludeRefs` filtré client-side après réception de page.
-`useTicketSearch` léger (pas de groupement sprint, pas de souscription events, debounce 200ms) — ne réutilise pas `useTickets`.
-`TicketFacetsBar` = recherche + facettes statut/priorité (labels aria préservés : `search tickets`, `filter status …`, `filter priority …`, `clear filters`). Le select assignee reste dans `TicketsPanel` (hors périmètre picker).
- Focus-trap inline (Tab cycling + Escape + restore focus) car aucun util partagé n'existe ; G5 respecté (montage niveau chrome par le consommateur #17/#19).
-`selectionMode:"multi"` câblé de façon extensible (footer réservé) mais non actif en V1.
QA repasse derrière. Consommateurs #17 (link) et #19 (sprint) importent `TicketPicker` depuis `@/features/tickets`.
description: Root cause et contrat de fix du EACCES (os error 13) au spawn de la CLI d'un assistant de ticket — le preset SandboxPlan::project_read_only handle la classe exec Landlock.
metadata:
type: reference
---
**Symptôme** : ouvrir un assistant de ticket (Codex ou Claude), premier message ⇒ `process error: agent session start failed: <cli>: Permission non accordée (os error 13)`. Indépendant de la CLI.
**Root cause** : `OpenTicketAssistant::execute` (`application/src/ticket_assistant.rs:97`) fabrique un plan bespoke via `SandboxPlan::project_read_only` (`domain/src/sandbox.rs:153`) = grant **RO** sur `project_root` + RW sur le run_dir, posture `Deny`. Dans l'adapter Landlock (`infrastructure/src/sandbox/landlock.rs:70`), un grant RO *handle* la classe read, et `AccessFs::from_read(V1)`**inclut `AccessFs::Execute`**. Le binaire CLI vit hors du project root ⇒ `execve` refusé ⇒ EACCES au `spawn()` sandboxé (`process.rs:248`). Le chemin workspace n'a pas le bug car son plan vient de `compile_sandbox_plan` sous posture `Allow`/write-only ⇒ l'adapter no-op ou ne handle que write, read/exec restent ouverts. Piège déjà documenté dans l'adapter (« enrichment is the launch-path's concern LP4-2 »).
**Fix (backend-pur, zéro port/DTO)** : remplacer le preset par un **write-fence** — ne jamais handle read/exec (pas de grant RO), handle write seul, RW-grant = run_dir + state/home CLI + temp, posture Deny ⇒ écriture projet refusée, binaire/libs/home lisibles-exécutables. Fallback sûr : passer `None` à `factory.start` pour la surface ticket (garde policy MCP + projection advisory). Garde-fous fonctionnels réels = policy MCP `idea_ticket_read/update*` + LP3, pas l'OS-sandbox. Lié à [[ui-rework-sprint-scoping-contracts]].
description: Lot F1 du ticket #28 livré sur feature/ticket28-firstrun-detect-hang — invariant busy/detectProfiles, flag detecting, timeout UI, test de non-régression.
metadata:
type: reference
---
Lot **F1** (frontend) du bug #28 implémenté sur `feature/ticket28-firstrun-detect-hang` (non committé, Git s'en charge).
**Invariant posé** : dans `frontend/src/features/first-run/useFirstRun.ts`, le retour de `busy` à `false` ne dépend d'aucun appel à `detectProfiles`. `busy` n'entoure plus que `firstRunState()` (reload) et `configureProfiles()` (finish). Toute future régression qui remet un `await profile.detectProfiles(...)` sur un chemin gouvernant `busy` re-gèle le wizard.
**Mécanisme** : `runDetection(candidates, { preselect, reportError })` factorise auto-détection et bouton manuel. Le flag `detecting` est relâché par un `setTimeout(DETECT_TIMEOUT_MS = 3_000)`, **jamais** par la promesse — car le `finally` d'un `await` sur une promesse jamais settled (panic Rust ⇒ pas de réponse IPC) n'est jamais atteint. Un compteur `detectRun` invalide les rounds périmés (re-clic, unmount).
**Piège UI à retenir** : `<Button loading>` implique `disabled` dans le design system (`shared/ui/Button.tsx:46`). Donc `detecting` ne peut pas être surfacé via `loading` sur les boutons Save/Detect sans réintroduire le grisage — il est rendu comme un `role="status"` « Detecting… » séparé.
**Point de vérité QA** : `FirstRunWizard.test.tsx`, describe « detection that never answers (ticket #28) », gateway `HangingDetectGateway` dont `detectProfiles` renvoie `new Promise(() => {})`. Vérifié à teeth : en réintroduisant `setBusy(true); await runDetection(...)` dans `reload`, 2 des 3 tests échouent avec exactement le symptôme du ticket.
Vert : `npx vitest run` 59 fichiers / 569 tests, `npx tsc --noEmit` exit 0. Pas de lint dans ce projet (aucun eslint config ni script). F1 débloque l'UI mais **B1 (DevBackend, `AgentRuntime::detect` sync→async) reste requis** pour que la détection remonte réellement un résultat.
description: Cadrage hexagonal du bug bloquant first-run (#28) — panic block_on imbriqué dans detect + découplage busy/détection, contrats et point de vérité QA.
metadata:
type: reference
---
Bug high #28 (build 0.3.0 / develop 3c6cd04) : au first-run, « Save and continue » ET « Detect installed CLIs » restent grisés → wizard impossible à terminer.
**Cause racine unique (bloquante) — CONFIRMÉE source :**`AgentRuntime::detect` est resté `fn` synchrone (`domain/src/ports.rs:754`, impl `infrastructure/src/runtime/mod.rs:182`) alors que le trait est déjà `#[async_trait]`. L'impl bridge le spawner async via `futures_block_on`/`block_on` (mod.rs:228-237), appelé DANS la commande async Tauri `detect_profiles` → `DetectProfiles::execute` (async) → `detect()` sync sur la même task → tokio panic « Cannot start a runtime from within a runtime ». Un panic dans une commande async Tauri = **aucune réponse IPC** → promesse `invoke` jamais résolue/rejetée → le try/catch interne (`useFirstRun.ts:79-90`, n'attrape que les rejets) ne l'attrape pas → `finally` jamais atteint → `busy` figé true → boutons `disabled: vm.busy` grisés à vie. Se déclenche au 1er profil sondé, toute machine. Déclencheur nouveau de #14 : auto-détection à l'ouverture dans `reload`.
**Cause secondaire (NON bloquante) :** profil `ollama-openai-compatible` (command `openai-compatible`, `detect:None`) → fallback `openai-compatible --version` (binaire absent) → `available:false` erroné (spawn échoue vite, pas de hang). Endpoint HTTP sondé comme un CLI = catégorie-fausse. base_url dispo via `HttpChatConfig` (http://localhost:11434/v1). Précédent réutilisable : `infrastructure/src/store/embedder.rs:401 detect_ollama` (reqwest, timeout 800ms, feature vector-http).
**Fix figé — Frontend (DevFrontend) :** F1 découpler `busy` de la détection : `busy` n'entoure que `firstRunState()`, relâché dès rendu des lignes. Auto-détection = étape détachée non bloquante suivie par un flag distinct `detecting` (ne grise pas Save), avec timeout UI (~3s). Invariant : retour de busy à false ne dépend JAMAIS de detectProfiles.
**Contrats :** port `AgentRuntime::detect` sync→async (validé Architecture). DTO detect_profiles INCHANGÉ (available:bool). Catalogue ollama : detect reste None, détection décidée par nature endpoint.
**Point de vérité QA :** Backend = test `#[tokio::test]` multi-thread sur DetectProfiles::execute (échoue au panic sur code actuel, passe après B1) + endpoint sans spawn CLI + CLI Claude/Codex inchangé. Frontend = RTL avec mock detectProfiles jamais résolu → Save+Detect actifs dès firstRunState. Live = first-run machine sans Claude/Codex, Ollama coché, boutons actifs, Save termine (rebuild AppImage obligatoire). F1 seul débloque déjà ; B1 supprime la cause racine ; les deux requis pour vert.
description: Topologie du frontend des annonces inter-agent (store borné, preview demandeur filtré requester, overlay cible piloté par le busy/idle par agent) — réintégré sur base develop.
metadata:
type: project
---
Branche `feature/agent-live-announcements` (base develop `ebd992e`, non commité). Frontend réintégré depuis `backup/announcements-work-20260704`. Feature `frontend/src/features/announcements/` :
-`announcementsStore.ts` : index pur `byTarget[target][ticketId]->Announcement[]`, `appendAnnouncement` borné (cap 50), `purgeAllForTarget` (au busy:false), sélecteurs target/requester. Éphémère, jamais persisté. (`purgeTurn` du backup RETIRÉ — plus de signal final sur develop.)
-`AnnouncementsProvider.tsx` (monté dans App.tsx) : 1 abonnement `system.onDomainEvent`. Deux signaux : CONTENU = `agentAnnouncement`→append ; CYCLE DE VIE = map `busy` par agent, seule autorité de montage/retrait, alimentée par `agentBusyChanged{agentId,busy}` + hydratée du read-model `ProjectWorkState.agents[].busy` via `useHydrateAgentBusy(projectId,agentId)` (seed « si absent » → event live gagne). `busy:false`→purgeAllForTarget. Hooks `useTargetAnnouncements` (active=busy[target]), `useRequesterAnnouncements`, `useHydrateAgentBusy`. Hors provider = store inerte.
- F3 `TargetAnnouncementsOverlay(projectId,agentId)` monté dans **`LayoutGrid` LeafView** (PAS ConversationCell — absent sur develop ; la cellule agent est le PTY `TerminalView`). Overlay `pointer-events-none`, z-20, au-dessus du write-portal overlay existant. F2 `AnnouncementsPreview` par ligne d'`AgentsPanel` (requester==a.id).
ADAPTATION develop appliquée : `agentTurnEvent{final}` N'EXISTE PAS sur develop → branche de purge retirée du provider, type NON réintroduit dans domain, 2 tests agentTurnEvent supprimés. Autorité unique F3 = busy. Confirmé présents sur develop : `agentBusyChanged{agentId,busy}` (domain/index.ts) et `AgentWorkState.busy: {state:"idle"}|{state:"busy";ticket;sinceMs}`. DTO annonce backend confirmé (events.rs, test JSON à events.rs:927) : `{type:"agentAnnouncement",projectId,requester("user"|agentId),target,ticketId,text,atMs}` — mirroir ajouté dans domain/index.ts.
Exclusion des voiles (arbitrage [[ticket4-overlay-composition-leafview]]) : le voile write-portal (injection PTY) et l'overlay F3 partagent le conteneur relatif du LeafView. Prédicat pur exporté `shouldShowWritePortalVeil(agentPinned, writePortalOverlay, busyActive) = agentPinned && overlay && !busyActive` ; F3 se gate sur le même `busyActive` (`useTargetAnnouncements(agentId).active`, remonté au LeafView). Résultat : exactement UN voile, F3 prioritaire. Purement visuel — `portal.isSuspended()`/l'injection PTY (TerminalView) INCHANGÉS. Testé exhaustivement (table de vérité) dans `overlayExclusion.test.ts`.
Écarts tranchés à signaler : (1) F3 hébergé dans la cellule PTY (LeafView) faute de ConversationCell ; (2) busy est l'autorité brute : sur develop busy vient du mediator (délégations/inter-agent), pas du typing PTF humain, donc proxy correct, mais surveiller un éventuel over-trigger ; (3) F2 filtre requester seul (pas de ticketId par ligne) = garde anti-fuite.
description: Règle de composition des deux voiles plein-cellule de LeafView (write-portal injection PTY vs F3 annonces busy) — exclusion mutuelle avec priorité F3, arbitrée pour éviter le double voile.
**Problème vérifié :** LeafView héberge DEUX voiles plein-cellule inset:0 dans le même conteneur relatif :
- write-portal (LayoutGrid.tsx:841, zIndex:4, rgba(0,0,0,0.45), « Un agent est en train de parler… ») = délégation injectée dans le PTY de l'agent (§20.3, flag `overlay` de useWritePortal).
- F3 TargetAnnouncementsOverlay (z-20, bg-canvas/85, « …en train de LUI parler… » + réflexions) = agent busy (agentBusyChanged mediator).
Ils rendent le MÊME événement depuis deux signaux et montent ENSEMBLE sur le chemin d'injection inter-agent (overlay+busy) → double voile empilé + bannières dupliquées = rendu cassé, chemin nominal.
**Décision (DevFrontend, layout, zéro backend) :** exclusion mutuelle, un seul voile à la fois, priorité F3 :
- sinon overlay → write-portal seul (repli fenêtre sans busy).
- jamais les deux.
Sécurité : on ne touche que le visuel ; la suspension des frappes (portal.isSuspended(), TerminalView.tsx:182) reste ; F3 est pointer-events-none.
**Couplage point 3 :** F3 DOIT rendre la bannière sur busy même à 0 annonce (déjà: if(!active) return null, TargetAnnouncementsOverlay.tsx:56), car F3 absorbe le voile write-portal — une garde « ≥1 annonce » rouvrirait le trou d'indication. Pas de garde.
**F2 (point 4) :** filtre requester==id de ligne suffit en V1 (isolation fil partagé) ; ticketId optionnel dormant.
**F3 hébergement (point 1) :** conteneur LeafView validé (pas de ConversationCell sur develop).
Liens : [[ticket4-restart-on-develop-ebd992e]], [[ticket4-f3-overlay-driven-by-agentbusychanged]], [[inter-agent-live-context-shared-per-agent]].
description: Cadrage du redémarrage des annonces inter-agent sur la base pré-canonique develop ebd992e — ce qui existe, l'écart live, la réutilisabilité du backup et le découpage B/F.
metadata:
type: reference
---
Redémarrage #4 sur base réalignée main=develop=feature/background=feature/agent-live-announcements=ebd992e (pré-canonique). Vérifié sur le dépôt le 2026-07-04.
**Signaux sur develop :**
- Émission intermédiaire : AUCUNE. codex.rs draine via run_turn BATCH (process.rs:268/321 lit ligne-à-ligne mais collecte en Vec, renvoie à EOF ; codex.rs:260 Box::new(events.into_iter())). structured.rs:266-271 jette TextDelta/ToolActivity/Heartbeat, ne renvoie que Final.
- Fix Final : PRÉSENT (62915ee) mais dégrade les agent_message intermédiaires en ToolActivity{label} en JETANT leur texte, et reste batch → inexploitable pour annonces.
**Écart live (faisable SANS moteur canonique) :** run_turn lit déjà incrémentalement ; le batch n'est qu'un artefact de sa signature ->Vec. Ajouter un tap Option<mpsc::Sender<String>> dans run_turn/drain/drain_sandboxed (boucle existante) ; l'orchestrateur (qui détient requester/target/ticket) publie DomainEvent::AgentAnnouncement au fil de l'eau. Thread Landlock n'envoie que des lignes brutes ; publication bus hors thread. Pas de spawn_turn/AgentTurnEvent/ConversationId.
**Réutilisabilité backup (backup/announcements-work-20260704, 691b2ce) :** frontend features/announcements/ (store borné, provider, AnnouncementsPreview=F2, TargetAnnouncementsOverlay=F3) réutilisable ; F3 sur busy = intact sur develop. Dépend d'agentAnnouncement{requester,target,ticketId,text,atMs} (absent→B) et agentTurnEvent{final} (absent, secondaire → RETIRER du store). Backend backup non réutilisable (canonique) ; contrat/attribution repris.
**Découpage :** B1 domaine/appli (ReplyEvent::Announcement + DomainEvent::AgentAnnouncement + publish au drain) ; B2 infra = CŒUR (tap mpsc process.rs + codex émet Announcement{text} live) ; B3 relay/DTO ; F1 store (–hook final) ; F2 preview (filtre ticketId+requester==self) ; F3 overlay (réutilisé, busy seul autorité).
Liens : [[ticket4-f3-overlay-driven-by-agentbusychanged]], [[ticket4-announcements-reintegration-topology]], [[inter-agent-announcements-feature-and-codex-final-bug]], [[inter-agent-live-context-shared-per-agent]].
title: Validation QA backend B1-B4 du système de plugins #43
type: reference
description: Verdict QA du périmètre backend plugin B1-B4 sur feature/ticket43-plugin-system, tests ajoutés et limites sandbox constatées.
---
# Validation QA backend B1-B4 du système de plugins #43
Sur `feature/ticket43-plugin-system`, QA a validé le périmètre backend B1-B4 sans modifier le frontend.
Tests/ajustements QA ajoutés :
-`crates/domain/tests/layout.rs` : les helpers de tests existants ignorent explicitement `LayoutNode::CustomPluginLayout(_)`, ce qui rétablit l'exhaustivité de compilation des tests domaine après l'ajout du layout plugin custom.
-`crates/application/src/plugin/mod.rs` : tests in-memory des ports plugin pour vérifier :
- catalogue runtime vide pour `Disabled`, `PendingUninstall`, `Invalid` ;
- réconciliation MCP limitée aux plugins enabled + serveurs `autoStart`, identité `plugin:<pluginId>:<serverId>` et résolution sous `pluginRoot` ;
- disable stoppe le MCP, publie `PluginDisabled`, retire le plugin du runtime catalog ;
- uninstall stoppe le MCP, retire registry + package et publie `PluginUninstalled`.
Commandes vertes constatées :
```text
cargo test -p application plugin --offline --no-fail-fast
# serde roundtrip custom plugin layout, infrastructure 3 plugin tests, dto plugin filtered test all ok.
cargo test -p app-tauri --test dto_plugins --offline --no-fail-fast
# 2 passed; 0 failed
cargo fmt --check
# OK
```
Workspace complet :
```text
cargo test --workspace --offline --no-fail-fast -- --test-threads=1
```
Résultat KO attendu dans ce sandbox, hors périmètre plugin :
-`-p infrastructure --lib` : 10 échecs `session::openai_compat::*` sur `bind: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }`.
-`-p web-server --lib` : 2 échecs de bind `127.0.0.1:0 Operation not permitted` et 5 scénarios websocket/terminal recevant `error` au lieu de `terminal.attached`, cohérents avec restrictions loopback/PTY du sandbox.
Verdict : backend plugins B1-B4 validé QA avec réserve uniquement environnementale sur le workspace global.
Contexte : après verdict QA orange initial, Architect a figé le carnet v5 : `customPluginLayout` top-level canonique, `gitRepository` obligatoire en F3, `${appDataDir}` obligatoire en B4, et `agentSelected`/`terminalFocused`/`layoutCellFocused` + persistance backend state layout plugin acceptés comme dette v1.
-`LayoutGrid` route `customPluginLayout` vers `PluginLayoutCellView`.
-`onStateChange`, `onOpenPlugins`, `onChooseAnotherLayout` sont câblés in-session (`setPluginLayoutState`, navigation settings plugins, remplacement par terminal).
-`ProjectsView` câble `gitRepository` via `GitGateway.branches(active.id)` ; `agentSelected`, `terminalFocused`, `layoutCellFocused` restent explicitement `false` en dette v1.
- Backend application substitue `${pluginRoot}` et `${appDataDir}` dans command/args/env/cwd des specs MCP plugin, avec test `reconcile_mcp_substitutes_app_data_dir_in_plugin_server_specs`.
Verdict QA : VERT pour merge local du ticket #43.
Dette v1 assumée :
-`agentSelected`, `terminalFocused`, `layoutCellFocused` non câblés, figés à `false` jusqu'à un lot focus/selection dédié.
- Persistance backend du `state` de layout plugin non implémentée ; F4 validé en in-session selon arbitrage Architect.
Aucun nouveau blocage détecté dans les suites demandées.
description: Overlay plein-cellule de préparation du serveur modèle local + progression (barre/%/bytes/source), corrélation factorisée, priorité de voile.
metadata:
type: reference
---
Ticket #54 livré et vert côté frontend (F1 = overlay, F2 = progression).
**Contrat** : `ModelServerStatus.downloading { downloadedBytes|totalBytes|percent|source: number|string|null }` (`frontend/src/domain/index.ts`), miroir exact de `ModelServerStatusDto` (`crates/app-tauri/src/events.rs`). Event `modelServerStatusChanged` passé tel quel — pas de mapping de désérialisation.
**Factorisation** dans `frontend/src/features/agents/modelServerLaunch.ts` : `correlateModelServerStatus` (chaîne `agentId→profileId→opencode.localModelServerId→serverId`), `modelServerOverlayText` (titre+gating de voile), `useModelServerLaunchState(projectId)` (source autonome pour LeafView), et F2 : `formatBytes` (SI base 1000 : o/Ko/Mo/Go) + `describeModelServerDownload` → `{percent(clamp 0..100 ou null=indéterminé), bytesLabel, source}` (null hors `downloading`). AgentsPanel réutilise le helper.
**Overlay** dans `LeafView`/`LayoutGrid.tsx` (`data-testid=model-server-overlay`, `zIndex CELL_Z.veil`) : titre + barre `role=progressbar` (aria-valuenow seulement en déterminé, sinon barre indéterminée via keyframe `model-server-indeterminate` dans `theme.css`) + label `"X % · A Mo / B Mo"` + ligne source HF. `starting`/`probing` = titre seul, pas de barre. **Priorité : voile modèle au-dessus** des voiles write-portal + F3 (les deux gatés sur `!modelServerOverlay`). Voir [[ticket4-overlay-composition-leafview]].
description: Fix frontend du sélecteur d'agent : la désactivation « visible ailleurs » doit venir du layout courant, pas du dernier nodeId du live registry.
metadata:
type: reference
---
Bug #56 : agent X affiché en cellule A → A bascule sur Y → X désactivé (« visible ailleurs ») en cellule B alors que X n'est plus affiché nulle part.
Cause : le live registry garde X→nodeId A même après le swap ; `LayoutGrid.visibleElsewhere` lisait `live.nodeId` comme « affiché ici ».
Fix (frontend pur, ne tue pas la session) dans `frontend/src/features/layout/LayoutGrid.tsx` :
-`visibleElsewhere(candidate)` dérive du **layout courant** : truthy uniquement s'il existe une feuille visible ≠ cellule courante avec `leaf.agent === candidate`. Retourne `{ nodeId }`.
-`backgroundLive` utilise `!visibleElsewhere(candidate)` au lieu de `!visibleNodeIds.has(live.nodeId)` ; garde `live.nodeId === id` (anti-boucle self-launch).
- onChange du select : même dérivation ; sélection inchangée (attachLiveAgent si sessionId sinon setCellAgent).
- Prop `visibleNodeIds` supprimé (devenu mort) de tout le threading LayoutGrid→NodeView→Split/Grid/Leaf.
Invariant préservé : agent réellement épinglé dans une cellule visible reste NON sélectionnable ailleurs.
Piège de test : un test qui mocke `listLiveAgents` avec un nodeId visible mais **sans** épingler l'agent dans ce leaf ne désactive plus rien — il faut `setCellAgent` réel. Tests dans `singletonAgent.test.tsx` (describe « ticket #56 » + ajustement R0d). Suite frontend : 706 verts.
description: Écart backend ticket #7/F2 — le rendez-vous délégué n'émet pas AgentRateLimited ni n'arme la reprise pour la cible limitée.
metadata:
type: project
---
Ticket #7, volet inter-agent. Constat F2 (frontend DevFrontend) sur `feature/session-limit-interagent`.
Le chemin délégué (rendez-vous headless) **n'appelle jamais**`session_limit_service.on_rate_limited(target, …)`. Dans `crates/application/src/orchestrator/service.rs:1876-1915`, la branche `TargetRateLimited` publie **uniquement**`DomainEvent::DelegationRateLimited{requester, target, resets_at_ms}` puis retourne `AppError::TargetRateLimited`. Les deux seuls sites de `on_rate_limited` sont les chemins humains PTY/chat (`crates/app-tauri/src/commands.rs:1409` et `:1535`).
**Conséquence** : quand la cible B est limitée **via une délégation**, aucun `AgentRateLimited`/`AgentResumeScheduled` n'est émis pour B ⇒ le badge mono-agent de B (piloté par `limitByAgent` dans `useAgents`) ne s'affiche pas et aucune reprise auto n'est armée pour B. Contredit le commentaire de `crates/app-tauri/src/events.rs:370-372` (« the target resume flow is surfaced by the existing agent limit events »).
**Why:** F2 supposait « même flux AgentRateLimited ». Le rendu frontend du badge est sain (re-render + bouton Annuler OK) ; l'écart est backend.
**How to apply:** correction = backend (appeler `on_rate_limited` pour la cible dans la branche rendez-vous, avec node/conversation adéquats). Ne PAS dériver le badge de B depuis `delegationRateLimited` côté front : cet event ne porte ni sémantique de reprise armée ni node_id/conversation_id pour armer le réveil → inventerait un contrat. Le côté DEMANDEUR (A) est, lui, bien couvert par F1 (`suspendedDelegationsByRequester`). Voir [[session-limit-handling-design]].
description: Deux artefacts Vite (dist/dist-web), mode `web` portable Windows, et le câblage anti-divergence de la constante de transport.
metadata:
type: reference
---
Le frontend produit **deux** artefacts Vite depuis les mêmes sources :
-`frontend/dist` → transport `tauri` (desktop, `build.frontendDist`) — `npm run build`.
-`frontend/dist-web` → transport `http` (packagé en ressource Tauri `web/`) — `npm run build:web`.
-`npm run build:bundle` produit les deux : c'est le point d'entrée du packaging Tauri.
**Le mode web passe par `--mode web` + `frontend/.env.web` (`VITE_TRANSPORT=http`), jamais par un préfixe inline `VITE_TRANSPORT=http vite build`** : non portable Windows/NSIS, cible active du bundle. `.env.web` est versionné (non gitignoré) ; `dist-web/` est ignoré.
**Anti-divergence de `__IDEA_TRANSPORT__`** : `frontend/src/app/transport.ts` expose `transportFromEnv(value)`, importé à la fois par `di.tsx` (`resolveTransport`/`shouldUseHttp`) et par `vite.config.ts`, qui lui passe `VITE_TRANSPORT` via `loadEnv(mode)`. Même variable + même prédicat = une seule source. Modifier le prédicat déplace constante et runtime ensemble. `vitest.config.ts` réplique le `define` via le même helper.
La constante est **publiée sur `window` dans `main.tsx`** : `define` ne remplace que les occurrences existantes et une constante non référencée serait tree-shakée ; l'affectation est un effet de bord qui survit à la minification. Elle reporte le transport hors mock (`VITE_USE_MOCK` reste un switch distinct).
Pour identifier le transport d'un bundle bâti : `grep -o '__IDEA_TRANSPORT__="[a-z]*"' dist*/assets/*.js`. **Ne pas se fier à la présence de `__TAURI_INTERNALS__` ni à un nom de symbole minifié** : les deux jeux d'adapters sont présents dans les deux bundles, un grep ne les discrimine pas.
Le rendez-vous `idea_ask_agent` (`run_inactivity_watchdog`, `crates/application/src/orchestrator/rendezvous.rs`) coupe en **faux `NoReply`** toute cible **structurée** dont le tour dépasse la fenêtre d'inactivité (défaut 600 s, = `turn_timeout_ms` du profil cible). Cause : la seule sonde de vivacité (`transcript_activity_token`) lisait le transcript Claude (`~/.claude/projects/<encoded-cwd>/*.jsonl`) — invisible pour OpenCode/Codex, et fragilise même Claude en mode `-p` headless si le transcript ne grossit pas mi-tour. `has_probe=true` mais la sonde renvoie `None` ⇒ bras `(true,_,None)=>false` ⇒ aucun réarmement ⇒ NoReply à la première fenêtre.
2.**Machinerie** (`crates/infrastructure/src/session/process.rs`) : `run_turn_with_activity(...)` bump un `Arc<AtomicU64>` à **chaque ligne stdout lue** (drain async ET drain sandboxé thread). `run_turn` (4 args) délègue avec `None` ⇒ les ~11 call sites de test sont intacts.
3.**Sessions** : `ClaudeSdkSession`, `CodexExecSession`, `OpenAiCompatibleSession` overrident `activity_token()` via `run_turn_with_activity` ; `OpenCodeSession` (drain inline) bump son propre compteur par ligne JSONL.
4.**Sonde composite** (`resolve_ask_liveness_token`, `crates/backend/src/lib.rs`) : préfère `session.activity_token()` de la session vivante (`structured.session_for_agent`), repli inchangé sur le transcript Claude. Câblée par `.with_ask_liveness_probe`.
## Contrats clés
-`has_probe` reste `true` dès qu'une sonde est câblée ; la sonde composite renvoie désormais `Some(token)` qui avance ⇒ la fenêtre se réarme. Le repli transcript Claude couvre le cas « session vivante sans override ».
- La fenêtre = `turn_timeout_ms` du **profil cible** (`turn_timeout_for`→`liveness_for_agent`), défaut `ASK_AGENT_TIMEOUT` 600 s. **Pas d'env global pour la fenêtre** (seul le plafond a `IDEA_ASK_RENDEZVOUS_CEILING_MS`, défaut 4 h).
- Pour un test live rapide sans attendre 600 s : mettre un petit `turn_timeout_ms` (ex. 20 000) sur le profil de la cible, puis déléguer une tâche de ~60-90 s.
## État validation
- Tests unitaires verts : `run_turn_with_activity_bumps_counter_per_line`, `*_session_activity_token_advances_across_a_turn` (claude/codex/opencode), + la sonde composite backend + le watchdog rendezvous existants.
- AppImage 0.3.0 rebuildée + installée (`/home/anthony/Documents/IdeA_0.3.0_amd64.AppImage`, backup `.old-pre95fix`). **Relance IdeA requise** pour l'activer (le binaire qui tourne = AppImage en mémoire ; relancer depuis l'intérieur tue la session courante).
## À noter (dette / hors périmètre)
-#96 (EffectivePermissions assistants de ticket) laissé ouvert, documenté dans le carnet #96 (décision produit différée).
- Le souvenir utilisateur initial décrivait un échec « demandeur GLM/OpenCode → cible Claude ». Mécaniquement le watchdog est **ciblé-cible** (keyed sur l'agent cible) : un échec sur cible Claude longue n'est possible que si le transcript Claude `-p` ne grossit pas mi-tour (couvert désormais par l'`activity_token` de ClaudeSdkSession). La théorie principale reste « cible structurée longue non observée → faux NoReply ».
Wizard first-run : profiles.json contient à la fois `opencode` (llamacpp) et `opencodeProvider` (cloud). Lecture priorise `opencode` → llamacpp l'emporte silencieusement.
## Root cause (vérifiée dans le code)
1.`SaveOpenCodeProviderProfile::execute` (`crates/application/src/agent/usecases.rs:364-367`) pose `opencode_provider` sans faire `opencode = None`.
2. Invariant `opencode_backend_is_consistent` (`crates/domain/src/profile.rs:1220`) existe + testé mais **jamais appelé** hors tests (garde morte).
- **Lot** : un seul lot cohérent. Backend = autoritaire (invariant + garde + use case via builder + migration). Frontend = strip de la config inactive au save selon le mode (requis pour les chemins SaveProfile/ConfigureProfiles qui ne portent pas d'intention explicite). Découplés/parallélisables. DevBackend + DevFrontend + QA.
- **Frontière** :
- Domaine : builders `with_opencode` (`profile.rs:1132`) et `with_opencode_provider` (`profile.rs:1140`) doivent imposer l'exclusion mutuelle (chacun efface l'autre). Aujourd'hui ce sont des setters muets = la faille.
- Infrastructure : `FsProfileStore::save` rejette tout profil violant l'invariant → `AppError::Invalid` (c'est ici que le prédicat enfin s'appelle). Défense en profondeur.
- Application : use cases utilisent les builders, jamais la mutation brute de champ.
- Refuser l'ad-hoc `opencode = None` par use case (DRY, 4 chemins d'écriture).
- **DTO** : garder `SaveOpenCodeProviderProfileRequestDto` (`dto.rs:1148`) whole-profile (zéro cassure frontend). Le `opencode` parasite devient inoffensif car le use case reconstruit via builder. Output = profil normalisé, autorité pour le frontend. Corriger aussi SaveProfile (`usecases.rs:269`) et ConfigureProfiles (`usecases.rs:460`).
- **Duplication résolution** (lifecycle.rs:2367 + assistant/mod.rs:252) : dette hexagonale préexistante, NE PAS élargir ce lot. Suivre via un futur `AgentProfile::effective_opencode_backend()` ou générateur partagé.
- **Migration REQUISE** : profils déjà corrompus sur disque portent les deux. Recovery : à la lecture (ou passe de migration), quand les deux présents, dropper `opencode` stale (l'intention est cloud, le bug ne venant que d'une action cloud explicite). Sans cela, #97 ne corrige que les nouveaux profils.
## Sites de résolution (priorité) = exactement 2
-`crates/application/src/agent/lifecycle.rs:2367`
-`crates/infrastructure/src/assistant/mod.rs:252`
Autres accès `.opencode` scoper en mode local, non affectés : `lifecycle.rs:2455-2468`, `model_server.rs:121-127`, `catalogue.rs:244-258`, `usecases.rs:410`.
## Statut
Décision Architect posée. À faire implémenter par DevBackend (domaine builders + garde store + migration + use cases) et DevFrontend (strip au save). QA : tests domaine/store + intégration cloud + scénario migration profils corrompus.
description: Cadrage figé du fix #98 (modèle écrasé par glm-5.2 au spawn pour provider catalogue cloud ex: zai) : cause racine = asymétrie picker↔spawn, approche B2 = semer best-effort le cache models.dev hôte dans le XDG_CACHE_HOME isolé.
Wizard profil OpenCode first-run : provider catalogue cloud (ex: zai/ZAI Code) + modèle choisi → après sauvegarde l'agent tourne en `glm-5.2` quel que soit le modèle. Persistance correcte (`profiles.json` conserve `opencodeProvider.model`). `glm-5.2` est le **fallback interne d'OpenCode**, absent du code IdeA.
## Cause racine (confirmée + affinée par Architect)
**Asymétrie picker↔spawn**, pas seulement « pas de bloc models » :
1.**Picker** (`provider_catalogue.rs:79-84,150-153`) : lit le cache models.dev via `opencode_models_cache_path()` qui résout le **vrai** cache hôte (`XDG_CACHE_HOME ?? ~/.cache`). Permet d'afficher zai + ses modèles.
2.**Spawn** (`lifecycle.rs:2386,2400-2407` ; `assistant/mod.rs:272,277-285`) : IdeA **ré-isole**`XDG_CACHE_HOME` → `.opencode/cache`**vide** sous `run_dir`. OpenCode ne voit plus le cache.
3.**Rendu** (`opencode_provider_config_json`, `lifecycle.rs:2795-2821` / `assistant/mod.rs:446-466`) : pour `config.custom == None` (défaut pour zai), IdeA n'émet que `apiKey` + `model: "zai/<m>"`, **sans** bloc `models`. Correct **uniquement** pour les 3 built-ins hardcodés dans le binaire OpenCode (`anthropic`, `openai`, `openrouter`).
→ OpenCode reçoit `model: "zai/<m>"`, ne reconnaît pas zai comme built-in, ne trouve pas le cache models.dev (isolé vide) → fallback silencieux glm-5.2.
- **3 built-ins marchent** : hardcodés dans OpenCode.
- Bug ne touche **que** les providers connus d'OpenCode *exclusivement via models.dev*.
## Approche figée : **B2 — Semer le cache models.dev isolé depuis le cache hôte**
Après création du `XDG_CACHE_HOME` isolé, copier **best-effort** le fichier cache models.dev hôte (`opencode_models_cache_path()` → `<isolated_cache>/opencode/models.json`) via le port `FileSystem`. Lecture hôte = `std::fs` (identique au picker) ; écriture dans la home isolée.
**Pourquoi pas les autres :**
- **(A)** Émettre un bloc `models`+`npm`+`baseURL` pour les catalogue → **écartée en v1** : IdeA devrait capturer le bon `npm` AI-SDK par provider depuis models.dev (`@ai-sdk/anthropic` vs `openai-compatible`…) — dupliquerait la connaissance du registry OpenCode (violation OCP), risque de régression built-ins. Gardée en **escalade** si B2 défait par refresh.
- **(B1)** Pointer vers le vrai cache hôte (ne plus isoler) → **écartée** : casse l'isolation en écriture (pollution + race entre sessions).
- **(C)** Bundler une copie statique models.dev dans IdeA → **écartée** : staleness + diverge picker/spawn.
**Auto-cohérence B2** : la précondition du bug (cache hôte présent au picker) garantit la précondition du fix (fichier à copier au spawn). Cache hôte absent → picker ne montre que les 3 built-ins → bug non atteint → fix non requis.
## Contrat figé
### Ports / DTO
- **Aucun nouveau port / entité domaine / DTO modifié.** Réutilise le port `FileSystem` déjà injecté sur le chemin spawn. Lecture source = `std::fs` hôte (mécanisme identique au picker).
- Rendre **publique**`opencode_models_cache_path()` (source de vérité unique — encode le workaround du bug upstream #8235 ; ne **pas** re-dériver).
-`crates/application/src/agent/lifecycle.rs` — branche `apply_mcp_config` (≈2382-2408) : après `create_dir_all` du cache + création `xdg_cache`, semer `<xdg_cache>/opencode/models.json` depuis le cache hôte, best-effort. Branche **commune** opencode + opencodeProvider.
-`crates/infrastructure/src/assistant/mod.rs` — branche miroir (≈268-285) : même appel.
- **Factoriser** : `application::agent::seed_opencode_models_cache(fs: &dyn FileSystem, isolated_cache_dir: &str)` appelée par les deux sites. Partie **pure** testable `seed_from_bytes(fs, dest, src_bytes)` ; wrapper impur `std::fs::read` fin.
### Invariants (stricts)
1. Isolation préservée en **écriture** : HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME restent sous `run_dir`. Aucune var d'env repointée vers l'hôte. Seul le **contenu** du cache isolé est semé (lecture seule).
2.**Best-effort, n'échoue jamais le launch** : source absente/illisible ou erreur FS → pas d'échec du spawn. Symétrique du contrat best-effort existant de `apply_mcp_config` (`lifecycle.rs:2422-2425`). ≠ `resolve_opencode_provider_api_key` (échec dur). Le seed est **mou**.
3. Pas de régression local/custom : `opencode_config_json` (llamacpp) et `opencode_provider_config_json(custom==Some)`**byte-identiques** (rendu non touché ; seed additif orthogonal).
4. Pas de régression built-ins : anthropic/openai/openrouter (`custom==None`, hardcodés) restent fonctionnels.
5. Exclusion mutuelle `opencode` vs `opencodeProvider` (#97) : non touchée.
6. Source de vérité unique du chemin models.dev : `opencode_models_cache_path()` réutilisée.
7. Cohérence picker↔spawn : modèles résolvables au spawn = sur-ensemble de ceux du picker (même fichier source).
### Hors périmètre (ne pas faire ici)
- Résolution providers pour **projets remote (SSH/WSL)** (picker lit déjà le cache hôte — incohérence pré-existante). Fix corrige le cas **local**, ne régresse pas le remote.
- Déduplication du rendu lifecycle↔infra (`opencode_provider_config_json`×2) — on factorise **uniquement le seeder**.
- Partie pure `seed_from_bytes(fs, dest, src_bytes)` : bytes présents → assert `<dest>/opencode/models.json` écrit identique ; `None` → aucun fichier **et** pas d'erreur.
- Non-régression rendu : JSON de `opencode_config_json` et `opencode_provider_config_json(custom==Some/None)` byte-identiques (tests existants `lifecycle.rs:4428+` verts).
- ⇒ Ne prouve **pas** la résolution réelle par OpenCode.
2.**Intégration / réelle-exécution (gated, propriété QA)** : spawn réel d'OpenCode avec profil `zai` (`custom==None`) contre un cache models.dev fixture contenant `zai` ; assert **pas de fallback glm-5.2** (modèle `zai/<choix>` effectivement utilisé). Gater `#[ignore]`/env (clé API + réseau). **C'est ce test qui prouve le bug corrigé.**
## Risque résiduel + escalade
- **Inconnu empirique** : est-ce qu'OpenCode au démarrage tente de **rafraîchir** models.dev (écrase notre copie, ou hang sans réseau) ? `OPENCODE_DISABLE_AUTOUPDATE=1` ne vise que l'auto-update du binaire, pas sûr qu'il couvre le refresh models. **QA doit vérifier** sur le test d'intégration. Si le refresh défait B2 → **escalader vers (A)** : capturer `npm`+`baseURL` réels par provider dans le parseur models.dev et peupler `custom`.
## Topologie
- Branche : `feature/ticket98-opencode-modelsdev-cache-seed` depuis `develop@69e5878` (#97 exclusion mutuelle prérequis y est fusionné : `0f0a76d` + merge `5533073`). `crates/` propre.
- Carnet #98 version 2 = source de vérité du cadrage.
description: Surface utilisateur attendue pour supprimer les modèles llama.cpp téléchargés, et règle UX pour les bugs de scroll/fit OpenCode qui doivent être corrigés sans nouvelle UI.
---
# Cadrage UX tickets #70 #100 #102
## #70 — Gestion des modèles locaux téléchargés
Ajouter une affordance humaine de suppression des artefacts de modèles téléchargés par les serveurs locaux llama.cpp, dans la surface existante `Local model servers` / configuration OpenCode locale.
Principes visibles :
- La suppression d'un serveur déclaré et la suppression du fichier modèle téléchargé sont deux actions distinctes.
- Une action destructive sur le fichier modèle doit être explicite, confirmée, et impossible pendant un usage actif/téléchargement du même artefact.
- Le libellé doit parler de `modèle téléchargé`, pas de cache interne ou chemin technique en premier niveau.
- Les modèles issus d'un `localPath` utilisateur ne doivent jamais être proposés à la suppression comme s'ils appartenaient à IdeA.
États attendus par serveur :
- Aucun modèle téléchargé connu : aucun bouton de suppression de modèle, ou bouton désactivé avec tooltip `Aucun modèle téléchargé par IdeA`.
- Téléchargement/préparation en cours : action désactivée, texte `Téléchargement en cours`.
- Serveur/agent utilisant ce modèle : action désactivée, texte `Modèle utilisé par un agent en cours`.
- Suppression en cours : ligne locale occupée, action désactivée, message `Suppression du modèle...`.
- Succès : toast/status non bloquant `Modèle téléchargé supprimé` ; la configuration serveur reste présente.
- Échec : alerte inline `Impossible de supprimer le modèle téléchargé : <raison courte>`.
Confirmation :
- Titre : `Supprimer le modèle téléchargé ?`
- Corps : `Le serveur local restera configuré, mais IdeA devra retélécharger ce modèle au prochain lancement.`
- Si la taille est connue : ajouter `Espace libéré : <taille>.`
- Action principale destructive : `Supprimer le modèle`
- Action secondaire : `Annuler`
## #100 — Scroll OpenCode
Pas de décision UX spécifique. Le comportement attendu est celui d'une cellule terminal native : l'utilisateur peut remonter dans le scrollback OpenCode jusqu'à la limite de rétention disponible, avec molette, trackpad, scrollbar et clavier, sans blocage prématuré propre à OpenCode.
Ne pas ajouter de bouton, message ou mode spécial OpenCode. QA doit valider le comportement visible dans une cellule OpenCode longue.
## #102 — Fit TUI après switch/layout/ajout cellule
Pas de nouvelle surface UX spécifique. Le terminal doit s'afficher correctement automatiquement après switch de projet, switch de layout, ajout/suppression/split/resize de cellules et rattachement d'une session existante.
Ne pas afficher de message demandant à l'utilisateur de redimensionner. Éviter tout flash durable vide/noir ; un voile technique transitoire n'est acceptable que s'il reste très bref et non bloquant.
description: État du frontend du système de tickets — livré, vert, décisions de contrat clés.
metadata:
type: project
---
Frontend V1 du système de tickets livré sur `feature/issue-ticket-system` (non commité, attend QA/Git). Build + 459 tests verts.
Décisions figées :
- Port nommé `TicketGateway` (pas IssueGateway) — aligné sur le wire `ticket_*`/`TicketDto` et le terme visible « ticket ».
- Statut/priorité côté UI passent par `ticket_update` : `ticket_update_status`/`ticket_update_priority` ne sont PAS des commands Tauri (MCP-only, non enregistrées dans `lib.rs`).
- Conflit de version = `code:"INVALID"` + message contenant `"issue version conflict"` (détection par message, pas de code dédié). Voir `isTicketVersionConflict`.
- F7 = `#ref` cliquables + linkification desc + bouton clipboard « Travaille sur le ticket #N : <titre> » (pas d'écriture terminal directe en V1).
Surface : `features/tickets/` (panel liste + overlay détail/carnet/liens/assign), onglet sidebar « Tickets » dans ProjectsView entre Work et Agents, `MockTicketGateway` émet les events `Issue*`. Voir [[checkpoint-issue-ticket-backend-v1-done]] et [[issue-ticket-system-design]].
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.