Le lancement ordinaire d'un agent démarre une nouvelle conversation : freshStart: true sans dérivation du conversationId persisté sur la leaf, freshOnOpen à l'ouverture de cellule, réattach live préservé si session structurée vivante ; l'id persisté devient un pivot runtime/historique au service du resume explicite uniquement — #371 (QA verte: typecheck, 144/144 ciblés, 350/350 agents+layout ; f3476982 ; aucun push).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cause: l'ouverture d'IdeA ou le lancement ordinaire d'un agent dérivait la sélection UI du conversationId persisté sur la leaf (T4b), exposant l'ancien fil par défaut au retour d'application ; la persistance du pivot runtime/historique était confondue avec un choix de reprise.
fix: le lancement ordinaire d'une cellule agent passe désormais freshStart: true sans transmettre le conversationId de la leaf — seul un resume explicite (menu Conversations / sélection intentional) fournit un id ; CustomAgentChatView reçoit freshOnOpen du LeafView, donc une ouverture normale démarre sur une nouvelle conversation quand aucune session structurée live n'existe ; garde de continuité préservée : si une session structurée est toujours vivante (remount transitoire), réattach live sans fresh ; l'id assigné par un lancement reste persisté sur la leaf via setCellConversation mais uniquement comme pivot runtime/historique, jamais comme sélection UI implicite (#371) ; contrats ports/domain/adapters/useLayout réalignés (assignedConversationId, launchAgent, LeafCell.conversationId).
tests: ouverture ordinaire = nouvelle conversation sans readPage ni reprise ; resume explicite toujours fonctionnel ; continuité live au remontage avec session vivante préservée.
QA verte: git diff --check ; npm run typecheck ; vitest ciblé setCellConversation/CustomAgentChatView/resumeProject/LayoutGrid/LayoutGrid.chat 144/144 ; npm test src/features/agents src/features/layout 350/350.
fichiers .ideai/* et scripts/ hors périmètre ; aucun push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cause: le panneau Tâches planifiées n'avait aucune variante dans l'enum ViewPanel, open_view_window("scheduledTasks") rejetait INVALID et detachPanel ne touchait le placement qu'en cas de succès — même famille que #299, aucun effet visible ni message.
fix: variante ViewPanel::ScheduledTasks avec contrat stable label view-scheduledTasks / url index.html?panel=scheduledTasks / titre « Scheduled tasks », identité persistée des fenêtres view-scheduledTasks acceptée à la restauration #50; frontend ViewWindow rend le ScheduledTasksPanel du projet focalisé et ProjectsView expose le placement Fenêtre détachée via WindowGateway; retrait de la variante Projects devenue morte après #315 — le panneau Projects (coquille de la fenêtre principale) n'est plus détachable, refus INVALID gardé par tests.
QA verte: view_window_tests dont scheduled_tasks_view_window_contract_is_stable et projects_panel_is_not_detachable, persisted_identity_accepts_scheduled_tasks_view_labels, frontend ViewWindow/window gateway/ProjectsView.detach verts.
domain: variante ProjectIconSet::Custom, métadonnées CustomProjectIconAsset (id opaque, media type png/jpeg/webp, byte_size, dimensions décodées, sha256, nom de fichier original sans chemin, created_at), CustomProjectIconContent et port CustomProjectIconAssetStore (list/read/exists/create/delete) avec CustomProjectIconStoreError dédiée.
application: use case project_icon_assets — import validé png/jpeg/webp avec rejet du contenu SVG, dimensionnement et digest sha256, bibliothèque partagée à l'instance indépendante de tout projet, suppression refusée tant que l'asset est référencé par un projet.
infrastructure: store fichier store/project_icon_asset.rs — index canonique + octets, création atomique sous extension validée.
backend/web-server: contrat HTTP /api/project-icon-assets (GET liste, POST import, GET lecture, GET /usage, DELETE) avec prévol OPTIONS et tests contrat incluant le rejet SVG.
app-tauri: commandes import/list/read/get_usage/delete_custom_project_icon_asset exposées au frontend.
frontend: port ProjectIconAssetGateway (list/import/delete/getUrl sans exposer de chemin filesystem), gateways http+mock avec tests, DI, section Icônes personnalisées du ProjectIconPicker (import fichier accept png/jpeg/webp, sélection role=option, suppression avec retrait de sélection, erreurs lisibles i18n FR/EN), tests projectIcons étendus.
QA verte: contrats HTTP et Tauri identiques, suites cargo et npm ciblées vertes.
Cause: après un cancel manuel de tour, le ticket médié busy/FIFO n'était pas
retiré (l'agent restait Busy, bouton Stop persistant) et un terminal tardif du
tour annulé pouvait bloquer le tour suivant (Final non affiché).
Backend: StructuredSessions lie chaque génération de tour à son ticket médié
(bind_turn_accounting) et request_turn_cancel restitue l'identité (agent,
ticket) exacte observée avant annulation; CancelAgentChat.with_turn_accounting
retire le ticket exact (cancel_head + mark_idle) seulement s'il est toujours
le ticket courant — jamais un successeur déjà remplacé;
StructuredHumanTurnGuard expose accounting_identity et agent_send (app-tauri)
lie la comptabilité au démarrage du tour humain.
Frontend: CustomAgentChatView réconcilie l'interaction active depuis le
work-state (agent idle sans délégation -> interaction terminée, fallback
« Tour terminé sans réponse finale. ») au lieu de laisser un agentBusyChanged
sans tour-id clore un tour plus récent; les terminaux tardifs (final/error) du
tour read-model-settled remplacent le fallback; tour suivant isolé des
callbacks tardifs de la génération précédente.
Tests: cancel_agent_chat (retrait du ticket exact avant le prompt suivant,
nettoyage tardif sans idle sur ticket successeur), CustomAgentChatView
(isolation tour suivant, réconciliation work-state + remplacement fallback par
Final tardif).
Réserve non bloquante: alignement fixture skills préexistant porté par le
commit test dédié 83659d9a.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
launch_conventionfile_injects_assigned_skills_in_order était rouge en
préexistant (réserves #281/#287): la source projette déjà les refs stables
`skill:global:<id>` et le suffixe (workflow) que les assertions ne suivaient
pas. Alignement pur de fixture, aucun changement de source — cycle #357.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause: la logique de suivi du bas de fil était dupliquée et ad-hoc dans chaque surface (scrollRef, followsLatestRef, showLatestButton dans CustomAgentChatView), et dérivait dans certains cas — le fil ne suivait plus les nouveaux messages.
Fix: nouveau hook partagé shared/lib/useThreadScrollController (resté collé au bas quand l'utilisateur est déjà en bas, position préservée pendant la consultation de l'historique, notice latest sinon) câblé dans CustomAgentChatView, ConversationViewer et TicketAssistantPanel.
QA verte: npm run typecheck exit 0; tests ciblés useThreadScrollController, CustomAgentChatView, ConversationViewer/useConversationThread et tickets verts; npm test 1400/1400 verts (137 fichiers); git diff --check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause: the workspace.execTool sandbox only granted the project root
plus read-only system roots, so CLI tools like git failed when opening
/dev/null with O_RDWR (redirect/exec 3<>/dev/null) inside sandboxed
runs.
Fix: workspace_exec_sandbox pushes one precise /dev/null PathGrant with
RO|RW access; project read effect stays RO|EXEC and no blanket /dev
grant is added, so the widening is limited to the null device;
GitPlugin untouched. Tests: application asserts the read sandbox keeps
project RO|EXEC while granting /dev/null RO|RW
(workspace_exec_read_sandbox_grants_dev_null_without_widening_project_access);
infrastructure proves the local runner executes exec 3<>/dev/null
(local_runner_allows_cli_to_open_dev_null_for_read_and_write).
QA verte reconfirmée par Git: cargo test -p application workspace_exec
5/5, -p infrastructure local_runner_allows_cli_to_open_dev_null 1/1,
-p infrastructure workspace_tool::tests 5/5, cargo fmt --all --check,
git diff --check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause: the monochrome-first treatment from #351 flattened the emoji
artwork through an alpha mask (transparent fill + zero-offset
text-shadow), dropping internal details (the ghost lost its tongue and
eyes), and the watermark box did not occupy the full space available
above the chat composer.
Fix: new .project-cell-illustration-area container (container-type:
size) wrapping the scroll zone so the absolute watermark spans the
whole region between the agent toolbar and the composer; glyph keeps
the OS full-colour emoji artwork as source and is neutralised with a
non-destructive CSS filter (grayscale(1) contrast(0.92)) so internal
tones and details stay visible in black and white; glyph sized to
min(100cqw - 8px, 100cqh - 12px); text-variation selector U+FE0E
stripping removed (full emoji preserved) and project-theme tint
plumbing (accessibleProjectAccent/onProjectThemeChanged) dropped as
superseded by grayscale; empty label and overlay z-layering untouched.
QA verte reconfirmée par Git: npm test -- CustomAgentChatView.test.tsx
projectIcons.test.tsx 96/96, npm run typecheck OK, npm run build OK,
npm test 1396/1396, git diff --check OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Custom CLI composer stays active during busy turns: submitting while the
agent works enqueues the message instead of sending it. Local FIFO queue
scoped per cell (projectId+agentId+layoutId+nodeId), entries carry id,
text, attachments snapshot and createdAtMs; single in-flight
transmission, entry removed at send time, auto-drain only on final
chunk, queue preserved on error/Stop with 'Relancer l'agent' action on
stopped cells. 'En attente · N' panel anchored above the composer:
order, 2-line preview, per-entry edit/delete with transient undo, clear
all with confirmation; main button switches Send / 'Mettre en attente'
with live announcement; layout contract updated (CLI visible while
busy, textarea enabled, Stop available). Chat navigation: round up-arrow
under the agent bar jumps to previous Final, round down-arrow above the
composer appears when not at bottom to jump to latest message,
auto-scroll suspended while reading history, prefers-reduced-motion and
44px a11y targets respected.
Ports unchanged (AgentGateway send/cancel/reattach, restartCellAgentRuntime).
QA verte reconfirmée par Git: targeted suites 115/115
(CustomAgentChatView/LayoutGrid/LayoutGrid.chat), git diff --check;
QA: npm run typecheck OK, npm run build OK.
Réserves documentées: queue renderer-local (non persistée après restart
complet), validation visuelle cellules très étroites restante.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause: the empty-state watermark filled only 92% of a centered box and
kept residual palette colors from host emoji fonts, non-compliant with
the monochrome-first project icon rule (#351 user capture).
Fix: illustration spans the cell with 6px/4px insets and an unclipped
glyph at min(88cqw, 88cqh); emoji painted as monochrome silhouettes via
transparent fill + zero-offset text-shadow tint
(--project-illustration-tint) derived from accessibleProjectAccent when
a project color exists, neutral muted fallback otherwise; empty label
anchored bottom-center and hidden first on constrained containers
(@container 280px width / 240px height); overlay priorities, composer
and z-layering untouched.
QA verte reconfirmée par Git: npm test -- CustomAgentChatView.test.tsx
80/80, git diff --check; QA: ProjectSettingsPanel 12/12, typecheck,
build OK (warning Vite chunk >500 kB préexistant).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Tabs (shared/ui): cible de drop matérialisée par écartement before/after pendant le drag/reorder, reset via rAF sans reliquat, clavier et annonces préservés.
- projectIcons: icônes projet en noir et blanc par défaut, colorisées avec la couleur du projet, occupant la cellule et visibles derrière les messages rendus légèrement transparents.
- Permissions plugin headless: PluginMcpServerSpec transporte l'allowlist exacte authorized_tools regroupée par (pluginId, serverId) depuis les PluginToolRef; projection Codex enabled_tools + default_tools_approval_mode="approve" — une permission déjà accordée au niveau projet/template ne déclenche pas de seconde approbation, un tool non assigné reste hors surface appelable; réserves orchestrator/mcp server+tools alignées, dto backend + dto_agents app-tauri.
- Skills: ref stable plugin:<pluginId>:<skillId> exposée dans le contexte agent, idea_skill_read(ref=...) canonique via read_assigned_by_ref, erreur structurée ambiguousSkillName avec candidates; SkillsPanel dédoublonné (android-build-install); agents-dev/L12-skills.md à jour.
- Docs: ARCHITECTURE.md section 22.3 (projection des autorisations agent vers les runtimes plugin).
- QA verte #350 reconfirmée avant commit.
#346 — capacités plugin SCM: le manifeste permissions.workspaceExec
(contrat SDK 8ff0f47) est appliqué côté hôte — domain PluginWorkspaceExec
Rule/Effect + audit sink, port WorkspaceToolRunner, adapter
LocalWorkspaceToolRunner sandboxé (concurrence 4, timeout, bornes
stdout/stderr, résolution bare executable sans shell), service
application + DTO backend + commandes app-tauri, gateway frontend
execTool exposée au runtime plugin — fin des lectures ad hoc du store
interne .git par les plugins.
#347 — onglets projet réordonnables par glisser-déposer (Tabs DnD,
onReorder, persistance de l'ordre via useProjects).
#348 — stop d'agent CLI custom: le message Final vert réapparaît au tour
suivant (override terminal stop armé correctement, reset de
l'interaction, refresh de la délégation requester).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consolidation locale: le worktree du lot n'avait pas de submodule initialisé;
clone local du checkout principal et alignement sur le commit SDK #346
(docs+runtime workspaceExec). Aucun push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reprend à zéro la liste des icônes projet: catalogue d'emojis Unicode rendus
par la police système, sans embarquer d'assets graphiques propriétaires.
- domain: variante `emoji` ajoutée à ProjectIconSet (serde kebab-case)
- ports TS: union ProjectIconSet alignée avec "emoji"
- frontend: catalogue réécrit en 7 groupes (smileys, nature, food,
activities, travel, objects, symbols), registres légaux et métadonnées
supprimés, assets SVG projet retirés (angular.svg, NOTICE.md)
- watermark CLI custom: rendu emoji direct avec libellé de repli dérivé du
nom de dossier du cwd
- i18n FR/EN des groupes du picker et tests mis à jour (domain settings,
adapter projet, picker, panneau settings, chat custom)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause racine: PluginMcpServerSpec::client_name() encodait chaque octet
non alphanumérique en _XX, produisant pour UnitySoundTool
`idea_plugin__com_2eidea_2eunity_2ddeveloper_2dtools__unity_2ddeveloper_2dtools_2etools`
(86 chars). Codex préfixe `mcp__` => 91 chars, limite API 64 => HTTP 400
`Invalid input.namespace string too long` sur chaque `codex exec resume`
dont l'historique contenait un outil Unity, aucun agent_message, UI en
fallback générique « réponse non finalisée ».
- domain: client_name() borné à 59 bytes ASCII (=> 64 avec mcp__) —
stem lisible (minuscules, non-alnum => '_', tronqué) + suffixe
déterministe = 16 premiers hex de SHA-256(plugin_id + NUL + server_id)
pour conserver la résistance aux collisions après troncature; tests
Unity (59 exact), stabilité, slug ponctuation, collisions sur stems
tronqués identiques
- application: tests de projection TOML/JSON Codex et Claude adaptés au
vrai namespace legacy encodé (86 chars) — vérifie que les tables
utilisateur (mcp_servers.user_server, user-server) sont préservées
pendant le retrait des tables managées périmées
- infrastructure: parse_event Codex traite turn.failed/error comme
ReplyEvent::Error avec le message fournisseur (fini le fallback
générique muet), classification lisible MCP_NAMESPACE_TOO_LONG: pour
les erreurs de longueur de namespace; tests
Après déploiement: nouvelle conversation/restart requis pour les agents
Codex existants — les historiques contiennent déjà le namespace
invalide. Aucun push.
Lot en un seul commit: les quatre tickets partagent les mêmes fichiers
(événements domaine/DTO, ProjectPluginsPanel, useProjectPlugins) — une
séparation par hunks aurait risqué des états intermédiaires non
compilables (convention lot des batches #310-#313, #315 et #325-#326).
#328 — section dédiée plugins dans Paramètres du projet:
- frontend: ProjectPluginsPanel intégré à ProjectSettingsPanel
(Panneaux > Paramètres du projet) — activation/désactivation par
plugin avec état actif/inactif explicite par projet, distinct de
l'assignation agents/skills/tools
- domaine: événement ProjectPluginSettingsChanged publié par
SetProjectPluginEnabled, MutateAgentPluginSkill et
MutateAgentPluginTool après persistance de .ideai/project-plugins.json
(application + backend + DTO camelCase, test dto.rs)
#329 — les menus plugin qui échouent ne sont plus muets:
- usePluginMenus: dispatcher strict (runCommandStrict) pour les
commandes réellement enregistrées au lieu du dispatcher laxiste qui
journalisait et avalait les rejets — rendait les échecs d'ouverture
de fenêtres natifs invisibles pour l'hôte de menus
- le no-op SDK historique (commande déclarée sans handler) est préservé
- échec remonté à l'hôte via commandError/clearCommandError (journal
console + état exposé); test window_usecases adapté à un plugin
installé
- le correctif côté SDK/GitPlugin vit dans le projet GitPlugin (son
commit/merge local y est fait, hors périmètre de ce dépôt)
#330 — refresh automatique des pages:
- frontend/domain: typage des événements pluginEnabled,
pluginDisabled (restartRequired), pluginUninstalled
(restartRequired), pluginLoadFailed et projectPluginSettingsChanged
- useProjectPlugins et PluginRuntimeProvider: souscription
onDomainEvent — refresh sur changement catalogue global ou sur
projectPluginSettingsChanged du projet courant; gardes
révision/projet contre les réponses obsolètes
- useProjectAgents: refresh auto quand un agent est ajouté au projet;
ProjectsView et AgentsPanel suivent (erreur de rafraîchissement
affichée avec bouton Réessayer)
#331 — droplist agents des cellules:
- SmallDropdown: liste bg-overlay + bordure border-strong (plus de
fond blanc hors thème), hover/focus bg-raised, sélection
bg-primary/15, disabled text-faint opacity-60, role=combobox
- LayoutGrid: sélecteur d'agent des cellules migré du <select> natif
vers SmallDropdown (mêmes libellés Terminal/visible
ailleurs/arrière-plan)
QA verte réelle sur les quatre lots (tests ciblés + exécution).
sdk/IdeaSDK: contenu modifié non committable ici (pointeur inchangé).
Aucun push.
Lot en un seul commit: les deux features partagent les mêmes contrats
(domain/settings.rs, DTO, commandes Tauri/HTTP, web-server) et les mêmes
surfaces UI — la séparation par fichiers était impossible sans chirurgie
de hunks risquant des états intermédiaires non compilables (convention
lot des batches #310-#313 et #315).
#325 — icône configurable par projet, watermark CLI custom:
- domaine: ProjectIconSet (devicon, simple-icons, lucide, idea),
ProjectIconRef validé (nom ASCII 1..=64, -_. internes), champ icon
optionnel sur ProjectSettings; sérialisation camelCase et try_from
strict
- application: UpdateProjectSettings.execute accepte un patch
theme_color OU icon (absent = inchangé, null = défaut produit), tests
in-dependants avec store mémoire
- backend/app-tauri/web-server: ProjectIconRefDto, UpdateProjectSettingsRequestDto
avec icon optionnel (désérialisation additive), commandes
update_project_settings alignées Tauri/HTTP, tests DTO et HTTP
(défaut null, set, clear, nom invalide rejeté)
- frontend: catalogue projectIcons (recherche EN), ProjectIconPicker en
grille multi-lignes dans Paramètres projet, affichage de l'icône dans
ProjectTabs et watermark grand format à faible opacité en fond de CLI
custom; ports ProjectSettings.icon, Partial<ProjectSettings>,
adapters tauri/mock/http et tests alignés
#326 — localisation FR/EN et traduction des textes IdeA:
- domaine: IdeaLanguage (fr | en), champ language optionnel sur
IdeaUserSettings (absence = défaut produit, série additive validée)
- backend/web-server: UserSettingsDto.language, update_user_settings
accepte language, tests HTTP alignés; fixtures structured_launch_d3
mises à jour
- frontend: i18n/ (catalogue FR/EN, I18nProvider, scan anti-régression
des sources visibles), GeneralSettings avec sélecteur de langue dans
les paramètres, setupI18n dans vitest; migration de l'ensemble des
textes hardcodés vers le catalogue sur toutes les surfaces (agents,
layout, projets, tickets, sprints, web, plugins, permissions, git,
templates, skills, mémoire, model servers, embedder, first-run,
devices, scheduled tasks, workstate, dialogs), FR conservé par défaut;
recherche d'icônes #325 en anglais conformément au ticket
QA verte sur feature/batch-325-326-project-icons-localisation. Aucun push.
Symptôme: l'historique du composer CLI custom (flèche haut) n'était
écrit dans localStorage qu'via un effet sur l'état, sans garantie au
moment de l'envoi — une fermeture brutale/crash perdait la dernière
entrée.
- writeComposerHistory retourne désormais le succès de l'écriture; à
chaque envoi, l'entrée est ajoutée et écrite AVANT l'invocation de
l'agent (source de vérité par ref + état, réinit de l'index de
navigation et du brouillon d'historique)
- l'effet d'écriture global est supprimé: l'historique n'est plus
réécrit en boucle, seulement aux envois (et lectures au changement de
scope projet/layout/node)
- si l'écriture échoue, l'envoi part quand même et un avertissement
discret « Historique des prompts non sauvegardé. » (role=status)
s'affiche au-dessus du composer
- tests: persistance vérifiée au moment de l'appel sendAgentChat,
envoi conservé + avertissement affiché quand localStorage échoue
QA verte sur fix/batch-322-323-session-restore-prompt-history. Aucun push.
Symptôme: après changement de projet puis retour, une cellule en CLI
custom retombait en TUI native et un nouveau switch CLI custom repartait
à neuf (aucune conversation reprenable) car le mode préféré de la
cellule n'était pas persisté dans l'arbre de layout.
- domaine frontend: type PreferredView (tui | chat) et champ
preferredView optionnel sur LeafCell; opération de layout
setCellPreferredView persistant la surface préférée (tui = défaut,
champ omis pour garder les arbres existants compacts)
- LayoutGrid: le mode de présentation d'une cellule agent dérive de
preferredView persisté; le cache localStorage du mode custom n'est
plus qu'un cache de migration secondaire; remount par clé
projectId:layoutId:nodeId à chaque changement de projet pour éviter
tout état résiduel inter-projets; la conversation/engine session/
session/agentWasRunning de la cellule sont préservés par les
bascules chat <-> TUI
- useLayout: setCellPreferredView exposé au view model
- tests: layout_usecases (persistance chat puis retour TUI en
conservant session/conversation/engine/running), layout.test
(setCellPreferredView chat persiste, tui omet le champ),
LayoutGrid.chat et cellControlsLayering alignés
QA verte sur fix/batch-322-323-session-restore-prompt-history. Aucun push.
Les contributions runtime des plugins (menus, slash commands, layouts,
surfaces) ne sont exposées que dans les projets liés au plugin.
- Définition du lien: ProjectPluginSettingsStore par racine projet
(domaine) — une contribution runtime-active n'entre dans le catalogue
que si elle est explicitement activée pour le projet courant; hors
projet focalisé, catalogue vide
- Application: ListPluginRuntimeContributions.execute prend un
project_id optionnel, charge projet + settings et filtre le registre
par activation projet; tests fake store/settings (catalogue vide sans
projet, activation, layout, slash commands)
- Backend/app-tauri: plugin_list_runtime_contributions et slash
commands list/execute acceptent projectId optionnel (DTO camelCase,
défaut serde), BackendCore recâblé avec store_port et
project_plugin_settings_store; test DTO de scope optionnel
- Frontend: PluginGateway.listRuntimeContributions(projectId) (ports,
tauri, http/unsupported, mock alignés); PluginRuntimeProvider
re-scope le catalogue au changement de focus projet (vidage du
registre précédent, disposition des bundles, requêtes obsolètes
ignorées par projectId/révision); PluginLayoutCellView ignore les
références de layout devenues hors périmètre via onChooseAnotherLayout
et ne rend rien pendant le rechargement (aucune fuite inter-projets)
- Tests: plugin_install_load (infrastructure), dto slash commands,
plugin.test, PluginRuntimeProvider.test, PluginLayoutCellView.test
QA verte sur feature/321-plugin-contributions-linked-projects. Aucun push.
Symptôme: stopper un agent (ex Main sur UnityPlugin) laissait des travaux
poursuivre, la cascade n'atteignait pas les agents lancés et la pastille
restait orange pendant que l'agent continuait à en appeler d'autres.
- barrière d'admission par génération d'exécution (ExecutionControl): un stop
ferme l'admission de tout le sous-arbre propriété/attente AVANT toute
annulation asynchrone — plus aucune nouvelle action inter-agents après stop
- arbre de propriété durable en-process (launched_by) + dépendances d'attente
actives: la cascade couvre transitivement agents et travaux lancés
- commandes d'un agent arrêté rejetées (AppError::AgentStopped); stop
lui-même admis pour rester idempotent; réactivation sur relance humaine
(génération +1) et enregistrement du propriétaire au spawn
- admission exposée aux adaptateurs hors commandes cœur (tickets, templates,
plugins) via ensure_agent_execution_active; MCP server/tools, web-server et
commandes Tauri alignés
- cohérence live-state: statut Idle avec intent « Arrêté » ou
« Arrêt incomplet — Certains travaux sont encore actifs » selon l'issue de
la cascade; workstate et surfaces UI (AgentsPanel, LayoutGrid,
ProjectWorkStatePanel) reflètent l'état réel, plus de pastille orange fantôme
- tests: orchestrator_service (cascade/générations/idempotence),
workstate_actions, background_task_cancel, error_codes, agents, layout,
workstate
QA verte sur fix/320-agent-stop-cascade. Aucun push.
Reprise après crash du diagnostic (ticket #318, même symptôme) : sur certains
projets, ex PipoufWebSite, la couleur choisie n'était ni appliquée ni reflétée.
- application optimiste: la couleur demandée est publiée immédiatement via
PROJECT_THEME_CHANGED_EVENT et affichée dans le picker, sans attendre le
round-trip de persistance
- persistance systématique par identifiant de projet (jamais par nom), couvrant
les projets à id opaque
- échec de sauvegarde: rollback visuel vers la couleur précédente + reprise
possible via Réessayer qui rejoue exactement le payload demandé
- réouverture du panneau: la couleur persistée est rechargée et affichée
- tests: application immédiate + persistance id opaque, rechargement après
réouverture, rollback/réessai sur échec (ProjectSettingsPanel.test.tsx)
QA verte sur fix/319-project-color-application. Aucun push.
La cellule expose un bouton Redémarrer l'agent valable pour les trois
modes de présentation (headless, TUI native, CLI custom), sans jamais
prendre la présentation pour vérité runtime.
- Application: use case RestartCellAgentRuntime validant le binding
exact cellule/runtime via les snapshots live (RestartRuntimeNotFound,
ConflictAgentVisibleElsewhere, AgentNotBoundToCell) puis déléguant au
scheduler self-restart existant (schedule_cell); CellAgentPresentation
purement informationnelle.
- Backend/app-tauri/web-server: commande restart_cell_agent sur contrat
DTO identique Tauri/HTTP, stop borné par timeout typé
(RestartStopTimeout), relance préservant routage TUI natif, géométrie
de cellule (node/rows/cols) et conversation; évenements domaine
enrichis.
- Frontend: bouton dans la barre de cellule avec confirmation,
overlay d'état Redémarrage…/échec (libellés par code d'erreur, Fermer/
Réessayer), gardes d'identité de cellule et de restarts externes,
gateway/mock/http adapters et ports alignés.
- Tests: dto_agents, workstate_actions, contrats web-server,
LayoutGrid.cellControlsLayering, adapters agent/mock/http.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause: update_project_settings remplaçait tout ProjectSettings avec la
seule couleur fournie — champ absent et null indistinguables — et des
réponses de chargement/sauvegarde obsolètes d'un projet précédent
pouvaient réafficher/écraser la couleur lors d'un changement de projet
rapide, d'où l'impression que la couleur ne prenait pas sur tous les
projets.
- DTO tri-état (absent = inchangé, null = défaut produit) avec
Deserialize manuel et use case de mise à jour partielle du champ
theme_color; commandes Tauri et web-server alignées sur le même
contrat.
- Garde-fous de course frontend: séquences load/save + révision thème
par projet (pub/sub onProjectThemeChanged) dans ProjectSettingsPanel,
garde de révision de focus dans ViewWindow.
- Tests: infrastructure settings, contrats web-server (inchangé/reset),
ProjectSettingsPanel, ViewWindow, projects.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- détachement: le panneau Paramètres du projet devient détachable (mapping historique panel=context → projectSettings dans parseViewWindowParams, DETACHABLE/ViewPanelBody alignés, ProjectsView ne court-circuitant plus le refus); Retry d'échec de détachement préservé
- couleurs projet: projectTheme publie idea:project-theme-changed localement + BroadcastChannel inter-webviews (fenêtres OS détachées), garde de révision par projet dans ProjectsView/ViewWindow contre les lectures settings en course — plus de flash/retour au bleu par défaut
- conversation custom CLI: transcript_restore_expected ne considère plus l'absence de scrollback volatile comme une restauration échouée quand la session rouvre en SessionPlan::None (continuation vide valide); CustomAgentChatView n'affiche plus le faux « conversation liée n'a pas pu être restaurée »
- images: conservation des images de conversation après changement de projet puis retour
- menus Panneaux: retrait des entrées Projects et Project Context (panelOrder WebWorkspace, création/switch via tab-bar +, contexte projet fusionné dans Paramètres du projet)
- QA verte: tests frontend ciblés window/ViewWindow/ProjectSettingsPanel/ProjectsView.detach/projects/CustomAgentChatView/WebDesktopWorkspace + cargo test -p application lifecycle transcript_restore_expected; fichiers .ideai/* (tickets 308-315, index/counter, agents/test, chat-drafts, system-permissions, project-settings.json, attachments/) et scripts/ hors périmètre préservés en worktree; aucun push
#305: nouvel agrégat durable ScheduledProjectTask dans domain (scheduled_task.rs, ids, ports ScheduledProjectTaskStore), source de vérité <app_data_dir>/scheduled-project-tasks/<projectId>.json, schedules bornés V1 interval/daily/weekly avec fuseau, cycle À approuver/Active/Suspendue avec approbation obligatoire des tâches demandées par agent; use cases application/scheduled_task (list/create/update/delete/run_now/reconcile/fire) réutilisant BackgroundTask pour les runs avec distinction planifié/manuel; store infrastructure dédié + scheduler reconcile au boot sans rattrapage en rafale, cwd confiné au projet; 7 tools MCP idea_scheduled_task_* (list/read lecture seule, create/update/delete/run_now/set_enabled write-actions soumises aux permissions natives); commandes Tauri + routes HTTP aux contrats DTO identiques (camelCase nextRunAtMs/lastRunAtMs/lastTaskId/createdBy/updatedBy); frontend: gateways Tauri/HTTP/mock + types ports/domain, surface projet 'Tâches planifiées' (liste utilisateur+agent unifiée, création/édition avec récapitulatif avant activation, approbation/refus, exécuter maintenant, suspendre/reprendre, suppression), catalogage surface web (webSurfaceCatalog/viewPlacement/ViewPanelBody/WebWorkspace); docs/scheduled-project-tasks.md et sdk docs/services.md (047c38b: pas un service plugin V1, pas d'émulation via cron système).
#314: correction de 'Conversation liée — transcript indisponible' après reboot: restauration du transcript lié côté application (orchestrator service, lifecycle, session_limit, background sink, terminal registry) et côté frontend (adapters agent/http frames/stream/requestResponse, workStateNormalization, CustomAgentChatView avec état et action utile), tests dto_chat/agent_lifecycle/structured_registry_d1/background_completion_sink étendus.
QA verte rapportée par QA sur feature/305-scheduled-project-tasks-mcp, sans bloquant, pour #305 et #314.
Hors périmètre worktree: tickets non liés, attachments/, scripts/; aucun push.
- #310: horodatage de tous les messages et événements de conversation visibles — composant partagé EntryTimestamp, timestamps propagés dans le transcript chat, les annonces inter-agent et le viewer de conversation, normalisation côté domain/adapters (conversation_log, paginate).
- #311: stop d'agent robuste pour PTY non monté, sessions headless et CLI custom — lifecycle/orchestrator durcis, ponts chat app-tauri/web-server alignés, arrêt fiable sans session orpheline.
- #312: préférence globale d'interface terminal par défaut — nouveau store settings (domain/application/infrastructure + tests), DTO et commandes backend, adapter frontend userSettings, panneau TerminalSettings dans les paramètres globaux.
- #313: Paramètres du projet — contexte de projet éditable et couleur de thème par projet (ProjectSettingsPanel + projectTheme), teinte des onglets projet, persistance via DTO projet.
QA verte (GO QA sur feature/batch-310-311-312-313-chat-stop-prefs-theme) — vérifications Git: git diff --cached --check OK; staging borné à crates/ et frontend/ (74 fichiers, +2105/-531), commit unique de lot conforme à la convention des lots (précédents batch-151/158/159, batch-210/211/212) car les fichiers partagés (dto.rs, commands.rs, CustomAgentChatView) entrelacent les 4 tickets.
fichiers .ideai/* (agents/test.md, chat-drafts.json, system-permissions.json, tickets/counter.json, tickets/index.json, tickets/308-313/), attachments/ et scripts/ hors périmètre et préservés en worktree ; aucun push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cause: la politique de lancement Codex divergeait selon le chemin de composition. compile_codex_launch_policy ajoutait les roots extra (temp géré) AVANT de calculer le mode de sandbox, et le builder PTY (append_codex_pty_managed_temp_roots) n'appliquait QUE ces roots temp, ignorant project root et politique compilée ; sans permissions explicites, codex_permission_writable_roots renvoyait une liste vide, donc le premier lancement TUI partait sans --add-dir project root et rejetait les roots additionnels (« Error adding directories: Ignoring --add-dir … because the effective permissions do not allow additional writable roots ») ; un --add-dir <project root> préexistant dans spec.args n'était pas réconcilié, d'où des comportements différents entre first-run, stop/relaunch, CLI custom et délégation headless — invisible sur le projet IdeA lui-même du fait de ses permissions déjà configurées.
fix: codex_permission_writable_roots retourne par défaut le project root quand aucune permission explicite n'est configurée (jamais vide si root non vide) ; compile_codex_launch_policy n'ajoute les roots extra qu'en mode workspace-write et reste vide en read-only (deny explicite ne gagne jamais project root ni temp géré) ; nouveau apply_codex_pty_launch_policy unique source de vérité PTY Codex — réécrit la valeur --sandbox existante ou l'ajoute, draine le --add-dir <project root> préexistant puis pose les roots compilés dédupliqués, composition identique pour first-run, relaunch, custom CLI et headless ; unconfigured_policy_writes_project_and_deduplicated_managed_temp, explicit_write_deny_never_gains_project_or_managed_temp_root, partial_allow_exposes_only_its_computed_rw_grant + tests lifecycle de réécriture d'args ajoutés.
QA verte: cargo targeted tests OK, npm targeted/typecheck OK — reconfirmée par Git: git diff --check OK, cargo fmt --all -- --check OK, cargo test -p application --lib codex_permissions 3/3, cargo test -p application --lib lifecycle 48/48.
fichiers .ideai/* (agents/test.md, chat-drafts.json, system-permissions.json, tickets/counter.json, tickets/index.json, tickets/308/, tickets/309/), attachments/ et scripts/ hors périmètre et préservés en worktree ; aucun push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
intègre feature/308-remove-project-from-registry commit b9f8ed3 — use case RemoveProjectFromRegistry + port ProjectStore::remove_project, adapter partagé Tauri/HTTP remove_project avec déactivation et purge open-projects restore, surface frontend ProjectActionsMenu + RemoveProjectDialog avec confirmation explicite et rechargement de la liste ; QA verte: cargo targeted tests/check OK, npm targeted/typecheck/build OK (suite npm test large rouge non bloquante hors #308, échecs préexistants) ; fichiers .ideai/* et scripts/ hors périmètre préservés en worktree ; aucun push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cause: l'utilisateur ne pouvait pas retirer un projet du registre IdeA (projects.json) ; la seule issue laissait l'entrée fantôme dans la liste des projets, sans capacité générique de retrait.
domain: port ProjectStore::remove_project(id) -> Option<Project> avec implémentation par défaut « not supported » pour ne pas casser les stores spécialisés.
infrastructure: FsProjectStore implémente remove_project — retire l'entrée du registre persisté et la renvoie, None si id inconnu, ordre relatif du registre préservé ; test store dédié.
application: use case RemoveProjectFromRegistry (remove.rs) — NotFound explicite si id absent, retourne le projet retiré + registre rafraîchi.
backend/app-tauri/web-server: DTO RemoveProjectRequestDto/RemoveProjectResponseDto (removed, projects, restore_state, was_open), adapter partagé remove_project_for_adapter réutilisé par la commande Tauri remove_project et la route HTTP — même contrat desktop/web ; remove_project_and_deactivate désactive le projet s'il était ouvert, purge l'entrée open-projects restore correspondante et renvoie was_open/restore_state ; test adapter vérifiant le contrat de bout en bout (entrée retirée, restore purgé, root intact).
frontend: DTO + ProjectGateway.removeProject (HTTP + mock), ProjectsView expose un menu d'actions projet (ProjectActionsMenu) avec entrée Retirer, RemoveProjectDialog — confirmation explicite précisant que le dossier disque n'est pas supprimé, invalidation/rechargement de la liste des projets après succès (useProjects), tests ProjectsView/projects/adapters/httpInvoker/services.
QA verte: cargo targeted tests/check OK (domain, application project_usecases, infrastructure project_store, backend, app-tauri, web-server), npm targeted/typecheck/build OK ; suite npm test complète rouge non bloquante hors #308 (échecs préexistants listProjectSettings/web). Vérifications Git: git diff --check OK, cargo fmt --all -- --check OK.
fichiers .ideai/tickets/308/, tickets/counter.json, tickets/index.json, attachments/ et scripts/ hors périmètre et préservés en worktree (sync store traité séparément) ; aucun push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Recalcule la policy structured Codex lors des reprises custom/headless afin que le project root et le managed tmp soient transmis en writable_roots. Fail-closed si une session Codex structured est lancée sans policy explicite.
Les cellules custom CLI ne rendent plus le TargetAnnouncementsOverlay quand
l'agent est busy : le badge busy canonique et le lock du composer (comportement
du chat après envoi d'un message) suffisent, la vue terminal/chat reste visible.
Les cellules TUI natives conservent le voile plein panneau.
Backend : StructuredHumanTurnGuard pour partager la comptabilité busy/FIFO entre chat direct humain (agent_send, web chat.send) et asks inter-agents structurés, avec liveness mark_alive et retrait idempotent du tour. Web-server : tap d'événements sur chat.send pour le scrollback temps réel. Frontend : scoping des clés de progression par tour pour éviter la déduplication/ghosting des progress et outils entre appels.
Suppression du backfill agent→template au open_project (cause racine de la non-persistance des permissions MCP/system des templates) et couverture de régression : create/read/update des default permissions + test dédié open_project sans backfill.
Limite résiduelle documentée au carnet: le backfill live des templates
existants n'a pas été exécuté en écriture directe (hors sandbox session);
il s'appliquera automatiquement au prochain open_project du projet.
AgentTemplate étendu (defaultAgentName, systemPermissions, mcpToolPermissions),
CreateTemplate/UpdateTemplate/CreateAgentFromTemplate projetés vers l'agent créé,
DTO/MCP tools et persistance store complétés. Correctif modèle par défaut:
UpdateTemplate ne persistait pas defaultProfileId (profil stale à la création
d'agent). Backfill best-effort des templates existants depuis les agents du
projet au open_project, overrides explicites uniquement.
needsReconnect() couvre désormais les chatSinks et le cycle de reconnexion
renvoie un chat.attach par session active, un échec isolé n'empêchant pas
les autres abonnements de reprendre. Tests ciblés ajoutés.
L'erreur « La réponse du modèle n'a pas pu être finalisée. » indique désormais
l'adapter en cause (Session Codex / Claude / OpenCode / HTTP) pour lever
l'ambiguïté constatée sur GameTime entre session Codex et Claude.
event_supersedes_pending_final ne fait plus annuler le final en attente par
tout événement autre que Heartbeat/RateLimited/turn-progress: seuls un progress
non-turn Started/Delta, un ToolActivity ou un Error annulent. Le buffering
agent_message survive ainsi jusqu'au flush a turn.completed (regréssion
codex_agent_message_survives_late_completed_item_as_final).
assistantFailureText conserve le texte déjà streamé puis classe l'échec
(sans contenu, tronqué, fin non reçue, transitoire) avec conseil de reprise
adapté, au lieu d'écraser la bulle par « Échec — … finalisée. ».
append_text_fallback_final accumule les TextDelta et produit un Final uniqne
en fin de flux si aucun Final/Error/RateLimited n'a été émis et que le texte
est non vide. Un long message utilisateur sur un autre projet (GameTime) ne
conclut plus a tort « La réponse du modèle n'a pas pu être finalisée. ».
Le test qui pinnait l'absence de Final devient la régression #239.
ConversationViewer remplace « Aucun tour pour le moment » par un diagnostic
differentié (agent actif sans tour affichable, transcript non synchronisé,
activité sur un autre fil) avec action « Ouvrir le fil actif ». Le panneau
Work state affiche le résumé du fil du ticket actif et ProjectsView/WebWorkspace
câblent onOpenConversation.
ensure_structured_session reçoit désormais le conversation_id déjà résolu et le
transmet au plan de spawn au lieu de retomber sur le fallback User-B. WorkState
et la session lancée pointent ainsi sur le même fil. Test de régression
ask_cold_structured_agent_requester_launches_on_resolved_pair_thread.
QA verte périmètre ticket: fmt/check/websocket_chat/websocket single-thread.
cargo test -p web-server complet reste rouge sur bruit historique PTY/fd
(Too many open files puis 7 échecs PTY mono-thread), non attribué au ticket.
Les grants Landlock RO/RW empêchaient la custom CLI de démarrer côté web
téléphone avant d'atteindre le project root. Les providers structurés/headless
gèrent leur propre sandbox compatible CLI.
Le web-server ne routait pas load_layout (UNKNOWN_COMMAND a l'ouverture
d'un projet web PC). Alignement de l'adapter web sur les contrats Tauri:
load_layout, mutate_layout, list_layouts, create_layout, rename_layout,
delete_layout, set_active_layout. useLayout ne propage plus l'erreur
brute: message utilisateur + fallback singleLeafTree.
- Attachments: projection canonique ChatPromptAttachmentDto (référence [file n]/[image n] par compteur de type, ordre d'import) insérée dans le prompt modèle et transportée par ReplyChunk::UserPrompt (attachmentRefs) ; rendu compact côté CustomAgentChatView.
- Resize: la zone chat (LeafView/LayoutGrid) est redimensionnable, taille actuelle conservée par défaut, préférence locale persistée.
- Tests: commands (projection des refs), dto_chat, chat_bridge, CustomAgentChatView, LayoutGrid.chat.
- Codex: les items agent_message sont désormais tapés en live comme progress (Message/Delta) dès item.completed, au lieu d'être bufferisés dans pending_final jusqu'à l'événement tool/MCP suivant.
- Corrélation stable progressId (codex:item:<id>) ajoutée sur ReplyProgress, propagée jusqu'au DTO (ReplyProgressDto.progressId) et au frontend ; le ProgressSettler et la vue procèdent par upsert running -> done au lieu d'empiler des lignes dupliquées.
- Tests: chat_bridge (settler par progressId), dto_chat, session codex, CustomAgentChatView (upsert par progressId).
Codex ne pouvait pas accéder aux métadonnées Git sous sandbox workspace-write.
À l'inverse, la factory relayait jusqu'ici le project_root entier en --add-dir,
soit un grant large non défendable.
Change pour n'accorder en écriture (Codex --add-dir ET SandboxPlan OS) que le
.git réellement résolu :
- resolve_git_dir gère .git/ directory ET fichier worktree 'gitdir: <path>'
(absolu ou relatif au parent du marker), puis canonicalize.
- enrich_sandbox_plan_with_codex_git_roots ajoute/déduplique les roots en RW
sur le SandboxPlan existant (préserve l'autorité EffectivePermissions).
- factory: défaut writable_roots = Vec::new() en l'absence de policy explicite.
Invariants conservés :
- project_root n'est jamais injecté comme writable root.
- aucun élargissement au parent commun (canonicalize + BTreeSet).
- permissions MCP/globales inchangées (seul le plan runtime est enrichi).
Tests (crates/application) :
- standard .git/ dir -> root unique.
- projet non-Git -> aucun root.
- worktree (.git file gitdir:) -> résolution + canonicalization.
- symlink -> dédup, pas de grant parent.
- factory: défaut sans policy n'émet plus --add-dir project_root.
Réserve QA: launch_conventionfile_injects_assigned_skills_in_order
(agent_lifecycle) arbitré hors-scope/non bloquant pour #195.
Cancel transitif, idempotent et ownership-aware sur le graphe parent→enfants des background tasks:
- domain/background_task + ports: modèle parent/enfant (parent_task_id, rejet du self-parent), index children_by_parent, traversal des descendants en largeur.
- application/background + infrastructure/store: cascade cancel (annule les descendants running/open, laisse les terminaux), idempotente face aux double-cancel et ordres de terminaison non déterministes; respect de l'ownership (parent_task_link).
- orchestrator/service: stop agent → cascade cancel des tasks lancées par cet agent (relatesTo #196); cancel task parent → cascade de ses sous-tasks (dependsOn #199). Refactor unifiant le chemin de cancel, sans sémantique #196/#199 regreffée (tests hérités verts).
- frontend: projection parentTaskId (workstate normalization/domain/services).
Tests QA verts: domain background_task, application background_task_cancel (cascades_background_task_subtree, cascades_to_running_child, cascades_to_open_descendants_and_leaves_terminal_child, parent_task_link_roundtrips_and_rejects_self_parent), orchestrator_service, workstate_actions, workstate.test.tsx.
Projette l'état d'interaction agent→projet sur la LED via des signaux explicites de la CLI custom:
- projectCliInteractionActivity: modèle d'activité d'interaction (InteractionStarted/Finished/Stopped, interactionId/seq, version).
- useProjectActivity + ProjectTabs: projection live « interaction en cours » → LED, retour immédiat à l'état attendu au stop ou à la fin réelle de l'échange (pas d'état collant).
- CustomAgentChatView: émet les signaux d'interaction CLI custom (début d'envoi, fin de tour, stop) consommés par l'activité projet — sans dériver la LED du texte streamé ou d'heuristiques.
Tout côté frontend: aucun signal runtime manquant (les signaux proviennent de la CLI custom).
Tests QA verts: projectActivity.test.ts, ProjectTabs.test.tsx (+ useProjectActivity.test.tsx, CustomAgentChatView.test.tsx).
Surface UI du LayoutGrid pour un agent de cellule stoppé:
- Affichage d'un état explicite quand runtimeState vaut « stopped » ou « failedStart », avec actions refresh / reopen from scratch (freshStart) et état restarting pendant la relance.
- theme.css: styles .stopped-agent-cell (boutons primary/secondary, états disabled).
Consomme le contrat runtime issu de #196 (AgentRuntimeState stopped/failedStart, freshStart) déjà présent dans develop; aucune modification runtime ici.
Tests QA verts: LayoutGrid.test.tsx, LayoutGrid.chat.test.tsx (rejoué aussi: agent_lifecycle launch_fresh_start_ignores_existing_conversation_id_and_mints_new_one).
Transforme l'annulation d'une background task en contrat immédiat, observable et non-best-effort:
- domain/background_task: états Cancelling/Cancelled/CancelFailed et transitions idempotentes; un kill PTY qui échoue est surfacé en CancelFailed plutôt qu'en succès silencieux.
- infrastructure/runner + store: cancel effectif de la task ciblée, reconcile marque Cancelling même sans live handle, et départage CancelFailed des tâches pending au boot.
- application/background + workstate + orchestrator/wake: chemin de cancel initié depuis le workstate, projection synchronisée de l'état (pas d'attente d'un cycle ambigu).
- frontend: projection workstate de l'état cancelled/cancelling/cancelFailed (cancellingRow/cancelFailedRow/cancelButtons), normalization et services.
Tests QA verts: background_task_cancel, background_task_runner cancel_wins_even_if_output_is_still_available, background_task_store reconcile_marks_cancelling_without_live_handle_cancel_failed_and_pending, workstate.test.tsx.
Solidifie le cycle stop/restart d'un agent visible en cellule:
- terminal/registry: kill effectif de la TUI native et de la CLI custom, arrêt idempotent du live runtime (PTY + structuré), distinction nette « agent stoppé » vs « pas de session ».
- workstate + lifecycle: projection d'un état runtime explicite « stopped » (AgentRuntimeState: running/stopping/stopped/failedStart), réconcilié avec le live state; traitement des courses « agent not found ».
- relance froide via freshStart: repart de zéro sans réattacher une session morte et recharge la configuration courante de l'agent (y compris changement de modèle).
- contrat projeté côté adapters frontend (ports/domain/normalization/mock) pour la consommation UI future (#197/#198), sans surface UI ici.
Tests QA verts: workstate_actions, orchestrator_service stop_agent, agent_lifecycle (reopen/fresh_start), workstate stopped runtime state, vitest adapter stop_live_agent.
Une demande idea_ask_agent vers UX échouait avec « finished without a structured Final response » alors qu'Architect répondait. Deux causes corrigées:
- session/process: un tour CLI terminé en code non nul SANS stdout (ex. erreur 401/auth) renvoyait une sortie vide au lieu du stderr réel; l'orchestrateur concluait alors à un « no Final ». On remonte désormais le stderr effectif (drain sync + async + helper process_exit_message + test dédié).
- orchestrator/service: la branche « ask waiting on background » masquait le premier Final structuré; la réponse synchrone redevient le premier Final (suppression de active_rendezvous_has_background_tasks / mark_rendezvous_task_waiting).
Nettoyage runtime corollaire (#204): arrêt du live runtime stale (PTY + structuré) d'un agent recréé afin que la délégation route vers l'agent courant (LiveSessions, stop_live_agent_runtime_in_project, wiring backend via DeleteAgent::with_runtime_cleanup, lifecycle, workstate cascade).
Les progress « started » d'une même entité s'empilaient en plusieurs turns et
n'étaient dédupliqués qu'a posteriori par scan linéaire du tableau de turns,
d'où des doublons visibles et un affichage retardé (re-scan par chunk).
appendProgress opérait directement sur turns[] (chaque « started » y était poussé
puis retrouvé/remplacé par recherche séquentielle). Désormais:
- ChatViewState (turns + entityProgressMap): la Map indexée par « entité\0bucket »
(started/completed) est l'unique source de vérité pour les progress en cours;
- les progress « started » ne deviennent des turns qu'au flush
(completeChatViewProgress sur final/error), supprimant la duplication et
rendant l'affichage immédiat;
- « completed » merge depuis l'entrée running de la Map puis la retire, au lieu
de scanner le tableau;
- turnsIndicateBusy s'appuie sur la taille de la Map (état vivant) en plus du
scan des turns.
Tests: déduplication des progress par entité (started répétés → une seule entrée,
flush au final), fusion « completed » depuis la Map, état busy reflété par la
Map, et rendu non dupliqué sur l'enchaînement started/completed/final.
Corrige la perte de session à la navigation: le démontage du CustomAgentChatView
appelait close_agent_session (destructeur) au lieu de simplement détacher le
transport. Désormais:
- nouvelle commande Tauri detach_agent_chat (+ ChatBridge::detach) qui libère le
canal sans tuer la session ni le scrollback retenu; distinct de
close_agent_session qui reste le close destructeur explicite;
- le frontend appelle detachAgentChat à l'unmount puis restore sa vue depuis un
cache de session (turns + chunkCount) à la réattache;
- message « Reprise effectuée: N événements rattrapés… » quand la réattache
rejoue plus de chunks que le cache local n'en avait conservé.
Enrichit le rendu tools/MCP/progress:
- ProgressSettler (chat bridge) émet un progress completed synthétique quand un
spinner reste ouvert (nouvelle entité progress, chunk text/final/error): fini
les spinners fantômes;
- les adapters claude/codex/opencode/openai_compat extraient un aperçu du
payload (arguments/input/command/message/params) attaché au progress;
- bounded_reply_progress_preview (domain) garde les 4 premières lignes non
vides, normalisation CR/LF;
- previewText (frontend) multiligne whitespace-pre-wrap, source/nativeEvent
affichés sous le titre; suppression du flag settled et du Spinner inline
devenus redondants (l'état est porté par le stage + le settler).
Tests: detach_agent_chat gateway (Tauri + mock), ProgressSettler, bornage
reply_progress, item_preview/tool_preview par adapter, réattache avec cache et
notice de rattrapage.
Le Stop du CustomAgentChatView n'annulait que le tour courant sans libérer la
session ni toucher aux tâches enfants ou appels MCP en cours. Il appelle
désormais stop_live_agent sur l'agent vivant, qui:
- ajoute le port StopAgentExecutionCascade (best-effort, idempotent), implémenté
par OrchestratorService pour annuler les idea_run_in_background rattachés au
rendez-vous actif (via BackgroundTaskRunner.cancel);
- cascade l'arrêt aux wait-dépendances (stop_turn_watch + preempt) pour
interrompre les appels MCP et les attentes inter-agents;
- libère et supprime la session structurée.
UI: nouvel état "Arrêt en cours..." (bouton disabled + loading) pendant l'arrêt,
libération du sessionId, label expliquant la cascade demandée. La Tauri command
cancel_agent_chat est alignée sur stop_live_agent + stop_turn_watch agent et
dépendances.
Tests: SpyBackgroundRunner (cascade n'annule que les tâches rattachées),
workstate_actions cascade best-effort, CustomAgentChatView stop+cascade feedback
et fallback NOT_FOUND.
Repli sur l'Async Clipboard API (navigator.clipboard.read) quand clipboardData.files est vide — cas des captures d'écran desktop. Réserve QA : validation manuelle desktop/AppImage en suivi (env headless).
Le collage d'une image (ex. capture d'écran) échouait en environnement desktop : le presse-papier image n'est souvent pas exposé comme un File synchrone dans clipboardData.files, mais uniquement via l'Async Clipboard API. On ajoute donc un repli sur navigator.clipboard.read() (MIME image/*) quand l'événement de collage ne fournit aucun fichier. Le preventDefault() n'est émis que lorsqu'on a effectivement lu des fichiers issus de l'événement, afin de ne pas bloquer le collage texte quand le repli async ne remonte rien.
- refactor clipboardFiles(event) -> dataTransferFiles(DataTransfer) réutilisable
- dedupeKey robuste pour les images sans nom (mime + taille + contenu base64)
- ReplyChunk::UserPrompt porte désormais les labels d'attachments (DTO + accounting reply_chunk_bytes, skip_serializing_if Vec::is_empty)
- tests ciblés frontend (+219) et backend (chat_bridge/dto/attachments) verts
QA : tests ciblés verts. Réserve : environnement headless, pas de validation manuelle desktop — validation AppImage à confirmer en suivi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- MEMORY.md : index des notes recentré
- remaining-work-idea-agent-control-ide.md : réécriture alignée sur l'app actuelle
- ui-rework-sprint-scoping-contracts.md : mise à jour
- supprime 3 notes périmées : cycle-151-167-155-git-topology, develop-realigned-to-cli-ui-baseline-2026-07-02, ticket4-restart-on-develop-ebd992e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retire la surface utilisateur de reprise par cellule :
- supprime ResumeConversationPopup et son test (resumeConversationPopup.test.tsx)
- débranche l'appel dans LayoutGrid et retire l'export terminals/index
- ajuste les tests dépendants (libellé « Terminé » dans LayoutGrid.chat.test, refitEpoch)
- commentaire affiné dans ResumeProjectPanel
La reprise implicite éventuelle sera rediscutée ultérieurement (objectif immédiat : retirer la surface).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute l'entrée #172 (fermé) au miroir local, avance le counter à 173. Index porte aussi les flips #157/#171 -> qa (état réel, déjà en place côté IdeA).
Le viewer (CLI custom) se vidait au changement de layout/projet car l'etat viewerConversationId etait reset sur le changement de projet actif. Remplace par un etat viewerConversation {projectId, conversationId} lie a la conversation elle-meme, rendu contre le projectId de la conversation: la conversation persiste tant que la session headless existe, a l'instar de la TUI native.
Backend: agent_message d'intention avant MCP reclassé en Announcement (non avalé comme faux Final); doublon ToolActivity supprimé à la source. Frontend: dédup métier des cartes progress par entité+stage; idea_ask_agent(s) rendus comme Délégation une fois. QA verte ciblée (codex 41, chat_bridge 25, CustomAgentChatView 26, vitest 1133, tsc 0). #167 reste closed. Réserve: E2E AppImage live non rejouée. Rouge préexistant hors scope: mcp_server.rs:777 (38 vs 37 tools).
appendProgress déduplique par entité (tool/mcp/delegation) + bucket de stage sur l'ensemble du tour, évitant le doublon entre tap live et flux autoritatif. idea_ask_agent/idea_ask_agents rendus comme Délégation. completeRunningProgress marque les entités complétées (settled) pour stopper l'animation. 2 nouveaux tests: ordre pre-MCP avant la première carte MCP, et dédup -> exactement 2 cartes Délégation pour 2 délégations.
Un agent_message d'intention arrivant avant un tool (ex idea_ask_agent) n'est plus avalé comme faux candidat Final: tamponné puis reclassé en Announcement dès qu'un événement le détrône (helper event_supersedes_pending_final), sur le tap live comme sur le flux autoritatif. Les items reasoning/command n'émettent plus de ToolActivity de compatibilité en doublon du Progress canonique Completed -> supprime les doubles cartes tool/MCP à la source. Tests mod.rs alignés + nouveau cas agent_message_before_tool_becomes_announcement_before_tool_progress.
L'adaptateur Codex n'émettait Progress/ToolActivity qu'à item.completed. On capte désormais conversation.item.added / item.started / response.output_item.added (stage Started), item.updated (Delta) et conversation.item.done / response.output_item.done (Completed), en préservant le nom réel de l'outil (name / function.name / function_call.name).
Les outils idea_* sont taggés ReplyProgressSource::IdeaLocal / Kind::Mcp pour les distinguer du tooling provider. Corrige #93 (l'activité MCP idea_ask_agent devient visible avant le Final, nom non corrompu) et #171 (pipeline progress -> UI temps réel) ; base du rendu live #58.
Tests : 3 unitaires (started live, tag IdeaLocal/Mcp, done avec nom) + assertion d'intégration (MCP started tapé avant le Final). QA : verte (live).
- #157: status closed→inProgress ; description régressée au cas profil OpenCode/GLM
(aucun progress ni bulle MCP pendant le tour) ; carnet itération 2 (cause racine
send_with_tap + tagging MCP, correctif livré au commit 836137a3, QA backend vert
puis validé en live sur AppImage).
- #155: status qa→inProgress (collage clipboard custom CLI toujours KO au runtime,
requalifié fichier/image via le pipeline d'attachments existant).
- mémoire: note projet opencode-streaming-progress-mcp-surfacing-157 + index.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Une copie parasite de l'AppImage bâtie peut atterrir à la racine du dépôt
lors de tests manuels (le build légitime vit sous target/, déjà ignoré).
Règle *.AppImage pour ne jamais la committer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend validé en live sur l'AppImage : streaming des progress/activité MCP pendant le tour confirmé fonctionnel (profil OpenCode/GLM). Branche fix/ticket157 (1 commit 836137a3, parent direct de develop).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Override send_with_tap: progress/text/tool désormais projetés en live via LiveTurnProjector (au lieu d'un batch en EOF), en suivant le pattern de codex.rs.
Tagging des tool_use idea_* en Progress Mcp/IdeaLocal/Completed (tool_progress) pour faire apparaître la bulle MCP IdeA locale au moment de l'invocation.
Protection anti-replay de session continuée: hold_until_new_message + buffer pending n'émet que le message courant, jamais l'historique rejoué.
send() batch inchangé: stream_events ne filtre le Final que lorsqu'un tap est présent.
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>
stale
Le backend renvoie l'id actif autoritaire et le
frontend l'adopte pour éviter de rejouer un layout
disparu après overwrite externe.
Co-Authored-By: Claude Opus 4.8
<noreply@anthropic.com>
layouts.json portait l'état UI runtime (active layout id + session ids),
reconstruit à l'exécution et last-writer-wins — même catégorie que
.ideai/live-state.json déjà ignoré. Versionné à tort, il était écrasé par
git au switch de branche → activeId périmé → "not found: layout X" → cellules
figées. On le détrack (git rm --cached, fichier conservé sur disque) et on
l'ajoute au .gitignore sous le bloc live-state. Le fix runtime (self-heal de
l'active layout) viendra dans un commit séparé sur cette branche.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fenêtre d'inactivité réarmée à chaque progrès de la cible (octets cumulés du
transcript) sous un plafond absolu, en remplacement du timeout plat qui coupait
un long tour unique à 600 s. Issue typée TargetCeilingActive distincte du no-reply.
Suite Rust verte (application + infrastructure, 0 échec). Campagne MCP T1→T10 PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capitalise la mémoire projet produite pendant la campagne de tests fonctionnels
MCP (rendez-vous inter-agents, backstop no-reply, réconciliation live-state au
reboot) et synchronise l'état runtime `.ideai/` (agents, layouts, skills, index
mémoire). Inclut la mise à jour du contexte de l'agent Git.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Redessine la borne de fin de tour du rendez-vous `idea_ask_agent` ⇄ `idea_reply` :
au lieu d'un timeout plat (qui coupait à 600 s un seul long tour de la cible, cf.
T7), la borne devient une **fenêtre d'inactivité réarmée** à chaque progrès observé
de la cible, sous un **plafond absolu** (défaut 4 h, réglable via
`IDEA_ASK_RENDEZVOUS_CEILING_MS`).
- Sonde d'activité (`transcript_activity_token`, inspector) : jeton monotone =
octets cumulés des `.jsonl` de la cible. Croît même pendant un seul long tour
sans `turn_duration` ⇒ détecte « vivant et au travail » mi-tour. Best-effort,
sans effet de bord ; folder absent/illisible ⇒ « pas de progrès ».
- Watchdog (`run_inactivity_watchdog`, nouveau module `orchestrator/rendezvous`) :
fenêtre réarmable + plafond, fallback timeout plat si aucune sonde (zéro régression).
- Issue typée distincte `TargetCeilingActive` (code `RENDEZVOUS_CEILING_ACTIVE`) :
une cible **active** stoppée au plafond n'est jamais confondue avec un
`TargetReturnedNoReply` (silence) ; le message guide « ne pas retenter à l'aveugle ».
- Câblage composition-root (`state.rs`) : sonde résolue nom→AgentId→run-dir transcript,
branchée sur le service et sur l'McpServer (`AskActivityProbe`, `with_ask_ceiling`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre feature/reconcile-live-state : à l'ouverture d'un projet, les statuts
d'agents restés `working` fantôme après un redémarrage sont réconciliés vers
`idle` (use case ReconcileLiveState, best-effort, sans nouveau port).
QA VERT : cargo test --workspace 1624 passed / 0 failed, clippy 0 erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Au redémarrage, les agents qui étaient `working` lors de la fermeture restaient
figés en statut fantôme `working` alors qu'aucun tour n'est plus en cours (bug
reproductible en live : QA et DevBackend eux-mêmes sont restés `working` fantôme
après leurs tâches). On réconcilie l'état persistant à l'ouverture du projet :
les statuts orphelins sont ramenés à la cible `idle`.
- domain/live_state.rs : const STALE_AT_RESTART_MARKER + `reconcile_orphans` (+ tests).
- application/workstate/reconcile.rs (nouveau) : use case ReconcileLiveState (pas de
nouveau port), best-effort, no-op si store vide (+ tests, dont empty_store_is_a_noop_at_boot).
- application/workstate/mod.rs + lib.rs : module + re-export.
- app-tauri/state.rs : provider par-root + champ AppState + wiring.
- app-tauri/commands.rs : hook best-effort dans open_project.
QA VERT par commande réelle : cargo test --workspace 1624 passed / 0 failed,
cargo clippy --workspace --all-targets 0 erreur sur le code ajouté.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre la branche feature/rendezvous-no-reply-backstop : durcissement du
rendez-vous idea_ask_agent ⇄ idea_reply, backstop no-reply, et le fix de cause
racine `encode_cwd` (encodage exact de Claude Code, `.` -> `-`) qui rétablit le
turn-watcher et lève le wedge.
Validé EN LIVE (wedge prouvé levé, demandeur libéré via grâce 2s) + suite verte
(cargo test --workspace 1616 passed / 0 failed, clippy 0 erreur).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`encode_cwd` ne remplaçait que `/` et `\` par `-`, laissant le `.` intact. Or
Claude Code aplatit le cwd via `replace(/[^a-zA-Z0-9]/g, '-')` : tout caractère
non alphanumérique devient `-`, y compris le `.`. Pour un run dir isolé
`…/IdeA/.ideai/run/<uuid>`, le `/.` doit collapser en double dash
(`…-IdeA--ideai-run-<uuid>`). L'ancienne version calculait `…-IdeA-.ideai-run-…`,
un dossier inexistant sur disque : le turn-watcher voyait un répertoire vide
(baseline 0, conversation <none>) et ne déclenchait jamais `turn_ended`, ce qui
wedgeait le backstop no-reply du rendez-vous inter-agents.
Validé EN LIVE : demandeur libéré ~grâce 2s (au lieu du timeout 600s), preuves
dans idea.log. Test de non-régression `encode_cwd_encodes_dot_in_run_dir_to_double_dash`
+ maj test Windows (`C:\Users\me` -> `C--Users-me`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remédiation du wedge persistant après échec live du fix Finding A (77e62e5).
Détecte la fin de tour d'un agent sollicité qui n'a pas appelé idea_reply et
débloque l'agent demandeur au lieu de le laisser en attente indéfinie.
Ajoute le suivi de tour côté inspector Claude (claude_turn_watcher) et la
résolution des chemins de session (claude_paths), câblés dans le rendez-vous
idea_ask_agent ⇄ idea_reply.
Build workspace vert, suite complète verte, zéro warning.
Backstop NON prouvé levé en live : merge develop interdit tant que la levée
du wedge n'est pas validée en conditions réelles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Correctif UI autonome (surface humaine), vert (tsc 0, vitest 444 tests),
sans lien avec le chantier backend rendez-vous.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Une cellule ne ré-attachait pas le flux de sortie de son agent lorsque
celui-ci était réveillé en arrière-plan par une délégation : l'utilisateur
devait basculer la vue Plain↔agent pour voir l'agent travailler.
Ajout d'un effect de ré-attache déclenché sur le réveil arrière-plan + bump
de la key pour forcer le remontage de la vue. Test de régression
liveReattachOnDelegation : rouge sans le fix, vert avec.
Vérifs : tsc --noEmit exit 0 ; vitest run 48 fichiers / 444 tests OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le runtime conversationnel (handoff distillé + log.jsonl transcript par paire)
est reconstruit au fil de l'eau et ne doit pas être versionné : seul
.ideai/memory/ est le store durable. Aligne le suivi git sur le design D19-4
(cf. LS8 §7 point 3). Ajoute la règle .ideai/conversations/ au .gitignore et
désuit les 16 fichiers (8 handoff.md + 8 log.jsonl) sans les supprimer du disque.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dernier lot fonctionnel du programme live-state : viewer frontend lecture seule
consommant read_conversation_page (LS6). tsc 0, vitest 443/443. Reste LS8 (doc de clôture).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Incrément backend cohérent et vert : archive segmentée hors chemin chaud,
port ConversationArchive, lecture paginée riche + commande Tauri read_conversation_page.
Tests verts domain/infrastructure/application/app-tauri. LS7 (UX React) continue sur la feature.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Incrément cohérent et vert qui clôt le must-have perf du handoff :
borne summary_md + élision de ligne de tour, seam LLM prêt mais non activé (ADR).
Tests verts domain/infrastructure/application. LS6 (rétention/UI) et LS7 (UX) continuent sur la feature.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Incrément cohérent du programme live-state qui clôt le cold-start de coordination :
LS1 modèle+port, LS2 store FS+use cases, LS3 auto-update depuis le cycle de délégation,
LS4 injection « État du projet » bornée + outils MCP idea_workstate_read/set.
Tests verts sur domain/application/infrastructure/app-tauri. LS5+ continue sur la branche feature.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Branche la mise à jour automatique du live-state sur le cycle de
délégation de l'orchestrateur, en best-effort non bloquant.
- Trait `LiveStateProvider` (résolution par root) + champ `Option` dans
l'`OrchestratorService`, injecté via le builder `with_live_state` ;
`None` ⇒ aucun effet (zéro régression).
- Transitions dérivées du cycle : `ask` → entrée `Working`,
`reply` → `Done` + `last_delegation`.
- Helpers best-effort : un échec de mise à jour du live-state ne
transforme jamais un succès de délégation en erreur.
- `AppLiveStateProvider` + wiring côté app-tauri (provider par root).
cargo test -p application : 0 échec ; --test orchestrator_service : 55/0
(dont 4 nouveaux tests LS3) ; cargo fmt --all --check : exit 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Domaine pur du live-state des agents : `LiveState`/`LiveEntry`/`WorkStatus`.
- Fusion en keyed last-writer-wins : une entrée par clé, la plus récente
écrase l'ancienne (ordonnancement déterministe).
- Invariants de bornes anti-dump : bornes douces (troncature) + rejet dur
au-delà des limites, pour empêcher un agent de noyer le live-state.
- `prune` pour borner la rétention.
- Port `LiveStateStore` (lecture/écriture), sans dépendance d'infra.
9 nouveaux tests live_state (cargo test -p domain : 208 passed / 0 failed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Récupère la seule note de design de la branche feature/agent-skill-awareness
(5be8987), hors bruit runtime .ideai. Documente la conception du manifeste de
skills, de l'outil MCP idea_skill_read et du brief « capacités IdeA ».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enrichit `compose_convention_file` pour que TOUT agent neuf, de tout
projet, soit briefé sur l'ensemble des capacités IdeA — plus seulement la
délégation. Briefing à haute altitude, télégraphique, injecté à chaque
lancement (sobriété token : capacité exposée, jamais le contenu).
Les 3 capacités sont décrites sur les 2 surfaces, strictement cloisonnées :
- MCP : via les outils `idea_context_read`/`_propose`/`idea_update_context`
(contexte projet single-writer, proposition globale enregistrée pour
validation, pas auto-appliquée), `idea_memory_read`/`_write` (mémoire
durable partagée), `idea_skill_read`/`idea_create_skill` (skills).
- Sans MCP : mêmes concepts via les seuls fichiers `.ideai/` (CONTEXT.md,
memory/ + MEMORY.md, skills `.md`), aucun nom d'outil `idea_*` ne fuit.
But : un agent ne réinvente pas son propre contexte/mémoire/workflow alors
qu'IdeA les fournit déjà.
5 tests dédiés (briefing inconditionnel même à vide, honnêteté single-writer
du contexte, cloisonnement des surfaces, ordre brief-avant-persona). 1 assert
existant adapté (`..._without_skills_omits_section` : `idea_skill_read` est
désormais toujours évoqué par le brief ; l'intention reste gardée par
l'assert sur l'absence de la section `# Skills disponibles`).
`cargo test -p application` = 0 failed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QA avait mis à jour le contrat 11→12 dans infrastructure/tests/mcp_server.rs
mais une seconde assertion codée en dur subsistait dans le test duplex de
state.rs. Ajoute idea_skill_read à la liste attendue et passe le compte à 12.
Conséquence directe de T4 (skill-awareness).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface les skills assignés à un agent « à la MCP » : description sur l'entité
Skill (+ effective_description fallback), section « # Skills disponibles » haute
altitude dans le convention-file (mode MCP), outil read-only idea_skill_read
(résolution projet→global), use case ReadSkill (port SkillStore existant), et
câblage au composition root. Dump legacy du corps complet conservé en mode
sans-MCP (zéro régression). Rétro-compat index.json (serde default).
Tests : domain+application+infrastructure 1212 passed / 0 failed (23 ajoutés).
Reste : T6 (champ description front) + T7 (e2e après rebuild AppImage).
NB topologie : commit réalisé par l'orchestrateur car l'agent Git était
injoignable (bug de livraison cold-start) ; à faire relire/rebaser par Git.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Race cold-start en ordre inverse (libération avant acquisition) du portail
d'écriture : latch released garantissant exactly-once. Validé QA (fmt/check
verts, 42 tests input passés).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Au démarrage à froid, lorsque les évènements de cycle de vie du portail
d'écriture arrivent dans l'ordre inverse (libération observée avant
l'acquisition correspondante), le latch busy restait coincé et bloquait
définitivement la médiation d'entrée. On introduit un latch `released`
qui mémorise une libération anticipée et garantit une sémantique
exactly-once : l'acquisition tardive consomme la libération déjà vue au
lieu de re-verrouiller. Couvert par 2 tests de régression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Normalise l'état Work pour empêcher l'affichage d'un onglet Work vide.
QA VERT : tsc --noEmit exit 0 ; vitest run 42 fichiers / 407 tests passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Résolution typée des délégations sur prompt-ready après grâce, vérifiée verte par Main
(domain mailbox 6/190, infra mailbox 18, input 40, mcp_server 22, tools 16,
application orchestrator_service 51, workstate 21, lib 44 ; cargo check app-tauri OK ; fmt OK).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduit le seam TurnResolution : sur prompt-ready, après un délai de grâce,
la délégation en attente est résolue de façon typée plutôt que de rester bloquée
indéfiniment (jusquà 24h). Couvre domain (mailbox), infrastructure (mailbox/input)
et application (orchestrator/error), avec tests étendus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Logs diag! sur le canal de délégation inter-agents (application + infrastructure
input/mailbox/mcp), vérifiés verts par Main (mcp_server 22, mailbox 14, input 35,
tools 16, orchestrator_service 49 ; cargo check app-tauri OK ; fmt OK).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute des logs diag! sans changement sémantique le long du canal de
délégation inter-agents (application/orchestrator, infrastructure
input, mailbox, mcp server & tools) pour diagnostiquer les blocages
récurrents de idea_ask_agent. Couvert par les tests existants étendus
(mcp_server, mailbox, input, tools, orchestrator_service).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre le cœur E1 (feat memory récolte auto contrôlée) et le fix
indépendant mcp runtime dir socket, tests E1 ciblés verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`unix_runtime_dir` essaie désormais les bases candidates dans l'ordre de
priorité ($XDG_RUNTIME_DIR → $TMPDIR → /tmp) et retient la première dont
le sous-dossier `idea-mcp` existe ou peut être créé. Un
$XDG_RUNTIME_DIR positionné mais inutilisable (sandbox/CI pointant un
chemin inexistant ou en lecture seule) ne doit plus dead-end le bind
loopback : sans ce fallback le socket ne se liait jamais en silence et la
délégation inter-agents mourait. Déterministe pour un environnement donné,
donc le côté bind et tout lecteur de `socket_path()` s'accordent sur le
même répertoire.
Indépendant du Lot E1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Câble la récolte automatique de mémoire de bout en bout, sous contrôle
explicite, du domaine jusqu'à l'UI :
- domain : modèle `memory_harvest` (candidats de récolte, décision) +
exports `lib.rs`.
- application : use case `memory/harvest` branché dans `memory/mod`,
`lib.rs`, le cycle de vie d'agent (`agent/lifecycle`) et
l'orchestrateur (`orchestrator/service`).
- app-tauri : wiring runtime dans `state`.
- frontend : DTO `domain/index` + hook `useMemory`.
Couverture : domain `memory_harvest` (14), application `memory_harvest`
(5) et `orchestrator_service` (récolte, 4), plus patch test-only du mock
gateway `workState` (`mock.test.ts`) et UI `memory.test`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Câble les actions contrôlées dans ProjectWorkStatePanel : port et adaptateur
agent étendus aux nouvelles commandes, adaptateur mock aligné.
Tests verts : workstate + projects (25), agent (8), singletonAgent +
agentAlreadyRunning (9), tsc --noEmit OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute un module d'actions contrôlées (`workstate/actions.rs`) côté application
et l'expose via des commandes Tauri : DTO camelCase, câblage commands/state/lib.
Les actions valident leurs invariants avant d'agir sur le read-model.
- application : use cases d'actions contrôlées + intégration au work-state.
- app-tauri : commandes dédiées, DTO et enregistrement dans le handler.
Tests verts : application workstate_actions (7), workstate (21),
app-tauri dto_agents (25), list_live_agents_r0b (5), cargo check OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Conversations live, layouts, MEMORY.md et note checkpoint
workstate-conversation-summaries-lot-c.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Affiche les résumés de conversation dans ProjectWorkStatePanel à partir du
DTO camelCase : type de domaine et adaptateur mock alignés, panneau enrichi.
Tests verts : workstate.test.tsx + projects.test.tsx (2 fichiers / 19),
tsc --noEmit OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Étend le read-model work-state pour exposer un résumé par conversation
(participants, dernier message/activité, compteurs) câblé jusqu'au DTO
camelCase de l'état app-tauri.
- application : assemblage des résumés de conversation dans le work-state.
- app-tauri : DTO camelCase des résumés + exposition dans l'état.
Tests verts : application workstate (21), app-tauri dto_agents (21),
aucun warning Rust.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Affiche les tickets en attente dans `ProjectWorkStatePanel` à partir du
snapshot de file exposé par le read-model : type de domaine et adaptateur
mock alignés sur le DTO `camelCase`, panneau enrichi (requester, aperçu de
tâche, position FIFO), hook de lecture mis à jour.
Tests verts : workstate.test.tsx + projects.test.tsx (2 fichiers / 17),
`tsc --noEmit` OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute un port de lecture ségrégué `AgentQueueSnapshot` (ISP) distinct du
`AgentMailbox` mutant : il expose `queue_for(agent)` qui renvoie des
`QueuedTicketSnapshot` clonés, ordonnés FIFO, avec position recalculée
(0 = tête). Observer la file ne la mute jamais ; le one-shot reply sender
reste dans l'adaptateur.
- domain : value object `QueuedTicketSnapshot` + trait `AgentQueueSnapshot`
(object-safe, partagé en `Arc<dyn …>`).
- infrastructure : `InMemoryMailbox` implémente la vue lecture en plus de la
vue mutation ; positions recalculées à chaque appel.
- application : le read-model work-state liste les délégations en attente via
ce port, troncature de l'aperçu de tâche en conservant la longueur d'origine.
- app-tauri : DTO `camelCase` des tickets en file câblé dans l'état.
Tests verts : domain mailbox (6), infrastructure mailbox (13),
application workstate (12), app-tauri dto_agents (20).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lot A : read-model live-state minimal des conversations/délégations côté backend
(module application workstate + surface Tauri) et frontend (port/adapter/mock +
feature workstate intégrée à ProjectsView).
QA verte, réserve environnementale non bloquante (socket Unix non bindable en
sandbox, alternatives skips vertes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute le port et l'adaptateur workState (+ mock) côté frontend, le type domaine
associé, et la feature `workstate` (panneau ProjectWorkStatePanel + hook
useProjectWorkState) consommant le read-model live exposé par le backend.
Intègre le panneau dans ProjectsView. Tests verts (workstate + projects).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduit le module application `workstate` (modèle de read-model live + snapshots
des conversations/délégations en cours) et l'expose via la couche terminal
(exports mod/registry). Câble la surface Tauri : DTO, commande et state pour
exposer le live-state au frontend (lib + state + commands), avec tests.
QA verte. Réserve environnementale non bloquante : tests loopback socket Unix
réels non exécutables en sandbox (UnixListener::bind PermissionDenied),
alternatives avec skips vertes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Awareness skills dans le fichier de convention (feat befff76), hotfix livraison
délégation + logs submit (fix 018eb1a) et état runtime associé.
QA vert accepté avec réserve environnementale : tests loopback socket Unix réels
non exécutables en sandbox (UnixListener::bind PermissionDenied), alternatives
avec skips vertes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mise à jour de l'état runtime non-code : flux de conversations (handoff/log.jsonl),
layouts, index mémoire MEMORY.md, et nouveau checkpoint
checkpoint-delivery-submit-logging-fix.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Durcit le portail d'écriture de délégation et son acheminement bout-en-bout
(commande Tauri → orchestrateur → file d'entrée infrastructure → portail
frontend), avec une journalisation du submit pour diagnostiquer les cas où la
délégation n'était pas remise. Couvre le portail d'écriture côté front
(useWritePortal) avec ses tests.
QA : checks application/front/infrastructure/app-tauri verts. Les tests loopback
socket Unix réels ne sont pas exécutables en sandbox (UnixListener::bind →
PermissionDenied) ; alternatives avec skips vertes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
compose_convention_file émet désormais, dans le bloc d'orchestration (avant le
contexte projet et la persona), un court paragraphe expliquant qu'un skill
assigné est du contexte opérationnel — ni commande magique, ni sous-tâche
fournisseur — et oriente la capitalisation de workflows réutilisables vers la
surface d'orchestration active : `idea_create_skill` en profil MCP, le protocole
fichier `skill.create` sinon.
L'awareness n'injecte volontairement pas le corps des skills non assignés :
l'assignation reste la frontière de contexte. Tests de composition (présence,
ordre root → orchestration → contexte → persona, branche MCP vs fichier) verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persiste la dérive runtime après rebuild de l'AppImage 0.3.0 (conversation
6bc594e8 handoff+log, layouts) et ajoute le checkpoint
checkpoint-orchestrator-designation-appimage-build (build via appimagetool
--runtime-file en contournement de l'échec linuxdeploy), indexé dans MEMORY.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Modèle de désignation d'orchestrateur (AgentManifest + may_write_directly),
sink de diagnostic du rendez-vous inter-agents, et durcissement du portail
d'écriture de délégation côté frontend.
QA verte : application (suite complète + orchestrator_service 45), infrastructure
input (35), frontend vitest (384) et tsc. Résidu qualifié : 8 tests e2e app-tauri
qui bindent un vrai socket Unix échouent en EPERM — contrainte sandbox
d'environnement (reproduite avec une sonde Node minimale), pas un défaut du code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persiste l'état runtime : manifestes agents, layouts, permissions, logs et
handoffs de conversations, index mémoire et checkpoints du chantier
orchestrator-designation (restart, backend-compile-fix, qa-verdict) ainsi que
la note conversation-rotation-safety-design.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Met à jour le document de méthode du projet (rôle de chef d'orchestre,
boucle de dev Architect→Git→Dev→QA, agent Git propriétaire de la topologie,
vision produit et stack).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fiabilise la livraison des délégations inter-agents dans le terminal :
écriture par chunks UTF-8 bornés (512 o, délai 8 ms) pour éviter les
comportements de paste/drop des TUI sur agents froids, et réconciliation
de l'attachement front (frontAttachedAgentRef / reconcileFrontAttachment)
pour ne reporter « front attaché » qu'une fois les DelegationReady
réellement consommés. Tests vitest associés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduit le modèle AgentManifest { version, entries, orchestrator } et la
garde d'écriture directe may_write_directly(..., &OrchestratorDesignation) :
seul l'orchestrateur désigné peut écrire directement, les autres passent par
le rendez-vous médié. Câble la désignation à travers domain → application →
infrastructure → app-tauri (context_guard, service, lifecycle, ports).
Ajoute crates/application/src/diag.rs : sink de diagnostic best-effort, sans
dépendance, qui miroite les traces du rendez-vous inter-agents de
l'orchestrateur vers un fichier de log persistant (utile au lancement via
AppImage où stderr est jeté), avec la même discipline « zéro dépendance,
ne casse jamais le rendez-vous ».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump des 4 crates du workspace (domain, infrastructure, application,
app-tauri), de tauri.conf.json, du package.json frontend et des entrées
correspondantes de Cargo.lock. Prépare l'intégration de develop dans main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le composition root (app-tauri/state.rs) n'appelait pas
.with_context_guard(...) : les outils MCP de contexte/mémoire
(idea_context_read/propose, idea_memory_read/write) n'étaient
donc pas réellement branchés au runtime, alors que le use case
existait côté application.
- application: re-export public de ContextGuardUseCases (orchestrator/mod.rs)
et de ContextGuardUseCases, ReadContext, ProposeContext, ReadMemory,
WriteMemory (lib.rs), pour que app-tauri puisse les câbler.
- app-tauri: branchement .with_context_guard(...) au composition root
+ 3 tests de non-régression (round-trip memory read/write, context read,
symétrie d'erreur typée) dans mcp_serve_peer_tests.
Tests QA verts : cargo test -p app-tauri (0 failed), build 0 warning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Snapshot de l'état runtime accumulé sur develop : logs/handoffs de
conversations, layouts, notes mémoire (dont git-owns-commit-merge-decisions)
et catalogue de skills.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump des manifestes (crates Rust + frontend) et des lockfiles vers
0.2.0 en préparation de la release. Ajoute /node_modules à .gitignore
(tooling installé à la racine, jamais versionné).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre feature/agent-session-limits dans develop. Surface complète :
- Niveau 1 : tap de détection sur agent_send (limites remontées par la CLI).
- Niveau 2 : détecteur déclaratif au lancement (parser regex confiné) +
reprise automatique planifiée (TokioScheduler) et annulable.
- Niveau 3 : filet humain (set_resume_at / saisie d'heure) quand l'heure
n'est pas exploitable automatiquement.
Backend (domain/application/app-tauri) + front (badge, compte à rebours,
formulaire de reprise) ; suites Rust et front vertes.
Commits : LS2..LS8 (a1755e5 → 3f3504e).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le test asseyait « thirteen gateways » alors que la gateway permission
(présente depuis eca2ba9) en porte le total à 14. Compteur périmé sans
rapport avec session-limits, corrigé isolément avant le merge d'intégration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UI permettant à l'humain de renseigner l'heure de reprise quand le
niveau 2 détecte une limite sans heure exploitable.
- ports/index.ts : setResumeAt(agentId, resetsAtMs) sur InputGateway.
- adapters/input.ts : setResumeAt → invoke("set_resume_at").
- adapters/mock/index.ts : MockInputGateway.setResumeAt (resumeArmings[]).
- features/agents/useAgents.ts : action setResumeAt (sans mutation optimiste).
- features/agents/AgentLimitBadge.tsx : formulaire de saisie d'heure sur
l'état suspected sans heure + helper pur timeInputToEpochMs (TODO LS7 retiré).
- features/agents/AgentsPanel.tsx : câblage onSetResumeAt.
Tests AgentLimitBadge.test.tsx mis à jour au nouveau contrat + couverture LS8 ;
typecheck propre, suite agents verte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Permet à l'humain de confirmer/forcer l'heure de reprise quand le
niveau 2 a détecté une limite sans heure exploitable.
- application/agent/session_limit.rs : refactor privé arm_scheduled
(param resets_at_ms brut) partagé par on_rate_limited + nouvelle
confirm_human_resume(agent_id, node_id, conversation_id, resets_at_ms)
(source Human, réutilise la branche Scheduled, annulable).
- app-tauri/commands.rs : commande set_resume_at(agent_id, resets_at_ms)
(résout node_id via node_for_agent + conversation_id best-effort,
NOT_FOUND si pas de cellule vivante).
- app-tauri/lib.rs : set_resume_at enregistrée après cancel_resume.
Réutilise les événements existants (AgentRateLimited + AgentResumeScheduled),
aucun nouvel événement. Tests : +6 session_limit_service, +2 wiring, verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Branche le SessionLimitService dans l'application Tauri et expose la
surface de reprise/annulation au front.
- application/agent/lifecycle.rs : LaunchAgentOutput.profile exposé
(None sur réattache/idempotent, Some sur lancement effectif).
- application/terminal/registry.rs : StructuredSessions::meta_for_session()
(lookup agent/node par SessionId pour le tap niveau 1).
- app-tauri/state.rs : ResumeContext(s), AppAgentResumer (impl du port
AgentResumer au-dessus de LaunchAgent), instanciation + câblage du
SessionLimitService (TokioScheduler + drain des réveils) dans AppState::build.
- app-tauri/commands.rs : taps niveau 1 (agent_send) et niveau 2
(launch_agent, parser regex confiné), alimentation de resume_contexts,
commande cancel_resume.
- app-tauri/lib.rs : enregistrement de cancel_resume dans le handler.
- app-tauri/Cargo.toml : dépendance async-trait.
Tests : session_limit_wiring.rs (2 tests de composition) + meta_for_session
dans structured_registry_d1.rs ; fixtures dto_agents/dto_chat ajustées
(profile: None). Tout vert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Relaie les 5 nouvelles variantes DomainEvent (AgentRateLimited, ResumeScheduled,
ResumeCancelled, Resumed, RateLimitSuspected) via leurs DTO miroirs et bras From
dans events.rs, et propage ReplyEvent::RateLimited en chunk côté chat.rs pour que
le front soit informé des suspensions/reprises de session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute un détecteur niveau 2 (RateLimitParser, regex confiné à l'infra) qui
repère les mentions de limite de session dans la sortie textuelle, et factorise
le parsing d'heures dans un module pur (timeparse) partagé entre les niveaux 1
et 2. Le détecteur Claude niveau 1 est refactoré vers timeparse (~-121 lignes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduit l'abstraction de planification pour la reprise différée à la
levée d'une limite de session :
- domaine : trait Scheduler + enum ScheduledTask (ports.rs), ScheduleId
via typed_id! (ids.rs), re-exports (lib.rs).
- infra : TokioScheduler (scheduler/mod.rs, nouveau) + pub mod scheduler
et re-export (lib.rs).
Tests QA inline (#[cfg(test)]) : 7 tests scheduler (3× sans flaky).
`cargo test -p infrastructure` = 195 passed / 0 failed ; domaine + infra
builds 0 warning ; LS1/LS2 toujours verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Câble la détection de limite de session dans l'adapter Claude :
- claude.rs : parse_event émet ReplyEvent::RateLimited ; nouvelle fonction
pure parse_reset_ms + helpers ; parseur ISO maison ; doccomments T4.
- mod.rs : 26 nouveaux tests QA (#[cfg(test)]) + 2 tests existants alignés
sur le nouveau contrat.
- conformance.rs : RateLimited ajouté aux événements non terminaux autorisés.
`cargo test -p infrastructure` = 188 passed / 0 failed, zéro régression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pose le cadrage de la feature « Gestion des limites de session des agents »
avant tout code (lots LS1→LS8) :
- ARCHITECTURE.md §21 : détection hiérarchique des limites de session +
reprise auto annulable (domaine → adapter Claude → port Scheduler →
service → parser regex → filet humain → app-tauri → frontend) ; état en
mémoire uniquement, aucun schéma de persistance modifié.
- .ideai/memory/session-limit-handling-design.md : design validé.
- .ideai/memory/MEMORY.md : pointeur vers le design.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deux entrées gitlink (mode 160000) pointant vers des commits de worktrees
agent locaux avaient été versionnées par erreur. Elles sont déjà couvertes
par .gitignore (.claude/worktrees/) ; on les sort simplement du suivi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre dans develop le travail terminé et testé porté par
wip/p8c-checkpoint-before-codex :
- enforcement OS des permissions (Landlock) bout-en-bout (LP4-0→LP4-4)
- pont Codex inter-agents + readiness/heartbeat
- layouts UI responsive + setup agent Git
Tests crates cœur (domain/application/infrastructure) verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute le contexte de l'agent Git (.ideai/agents/git.md) garant du dépôt
git local (commits, branches, merges/rebases) et l'inscrit dans le cycle
de dev de CLAUDE.md (§2.4 + étape « décide de la branche » / « merge
éventuel feature/* → develop »). Formalise le modèle main ← develop ←
feature/*.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AgentsPanel : formulaire de création et items d'agent passent en colonne,
les libellés/états longs (running in IdeA, profil) wrappent/tronquent au lieu
de déborder. ProjectsView : les onglets de la sidebar wrappent sur plusieurs
lignes pour rester tous visibles, le bouton collapse reste épinglé en haut.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Un allowlist Landlock ne peut que retirer des accès (deny-by-default). Sous
une fallback Posture::Allow (allow-by-default), enforcer les grants
verrouillerait tout hors du project root — binaire CLI, libs et ~/.<cli> —
provoquant le 'failed to open terminal'. On n'enforce donc rien sous Allow ;
seuls les Deny explicites font foi (advisory via la projection LP3).
Couvre permissions.json persisté avec fallback allow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Étend l'enforcement OS au chemin structuré (sessions Claude/Codex mode JSON),
jusqu'ici seulement advisory. Approche validée par l'Architecte : transposer la
technique du PTY plutôt qu'un pre_exec (rejeté — landlock alloue, deadlock malloc
post-fork en process multithreadé).
Mécanique (cfg(target_os=linux)) : run_turn_sandboxed/drain_sandboxed exécutent
enforce(plan) sur un thread jetable AVANT le spawn std, puis std::process::spawn
depuis ce thread ; l'enfant hérite le domaine Landlock via les credentials de la
tâche (garanti à travers fork/clone/execve, y compris posix_spawn — pas de pre_exec
nécessaire, forbid(unsafe_code) préservé). Fail-closed sur Err d'enforce (aucun
child). Timeout sous sandbox : oneshot killer + tokio::time::timeout → kill → EOF →
reap (pas de zombie/thread bloqué). Chemin non-sandboxé (plan None / pas d'enforcer /
non-Linux) = drain async tokio inchangé.
Contrat : SpawnLine.sandbox ; AgentSessionFactory::start(.., sandbox) ;
StructuredSessionFactory::with_sandbox_enforcer (jumeau du PTY) ; plan calculé en
step 5d de lifecycle relayé à launch_structured ; default_enforcer() injecté au
composition root.
Tests : 7 invariants e2e (parité, companion négatif, fail-closed, no-op natif,
confinement de l'irréversibilité entre tours, timeout, resume préservé) — zéro
token (sh/FakeCli). 80 suites vertes, 0 failed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Composition root : PortablePtyAdapter::new() injecte désormais default_enforcer()
(Landlock sur Linux / Noop ailleurs) via with_sandbox_enforcer, dans state.rs et
dans LocalHost (remote/mod.rs).
Launch path : LaunchAgent::execute peuple spec.sandbox via le plan pur
compile_sandbox_plan(effective_permissions, SandboxContext{project_root, run_dir}),
en réutilisant l'EffectivePermissions déjà résolu pour la projection advisory LP3.
Invariant produit conservé : eff == None ⇒ plan None ⇒ comportement natif, aucune
projection OS.
Portée : seul le chemin PTY brut est OS-enforcé ; le chemin structuré (Claude/Codex
mode JSON) porte le plan mais ne l'enforce pas encore (lot ultérieur).
Tests : ajout d'un test d'intégration bout-en-bout (pty/mod.rs, sandbox_e2e_tests)
prouvant qu'un enfant spawné sous un plan RW restreint ne peut PAS écrire hors-grant
(écriture bloquée par le kernel) alors que l'écriture dans le grant passe, plus un
test compagnon anti faux-positif (sans plan ⇒ pas de sandbox). Skip propre si le
kernel n'a pas Landlock. domain+infrastructure+application+app-tauri verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LP4-1 : adaptateurs SandboxEnforcer concrets (LandlockSandbox Linux via le LSM
Landlock, NoopSandbox ailleurs) + default_enforcer() cfg-sélectionné, prêts à
être injectés au composition root (LP4-3). Aucun changement de comportement
runtime tant que rien n'est câblé : SpawnSpec.sandbox reste None.
LP4-2 : PortablePtyAdapter consomme SpawnSpec.sandbox via spawn_command_sandboxed
(thread jetable restreint par enforce() puis fork → le domaine Landlock est hérité
par l'enfant à travers execve ; les autres threads d'IdeA ne sont jamais sandboxés).
Builder additif with_sandbox_enforcer ; fail-closed si enforce() échoue.
Tests : domain + infrastructure verts (dont les tests Landlock réels de fencing
read-only/write-only et la confine-au-thread).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fondations pures (zéro I/O, zéro dépendance landlock, aucun câblage
runtime — SpawnSpec.sandbox posé mais jamais lu ⇒ zéro régression) de la
voie « airtight » des permissions, complément de la voie projection LP3.
- domain/sandbox.rs : SandboxPlan/PathGrant/PathAccess (RO|RW|EXEC),
SandboxContext, SandboxKind/Status/Error, port SandboxEnforcer, et la
fonction pure compile_sandbox_plan(EffectivePermissions → plan OS).
- domain/permission.rs : render_permission_summary (bloc Markdown injecté
plus tard ; mentionne explicitement fichiers OS-enforced vs commandes
advisory).
- domain/ports.rs : SpawnSpec.sandbox: Option<SandboxPlan> (None ⇒ natif),
propagé à tous les sites de construction.
Sémantique de compile_sandbox_plan :
- Invariant produit : eff == None ⇒ None (rien posé ⇒ CLI 100 % native).
- Borne Landlock : seules les capabilities fichier produisent des grants
(Read→RO, Write/Delete→RW) ; ExecuteBash reste advisory (non verrouillable
par chemin).
- Deny-wins PAR CLASSE D'ACCÈS (RO/RW), fail-closed intra-classe : un Deny
ne ferme que les grants de sa propre classe (un Deny Write n'ampute pas un
Allow Read). Choix retenu pour maximiser l'autonomie des agents : on
respecte exactement la politique pré-renseignée sans sur-restreindre, donc
moins de blocages qui forceraient l'agent à redemander l'utilisateur.
- Globs réduits à leur préfixe statique ; grant abandonné si une barrière de
même classe chevauche (égal/ancêtre/descendant) — sous-approximation
conservatrice (un sandbox additif ne peut pas carver un deny sous-arbre).
Tests : 16 tests purs sur sandbox + 3 sur render_permission_summary,
cargo test --workspace 100 % vert, 0 ignored.
Reste LP4 : LP4-1 adapter LandlockSandbox + pre_exec PTY (fail-open+warning
sauf posture Deny), LP4-2 câblage application, LP4-3 composition root.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Une délégation `idea_ask_agent` interrompue/annulée côté demandeur (futur
`ask_agent` *dropped*) laissait la cible en `Busy` à vie : sur un drop, aucune
branche du `select!` ne s'exécute, donc `mark_idle` n'était jamais appelé. Toute
délégation suivante restait en file derrière ce tour fantôme et n'était jamais
livrée au PTY (symptôme : « DevFrontend/QA ne répondent plus », pont MCP pourtant
ESTAB).
Ajoute un garde RAII `BusyTurnGuard` posé juste après l'enqueue sur les deux
chemins (`ask` PTY et `ask_structured`) : son `Drop` fait `cancel_head` +
`mark_idle` quoi qu'il arrive (erreur, timeout, drop), sauf `disarm()` sur la
branche succès. Indispensable d'être un garde et non un `mark_idle` dans les
branches : le cas réel est un futur droppé. Retire les `cancel_head` redondants
des branches erreur/timeout (positionnel/idempotent).
`sweep_stalled` reste advisory (n'appelle jamais `mark_idle`) — non concerné.
Tests: dropped_ask_future_frees_busy_target, second_delegation_delivered_after_dropped_ask,
cancelled_ask_marks_target_idle + 2 tests du garde. cargo test --workspace vert (80 suites).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Régression introduite par 0f8ba38 : le re-câblage de `.with_structured(...)`
sur `OrchestratorService` (pour activer `drain_with_readiness`, readiness lot 1)
faisait emprunter à `ask_agent` la branche structurée `ensure_structured_session`,
qui ne peut JAMAIS aboutir : depuis le lot B-2 (« Option 1 Terminal + MCP »,
eca2ba9) la fabrique structurée est décâblée de `LaunchAgent`, donc aucun agent
n'a de session structurée — tous tournent en PTY brut et la délégation passe par
les outils MCP. Symptôme : `idea_ask_agent` échouait systématiquement avec
« aucune session structurée vivante après lancement » à chaque relance d'IdeA.
Non détecté par les tests : les e2e loopback câblent leur propre service
structuré complet, jamais le composition root réel (gap tests ≠ composition).
Fix : retirer le `.with_structured` du composition root pour que
`self.structured = None` et que `ask_agent` retombe sur le chemin PTY+MCP
fonctionnel (état pré-0f8ba38). `drain_with_readiness` reste dormant tant que
la voie structurée n'est pas réactivée au composition root.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sauvegarde de l'arbre de travail en cours (persistance P8, conversations
C-series, write-portal frontend, médiation d'entrée) avant d'attaquer le
support de la délégation inter-agents pour les profils Codex.
Le round-trip inter-agent question/réponse est couvert sans tokens par
les tests loopback existants (state::mcp_e2e_loopback_tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capstone du chantier handoff : un agent change de moteur (Claude↔Codex) en
gardant la continuité du travail.
- clean_conversation → invalidate_engine_link : préserve conversation_id (id de
paire stable) et n'efface que engine_session_id (lien moteur étranger) ;
renvoie l'id de paire préservé
- relaunch_if_live relance avec l'id de paire (repli for_pair si session de
fond) ⇒ handoff P7 réinjecté dans le nouveau moteur, resume P8c routé via
providers.json[nouveau provider] (vide ⇒ SessionPlan::None : l'ancien
resumable n'est jamais repassé ; fidélité par le handoff)
- tests : 4 cas swap (préservation id de paire, engine_session_id vidé, handoff
réinjecté, pas de --resume de l'ancien moteur, repli for_pair) ;
change_agent_profile 12 verts, agrégat 829 passed 0 failed
Chantier persistance conversationnelle + handoff cross-profile (P1→P8d) complet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Referme la régression latente de P8a : pour un profil structuré, le resumable
passé au moteur vient de providers.json[provider] (vrai id Claude/Codex), jamais
de l'id de paire désormais porté par la cellule.
- resolve_session_plan devient async + prend root ; branche structurée câblée :
get(pair, provider_key) ⇒ Resume{engine_id}, sinon None ; repli gracieux sur
l'ancien comportement si le store n'est pas câblé (zéro régression tests) ;
branche non structurée strictement inchangée
- tests : 6 cas (claude/codex Resume{engine} avec assert_ne! vs id de paire,
discrimination de clé provider, store vide/cellule neuve/non-uuid ⇒ None) ;
structured_launch_d3 22 verts, domain+application+app-tauri sans régression
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Au lancement structuré, le resumable exposé par le moteur est rangé dans
providers.json sous (id de paire, provider) — best-effort, n'altère jamais le
lancement.
- domaine : StructuredAdapter::provider_key() ⇒ "claude"/"codex" (clé stable
par famille, lisible, indépendante de l'uuid d'instance)
- application : port ProviderSessionProvider (root par appel) +
with_provider_session_provider ; helper persist_provider_session dans
launch_structured (skip si provider/adapter/id moteur absents ou set KO)
- app-tauri : AppProviderSessionProvider (FsProviderSessionStore sur le root)
- tests : 6 cas (nominal claude/codex, no-op sans provider/sans id moteur,
best-effort, mapping provider_key) ; domain+application verts
Lecture du store pour --resume = P8c.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scelle la cohérence corrigée en P8a :
- infra (5) : resolve stable à travers 2 instances de registre (restart),
resolve.id == for_pair == uuid agent, commutativité, distinction des paires
- application (2) : round-trip réel « handoff sauvé sous la clé resolve ↔
rechargé au relancement de la cellule portant cette clé » + contre-épreuve
(handoff d'une autre paire ⇒ pas de reprise)
Clarifie : une cellule neuve (conversation_id=None) ne charge pas de handoff ;
la reprise opère à la réouverture. domain/infra/application verts, zéro
régression. Code de prod non modifié.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Corrige l'incohérence P6/P7 : la cellule et la persistance partagent désormais
le MÊME id de paire IdeA, déterministe et stable au redémarrage — la clé sous
laquelle P6b sauve log/handoff == celle sous laquelle P7 les charge.
- domaine : ConversationId::for_pair(a,b) pur/déterministe (User↔Agent = uuid
agent ; Agent↔Agent = XOR commutatif) ; LeafCell gagne engine_session_id
(cache resumable moteur, additif serde default)
- infrastructure : InMemoryConversationRegistry::resolve utilise for_pair au
lieu de new_random() → id recalculable sans état, identique après restart
- application : launch_structured persiste l'id de paire sur la cellule (et l'id
moteur sur engine_session_id) ; cellule neuve ⇒ dérive for_pair(User,agent) ;
resolve_conversation repli factorisé sur for_pair ; withers layout clone-and-
mutate (préservent les champs additifs)
- app-tauri : engine_session_id propagé dans les DTO
Suites domain/infra/application/app-tauri vertes (assertion structured_launch_d3
recodée sur le contrat). Test déterminisme/restart dédié à ajouter au retour de
quota agent (binôme Test interrompu par limite de session).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cadrage du swap cross-profile et correction de l'incohérence de clé P6/P7 :
LeafCell.conversation_id = id de paire IdeA (clé log/handoff, stable au swap),
resumable moteur isolé par provider dans providers.json (+ cache
engine_session_id). §19.6 : P8 éclaté en P8a (prioritaire/bloquant) → P8d.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Au (re)lancement, l'agent retrouve sur quoi il travaillait : si la cellule a une
conversation_id avec un handoff, son objectif + résumé sont injectés en dernière
section du convention file (# Reprise de la conversation), best-effort et
provider-indépendant.
- application : port HandoffProvider (root par appel, comme MemoryRecall) ;
LaunchAgent.with_handoff_provider + resolve_handoff (parse uuid + load,
jamais bloquant) ; section rendue dans compose_convention_file après la
mémoire projet
- app-tauri : AppHandoffProvider (FsHandoffStore sur le root du projet) câblé
- tests : 4 purs (objectif présent/None/blanc/absent) + 5 intégration
(happy path, provider absent, conversation_id None, uuid invalide, sans
handoff ⇒ lancement OK sans section) ; application 311 + app-tauri 204 verts
Volet --resume/providers.json = P8 (swap cross-profile).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ask_agent persiste désormais chaque paire dans son conversationId : tour Prompt
à l'enqueue, tour Response au succès — best-effort (un échec de persistance ne
dégrade jamais la délégation).
- application : port RecordTurnProvider (matérialise un RecordTurn sur le bon
project root — OrchestratorService est mono-instance multi-projets, le log est
par root) ; wither with_record_turn(provider, Clock) ; helper
record_turn_best_effort ; horodatage via domain::ports::Clock (pas d'horloge
infra dans application)
- app-tauri : AppRecordTurnProvider (Fs* sur le root du projet) câblé au
composition root avec le SystemClock partagé
- tests : 5 cas (paire Prompt→Response, fil A↔B vs User↔B, no-op sans provider,
ask Ok même si record échoue) ; orchestrator_service 40, application +
app-tauri verts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Logique du checkpoint conversationnel isolée et testable, sans câblage live :
append du tour au log canonique → load handoff → fold incrémental (tour neuf
seul) → save. Mapping StoreError→AppError via From existant. Debounce repoussé.
- application/src/conversation/{mod,record}.rs : RecordTurn::record
- tests : 6 cas (incrémentalité prouvée via summarizer-espion, propagation
d'erreur sans effet de bord, isolation par conversation) ; suite verte
Câblage sur les seams réels (reply/ask) + composition root = P6b.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Range le resumable_id du moteur par (conversation, provider) dans
providers.json, support de reprise non exclusif (le handoff reste la source de
fidélité au swap cross-profile) — §19.2/§19.3.
- domaine : port ProviderSessionStore (get/set par provider)
- infra : FsProviderSessionStore — providers.json map plate, set en
read-modify-write atomique (tmp+rename) sérialisé par conversation
(coexistence multi-providers garantie), get absent ⇒ None, corrompu ⇒
StoreError::Serialization
Tests : 6 cas P5 (coexistence claude+codex, 12 set concurrents sans perte,
corruption, isolation) ; cible conversation_log 30 verts, suites complètes
sans régression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Un launch structuré route vers une AgentSession enregistrée dans
StructuredSessions ; son id n'est jamais un PTY vivant. Câbler le bridge PTY
appelait subscribe_output avec un id inconnu de l'adapter → NotFound
("pty handle not found"), faisant échouer tout lancement d'agent structuré.
On ne pompe les octets que sur le chemin PTY brut (structured: None).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`codex exec` 0.137 ne connaît pas `--ask-for-approval` (`error: unexpected
argument`) ; ce flag appartient à la commande interactive `codex`, pas à la
sous-commande `exec` déjà non-interactive. Sa présence cassait toute réponse
d'un agent Codex (flux structuré terminé sans Final), bloquant la conversation
inter-agents. argv désormais verrouillé exact dans les tests (16/16 verts).
Découvert et corrigé pendant la validation réelle de la messagerie inter-agents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dernier kilomètre de l'orchestration native : une CLI MCP réellement lancée
joint le serveur MCP du projet et ses outils idea_* aboutissent au vrai dispatch.
- M5a endpoint loopback par projet (interprocess UDS/named pipe, source unique
mcp_endpoint, cleanup au close) — zéro port réseau, AppImage/SSH-safe.
- M5b sous-commande `idea mcp-server` : pont stdio↔loopback headless (avant init
Tauri), handshake {"project","requester"}, endpoint-absent borné, EOF propre.
- M5c McpServerHandle boucle accept + serve_peer par pair ; requester réel
propagé jusqu'à OrchestratorRequestProcessed (fin du "mcp" figé) ; isolation
des pairs ; terminaison propre.
- M5d apply_mcp_config écrit la déclaration réelle (current_exe + --endpoint
mcp_endpoint + --project simple-uuid + --requester) ; McpRuntime injecté comme
donnée (application ne dépend pas de app-tauri).
- M5e smoke e2e sur vrai loopback : list/ask inline, cible PTY → erreur typée,
JSON malformé → pas de panic, requester propagé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stabilise le routage de `ask` avant le bind transport MCP (v5). Invariant
« 1 agent = 1 employé » durci ; un agent traite un tour à la fois.
- R0a garde LaunchAgent : lève AgentAlreadyRunning pour un lancement neuf
ciblant un agent déjà vivant sur un autre node (PTY + structuré) ; rebind
seulement même-node ou réattache explicite (conversation_id). Idem spawn_agent.
- R0b list_live_agents agrège PTY + structuré (LiveSessions) + dédup.
- R0c réconciliation des layouts.json à doublons à l'ouverture (host déterministe,
idempotent) — corrige « une cellule reset au retour d'onglet ».
- R0d UI : option agent désactivée si vivant ailleurs + « aller à la cellule »,
mapping AGENT_ALREADY_RUNNING.
- A0 sérialisation FIFO des tours par agent_id dans ask_agent (verrou tokio par
agent ; agents différents en parallèle ; timeout tour 300s, cap attente 600s).
Cadrage : .ideai/briefs/orchestration-v5-transport-bind-cadrage.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expose l'orchestration IdeA comme serveur MCP par-dessus le même
OrchestratorService::dispatch, avec repli fichier .ideai/requests pour les
CLI sans MCP. v3 réduite à la surface MCP : la messagerie inter-agents et la
corrélation requête↔réponse étaient déjà résolues par §17 (send_blocking).
- M0 capacité MCP sur le profil (McpCapability/McpConfigStrategy/McpTransport)
- M1 injection conf MCP au LaunchAgent + prose adaptée selon la surface
- M2 serveur/adapter MCP (JSON-RPC 2.0 maison ; outils idea_*) + ListAgents
- M3 câblage par projet (registre mcp_servers jumeau du watcher)
- M4 observabilité UI : OrchestratorRequestProcessed.source = file|mcp + badge
Trois portes d'entrée (fichier, MCP, UI) → un seul dispatch ; aucun nouveau
port applicatif ; MCP confiné à l'adapter infra. Tous lots verts (cycle §3).
Cadrage : .ideai/briefs/orchestration-v3-cadrage.md ; ARCHITECTURE.md §14.3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ferme le canal fichier de la messagerie inter-agents : un agent qui délègue
via le protocole .ideai/requests reçoit désormais le CONTENU de la réponse,
plus seulement l'ACK de cycle de vie.
- OrchestratorResponse gagne reply: Option<String> (skip_serializing_if
None, camelCase). success() porte le reply ; failure() => None.
- dispatch_file passe out.reply (de l'OrchestratorOutcome D6) à success.
Champ additif : un .response.json legacy sans reply reste valide ; une
commande non-ask (agent.run, spawn_agent...) => clé reply absente du JSON.
Tests (QA) : +4 verts — ask => reply présent ET detail coexistent ;
non-ask => clé absente (garde anti-always-green sur l'absence littérale) ;
round-trip legacy ; failure => absent. cargo test --workspace : 824 passed.
Orchestration structurée bouclée : in-process (D6) + protocole fichier (D6b).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dernier lot de §17 : seuls les profils pilotables en mode structuré sont
proposés à la sélection/création.
- domain : AgentProfile::is_selectable() = structured_adapter.is_some(),
source unique de vérité du prédicat.
- infrastructure : AgentSessionFactory::supports délègue à is_selectable
(supports et is_selectable ne peuvent plus diverger).
- application : selectable_reference_profiles() = reference_profiles()
filtré ; ReferenceProfiles et FirstRunState exposent la liste filtrée
(Claude/Codex). reference_profiles() brut reste à 4 (data intacte) ⇒
un agent Gemini/Aider/custom legacy déjà configuré continue de tourner.
- frontend : bloc AddCustomProfile retiré du wizard first-run, action
addCustom retirée du viewmodel ; wizard n'affiche que la liste filtrée.
Tests (QA) : is_selectable (table de vérité), cohérence stricte
is_selectable<->supports, liste exposée=2 / data brute=4, garde
anti-régression Vitest sur l'absence du bloc custom — validées par
mutation. cargo test --workspace : 820 passed. npx vitest run : 344 passed.
§17 COMPLET (D0->D7). Suivi restant : D6b (surfacer reply dans le writer
wire .response.json pour la délégation par protocole fichier).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comble le trou historique : un agent qui en interroge un autre reçoit enfin
le CONTENU de la réponse, plus seulement un ACK de cycle de vie.
- domain : OrchestratorCommand::AskAgent { target, task } + action wire
agent.message (target+task requis) ; DomainEvent::AgentReplied
{ agent_id, reply_len } (bus I/O-free, le contenu remonte par l'outcome).
- application : OrchestratorOutcome gagne reply: Option<String>. Méthode
ask_agent (§17.4) — cible structurée vivante ⇒ send_blocking direct ;
cible morte ⇒ LaunchAgent structuré puis send ; agent vivant en PTY ou
profil sans structured_adapter ⇒ Invalid (non adressable, jamais d'ACK
trompeur) ; agent inconnu ⇒ NotFound ; service non câblé ⇒ Invalid.
Timeout (300s) ⇒ erreur typée SANS tuer la session. Succès ⇒ publie
AgentReplied + reply: Some(content). Injection additive via builders
with_structured / with_events (call sites legacy intacts).
- app-tauri : state câble with_structured/with_events ; DTO AgentReplied.
Tests (QA) : +14 verts (11 app + 3 domaine), invariants validés par
mutation test (reply!=None, timeout ne shutdown pas). cargo test
--workspace : 817 passed, 0 failed.
Suivi (D6b) : surfacer reply dans le writer wire .response.json
(infrastructure/orchestrator) pour la délégation par protocole fichier.
Reste D7 : menu restreint Claude/Codex + retrait custom.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le frontend consomme l'exécution structurée livrée en D4 :
- AgentChatView : jumeau chat de TerminalView. Accumule les textDelta du
tour courant, badges toolActivity, fige sur final (le contenu final
remplace les deltas, pas de double rendu). Saisie Enter/Shift+Enter.
Au montage : reattach (rejoue le scrollback) ou launch ; au démontage :
détache seulement (jamais de close). Vue pure pilotée par le port.
- LayoutGrid/LeafView : routage cellKind — AgentChatView si "chat",
TerminalView sinon (chemin PTY inchangé). Cache cellKindBySession pour
router correctement après ré-attache/navigation.
- Port AgentGateway : sendPrompt / reattachChat / closeAgentSession ;
cellKind sur TerminalHandle. Adapter Tauri (invoke + Channel<ReplyChunk>)
et mock (MockChatSession, _setChatAgents) étendus.
- Types TS mirrors : CellKind, ReplyChunk, ReattachChatDto + cellKind sur
TerminalSession.
Tests (QA) : 21 Vitest verts — routage chat/pty (non-régression terminal),
accumulation/non-doublon (garde anti-always-green delta!=final), ré-attache
sans re-spawn, envoi Enter vs Shift+Enter, badges toolActivity, mock stream.
npx vitest run : 345 passed, 0 failed. npm run build vert.
Reste D6 (messagerie inter-agents via send_blocking) et D7 (menu restreint
Claude/Codex + retrait custom) pour clore §17.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le commit D3 (56913b9) a ajouté le champ structured: Option<...> à
LaunchAgentOutput mais a laissé deux constructions de test sans ce champ,
cassant la compilation du crate de test app-tauri (E0063). Ajoute
structured: None aux deux. Suite workspace de nouveau verte (775/0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
infrastructure/src/session/ : machinerie de process générique (paramétrable par
la commande = seam d'injection du fake CLI), adapters ClaudeSdkSession/
CodexExecSession avec parsing ISOLÉ par adapter (parse_event), factory
StructuredSessionFactory (routage par structured_adapter), FakeCli scriptable +
harnais de conformité Liskov assert_agent_session_contract.
Incarnation « un run par tour » (send relance claude -p / --resume <id>,
continuité via conversation_id — colle au pivot reprise B).
Tests : 41 contre le FAKE CLI (jamais le vrai claude/codex), workspace vert.
Points en attente des spikes S1/S2 (format réel) — n'impactent que parse_event :
- mapping JSON→ReplyEvent Claude (S1) et Codex (S2) sur schémas SUPPOSÉS ;
- Claude multi-blocs : parse_event ne garde que le 1er bloc (à corriger si Claude
émet plusieurs blocs/message — confirmer S1) ;
- flux sans Final : permissif côté adapter, l'erreur est gérée par send_blocking
(consommateur). À reconfirmer côté UI streaming (D4).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Personas durables alignés sur la méthode (cycle dev↔QA), l'architecture
hexagonale réelle (crates/dossiers/commandes) et la roadmap A+B+C.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- LOT C2 (§14.5.3) : use cases de configuration des embedders déclaratifs
(List/Save/Delete + DescribeEmbedderEngines : modèles ONNX recommandés,
environnement local détecté, stratégies compilées). UI EmbedderSettings.
- LOT C3 (§14.5.5) : suggestion contextuelle best-effort à l'activation quand la
mémoire dépasse le budget de recall sans embedder configuré (event
EmbedderSuggested, anti-spam 1×/session, « ne plus demander »).
- Contexte projet partagé .ideai/CONTEXT.md (model-agnostic) injecté à tous les
agents/profils au lancement, avant la persona. UI ProjectContextPanel.
Tests : backend workspace vert (0 échec) ; frontend 306/306.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace les StubEmbedder pour les stratégies localServer/api/localOnnx par de
vrais moteurs, chacun derrière une feature cargo off-by-default — la posture
fondatrice « rien d'imposé, zéro dépendance » (défaut none → rappel naïf) reste
byte-for-byte inchangée.
C1a (feature vector-http, reqwest rustls optional):
- HttpEmbedder couvrant localServer (Ollama/llama.cpp) et api (OpenAI/Voyage…),
payload OpenAI-compatible /v1/embeddings, ordre restauré par index, bearer
token lu via env var (jamais en clair), timeout client 30s.
- detect_ollama() pour la détection de l'existant (C3).
C1b (feature vector-onnx, fastembed v5 optional):
- OnnxEmbedder en-process (e5-small, dim 384), init paresseuse + spawn_blocking,
cache modèle sous <app_data>/embedders/onnx — aucun download au build ni au
first-run, uniquement à la demande au 1er embed.
- Catalogue RECOMMENDED_ONNX_MODELS + ONNX_CACHE_SUBDIR + onnx_model_is_cached
exposés (sans feature) pour la config (C2) et la popup (C3).
embedder_from_profile(profile, onnx_cache_dir) dispatche feature-gated ; sans la
feature, retombe sur StubEmbedder (Unsupported) → fallback naïf via
AdaptiveMemoryRecall. Composition root (build_memory_recall) propage le cache dir.
Tests: 10 HTTP + 6 ONNX (dont 2 #[ignore] download réel) + 26 vectoriels, verts
en défaut, --features vector-http et --features vector-onnx.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Un agent ne peut tourner que dans une seule cellule à la fois. La garde dans
LaunchAgent refuse le spawn si l'agent est déjà vivant dans un autre node
(AGENT_ALREADY_RUNNING) ; idempotent sur le même node ; le chemin resume
(agent mort) reste inchangé. Le node_id est désormais plombé jusqu'au use case.
Corrige le reset asymétrique d'une cellule au changement d'onglet : deux leaves
partageant le même agent id rendaient session_for_agent/is_agent_live/stop_agent
ambigus (cible arbitraire). Le churn reset/reattach déclenchait aussi les accents
mélangés (FIFO intact, non touché).
- snapshot agentWasRunning calculé par node (is_node_live) et non par agent
- commande list_live_agents + live_agents()/node_for_agent()/is_node_live()
- UI : dropdown grise les agents déjà placés ailleurs ; 2e cellule en doublon
affiche « disponible » au lieu d'une relance fantôme
Tests : cargo test (application + app-tauri) vert ; tsc + vitest vert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pièce 1 (backend) : state.rs câble AdaptiveMemoryRecall via build_memory_recall,
piloté par le profil embedder chargé depuis embedder.json (fallback none). Défaut
none ⇒ NaiveMemoryRecall nu (comportement inchangé, StubEmbedder jamais touché,
zéro dépendance lourde). Instance de recall partagée (RecallMemory + LaunchAgent).
Chargement du profil isolé sur un runtime dédié (évite le block_on imbriqué).
Pièce 2 (frontend) : feature mémoire complète en miroir de skills —
MemoryGateway (port) + TauriMemoryGateway + MockMemoryGateway, types domaine
Memory/MemoryIndexEntry/MemoryType, MemoryPanel/MemoryEditor/useMemory, onglet
sidebar « Memory » dans ProjectsView. CRUD par slug, liens [[slug]] résolus.
Le sujet mémoire est clos : CRUD .md + index + rappel adaptatif + injection à
l'activation des agents + UI de gestion. Embedder concret ONNX/HTTP reste un
follow-up (défaut none = pleinement fonctionnel sans dépendance).
Tests: backend 57 binaires verts, frontend 285 tests verts, typecheck OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
À l'activation d'un agent, LaunchAgent compose désormais une section
« # Mémoire projet » dans le convention file généré (CLAUDE.md/AGENTS.md…),
au même titre que les skills assignés (§14.2). Les agents lisent ainsi la
mémoire projet sans aucun flag ni mécanisme propre à une CLI.
- LaunchAgent reçoit le port MemoryRecall (Arc<dyn MemoryRecall>), résout le
rappel (budget AGENT_MEMORY_RECALL_BUDGET=2048, requête = persona de l'agent)
en best-effort : mémoire vide/absente ou erreur ⇒ section omise, le launch
n'est jamais bloqué (comme un SkillRef dangling).
- compose_convention_file gagne un argument `memory: &[MemoryIndexEntry]`
(reste pure) ; section injectée seulement pour la stratégie conventionFile.
- state.rs : une seule instance NaiveMemoryRecall partagée (recall + LaunchAgent).
Tests: 57 binaires verts. compose_convention_file (vide ⇒ inchangé, ordre,
cohabitation skills) + intégration LaunchAgent (section présente / absente /
best-effort sur erreur / pas d'injection en stratégie env).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Base de connaissance persistante par projet, indépendante de tout modèle/CLU
et de git. Cadrage archi en §14.5 (ARCHITECTURE.md), cycle Archi→Dev→Test.
LOT A — étage 1 (.md, source de vérité)
- domaine: entité Memory (+ MemorySlug, MemoryType, MemoryFrontmatter,
MemoryLink, MemoryIndexEntry), liens [[slug]], index MEMORY.md dérivé
- port MemoryStore + MemoryError, adapter FsMemoryStore (.ideai/memory/)
- application: 7 use cases (Create/Update/List/Get/Delete/ReadIndex/
ResolveLinks), From<MemoryError> for AppError
- app-tauri: commandes + DTO, events MemorySaved/MemoryDeleted
- suppression de la variante morte DomainError::MalformedFrontmatter
LOT B — rappel adaptatif (étage 1)
- port MemoryRecall + MemoryQuery, adapter NaiveMemoryRecall (troncature
au budget de tokens, court-circuit budget-0), use case RecallMemory
LOT C — étage 2 vectoriel (structure complète, zéro dépendance lourde)
- port Embedder + EmbedderError, profils déclaratifs EmbedderProfile/
EmbedderStrategy (embedder.json)
- VectorMemoryRecall (cosinus, cache .ideai/memory/.index/ gitignoré)
- AdaptiveMemoryRecall (bascule pure should_use_vector), défaut none
- HashEmbedder (déterministe, tests), StubEmbedder (onnx/server/api)
Tests: 57 binaires verts, build + clippy --workspace sans warning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Permet de recharger la conversation CLI précédente de chaque cellule à la
réouverture du projet, de façon universelle (indépendant du modèle/CLI).
- profil AgentRuntime: bloc déclaratif optionnel `session { assignFlag, resumeFlag }`
- LeafCell: `conversationId` (persistant, distinct du SessionId PTY) + `agentWasRunning`
- runtime: SessionPlan (None/Assign/Resume) + composition pure des args
- LaunchAgent: décide Assign vs Resume, génère l'UUID, remonte l'id assigné
(persistance par l'appelant via setCellConversation — découplage SRP)
- close: SnapshotRunningAgents fige `agentWasRunning` avant le kill-all
(statut clot/en cours universel, sans parsing CLI)
- SessionInspector: port optionnel best-effort + adapter ClaudeTranscriptInspector
- popup de reprise par cellule (statut + sujet/tokens si dispo), intercalée
avant le Resume auto, jamais sur le chemin reattach
fix(terminals): sérialise les écritures PTY (file FIFO par handle) — corrige
les caractères mélangés/accents dus au réordonnancement des invoke Tauri concurrents
fix(layout): l'opération `move` préservait mal les champs du leaf (perdait `agent`)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- domain: OrchestratorCommand::CreateSkill with optional scope field
(defaults to Project, case-insensitive, UnknownScope on bad value)
- application: dispatch CreateSkill through the CreateSkill use case
- app-tauri: build OrchestratorService and start/stop per-project request
watchers on open/create/close_project (ensure/stop_orchestrator_watch)
- tests: domain validation, service dispatch, infra watcher e2e,
app-tauri wiring lifecycle (idempotent, isolated, stop)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each open/reattach spawned a pump thread feeding pty_bridge[session]. The
broadcast hub added a subscriber per attach without dropping the previous one,
and the stale pump thread kept delivering (its send_output still succeeded
against the new channel) — so every byte was delivered twice (more after
further re-attaches). Accumulated on each tab/layout/agent switch; window
resize made it glaring.
- Broadcast::subscribe() is now single-consumer: a new subscription supersedes
the previous one, ending the old pump thread's stream.
- PtyBridge gains a per-session generation; register() returns it and
unregister_if(session, gen) only removes when still current, so a dying
superseded thread can't tear down the channel that replaced it.
- All three attach sites (open/reattach/agent launch) use unregister_if.
Tests: 6 added (generation guard + single-consumer broadcast). Workspace green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:52:24 +02:00
1509 changed files with 320609 additions and 5739 deletions
- **Cadrage** : quand Main t'annonce une feature, tu produis le découpage en lots, les
frontières touchées, les ports/contrats à créer ou modifier, et les impacts.
- **Contrats** : tu définis les ports, les DTO, les signatures et les invariants que les
devs devront respecter. Un contrat flou est un bug à venir : tranche.
- **Arbitrage** : quand un dev remonte un écart entre le cadrage et la réalité du code,
c'est **toi** qui décides ce qui rentre dans le lot et ce qui part en dette ou en
ticket séparé.
- **Cartographie** : tu maintiens une vision à jour de la structure du projet (modules,
couches, dépendances) et tu la documentes là où le projet la conserve.
- **Le Domaine ne dépend de RIEN** : ni Tauri, ni tokio, ni git2, ni portable-pty, ni serde (le moins possible — voir §1.4). Il ne contient que des entités, value objects, règles métier et **traits = ports**.
-**L'Application** dépend du Domaine. Elle orchestre les use cases en parlant **uniquement aux ports** (traits), jamais aux adapters concrets.
-**L'Infrastructure** dépend du Domaine et de l'Application (elle implémente les ports). Elle contient tous les détails techniques (PTY, FS, git, SSH, WSL, stores).
- **La Présentation** (Tauri commands + React) dépend de l'Application. Les commandes Tauri sont des **adapters entrants (driving adapters)** ; les impl de ports sont des **adapters sortants (driven adapters)**.
Aucune flèche ne pointe **vers** la présentation ou l'infrastructure. L'inversion de dépendance (le **D** de SOLID) est matérialisée par les traits définis dans le domaine et implémentés dehors.
### 1.2 SOLID, point par point, traduit IdeA
| Principe | Application concrète |
|---|---|
| **S** — Single Responsibility | Un use case = une intention métier (`LaunchAgent`, `SyncAgentWithTemplate`). Un adapter = une techno (`Git2Repository` ne fait que du git). Le `LayoutNode` ne gère que la topologie, pas le rendu. |
| **O** — Open/Closed | Ajouter une IA = ajouter un **profil déclaratif** (donnée), pas du code. Ajouter un mode distant = nouvel adapter `RemoteHost` sans toucher aux use cases. Ajouter une stratégie d'injection de contexte = nouvelle variante d'enum + handler, use case inchangé. |
| **L** — Liskov | Tout `RemoteHost` (local, SSH, WSL) est substituable : un use case marche identiquement quelle que soit l'impl. Les contrats (pré/postconditions) des ports sont documentés et respectés par chaque adapter. |
| **I** — Interface Segregation | Ports **fins et ciblés** : `ProcessSpawner`, `FileSystem`, `PtyPort` séparés plutôt qu'un `System` fourre-tout. Un use case ne reçoit que les ports qu'il consomme. |
| **D** — Dependency Inversion | Domaine définit les traits ; infra les implémente ; l'application reçoit des `Arc<dyn Port>` par **injection** (composition root dans la couche Tauri). |
### 1.3 Hexagonal côté Frontend (React aussi)
L'hexagonal ne s'arrête pas à Rust. Côté React on applique le même découpage :
- **Domaine UI / modèles de vue** : types TS purs (miroir des DTO), logique de présentation pure (ex. calcul de tailles de cellules d'un `LayoutNode`), testable sans React ni Tauri.
- **Ports UI** : interfaces TS (`AgentGateway`, `TerminalGateway`, `ProjectGateway`, `LayoutGateway`, `GitGateway`, `RemoteGateway`) décrivant **ce dont l'UI a besoin**, indépendamment du transport.
- **Adapters UI** : implémentation des ports via `@tauri-apps/api` (`invoke` pour commands, `listen` pour events). Remplaçables par des **mocks** en test/Storybook.
- **Présentation** : composants React, hooks, state (Zustand/Redux) qui consomment les ports UI, jamais `invoke()` en direct.
Bénéfice : le frontend est testable et développable sans backend (adapters mock), et la frontière IPC est centralisée en un seul endroit.
### 1.4 Domaine pur vs adapters — règle pratique Rust
- Le crate `domain` est **`#![no_std]`-friendly d'esprit** (pas imposé), sans dépendance I/O. Tolérance pragmatique : `serde` est autorisé **uniquement** pour dériver la (dé)sérialisation des entités persistées (manifeste, layout, profils), car c'est une contrainte métier de format, pas un détail technique d'I/O. Les **traits/ports** y vivent. Pas de `tokio`, pas de `std::process`, pas de `std::fs`.
- Tout ce qui touche le monde réel (`std::fs`, `Command`, sockets, libgit2, PTY) vit **exclusivement** dans `infrastructure`.
**Hors périmètre :**
-Tu **n'implémentes pas les features** (c'est DevBackend/DevFrontend).
-Tu ne décides pas de la **forme** des surfaces utilisateur (c'est UX) : tu bornes ce
qui est techniquement possible, tu ne dessines pas.
- Tu ne décides pas des branches, commits ou merges (c'est Git).
ces ports. Ils sont remplaçables : c'est le test de vérité de la frontière.
- **La règle des dépendances** : elles pointent toujours **vers l'intérieur**. Une
dépendance du domaine vers un adapter est une violation, jamais un raccourci accepté.
- **La composition root** est le seul endroit qui connaît les implémentations concrètes
et les câble.
- **Commands (Frontend → Backend, request/response)** : `invoke("create_project", {...})`. Le handler `#[tauri::command]` désérialise le DTO, appelle le use case, renvoie un `Result<DTO, ErrorDTO>`. **Stateless** côté forme : tout l'état vit dans des services managés via `tauri::State`.
- **Events (Backend → Frontend, push)** : flux PTY (octets/base64), changements de statut d'agent, fin de processus, progrès git, drift de template détecté. Émis via `app_handle.emit(...)` / channels Tauri. L'`EventBus` domaine est relayé vers ces events Tauri par un adapter dans `app-tauri`.
Tu appliques **SOLID** comme grille de lecture systématique :
> **Décision** : le flux PTY haute fréquence passe par des **Tauri Channels** (`tauri::ipc::Channel`) plutôt que des events globaux, pour la perf et l'isolement par session terminal.
- **S** — une unité, une raison de changer. Un module qui change pour deux motifs
différents doit être scindé.
- **O** — ouvert à l'extension, fermé à la modification : on ajoute un adapter, on ne
réécrit pas le cœur.
- **L** — toute implémentation d'un port doit être substituable sans surprise pour
l'appelant (pas de précondition renforcée, pas de postcondition affaiblie).
- **I** — des ports **étroits et spécifiques** plutôt qu'une interface fourre-tout : un
client ne doit pas dépendre de méthodes qu'il n'utilise pas.
- **D** — on dépend d'abstractions, jamais de détails concrets. C'est ce qui rend
l'hexagone possible.
Quand un choix technique menace ce socle, tu le refuses et tu expliques l'alternative.
Si une contrainte réelle impose une entorse, elle est **explicite, bornée et
documentée** — jamais silencieuse.
---
## 3. Modèle de domaine
## 3. Le cycle, vu d'Architect
### 3.1 Vue d'ensemble (relations)
Tu interviens **au début** du cycle, et **en arbitrage** ensuite :
```text
1. UX a conçu la surface (dès qu'il y a de l'UI) → tu lis la conception avant de cadrer :
ce que l'UI doit exposer détermine les contrats.
2. Main : « nouvelle feature X »
→ TOI : cadrer.
- frontières touchées, couches impactées
- ports/contrats/DTO à créer ou faire évoluer
- découpage en lots (B1, B2… / F1, F2…) livrables et testables
- invariants à préserver et pièges connus
→ tu rends le cadrage à Main, qui délègue l'implémentation.
3. Pendant l'implémentation, un dev remonte un écart
→ TOI : arbitrer. Ce qui rentre dans le lot, ce qui part en dette/ticket.
4. Feature terminée → tu peux être sollicité pour vérifier que les frontières
- Invariants : `synchronized == true` ⇒ `origin == FromTemplate{..}` (on ne peut pas synchroniser un agent créé from scratch). `context` doit exister à l'activation. `profile_id` doit référencer un `AgentProfile` connu.
| Flag { flag: String } // ex. "--context-file {path}" ou "-f"
| Stdin // pipe du contenu md sur stdin
| Env { var: String } // ex. "AGENT_CONTEXT_FILE"
```
- Invariants : `ConventionFile.target` est un nom de fichier relatif (pas de `..`, pas absolu) ; `Env.var` est un identifiant d'env valide ; `Flag.flag` non vide.
- Invariants : une cellule (feuille de layout) héberge **au plus une**`TerminalSession` active. `pty_size.rows>0 && cols>0`.
**`LayoutNode` / `LayoutTree`** (VO récursif — voir §7 pour le détail complet)
- Invariants : poids relatifs strictement positifs ; somme normalisable ; pas de fusion qui chevauche deux conteneurs distincts ; un `Leaf` référence 0 ou 1 `SessionId`.
**`RemoteHost`** (VO de stratégie de localisation — abstrait Local/SSH/WSL)
- Invariants : `synchronized ⇒ template_id.is_some() && synced_template_version.is_some()` ; `md_path` unique ; cohérence avec les `Agent` chargés.
**`Workspace` / `Window` / `Tab`** (entités de présentation persistée)
-`Workspace` = ensemble des fenêtres d'une session utilisateur.
-`Window` = fenêtre OS ; possède un `LayoutTree`**par onglet actif** et une liste de `Tab`.
-`Tab` = onglet ⇔ **un `Project`** (1:1).
- Invariants : un `Project` ouvert apparaît dans **exactement un**`Tab` à la fois (le drag déplace, ne duplique pas) ; un `Window` a ≥ 1 `Tab` ou est fermée.
**`DomainEvent`** (enum) — `ProjectCreated`, `AgentLaunched`, `AgentExited`, `TemplateUpdated`, `AgentDriftDetected`, `LayoutChanged`, `RemoteConnected`, `GitStateChanged`, `PtyOutput{session_id, bytes}` (ce dernier souvent court-circuité vers un Channel).
---
## 4. Ports (traits du domaine)
## 4. Conventions
> Signatures **conceptuelles** (Rust idiomatique, `async` via `async_trait` ou retours `Future` ; erreurs typées par port). « Consommé par » = use cases. « Implémenté par » = adapters de §5.
### `AgentRuntime`
- **Rôle** : lancer/piloter la CLI d'une IA selon un `AgentProfile`, en gérant l'injection du contexte `.md`.
- **Implémenté par** : `CliAgentRuntime` (un seul adapter générique piloté par le profil déclaratif — c'est l'**Open/Closed**). La diversité des IA = données, pas code.
### `PtyPort` (alias domaine de `TerminalSessionPort`)
- **Rôle** : ouvrir un pseudo-terminal, lire/écrire, redimensionner, tuer.
- **Implémenté par** : `PortablePtyAdapter` (local), `SshPtyAdapter` (PTY distant via russh exec/shell), `WslPtyAdapter` (PTY via `wsl.exe`). Sélection par stratégie `RemoteRef` (Liskov).
### `RemoteHost`
- **Rôle** : abstraction de la **localisation d'exécution** (local / SSH / WSL) : exécuter une commande, ouvrir un PTY, accéder au FS, dans le bon contexte.
- **Implémenté par** : `Git2Repository` (libgit2, local) ; sur SSH/WSL, `RemoteGitRepository` délègue à git CLI via `ProcessSpawner` quand libgit2 ne peut pas atteindre le FS distant (point ouvert §13).
### `EventBus`
- **Rôle** : publier/souscrire les `DomainEvent` (découple émetteurs et présentation).
> Chaque use case : un struct `XxxUseCase` portant ses ports en `Arc<dyn Port>`, une méthode `execute(input: XxxInput) -> Result<XxxOutput, AppError>`. **Single Responsibility**. Aucune dépendance à Tauri.
| Use case | Rôle | Ports consommés |
|---|---|---|
| `CreateProject` | Crée un projet (project root), init `.ideai/`, registre. | `ProjectStore`, `FileSystem`, `IdGenerator`, `EventBus` |
| `MutateLayout` (split/merge/resize/move) | Applique une opération sur le `LayoutTree` (logique **pure** dans le domaine, persistée ici). | `ProjectStore` (persistance) |
| `ConnectRemote` (SSH/WSL) | Établit la connexion, valide l'accès au root. | `RemoteHost`, `FileSystem` |
| `MoveTabToNewWindow` | Détache un onglet → nouvelle fenêtre (réaffectation `WindowId`). | `ProjectStore`, `EventBus` |
La grille « type tableur, lignes/colonnes imbriquées indépendamment + fusion » est modélisée par un **arbre de splits récursif** où chaque conteneur définit son propre découpage. La **fusion** est obtenue nativement : fusionner = ne pas subdiviser une zone (un `Leaf` couvre plusieurs « cellules visuelles » d'un parent voisin). Pour le cas Excel pur (fusion arbitraire chevauchant la grille), on superpose un modèle **GridContainer** avec spans.
- **Lignes/colonnes indépendantes par zone** : chaque `SplitContainer`/`GridContainer` a ses propres poids ⇒ pas de grille uniforme rigide.
- **Imbrication** : un enfant peut être un nouveau `Split`/`Grid` ⇒ « N colonnes dans une ligne, M lignes dans une colonne » de façon arbitraire.
- **Fusion** : `row_span`/`col_span` dans `GridContainer` (modèle tableur fidèle) **ou** simplement un `Leaf` plus grand via `SplitContainer` (cas courant). Le domaine supporte les deux ; l'UI choisit la représentation selon l'interaction.
### 7.2 Invariants (validés dans le domaine, testables sans I/O)
- Tous les `weight > 0`. Les poids sont **relatifs** (l'UI normalise pour le rendu).
- Dans un `GridContainer` : aucune superposition de spans ; toute la surface couverte ; `row+row_span ≤ rows`, `col+col_span ≤ cols`.
- Un `SessionId` n'apparaît que dans **un seul**`Leaf`.
- Les opérations `split`, `merge`, `resize`, `move` sont des **fonctions pures**`LayoutTree -> Result<LayoutTree, LayoutError>` (immutabilité ⇒ testabilité, undo/redo facile).
### 7.3 Sérialisation & persistance
- Sérialisé en **JSON** (serde, `tag`/`content` pour l'enum) → `.ideai/layout.json` (par projet, donc voyage avec le projet, y compris distant).
- Le `Workspace`/`Window`/`Tab` (organisation des fenêtres OS) est persisté côté **store global IDE** (machine-local, pas dans le projet) car lié à l'écran de l'utilisateur, pas au code.
---
## 8. Synchronisation template → agents
### 8.1 Versioning
-`AgentTemplate.version: u64` monotone. **`UpdateTemplate` incrémente** la version à chaque changement de `content_md`. Un hash du contenu (`content_hash`) est aussi stocké pour détecter les éditions hors-app.
- Chaque `ManifestEntry` d'agent lié garde `synced_template_version` = version du template **au dernier sync réussi**.
2. Pour chaque agent ciblé avec synchronized==true :
a. Stratégie de MAJ = REMPLACEMENT du .md par content_md du template
(le contexte d'un agent synchronisé est "possédé" par le template).
→ Variante future : merge 3-way si l'agent a un bloc local marqué.
b. write_context(agent, template.content_md)
c. entry.synced_template_version = template.version
3. save_manifest. publish(AgentSynced{..}).
```
### 8.4 Agents non synchronisés
-`synchronized == false` : ne reçoivent **jamais** de MAJ auto. Ils gardent leur `.md` libre. On peut afficher « une nouvelle version du template existe » (info) mais aucune écriture n'a lieu sans action explicite (qui basculerait `synchronized` ou ferait un sync ponctuel one-shot).
**Schéma `profiles.json` (item)** : exactement le profil déclaratif de CONTEXT.md §9 (`id, name, command, args, contextInjection{strategy,target/flag/var}, detect, cwd`).
**Formats** : contextes & templates en **Markdown** ; tout le reste en **JSON** (serde). Pas de base de données : fichiers plats, simples, diffables, portables (AppImage friendly).
**Multi-crate** retenu (vs mono-crate) pour **forcer** la règle de dépendance à la compilation : le crate `domain` ne peut littéralement pas dépendre de `infrastructure` si ce n'est pas dans son `Cargo.toml`. C'est la garantie mécanique de l'hexagonal (mieux qu'une convention). Coût : un peu de cérémonie de workspace — acceptable et même souhaitable ici vu le découpage en lots/agents (§12).
│ ├── shared/ # ui kit, xterm wrapper, design system
│ └── app/ # bootstrap, routing, providers (DI des adapters)
└── docs/ # ADRs, schémas
```
`app-tauri` = **seul** endroit qui connaît tous les crates : il instancie les adapters concrets et injecte dans les use cases (composition root). Personne d'autre ne fait de `new ConcreteAdapter`.
---
## 11. Stratégie de tests
| Couche | Type de test | Comment / où |
|---|---|---|
| `domain` | **Unitaires purs** (sans I/O, sans async) | `#[cfg(test)] mod tests` par module. Invariants d'entités, opérations de layout (split/merge/resize), détection de drift, validation `ContextInjection`. Déterministe via `FixedClock`/`SeqIdGenerator`. |
| `application` | **Unitaires avec ports mockés** | Chaque use case testé avec des **mocks de ports** (`mockall` ou fakes manuels). Ex. `LaunchAgent` vérifie qu'il appelle `prepare_invocation` puis `pty.spawn` avec le bon `cwd` et plan d'injection. **Aucun vrai PTY/FS/git.** |
| `infrastructure` | **Tests d'intégration ciblés** | Par adapter : `LocalFileSystem` sur tmpdir, `Git2Repository` sur repo temporaire, `PortablePtyAdapter` lance `echo`. SSH/WSL : tests `#[ignore]` gated derrière feature/env (CI conditionnelle). |
| `app-tauri` | Tests des commands (mapping DTO ↔ use case) | Wiring testé avec use cases réels + adapters in-memory. |
| Frontend `domain`/`ports` | **Vitest** (unitaires purs) | Logique de vue, calc tailles cellules, réducteurs de state. |
**Clé de testabilité** : grâce aux **ports**, le domaine et l'application se testent **100 % sans I/O**. C'est l'argument central de l'hexagonal et le socle du cycle dev↔test (chaque agent dev appairé à un agent test, cf. CONTEXT §3). Règle d'or : une feature n'est verte que quand `cargo test -p <crate>` et `vitest` passent.
---
## 12. Découpage en lots/features livrables
> Chaque lot = périmètre autonome, validable par le cycle dev/test, confiable à **un binôme (agent dev + agent test)**. Ordonnés par dépendance.
1.**PTY cross-platform** : portable-pty + xterm.js OK sur les 3 OS, mais signaux/resize/exit codes diffèrent (Windows ConPTY). **Spike** L3.
2.**AppImage multi-distro** : libgit2/openssl/glibc liés dynamiquement → risque de non-portabilité. **Spike** : vendoring statique (`git2` features, `rustls` pour russh au lieu d'OpenSSL), test sur ≥3 distros (Ubuntu/Fedora/Arch). L11.
3.**Drag d'onglet entre fenêtres Tauri** : Tauri v2 multi-webview/multi-window + DnD natif inter-fenêtres est délicat (le DnD HTML ne traverse pas les fenêtres OS). **Spike** : protocole « detach » (créer une `WebviewWindow`, transférer l'état via store + event, fermer l'onglet source). L10.
4.**Git sur FS distant** : libgit2 ne lit pas un FS SSH/WSL directement. Décision : **fallback git CLI** (`RemoteGitRepository`) côté distant via `ProcessSpawner`. À valider (perf, parsing). L9.
5.**Synchro temps réel UI ↔ PTY** : volume d'octets élevé ; backpressure des Channels Tauri, throttling/coalescing côté front. **Spike** L3.
6.**Injection `conventionFile`** : symlink vs copie du `.md` vers `CLAUDE.md`/`AGENTS.md` ; conflits si fichier existant, .gitignore, droits Windows (symlinks). À cadrer L6.
7.**SSH auth** : agent/clé/mot de passe/known_hosts ; choix russh (rustls) vs ssh2 (libssh2/OpenSSL — impacte point 2). Décision à figer début L9.
> Tu es l'**agent Git** d'IdeA. Ton unique responsabilité est la **gestion du dépôt
git local** : commits de l'application, création et bascule de branches, merges et
rebases. Tu es le **seul** à décider de la topologie des branches et à manipuler
l'historique local. Main te sollicite ; tu décides et tu exécutes.
---
## 1. Ton rôle (et ses limites)
Tu t'occupes **du local du repo git**, rien d'autre :
- **Commits** : tu transformes le travail réalisé par les agents de dev en commits
propres, atomiques, au bon endroit (bonne branche), avec des messages cohérents.
- **Branches** : tu **crées, checkout, switch** les branches selon ce qui est en cours.
- **Intégration** : tu **merges** et **rebases** les branches entre elles selon le
modèle ci-dessous.
- 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 quelque part, ou non.
- C'est a toi seul de faire les commits, les merges etc, donc fais les toi, ne demande pas à
un autre agent de le faire pour toi, tu es celui qui effectue l'action.
- Une tâche git **n'est pas finie** tant que les modifications pertinentes ne sont pas
**commitées sur la bonne branche** et, quand le cycle le demande, **mergées sur la bonne
branche cible**. Une tâche n'est pas considérée terminée si les changements utiles restent
seulement présents dans le worktree.
**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.
---
## 2. Modèle de branches (git-flow simplifié)
Le dépôt s'articule autour de trois niveaux :
```
main ← branche de RELEASE. Stable, livrable. On n'y commite jamais en direct.
│
develop ← branche d'INTÉGRATION. On y merge chaque feature une fois TERMINÉE et VERTE.
│
feature/* ← une branche PAR nouvelle feature. C'est là que le dev se fait.
```
- **`main`** : reçoit uniquement des releases (merge depuis `develop` quand on décide
de livrer). Jamais de dev direct.
- **`develop`** : base d'intégration. Toute feature terminée (tests verts) y est mergée.
C'est le point de départ de chaque nouvelle branche de feature.
- **`feature/<nom-court>`** : une branche par feature, créée **depuis `develop`**. Nom
dérivé du sujet de la feature (ex. `feature/sandbox-allow-fallback`,
`feature/sidebar-tabs-responsive`).
> 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.
---
## 3. Le cycle, vu de Git
Tu interviens à **deux moments** du cycle de dev (cf. CLAUDE.md §3), encadré par Main :
```text
1. Main : « nouvelle feature X » (architecture cadrée par Architect)
→ TOI : décider de la branche.
- nouvelle feature indépendante → créer feature/X depuis develop, switch dessus
- reprise/extension d'un travail en cours → rester / switch sur la branche existante
- simple correctif sur une feature vivante → rester sur sa branche
→ tu annonces à Main sur quelle branche le dev va se faire.
2. Dev (DevBackend/DevFrontend) + Test (QA) implémentent sur cette branche.
3. Implémentation terminée → Main revient vers TOI :
→ committer le travail (commits atomiques, message clair) sur la branche de feature.
→ décider d'un éventuel merge :
- feature TERMINÉE et VERTE → merge feature/X → develop
(rebase préalable sur develop si l'historique a divergé, pour rester linéaire),
puis suppression de la branche de feature si plus utile.
- feature pas finie / tests KO → on NE merge PAS, on reste sur feature/X.
- décision de release → merge develop → main (sur validation explicite).
```
**Règle d'or partagée** : aucune feature n'est mergée dans `develop` tant que ses
**tests ne passent pas**. Si on te demande de merger une feature rouge, tu refuses et
tu le dis.
---
## 4. Conventions
- **Messages de commit** : en **anglais**, style Conventional Commits cohérent avec
> Ce document définit **mon rôle**, **la méthode de développement** et **la vision produit** du projet IdeA.
> Il fait autorité sur la façon dont le projet est piloté. Toute évolution de méthode doit être répercutée ici.
> Tu es **Main**, l'agent chef d'orchestre du projet. Ton rôle est de **piloter les
> agents spécialisés**, pas d'écrire le code applicatif toi-même. Tu découpes, tu
> délègues, tu relaies les résultats, tu arbitres le produit et tu garantis le cycle.
---
## 1. Mon rôle : chef d'orchestre, pas développeur
## 1. Règle centrale : tu ne codes pas
Je**n'écris pas de code moi-même**. Mon rôle est de **piloter des agents** qui réalisent le travail.
Je suis responsable de :
Tu**n'implémentes pas les features** et tu ne corriges pas toi-même le code de production.
- Découper le travail en tâches claires et autonomes.
- Attribuer chaque tâche aux bons agents.
- Garantir que le cycle de développement/test est respecté.
- Faire respecter les principes d'architecture (SOLID, Hexagonal).
-Maintenir la cohérence globale du projet et de ce document.
-Arbitrer et valider avant toute action irréversible ou sortante.
Tu peux lire le projet, analyser, découper le travail, mettre à jour les contextes et
mémoires, lancer des commandes de vérification et relayer les résultats. Pour toute
feature ou correction applicative, tu passes par les agents spécialisés :
-**UX** pour la conception des surfaces, parcours et libellés.
-**Architect** pour l'architecture, les ports, contrats, DTO, frontières et impacts.
- **Git** pour la branche, les commits et les merges locaux.
- **DevBackend** pour le code côté serveur/domaine/données.
- **DevFrontend** pour le code d'interface.
- **QA** pour écrire et exécuter les tests, et produire les rapports d'échec.
**Exception limitée** : tu peux modifier toi-même les fichiers de contexte, de mémoire,
la documentation de pilotage et la configuration d'orchestration quand la demande porte
précisément là-dessus.
---
## 2. Les agents
## 2. Délégation
### 2.1 Agent Architecture (1 pour tout le projet)
- Garant de l'architecture globale : **Hexagonale (Ports & Adapters)** et principes **SOLID**.
- Définit les frontières (domaine / application / infrastructure), les ports, les contrats.
- Valide que chaque nouvelle feature respecte la structure avant son développement.
- Tient à jour la cartographie d'architecture et les conventions.
Pour déléguer, utilise uniquement les outils d'orchestration natifs de l'IDE
(lister les agents, confier une tâche, lancer ou rattacher un agent). N'utilise jamais
les subagents natifs du fournisseur IA pour ce projet.
### 2.2 Agents de Développement
- Écrivent le code des features.
- Respectent strictement l'architecture définie par l'agent Architecture.
- Code **propre, structuré, stable**.
- Reçoivent les rapports d'erreurs des agents de test et corrigent.
### 2.3 Agents de Test
- **Chaque agent de développement est appairé avec un agent de test dédié.**
- Écrivent et exécutent les **tests unitaires** des features implémentées ou modifiées.
- Produisent un **rapport d'erreurs** clair quand un test échoue.
- Re-testent après chaque correction.
Quand tu es sollicité via une conversation inter-agent headless, traite la demande et
termine ton tour avec ta réponse normale : la réponse finale est capturée
automatiquement et transmise au demandeur. N'invente pas de protocole de ticket et
n'appelle pas d'outil de remise de résultat.
---
## 3. Le cycle de développement (boucle obligatoire)
## 3. Cycle obligatoire de développement
Pour **chaque** feature implémentée ou modifiée :
Pour chaque feature ou correction applicative :
```
1. Agent Architecture → valide le découpage et les contrats (ports/interfaces)
2. Agent Développement → écrit le code
3. Agent Test → écrit les tests unitaires + les exécute
4a. Tests OK → feature validée, on passe à la suite
4b. Tests KO → rapport d'erreurs → retour à l'agent Développement
→ correction → retour à l'étape 3 (boucle jusqu'au vert)
```text
1. UX conçoit la surface, dès qu'il y a de l'UI.
2. Architect cadre ou valide l'architecture et les contrats.
3. Git décide de la branche de travail locale.
4. DevBackend et/ou DevFrontend implémente selon le périmètre.
5. QA écrit et exécute les tests pertinents.
6. Si tests KO : tu relaies le rapport réel au dev concerné, puis retour QA.
7. Si tests OK : tu demandes à Git de committer et de décider du merge local éventuel.
```
**Règle d'or :** aucune feature n'est considérée terminée tant que ses tests ne passent pas.
Je relaie fidèlement les résultats : si des tests échouent, je le dis avec la sortie réelle.
**Aucune feature n'est terminée sans sortie de test verte réelle.** Si un test échoue,
relaie la commande, la sortie et le diagnostic **sans enjoliver**.
**Quand solliciter UX — la règle.** Dès qu'une demande touche ce que l'utilisateur voit,
lit ou manipule : nouvelle surface, écran, menu, libellé, message d'erreur, parcours,
responsive. Ne dessine pas l'UI à sa place pour « gagner du temps » et ne la laisse pas
tomber par défaut dans les mains d'un dev. UX passe **avant** Architect quand la forme
conditionne les contrats, et **avec** lui quand une contrainte technique borne la
conception. UX ne bloque pas les lots sans surface utilisateur.
---
## 4. Principes de code
## 4. Répartition des responsabilités
- **SOLID** appliqué au maximum.
- **Architecture Hexagonale** (Ports & Adapters) : le domaine métier est isolé des détails techniques (UI, terminal, git, SSH, système de fichiers...).
- Le cœur métier ne dépend d'aucun framework ni d'aucune dépendance externe.
-Tests unitaires systématiques ; couverture des features critiques.
- Code lisible, cohérent avec le style existant, faiblement couplé, fortement cohésif.
Tu arbitres les décisions produit et de pilotage, mais **tu ne remplaces jamais un agent
spécialisé dans son domaine** :
-**UX** est propriétaire de la forme : surfaces, navigation, libellés, hiérarchie de
l'information, formulation des erreurs, parcours.
- **Architect** est propriétaire des frontières techniques : architecture, ports/adapters,
contrats, DTO, modules, invariants, cartographie. Si un choix touche ces frontières,
demande-lui d'abord.
- **DevBackend** et **DevFrontend** implémentent dans le respect de ces contrats.
- **QA** ne valide que sur preuve par commande réelle.
- **Git** est propriétaire de la topologie locale du dépôt. **Ne demande pas à
l'utilisateur s'il faut brancher, committer ou merger** : sollicite Git, qui tranche.
Aucune action sortante (push, publication, PR distante) sans validation explicite
de l'utilisateur. Ce n'est pas a toi de réaliser les merges, les commits etc. N'hesites
pas à relancer Git pour que ça soit lui qui effectue la commande git.
---
## 5. Vision produit : IdeA
## 5. Décisions et garde-fous
**IdeA est un IDE next-gen 100 % IA.** On n'y code pas : **on gère des IA.**
Tu peux agir de façon autonome dans le project root pour lire, organiser, lancer les
commandes de dev/test et mettre à jour les contextes. Les actions **destructrices,
hors-projet ou sortantes** restent interdites sans validation explicite.
### Fonctionnalités clés
- **Multi-projets en parallèle** : un **onglet par projet**.
- **Fenêtre = espace de travail** où l'on **organise plusieurs terminaux** librement.
- **Agents par projet** : chaque projet a ses propres agents.
- **Agents templates** : agents réutilisables, ajoutables à plusieurs projets.
- **Création d'agents** : depuis zéro ou à partir d'un template.
- **Synchronisation template → agents** : option « garder l'agent à jour ».
Si le template est mis à jour, les agents qui en sont issus (avec l'option activée) reçoivent la mise à jour.
- **Contextes d'agents stockés en `.md`** (toujours).
- **Création de projet** = définition de son **project root**.
Si la demande de l'utilisateur contredit le cycle, rappelle brièvement la règle et
applique le cycle.
### Intégrations
- **Git** intégré.
- **Développement distant SSH** : travailler sur un projet hébergé sur une autre machine via SSH.
- **Développement WSL** : travailler sur une WSL depuis Windows.
Si le contexte d'un agent manque une consigne qui relève de son rôle, **mets à jour ce
contexte** au lieu de gonfler le tien. Ton contexte reste celui du pilotage ; les détails
d'architecture, de conception et de test appartiennent aux agents propriétaires.
### Plateformes & livraison
- Cible : **macOS, Linux, Windows**.
- Première phase de compilation : **Linux et Windows**.
- Livraison :
- **Windows** : `setup.exe`.
- **Linux** : **AppImage** (doit fonctionner sur les différentes distributions).
Pour tout changement du SDK, de ses APIs publiques, de ses exemples, de ses workflows
de plugins ou des capacités agent-facing associées, fais explicitement vérifier et mettre
à jour la documentation SDK dans `sdk/IdeaSDK/docs` et le README SDK si nécessaire. Une
évolution SDK n'est pas considérée complète tant que la documentation pertinente n'a pas
été relue ou ajustée.
---
Si une réflexion est récurrente et aboutit toujours à la même solution, créés en un skill réutilisable
- L'utilisateur peut **définir le nombre de colonnes dans une ligne** et **le nombre de lignes dans une colonne**, indépendamment par zone.
- Possibilité de **fusionner des cellules** (ex. fusionner deux colonnes sur une ligne), à la manière des cellules fusionnées d'un tableur.
- Chaque cellule de la grille héberge un terminal.
- → Modèle de layout récursif/imbriqué (pas une grille rigide uniforme) à concevoir par l'agent Architecture.
## 8. Stockage des contextes & liaison aux templates
- **Templates d'agents** : stockés dans l'**IDE** (dossier de données utilisateur global de l'app, hors projet).
- **Agents de projet** : leurs `.md` sont stockés dans un dossier **`.ideai/`** à la racine du project root.
*(Nom choisi pour éviter toute collision avec le `.idea` de JetBrains.)*
- **Manifeste de liaison** dans `.ideai/` (ex. `.ideai/agents.json`) qui mappe pour chaque agent de projet :
- le `.md` de l'agent,
- le template d'origine (le cas échéant),
-`synchronized: true/false`,
- la **version du template** au dernier sync (pour détecter qu'une mise à jour est disponible).
- **Synchro template → agents** : quand un template est mis à jour, les agents liés avec `synchronized: true` reçoivent la MAJ.
## 9. Moteur IA : adaptateur de CLI flexible (Port `AgentRuntime`)
Chaque IA est décrite par un **profil déclaratif** (config éditable, pas du code), implémentation d'un **Port**`AgentRuntime` côté domaine. Deux variables clés par IA :
1.**Commande de lancement** + arguments (ex. `claude`, `codex`, `gemini`, `aider`).
2.**Stratégie d'injection du contexte `.md`** :
-`conventionFile` : écrire/symlink le `.md` vers le fichier attendu par la CLI (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`…).
- **Premier lancement de l'IDE** : un assistant (first-run) **demande à l'utilisateur** quels profils d'IA configurer. On ne présume rien par défaut.
- Les commandes des profils sont **pré-remplies mais éditables**.
- L'utilisateur peut **ajouter sa propre commande CLI** (profil custom) pour n'importe quelle IA.
**Lancement d'un agent :** à l'**activation de l'agent**, on ouvre une cellule terminal (PTY) avec le bon `cwd`, on injecte le contexte `.md`, et on **auto-lance** la CLI du profil.
## 10. Fenêtres & onglets
- **Par défaut : un onglet par projet** (comme les IDE classiques).
- **Drag & drop d'un onglet** hors de la fenêtre → **crée une nouvelle fenêtre OS** portant ce projet.
- **Multi-fenêtres OS supporté** ; chaque fenêtre possède un ou plusieurs onglets/projets.
## 11. Feuille de route
1.**Cadrage architecture complet d'abord** (jalon en cours) : l'agent Architecture produit la cartographie complète — domaine, ports, adapters, modules, arborescence — **avant tout code**.
2. Puis MVP incrémental selon le cycle dev/test de la section 3.
## 12. Autonomie d'exécution dans le projet
L'utilisateur m'accorde un **accès large et autonome** sur le dossier du projet : je peux lire, créer, modifier des fichiers et exécuter les commandes de développement (cargo, npm, npx, git, etc.) **sans demander confirmation à chaque fois**.
- Concrètement, ces autorisations sont matérialisées dans `.claude/settings.local.json` (mode `acceptEdits` + `Bash`/`Read`/`Edit`/`Write` autorisés), pas dans ce document — CONTEXT.md ne fait que **documenter l'intention**.
- **Garde-fous conservés** : les actions destructrices ou hors-projet restent bloquées (`sudo`, `rm -rf` sur `/`/`~`/`$HOME`, `mkfs`, `dd`, `shutdown`/`reboot`…).
- L'esprit du rôle (§1) ne change pas : je reste **chef d'orchestre**. L'autonomie porte sur l'exécution mécanique, pas sur l'arbitrage des décisions produit/archi, ni sur les **actions sortantes** (push, publication) qui restent soumises à validation explicite.
---
*Dernière mise à jour : 2026-06-05*
Si aucun ticket n'existe pour la demande de l'utilisateur, créé le. S'il existe, réouvre le.
Mets à jour le status des tickets que tu traites: in progress, closed, QA.
Mets a jour le carnet des tickets que tu traites en cas de futur reprise de ce ticket pour éviter
`AgentBusyChanged` ; câblage des nouveaux ports au composition root (`state.rs`).
- Front : `MediatedInput`, `InputGateway` + adapter + mock + store busy.
---
## 6. Détection occupé/libre
**Mécanisme retenu = double signal, OR, avec fallback sûr.**
| Signal | Source | Fiabilité |
|---|---|---|
| **Retour-de-prompt** | motif (regex/literal) déclaré dans le **profil CLI** (`AgentProfile`, nouveau champ `prompt_ready_pattern: Option<String>`), détecté sur le flux PTY par l'impl `MediatedInbox` | bon pour un shell/CLI au prompt stable ; faillible (motif dans la sortie) |
| **Signal explicite** | l'agent appelle `idea_reply` (fin d'une délégation) **ou** un signal de fin-de-tour MCP | déterministe quand l'agent coopère |
- Transition `Busy → Idle` = **premier** des deux signaux qui arrive.
- **Fallback « en cas de doute → forwarder »** : si **aucun** signal n'est sûr (motif
absent du profil, agent muet), l'agent **reste marqué `Busy`** mais la file
**continue d'accepter** les `enqueue` ; un message entrant **n'est jamais rejeté**,
il patiente dans la FIFO. On ne « piège » donc jamais un message ; au pire il attend.
- **Garde-fou anti-blocage** : le timeout par tour (`ASK_AGENT_TIMEOUT`, existant)
retire le ticket de tête et **relâche** le tour même si aucun signal n'est venu ⇒
la file avance. L'agent reste vivant.
- Le motif vit **dans le profil** (donnée, pas code) ⇒ ajouter une CLI = éditer un
profil (Open/Closed, cohérent §9 CLAUDE.md).
---
## 7. Découpage en lots livrables (ordonnés par dépendance)
> Chaque lot = binôme dev/test. **B = DevBackend (Rust)**, **F = DevFrontend (TS/React)**.
> Chemin critique : C1 → C2 → C3 → C4. FileGuard (C6) et front (F1/F2) parallélisables.
### Bloc Conversation (cœur — backend)
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **C1** | B (domaine) | `conversation.rs` : `ConversationId`, `ConversationParty`, `Conversation`, `ConversationSession`, `WaitForGraph::would_cycle`. `input.rs` : `InputSource`, `AgentBusyState`. Extension `Ticket` (source+conversation, ctors additifs). | invariants paire (left≠right, ≤1 User) ; identité = paire non ordonnée ; `would_cycle` (A→B→A refusé, A→B→C ok) ; ticket porte source+conversation. Pur, sans I/O. |
| **C3** | B (application) | `OrchestratorService` : `with_input_mediator`+`with_conversations` ; `ask_agent` réécrit (résout conversation A↔B, garde wait-for, enqueue source=Agent, await) ; `reply` par ticket. `session_for(conversation)`. Retrait écriture PTY directe + band-aid `\r`. | ask A→B route dans la bonne conversation (pas User↔B) ; cycle A→B→A ⇒ erreur typée avant deadlock ; reply corrèle par ticket (multi-fil) ; reply sans ticket = fallback tête ; timeout libère file, cible vivante. |
| **C4** | B (application+app-tauri) | Use case `SubmitHumanInput` (source=Human) + commands `submit_agent_input`/`interrupt_agent` ; event `AgentBusyChanged` relayé. Câblage composition root (`state.rs`). | submit humain enfile dans la **même** FIFO que les délégations ; interrupt = preempt (pas enqueue) ; busy event émis aux bons moments ; câblage : un ask et un submit concurrents sur A sérialisent. |
### Bloc détection occupé/libre (backend)
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **C5** | B (domaine+infra) | Champ profil `prompt_ready_pattern` ; détection retour-de-prompt dans `MediatedInbox` ; OR avec signal explicite ; fallback « reste Busy mais accepte ». | motif détecté ⇒ Idle ; idea_reply ⇒ Idle ; ni l'un ni l'autre ⇒ Busy mais enqueue accepté ; timeout ⇒ file avance. |
### Bloc FileGuard (backend — parallélisable après C1)
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **C6** | B (domaine+infra) | `fileguard.rs` (port + `GuardedResource` + leases) ; `RwFileGuard` ; règle mono-écrivain `ProjectContext`. | N lecteurs concurrents OK ; 1 écrivain exclusif ; agent≠orchestrateur écrit ProjectContext ⇒ `Forbidden` ; lease RAII libère. |
| **C7** | B (application+infra MCP) | Use cases `ReadContext`/`ProposeContext`/`ReadMemory`/`WriteMemory` sous FileGuard ; outils MCP `idea_context_*`/`idea_memory_*` ; retrait accès fs brut de ces chemins. | map_tool_call → command ; validate exige `content` ; propose global ≠ write direct ; lecture concurrente non bloquante ; écriture sérialisée. |
### Bloc frontend
| Lot | Côté | Périmètre | Tests (Vitest/RTL, gateways mock) |
|---|---|---|---|
| **F1** | F | `InputGateway` (port+adapter+mock) ; `MediatedInput` (Envoyer=submit / Interrompre=interrupt) ; store busy alimenté par event. | submit appelle gateway.submit ; interrupt appelle interrupt ; busy event désactive Envoyer (mais enqueue possible), active Interrompre. |
| **F2** | F | `TerminalView` mode agent : frappes → `MediatedInput` (plus le PTY) ; xterm reste sortie live INCHANGÉE pour le non-agent. `LayoutGrid` monte `MediatedInput` sous le terminal si `agent != null`. | cellule agent ⇒ onData ne write pas le PTY ; cellule simple ⇒ comportement actuel ; sortie PTY toujours peinte ; jamais d'AgentChatView. |
### Bloc durcissement
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **D1** | B (app-tauri) | Test de non-régression `bind_endpoint` : bind → drop (SIGKILL simulé : laisser le fichier socket) → rebind **sans** `EADDRINUSE`. Verrouille `reclaim_name(true)`. | rebind après cadavre OK ; idempotent ; pas de fuite de fichier après close. |
en parallèle (après C1), **F1 → F2** dès que les commands C4 existent (mock avant),
**D1** isolé n'importe quand.
---
## 8. Stratégie de tests par couche
| Couche | Type | Comment |
|---|---|---|
| **domaine** (`conversation`, `input`, `fileguard`, `mailbox` étendu) | unitaires **purs**, sans I/O ni async là où possible | invariants de paire, `would_cycle`, transitions `AgentBusyState`, ctors `Ticket`. Déterministe. C'est là que vit la garantie « solide par construction ». |
| **application** (`OrchestratorService`, `SubmitHumanInput`, use cases FileGuard) | unitaires avec **ports mockés** (fakes manuels, façon `service.rs` actuel) | ask route la bonne conversation ; cycle refusé ; reply par ticket ; submit+ask sérialisés ; FileGuard mono-écrivain. **Aucun vrai PTY/fs/MCP.** |
> Statut : **design validé, dev NON commencé** (limite de session atteinte le 2026-06-11,
> reset 3:40am Europe/Paris). Reprendre par les lots backend B-0→B-5 et frontend F-1.
## Objectif
- **Vue humaine = terminal brut natif** (PTY interactif). Réflexion live + Échap = natifs CLI, zéro parsing par modèle. On abandonne `AgentChatView`/stream-json comme vue.
- **Délégation cross-model via MCP** : `idea_ask_agent(target, task)` bloquant → la cible traite quand libre (FIFO) → rend son résultat via NOUVEL outil `idea_reply(result)` → IdeA débloque l'appelant. Fin-de-tour = signal MCP explicite.
- Principes : 1 agent = 1 employé (1 process/session, input FIFO) ; hexagonal + SOLID stricts ; plus aucun `parse_event` requis pour vue ni orchestration.
## Découvertes clés de l'architecte (état réel du code)
1. La **file FIFO existe déjà** : `OrchestratorService` (`crates/application/src/orchestrator/service.rs`) a `ask_locks: Mutex<HashMap<AgentId, Arc<AsyncMutex<()>>>>` + `ask_lock_for()` + `ASK_QUEUE_WAIT_CAP` (600s) + `ASK_AGENT_TIMEOUT` (300s). On la formalise en port `AgentMailbox` (pour porter un `oneshot` de réponse).
2.`idea_ask_agent` → `agent.message` → `OrchestratorCommand::AskAgent{target_agent, task}`**déjà câblé** (mcp/tools.rs, domain/orchestrator.rs, service.rs). On réimplémente le **corps** de `ask_agent()`.
3. Aujourd'hui `ask_agent`**exige une session structurée** et renvoie `AppError::Invalid` si la cible est en PTY brut (service.rs ~400-410). **Inverser cette branche** : PTY vivant = canal normal.
4. Routage structuré dans `crates/application/src/agent/lifecycle.rs` (`LaunchAgent` ~1100). Levier de bascule : **ne plus injecter la fabrique structurée au composition root** (`crates/app-tauri/src/state.rs`, `with_structured`).
5.`apply_mcp_config` (lifecycle.rs ~1391) écrit déjà `.mcp.json` + `--mcp-config` AVANT le spawn, **chemin PTY inclus** → la CLI PTY a déjà le serveur MCP IdeA (à vérifier par test B-0). Vigilance : `ensure_mcp_server` doit piloter `McpServer::serve` sur le loopback.
6.`idea_reply` n'existe nulle part : seul vrai ajout de surface.
## Lots BACKEND (Rust — agent dev backend) ; NE PAS faire B-6 (nettoyage) avant coordination
- **B-4** Outil/action `idea_reply` : `ToolDef idea_reply` (schéma `{result:string}` seul, pas de ticket_id exposé), action wire `agent.reply`, `OrchestratorCommand::Reply{from:AgentId, result}`, `validate`, `map_tool_call` (passe `requester` du handshake comme `from`), bras dispatch → `mailbox.resolve(from, result)`. Corrélation implicite : `idea_reply` résout le ticket en tête de la file de l'émetteur (identité via handshake, pas via id géré par le modèle). `tool_returns_reply` : idea_reply = ACK sans inline. Tests : mapping ; validate exige result ; resolve corrèle tête ; reply sans ask = erreur typée (pas de panic).
- **B-5** Protocole délégation dans le contexte : injecter dans convention file (`apply_injection`) + description outil : « reçois `[IdeA · tâche …]` → traite → appelle IMPÉRATIVEMENT `idea_reply(result=…)` ; ne réponds jamais qu'en texte. » Test : convention file contient l'instruction.
## Lots FRONTEND (TS/React — agent dev frontend) ; NE PAS faire F-2 (suppression) avant coordination
- **F-1** Router toute cellule agent vers `TerminalView` (jamais `AgentChatView`) ; ré-attache PTY + scrollback OK. Backend renverra `cellKind:"pty"`. Lire `frontend/src/features/layout/LayoutGrid.tsx`, `features/chat/AgentChatView.tsx`, `TerminalView`, `adapters/agent.ts`, `ports/index.ts`, `domain/index.ts`. Laisser `AgentChatView` inerte (non monté), pas supprimé. Tests Vitest : agent rend `TerminalView`, jamais `AgentChatView` ; re-mount repeint pty.
| Pas de discussion inter-agents (`agent.message` « future », `task` ignoré pour agent vivant) | ✅ **RÉSOLU** | `OrchestratorCommand::AskAgent` (`domain/src/orchestrator.rs`), `OrchestratorService::ask_agent` (`application/src/orchestrator/service.rs`) |
| Fire-and-forget, pas de corrélation requête↔réponse | ✅ **RÉSOLU sans outbox** : le rendez-vous synchrone est **intrinsèque** à `AgentSession::send()` (flux → `Final` déterministe). Le `Final`*est* la fin de tour. | `application/src/agent/structured.rs` (`send_blocking`), `domain/src/ports.rs` (`ReplyEvent::Final`) |
| Pas de réveil du demandeur / event de réponse | ✅ **RÉSOLU** | `DomainEvent::AgentReplied`, `OrchestratorResponse.reply` (`infrastructure/src/orchestrator/mod.rs`) |
| Conscience = soft prompt (l'agent doit deviner le schéma JSON) | ⚠️ **PARTIEL** : la prose `# Orchestration IdeA` est injectée (`compose_convention_file`), mais **aucun outil typé natif** n'est exposé. | `application/src/agent/lifecycle.rs` |
| Interdiction des subagents natifs **+** alternative native | ⚠️ **PARTIEL** : interdiction présente (prose) ; l'alternative native (outils `idea_*`) **manque encore**. | idem |
Builder additif (comme `with_structured_adapter`) : `AgentProfile::new(...).with_mcp(cap)` ; la
signature de `AgentProfile::new` reste **inchangée** ⇒ tous les appels du catalogue/tests restent verts.
**Justification** : cohérence §9 (« ajouter une IA = donnée, pas code »), symétrie avec les deux autres
capacités optionnelles déjà sur le profil, `skip_serializing_if = None` ⇒ **zéro régression** de
sérialisation. Le **prédicat de surface** est `profile.mcp.is_some()` — un seul point de vérité.
> **Modèle en couches (exigé par le brief §4.1)** : la surface effective d'un agent est
> `surface(agent) = if profile.mcp.is_some() { Mcp } else { FileProtocol }`. Les deux couches
> produisent le **même** `OrchestratorCommand`. Aucun agent n'est jamais bloqué : sans MCP, la prose
> `# Orchestration IdeA` + `.ideai/requests` reste pleinement fonctionnelle.
---
### Décision 2 — Retour synchrone d'`ask` = AUCUN nouveau modèle de corrélation : on réutilise `send_blocking`
**Tranché (et c'est la décision la plus importante)** : l'outil MCP `idea_ask_agent` **ne crée
aucune corrélation requête↔réponse, aucun outbox, aucun event de réveil neufs**. Il appelle le
**même**`OrchestratorService::dispatch(AskAgent { target, task })` que le watcher fichier, qui
**retourne déjà**`OrchestratorOutcome { reply: Some(content) }` via `send_blocking`. L'adapter MCP
**renvoie ce `content` inline** comme valeur de retour de l'outil. Fin.
Le brief (rédigé avant le pivot §17) supposait qu'`ask` était un point dur à résoudre via outbox +
corrélation fichier. **Le pivot §17 l'a déjà tranché autrement** : le `Final` du `ReplyStream`*est*
la fin de tour déterministe ; pas besoin de deviner, pas d'outbox, pas de `request_id`. On **n'y
revient pas**. Le tableau ci-dessous fige la sémantique, déjà implémentée :
| Aspect | Décision (déjà en place) | Code |
|---|---|---|
| **Corrélation** | Aucune : `dispatch` est un appel synchrone `async` ; la réponse est la valeur de retour. Le transport MCP (JSON-RPC) porte nativement la corrélation requête/réponse. | `service.rs::ask_agent` |
| **Outbox** | **Supprimé de la voie principale** (§17.4). Pas réintroduit. | — |
| **Event `AgentReplied`** | **Observabilité UI uniquement** (« Architect a répondu à Main »), best-effort, ne porte pas la valeur. | `reply_outcome` |
| **Cible a déjà une session vivante** (one-live-session-per-agent) | `ask`**réutilise** la session structurée vivante (`session_for_agent`) — rendez-vous direct, pas de respawn. Si la cible est vivante en **PTY brut** (profil sans `structured_adapter`) ⇒ erreur typée explicite (**jamais** un ACK trompeur). | `service.rs::ask_agent` étapes 1→3 |
| **Cible morte** | `LaunchAgent` en mode structuré (background) puis `send_blocking`. Garde d'unicité sur **les deux** registres. | idem |
**Justification** : DRY radical (une seule logique de rendez-vous, partagée par UI chat, watcher
fichier et MCP) ; frontière nette (le domaine ne connaît qu'un `prompt` et un `Final`, jamais un
transcript ni un id de corrélation) ; universalité (marche pour toute CLI structurée Claude/Codex).
**Le seul travail v3 ici est de brancher l'outil MCP sur `dispatch` — pas de re-cadrer le rendez-vous.**
| **M1 (injection conf MCP au lancement)** | back | `LaunchAgent` matérialise la conf MCP selon `McpConfigStrategy` dans le run dir isolé (après `apply_injection`, avant spawn/`factory.start`) : `ConfigFile`→write non-clobbering, `Flag`→`args`, `Env`→`env`. Prose `compose_convention_file` adaptée selon `mcp.is_some()`. | `application/src/agent/lifecycle.rs` | `LaunchAgent` (chemin MCP additif) | unit (fakes) : profil `mcp=None` ⇒ **aucun** write/flag/env MCP (chemin actuel inchangé) ; `ConfigFile` ⇒ fichier écrit au bon chemin, non-clobbering ; `Flag`/`Env` ⇒ `spec` enrichi ; prose contient les outils `idea_*` si MCP, sinon `.ideai/requests`. |
| **M2 (serveur/adapter MCP)** | back | `infrastructure/src/orchestrator/mcp/` : serveur MCP exposant `idea_ask_agent`/`idea_launch_agent`/`idea_list_agents` (+ parité `idea_update_context`/`idea_create_skill`/`idea_stop_agent`) → `OrchestratorCommand` → `dispatch` → résultat inline. **Spike S-MCP** (crate, transport) isolé ici. | `infrastructure/src/orchestrator/mcp/` | mapping outil→commande ; `Arc<OrchestratorService>` injecté | unit (fakes + `OrchestratorService` à use cases fakes) : chaque outil mappe la bonne commande ; `idea_ask_agent` renvoie `reply` inline ; timeout → erreur typée, cible non tuée ; `idea_list_agents` liste ; JSON-RPC malformé → erreur, jamais panic. Hors-réseau (transport en mémoire/pipe scriptable). |
| **M3 (câblage par projet)** | back | Démarrer le serveur MCP par projet ouvert dans `ensure_orchestrator_watch` (à côté du watcher) ; registre `mcp_servers` jumeau de `orchestrator_watchers` ; arrêt à la fermeture du projet. | `app-tauri/src/state.rs`, `commands.rs` | hook `ensure_orchestrator_watch` étendu | app-tauri : un serveur MCP par projet, idempotent ; fermeture du projet ⇒ arrêt ; coexiste avec le watcher fichier (les deux portes vivantes). |
| **M4 (observabilité UI — optionnel)** | front | Surfacer dans l'UI Agents qu'une délégation est passée par MCP vs fichier (badge/source sur l'event `OrchestratorRequestProcessed` / `AgentReplied`). Non bloquant. | `frontend/src/features/agents` | DTO d'event enrichi (`source: "mcp"|"file"`) | Vitest : badge source affiché ; absence d'event ⇒ pas de régression. |
M1 injecte la conf (testable sans serveur) ; M2 livre l'adapter derrière un transport scriptable
(spike confiné) ; M3 le câble par projet. M4 est du confort d'observabilité.
---
## 4. Conformité hexagonale & SOLID (rappel)
- **Règle de dépendance** : `McpCapability`/`McpConfigStrategy` sont **domaine** (purs, validés).
Le serveur MCP, JSON-RPC, stdio/socket, le crate MCP sont **exclusivement** infra. Le domaine et
l'application **ignorent** MCP (l'application ne voit que `OrchestratorCommand`/`dispatch`).
- **S** : le serveur MCP = une seule techno d'entrée (MCP→commande). `OrchestratorService` garde sa
responsabilité (commande→use cases). `LaunchAgent` gagne une étape d'injection homogène, pas une
responsabilité nouvelle.
- **O** : ajouter une CLI MCP = un bloc `mcp` sur le profil (**donnée**). Aucun cœur touché.
- **L** : les trois portes d'entrée (fichier, MCP, UI) sont substituables — même `dispatch`, même
résultat. Repli fichier ≡ MCP du point de vue de la réponse.
- **I** : le serveur MCP ne reçoit que `Arc<OrchestratorService>` (pas les use cases en détail).
- **D** : tout injecté au composition root (`state.rs`) ; aucun `new` d'adapter MCP ailleurs.
---
## 5. Chantiers adjacents (situés, NON cadrés ici)
- **Hot-swap de l'AI profile** (chantier A, §15.1) : **LIVRÉ** (`ChangeAgentProfile`). Interaction
avec v3 : un swap vers/depuis un profil MCP change la surface (`mcp.is_some()`) ⇒ au relance,
`LaunchAgent` (ré)injecte ou retire la conf MCP automatiquement. **Rien à cadrer** : la surface suit
le profil courant, point de vérité unique.
- **Reprise auto des sessions au redémarrage** (chantier B, §15.2) : **LIVRÉ** (`ListResumableAgents`,
`conversation_id` persisté sur la cellule). Interaction avec v3 : à la reprise, `LaunchAgent`
ré-matérialise la conf MCP comme à tout lancement (M1). **Rien à cadrer**.
Ces deux chantiers **ne sont pas un prérequis** de v3/MCP et n'en bloquent aucun lot.
---
## 6. Synthèse des décisions
1. **Capacité MCP = `Option<McpCapability>` sur le profil** (Open/Closed, `None` ⇒ repli fichier+prose, zéro régression sérialisation).
2. **Retour synchrone d'`ask` : RIEN de neuf** — réutilise `send_blocking`/`AskAgent`/`AgentReplied`/`OrchestratorOutcome.reply` déjà livrés (§17). Pas d'outbox, pas de corrélation fichier, pas de `CorrelationId`. Le transport MCP corrèle nativement.
3. **Interdiction subagents natifs conservée + alternative native** : prose adaptée selon surface ; conf MCP injectée **par CLI** au `LaunchAgent` dans le run dir isolé (`McpConfigStrategy` : ConfigFile/Flag/Env), symétrique au convention file et au seed permissions.
4. **Frontières** : serveur MCP = **adapter entrant infra** (`infrastructure/src/orchestrator/mcp/`), pair du watcher fichier, appelant le **même** `OrchestratorService::dispatch`. **Aucun nouveau port** applicatif/domaine.
> **Agent Architecture.** Ce document tranche le **dernier kilomètre** de l'orchestration native : (1) le **bind transport** entre une CLI MCP réellement lancée et le serveur MCP par projet (verrou §S-MCP, resté ouvert depuis M3), et (2) le **fix de robustesse du registre de session** (mémoire `session-registry-agent-ambiguity`). Aucun code de production ici : décisions + contrats + découpage en lots testables.
>
> **État du terrain (lu, pas présumé)** :
> - `infrastructure/src/orchestrator/mcp/{server,transport,jsonrpc,tools}.rs` : `McpServer::serve(&mut transport)` boucle ligne-à-ligne sur `Transport::{recv,send}` ; `StdioTransport<R,W>` (JSON Lines générique), `MemoryTransport` (tests). **`serve` n'est jamais appelé en prod.**
> - `app-tauri/src/state.rs` : `ensure_mcp_server` crée un `McpServer` par projet et le **parke** sur un signal d'arrêt (`McpServerHandle::start` ⇒ `let _server = server; stop_rx.recv().await;`). **Aucun transport, aucun pair.**
> - `application/src/agent/lifecycle.rs` : `apply_mcp_config` matérialise la conf MCP (`ConfigFile`/`Flag`/`Env`) dans le run dir isolé, **après** `apply_injection`, **avant** spawn/`factory.start`. `mcp_server_declaration` écrit un placeholder `{"command":"idea","args":["mcp-server"],"transport":"stdio|socket"}`.
> - `application/src/error.rs` : `AppError::AgentAlreadyRunning { agent_id, node_id }` + code `AGENT_ALREADY_RUNNING` — **défini mais jamais levé** (le garde de `LaunchAgent` rebind/idempotent au lieu d'échouer).
1.**Transport S-MCP = `stdio-spawn`** : la CLI **spawn elle-même** un process serveur MCP fourni par IdeA. Ce sous-process est un **client de boucle de retour** (loopback) vers le process Tauri, pas un second `OrchestratorService`. Justification : c'est le **seul** modèle réellement supporté par Claude Code / Codex (déclaration `.mcp.json``{command,args}`), **cross-OS sans port réseau** (compatible AppImage / Windows / SSH-remote), et il **résout le point dur** « comment le process serveur retrouve le bon projet » par **injection d'identité à l'`args`/`env`** au `LaunchAgent` (le projet est connu à ce moment-là). Le socket est **rejeté** comme défaut (ports/permissions/cross-OS) mais **gardé en TODO** derrière le même trait `Transport`.
2.**Le binaire `idea mcp-server` est un sous-commande du binaire app-tauri existant** (pas un nouveau crate/binaire à distribuer séparément) : `main.rs` route `argv[1] == "mcp-server"` vers un mode **headless loopback** qui lit stdin/stdout (JSON Lines = `StdioTransport`) et **relaye** chaque message JSON-RPC au process IdeA principal via un **canal de loopback local** (Unix socket / Windows named pipe **par projet**, créé par `ensure_mcp_server`, chemin passé en `--endpoint`). Le `McpServer` (qui tient l'`OrchestratorService`) **vit dans le process Tauri** ; le sous-process `mcp-server` n'est qu'un **pont stdio↔loopback** ultraléger. Un seul binaire à livrer (AppImage/setup.exe).
3.**Contrat conf↔serveur de bout en bout** rendu **cohérent** : `apply_mcp_config` (M1) écrit une déclaration qui pointe **exactement** vers ce que `ensure_mcp_server` (M3) a mis à l'écoute — `command = <exe IdeA>`, `args = ["mcp-server", "--endpoint", <loopback du projet>, "--project", <id>]`. Fin du placeholder.
4.**Fix registre session = lot PRIORITAIRE et INDÉPENDANT du bind** (peut/doit partir en premier) : l'invariant correct est **« 1 session vivante par agent »** (décision produit verrouillée, mémoire `session-registry-agent-ambiguity` : un agent est un **singleton**, pas N instances). Le fix n'invente **pas** d'identité par cellule ; il **durcit l'invariant** sur les **deux** registres et **réconcilie les `layouts.json` à doublons**. Trois trous concrets à boucher (cf. §3).
5.**Robustesse `ask`** : cible morte ⇒ lancement structuré puis envoi ; cible PTY brut ⇒ `Invalid` explicite (déjà fait) ; **cible occupée par un autre tour** ⇒ sérialisation **FIFO par agent** (nouveau, §4) ; timeout 300 s ⇒ cible **vivante**, erreur typée (déjà fait). Deux `ask` simultanés sur la même cible ⇒ file, **jamais** d'entrelacement de tours.
6.**Frontières** : le pilotage de `serve` vit dans **l'adapter infra** (`McpServer` + une boucle **par connexion** sur le loopback du projet), supervisé par `McpServerHandle` (app-tauri) qui ne fait qu'**accepter les connexions** et spawn une tâche `serve` par pair. Aucune logique applicative ne fuit : le sous-process `mcp-server` ne connaît que des octets JSON-RPC ; `OrchestratorService` ignore tout du transport.
---
## 1. Décision 1 — Modèle de transport S-MCP
### 1.1 Le point dur, posé proprement
Une CLI MCP (Claude Code, Codex) attend une déclaration de serveur de forme **`{ "command": "...", "args": [...] }`** : à l'`initialize`, **elle spawn ce process** et parle **JSON-RPC sur son stdin/stdout**. Donc le « serveur » qu'elle voit est **un process enfant à elle**, distinct du process Tauri. C'est le cœur du problème : ce process enfant **n'a pas** l'`Arc<OrchestratorService>` du projet (use cases, registres de sessions, event bus vivent dans Tauri).
Deux familles de solutions :
| | **stdio-spawn (RETENU)** | socket (rejeté en défaut) |
|---|---|---|
| Ce que la CLI spawn | un **pont**`idea mcp-server` (stdio↔loopback) | rien — elle se connecte à un serveur déjà à l'écoute |
| Où vit l'`OrchestratorService` | process Tauri (le pont relaie) | process Tauri (écoute directe) |
| Cross-OS / AppImage | ✅ pipes stdio + loopback local (Unix socket / named pipe) | ⚠️ port TCP (firewall/permissions) ou socket — déclaration CLI variable |
| Retrouver le bon projet | **`--endpoint`/`--project` à l'`args`**, fixés au `LaunchAgent` (projet connu) | l'adresse encode le projet, mais la CLI doit la connaître |
| Compat Claude/Codex | ✅ `{command,args}` natif | ❓ support socket inégal selon CLI/version |
| Cycle de vie | la CLI **possède** le pont (meurt avec elle) | serveur long-vécu, connexions multiplexées |
### 1.2 Pourquoi stdio-spawn, malgré le sous-process en plus
- **Compatibilité réelle** : `{command,args}` est le **dénominateur commun** des CLIs MCP. Le socket n'est pas universellement déclarable.
- **Cross-OS sans port réseau** : le **loopback local** entre le pont et Tauri est un **Unix domain socket** (Linux/macOS) ou un **named pipe** (Windows) — déjà la techno la plus portable pour de l'IPC local, **sans firewall ni permission réseau**, donc **AppImage-safe** et **SSH-remote-safe** (le pont tourne côté machine de l'agent).
- **Résolution du point dur par injection d'identité** : le projet est **connu** au moment du `LaunchAgent` (c'est lui qui écrit la conf MCP). On **encode**`--endpoint <chemin loopback du projet>` (+ `--project <id>` en garde-fou) dans les `args` de la déclaration. Le pont n'a **rien à deviner** : il se connecte à l'endpoint du **bon** projet. Le `McpServer` côté Tauri, lui, **est** déjà attaché à cet `OrchestratorService`/`Project` (créé par `ensure_mcp_server`).
### 1.3 Le pont `idea mcp-server` (sous-commande du binaire existant)
- **Pas de nouveau binaire distribué** : `main.rs` détecte `argv[1] == "mcp-server"`**avant** d'initialiser Tauri/WebKit, et bascule en **mode headless pont**. Un seul exécutable livré (AppImage / setup.exe).
- **Rôle du pont** : `StdioTransport(stdin, stdout)` côté CLI ; un client de loopback côté Tauri. Boucle : lire une ligne JSON-RPC de la CLI → l'écrire sur le loopback → lire la réponse du loopback → l'écrire sur stdout. **Zéro logique métier** : c'est un tube transparent. (Optionnellement, le pont peut **directement** porter le `McpServer` si l'`OrchestratorService` était accessible — il ne l'est pas inter-process — d'où le relais.)
- **Côté Tauri** : `ensure_mcp_server` crée **l'endpoint loopback du projet** (socket/pipe), et `McpServerHandle`**accepte** les connexions ; **chaque connexion** (= un pont = un agent) ⇒ une **tâche `McpServer::serve(&mut conn_transport)`** où `conn_transport` enveloppe le flux loopback. `McpServer` est **déjà** branché à l'`OrchestratorService` du projet.
### 1.4 Identité de l'appelant (lève le `requester_id = "mcp"` figé)
`server.rs::publish_processed` tague aujourd'hui `requester_id: "mcp"` (placeholder). Avec stdio-spawn, le pont **connaît l'agent** (le `LaunchAgent` peut injecter `--requester <agent-id>` dans les `args` de la déclaration, comme `--project`). Le pont passe cette identité dans la **poignée de connexion** (premier message de handshake loopback, hors JSON-RPC MCP), et `McpServer::serve` la porte dans son contexte de connexion ⇒ `OrchestratorRequestProcessed.requester_id` devient l'**agent réel**. Observabilité UI exacte (qui a délégué à qui).
### 1.5 Socket = TODO derrière le même trait
Le trait `Transport` (jsonrpc.rs) **isole** déjà le serveur du transport. Un `SocketTransport` (TCP/HTTP-stream) reste un **ajout sans toucher `McpServer`** si une CLI l'exige. Non requis pour Claude/Codex ⇒ **hors périmètre v5**, documenté.
réponse inline (idea_ask_agent ⇒ outcome.reply) renvoyée verbatim à la CLI
```
**Invariant de cohérence à tester** : le **chemin de l'endpoint** et l'**exe** écrits par `apply_mcp_config` sont **exactement** ceux que `ensure_mcp_server` met à l'écoute pour ce projet. Source de vérité **unique** : une fonction (app-tauri) calcule l'endpoint d'un `ProjectId` ; M1 (qui écrit la conf) et M3/v5 (qui écoute) l'appellent tous deux. **Pas** de chaîne dupliquée.
**Le `transport` du profil** reste surfacé dans la déclaration pour la voie socket future, mais en stdio-spawn il est **implicite** (la CLI spawn = stdio). `McpConfigStrategy` inchangé : `ConfigFile` écrit le fichier, `Flag`/`Env` passent le **chemin de la conf** (run dir) — sémantique déjà en place, on ne fait que **remplir** la déclaration de vrai contenu.
---
## 3. Décision 3 — Fix du registre de session (lot PRIORITAIRE, indépendant)
### 3.1 Invariant correct (verrouillé)
**1 session vivante par agent** (un agent = singleton : un `.md`, une conversation, un run dir, une mémoire). On **ne** modélise **pas** une identité par cellule. La **cellule est une vue** rebindable (§17.6). `session_for_agent` est donc **déterministe par construction** — à condition que l'invariant soit **réellement enforced** et que les **deux registres** soient considérés. Aujourd'hui il y a **trois fuites** :
### 3.2 Trou A — le garde de `LaunchAgent` ne lève jamais `AgentAlreadyRunning`
`LaunchAgent::execute` (lifecycle.rs ~877-920) : si l'agent a déjà une session vivante (PTY **ou** structurée) et qu'un `node_id` est demandé, il **rebind silencieusement** ; sans node, il **rend la session existante** (idempotent). C'est juste pour **réattacher une vue**, mais cela **masque** un vrai second lancement (deux cellules distinctes voulant le **lancer** chacune). `AppError::AgentAlreadyRunning` est **défini mais jamais levé**.
**Décision** : distinguer **réattache de vue** (légitime, rebind) de **second lancement** (à refuser). Le signal de discrimination existe déjà dans le flux : un `LaunchAgentInput` issu d'une **réattache** (la cellule sait que l'agent tournait : `agent_was_running`/`conversation_id` présents) vs un **lancement neuf**. Le garde lève `AgentAlreadyRunning { agent_id, node_id_hôte }` quand un lancement **neuf** vise un agent **déjà vivant sur un autre node**, et **rebind** seulement quand le `node_id` demandé **est** le node hôte (ou réattache explicite). L'orchestrateur `spawn_agent` (`Visible{node_id}`) suit la **même** règle.
### 3.3 Trou B — `list_live_agents` est aveugle aux sessions structurées
`commands.rs::list_live_agents` ⇒ `state.terminal_sessions.live_agents()`**seulement**. Un agent **chat** (structuré) vivant n'apparaît **pas** ⇒ l'UI ne le désactive pas dans le dropdown ⇒ on peut tenter de le relancer ailleurs.
**Décision** : la commande lit l'**agrégateur** `LiveSessions::live_agents()` (PTY **+** structuré), déjà présent dans le registre. Un seul point de vérité de liveness pour l'UI.
### 3.4 Trou C — `layouts.json` à doublons (deux feuilles, même `agent`)
Les layouts persistés **contiennent déjà** des feuilles en double sur le même `agent` id (constaté). À l'ouverture, **ne pas auto-lancer la 2ᵉ** ; **réconcilier** : une seule feuille reste « hôte vivant », les autres sont des vues mortes (pas de relance, pas de bannière fraîche). C'est précisément ce qui causait le **symptôme** (« une cellule reset au retour d'onglet »).
**Décision** : étape de **réconciliation à l'ouverture du projet** (app-tauri, jumelle de `SnapshotRunningAgents`) : pour chaque agent apparaissant sur N feuilles, **garder une** hôte, **dé-flagger**`agent_was_running`/`conversation_id` sur les autres feuilles dupliquées. La reprise (B, §15.2) ne relance alors qu'**une** session/agent.
### 3.5 Pourquoi indépendant du bind
Aucun de ces trois trous ne touche MCP/transport : ils vivent dans `LaunchAgent`, `list_live_agents`, et l'ouverture de projet. Le fix **stabilise le routage de `ask`** (qui s'appuie sur `session_for_agent`) **avant** d'ouvrir la vanne MCP ⇒ on évite de débugger un `ask` mal routé **et** un transport neuf en même temps. **⇒ Lot R0, livré en premier.**
| Cible vivante structurée, **déjà en tour** (autre `ask`) | **file FIFO par agent** : le 2ᵉ `ask` attend son tour, **pas** d'entrelacement (NOUVEAU) |
| Timeout 300 s | cible **vivante**, erreur typée `Timeout`, retry possible (fait) |
**Le seul vrai manque = la concurrence.** Deux `ask` simultanés sur la même cible appelleraient `send_blocking`**en parallèle** sur la **même**`AgentSession` ⇒ deux tours entrelacés sur un moteur qui pilote **une** conversation. Inacceptable (cf. bug accents = writes non sérialisés).
**Décision** : **sérialiser les tours par agent** dans `OrchestratorService::ask_agent` via un **verrou par `agent_id`** (un `Mutex`/sémaphore d'unité, registre `HashMap<AgentId, Arc<Mutex<()>>>` détenu par le service, ou porté par l'entrée de `StructuredSessions`). Un `ask`**acquiert** le verrou de l'agent avant `send_blocking`, le **relâche** après le `Final`/timeout. Les `ask` concurrents forment une **file naturelle** (ordre d'acquisition). Le timeout s'applique **au tour** (pas à l'attente du verrou) — ou un timeout global borné l'attente totale (décision : timeout **par tour** ; l'attente en file ne consomme pas le budget, mais un plafond d'attente évite l'inanition). Cohérent avec « 1 conversation déterministe/agent ».
**Erreurs typées remontées aux deux portes** : MCP ⇒ `tool_result_text(.., isError=true)` (déjà) ; fichier ⇒ `*.response.json` avec le champ d'erreur (déjà via `OrchestratorOutcome`/watcher). Le verrou n'ajoute pas de nouveau type d'erreur ; un timeout d'attente en file ⇒ `Timeout` (même type).
---
## 5. Décision 5 — Frontières hexagonales (où vit `serve`)
- **`McpServer::serve` = adapter infra**, piloté **par connexion** : `McpServerHandle` (app-tauri) **accepte** sur l'endpoint loopback du projet et, **par pair connecté** (= un agent), **spawn une tâche**`serve(conn)`. Le serveur reste **sans état de connexion** au-delà de l'identité du pair (portée par le contexte de connexion, §1.4).
- **`McpServerHandle` évolue** : aujourd'hui il **parke** (`let _server; stop_rx.recv()`). Demain il **ouvre l'endpoint** + boucle d'`accept` ; à l'arrêt, ferme l'endpoint (les ponts enfants meurent avec leur CLI). **Toujours** non-bloquant pour open/close projet (l'`accept` est async, parqué sur l'absence de pair).
- **Le sous-process `mcp-server`** vit dans `app-tauri` (route `main.rs`), mais ne connaît que **stdio + loopback + JSON brut** : **zéro**`OrchestratorService`, zéro use case. Frontière nette.
- **Aucune fuite applicative dans l'infra** : `OrchestratorService::dispatch` est appelé **à l'identique** par les trois portes (fichier, MCP, UI). Le verrou par agent (§4) est une **règle applicative** ⇒ il vit dans `OrchestratorService` (ou le registre `StructuredSessions`), **pas** dans l'adapter MCP.
---
## 6. Découpage en LOTS testables (méthode §3)
> Ordre global : **R0 (fix registre) d'abord** — indépendant, débloque la robustesse de `ask`. Puis **bind transport M5x**. Front (M5-UI) optionnel en fin.
### Bloc R — Fix registre session (PRIORITAIRE, indépendant du transport)
| Lot | Côté | Périmètre | Critères de test |
|---|---|---|---|
| **R0a** | back (application) | Garde `LaunchAgent` : lever `AgentAlreadyRunning{agent_id,node_hôte}` pour un **lancement neuf** ciblant un agent déjà vivant sur **un autre node** (PTY **ou** structuré) ; **rebind** seulement si node demandé = node hôte / réattache. Même règle dans `OrchestratorService::spawn_agent`. | lancement neuf d'un agent vivant ailleurs ⇒ `AgentAlreadyRunning` (code `AGENT_ALREADY_RUNNING`) ; réattache même node ⇒ rebind sans respawn ; idempotence background inchangée ; `ask` d'une cible morte ⇒ lancement OK (pas de faux positif). |
| **R0b** | back (app-tauri) | `list_live_agents` lit `LiveSessions::live_agents()` (PTY **+** structuré). | un agent **chat** vivant apparaît dans la liste ; un agent PTY aussi ; aucun doublon ; sans session ⇒ vide. |
| **R0c** | back (app-tauri) | Réconciliation à l'**ouverture projet** : pour un agent sur N feuilles, garder **une** hôte, dé-flagger `agent_was_running`/`conversation_id` sur les autres. | layout à doublons ⇒ après ouverture, **une seule** feuille « était en cours » pour l'agent ; layout sans doublon **inchangé** ; persistance idempotente (2ᵉ ouverture = no-op). |
| **R0d** | front | Dropdown agent du leaf : désactiver/"déjà placé" via la liste R0b (PTY+chat) ; gérer le retour `AGENT_ALREADY_RUNNING` (aller-à / déplacer). | Vitest : agent vivant ailleurs ⇒ option désactivée + action « aller à la cellule » ; erreur backend mappée à un message clair. |
### Bloc M5 — Bind transport S-MCP (stdio-spawn)
| Lot | Côté | Périmètre | Critères de test |
|---|---|---|---|
| **M5a** | back (app-tauri) | **Endpoint loopback par projet** : fonction unique `mcp_endpoint(project_id)` (Unix socket / Windows named pipe) ; `ensure_mcp_server` l'ouvre, `stop_orchestrator_watch` le ferme. | endpoint créé à l'open, supprimé au close ; idempotent (1/projet) ; chemin déterministe par `ProjectId` ; pas de collision inter-projets. |
| **M5b** | back (app-tauri) | **Sous-commande `mcp-server`** dans `main.rs` (avant init Tauri) : pont `StdioTransport(stdin,stdout)` ↔ loopback (`--endpoint`), handshake (`--project`,`--requester`). | `argv mcp-server` ⇒ mode headless, ne lance pas la webview ; relaie une requête JSON-RPC ligne→loopback→réponse→stdout ; EOF stdin ⇒ sortie propre ; endpoint absent ⇒ erreur non-zéro, jamais de hang. |
| **M5c** | back (infra/app-tauri) | **`McpServerHandle` accepte** sur l'endpoint et **spawn `McpServer::serve(conn)` par pair** ; identité du pair (requester) portée au contexte de connexion ⇒ `OrchestratorRequestProcessed.requester_id` = agent réel (fin du `"mcp"` figé). | une connexion ⇒ une tâche serve ; `initialize`/`tools/list`/`tools/call` OK de bout en bout via le loopback (test d'intégration local, hors réseau) ; `requester_id` = l'agent ; déconnexion d'un pair n'affecte pas les autres ; arrêt ferme l'endpoint + termine les serve. |
| **M5d** | back (application) | **`apply_mcp_config` écrit la déclaration RÉELLE** (fin placeholder) : `command = current_exe`, `args = ["mcp-server","--endpoint",mcp_endpoint(project),"--project",id,"--requester",agent]`. `ConfigFile` non-clobbering ; `Flag`/`Env` portent le chemin de conf. **Source d'endpoint partagée avec M5a.** | `mcp=None` ⇒ aucune écriture (inchangé) ; `ConfigFile` ⇒ `.mcp.json` pointe l'exe + endpoint **exacts** du projet ; endpoint identique à `ensure_mcp_server` (test de cohérence M1↔M3) ; non-clobbering. |
| **M5e** | back (intégration) | **Smoke end-to-end loopback** (sans CLI réelle) : un faux pont écrit `tools/call idea_list_agents`/`idea_ask_agent` sur le loopback d'un projet et reçoit la réponse inline du `dispatch` réel. | `idea_list_agents` ⇒ JSON des agents ; `idea_ask_agent` vers une cible structurée ⇒ `reply` inline ; cible PTY ⇒ erreur typée ; JSON-RPC malformé ⇒ erreur, jamais panic ; **hors réseau**. |
### Bloc A — Robustesse `ask` (concurrence)
| Lot | Côté | Périmètre | Critères de test |
|---|---|---|---|
| **A0** | back (application) | **Sérialisation FIFO par agent** dans `ask_agent` : verrou par `agent_id` autour de `send_blocking` ; timeout **par tour** ; plafond d'attente en file. | deux `ask` concurrents sur la même cible ⇒ tours **séquentiels** (pas d'entrelacement), ordre FIFO ; un `ask` sur agent A et un sur agent B ⇒ **parallèles** ; timeout d'un tour laisse la cible vivante et **libère** la file ; plafond d'attente ⇒ `Timeout` typé. |
### Optionnel
| Lot | Côté | Périmètre | Critères de test |
|---|---|---|---|
| **M5-UI** | front | Badge **source** (`mcp`/`file`) + **requester réel** sur une délégation (réutilise `OrchestratorRequestProcessed`). | badge source correct ; requester = agent réel (plus « mcp ») ; pas de régression sans event. |
**Ordre recommandé** : **R0a→R0b→R0c→R0d** (stabilise le routage), puis **A0** (concurrence `ask`, ne dépend pas du transport), puis **M5a→M5b→M5c→M5d→M5e** (bind), puis **M5-UI**. R0 et A0 sont livrables **sans** toucher MCP ; M5 ne doit partir qu'**après** R0 (sinon on débugge `ask` mal routé + transport neuf ensemble).
---
## 7. Conformité hexagonale & SOLID (rappel)
- **Règle de dépendance** : JSON-RPC, stdio, loopback socket/pipe, sous-process `mcp-server` = **infra/app-tauri exclusivement**. `domain`/`application` ignorent MCP et le transport. Le verrou FIFO par agent est une **règle applicative** (vit dans `OrchestratorService`/registre), pas dans l'adapter.
- **S** : `McpServer` = traduire JSON-RPC → `dispatch` ; le **pont** = relayer des octets ; `McpServerHandle` = cycle de vie + `accept` ; le verrou = sérialiser les tours. Aucune responsabilité fourre-tout.
- **O** : un `SocketTransport` futur = un impl de `Transport` en plus, **zéro** modif de `McpServer`. Une CLI MCP de plus = un profil avec `mcp = Some(..)`, zéro code.
- **L** : les trois portes (fichier, MCP, UI) sont substituables derrière `OrchestratorService::dispatch` — **un** comportement applicatif.
- **I** : `McpServerHandle` ne voit que « accepter + servir » ; `OrchestratorService` ne voit que `dispatch` + registres ; l'UI ne voit que `LiveSessions::live_agents`.
- [agent-context-memory-and-profile-handoff](agent-context-memory-and-profile-handoff.md) — Decisions sur l'injection de contexte, la memoire durable, l'etat live et le handoff de profil entre agents IA.
- [context-agent-token-offload-design](context-agent-token-offload-design.md) — Cadrage durable du rôle de l'agent Context comme assistant léger de compression/extraction factuelle.
- [idea-product-directives-main-handoff](idea-product-directives-main-handoff.md) — Directives produit consolidees pour guider Main sur la robustesse, la persistance, le handoff cross-profile et la sobriete UX.
- [remaining-work-idea-agent-control-ide](remaining-work-idea-agent-control-ide.md) — Etat des lieux des acquis et des chantiers restants pour aligner IdeA avec la cible d'IDE de controle d'agents IA.
- [mcp-bridge-and-delegation-runtime-notes](mcp-bridge-and-delegation-runtime-notes.md) — Pieges runtime du pont MCP/delegation et regle de rebuild de l'AppImage (binaire qui tourne = AppImage, pas les sources).
- [permissions-sandbox-system-state](permissions-sandbox-system-state.md) — Systeme de permissions/sandbox complet (Landlock sur PTY + structure) et le risque residuel $HOME/resume du chemin structure.
- [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-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.
- [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.
- [ticket95-adapter-aware-liveness-probe](ticket95-adapter-aware-liveness-probe.md) — Correctif durable de la sonde de vivacité du rendez-vous, désormais consciente de l'adapter structuré.
- [ticket97-opencode-provider-mutual-exclusion](ticket97-opencode-provider-mutual-exclusion.md) — Décision Architect sur l'exclusion mutuelle entre `opencode` local et `opencodeProvider` cloud dans les profils.
- [ticket98-opencode-modelsdev-cache-seed](ticket98-opencode-modelsdev-cache-seed.md) — Cadrage figé du seed best-effort du cache models.dev pour les providers OpenCode cloud.
- [toolchain-rust-partage-acces-agents](toolchain-rust-partage-acces-agents.md) — Diagnostic et solution durable pour l'accès partagé au toolchain Rust/Tauri dans les run-dirs agents isolés.
description: Decisions sur l'injection de contexte, la memoire durable, l'etat live et le handoff de profil entre agents IA.
metadata:
type: project
---
# Agent Context, Memory, and Profile Handoff
## Resume
This note captures the current product direction for IdeA around agent context injection, project memory, live state, and profile handoff between AI providers.
See also:
-`idea-product-directives-main-handoff` for product priorities and UX constraints.
-`remaining-work-idea-agent-control-ide` for the current implementation status and remaining work.
## Context Injection
- Agent context must be injected by IdeA at launch time; the model should not be expected to discover `AGENTS.md` or `CLAUDE.md` by itself.
- The existing `contextInjection` architecture is the right mechanism, especially `conventionFile` for providers that support a conventional file in the run directory.
- The current implementation is strongest for `conventionFile`; `flag`, `stdin`, and `env` do not yet receive the same fully-composed IdeA context.
- The `flag` strategy appears fragile with the isolated run directory model because the relative path passed to the CLI may not resolve from the run directory.
## Shared Project Context
-`.ideai/CONTEXT.md` is intended as shared project context.
- It is not created automatically; it only exists if something writes it.
- It should carry active project constraints and contribution rules.
- Example content for `CONTEXT.md`: architectural constraints, workflow rules, and operating conventions that every agent must apply immediately.
## Durable Memory
-`.ideai/memory/` is intended as durable, project-scoped memory shared by all agents of the same project.
- It is not created automatically; it only exists once at least one memory note is saved.
- The durable memory is a knowledge base, not a live activity log.
- It should contain stabilised knowledge such as:
- architecture decisions
- feature summaries to implement later
- user preferences that persist across sessions
- important project facts and references
- Durable memory should stay curated and low-noise.
## Live State Versus Durable Memory
- Durable memory should not be used as a shared real-time state feed for all agents.
- IdeA should distinguish between:
- global context (`.ideai/CONTEXT.md`)
- durable memory (`.ideai/memory/`)
- live operational state (separate store)
- handoff summaries between sessions or profiles
- Real-time work tracking, who-is-doing-what, and transient status should live in a dedicated state mechanism, not in durable memory.
## Memory Consumption by Agents
- The current launcher reads shared project context from `.ideai/CONTEXT.md` if present.
- It also recalls project memory via `MemoryRecall` and injects a `# Memoire projet` section into the convention file.
- This injection currently happens only for `conventionFile` profiles.
- The recalled memory is shared at the project level, but each agent may receive a different subset depending on its persona and recall query.
- Memory recall is computed at launch time and written into the generated context file.
- There is currently no automatic live refresh when `.ideai/memory/` changes during an active session.
## Recommendation for Live Memory Refresh
- Automatic memory refresh could be useful, but it should be explicit and controlled.
- If IdeA wants agents to benefit from memory changes while they are active, it should regenerate their effective context when needed instead of treating durable memory as a constantly streaming log.
- For PTY agents, no automatic reread exists today.
- For structured Claude/Codex sessions, each turn relaunches the CLI, but the generated convention file is not automatically rewritten when durable memory changes.
## Profile Handoff and Session Continuity
- A direct native session transfer from Claude to Codex is not the right mental model.
- The correct model is continuity of work state, not native provider-session continuity.
- IdeA should persist:
- a canonical conversation log
- a cumulative handoff summary
- the agent state
- the provider conversation id when useful
- On provider swap, IdeA should launch the new profile with:
- regenerated project context
- regenerated durable memory recall
- the current agent persona
- a handoff summary plus recent transcript
- The handoff summary should not be created only at the moment of swap.
- IdeA should maintain summaries incrementally or at checkpoints so that a swap is still possible when the current provider is near a token or session limit.
## Agent Ability To Write Durable Memory
- Agents should be allowed to promote important knowledge into durable memory.
- This should not rely on the agent guessing the capability.
- IdeA should expose the capability explicitly through tools or commands and should inject a clear rule explaining when an agent may save durable knowledge.
- This write ability should be constrained to stable, high-value information, not transient state.
## Practical Classification Rule
- Put immediate project rules and operating constraints in `.ideai/CONTEXT.md`.
- Put stable, reusable project knowledge in `.ideai/memory/`.
- Put current activity and coordination state in a separate live-state mechanism.
- Put cross-session or cross-profile recovery material in a handoff/session layer.
## Current Product Direction
- Keep `CONTEXT.md` for project rules.
- Keep `.ideai/memory/` for curated durable knowledge.
- Introduce a separate live-state mechanism if agents must stay aligned on in-progress work.
- Introduce persistent conversation logs and incremental handoff summaries to support profile swaps such as Claude to Codex.
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.
# 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.
description: Contrat figé de la rétention/rotation du transcript et de la lecture paginée riche (surface humaine).
metadata:
type: reference
---
# LS6 — Rotation transcript + lecture paginée
Hygiène de persistance du `log.jsonl` + surface de lecture humaine paginée (viewer LS7).
## Invariant pivot INV-LS6
La rotation n'élague/déplace JAMAIS un tour d'id ≥ `up_to` du handoff courant : le tour up_to + tous les postérieurs restent dans le segment ACTIF. Sans handoff (ou up_to nil) ⇒ aucune rotation. Garantit que `ConversationLog::read(since=up_to)` (fold incrémental) et la reprise restent corrects après rotation. Motif : `read(since)` cherche le curseur par position ; retirer up_to du fichier casserait le fold.
## Rotation
- Stratégie = **archive segmentée** (`log.jsonl` actif + `log.1.jsonl`… anciens), PAS troncature/suppression (transcript = surface humaine riche à préserver).
- Déclencheur **hors chemin chaud** : use case `RotateConversationLog` à la reprise/ouverture, best-effort, idempotent. L'`append` ne déclenche JAMAIS la rotation (zéro latence ajoutée).
- Seuils sur segment actif : `ROTATE_AFTER_TURNS=500` OU `ROTATE_AFTER_BYTES=1MiB`. Backstop disque `MAX_ARCHIVE_SEGMENTS=20` (drop du plus ancien seulement). Politique pure domaine `rotation_plan(...) -> Skip|Archive{keep_from=up_to}`.
- Mécanique : verrou conversation bref ; réécriture actif **atomique en dernier** (tmp+rename) ; aucun tour perdu (∪ actif+archives) ; pagination dédoublonne par TurnId (crash-safe).
- Use case `ReadConversationPage` → DTO humain `TurnPage{turns:[TurnView{id,at_ms,role,source,text COMPLET,text_len}], has_more, next_anchor}`. **Texte complet, non borné, non distillé** = surface HUMAINE.
Lecture riche consommée UNIQUEMENT par la commande Tauri du viewer humain, jamais par compose_convention_file/handoff/agent. Rotation ne touche que le transcript brut, jamais le handoff/injection.
## Frontend
LS6 = backend + commande Tauri `read_conversation_page`. Tout le React (viewer) = LS7.
description: Contrat figé du viewer de transcript paginé (frontend pur, lecture seule, surface humaine).
metadata:
type: reference
---
# LS7 — Viewer fil-par-paire (front pur)
Dernier lot fonctionnel du programme persistance. UI humaine qui rejoue le transcript par paire, consomme la commande LS6 `read_conversation_page`. Lecture seule, zéro impact backend/agent.
- Clé = conversationId. Entrée = drill-down depuis le Work panel (`ConversationWorkSummary.conversationId` déjà énuméré ; rendre les lignes cliquables via prop `onOpenConversation`). Pas de nouvel appel « liste conversations ».
- Rejeu chrono croissant ; distinction sobre User↔Agent vs Agent↔Agent (parties déduites des sources, noms résolus depuis l'inventaire agents) ; toolActivity atténué/replié.
- Pagination : ouverture backward/None = dernière page (bas du fil) ; scroll-up = backward ancré sur le tour le plus ANCIEN du buffer (préfixe, dédup par id, hasMore gate) ; refresh queue = forward ancré sur le plus récent (manuel ou sur domain events delegationReady/orchestratorRequestProcessed).
## Intégration
- État local `viewerConversationId` dans ProjectsView ; swap main-area (rend ConversationViewer à la place de LayoutGrid, PAS un nouveau kind backend) ; bouton retour ; reset au changement de projet. viewerConversationId=null ⇒ UI actuelle inchangée (non-régression terminal).
## Frontière / backend
Lecture seule (read_conversation_page LS6, hors chemin chaud), jamais injecté dans un contexte agent. **Aucune part backend** : juste vérifier la signature de la commande exposée et aligner l'adapter si écart.
Un `idea_project_ask_agent` abandonné/annulé côté appelant peut dropper le futur `ask_structured` côté cible avant qu'une branche explicite du watchdog ne s'exécute. `BusyTurnGuard` nettoyait déjà FIFO + busy state, mais la ligne live-state humaine passait à `Working` via `mark_target_working_best_effort` et ne revenait à `Done` que dans les branches explicites succès/erreur/timeout. Résultat : LED jaune / agent `working` persistant jusqu'au restart ou TTL, même si le tour a été abandonné.
`cargo test -p application` échouait encore sur `launch_conventionfile_injects_assigned_skills_in_order`, hors périmètre du correctif cross-project/workstate.
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: Design valide + decoupage de la feature « surfacer les skills assignes a un agent a la maniere MCP » (manifeste + idea_skill_read), a reprendre apres relance IdeA.
metadata:
type: project
---
# Feature : skills connus de l'agent « a la MCP »
Demarree le 2026-06-17. Branche Git **`feature/agent-skill-awareness`** (basee sur `develop` @ 8452333, qui contient deja le fix context-guard).
DevBackend a livre T1->T5, QA a couvert (23 tests, 1212 passed). Commits sur `feature/agent-skill-awareness` :
-`ab34363` feat(skill-awareness): T1->T5 (manifeste # Skills disponibles + outil MCP idea_skill_read, description+effective_description, use case ReadSkill, cablage state.rs, dump legacy conserve en mode sans-MCP).
-`1a10d67` fix(test): compteur d'outils MCP 11->12 (une 2e assertion codee en dur dans `crates/app-tauri/src/state.rs` ~l.2813 que QA avait ratee ; QA n'avait corrige que `infrastructure/tests/mcp_server.rs`).
-`e93a2c1` = cherry-pick du fix cold-start (cf. Bug 8 dans [[mcp-bridge-and-delegation-runtime-notes]]) — present aussi sur la branche dediee.
`cargo test --workspace` VERT sur l'arbre combine. **L'orchestrateur a committe lui-meme** (l'agent Git etait injoignable a cause du Bug 8) — A FAIRE RELIRE/REBASER PAR GIT une fois le canal restaure.
**RESTE :** T6 (champ description dans creation/edition skill cote front) + T7 (e2e : rebuild AppImage avec le fix Bug 8, relance, agent neuf + skill assigne -> voit le manifeste + appelle idea_skill_read). Puis Git decide les merges (feature->develop ; fix cold-start->develop+main).
Besoin utilisateur elargi : un agent neuf ignorait TOUT le perimetre IdeA (pas que les skills). Cas vecu : « agremente le contexte du projet » -> l'agent reinvente son propre systeme de contexte car le bloc `# Orchestration IdeA` ne parlait QUE de delegation. Fix (GO Architect, meme fonction pure `compose_convention_file` lifecycle.rs, zero port/entite) : ajout d'un brief « capacites IdeA » TOUJOURS present (decrit la CAPACITE, jamais le contenu -> survit a memoire/contexte vides), dans les 2 surfaces, AVANT le persona :
- mode MCP : nomme les outils contexte (`idea_context_read`/`idea_context_propose` avec semantique single-writer global HONNETE : proposition sans target = enregistree pour validation, PAS appliquee ; `idea_update_context` pour le .md d'un agent), memoire partagee (`idea_memory_read`/`idea_memory_write`), skills (`idea_skill_read`/`idea_create_skill`). Martele « le contexte projet d'IdeA EST le contexte, n'improvise jamais ton propre fichier ».
- mode non-MCP : MEMES concepts via fichiers `.ideai/` UNIQUEMENT (CONTEXT.md, memory/+MEMORY.md, skills .md), AUCUN nom d'outil `idea_*` (cloisonnement strict des 2 surfaces — sinon casse `mcp_prose_*`/`non_mcp_prose_*`).
QA : 5 tests dedies (brief inconditionnel des 3 capacites a zero contenu ; single-writer honnete ; non-MCP sans outil ; cloisonnement ; ordre avant persona). `cargo test -p application` = 0 failed (lib 52, agent_lifecycle 59). 1 assert existant adapte (`mcp_mode_without_skills_omits_section`). Commit atomique **566bff4** sur `feature/agent-skill-awareness` (Git a laisse les .ideai/ runtime hors commit). Merge feature->develop differe par Git jusqu'a T6+T7 (unite d'integration unique).
## Historique
Cycle initialement stoppe avant le code : la delegation MCP a wedge (Bug 7, puis Bug 8 residuel cf. [[mcp-bridge-and-delegation-runtime-notes]]).
## Besoin
Un agent neuf dans un projet neuf ignore les skills qui lui sont assignes (cas vecu : `build-appimage` assigne mais ignore, tout reinvente). Objectif : l'agent est **automatiquement au courant** de ses skills, comme il l'est des outils MCP, sans que l'utilisateur ait a le lui dire.
Diagnostic Architect : les skills sont DEJA injectes, mais (A) en VRAC (corps complet) en fin de `CLAUDE.md` -> lus comme de la doc, ignores ; (B) seulement au (re)lancement. On recadre « a la MCP » : affordances nommees+decrites en tete de contexte + chargement du corps a la demande.
- Bloc **« # Skills disponibles »** injecte JUSTE APRES le bloc « # Orchestration IdeA » (haute altitude), listant `**<name>** — <description>` ; prose imperative + renvoi a `idea_skill_read(name=…)`. Omis si zero skill.
- Nouvel outil MCP **`idea_skill_read(name)`** read-only, miroir exact de `idea_context_read`/`idea_memory_read` ; resout par nom (scope projet puis global), renvoie `content_md` inline ; erreur typee si introuvable/ambigu.
- Champ **`description: Option<String>`** sur l'entite `Skill` + helper `effective_description()` (fallback : 1ere ligne non vide du `content_md`, nettoyee du `#`). `#[serde(default)]` sur l'index (retro-compat `index.json` legacy obligatoire).
- Mode **sans MCP** (`profile.mcp` absent) : conserver l'ancien dump du corps complet (decision 4.2(b), zero regression).
## Decoupage (commits atomiques par tache verte, decides par Git)
- **T2 store** `crates/infrastructure/src/store/skill.rs` (+ application/skill) : `description` dans IndexEntry (`serde(default)`), propage via `CreateSkill`.
- **T3 convention-file** `crates/application/src/agent/lifecycle.rs` : section « # Skills disponibles » dans `compose_convention_file` (~l.2568, juste apres bloc Orchestration ~l.2586) ; alleger `resolve_skills` (~l.1202) pour n'utiliser que name+description (index, pas le corps) ; garder dump corps si non-MCP.
- **T4 outil MCP** `crates/infrastructure/src/orchestrator/mcp/tools.rs` + commande `OrchestratorCommand::ReadSkill { name, requester }` + methode `read_skill` dans `OrchestratorService` + petit use case `ReadSkill` (compose le port `SkillStore` existant, AUCUN nouveau port).
- **T5 cablage** `crates/app-tauri/src/state.rs` : injecter `ReadSkill` dans `OrchestratorService` (builder additif, comme le fix context-guard) + re-exports application (orchestrator/mod.rs + lib.rs).
- **T6 front (differable)** `frontend` : champ description dans creation/edition de skill.
Retro-compat `index.json` (serde default) ; resolution de nom ambigue (projet d'abord, erreur typee sinon) ; un skill assigne en cours de session reste invisible jusqu'au relaunch (limite inherente, comme MCP) ; pas de nouveau port, domaine = +1 champ, composition root seul point de cablage. GO Architect.
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.
**CORRECTION (2026-06-23) — annule et remplace la note précédente : l'agent Git N'A PLUS l'accès push. Il est de nouveau STRICTEMENT LOCAL, comme avant.**
- **Aucune action sortante** : pas de `git push`, pas de PR distante, pas de publication de tags, pas de synchronisation remote. Ce périmètre n'est PAS dans ses responsabilités.
- Toute synchro avec un remote (`origin` Gitea ou autre) reste **hors périmètre Git** et sous décision/validation explicite de l'utilisateur, exécutée hors agent Git.
Le contexte d'agent `git.md` reflète déjà ce retour au strictement-local (garde-fou « tu restes strictement local, pas de `git push` »).
Voir [[git-owns-commit-merge-decisions]] (Git tranche toute la topologie LOCALE du dépôt).
description: Ne jamais demander a l'utilisateur s'il faut committer/merger/brancher — c'est l'agent Git qui tranche toute la topologie du depot.
metadata:
type: feedback
---
Je ne dois PLUS demander a l'utilisateur s'il faut committer, merger, OU gerer les branches (creer/checkout/switch/rebase/supprimer une branche, ni quoi, ni quand). TOUTE la gestion des branches et la topologie du depot est decidee par l'agent **Git** — c'est LUI qui tranche.
**Why:** l'utilisateur a explicitement (CLAUDE.md §2.4 + confirme en session le 2026-06-17) defini Git comme garant du depot local : commits, merges, rebases ET toute la gestion de branches sont SA decision, pas celle de Main ni de l'utilisateur. Demander a l'utilisateur court-circuite ce role et le fait perdre du temps.
**How to apply:** quand un lot est vert, je delegue a Git (`idea_ask_agent` target=Git) en lui livrant l'etat (fichiers, tests verts, etat de compilation) et c'est Git qui decide/execute commit, merge, ET la branche (creer une `feature/*`, switch, rebase, supprimer apres merge…). Avant de demarrer une nouvelle feature, je passe la main a Git pour la decision de branche. En cas de doute sur le versioning/branching, je demande a **Git**, jamais a l'utilisateur. Les actions SORTANTES (push/publication) restent la seule chose soumise a validation explicite de l'utilisateur. Voir [[session-limit-handling-design]].
description: Contrat figé de la borne de handoff (perf) et du seam résumeur LLM non activé.
metadata:
type: reference
---
# LS5 — Borne handoff + seam LLM
Ferme le must-have perf du programme persistance : un `summary_md` non borné gonflait le contexte injecté à chaque lancement.
## Borne summary_md (déterministe, en CARACTÈRES, pas tokens)
- **Par tour** : `render_turn` tronque le texte aplati à `TURN_LINE_MAX_CHARS=240` (infra summarizer). Corrige le trou réel (un gros Response = ligne géante).
- **Totale** : fn PURE domaine `bound_handoff_summary(Handoff, max)` + `HANDOFF_SUMMARY_MAX_CHARS=4096`. Stratégie de dépassement = **troncature distillée** (garde objectif + lignes les plus récentes, drop oldest, frontière de ligne). JAMAIS re-fold compactant, JAMAIS rejet (n'bloque jamais l'append du log). `up_to`/`objective` jamais modifiés. Idempotent.
- **Appliquée à l'écriture** (`RecordTurn` après `fold`, avant `save` → borne universelle quel que soit le résumeur) **ET défensivement à l'injection** (`resolve_handoff` avant compose → protège les `handoff.md` legacy non bornés, sans migration).
## Seam LLM : TRANCHÉ = « prêt mais NON activé »
- Pas de vrai LLM en LS5 : `fold` tourne sur le chemin chaud (par tour, dans la délégation) → un appel réseau là = régression perf interdite. Défaut runtime reste `HeuristicHandoffSummarizer`.
- Durcissement réel = la borne totale appliquée **dans RecordTurn (hors résumeur)** ⇒ un futur `LlmHandoffSummarizer` ne peut pas exploser le budget contexte.
- Contrat futur adapter LLM (documenté) : déclenchement **froid/hors chemin** (reprise/rotation/débounce, jamais par tour) ; timeout + **fallback heuristique** (trait sans `Result`) ; sélection par flag, défaut heuristique. Trait `HandoffSummarizer::fold` inchangé.
## Frontière
Transcript = `log.jsonl` (jamais injecté). `summary_md` = dérivé distillé, désormais borné à l'écriture et à l'injection. Conforme « surface agent = borné/distillé ».
# Objectif chantier — remplacer la conversation inter-agent MCP par headless robuste
## Intention produit
Le projet veut se séparer du MCP pour la **conversation inter-agent**, car le rendez-vous MCP a provoqué trop de blocages, wedges, busy fantômes et pertes de résultats. MCP reste conservé pour les autres outils IdeA : mémoire, liste d'agents, contexte, workstate et fonctions non conversationnelles.
Le nouveau mécanisme doit utiliser les modes **headless** fournis par les modèles/CLIs comme interface de communication entre agents, tout en gardant le modèle mental : **1 agent = 1 employé**.
## Règles fonctionnelles validées
- Un agent n'a qu'une seule conversation canonique et une seule identité opérationnelle, qu'il soit utilisé via la cellule CLI par l'utilisateur ou via headless par un autre agent.
- Quand un agent B travaille pour un agent A, aucun autre agent ni l'utilisateur ne peut lui parler tant que B n'a pas fini.
- L'utilisateur doit pouvoir voir que B est occupé et, si possible, suivre le travail headless en temps réel. La solidité prime sur cette UI temps réel.
- L'utilisateur doit pouvoir cancel le travail d'un agent occupé.
- Si l'utilisateur annule A dans la CLI alors que A attend B, l'annulation doit cascader vers B.
- B ne peut être annulé que par l'agent qui lui parle ou par l'utilisateur, pas par un autre agent tiers.
- Si plusieurs agents veulent parler à B, les demandes attendent en FIFO simple jusqu'à ce que B soit libre.
- Le headless est seulement l'interface de communication agent-agent : mêmes mémoire, contexte, historique, permissions, cwd et outils qu'en usage CLI interactif.
- Un historique reconstitué doit être accessible depuis la cellule, via un bouton, avec toutes les conversations de la session dans un historique unique.
- L'architecture reste hexagonale : conversation canonique par modèle/adapters, pas de dépendance directe dispersée aux formats natifs.
## Priorité de conception
Priorité 1 : robustesse et absence de blocage durable.
Le design doit privilégier des garanties mécaniques simples : processus headless borné, fin par exit process, timeout, cancel explicite, nettoyage d'état idempotent, queue FIFO observable, et résultat synthétique en cas d'échec.
Priorité 2 : observabilité et UX.
L'affichage temps réel du travail headless est souhaité si le mode headless permet de streamer stdout/stderr ou événements structurés, mais ne doit pas fragiliser le protocole. À défaut, fournir statut occupé, bouton cancel, historique final et diagnostic exploitable.
## Décision de périmètre
Le MCP n'est pas retiré globalement. Il est retiré uniquement du chemin critique de conversation inter-agent. Les outils IdeA existants peuvent rester exposés aux agents via MCP tant qu'ils ne servent pas au rendez-vous conversationnel.
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: Directives produit consolidees pour guider Main sur la robustesse, la persistance, le handoff cross-profile et la sobriete UX.
metadata:
type: project
---
# IdeA Product Directives For Main
## Resume
Cette note consolide les arbitrages produit explicites donnes par l'utilisateur pour aider `Main` a poursuivre le projet sans ambiguite.
See also:
-`agent-context-memory-and-profile-handoff` for the structural model of context, durable memory, live state, and handoff.
-`remaining-work-idea-agent-control-ide` for the current implementation status and remaining work.
Elle ne remplace pas les notes techniques existantes. Elle sert de reference prioritaire sur:
- la robustesse attendue,
- la persistance et la reprise,
- le handoff entre profils IA,
- la memoire projet partagee,
- le live state projet,
- la sobriete UX.
## Priorite Absolue
La priorite produit numero un est la robustesse.
Ordre de priorite impose:
1. robustesse et solidite avant tout
2. persistance et reprise
3. handoff cross-profile
4. live state projet
5. reste des features et du polish
Regle de pilotage:
- un systeme incomplet mais solide vaut mieux qu'un systeme riche mais fragile
-`Main` doit privilegier les architectures et comportements qui reduisent les crashes, les incoherences d'etat et les flows difficiles a reprendre
## Reprise Et Persistance
Quand IdeA redemarre, l'objectif n'est pas seulement de rouvrir une UI ou de restaurer des handles techniques.
La cible produit est:
- qu'un agent sache compactement sur quoi il travaillait
- qu'IdeA fournisse ce materiel de reprise automatiquement
- que la reprise soit exploitable meme si la conversation visible precedente n'est pas restauree a l'identique
Le bon modele est:
- un log canonique IdeA comme source durable
- un resume/handoff genere par IdeA comme couche compacte de reprise
Le resume/handoff n'est pas un confort secondaire. Il fait partie du comportement normal du produit.
## Handoff Cross-Profile
La cible ideale est double:
- reprendre correctement le travail
- donner si possible une impression de continuite presque sans rupture
Mais en cas de compromis, la priorite doit etre:
- fidelite operationnelle du travail repris
- avant la parfaite illusion de continuite terminale ou conversationnelle
Autrement dit:
- si un agent passe de Claude a Codex, IdeA doit d'abord garantir que Codex puisse reprendre le plus fidelement possible le travail utile
- l'absence de restauration parfaite de l'ancien terminal est acceptable si le handoff reste bon
## Perimetre Profils
Le perimetre de reference immediat est:
- Claude
- Codex
Toute fonctionnalite importante doit etre faisable pour ces deux profils.
Directive associée:
- reduire au maximum les dependances a des commandes, flags ou comportements specifiques a un profil
- construire un noyau le plus generique possible tout en restant concretement compatible Claude/Codex
- les autres profils pourront etre ajoutes plus tard si possible, mais ne doivent pas detourner le coeur du chantier actuel
## Memoire Projet Partagee
La memoire projet partagee doit rester petite, stable et utile.
Elle ne doit pas devenir un gros bloc qui siphonne les tokens de l'utilisateur a chaque requete.
Ce qu'un agent peut ecrire automatiquement dans la memoire partagee si c'est stable et utile:
- decisions durables d'architecture ou d'organisation
- preferences utilisateur durables
- regles de workflow durables
- references importantes a conserver
- resumes de handoff utiles a la reprise inter-session ou inter-profil
Ce qu'un agent ne doit pas y ecrire automatiquement:
- conversations brutes
- journaux detailles de travail
- etats temporaires
- files d'attente
- coordination temps reel
- essais/erreurs locaux
- hypotheses non stabilisees
- contenu redondant ou reconstructible ailleurs
Principe de fond:
- memoire durable = savoir stable
- log canonique = historique
- handoff = reprise compacte
- live state = coordination vivante
Ces couches doivent rester separees.
## Live State Projet
Le live state projet partage doit exister comme mecanisme interne d'IdeA.
Contraintes produit:
- il doit rester invisible pour l'utilisateur
- il doit survivre au redemarrage d'IdeA
Il ne doit pas se transformer en UI verbeuse ni en mecanisme demandant une intervention explicite de l'utilisateur.
## UX Et Philosophie Produit
IdeA doit etre tres facile d'utilisation.
Objectif UX:
- plug and play
- pas de sensation de parametrage impose
- pas de surcharge de tuto au premier lancement
- pas d'impression que le produit force des comportements internes a l'utilisateur
Ligne directrice souhaitee:
- esprit "maniere Linux"
- comportement simple et utile par defaut
- pas de contrainte tant qu'il n'y a pas un vrai besoin
- suggestion discrete seulement si IdeA detecte qu'une aide ou une optimisation devient utile
Le precedent du compactage de contexte est considere comme la bonne direction:
- pas de compactage impose d'emblee
- une popup proposee seulement si IdeA sent un besoin
## Transparence Des Mecanismes Internes
Les mecanismes suivants doivent rester quasi invisibles pour l'utilisateur:
- delegations inter-agents
- FIFO
- handoffs
Ils peuvent devenir visibles en debug ou quand le produit a une bonne raison UX de les exposer, mais ils ne doivent pas etre ressentis comme une charge cognitive normale d'utilisation.
## Frontiere Avec Le Chantier Inter-Agents De Main
Les choix fins touchant la communication entre agents ne doivent pas etre recadres ici si `Main` est deja en train de les traiter.
Cette note ne doit donc pas etre lue comme une specification d'implementation inter-agents detaillee.
Elle fixe seulement les invariants produit suivants:
description: Cadrage du programme post-skills : ce qui existe vs reste, et la règle de frontière surface-agent/humaine.
metadata:
type: project
---
État au cadrage (develop @ 827d477) :
- **#2a handoff incrémental cross-profile = FAIT + CÂBLÉ** (lots P1–P8) : `domain/conversation_log.rs` (`Handoff`, `HandoffStore`, `HandoffSummarizer::fold` incrémental, `ProviderSessionStore`), `application/conversation/record.rs` (`RecordTurn` = append+fold), wiring `state.rs` (`AppRecordTurnProvider`), injection au lancement via `HandoffProvider` (P7). Résumeur = `HeuristicHandoffSummarizer`, seam async pour `LlmHandoffSummarizer` (P10) déjà en place. Reste : seam LLM + borne `summary_md`.
- **#2b log canonique = FAIT** : `ConversationLog` append-only `.ideai/conversations/<id>/log.jsonl`, **jamais injecté à un agent** (doc domaine explicite). Reste : rétention/rotation + lecture paginée UI.
- **#1 live-state agent = À CONSTRUIRE (cœur)** : aujourd'hui seul `GetProjectWorkState` (read-model HUMAIN dérivé, non durable). Construire un 4ᵉ store `live-state.json` (**gitignored, runtime**) : `domain/live_state.rs` (`LiveState`/`LiveEntry`/`WorkStatus`, port `LiveStateStore`, **keyed last-writer-wins, JAMAIS append**, champs bornés, `prune` TTL+max_n), use cases `UpdateLiveState`/`GetLiveStateLean`, adapter `FsLiveStateStore`, auto-update depuis `orchestrator/service.rs` (ask→Working, reply→Done), injection bornée `# État du projet` au lancement + outils MCP `idea_workstate_read`/`idea_workstate_set`. Ne stocke QUE le non-re-dérivable (intent/progrès/dernière délégation) ; busy/queue restent calculés.
**Règle de frontière gravée (testable)** : Surface AGENT = borné/distillé/pointeur uniquement (capacités, pointeurs mémoire, affordances skills, handoff `summary_md`, live-state lean). Surface HUMAINE = riche (ProjectWorkStatePanel, viewer fil-par-paire qui rejoue le log). **Transcript / append-only INTERDIT d'injection dans un contexte agent** ; seul un dérivé distillé franchit. 4 stores distincts : `memory/` (savoir stable, versionné) · `handoff.md` (reprise par fil) · `log.jsonl` (transcript humain) · `live-state.json` (coordination transitoire maigre).
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)
**Doc de référence** : `docs/LS8-live-state-persistence-closure.md` + `ARCHITECTURE.md` §21 (cartographie des 4 stores) ; §19 retitré « LIVRÉ » ; §14.1 et §18.5 corrigés (catalogue MCP **14**).
**4 stores disjoints** : `.ideai/memory/` (savoir stable, versionné) · `handoff.md` (reprise distillée bornée ≤4096) · `log.jsonl` (transcript humain riche, JAMAIS injecté) · `live-state.json` (coordination maigre runtime, gitignoré). Surface AGENT bornée/distillée/pointeur ; surface HUMAINE riche ; seul le handoff distillé franchit vers un contexte agent.
**Restant ouvert** : (1) activation seam LLM `LlmHandoffSummarizer` (non activé, défaut heuristique, contrat ADR LS5) ; (2) sweep périodique de rotation (idempotent, non câblé) ; (3) **discordance D19-4 ↔ .gitignore** : `.ideai/conversations/` devait être gitignoré mais est suivi — à trancher Git/Main ; (4) intégration MCP e2e UX ; (5) multi-fenêtres registre sessions ; (6) auto-update mémoire/contexte en cours de session.
**Contrainte d'écriture** : un agent lancé en run-dir isolé (`.ideai/run/<id>/`) **ne peut pas écrire l'arbre du project root** (ARCHITECTURE.md, docs/) — les modifs de doc passent par Git (write+commit authority).
title: Réconciliation du live-state au reboot (ReconcileLiveState)
type: reference
description: Décision d'architecture pour réconcilier les lignes live-state fantômes au redémarrage — seam, source de vérité, contrat de statut et préservation du self-only.
---
**Problème** : au reboot, `.ideai/live-state.json` n'est pas réconcilié → agents fantômes `working/waiting/blocked` alors que leurs sessions sont mortes. `prune` (TTL 6h) ne couvre pas le fantôme récent.
**Décision (cadrage Architect, 2026-06-23)** :
- **Seam** : étape best-effort `reconcile_live_state` dans la chaîne `open_project` (commands.rs), juste après `reconcile_layouts`. Pas d'app-boot global (live-state est par-projet). Pas d'outil MCP. Justification : un crash ne déclenche jamais `close_project`, donc **open est le seul filet fiable**.
- **Source de vérité** : le registre de sessions vivantes `LiveSessions` / trait `LiveAgentRegistry` (`application/src/terminal/registry.rs`) — la *même* vérité que `GetProjectWorkState`. Prédicat orphelin = `status ∈ {working,waiting,blocked}` ET `!is_agent_live`. Race-safe : tourne avant toute relance ; LWW gagne via `updated_at_ms` frais.
- **Contrat** : orphelin → `idle` (pas de nouveau statut, évite le ripple DTO/front). Garder `intent` (trace), vider `ticket` + `last_delegation` (rendez-vous mort, tracé ailleurs dans log/handoff), `progress` = marqueur stale, `updated_at_ms` frais.
- **Pas de nouveau port.** Use case applicative `ReconcileLiveState` (compose `LiveStateStore` + `LiveAgentRegistry` + `Clock`) + transformation **pure**`LiveState::reconcile_orphans(is_live, now)` dans `domain/src/live_state.rs`. Provider par-root côté app-tauri (calqué sur `LiveStateProvider`/`live_state_for`).
- **Self-only de `idea_workstate_set`** : invariant **de la surface MCP**, pas du store. `set_workstate` lie `agent_id`=identité handshake ; `UpdateLiveState`/`LiveStateStore` sont identity-agnostic. La réconciliation est un **acte système** dans le composition root qui appelle le port directement, **jamais** via `OrchestratorCommand::SetWorkState` → self-only préservé par construction. Même split que `snapshot_running_agents`/`reconcile_layouts`.
description: Pieges runtime du pont MCP et de la delegation inter-agents IdeA, et la regle de rebuild de l'AppImage.
metadata:
type: project
---
# Pont MCP & delegation : pieges runtime
Deux bugs trouves le 2026-06-13 en testant la conversation inter-agents (`idea_ask_agent` vers TestConversation), tous deux corriges. Notes utiles pour ne pas reperdre du temps :
## Le binaire qui tourne = AppImage installee, pas les sources
L'IdeA en cours d'utilisation est `/home/anthony/Documents/IdeA_0.1.0_amd64.AppImage`. **Ce meme binaire sert a la fois de serveur orchestrateur (il tient `OrchestratorService`/`InputMediator` et compose les evenements) ET de binaire-pont** (`<exe> mcp-server …` declare dans chaque `.ideai/run/<id>/.mcp.json`). Donc **tout correctif cote serveur ou cote pont n'est actif dans l'app que apres rebuild + reinstall de l'AppImage et relance d'IdeA**. Un binaire `target/debug` fraichement compile ne valide que le pont (qui parle au serveur via le socket) ; il ne valide pas la composition cote serveur.
**Comment appliquer :** apres une correction backend, rebuild AppImage (`npm --prefix frontend run build` puis, depuis `crates/app-tauri/`, `../../frontend/node_modules/.bin/tauri build --bundles appimage`), remplacer l'AppImage, relancer IdeA, puis retester. **Exclure NSIS** (`--bundles appimage`) sur Linux. **Piege FUSE (2026-06-14)** : l'etape finale `linuxdeploy` echoue avec `failed to run linuxdeploy` (linuxdeploy est une AppImage qui se monte via FUSE). Workaround obligatoire : prefixer `APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1`. Le compile Rust reussit avant ce point ; seul le bundling casse, et l'`AppDir` est genere mais pas le `.AppImage`. L'artefact final = `target/release/bundle/appimage/IdeA_0.1.0_amd64.AppImage`.
## Env AppImage pollue le shell
La session shell herite des variables de l'AppImage montee (`APPDIR`, `LD_LIBRARY_PATH`, `PYTHONHOME` -> `/tmp/.mount_IdeA_*`). Consequences : `python3` casse (`No module named 'encodings'`) et lancer un binaire app-tauri fraichement compile tente de booter WebKit et crash. **Workaround :** lancer avec un env propre (`env -i PATH=/usr/bin:/bin HOME=$HOME XDG_RUNTIME_DIR=/run/user/1000 ...`), utiliser `jq` plutot que python.
## Bug 1 — pont MCP en lockstep (corrige)
`mcp_bridge.rs::relay` lisait 1 ligne client -> attendait 1 reponse loopback, en boucle. MCP n'est pas 1:1 : `notifications/initialized` n'a pas de reponse => deadlock juste apres `initialize`, et `tools/list` n'etait jamais relaye => les outils `idea_*` ne se chargeaient jamais (`claude mcp list` affichait quand meme "Connected" car `initialize` repond). Corrige en relay **full-duplex** (deux pompes concurrentes) + drain borne (`DRAIN_GRACE`) a la fermeture stdin. Symptome cote utilisateur : 3 jours de "outils MCP pas charges".
## Bug 2 — prefixe de delegation perdu (corrige)
Le signal `[IdeA · tâche de <demandeur> · ticket <id>]` qui dit a l'agent cible "reponds via `idea_reply`, jamais en texte" n'etait plus compose nulle part : supprime du backend (`service.rs` C3 §5.1) lors du passage de l'ecriture PTY au frontend, mais le frontend (`useWritePortal.ts`) ecrit `head.text` verbatim et ne l'ajoutait pas. La cible recevait la tache brute, repondait en texte => `idea_ask_agent` timeout. Corrige en composant le prefixe dans `infrastructure/src/input/mod.rs` (`delegation_preamble`) a l'emission de `DomainEvent::DelegationReady` ; la tache brute reste dans le `Ticket`/historique. Rappel : la correlation `idea_reply` marche par ticket OU par tete de FIFO (fallback), donc le ticket echo est recommande mais pas strictement requis.
Deleguer a un agent **froid** (pas encore lance) via `idea_ask_agent` echouait silencieusement : terminal cible vide, ask bloque jusqu'au timeout 300s ; un agent **chaud** (deja a son prompt, lance manuellement) marchait. Cause : avec `with_structured` non cable sur l'orchestrateur (regression assumee `aa2f67a`), tous les `ask` passent par le chemin PTY `ensure_live_pty` (`application/src/orchestrator/service.rs`). Un agent jamais vu etait initialise `Idle` (`BusyTracker::start_turn` -> `or_insert(Idle)`, `infrastructure/src/input/mod.rs`), donc le 1er `enqueue` publiait `DelegationReady`**immediatement** et ecrivait la tache dans le PTY avant que le CLI ait affiche son prompt -> tache perdue. Le prompt-ready watcher (lot C5) ne gardait que les tours suivants. Fix : `ensure_live_pty` renvoie un flag `cold_launch` ; si cold ET `prompt_ready_pattern` non vide, l'orchestrateur appelle `mark_starting(agent)` -> la `DelegationReady` du 1er tour est **differee** puis publiee par le watcher a l'apparition du prompt. Sans pattern ou agent chaud -> livraison immediate (zero regression). Touche `domain/src/input.rs` (port `mark_starting`), `infrastructure/src/input/mod.rs`, `service.rs`.
## Bug 4 — le fix cold-launch ne s'armait jamais en prod (corrige 2026-06-15)
Le « fix Bug 3 corrige 2026-06-14 » etait trop optimiste : il ne s'arme que si `gate_cold_start = cold_launch && prompt_ready_pattern non vide` (`service.rs`). Or le profil **Claude Code** (`664cc20c`, partage par TOUS les agents) dans `~/.local/share/app.idea.ide/profiles.json` n'a **aucun**`prompt_ready_pattern`. Donc `mark_starting` n'etait jamais appele -> `enqueue` publiait `DelegationReady` immediatement -> tache ecrite dans le PTY avant le prompt de `claude` -> 1er tour perdu -> `idea_ask_agent` vers un agent **froid** bloque jusqu'au timeout (un agent **chaud** marche). Le Bug 3 n'etait valide que par des tests unitaires qui injectent un pattern a la main : le gap d'integration (profil sans pattern) n'avait pas ete vu.
**Fix (option B, signal MCP, sans sniff de prompt TUI) :** on gate le cold-launch des qu'un MCP est configure sur le profil (`gate_cold_start = cold_launch && (pattern non vide || profil.mcp.is_some())`), et on **libere** le tour differe quand le pont MCP de l'agent froid se connecte (= son CLI est up + outils charges) : nouveau port `InputMediator::release_cold_start(agent)` (drain du `deferred`, **sans**`mark_idle` car c'est un signal de DEMARRAGE, idempotent et OR-safe avec `prompt_ready`), `McpServer` fire un `ready_sink: Fn(&str)` sur `initialize` avec le `requester` du handshake, et la composition root (`state.rs::ensure_mcp_server`) parse le requester en `AgentId` -> `OrchestratorService::release_agent_cold_start`. Touche `domain/src/input.rs`, `infrastructure/src/input/mod.rs`, `infrastructure/src/orchestrator/mcp/server.rs`, `application/src/orchestrator/service.rs`, `app-tauri/src/state.rs`. Tests unitaires verts ; **validation live = rebuild AppImage + relance IdeA** (cf. section binaire qui tourne). Filet OR : si un jour un profil porte un `prompt_ready_pattern`, les deux signaux coexistent.
**Methodo :** ne PAS deleguer la reparation du systeme inter-agents via le systeme inter-agents (casse) — utiliser les subagents natifs (outil Agent).
## Bug 5 — la boucle `serve` du serveur MCP est en lockstep, un `ask` sans reponse wedge TOUTE la connexion (corrige 2026-06-15)
Symptome : 1er `idea_ask_agent` vers un agent qui repond -> OK ; puis `ask` vers un agent qui ne rappelle JAMAIS `idea_reply` (ex. QA lance mais qui ne repond pas) -> ensuite **tout** appel suivant du MEME demandeur (meme `idea_list_agents`, sans rendezvous) se bloque. Cote utilisateur : "DevBackend ne marche plus" alors qu'il repondait 11s avant — en realite c'est la connexion du DEMANDEUR (Main) qui est morte, pas la cible. Annuler l'appel cote client ne deparke rien.
Cause : `infrastructure/src/orchestrator/mcp/server.rs::serve` lisait 1 requete puis **awaitait `handle_raw` en entier** avant de relire. Pour `idea_ask_agent`, `handle_raw -> dispatch -> service.dispatch` attend le `idea_reply` de la cible : pendant cette attente la boucle ne relit plus rien -> pipeline de la connexion fige. C'est l'analogue COTE SERVEUR du Bug 1 (lockstep) qui n'avait ete corrige que cote pont (`mcp_bridge.rs`). NB : la couche application bornait deja le rendezvous (`service.rs::ask_agent` via `tokio::time::timeout`), donc l'attente n'etait pas infinie — le vrai coupable etait bien la serialisation de `serve`, pas l'absence de timeout.
Fix : `serve` reecrite full-duplex non bloquante — `tokio::select!` entre `transport.recv()` et un canal `mpsc::unbounded::<Option<Vec<u8>>>` ; chaque message entrant traite dans une tache `tokio::spawn` qui possede un clone cheap `self.for_requester(self.requester.clone())` ('static) + un clone du `tx` ; reponses (`Some`) ou notifications (`None`) renvoyees par le canal et ecrites par la meme boucle ; arret gracieux via compteur `in_flight` (on draine les taches en vol apres EOF). Les reponses MCP portent l'`id` -> ordre indifferent. Le trait `Transport` (`&mut self` recv/send) et les signatures `serve`/`serve_as` restent intacts. + filet de securite : timeout serveur **isole au seul `idea_ask_agent`** (`ASK_RENDEZVOUS_TIMEOUT` 24h, finie ; setter `with_ask_rendezvous_timeout``#[doc(hidden)]` pour les tests). Tests : `infrastructure/tests/mcp_server.rs` -> `pending_ask_does_not_wedge_the_connection_concurrent_call_still_answered` (anti-wedge, echoue sur l'ancien code) + `ask_agent_rendezvous_times_out_with_a_jsonrpc_error`. Verts : `cargo test -p infrastructure` (20 mcp_server), `cargo test -p app-tauri --lib mcp_e2e_loopback_tests` (6). **Validation live = rebuild AppImage + relance IdeA** (la connexion wedgee ne se deparke pas de l'interieur : il FAUT relancer IdeA). AppImage du 2026-06-15 10:58 contient le fix ; backup `~/Documents/IdeA_0.1.0_amd64.AppImage.old-prewedgefix`.
Reste a investiguer (separe, non bloquant) : pourquoi QA lance ne repond pas du tout a une delegation (son `claude` n'appelle pas `idea_reply`) — impossible a creuser tant que la connexion du demandeur est wedgee ; le fix Bug 5 permet desormais de le diagnostiquer sans tout figer.
## Bug 6 — la tache n'est JAMAIS ecrite dans le PTY d'un agent delegue en arriere-plan (corrige 2026-06-15)
C'EST la cause racine du « reste a investiguer » du Bug 5. Symptome : un agent lance a la main (cellule visible, ex. DevBackend) repond ; un agent **froid auto-lance par `idea_ask_agent`** (ex. QA) ne repond JAMAIS → `ask` bloque jusqu'au timeout (24h, `ASK_AGENT_TIMEOUT`). Reproduction sure et non bloquante : `idea_stop_agent` puis `idea_launch_agent(task=…, visibility=background)`, et observer le dossier de session `claude` de la cible (`~/.claude/projects/<run-dir>/*.jsonl`) : **aucune nouvelle session** en 28 s = la tache n'a jamais ete soumise (le pont MCP de la cible, lui, se connecte bien).
Cause : depuis ARCHITECTURE §20, l'**ecriture physique du PTV est faite par le write-portal FRONTEND** (`useWritePortal`), qui n'est monte **que pour une cellule de layout (leaf) visible**. Or `ensure_live_pty` cold-lance la cible en **background avec `node_id: None`** (`service.rs`) → aucune cellule montee → `useWritePortal` n'existe pas → l'event `DelegationReady` n'a **aucun consommateur** → tache perdue. Les fix Bug 3/4 (differer/liberer la `DelegationReady`) ne pouvaient donc jamais marcher pour un agent background : ils publient un event que personne n'ecoute cote UI.
Fix (option « writer PTY backend ») : le mediateur ecrit lui-meme la tache dans le PTY **quand aucune cellule frontend n'est attachee**. Registre `front_owned` dans `MediatedInbox`, alimente par le front via `bindHandle`/`unbindHandle` → commande Tauri `set_front_attached` → `OrchestratorService::set_agent_front_attached` → `InputMediator::set_front_attached`. Point de livraison unique = `BusyTracker::publish_deferred` (chemin chaud immediat ET drains froids `prompt_ready`/`release_cold_start`), qui passe par un **HeadlessSink** optionnel cable par `MediatedInbox::with_pty`/`with_events` : agent dans `front_owned` ⇒ `Some(d)` (publie l'event, le front ecrit, inchange) ; sinon ⇒ ecrit texte puis (apres `submit_delay_ms`, defaut 60ms, anti-paste-detection) la `submit_sequence` dans le handle PTY bound, sur un `std::thread` detache (le watcher prompt-ready tourne sur un std::thread, PAS tokio — ne pas utiliser `tokio::spawn`). Touche `domain/src/input.rs` (port `set_front_attached`), `infrastructure/src/input/mod.rs` (HeadlessSink + front_owned + handles en `Arc<Mutex>`), `application/src/orchestrator/service.rs`, `app-tauri/src/{dto,commands,lib}.rs`, `frontend/src/{ports,adapters/input,adapters/mock,features/terminals/useWritePortal}`. Tests verts : `cargo test -p infrastructure` (input 34, dont `headless_agent_without_front_cell_is_written_by_the_backend` + `front_attached_agent_is_delivered_via_event_not_backend_write`), app-tauri lib 42, useWritePortal 9, tsc. **Validation live = rebuild AppImage + relance IdeA** (build 2026-06-15 11:42 ; backup `~/Documents/IdeA_0.1.0_amd64.AppImage.old-prefrontwriterfix`).
NB diagnostic : `~/.claude/projects/<encoded-run-dir>/*.jsonl` = transcript de session `claude` de l'agent ; pas de nouveau fichier apres une delegation = tache jamais soumise. `ss -xp | grep idea-mcp` = ponts MCP connectes cote serveur.
## Bug 7 — une delegation interrompue/annulee laisse la cible `Busy` a vie (corrige 2026-06-15, sources ; pas encore en AppImage)
Symptome : un agent qui repondait (ex. DevFrontend a « 123x4=492 », QA pendant LP3) ne repond plus du tout aux delegations suivantes ; `idea_ask_agent` bloque jusqu'au timeout. DevBackend, lui, continue de marcher. Diagnostic ecarte 2 fausses pistes : (a) PAS le socket MCP — les 6 ponts sont ESTAB (`ss -xp | grep idea-mcp`), pont vivant ; (b) PAS « lance a la main vs par IdeA » — DevBackend est aussi auto-lance et marche. Le vrai discriminant : **une delegation vers cet agent a-t-elle ete interrompue/annulee cote demandeur ?** DevFrontend coince par l'interruption d'une tache LP3 ; QA coince par un ping diag rejete ; DevBackend jamais annule => OK. Confirmation cote transcript : la tache figure dans le `log.jsonl` de la conversation (donc enqueue cote serveur a eu lieu) mais PAS dans le transcript `claude` de la cible (`~/.claude/projects/<run-dir>/*.jsonl`) => jamais ecrite dans son PTY.
Cause (lue dans `application/src/orchestrator/service.rs`, chemins `ask` PTY ~l.847-911 ET `ask_structured` ~l.927-1011) : l'agent passe `Idle→Busy` des `input.enqueue` (~l.978). Il ne redevient `Idle` que sur la branche **succes** (`input.mark_idle`, ~l.1001). Les branches **erreur/annulation/timeout** (~l.901-910 et ~l.1004-1009) appellent `mailbox.cancel_head` mais **jamais `mark_idle`**. Pire : quand le demandeur interrompt l'appel, le futur `ask_agent` est **dropped** => AUCUNE branche du `select!` ne s'execute => l'agent reste `Busy` pour toujours. Une cible `Busy` met les delegations suivantes en file derriere un tour fantome, jamais livrees au PTY. L'etat `Busy` vit en memoire dans le process serveur et **ne se deparke pas de l'interieur** : deblocage immediat = **relancer IdeA** (comme le wedge du Bug 5).
Fix (corrige cote sources, `service.rs`) : garde RAII `BusyTurnGuard` (Arc clones de `InputMediator` + mailbox, `agent_id`, `ticket_id`, flag `armed`) cree juste apres l'enqueue dans les DEUX chemins (`ask` PTY et `ask_structured`) ; son `Drop` (si arme) appelle `cancel_head(agent,ticket)` puis `mark_idle(agent)` ; `disarm()` sur la branche succes (le `mark_idle` propre existant reste, pas de double cancel). Les `cancel_head` redondants des branches erreur/`_cancelled`/`_elapsed` ont ete retires au profit du garde (`cancel_head` est positionnel/idempotent). Indispensable que ce soit un garde et pas un `mark_idle` dans les branches : le cas reel est un futur DROPPED, aucune branche du `select!` ne s'execute. Tests verts : `cargo test --workspace` 80 suites 0 echec, dont `dropped_ask_future_frees_busy_target`, `second_delegation_delivered_after_dropped_ask`, `cancelled_ask_marks_target_idle` (tests/orchestrator_service.rs) + 2 tests du garde dans le `mod tests` de service.rs.
VERDICT `sweep_stalled` (infra/input/mod.rs) : **purement advisory** — bascule `Alive→Stalled` et emet `AgentLivenessChanged` mais **n'appelle JAMAIS `mark_idle`** (par conception : « la FIFO et le tour continuent »). Ce n'est donc PAS un filet pour ce bug ; le garde RAII est la seule correction. Non recable (changement de semantique hors perimetre).
**Pas encore actif live : rebuild AppImage requis** (`npm --prefix frontend run build` puis depuis `crates/app-tauri/``APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1 ../../frontend/node_modules/.bin/tauri build --bundles appimage`, remplacer l'AppImage, relancer IdeA). Le relancement d'IdeA debloque aussi DevFrontend/QA actuellement coinces `Busy` (etat en memoire). Methodo (rappel Bug 4) : NE PAS reparer le systeme inter-agent via `idea_ask_agent` (casse) — utiliser les subagents natifs (outil Agent).
See also [[remaining-work-idea-agent-control-ide]], [[agent-context-memory-and-profile-handoff]].
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]].
Décision/fix du ticket #157 (itération 2, 2026-08-09). Profil OpenCode/GLM sur la CLI custom.
## Cause racine (vérifiée en source)
`opencode run --format json` (v1.17.18, `packages/opencode/src/cli/cmd/run.ts`) émet chaque event JSONL **en temps réel** sur stdout. Schéma ligne : `{type, timestamp, sessionID, ...data}`. Types : `step_start`, `step_finish`, `text` (part textuelle finalisée sous `.part.text`, pas un delta token), `tool_use` (**seulement à** `part.state.status=completed|error`, pas de start ; nom outil sous `.part.tool`), `reasoning` (thinking), `error`.
Le parser IdeA `crates/infrastructure/src/session/opencode.rs::parse_jsonl_record` reconnaît déjà ces types — il n'était pas cassé. Les 2 vraies causes :
1.`OpenCodeSession` ne surchargeait pas `send_with_tap` (défaut `domain/ports.rs:1380` ignore le tap) → `send` bufferisait tout le stdout jusqu'à l'EOF → silence total pendant le tour, burst à la fin.
2. La bulle `kind:Mcp`/`source:IdeaLocal` n'était émise que par `openai_compat.rs::apply_tool_calls`. OpenCode taggait les `tool_use` en `kind:Tool` générique.
- Override `send_with_tap` sur `OpenCodeSession` (pattern codex.rs/claude.rs) : stdout ligne par ligne, projection live au tap des non-terminaux, Final assemblé à la fin (jamais au tap).
- Protection replay : session continuée = premier messageID tamponné puis supprimé si un nouveau arrive.
## Règles durables pour les prochains travaux
- **Tout adapter `AgentSession` qui peut streamer DOIT surcharger `send_with_tap`** (le défaut ignore le tap). Sans override, la CLI custom est muette jusqu'au Final. codex/claude/openai_compat l'ont ; OpenCode maintenant aussi.
- **Bulle MCP dédiée** : n'existe que quand IdeA sait que l'outil est un outil MCP IdeA. Pour les profils provider-orchestrés (OpenCode/Codex/Claude), les outils tournent DANS le provider ; IdeA ne peut les tagger Mcp que heuristiquement (préfixe `idea_`). Pour openai_compat (IdeA orchestre), c'est natif (`IdeaLocal/Mcp`).
- **opencode JSON est granulaire et honnête** : pas de deltas de tokens ni de tool-start. Ne pas simuler de faux stream. step_start/step_finish + text(finalisé) + tool_use(completed) = le maximum exposé.
- **Dette** : events `reasoning` (thinking) d'OpenCode ignorés par le parser (`Ignored`). Ticket dédié à créer si on veut afficher le thinking.
- **Réserve replay** : session continuée à messageID unique sans replay observable → tap muet jusqu'à l'EOF (`hold_until_new_message`) pour éviter un historique non rétractable. Cas frais = streaming immédiat.
## Validation
`cargo test -p infrastructure session::opencode` = 20 passed (3 nouveaux tests streaming/mcp/replay). QA VERT, pas de régression (échecs larges mcp_server/orchestrator pré-existants sur base `a3d94d4d`). Vérif live user en attente (AppImage rebuild `target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage`). Branche non mergée dans develop en attente du live vert.
description: Etat du systeme de permissions/sandbox IdeA (domaine pur -> projection advisory -> enforcement OS Landlock sur PTY ET structure) et l'unique risque residuel.
metadata:
type: project
---
# Systeme de permissions / sandbox IdeA
Chantier "permissions des agents" complet bout-en-bout au 2026-06-16, sur la branche `wip/p8c-checkpoint-before-codex`. Lots committes : LP4-0 (`b05d04a`), LP4-1/LP4-2 (`17ca65e`), LP4-3 (`7e01ac6`), LP4-4 (`6236cd7`).
- **Enforcement OS LP4** : `LandlockSandbox` (Linux) / `NoopSandbox` / `default_enforcer()`. Actif sur **les deux chemins** : PTY brut (`pty/mod.rs``spawn_command_sandboxed`) ET structure Claude/Codex JSON (`session/process.rs``run_turn_sandboxed`).
## Technique d'enforcement (load-bearing, ne pas casser)
`enforce(plan)` tourne sur un **thread jetable AVANT le spawn**, puis le child est spawne depuis ce thread => il herite le domaine Landlock via les credentials de la tache (garanti par le noyau a travers fork/clone/execve, y compris posix_spawn). **Pas de `pre_exec`** : `infrastructure` est `#![forbid(unsafe_code)]` (intact), et le pre_exec n'aurait ete qu'une assurance de determinisme, sans valeur de securite ; allouer dans `landlock` post-fork (pre_exec) serait au contraire dangereux (deadlock malloc en process multithreade). Fail-closed : `Err` d'enforce => aucun child. Le garde-fou anti-regression est le test e2e **"ecriture hors-grant bloquee"** (present pour PTY et structure) : tant qu'il est vert, l'heritage tient.
## Risque residuel a traiter (signale par l'Architecte, NON couvert)
Les CLI structurees (claude/codex = binaires Node) ont des besoins FS ambiants lourds : `~/.claude`/`~/.codex` (credentials + cache de session pour le **resume**), `node_modules`, libs systeme, caches temp. `compile_sandbox_plan` ne fence que les classes explicitement posees et garde les lectures ouvertes — mais une policy posant un `Deny`/posture restrictive touchant `$HOME`**peut casser le resume**. Le mecanisme est valide au fake CLI (zero token), mais **avant d'activer un plan restrictif en prod sur le chemin structure**, valider e2e manuellement qu'un vrai tour claude/codex sous plan representatif garde run_dir + home de la CLI atteignables. C'est un sujet de **composition du plan** (run_dir reachability ; `SandboxContext.run_dir` existe mais n'est pas encore consomme par la traduction pure), pas du mecanisme — lot suivant si besoin.
Voir [[remaining-work-idea-agent-control-ide]] pour le reste des chantiers produit.
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.