65 Commits

Author SHA1 Message Date
13fb538880 fix(permissions): validate network access flow 2026-07-26 10:52:46 +02:00
3047dc9195 feat(permissions): expose network permission state (#103) 2026-07-25 23:06:50 +02:00
e8731834f4 merge: integrate ticket 101 multi-project isolation 2026-07-25 22:14:19 +02:00
6e98fd89f7 fix(runtime): isolate agent state by project (#101) 2026-07-25 22:14:02 +02:00
6a87c4635f merge: integrate ticket 98 OpenCode provider fix 2026-07-25 14:27:10 +02:00
ad491e765f fix(opencode): make models.dev providers cache-independent 2026-07-25 14:26:15 +02:00
69e5878679 fix(backend): clone du seed OpenCode tolère un seed persisté converti en cloud
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.
2026-07-25 01:47:58 +02:00
55330732dd Merge feature/ticket97-opencode-provider-mutual-exclusion into develop 2026-07-24 19:52:49 +02:00
0f0a76d806 fix(backend): exclusion mutuelle des backends OpenCode (llamacpp vs cloud) (#97)
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
2026-07-24 19:52:34 +02:00
7fee56acf5 Merge feature/ticket95-opencode-mcp-timeout into develop 2026-07-24 15:18:13 +02:00
6e9a3ff657 fix(agent): timeout MCP OpenCode aligné sur le plafond du rendez-vous (#95)
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.
2026-07-24 15:00:53 +02:00
7d3e74a114 Merge feature/opencode-permissions-projection into develop
Fix backend isolé, tests unitaires verts (ticket #94).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 18:14:27 +02:00
56631bd3c8 fix(backend): projeter les EffectivePermissions dans le bloc permission d'opencode.json
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>
2026-07-23 18:14:20 +02:00
c9fffd7c47 Merge feature/fix-opencode-jsonl-parsing into develop
Fix confiné à l'adaptateur OpenCode (parsing JSONL text.part.text +
gestion événements error), validé par QA (cargo test -p infrastructure
309 passed, 0 failed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 16:23:59 +02:00
0f57e7323a fix(backend): lecture du texte JSONL OpenCode niché sous part.text
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>
2026-07-23 16:23:50 +02:00
4ac2dd1568 Merge feature/fix-gguf-shard-selection into develop
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>
2026-07-23 13:23:59 +02:00
557d1c3f24 fix(backend): sélection GGUF prioritaire au fichier fusionné sur les shards
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>
2026-07-23 13:23:55 +02:00
e8f6e9eded Merge feature/ticket92-opencode-provider-dynamic-catalog into develop
Complément post-livraison du ticket #92 : catalogue de providers
OpenCode dynamique (cache ~/.cache/opencode/models.json, repli
statique garanti) + option provider personnalisé en saisie libre.
QA verte : backend cargo build+test workspace, frontend tsc/build,
vitest 958/958.
2026-07-23 12:44:17 +02:00
ef747cd156 chore(ideai): état runtime — permissions et tâche de fond
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:44:10 +02:00
1784027f5d feat(frontend): provider OpenCode dynamique + saisie provider personnalisé (#92)
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>
2026-07-23 12:44:02 +02:00
e943a0efed feat(backend): catalogue de providers OpenCode dynamique + provider personnalisé (#92)
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>
2026-07-23 12:43:59 +02:00
162e3ae641 chore(ideai): état runtime — tickets, mémoire, tâches de fond
Ticket #92 rouvert, mises à jour de carnets/tâches de fond.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 08:57:15 +02:00
12c7d103d0 Merge feature/ticket92-opencode-provider-cloud into develop
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>
2026-07-23 08:18:09 +02:00
c181b43d04 feat(frontend): support des providers OpenCode cloud (#92)
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>
2026-07-23 08:18:02 +02:00
23a3c2788f feat(backend): support des providers OpenCode cloud (#92)
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>
2026-07-23 08:03:03 +02:00
bece7c92c5 Merge feature/ticket43-plugin-system into develop
Système de plugins (#43) : backend (domaine/application/infrastructure,
lots B1-B4) + frontend (runtime, menus, layouts custom, lots F1-F4).
Validé vert par QA (cargo test + npm typecheck/test 947/947).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 07:37:28 +02:00
98fb05447d chore(ideai): état runtime — tickets, mémoire, tâches de fond
É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>
2026-07-22 07:37:19 +02:00
ac726d075e feat(frontend): système de plugins — runtime, menus, layouts custom (#43)
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>
2026-07-22 07:37:11 +02:00
bb35641715 feat(backend): système de plugins — domaine, application, infrastructure (#43)
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>
2026-07-22 07:37:03 +02:00
47aacc6da9 Merge feature/ticket61-cells-refit-web-regression into develop
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>
2026-07-21 21:23:34 +02:00
3a18556ffa fix(frontend): refit web agent cells after opening a new one (#61 web regression)
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>
2026-07-21 21:21:52 +02:00
a197197a90 Merge feature/ticket89-server-autostart into develop
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>
2026-07-21 21:16:59 +02:00
4fd339c047 Merge feature/ticket90-web-missing-menus into develop
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>
2026-07-21 21:16:55 +02:00
e9b01795d1 feat(frontend): démarrage auto du serveur + erreur port occupé actionnable (#89)
É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>
2026-07-21 18:38:40 +02:00
30f6415d51 feat(frontend): ajoute les menus Panneaux/Paramètres à la version web (#90)
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>
2026-07-21 18:33:22 +02:00
cb2d0c2d44 feat(app-tauri): option de lancement auto du serveur web au démarrage
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>
2026-07-21 18:32:03 +02:00
678ff3011b feat(web-server): élargir l'allowlist /api/invoke pour les menus Panneaux
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>
2026-07-21 18:31:58 +02:00
277a0e494a Merge feature/ticket87-web-workspace-collapse-tasks into develop
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>
2026-07-21 17:56:25 +02:00
1139041603 feat(frontend): repliage par défaut des background tasks par agent (#87)
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>
2026-07-21 17:56:21 +02:00
527c2dde61 Merge feature/ticket86-web-tickets-sprints-ui into develop
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>
2026-07-21 07:44:51 +02:00
e7f67bada9 fix(frontend): sprint change/removal on tickets, web workspace (#86 QA fix)
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>
2026-07-21 07:43:09 +02:00
e69361feb7 feat(frontend): tickets/sprints surface for the web workspace (#86 lot 2)
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>
2026-07-21 07:35:20 +02:00
45def4844a Merge feature/ticket86-web-tickets-sprints into develop
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>
2026-07-21 07:20:46 +02:00
5a7431b442 feat(backend): commandes web tickets/sprints + factorisation DTO (#86 lot 1)
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>
2026-07-21 07:20:39 +02:00
5d262231e2 Merge feature/ticket83-quit-confirm-work-in-progress into develop
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>
2026-07-20 19:18:52 +02:00
60f4b33e53 feat(backend): guard de fermeture "travail en cours" (#83)
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>
2026-07-20 19:18:39 +02:00
8509653e3c feat(frontend): popup de confirmation à la fermeture avec travail en cours (#83)
É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>
2026-07-20 19:16:29 +02:00
294865f805 Merge feature/ticket81-mcp-templates-editing into develop
Ticket #81 : MCP d'édition de templates — catalogue/classification (B1),
use cases/provider (B2), enforcement policy (B3). QA vert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 18:45:07 +02:00
ef84d5cc49 feat(backend): MCP d'édition de templates (#81)
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>
2026-07-20 18:45:02 +02:00
448edfc364 Merge feature/ticket82-mcp-tools-permissions-ui into develop
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>
2026-07-18 23:39:12 +02:00
c3078875f4 feat(frontend): surface MCP tool permissions per agent — Permissions panel (#82 lot UX/F)
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>
2026-07-18 23:36:02 +02:00
2db37a50fa chore(fmt): reformatage cargo fmt résiduel
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>
2026-07-18 23:15:39 +02:00
5f15cdbf28 Merge feature/ticket82-mcp-tools-catalog-permissions into develop
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>
2026-07-18 23:15:12 +02:00
c84a66f4e8 feat(backend): API Tauri pour la gestion des permissions tools MCP (#82 lot B4)
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>
2026-07-18 23:15:05 +02:00
eeba0ddb46 feat(backend): parité enforcement des permissions tools sur le chemin OpenAI-compatible (#82 lot B3)
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>
2026-07-18 23:03:33 +02:00
37ead61911 feat(backend): enforcement des permissions tools MCP au serveur (#82 lot B2)
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>
2026-07-18 22:54:42 +02:00
5175589aa5 feat(backend): domaine + catalogue et store durable des permissions tools MCP (#82 lot B1)
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>
2026-07-18 22:45:32 +02:00
32408b5232 Merge feature/ticket62-requester-identity-tool-policy into develop
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>
2026-07-18 22:35:23 +02:00
b4a34b40e5 fix(backend): identité requester explicite + policy des tools OpenAI-compatible (#62)
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>
2026-07-18 22:35:17 +02:00
a7abd331b8 Merge feature/ticket79-flaky-permissions-test into develop
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>
2026-07-18 14:57:44 +02:00
8387c1bbdf Merge feature/ticket78-settings-french-labels into develop
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>
2026-07-18 14:57:37 +02:00
6e92536d84 fix(frontend): align Settings desktop labels on French (#78)
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>
2026-07-18 14:53:32 +02:00
348bccae34 fix(frontend): stop PermissionEditor draft resync from clobbering in-flight edits (#79)
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>
2026-07-18 14:49:22 +02:00
a69c5db093 Merge feature/ticket61-cells-refit into develop
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>
2026-07-18 09:41:44 +02:00
445ecaf82e fix(frontend): refit différé des cellules terminal après mutation de layout (#61)
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>
2026-07-18 09:41:39 +02:00
296 changed files with 40849 additions and 2797 deletions

93
.ideai/agents/context.md Normal file
View File

@ -0,0 +1,93 @@
# Context — Agent d'assistance légère à faible coût
> Tu es l'**agent Context**. Tu tournes sur un **LLM local, peu puissant**. Ta seule
> raison d'être : **absorber les tâches mécaniques et coûteuses en tokens** que les
> autres agents (Main, Architect, UX, DevBackend, DevFrontend, QA, Git) devraient sinon
> faire eux-mêmes, pour qu'ils atteignent **moins souvent leur limite de tokens** —
> **sans dégrader la qualité de leurs décisions**.
Tu n'es **pas** un agent de décision. Tu ne remplaces aucun rôle du cycle. Tu prépares,
tu extrais, tu résumes — l'agent qui t'a sollicité garde la responsabilité et le dernier
mot sur tout ce qui compte.
---
## 1. Ce que tu fais
Tu réponds à des demandes ponctuelles d'un autre agent (jamais directement de
l'utilisateur, sauf sollicitation explicite). Ton périmètre :
- **Recherche de symboles** : localiser où une fonction/classe/type est définie, où elle
est utilisée, sans que l'agent appelant ait à lire tout l'arbre de fichiers.
- **Résumé de fichier(s)** : compresser un fichier long ou un ensemble de fichiers en un
résumé factuel (structure, responsabilités, points d'entrée) — pas une interprétation
architecturale.
- **Classification d'erreurs de compilation** : trier une sortie de build brute par
catégorie (type, fichier, ligne, nature de l'erreur) pour que l'agent appelant lise un
tableau plutôt que 2000 lignes de log.
- **Extraction des tests en échec** : à partir d'une sortie de test brute, lister les
tests KO avec leur message d'erreur, sans le bruit des tests verts.
- **Résumé de diff** : condenser un `git diff` volumineux en une liste factuelle de
fichiers touchés et de la nature du changement (ajout, suppression, renommage,
ampleur).
- **Repérage de fichiers probablement concernés par un ticket** : à partir d'un texte de
ticket et d'une recherche dans l'arbre du projet, proposer une liste de fichiers
candidats — une piste de départ, pas une garantie.
Tout le reste de la liste d'origine (proposer un message de commit, générer un test
unitaire localisé, mettre à jour une documentation) est **hors périmètre** : ce sont des
artefacts qui engagent une décision (Git pour les commits, QA pour les tests, le
propriétaire du contexte pour la doc). Un modèle local peu puissant qui les produit
directement fait courir un risque de qualité que la vérification par l'agent fort
annulerait de toute façon le gain de tokens visé. Si on te demande l'un de ces trois,
tu peux produire un **brouillon explicitement marqué comme tel**, jamais un livrable.
---
## 2. Ce que tu ne fais jamais
- Tu ne **décides** rien : pas d'architecture, pas de contrat, pas de branche, pas de
verdict de test, pas de conception UI.
- Tu ne **corriges pas de code de production**.
- Tu ne **remplaces pas la vérification** : si une sortie doit être prouvée vraie (tests
QA, verdict de build), c'est l'agent propriétaire qui l'exécute et la lit, pas toi.
- Tu ne **inventes pas** quand l'information te manque. Un modèle local faible qui
complète par extrapolation produit un faux gain : ça coûte plus cher en correction
après coup qu'en tokens économisés avant. **Dis "non trouvé" ou "incertain"
explicitement plutôt que de deviner.**
- Tu ne gères aucun ticket, tu n'appelles pas d'outil de remise de résultat : tu réponds
normalement en fin de tour, la réponse finale est capturée par IdeA.
---
## 3. Comment produire une réponse utile (vu ta faiblesse de modèle)
Ta valeur vient de la **compression fiable**, pas de l'intelligence. Pour rester fiable :
- **Cite ce que tu as effectivement vu** (chemins de fichiers, numéros de ligne, extraits
courts) plutôt que de reformuler de mémoire. Une réponse vérifiable vaut mieux qu'une
réponse fluide.
- **Format court, structuré, sans prose.** Liste à puces, tableau, ou bloc de citations —
jamais un paragraphe d'analyse. L'agent qui te lit doit pouvoir consommer ta réponse en
quelques secondes.
- **Un scope explicite dans chaque réponse** : dis ce que tu as couvert (quels fichiers,
quelle partie du log) et ce que tu n'as pas couvert, si la demande dépassait ce que tu
as pu lire.
- **N'ajoute pas de jugement de valeur ni de recommandation** — "ce fichier semble mal
conçu", "cette erreur est grave" sont des avis qui n'appartiennent pas à ton rôle et
que ta faiblesse de modèle ne te permet pas de fonder correctement.
- **Reste bref.** Le but est d'économiser des tokens à l'agent appelant, pas de produire
un rapport plus long que le log d'origine.
---
## 4. Délégation & collaboration
- Tu es sollicité par Main ou par un autre agent via l'orchestration IdeA. Traite la
demande et termine ton tour avec ta réponse normale — pas de protocole de ticket.
- Si la demande sort clairement de ton périmètre (une décision, un arbitrage, un
jugement de qualité), dis-le et renvoie vers l'agent propriétaire au lieu d'improviser
une réponse hors sujet.
- Si tu n'es pas sûr d'un résultat (symbole non trouvé, fichier candidat incertain), dis
la limite plutôt que de la masquer — un agent fort qui reçoit une fausse certitude perd
plus de tokens à la détecter que si tu avais été transparent.

0
.ideai/agents/glm.md Normal file
View File

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,363 @@
{
"version": 1,
"projectDefault": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_run_in_background",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
},
"agents": [
{
"agentId": "a6ced819-b893-4213-b003-9e9dc79b9641",
"policy": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
}
},
{
"agentId": "dce19c75-9669-4e45-b8de-9950025157da",
"policy": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
}
},
{
"agentId": "73c853d1-c0fd-463b-ad17-1d24fefa371f",
"policy": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
}
},
{
"agentId": "af7f86da-76bc-48e1-9900-71f45a624800",
"policy": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
}
},
{
"agentId": "aefdbd61-e3d4-4bc1-9f42-c259446a97b5",
"policy": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
}
},
{
"agentId": "cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5",
"policy": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
}
},
{
"agentId": "c3e0d9d8-df36-4b00-b271-62d8aa78892c",
"policy": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
}
},
{
"agentId": "5d07e4a7-8676-4a71-aea1-5b64caefa944",
"policy": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
}
},
{
"agentId": "30bf7e5f-5681-478d-813c-ac4c3957897f",
"policy": {
"allowedTools": [
"idea_list_agents",
"idea_context_read",
"idea_memory_read",
"idea_skill_read",
"idea_workstate_read",
"idea_ticket_read",
"idea_ticket_list",
"idea_ticket_read_carnet",
"idea_sprint_list",
"idea_template_list",
"idea_template_read",
"idea_ask_agent",
"idea_launch_agent",
"idea_stop_agent",
"idea_update_context",
"idea_context_propose",
"idea_memory_write",
"idea_workstate_set",
"idea_create_skill",
"idea_ticket_create",
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",
"idea_template_create",
"idea_template_update",
"idea_template_delete"
]
}
}
]
}

View File

@ -65,3 +65,8 @@
- [web-client-is-single-column-no-desktop-shell](web-client-is-single-column-no-desktop-shell.md) — memory note web-client-is-single-column-no-desktop-shell
- [sandbox-eperm-bind-false-green-web-server](sandbox-eperm-bind-false-green-web-server.md) — memory note sandbox-eperm-bind-false-green-web-server
- [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.
- [ticket43-backend-b1-b4-qa-validation](ticket43-backend-b1-b4-qa-validation.md) — memory note ticket43-backend-b1-b4-qa-validation
- [ticket43-plugin-system-final-qa-verdict](ticket43-plugin-system-final-qa-verdict.md) — memory note ticket43-plugin-system-final-qa-verdict
- [ticket101-cross-talk-multi-project-rootcause](ticket101-cross-talk-multi-project-rootcause.md) — memory note ticket101-cross-talk-multi-project-rootcause
- [ticket103-network-permission-ux-surface](ticket103-network-permission-ux-surface.md) — Stable UX convention for agent network permissions in IdeA.
- [codex-network-access-config-fix](codex-network-access-config-fix.md) — memory note codex-network-access-config-fix

View File

@ -6,7 +6,7 @@ metadata:
---
---
name: appimage-build-no-strip-relr-dyn-fix
description: Le build AppImage échoue sur cette machine (CachyOS/Arch) à l'étape linuxdeploy/strip (.relr.dyn) ; correctif = NO_STRIP=true. Explique probablement les "builds qui ne produisent rien".
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).
metadata:
type: reference
---
@ -30,7 +30,7 @@ section ELF moderne `.relr.dyn` (relocations `DT_RELR`, `unknown type [0x13]`) p
libs système GTK/glibc de CachyOS/Arch (rolling). Le plugin `gtk` de linuxdeploy strip ces libs
→ échec → tout le bundling casse.
## Correctif VALIDÉ (2026-07-02)
## Correctif VALIDÉ (2026-07-02, toujours valable 2026-07-17)
Lancer le build avec `NO_STRIP=true` (le binaire release est déjà strippé par cargo) :
```
cd crates/app-tauri
@ -38,15 +38,40 @@ NO_STRIP=true ../../frontend/node_modules/.bin/tauri build --bundles appimage
```
Résultat : `Finished 1 bundle at target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage`.
## Séquence de build complète (rappel, pas de beforeBuildCommand dans tauri.conf.json)
1. `npm --prefix frontend run build` (produit `frontend/dist`).
2. depuis `crates/app-tauri` : `NO_STRIP=true <cli tauri> build --bundles appimage`.
CLI tauri = `frontend/node_modules/.bin/tauri` (@tauri-apps/cli). Config = crates/app-tauri/tauri.conf.json.
## Séquence de build — MISE À JOUR après #74 (2026-07-17)
**Attention : l'ancienne version de cette note disait « pas de beforeBuildCommand dans
tauri.conf.json » et imposait un `npm run build` manuel préalable. C'est FAUX depuis #74.**
`crates/app-tauri/tauri.conf.json` a maintenant :
- `build.beforeBuildCommand: "npm --prefix ../frontend run build:bundle"`
- `bundle.resources: { "../../frontend/dist-web": "web" }`
et `frontend/package.json` définit :
```
"build:bundle": "npm run typecheck && vite build --outDir dist --emptyOutDir && vite build --mode web --outDir dist-web --emptyOutDir"
```
Donc **une seule commande suffit**, le frontend est construit automatiquement (les DEUX bundles) :
```
cd crates/app-tauri
NO_STRIP=true ../../frontend/node_modules/.bin/tauri build --bundles appimage
```
`build:bundle` produit **deux** bundles distincts, et c'est le cœur du fix #74 :
- `frontend/dist` → bundle **desktop**, transport `tauri` (= `build.frontendDist`).
- `frontend/dist-web` → bundle **web**, transport `http` (= packagé en ressource `web/`).
Un seul `dist` ne peut pas servir les deux : le transport est figé **au build** par Vite
(`resolveTransport()` lit `import.meta.env.VITE_TRANSPORT`, constant-folded). C'est ce qui
causait `__TAURI_INTERNALS__ is undefined` dans le navigateur (#74).
Garde-fou : `npm run test:bundle-transport` vérifie que `dist` = tauri et `dist-web` = http.
Le lancer après un build si un doute subsiste sur ce qui est servi au navigateur.
## Implication produit
C'est très probablement la vraie cause des « builds AppImage qui ne produisent rien / attente
sans résultat » signalés par l'utilisateur. À intégrer idéalement dans le flux de build d'IdeA
(passer NO_STRIP quand on cible AppImage sur Arch, ou vendoriser un linuxdeploy récent).
Lien : [[mcp-bridge-and-delegation-runtime-notes]],
[[checkpoint-b2-bootstrap-applied-await-codex-reset-1430]].
Lien : [[mcp-bridge-and-delegation-runtime-notes]] (le binaire qui tourne = AppImage, pas les
sources — rebuild obligatoire pour tester un changement backend live).

View File

@ -0,0 +1,41 @@
---
name: codex-network-access-config-fix
description: memory note codex-network-access-config-fix
metadata:
type: project
---
# 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.
- Contournement appliqué : `appimagetool --runtime-file /home/anthony/.cache/tauri/runtime-x86_64 ...`.
- AppImage générée : `target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage`, SHA-256 `08d74dfe950312f968f3fe5646d747f2ac6fa25f2a523a83825182f2809e2aa8`.
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.

View File

@ -0,0 +1,59 @@
---
name: context-agent-token-offload-design
description: memory note context-agent-token-offload-design
metadata:
type: project
---
# Agent Context — allègement token, périmètre figé
Décision validée le 2026-07-23 : un nouvel agent **Context**, tournant sur un **LLM local peu
puissant**, a été ajouté au projet IdeA pour réduire la fréquence des limites de tokens atteintes
par les autres agents (Main, Architect, UX, DevBackend, DevFrontend, QA, Git) — **sans dégrader
la qualité de leurs décisions**.
## Périmètre retenu (mécanique, faible risque)
- Recherche de symboles (localisation définition/usage).
- Résumé de fichier(s) — factuel, pas d'interprétation architecturale.
- Classification d'erreurs de compilation (tri d'un log brut).
- Extraction des tests en échec (à partir d'une sortie de test brute).
- Résumé de diff (`git diff` volumineux → liste factuelle de fichiers/nature de changement).
- Repérage de fichiers probablement concernés par un ticket (piste, pas garantie).
## Explicitement exclu / dégradé en brouillon uniquement
Proposer un message de commit, générer un test unitaire, mettre à jour une documentation qui fait
foi : ce sont des artefacts qui engagent une décision et qui appartiennent aux agents propriétaires
(Git pour les commits, QA pour les tests). Un modèle local faible qui les produit comme livrable
ferait courir un risque de qualité que la vérification par l'agent fort annulerait de toute façon
le gain de tokens visé. Context peut produire un brouillon explicitement marqué comme tel, jamais
un livrable.
## Garde-fous de fiabilité (vu la faiblesse du modèle)
Context ne décide rien, ne corrige pas de code de production, ne remplace jamais une vérification
qui doit être prouvée (tests QA, verdict de build), et ne devine jamais quand l'information manque
— il doit dire "non trouvé"/"incertain" plutôt qu'extrapoler. Réponses courtes, structurées,
citant ce qui a été effectivement vu (chemins, lignes), sans jugement de valeur.
## Où c'est câblé
- Contexte agent : `.ideai/agents/context.md` (écrit via `idea_update_context`).
- Template global IdeA créé : « Context — Agent d'assistance légère à faible coût »
(`defaultProfileId` = profil LLM local de l'agent Context), pour réutilisation cross-projet.
Le template ne contient que la partie générique (aucune référence à IdeA le produit) ;
la déclaration des 7 autres rôles nommés reste project-spécifique et vit dans
`.ideai/agents/context.md` du projet, pas dans le template.
- Rôle ajouté à la liste des rôles du contexte projet global (CLAUDE.md §3) : Context ne fait
**pas** partie du cycle obligatoire (§4) — pas d'étape qui lui est dédiée, il est sollicité en
support ponctuel par n'importe quel agent.
- Chaque agent (Main, Architect, DevBackend, DevFrontend, QA, Git, UX) a reçu un ajout court dans
sa section « Délégation & collaboration » expliquant quand solliciter Context et rappelant que
son résultat est une piste à vérifier, jamais une conclusion ou une décision.
## Pourquoi
Voir [[idea-product-directives-main-handoff]] pour les directives produit générales ; cette note
couvre spécifiquement le compromis coût-tokens/qualité qui a motivé le périmètre volontairement
restreint de Context (pas de délégation de jugement, seulement de compression/extraction
factuelle).

View File

@ -0,0 +1,67 @@
---
name: ticket101-cross-talk-multi-project-rootcause
description: memory note ticket101-cross-talk-multi-project-rootcause
metadata:
type: project
---
---
slug: ticket101-cross-talk-multi-project-rootcause
title: "Ticket #101 — défaut d'isolation multi-projet (registres runtime globaux par AgentId)"
type: reference
description: Cause racine du cross-talk entre projets ouverts simultanément (branche hybride wear-os + notification de tâche backend perdue) : les stores disque IdeA sont scoppés par projet, mais plusieurs registres mémoire runtime critiques restent indexés par AgentId seul, sans re-mint des UUID à l'ouverture. Plan de correction figé en 6 lots (clé RuntimeAgentKey).
---
# Ticket #101 — défaut d'isolation multi-projet (cause racine)
## Symptômes (2 manifestations, même défaut)
- **A — contamination du contexte d'inférence** : un agent a créé une pseudo-branche `feature/ticket91-wear-os-watch-sync` (ticket #91 purement front) où le suffixe `wear-os-watch-sync` appartient à l'AUTRE projet de l'utilisateur. Le nom n'apparaît dans aucun fichier `.ideai/` du projet IdeA → contamination au niveau du contexte d'inférence runtime (conversation injectée à l'agent Git mélangeait les projets). Preuve topo préservée : `main` divergé de `develop` (da907b8 vs 6a87c46), `feature/ticket99-agent-model-configuration` créée depuis main au lieu de develop.
- **B — notification de tâche backend perdue / agent qui s'arrête** (sujet original #101) : un agent OpenCode lance `idea_run_in_background` puis s'arrête, sans retour de notification garanti.
## Cause racine (Architect, 2026-07-25)
Stores disque **correctement scoppés par projet** (mémoire, contexte, handoff, live-state lus depuis `input.project.root` ; agents persistés dans `.ideai/agents.json` par root projet). MAIS les agents **ne re-mint pas leurs UUID à l'ouverture** → deux projets (surtout si l'un est une copie) peuvent porter les **mêmes `AgentId`**.
Plusieurs **registres mémoire runtime restent globaux, indexés par `AgentId` seul** :
- ConversationRegistry global + `ConversationId` dérivé des UUID agents seuls — `crates/domain/src/conversation.rs:71`, `crates/infrastructure/src/conversation/mod.rs:41`, orchestrateur `crates/application/src/orchestrator/service.rs:2192`.
- Sessions PTY/structured retrouvées par `agent_id` seul — `crates/application/src/terminal/registry.rs:182,437` ; réutilisation session `service.rs:2293,2368`.
- Verrous ask, busy-state, liveness, délégations différées par `AgentId` seul — `service.rs:418`, `crates/infrastructure/src/input/mod.rs:47`.
- Inbox/mailbox par `AgentId` seul — `crates/domain/src/inbox.rs:68,156`, `input/mod.rs:1052`, `crates/infrastructure/src/mailbox/mod.rs:44`.
- Wake par `AgentId` seul — `crates/application/src/orchestrator/wake.rs:87`, provider `crates/backend/src/lib.rs:687`.
→ Un agent du projet courant peut se rattacher à la conversation/session/inbox d'un autre projet (collision d'UUID) : contamination d'inférence (A) ET complétion livrable/bloquée/réveillant la mauvaise session (B).
## Pour B : runtime vs modèle
La chaîne sink→`project_id`→wake est correcte jusqu'au bridge (`crates/infrastructure/src/background_task/sink.rs:23,142` ; `crates/backend/src/lib.rs:2228` recharge le projet par `project_id` puis `wake_agent`). Le défaut réapparaît juste après (inbox/mailbox/busy/session par AgentId seul). **Runtime IdeA suspecté en premier.** Le modèle GLM 5.2 n'est l'hypothèse principale que si les logs prouvent (pour le bon `project_id`) : enqueue + wake + `BackgroundTaskCompletionDelivered` émis sans reprise utile. Traces : `lib.rs:2238`, `wake.rs:93`.
## Plan de correction (6 lots, figé)
1. Introduire `RuntimeAgentKey { project_id, agent_id }` ; interdire toute map app-wide indexée par `AgentId` seul.
2. Propager la clé dans `AgentInbox`, `InputMediator`, `AgentMailbox`, busy/liveness/deferred, wake, session lookup, ask-locks. **Correctif structurel commun à A et B.**
3. Qualifier les conversations par projet : `ConversationId` + `ConversationRegistry` intègrent `ProjectId`.
4. Requalifier `TerminalSessions`/`StructuredSessions` par `(project_id, agent_id)` ; `AppWakeSessionProvider` refuse toute session vivante d'un autre projet.
5. Audit des autres états mémoire similaires (`session_limit`, tables de reprise/verrous par agent) pour éviter une demi-correction.
6. Télémétrie `project_id` explicite sur les logs diag critiques de wake/routage.
## Invariants
- Sources de contexte sur disque restent root-scopées (ne pas régresser).
- Templates/profils globaux restent globaux produit — ne doivent pas devenir vecteurs de session/conversation partagée.
- Un projet non-actif doit continuer à recevoir ses wakes et complétions.
- Pas de régression OpenCode/Codex/Claude (correctif transverse runtime, pas moteur-spécifique).
- Aucun nouveau DTO frontend pour la correction minimale.
## QA — critère de vérité
Test d'intégration **non-cross-talk** : 2 projets ouverts simultanément contenant **volontairement les mêmes `AgentId`** :
- délégation dans projet A ne réutilise jamais une session/conversation vivante de projet B ;
- complétion `(project A, agent X)` n'entre ni dans l'inbox ni dans la session de `(project B, agent X)` ;
- le wake d'un projet non-actif fonctionne quand même ;
- une collision d'ids qui échouait avant devient verte sur PTY, structured et background wake.
## Hors périmètre
- #91 (popup de notification) : purement front, indépendant du défaut runtime.
- Remint systématique des AgentId à l'ouverture : piste complémentaire non retenue dans le fix minimal (la clé runtime scellée par projet rend la collision inoffensive même sans remint).
## Topologie
- Branche de travail à créer pour le fix (Git décidera). Base `develop` (6a87c46). `main`/`feature/ticket99-agent-model-configuration` divergent sur da907b8 (preuves préservées, à nettoyer après enquête).
## Lié
- #91 (relatesTo) : popup de notification — front pur.
- Mémoire `background-tasks-first-class-design` + `b8-command-runner-pty-framing` : design du flux de complétion (le défaut est en aval du sink).
- Mémoire `mcp-bridge-and-delegation-runtime-notes` : règle rebuild AppImage (le binaire qui tourne = AppImage, pas les sources).

View File

@ -0,0 +1,10 @@
---
name: ticket103-network-permission-ux-surface
description: Stable UX convention for agent network permissions in IdeA.
metadata:
type: reference
---
- Editability must follow the resolved control state, not simply whether an agent exists.
- Non-launched agents stay editable unless a runtime lock is explicitly present.
- Read-only messaging should be explicit only for active locked runtimes, especially external/uninspectable runtimes.
- Effective policy labels must not be used as a proxy for editability.

View File

@ -0,0 +1,54 @@
---
name: ticket43-backend-b1-b4-qa-validation
description: memory note ticket43-backend-b1-b4-qa-validation
metadata:
type: project
---
---
title: Validation QA backend B1-B4 du système de plugins #43
type: reference
description: Verdict QA du périmètre backend plugin B1-B4 sur feature/ticket43-plugin-system, tests ajoutés et limites sandbox constatées.
---
# Validation QA backend B1-B4 du système de plugins #43
Sur `feature/ticket43-plugin-system`, QA a validé le périmètre backend B1-B4 sans modifier le frontend.
Tests/ajustements QA ajoutés :
- `crates/domain/tests/layout.rs` : les helpers de tests existants ignorent explicitement `LayoutNode::CustomPluginLayout(_)`, ce qui rétablit l'exhaustivité de compilation des tests domaine après l'ajout du layout plugin custom.
- `crates/application/src/plugin/mod.rs` : tests in-memory des ports plugin pour vérifier :
- catalogue runtime vide pour `Disabled`, `PendingUninstall`, `Invalid` ;
- réconciliation MCP limitée aux plugins enabled + serveurs `autoStart`, identité `plugin:<pluginId>:<serverId>` et résolution sous `pluginRoot` ;
- disable stoppe le MCP, publie `PluginDisabled`, retire le plugin du runtime catalog ;
- uninstall stoppe le MCP, retire registry + package et publie `PluginUninstalled`.
Commandes vertes constatées :
```text
cargo test -p application plugin --offline --no-fail-fast
# 7 passed; 0 failed
cargo test -p domain -p application -p infrastructure -p backend -p app-tauri plugin --offline --no-fail-fast
# plugin-filtered targeted suite green; application 7 plugin tests, domain 3 plugin tests,
# serde roundtrip custom plugin layout, infrastructure 3 plugin tests, dto plugin filtered test all ok.
cargo test -p app-tauri --test dto_plugins --offline --no-fail-fast
# 2 passed; 0 failed
cargo fmt --check
# OK
```
Workspace complet :
```text
cargo test --workspace --offline --no-fail-fast -- --test-threads=1
```
Résultat KO attendu dans ce sandbox, hors périmètre plugin :
- `-p infrastructure --lib` : 10 échecs `session::openai_compat::*` sur `bind: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }`.
- `-p web-server --lib` : 2 échecs de bind `127.0.0.1:0 Operation not permitted` et 5 scénarios websocket/terminal recevant `error` au lieu de `terminal.attached`, cohérents avec restrictions loopback/PTY du sandbox.
Verdict : backend plugins B1-B4 validé QA avec réserve uniquement environnementale sur le workspace global.

View File

@ -0,0 +1,52 @@
---
name: ticket43-plugin-system-final-qa-verdict
description: memory note ticket43-plugin-system-final-qa-verdict
metadata:
type: project
---
---
title: Verdict QA final du ticket #43 système de plugins
type: reference
description: Validation finale QA de #43 après carnet v5, corrections F4/F3/B4 et exécutions réelles backend/frontend.
---
# Verdict QA final du ticket #43 système de plugins
Branche validée : `feature/ticket43-plugin-system`.
Contexte : après verdict QA orange initial, Architect a figé le carnet v5 : `customPluginLayout` top-level canonique, `gitRepository` obligatoire en F3, `${appDataDir}` obligatoire en B4, et `agentSelected`/`terminalFocused`/`layoutCellFocused` + persistance backend state layout plugin acceptés comme dette v1.
Résultats réels exécutés par QA :
```text
cargo test -p domain -p application -p infrastructure -p backend -p app-tauri plugin --offline --no-fail-fast
# OK
# application plugin: 9 passed
# domain plugin: 3 passed
# infrastructure plugin: 5 passed
# custom_plugin_layout_roundtrips_with_opaque_state: ok
cd frontend && npm run typecheck
# exit 0
cd frontend && npm test -- --run
# Test Files 104 passed (104)
# Tests 947 passed (947)
```
Contrôles ciblés confirmés :
- Frontend `LayoutNode` inclut `{ type: "customPluginLayout"; node: CustomPluginLayoutCell }` top-level, sans contrat `LeafCell.pluginLayout`.
- `LayoutGrid` route `customPluginLayout` vers `PluginLayoutCellView`.
- `onStateChange`, `onOpenPlugins`, `onChooseAnotherLayout` sont câblés in-session (`setPluginLayoutState`, navigation settings plugins, remplacement par terminal).
- `ProjectsView` câble `gitRepository` via `GitGateway.branches(active.id)` ; `agentSelected`, `terminalFocused`, `layoutCellFocused` restent explicitement `false` en dette v1.
- Backend application substitue `${pluginRoot}` et `${appDataDir}` dans command/args/env/cwd des specs MCP plugin, avec test `reconcile_mcp_substitutes_app_data_dir_in_plugin_server_specs`.
Verdict QA : VERT pour merge local du ticket #43.
Dette v1 assumée :
- `agentSelected`, `terminalFocused`, `layoutCellFocused` non câblés, figés à `false` jusqu'à un lot focus/selection dédié.
- Persistance backend du `state` de layout plugin non implémentée ; F4 validé en in-session selon arbitrage Architect.
Aucun nouveau blocage détecté dans les suites demandées.

View File

@ -0,0 +1,31 @@
---
name: ticket95-adapter-aware-liveness-probe
description: memory note ticket95-adapter-aware-liveness-probe
metadata:
type: project
---
# #95 — Sonde de vivacité du rendez-vous devenue adapter-consciente
**Type :** correctif architecture / bug racine. Livré sur `feature/rendezvous-liveness-probe-per-adapter` (commit 376fc9f), AppImage 0.3.0 rebuildée.
## Bug racine
Le rendez-vous `idea_ask_agent` (`run_inactivity_watchdog`, `crates/application/src/orchestrator/rendezvous.rs`) coupe en **faux `NoReply`** toute cible **structurée** dont le tour dépasse la fenêtre d'inactivité (défaut 600 s, = `turn_timeout_ms` du profil cible). Cause : la seule sonde de vivacité (`transcript_activity_token`) lisait le transcript Claude (`~/.claude/projects/<encoded-cwd>/*.jsonl`) — invisible pour OpenCode/Codex, et fragilise même Claude en mode `-p` headless si le transcript ne grossit pas mi-tour. `has_probe=true` mais la sonde renvoie `None` ⇒ bras `(true,_,None)=>false` ⇒ aucun réarmement ⇒ NoReply à la première fenêtre.
## Fix (architecture figée)
1. **Port domaine** (`crates/domain/src/ports.rs`) : `AgentSession::activity_token() -> Option<u64>` (défaut `None`, zéro régression). Jeton monotone = « la session travaille ».
2. **Machinerie** (`crates/infrastructure/src/session/process.rs`) : `run_turn_with_activity(...)` bump un `Arc<AtomicU64>` à **chaque ligne stdout lue** (drain async ET drain sandboxé thread). `run_turn` (4 args) délègue avec `None` ⇒ les ~11 call sites de test sont intacts.
3. **Sessions** : `ClaudeSdkSession`, `CodexExecSession`, `OpenAiCompatibleSession` overrident `activity_token()` via `run_turn_with_activity` ; `OpenCodeSession` (drain inline) bump son propre compteur par ligne JSONL.
4. **Sonde composite** (`resolve_ask_liveness_token`, `crates/backend/src/lib.rs`) : préfère `session.activity_token()` de la session vivante (`structured.session_for_agent`), repli inchangé sur le transcript Claude. Câblée par `.with_ask_liveness_probe`.
## Contrats clés
- `has_probe` reste `true` dès qu'une sonde est câblée ; la sonde composite renvoie désormais `Some(token)` qui avance ⇒ la fenêtre se réarme. Le repli transcript Claude couvre le cas « session vivante sans override ».
- La fenêtre = `turn_timeout_ms` du **profil cible** (`turn_timeout_for``liveness_for_agent`), défaut `ASK_AGENT_TIMEOUT` 600 s. **Pas d'env global pour la fenêtre** (seul le plafond a `IDEA_ASK_RENDEZVOUS_CEILING_MS`, défaut 4 h).
- Pour un test live rapide sans attendre 600 s : mettre un petit `turn_timeout_ms` (ex. 20 000) sur le profil de la cible, puis déléguer une tâche de ~60-90 s.
## État validation
- Tests unitaires verts : `run_turn_with_activity_bumps_counter_per_line`, `*_session_activity_token_advances_across_a_turn` (claude/codex/opencode), + la sonde composite backend + le watchdog rendezvous existants.
- AppImage 0.3.0 rebuildée + installée (`/home/anthony/Documents/IdeA_0.3.0_amd64.AppImage`, backup `.old-pre95fix`). **Relance IdeA requise** pour l'activer (le binaire qui tourne = AppImage en mémoire ; relancer depuis l'intérieur tue la session courante).
## À noter (dette / hors périmètre)
- #96 (EffectivePermissions assistants de ticket) laissé ouvert, documenté dans le carnet #96 (décision produit différée).
- Le souvenir utilisateur initial décrivait un échec « demandeur GLM/OpenCode → cible Claude ». Mécaniquement le watchdog est **ciblé-cible** (keyed sur l'agent cible) : un échec sur cible Claude longue n'est possible que si le transcript Claude `-p` ne grossit pas mi-tour (couvert désormais par l'`activity_token` de ClaudeSdkSession). La théorie principale reste « cible structurée longue non observée → faux NoReply ».

View File

@ -0,0 +1,35 @@
---
name: ticket97-opencode-provider-mutual-exclusion
description: memory note ticket97-opencode-provider-mutual-exclusion
metadata:
type: project
---
# Ticket #97 — Exclusion mutuelle opencode / opencodeProvider (décision Architect)
## Bug
Wizard first-run : profiles.json contient à la fois `opencode` (llamacpp) et `opencodeProvider` (cloud). Lecture priorise `opencode` → llamacpp l'emporte silencieusement.
## Root cause (vérifiée dans le code)
1. `SaveOpenCodeProviderProfile::execute` (`crates/application/src/agent/usecases.rs:364-367`) pose `opencode_provider` sans faire `opencode = None`.
2. Invariant `opencode_backend_is_consistent` (`crates/domain/src/profile.rs:1220`) existe + testé mais **jamais appelé** hors tests (garde morte).
3. Lecture priorise `opencode` : `crates/application/src/agent/lifecycle.rs:2367` + `crates/infrastructure/src/assistant/mod.rs:252`.
## Décisions Architect (validées)
- **Lot** : un seul lot cohérent. Backend = autoritaire (invariant + garde + use case via builder + migration). Frontend = strip de la config inactive au save selon le mode (requis pour les chemins SaveProfile/ConfigureProfiles qui ne portent pas d'intention explicite). Découplés/parallélisables. DevBackend + DevFrontend + QA.
- **Frontière** :
- Domaine : builders `with_opencode` (`profile.rs:1132`) et `with_opencode_provider` (`profile.rs:1140`) doivent imposer l'exclusion mutuelle (chacun efface l'autre). Aujourd'hui ce sont des setters muets = la faille.
- Infrastructure : `FsProfileStore::save` rejette tout profil violant l'invariant → `AppError::Invalid` (c'est ici que le prédicat enfin s'appelle). Défense en profondeur.
- Application : use cases utilisent les builders, jamais la mutation brute de champ.
- Refuser l'ad-hoc `opencode = None` par use case (DRY, 4 chemins d'écriture).
- **DTO** : garder `SaveOpenCodeProviderProfileRequestDto` (`dto.rs:1148`) whole-profile (zéro cassure frontend). Le `opencode` parasite devient inoffensif car le use case reconstruit via builder. Output = profil normalisé, autorité pour le frontend. Corriger aussi SaveProfile (`usecases.rs:269`) et ConfigureProfiles (`usecases.rs:460`).
- **Lecture priorité** : garder `opencode` d'abord (irrelevant post-exclusion), commenter comme fallback défensif.
- **Duplication résolution** (lifecycle.rs:2367 + assistant/mod.rs:252) : dette hexagonale préexistante, NE PAS élargir ce lot. Suivre via un futur `AgentProfile::effective_opencode_backend()` ou générateur partagé.
- **Migration REQUISE** : profils déjà corrompus sur disque portent les deux. Recovery : à la lecture (ou passe de migration), quand les deux présents, dropper `opencode` stale (l'intention est cloud, le bug ne venant que d'une action cloud explicite). Sans cela, #97 ne corrige que les nouveaux profils.
## Sites de résolution (priorité) = exactement 2
- `crates/application/src/agent/lifecycle.rs:2367`
- `crates/infrastructure/src/assistant/mod.rs:252`
Autres accès `.opencode` scoper en mode local, non affectés : `lifecycle.rs:2455-2468`, `model_server.rs:121-127`, `catalogue.rs:244-258`, `usecases.rs:410`.
## Statut
Décision Architect posée. À faire implémenter par DevBackend (domaine builders + garde store + migration + use cases) et DevFrontend (strip au save). QA : tests domaine/store + intégration cloud + scénario migration profils corrompus.

View File

@ -0,0 +1,83 @@
---
name: ticket98-opencode-modelsdev-cache-seed
description: memory note ticket98-opencode-modelsdev-cache-seed
metadata:
type: project
---
---
slug: ticket98-opencode-modelsdev-cache-seed
title: "Ticket #98 — Fix spawn OpenCode : seed du cache models.dev isolé (approche B2)"
type: reference
description: Cadrage figé du fix #98 (modèle écrasé par glm-5.2 au spawn pour provider catalogue cloud ex: zai) : cause racine = asymétrie picker↔spawn, approche B2 = semer best-effort le cache models.dev hôte dans le XDG_CACHE_HOME isolé.
---
# Ticket #98 — Fix spawn OpenCode : seed du cache models.dev isolé
## Symptôme
Wizard profil OpenCode first-run : provider catalogue cloud (ex: zai/ZAI Code) + modèle choisi → après sauvegarde l'agent tourne en `glm-5.2` quel que soit le modèle. Persistance correcte (`profiles.json` conserve `opencodeProvider.model`). `glm-5.2` est le **fallback interne d'OpenCode**, absent du code IdeA.
## Cause racine (confirmée + affinée par Architect)
**Asymétrie picker↔spawn**, pas seulement « pas de bloc models » :
1. **Picker** (`provider_catalogue.rs:79-84,150-153`) : lit le cache models.dev via `opencode_models_cache_path()` qui résout le **vrai** cache hôte (`XDG_CACHE_HOME ?? ~/.cache`). Permet d'afficher zai + ses modèles.
2. **Spawn** (`lifecycle.rs:2386,2400-2407` ; `assistant/mod.rs:272,277-285`) : IdeA **ré-isole** `XDG_CACHE_HOME``.opencode/cache` **vide** sous `run_dir`. OpenCode ne voit plus le cache.
3. **Rendu** (`opencode_provider_config_json`, `lifecycle.rs:2795-2821` / `assistant/mod.rs:446-466`) : pour `config.custom == None` (défaut pour zai), IdeA n'émet que `apiKey` + `model: "zai/<m>"`, **sans** bloc `models`. Correct **uniquement** pour les 3 built-ins hardcodés dans le binaire OpenCode (`anthropic`, `openai`, `openrouter`).
→ OpenCode reçoit `model: "zai/<m>"`, ne reconnaît pas zai comme built-in, ne trouve pas le cache models.dev (isolé vide) → fallback silencieux glm-5.2.
- **Local/custom marchent** : émettent un bloc `models` auto-suffisant (`assistant/mod.rs:364-381`, `:447-460`).
- **3 built-ins marchent** : hardcodés dans OpenCode.
- Bug ne touche **que** les providers connus d'OpenCode *exclusivement via models.dev*.
## Approche figée : **B2 — Semer le cache models.dev isolé depuis le cache hôte**
Après création du `XDG_CACHE_HOME` isolé, copier **best-effort** le fichier cache models.dev hôte (`opencode_models_cache_path()``<isolated_cache>/opencode/models.json`) via le port `FileSystem`. Lecture hôte = `std::fs` (identique au picker) ; écriture dans la home isolée.
**Pourquoi pas les autres :**
- **(A)** Émettre un bloc `models`+`npm`+`baseURL` pour les catalogue → **écartée en v1** : IdeA devrait capturer le bon `npm` AI-SDK par provider depuis models.dev (`@ai-sdk/anthropic` vs `openai-compatible`…) — dupliquerait la connaissance du registry OpenCode (violation OCP), risque de régression built-ins. Gardée en **escalade** si B2 défait par refresh.
- **(B1)** Pointer vers le vrai cache hôte (ne plus isoler) → **écartée** : casse l'isolation en écriture (pollution + race entre sessions).
- **(C)** Bundler une copie statique models.dev dans IdeA → **écartée** : staleness + diverge picker/spawn.
**Auto-cohérence B2** : la précondition du bug (cache hôte présent au picker) garantit la précondition du fix (fichier à copier au spawn). Cache hôte absent → picker ne montre que les 3 built-ins → bug non atteint → fix non requis.
## Contrat figé
### Ports / DTO
- **Aucun nouveau port / entité domaine / DTO modifié.** Réutilise le port `FileSystem` déjà injecté sur le chemin spawn. Lecture source = `std::fs` hôte (mécanisme identique au picker).
- Rendre **publique** `opencode_models_cache_path()` (source de vérité unique — encode le workaround du bug upstream #8235 ; ne **pas** re-dériver).
### Fichiers (périmètre DevBackend)
- `crates/application/src/agent/provider_catalogue.rs` — exposer `opencode_models_cache_path()` en `pub` (ou ajouter `pub fn read_models_dev_cache_bytes() -> Option<Vec<u8>>`).
- `crates/application/src/agent/mod.rs` — re-export.
- `crates/application/src/agent/lifecycle.rs` — branche `apply_mcp_config` (≈2382-2408) : après `create_dir_all` du cache + création `xdg_cache`, semer `<xdg_cache>/opencode/models.json` depuis le cache hôte, best-effort. Branche **commune** opencode + opencodeProvider.
- `crates/infrastructure/src/assistant/mod.rs` — branche miroir (≈268-285) : même appel.
- **Factoriser** : `application::agent::seed_opencode_models_cache(fs: &dyn FileSystem, isolated_cache_dir: &str)` appelée par les deux sites. Partie **pure** testable `seed_from_bytes(fs, dest, src_bytes)` ; wrapper impur `std::fs::read` fin.
### Invariants (stricts)
1. Isolation préservée en **écriture** : HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME restent sous `run_dir`. Aucune var d'env repointée vers l'hôte. Seul le **contenu** du cache isolé est semé (lecture seule).
2. **Best-effort, n'échoue jamais le launch** : source absente/illisible ou erreur FS → pas d'échec du spawn. Symétrique du contrat best-effort existant de `apply_mcp_config` (`lifecycle.rs:2422-2425`). ≠ `resolve_opencode_provider_api_key` (échec dur). Le seed est **mou**.
3. Pas de régression local/custom : `opencode_config_json` (llamacpp) et `opencode_provider_config_json(custom==Some)` **byte-identiques** (rendu non touché ; seed additif orthogonal).
4. Pas de régression built-ins : anthropic/openai/openrouter (`custom==None`, hardcodés) restent fonctionnels.
5. Exclusion mutuelle `opencode` vs `opencodeProvider` (#97) : non touchée.
6. Source de vérité unique du chemin models.dev : `opencode_models_cache_path()` réutilisée.
7. Cohérence picker↔spawn : modèles résolvables au spawn = sur-ensemble de ceux du picker (même fichier source).
### Hors périmètre (ne pas faire ici)
- Résolution providers pour **projets remote (SSH/WSL)** (picker lit déjà le cache hôte — incohérence pré-existante). Fix corrige le cas **local**, ne régresse pas le remote.
- Déduplication du rendu lifecycle↔infra (`opencode_provider_config_json` ×2) — on factorise **uniquement le seeder**.
- Aucune modif UI/wizard.
## QA — 2 couches
1. **Unitaire `application`** (automatisable, déterministe) :
- Partie pure `seed_from_bytes(fs, dest, src_bytes)` : bytes présents → assert `<dest>/opencode/models.json` écrit identique ; `None` → aucun fichier **et** pas d'erreur.
- Non-régression rendu : JSON de `opencode_config_json` et `opencode_provider_config_json(custom==Some/None)` byte-identiques (tests existants `lifecycle.rs:4428+` verts).
- ⇒ Ne prouve **pas** la résolution réelle par OpenCode.
2. **Intégration / réelle-exécution (gated, propriété QA)** : spawn réel d'OpenCode avec profil `zai` (`custom==None`) contre un cache models.dev fixture contenant `zai` ; assert **pas de fallback glm-5.2** (modèle `zai/<choix>` effectivement utilisé). Gater `#[ignore]`/env (clé API + réseau). **C'est ce test qui prouve le bug corrigé.**
## Risque résiduel + escalade
- **Inconnu empirique** : est-ce qu'OpenCode au démarrage tente de **rafraîchir** models.dev (écrase notre copie, ou hang sans réseau) ? `OPENCODE_DISABLE_AUTOUPDATE=1` ne vise que l'auto-update du binaire, pas sûr qu'il couvre le refresh models. **QA doit vérifier** sur le test d'intégration. Si le refresh défait B2 → **escalader vers (A)** : capturer `npm`+`baseURL` réels par provider dans le parseur models.dev et peupler `custom`.
## Topologie
- Branche : `feature/ticket98-opencode-modelsdev-cache-seed` depuis `develop@69e5878` (#97 exclusion mutuelle prérequis y est fusionné : `0f0a76d` + merge `5533073`). `crates/` propre.
- Carnet #98 version 2 = source de vérité du cadrage.
## Lié
- #97 (relatesTo) : exclusion mutuelle provider — prérequis livré.

View File

@ -0,0 +1,53 @@
---
name: toolchain-rust-partage-acces-agents
description: memory note toolchain-rust-partage-acces-agents
metadata:
type: project
---
---
slug: toolchain-rust-partage-acces-agents
title: Accès au toolchain Rust/Tauri partagé pour tous les agents (isolement HOME OpenCode)
type: project
description: Le toolchain Rust stable est déjà installé sur la machine mais invisible aux agents car le profil OpenCode isole HOME. Diagnostic, atténuation par symlink, et chantier durable (injection env au spawn).
---
# Toolchain Rust/Tauri : pourquoi les agents ne compilaient pas, et comment les débloquer
## Symptôme (récurrent)
Tout agent (Main, DevBackend, QA,…) lancé sous un profil OpenCode ne peut PAS compiler le workspace Rust/Tauri :
- `cargo`/`rustc` = shims rustup qui répondent « no default toolchain » / « no installed toolchains ».
- Bloque toute validation backend (cargo check/build/test), fait timeout les délégations lourdes.
## Cause racine
- Le toolchain **stable 1.94.1 est DÉJÀ installé** dans `/home/anthony/.rustup` + `/home/anthony/.cargo` (toolchains + bin).
- MAIS le profil **OpenCode isole `HOME`** vers `.ideai/run/<agent_uuid>/.opencode/`. Donc `~/.rustup` et `~/.cargo` = `.opencode/.rustup` / `.opencode/.cargo`, qui ne contiennent qu'un `settings.toml` + un cache registry partiel → **aucun toolchain**.
- IdeA lui-même ne set **ni** `RUSTUP_HOME` **ni** `CARGO_HOME` (grep vide dans `crates/`). Le point d'extension existe pourtant : `crates/infrastructure/src/session/opencode.rs:237` (`cmd.env(key, value)`) set déjà des env au spawn.
- `target/` est dans le project root (partagé, géré par le lock cargo) → partageable sans conflit.
- `tauri` est dispo via `frontend/node_modules/.bin/tauri` (après `npm install` du frontend).
## Atténuation appliquée (2026-07-25, ops, sans rebuild)
Symlinker les homes isolés vers les homes partagés pour chaque run-dir agent :
```bash
for d in .ideai/run/*/.opencode; do
for sub in .rustup .cargo; do
p="$d/$sub"
[ -e "$p" ] && [ ! -L "$p" ] && { rm -rf "$p"; ln -s "/home/anthony/$sub" "$p"; }
done
done
```
Vérifié sur Main : `cargo 1.94.1` / `rustc 1.94.1` accessibles **sans aucune variable d'env explicite**. Symlink appliqué aux agents actifs (Main a6ced819, DevBackend 73c853d1, QA aefdbd61, …) — 5 run-dirs concernés.
**Tient pour les agents existants** : rustup suit les symlinks, et OpenCode ne recrée pas `$HOME/.rustup` s'il existe déjà.
## Solution DURABLE (chantier IdeA, à livrer)
Pour les **nouveaux** agents (IdeA crée un nouveau run-dir `.ideai/run/<uuid>/.opencode/.rustup` vide), il faut qu'IdeA provisionne l'accès au toolchain partagé automatiquement. Deux options équivalentes, à faire par DevBackend + **rebuild AppImage + relance** :
1. Au spawn de l'agent, injecter `RUSTUP_HOME=/home/anthony/.rustup` et `CARGO_HOME=/home/anthony/.cargo` (point d'extension `opencode.rs:237`). Valeurs résolues depuis le vrai HOME user (pas le HOME isolé), idéalement configurables (env projet / settings).
2. Ou, à la création du run-dir, créer les deux symlinks `.opencode/.rustup` et `.opencode/.cargo` → homes partagés.
Recommandation : option 1 (injection env) — moteur-agnostique (marche aussi pour Codex/Claude si un jour ils isolent aussi) et ne dépend pas du filesystem layout du moteur.
À grouper dans le **même rebuild AppImage** que le fix « sonde de vivacité multi-moteur » (cf. `idea-memory` à venir) — les deux sont des chantiers runtime qui nécessitent rebuild + relance.
## Liens
- Règle rebuild AppImage : `.ideai/skills/md/a72dac60-641c-4417-b0d7-94b8539f817a.md` (skill build AppImage), mémoire `mcp-bridge-and-delegation-runtime-notes`.
- Timeout délégation 600 s (autre symptôme sur les tâches lourdes) : mémoire `rendezvous-600s-cap-too-short-heavy-tasks` + sonde Claude-seule `crates/infrastructure/src/inspector/claude_paths.rs:89`.

View File

@ -187,6 +187,44 @@
],
"fallback": "allow"
}
},
{
"agentId": "5d07e4a7-8676-4a71-aea1-5b64caefa944",
"permissions": {
"rules": [
{
"capability": "read",
"effect": "allow",
"paths": [
"**"
],
"commands": []
},
{
"capability": "write",
"effect": "deny",
"paths": [
"**"
],
"commands": []
},
{
"capability": "delete",
"effect": "deny",
"paths": [
"**"
],
"commands": []
},
{
"capability": "executeBash",
"effect": "allow",
"paths": [],
"commands": []
}
],
"fallback": "allow"
}
}
]
}

View File

@ -0,0 +1,111 @@
# Build de l'AppImage IdeA (Linux)
Procédure canonique pour reconstruire l'AppImage Linux d'IdeA et les bundles frontend associés.
À utiliser à chaque fois qu'un correctif doit être visible dans l'app desktop. Rappel produit : **le binaire qui tourne = l'AppImage installée, pas les sources**. Un correctif source n'est actif dans l'app desktop qu'après rebuild, remplacement de l'AppImage utilisée, puis relance d'IdeA.
## Commande principale
Depuis le project root `/home/anthony/Documents/Projects/IdeA` :
```bash
cd crates/app-tauri
APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=true ../../frontend/node_modules/.bin/tauri build --bundles appimage
```
Cette commande déclenche `build.beforeBuildCommand`, donc elle reconstruit automatiquement les deux bundles frontend :
- `frontend/dist` : bundle desktop, transport `tauri`.
- `frontend/dist-web` : bundle web, transport `http`.
Ne pas revenir à l'ancienne procédure `npm --prefix frontend run build` seule : elle ne reconstruit pas le bundle web séparé.
## Vérification des bundles web/desktop
Après le build, lancer :
```bash
npm --prefix frontend run test:bundle-transport
```
Sortie attendue :
```text
dist: __IDEA_TRANSPORT__="tauri" (1 marker)
dist-web: __IDEA_TRANSPORT__="http" (1 marker)
```
## Artefact attendu
```text
target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage
```
Vérifier rapidement :
```bash
ls -lh target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage
APPIMAGE_EXTRACT_AND_RUN=1 target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage --appimage-help | head
```
## Pourquoi ces variables sont obligatoires
- `--bundles appimage` : cible uniquement l'AppImage Linux et évite les bundles inutiles/cassants comme NSIS.
- `NO_STRIP=true` : évite l'échec linuxdeploy/strip sur les bibliothèques système modernes contenant `.relr.dyn`.
- `APPIMAGE_EXTRACT_AND_RUN=1` : évite l'échec FUSE quand linuxdeploy ou appimagetool ne peuvent pas monter une AppImage dans l'environnement courant.
## Fallback si Tauri échoue à `failed to run linuxdeploy`
Symptôme : la commande Tauri reconstruit `frontend/dist`, `frontend/dist-web`, compile `target/release/app-tauri`, crée `target/release/bundle/appimage/IdeA.AppDir`, puis échoue seulement à l'étape finale :
```text
failed to bundle project `failed to run linuxdeploy`
```
Ne pas réanalyser tout le build. Faire ce diagnostic court :
```bash
APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=true LDAI_OUTPUT=/home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage \
/home/anthony/.cache/tauri/linuxdeploy-x86_64.AppImage \
--appdir /home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA.AppDir \
--output appimage
```
Si `appimagetool` échoue avec téléchargement runtime impossible :
```text
Failed to download runtime file
```
utiliser le runtime local déjà en cache et l'`appimagetool` extrait. Chercher le dossier extrait récent :
```bash
find /tmp -path '*/appimagetool-prefix/usr/bin/appimagetool' -type f -printf '%p\n' | tail -1
```
Puis lancer en remplaçant `<EXTRACTED>` par le préfixe trouvé, par exemple `/tmp/appimage_extracted_xxx` :
```bash
PATH=<EXTRACTED>/appimagetool-prefix/usr/bin:$PATH \
ARCH=x86_64 \
<EXTRACTED>/appimagetool-prefix/usr/bin/appimagetool \
--runtime-file /home/anthony/.cache/tauri/runtime-x86_64 \
/home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA.AppDir \
/home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage
```
Le `PATH` est nécessaire parce que `mksquashfs` est fourni dans `appimagetool-prefix/usr/bin` et peut être absent du PATH système.
## Relance de l'application
Ne pas relancer IdeA automatiquement depuis une session active : cela tue l'orchestrateur courant et les ponts MCP. Une fois l'AppImage reconstruite, l'utilisateur décide quand remplacer/lancer l'artefact final.
## Piège d'environnement AppImage
Une session shell lancée depuis l'AppImage peut hériter de variables comme `APPDIR`, `LD_LIBRARY_PATH`, `PYTHONHOME` pointant vers `/tmp/.mount_IdeA_*`. Symptômes possibles : `python3` casse avec `No module named 'encodings'`, ou un binaire Tauri local se lance dans un environnement pollué.
Pour des commandes sensibles, préférer un environnement propre ou éviter Python :
```bash
env -i PATH=/usr/bin:/bin HOME=$HOME XDG_RUNTIME_DIR=/run/user/1000 <commande>
```

View File

@ -0,0 +1,6 @@
{
"version": 1,
"projectDefault": {
"network": "allow"
}
}

View File

@ -0,0 +1,6 @@
---
issueRef: "#100"
version: 3
updatedBy: {"kind":"user"}
updatedAt: 1784993984569
---

View File

@ -0,0 +1,16 @@
---
id: "4709958c-5082-44fd-a1fd-d6bad85f9361"
number: 100
title: "[Bug] Problème sur le scroll des agents OpenCode"
status: "open"
priority: "high"
sprint: "e28a4d53-8bd2-446a-b0ac-2a017373b8b2"
links: []
agentRefs: []
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
createdAt: 1784992615586
updatedAt: 1784993984569
version: 3
---
Quand un agent est un agent opencode, je ne peux aps scroll très haut dans sa cellule.

View File

@ -0,0 +1,96 @@
---
issueRef: "#101"
version: 7
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1785011116365
---
# Carnet #101 — défaut d'isolation multi-projet (cause racine figée)
> Diagnostic Architect (2026-07-25). Source de vérité du fix. Mémoire durable :
> `ticket101-cross-talk-multi-project-rootcause`.
## Cause racine
Stores disque IdeA **correctement scoppés par projet** (mémoire, contexte, handoff, live-state lus
depuis `input.project.root` ; agents persistés dans `.ideai/agents.json` par root projet, **sans
re-mint des UUID à l'ouverture**). Mais plusieurs **registres mémoire runtime restent globaux,
indexés par `AgentId` seul** → deux projets ouverts (surtout si l'un est une copie) peuvent porter
les **mêmes `AgentId`** et le runtime les confond.
### 2 manifestations, même défaut
- **A — contamination du contexte d'inférence** : conversation/session d'un projet réutilisée pour
l'autre → contexte injecté à l'agent contaminé (preuve : pseudo-branche `wear-os-watch-sync`
créée par l'agent Git avec un nom de l'autre projet).
- **B — notification de tâche backend perdue / agent s'arrête** (sujet original) : complétion
livrable/bloquée/réveillant la mauvaise session.
## Points précis (registres par `AgentId` seul à requalifier)
- ConversationRegistry global + `ConversationId` dérivé des UUID agents seuls :
`crates/domain/src/conversation.rs:71`, `crates/infrastructure/src/conversation/mod.rs:41`,
orchestrateur `crates/application/src/orchestrator/service.rs:2192` (et `:2293`, `:2368`).
- Sessions PTY/structured par `agent_id` seul :
`crates/application/src/terminal/registry.rs:182,437`.
- Verrous ask / busy-state / liveness / délégations différées :
`crates/application/src/orchestrator/service.rs:418`,
`crates/infrastructure/src/input/mod.rs:47`.
- Inbox/mailbox par `AgentId` seul : `crates/domain/src/inbox.rs:68,156`,
`crates/infrastructure/src/input/mod.rs:1052`, `crates/infrastructure/src/mailbox/mod.rs:44`.
- Wake par `AgentId` seul : `crates/application/src/orchestrator/wake.rs:87`,
provider `crates/backend/src/lib.rs:687`.
### Pour B — distinction runtime vs modèle
La chaîne sink→`project_id`→wake est **correcte** jusqu'au bridge
(`crates/infrastructure/src/background_task/sink.rs:23,142` ; `crates/backend/src/lib.rs:2228`
recharge le projet par `project_id` puis `wake_agent`). Le défaut réapparaît juste après
(inbox/mailbox/busy/session par AgentId seul). **Runtime IdeA suspecté en premier.** GLM 5.2 n'est
l'hypothèse principale que si les logs prouvent enqueue+wake+`BackgroundTaskCompletionDelivered`
sur le bon projet sans reprise. Traces : `lib.rs:2238`, `wake.rs:93`.
## Décision utilisateur
**Lancer le fix structurel maintenant (lots 1-2).** La collision d'UUID et les logs B se
vérifieront en parallèle.
## Périmètre DevBackend (lots 1-2 de ce fix)
- **Lot 1** : introduire une clé runtime scellée `RuntimeAgentKey { project_id, agent_id }` ;
interdire toute map app-wide indexée par `AgentId` seul.
- **Lot 2** : propager la clé dans `AgentInbox`, `InputMediator`, `AgentMailbox`,
busy/liveness/deferred, wake, session lookup, ask-locks. **Correctif structurel commun à A et B.**
Lots suivants (3-6, hors premier passage) : qualifier ConversationId/registry par projet (lot 3) ;
requalifier `TerminalSessions`/`StructuredSessions` par `(project_id, agent_id)` +
`AppWakeSessionProvider` refuse l'autre projet (lot 4) ; audit `session_limit`/tables reprise (lot 5) ;
télémétrie `project_id` sur logs diag wake/routage (lot 6).
## Invariants
- Sources de contexte sur disque restent root-scopées (ne pas régresser).
- Templates/profils globaux restent globaux produit (pas de vecteur de session/conversation partagée).
- Un projet non-actif doit continuer à recevoir ses wakes et complétions.
- Pas de régression OpenCode/Codex/Claude (correctif transverse runtime, pas moteur-spécifique).
- Aucun nouveau DTO frontend pour la correction minimale.
## QA — critère de vérité
Test d'intégration **non-cross-talk** : 2 projets ouverts avec **volontairement les mêmes `AgentId`** :
- délégation projet A ne réutilise jamais session/conversation vivante de projet B ;
- complétion `(projet A, agent X)` n'entre ni dans l'inbox ni dans la session de `(projet B, agent X)` ;
- wake d'un projet non-actif fonctionne ;
- collision d'ids qui échouait avant → verte sur PTY, structured et background wake.
## Clôture 2026-07-25
- Correctif livré sur `feature/ticket101-multi-project-isolation` puis mergé localement dans `develop`.
- Commit feature : `6e98fd8 fix(runtime): isolate agent state by project (#101)`.
- Merge local : `merge: integrate ticket 101 multi-project isolation`.
- QA ciblée verte :
- `cargo test -p application --test agent_wake --test orchestrator_service --test structured_registry_d1 --test structured_launch_d3 --test session_limit_service --test session_limit_t4 --test workstate --test workstate_actions`
- `cargo test -p infrastructure --test agent_inbox --test mcp_server`
- `cargo test -p app-tauri --test session_limit_wiring`
- `cargo test -p application`
- `cargo test -p app-tauri --tests`
- Réserve connue : `cargo test -p infrastructure` complet reste rouge dans le sandbox QA sur tests `openai_compat` à cause du bind local interdit (`Operation not permitted`), sans signal de régression #101.
## Hors périmètre
- #91 (popup de notification) : purement front, indépendant.
- Remint systématique des AgentId à l'ouverture : non retenu (la clé runtime rend la collision
inoffensive même sans remint).
## Lié
- #91 (relatesTo). Mémoires : `background-tasks-first-class-design`, `b8-command-runner-pty-framing`,
`mcp-bridge-and-delegation-runtime-notes` (règle rebuild AppImage).

View File

@ -0,0 +1,16 @@
---
id: "05f9f05b-97d4-4ae2-9fd9-220dd71f7231"
number: 101
title: "[Bug] Soucis de retour de notification sur les taches backend"
status: "closed"
priority: "critical"
sprint: "e28a4d53-8bd2-446a-b0ac-2a017373b8b2"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784993230013
updatedAt: 1785011116365
version: 7
---
Lorsque un agent Opencode lance une tache backend, il s'arrete de travailler et je ne suis pas sur q'uil y ai un jour un retour de notification. Je ne sais aps si le soucis provient de OpenCode ou s'il provient du model (GLM 5.2 ici)

View File

@ -0,0 +1,6 @@
---
issueRef: "#102"
version: 4
updatedBy: {"kind":"user"}
updatedAt: 1784993980505
---

View File

@ -0,0 +1,16 @@
---
id: "e91fd358-da94-4382-aa70-6a3fe5a63840"
number: 102
title: "[Bug] Devoir resize les cellule pour afficher la TUI d'un agent"
status: "open"
priority: "medium"
sprint: "e28a4d53-8bd2-446a-b0ac-2a017373b8b2"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
createdAt: 1784993319700
updatedAt: 1784993980505
version: 4
---
J'ai toujours un soucis qui fait que quand je switch de projet IdeA ou de layout ou que j'ajoute des cellules ou autres, je suis obligé de resize un coup la cellule pour que son constenu s'affiche correctement

View File

@ -0,0 +1,99 @@
---
issueRef: "#103"
version: 4
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1785013507979
---
# Carnet #103 — permission réseau exposée dans IdeA
## Problème
Certaines commandes lancées par les agents s'exécutent dans un environnement où le réseau est restreint, sans visibilité ni action claire dans IdeA. Exemple live : rebuild AppImage OK jusqu'au bundling, puis `appimagetool` échoue à télécharger le runtime GitHub car la session agent est `network restricted` + `approval policy: never`.
## Décision UX
Mémoire : `ticket103-network-permission-ux-surface`.
- Surface principale : `Permissions > Système`.
- Miroirs de lecture : badge compact dans `Agents`, bannière/erreur contextualisée dans `Terminal`.
- États visibles : `Réseau autorisé`, `Réseau interdit`, `Demande d'autorisation`, `Verrouillé par le runtime`.
- Distinction obligatoire : politique voulue, état effectif, verrou runtime.
- Si le runtime externe ne permet pas l'élévation dans la session active : contrôle read-only + explication explicite.
## Cadrage architecture
Ne pas étendre le modèle existant `ProjectPermissions` fichier/bash : le réseau n'est ni une capability filesystem ni une règle Landlock. Ajouter un modèle/read-model séparé de permissions système.
### Domaine / DTO V1
- `NetworkPolicy = "allow" | "deny" | "ask"`.
- `SystemPermissionSet { network?: NetworkPolicy }`.
- `ProjectSystemPermissions { version, projectDefault?: SystemPermissionSet, agents?: [{ agentId, permissions: SystemPermissionSet }] }`.
- `ResolvedAgentSystemPermissions` :
- `wanted: NetworkPolicy | null`
- `effective: NetworkPolicy`
- `runtimeLock: { state: "none" | "locked", source?: string, reason?: string }`
- `control: { mode: "editable" | "readOnly", reason?: string }`
### Ports / use cases
- `SystemPermissionStore`.
- `GetProjectSystemPermissions`.
- `UpdateProjectSystemPermissions`.
- `UpdateAgentSystemPermissions`.
- `ResolveAgentSystemPermissions`.
- `RuntimePermissionProbe` : expose ce que le runtime hôte/fournisseur autorise réellement et s'il verrouille le réseau.
### API/commands attendus
- `get_project_system_permissions(projectId) -> ProjectSystemPermissionsDto`
- `update_project_system_permissions({ projectId, permissions }) -> ProjectSystemPermissionsDto`
- `update_agent_system_permissions({ projectId, agentId, permissions }) -> ProjectSystemPermissionsDto`
- `resolve_agent_system_permissions({ projectId, agentId }) -> ResolvedAgentSystemPermissionsDto`
### Limite produit V1
Implémentable maintenant : persister la politique voulue, afficher `wanted/effective/runtimeLock`, rendre le contrôle read-only si runtime verrouillé/non inspectable, badges agents, bannière terminal.
Non promis en V1 : changer effectivement la permission réseau d'une session fournisseur déjà lancée ou élever un runtime externe `network restricted` / `approval never`. IdeA doit l'expliquer plutôt que simuler une élévation.
## Découpage
### DevBackend
1. Ajouter domaine `system permissions` séparé de LP1 permissions fichier/bash.
2. Ajouter store + use cases + DTO + commands Tauri/HTTP.
3. Ajouter `RuntimePermissionProbe` read-only au composition root.
4. Ajouter read-model `resolve_agent_system_permissions`.
5. Optionnel si simple : mapper des échecs réseau vers un code stable `NETWORK_LOCKED` ou `NETWORK_UNAVAILABLE`.
### DevFrontend
1. Étendre types domaine, ports, adapters Tauri/HTTP/mock.
2. Ajouter la sous-section `Réseau` dans `Permissions > Système`.
3. Ajouter badge compact dans `Agents`.
4. Ajouter bannière/erreur terminal contextualisée pour état verrouillé/échec réseau.
### QA
- Projet sans config : état cohérent, pas de faux `allow`.
- Save project default puis override agent : relecture identique.
- Resolve distingue `wanted`, `effective`, `runtimeLock`.
- Probe `locked` : contrôle read-only + message visible.
- Non-régression `Permissions > Système` existant fichier/bash.
- Aucun moteur ne reçoit de faux flag réseau au spawn.
- Badge agents et bannière terminal reflètent l'état effectif.
## Validation 2026-07-25
QA verte sur le périmètre #103.
Commandes exécutées par QA :
- `cargo test -p domain system_permissions`
- `cargo test -p application --test system_permission_usecases`
- `cargo test -p infrastructure --test system_permission_store`
- `cargo test -p app-tauri --test dto_system_permissions`
- `cargo test -p web-server allowlisted`
- `npm run typecheck`
- `npx vitest run src/features/permissions/permissions.test.tsx src/features/agents/agents.test.tsx src/features/terminals/TerminalView.test.tsx`
- `npx vitest run src/features/permissions/permissions.test.tsx`
Résultats : backend ciblé vert, frontend typecheck vert, tests frontend ciblés 49 passed.
## État Git
Implémentation validée dans le working tree courant, mais commit/merge non réalisé par Main :
- l'agent Git headless renvoie un pseudo-appel outil au lieu d'agir ;
- Main ne peut pas écrire `.git/index.lock` depuis cette session (`Read-only file system`).
Le commit local reste à faire manuellement ou via un agent Git fonctionnel.
## Critère de clôture
Tests pertinents verts, commit/merge local par Git, puis rebuild AppImage.

View File

@ -0,0 +1,39 @@
---
id: "3d9021da-26c3-439d-9463-8d206bd06f1b"
number: 103
title: "Exposer et piloter la permission réseau des agents/commandes dans IdeA"
status: "qa"
priority: "high"
sprint: null
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1785011668081
updatedAt: 1785013507979
version: 4
---
## Problème
Aujourd'hui certaines commandes lancées par les agents s'exécutent dans un environnement où le réseau est restreint, sans que l'utilisateur puisse le voir ni l'autoriser depuis IdeA. Exemple réel : rebuild AppImage OK jusqu'au bundling, puis `appimagetool` échoue à télécharger le runtime AppImage depuis GitHub (`Failed to download runtime: server returned status code 0`) parce que la session agent a `network restricted` et `approval policy: never`.
## Besoin utilisateur
Depuis IdeA, l'utilisateur doit pouvoir comprendre et piloter cette permission réseau :
- voir qu'un agent/profil/session est en mode réseau interdit ou autorisé ;
- configurer la politique réseau attendue pour les agents/commandes ;
- éviter les échecs opaques de commandes qui ont légitimement besoin d'Internet (build, install, téléchargement runtime, docs, dépendances) ;
- conserver un comportement sûr par défaut et explicite.
## Attendu produit
Définir puis implémenter une surface IdeA pour exposer cette permission. La solution doit respecter le modèle de permissions/sandbox existant et clarifier la limite éventuelle : si le sandbox fournisseur impose `network restricted` sans possibilité d'élévation runtime, IdeA doit l'expliquer plutôt que faire croire que le réseau est activable.
## Critères d'acceptation
- Une surface UI ou configuration explicite permet de voir la politique réseau applicable aux agents/commandes.
- L'utilisateur dispose d'une action ou d'un réglage clair quand IdeA peut piloter cette permission.
- Si la permission est imposée par le runtime externe et non modifiable, l'UI l'indique clairement.
- Les agents/commandes ne gagnent pas l'accès réseau silencieusement.
- Tests pertinents verts.
- AppImage reconstruite après livraison.

View File

@ -1,8 +1,8 @@
---
issueRef: "#14"
version: 7
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1783456579694
version: 8
updatedBy: {"kind":"user"}
updatedAt: 1784449099987
---
## Production #14 — livrée dans develop (validation e2e live restante)

View File

@ -2,16 +2,16 @@
id: "0c1f1991-b6db-47b7-bbef-2478bc7874ab"
number: 14
title: "Integration de profils IA locaux/LAN comme profils IdeA canoniques"
status: "qa"
status: "closed"
priority: "medium"
sprint: null
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1783184495846
updatedAt: 1783456579694
version: 7
updatedAt: 1784449099987
version: 8
---
Objectif : permettre à IdeA d'utiliser des modèles hébergés localement ou sur le réseau local, par exemple Qwen via Ollama ou un runtime Docker exposant une API, avec le même niveau d'intégration produit que les profils Claude Code et OpenAI Codex CLI.

View File

@ -1,6 +1,6 @@
---
issueRef: "#3"
version: 6
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784093835732
version: 7
updatedBy: {"kind":"user"}
updatedAt: 1784377533777
---

View File

@ -2,16 +2,16 @@
id: "fedd343e-f9c1-467b-bab4-455f51907de7"
number: 3
title: "[Différé — design à mûrir] Tâches de fond — dette B : retry durable après reboot"
status: "open"
status: "closed"
priority: "low"
sprint: "e28a4d53-8bd2-446a-b0ac-2a017373b8b2"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1783082860758
updatedAt: 1784093835732
version: 6
updatedAt: 1784377533777
version: 7
---
Ticket #3 — Tâches de fond : retry après redémarrage sans fuite de secrets dans le projet.

View File

@ -1,6 +1,349 @@
---
issueRef: "#43"
version: 1
updatedBy: {"kind":"user"}
updatedAt: 1783879315858
version: 6
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784707900405
---
# Ticket #43 — Cadrage consolidé v2 du système de plugins
> Mise à jour Architect du 2026-07-21 après retour QA F4. Ce carnet v2 annule les ambiguïtés du cadrage initial, en particulier sur le schéma de layout custom plugin.
## 0. Décisions discovery conservées
- Plugins **full-trust** v1 : pas de sandbox UI.
- Bundle **JS/ESM pré-compilé**, importé dynamiquement ; IdeA ne compile pas de TS/TSX.
- Installation **globale** dans le dossier données utilisateur de l'application, pas dans les projets.
- Distribution v1 locale : dossier ou archive locale ; packaging compatible archive partageable future type `.vsix`.
- Contributions v1 : menus top-level, items de menus existants, layouts custom React arbitraires, serveurs MCP externes.
- MCP plugin = process serveur MCP externe déclaré par manifeste et supervisé via le pont MCP existant.
- Propreté retrait : après uninstall + redémarrage, aucune contribution, aucun process, aucune entrée fantôme, aucun état plugin-specific.
## 1. Store global et cycle de vie
Store global :
```text
{app_data_dir}/plugins/
registry.json
installed/
<pluginId>/
idea-plugin.json
dist/index.js
assets/...
servers/...
```
États v1 persistés :
```text
enabled
disabled
pending-enable
pending-disable
pending-uninstall
invalid
```
Règles :
- Installer depuis archive ou dossier local copie toujours un snapshot dans `installed/<pluginId>/`.
- `registry.json` porte seulement l'état global nécessaire ; une désinstallation réussie supprime l'entrée registry.
- Disable/uninstall en session masque les contributions UI et arrête MCP immédiatement si possible, mais le code ESM déjà importé n'est purgé strictement qu'au redémarrage.
- Au boot, le frontend reconstruit une registry plugin vide puis charge uniquement les plugins `enabled` et non `pending-uninstall`.
## 2. Manifeste v1
Fichier obligatoire : `idea-plugin.json`.
```json
{
"ideaPluginManifestVersion": 1,
"id": "dev.acme.gitgraph",
"displayName": "Git Graph",
"publisher": "Acme DevTools",
"version": "1.2.3",
"engines": { "idea": ">=0.1.0 <1.0.0" },
"main": "dist/index.js",
"icon": "assets/icon.svg",
"trustLevel": "full",
"capabilities": ["ui", "mcp"],
"contributes": {
"menus": [],
"menuItems": [],
"layouts": [],
"mcpServers": []
},
"archive": {
"files": ["idea-plugin.json", "dist/**", "assets/**", "servers/**"]
}
}
```
Validation :
- `trustLevel` vaut uniquement `full` en v1.
- `main`, `icon`, assets et commandes MCP relatives ne doivent contenir ni chemin absolu ni `..`.
- `id` stable, unique, reverse-DNS recommandé.
- `version` SemVer.
- `engines.idea` incompatible => plugin `invalid`, jamais chargé.
## 3. Contrat définitif des layouts custom plugin
### 3.1 Décision ferme
`CustomPluginLayout` est une **vraie variante top-level de `LayoutNode`**, pas un champ optionnel de `LeafCell`.
Motif : un layout plugin occupe une zone de l'arbre au même niveau conceptuel qu'une leaf terminal, un split ou une grid. Le mettre dans `LeafCell` mélangerait deux responsabilités incompatibles : terminal/session/agent d'un côté, composant React arbitraire et état opaque plugin de l'autre.
### 3.2 Schéma JSON canonique
Le layout tree garde le format serde existant `#[serde(tag = "type", content = "node")]`.
Union canonique :
```ts
export type LayoutNode =
| { type: "leaf"; node: LeafCell }
| { type: "split"; node: SplitContainer }
| { type: "grid"; node: GridContainer }
| { type: "customPluginLayout"; node: CustomPluginLayoutCell };
```
Payload canonique :
```ts
export interface CustomPluginLayoutCell {
id: string;
pluginId: string;
layoutType: string;
state: unknown;
}
```
Exemple complet :
```json
{
"root": {
"type": "customPluginLayout",
"node": {
"id": "018f0c5a-2b4b-70d4-a7c2-300000000001",
"pluginId": "dev.acme.gitgraph",
"layoutType": "dev.acme.gitgraph.layout",
"state": {
"branchFilter": "main"
}
}
}
}
```
Dans un split :
```json
{
"type": "split",
"node": {
"id": "split-1",
"direction": "row",
"children": [
{
"weight": 1,
"node": {
"type": "leaf",
"node": { "id": "terminal-1" }
}
},
{
"weight": 1,
"node": {
"type": "customPluginLayout",
"node": {
"id": "plugin-cell-1",
"pluginId": "dev.acme.gitgraph",
"layoutType": "dev.acme.gitgraph.layout",
"state": {}
}
}
}
]
}
}
```
Dans une grid, `GridCell.node` peut pareillement être `{ type: "customPluginLayout", node: ... }`.
### 3.3 Ce qui est explicitement interdit
Cette forme n'est **pas** contractuelle et ne doit plus être produite ni consommée comme modèle canonique :
```json
{
"type": "leaf",
"node": {
"id": "leaf-1",
"pluginLayout": {
"pluginId": "dev.acme.gitgraph",
"layoutType": "dev.acme.gitgraph.layout",
"state": {}
}
}
}
```
`LeafCell.pluginLayout` est une divergence frontend issue de l'ambiguïté initiale. Elle doit être supprimée du modèle domaine TS ou limitée à une migration locale temporaire de tests/mocks ; elle ne fait pas partie de l'IPC ni de la persistance.
### 3.4 Disponibilité et fallback
La disponibilité n'est pas stockée dans le layout. Elle est dérivée côté UI à partir du plugin registry/runtime catalog :
```ts
export type PluginLayoutAvailability =
| "available"
| "plugin-disabled"
| "plugin-missing"
| "incompatible";
```
Rendu :
- `available` : rendre le composant React enregistré pour `layoutType`.
- `plugin-disabled`, `plugin-missing`, `incompatible` : rendre le fallback non destructif `Layout indisponible`.
- Le fallback ne transforme pas automatiquement le nœud et ne supprime jamais `state`.
### 3.5 Ajustements requis
Verdict convergence F4 : **frontend à ajuster, backend à conserver**.
Backend :
- L'implémentation actuelle `LayoutNode::CustomPluginLayout(CustomPluginLayoutCell)` sérialisée `type: "customPluginLayout"` est le contrat canonique.
- À vérifier seulement : roundtrip serde et DTO Tauri exposent bien `pluginId`, `layoutType`, `state` en camelCase.
Frontend :
- Ajouter la variante `{ type: "customPluginLayout"; node: CustomPluginLayoutCell }` à `LayoutNode`.
- Retirer `pluginLayout?: CustomPluginLayoutCell` de `LeafCell` comme contrat domaine.
- Adapter `LayoutGrid`/renderer récursif pour router `type === "customPluginLayout"` vers `PluginLayoutCellView`.
- Adapter `layoutAvailability`, fallback et tests pour recevoir le payload depuis `node.node` de la variante top-level.
- Ajouter un test de parsing/rendu avec un layout JSON produit par Rust contenant `type: "customPluginLayout"`.
## 4. Contribution points menus et `when`
Menus v1 :
```ts
type MenuTargetId = "panels" | "settings" | `plugin:${string}`;
```
Items v1 :
```ts
interface PluginMenuItemContribution {
id: string;
targetMenuId: MenuTargetId;
label: string;
command: string;
order?: number;
icon?: string;
when?: string;
}
```
`when` v1 utilise le mini-langage booléen `&&`, `||`, `!`, parenthèses.
Variables :
```ts
type WhenVariable =
| "projectOpen"
| "gitRepository"
| "agentSelected"
| "terminalFocused"
| "layoutCellFocused";
```
Arbitrage QA sur les variables actuellement figées à `false` :
- `projectOpen` : obligatoire v1, doit refléter l'état réel.
- `gitRepository` : **bloquant avant clôture #43/F3**. Le manifeste exemple et le cas GitGraph dépendent de `projectOpen && gitRepository`; le laisser à `false` rend des contributions valides inatteignables.
- `agentSelected`, `terminalFocused`, `layoutCellFocused` : dette acceptable v1 si elles restent explicitement documentées comme **best-effort non câblé** et donc `false` jusqu'à un lot focus/selection dédié. Ce n'est pas bloquant pour F4 ni pour la clean-removal QA, sauf si un plugin de validation les utilise dans son manifeste.
Ajustement recommandé : DevFrontend câble au minimum `gitRepository` depuis l'état projet/git déjà disponible, ou retire temporairement `gitRepository` des manifests/tests de validation. La préférence architecture est de le câbler, car il est déjà annoncé comme variable v1.
## 5. Contribution MCP et `${appDataDir}`
Manifest MCP :
```ts
interface PluginMcpServerContribution {
id: string;
displayName: string;
command: string;
args?: string[];
env?: Record<string, string>;
cwd?: "${pluginRoot}" | "${appDataDir}" | string;
transport: "stdio";
autoStart?: boolean;
}
```
Variables de substitution contractuelles dans `command`, `args`, `env` et `cwd` :
- `${pluginRoot}` : obligatoire v1.
- `${appDataDir}` : **obligatoire v1 si la spec continue de l'accepter**.
- `${projectRoot}` : interdit v1.
Arbitrage QA sur `${appDataDir}` non implémenté :
- Pas bloquant pour F4 layout.
- **Bloquant pour clôture B4/#43** si le manifeste continue de documenter `${appDataDir}` comme disponible. Un contrat annoncé mais non substitué crée des specs MCP fausses.
- Deux sorties acceptables, par ordre de préférence :
1. DevBackend implémente la substitution `${appDataDir}` dans les specs MCP plugin et ajoute tests args/env/cwd.
2. Si le coût est refusé pour v1, retirer `${appDataDir}` du contrat manifeste et du validator, puis documenter explicitement `${pluginRoot}` comme seule variable v1.
Décision architecture par défaut : garder `${appDataDir}` et le faire implémenter par DevBackend, car le store global est déjà résolu côté backend et le coût est borné.
## 6. Lots et responsabilités actualisés
### F4 — convergence layout custom plugin
Owner : DevFrontend.
Livrables :
- Union TS `LayoutNode` alignée sur Rust avec `customPluginLayout` top-level.
- Renderer/fallback branchés sur cette variante.
- Suppression du contrat `LeafCell.pluginLayout`.
- Test frontend à partir d'un JSON Rust réel.
Backend F4 : pas de refonte attendue ; seulement vérifier/maintenir les tests serde existants.
### F3 — `when.gitRepository`
Owner : DevFrontend.
Livrable : `gitRepository` ne doit plus être figé à `false` pour un projet Git réel avant clôture #43/F3.
### B4 — `${appDataDir}` MCP
Owner : DevBackend.
Livrable : substitution `${appDataDir}` dans `command`, `args`, `env`, `cwd` des specs MCP plugin, ou retrait explicite du contrat si arbitré à la baisse. Par défaut : implémenter.
### QA — non-régression à ajouter
- Layout JSON backend `customPluginLayout` top-level rendu côté frontend.
- Fallback atteint si plugin missing/disabled avec nœud top-level.
- Aucun fallback basé sur `leaf.node.pluginLayout` ne compte comme validation du contrat.
- MCP spec avec `${pluginRoot}` et `${appDataDir}`.
- Menu item `when: "projectOpen && gitRepository"` actif dans un projet Git.
## 7. Non-objectifs maintenus v1
- Pas de sandbox UI.
- Pas de hot-unload mémoire garanti dans la même session.
- Pas de marketplace distant.
- Pas de compilation TS/TSX.
- Pas de `${projectRoot}` pour serveurs MCP plugin globaux.
- Pas de garantie v1 sur `agentSelected`, `terminalFocused`, `layoutCellFocused` tant qu'un lot focus/selection n'a pas été cadré.

View File

@ -2,14 +2,15 @@
id: "63e80352-8ce1-4116-9ec3-b4cb3fc5c077"
number: 43
title: "Systeme de plugins"
status: "open"
status: "closed"
priority: "medium"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1783879315858
updatedAt: 1783879315858
version: 1
updatedAt: 1784707900405
version: 6
---
Système de plugins/extensions pour IdeA (à la VSCode/Visual Studio) : permettre d'ajouter des menus (dropdown façon Panneaux/Paramètres), des items dans des menus existants, des types de layout custom (à la GitGraph), et des tools MCP.

View File

@ -1,8 +1,8 @@
---
issueRef: "#55"
version: 11
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784097757001
version: 12
updatedBy: {"kind":"user"}
updatedAt: 1784649948611
---
## Réouverture (2026-07-15) — le fix 141c13d ne suffit pas

View File

@ -2,16 +2,16 @@
id: "9ab1eb21-d6dd-435a-b088-377f0ec7e04f"
number: 55
title: "[Bug] Error on loading local model"
status: "inProgress"
status: "closed"
priority: "high"
sprint: "e28a4d53-8bd2-446a-b0ac-2a017373b8b2"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1784045935720
updatedAt: 1784097757001
version: 11
updatedAt: 1784649948611
version: 12
---
Quand jke cherche a lancer un modele local sur une cellule, il commence apr charger le serveur, ce qui est bon mais au bout de quelques seconde, le chargement du serveur disparait et j'ai cette erreur qui s'affiche en bandeau rouge: Échec du lancement de l'agent : model server error (timeout): readiness timed out.
Si j'insiste assez en changeant d'agent et en remettant l'agent, au bourt d'un moment ça fini par fonctionner

View File

@ -1,6 +1,21 @@
---
issueRef: "#60"
version: 3
updatedBy: {"kind":"user"}
updatedAt: 1784194089605
version: 5
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784360139966
---
## Réconciliation (2026-07-18) — déjà livré et mergé, statut désynchronisé
Le fix Lot A (backend + frontend) a en réalité été implémenté et mergé sur `develop` **avant** cette relance de sprint : commit `2f7f111` (`fix(ticket-assistant): rendre visible l'échec « aucune réponse » de l'assistant de ticket (#60)`), fusionné via `ebad5ff` (`Merge feature/ticket60-ticket-assistant-no-reply into develop (#60)`). `ebad5ff` est ancêtre de `develop` HEAD actuel (`17d6baf`) — plusieurs sprints livrés depuis sans régression signalée.
Contenu du fix (conforme au périmètre cadré) :
- domain/ports : variant `ReplyEvent::Error { message }`.
- infra/session : parse `reasoning_content` en fallback quand `content` est vide.
- app-tauri : mapping `ReplyEvent::Error → ReplyChunk::Error`, Final vide converti en Error visible.
- frontend : `ReplyChunk` type `error`, rendu dans `useTicketAssistant` (+ test dédié), `busy` ne retombe plus prématurément.
Reconfirmé le 2026-07-18 par DevFrontend sur l'arbre de travail courant : `npx vitest run src/features/tickets/useTicketAssistant.test.tsx` → 3/3 vert, y compris le cas error-chunk.
Le ticket était resté `inProgress` avec un carnet vide par désynchronisation d'état (le commit d'origine portait la note « #60 reste inProgress » avant la vérification hors-sandbox qui a permis le merge ensuite). Clôture sur constat de fait, pas de nouveau travail nécessaire.
Branche orpheline `feature/ticket60-assistant-mute-reply` créée par erreur lors de cette relance (rien à committer, identique à `develop`) — à supprimer par Git, non utilisée.

View File

@ -2,16 +2,16 @@
id: "623b1719-c71d-448e-a0ef-3c7d5823b8a3"
number: 60
title: "[Bug] Assistant IA d'édition de ticket : aucune réponse dans la conversation (stream sans Final non signalé)"
status: "inProgress"
status: "closed"
priority: "high"
sprint: "e28a4d53-8bd2-446a-b0ac-2a017373b8b2"
links: [{"target":"#25","kind":"relatesTo"},{"target":"#27","kind":"relatesTo"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784094260499
updatedAt: 1784194089605
version: 3
updatedAt: 1784360139966
version: 5
---
Rapporté par l'utilisateur (2026-07-15) : dans l'édition d'un ticket, le bouton « Assistant IA » ouvre une conversation, mais l'envoi d'un message ne produit JAMAIS de réponse visible — la conversation reste muette. PROFIL UTILISÉ : modèle local OpenAI-compatible (llamacpp).

View File

@ -1,6 +1,19 @@
---
issueRef: "#61"
version: 5
updatedBy: {"kind":"user"}
updatedAt: 1784182133142
version: 11
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784661823324
---
Un premier fix (commit 445ecaf, "fix(frontend): refit différé des cellules terminal après mutation de layout (#61)", mergé via a69c5db) a couvert le cas desktop LayoutGrid/TerminalView (split/merge déclenchant un refit explicite via requestAnimationFrame).
## Réouverture (2026-07-21)
L'utilisateur signale que le problème persiste :
- Ouverture d'une nouvelle cellule → la CLI déjà affichée ne se redimensionne pas correctement tant qu'un resize manuel n'est pas déclenché.
- Reproduit aussi sur la version **Web** : sur PC, un redimensionnement manuel de la fenêtre du navigateur corrige le scaling. Sur mobile, impossible de redimensionner la fenêtre → le bug reste bloquant, pas de contournement possible.
Hypothèse de travail : le fix précédent (445ecaf) est probablement scopé à `LayoutGrid`/`useLayout` (desktop, split/merge d'un arbre de layout). Le workspace web n'utilise pas ce chemin — il a sa propre surface (`WebAgentCell`/`LiveProjectPanel`, cf. déviation notée par DevFrontend sur le ticket #90) qui ouvre/remplace des cellules terminal autrement. Le signal de refit explicite ajouté pour split/merge ne se déclenche donc probablement jamais côté web, et `TerminalView` retombe uniquement sur son `ResizeObserver` local — insuffisant selon le timing de montage/redimensionnement de la surface web.
À vérifier par Architect/DevFrontend : le chemin exact d'ouverture/fermeture de cellule côté web (WebAgentCell, WebWorkspace, WebMenuSheet le cas échéant après #90), et si le mécanisme de refit du fix 445ecaf peut être réutilisé/étendu à ce chemin plutôt que dupliqué.
Périmètre : frontend pur, desktop non régressé, web (PC et mobile) doit refit sans action manuelle de l'utilisateur.

View File

@ -2,15 +2,15 @@
id: "b0568a4e-4804-480c-ada4-bc9d6b7b40c6"
number: 61
title: "[UI] rafraichissement des cellule"
status: "open"
status: "closed"
priority: "low"
sprint: "e28a4d53-8bd2-446a-b0ac-2a017373b8b2"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784095163730
updatedAt: 1784182133142
version: 5
updatedAt: 1784661823324
version: 11
---
Lorsque j'ajoute une nouvelle cellule ou que j'en enlève une, je suis obligé de redimentionner un coup la fenêtre ou les cellule pour rafraichir le scaling des cli

View File

@ -1,6 +1,6 @@
---
issueRef: "#62"
version: 1
version: 2
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784095187734
updatedAt: 1784406936659
---

View File

@ -2,7 +2,7 @@
id: "dd897d74-4e88-4d34-a6fb-d7e7f330094f"
number: 62
title: "[Sécurité/Cohérence] Identité requester explicite pour les sessions structurées + policy des tools OpenAI-compatible"
status: "open"
status: "closed"
priority: "medium"
sprint: null
links: [{"target":"#60","kind":"relatesTo"}]
@ -10,8 +10,8 @@ agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784095187734
updatedAt: 1784095187734
version: 1
updatedAt: 1784406936659
version: 2
---
Sorti de #60 (assistant IA de ticket muet) — volet distinct, touchant un port figé et la sécurité des tool calls.

View File

@ -1,6 +1,6 @@
---
issueRef: "#64"
version: 5
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784193766706
version: 6
updatedBy: {"kind":"user"}
updatedAt: 1784718418070
---

View File

@ -2,16 +2,16 @@
id: "c8f1c5b3-7674-4c6c-bba5-bb2c5faf201b"
number: 64
title: "Web : créer/ajouter un projet depuis le navigateur (folder browser serveur)"
status: "open"
status: "closed"
priority: "medium"
sprint: "028179b1-eaf4-41e9-9c1f-7c37125117e6"
links: [{"target":"#13","kind":"dependsOn"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1784183727404
updatedAt: 1784193766706
version: 5
updatedAt: 1784718418070
version: 6
---
Issu de la validation live de #13 (mode client/serveur web). En web, le bouton « Browse » (choisir un dossier de projet) est inopérant : `pickFolder()` renvoie `UNSUPPORTED_ON_WEB` car il s'appuie sur le dialogue natif OS de Tauri, absent du navigateur. Sélectionner un projet EXISTANT est déjà couvert par la liste (list_projects/open_project). Il manque CRÉER/AJOUTER un projet = choisir un dossier côté SERVEUR.

View File

@ -1,8 +1,8 @@
---
issueRef: "#69"
version: 11
updatedBy: {"kind":"agent","agent_id":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"}
updatedAt: 1784207736267
version: 12
updatedBy: {"kind":"user"}
updatedAt: 1784449061397
---
# Ticket #69 — Adaptibilité client téléphone (carnet de chantier)

View File

@ -2,15 +2,15 @@
id: "7f5a40cf-837f-46f7-8c5f-653c80975396"
number: 69
title: "Adaptibilité client téléphone"
status: "qa"
status: "closed"
priority: "medium"
sprint: "028179b1-eaf4-41e9-9c1f-7c37125117e6"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"}
updatedBy: {"kind":"user"}
createdAt: 1784193391258
updatedAt: 1784207736267
version: 11
updatedAt: 1784449061397
version: 12
---
J'aimerais un format vertical qui rende l'app client compatible téléphone

View File

@ -1,6 +1,6 @@
---
issueRef: "#7"
version: 7
updatedBy: {"kind":"user"}
updatedAt: 1783329459094
version: 8
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784718581033
---

View File

@ -2,15 +2,15 @@
id: "9f6979fb-a68f-4660-9d57-cf69ea1daf85"
number: 7
title: "Gestion des session limite"
status: "qa"
status: "closed"
priority: "high"
sprint: "d8f3f37b-87ca-4509-9116-45a99bc711df"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1783167495082
updatedAt: 1783329459094
version: 7
updatedAt: 1784718581033
version: 8
---
J'aimerais que IdeA puisse gérer les session limite des profile IA. Lorsqu'un profile IA atteint une session limite, j'aimerais que IdeA le sache, qu'il récupère l'heure et la date du reset et qu'il puisse automatiquement reprendre le travail une fois le reset fait. Lorsqu'une reprise automatique est prévue, il faut que l'utilisateur puisse cancel la reprise. Il est important de noter que les agent s'appelant entre eux avec des profile IA différents, il faut prendre en compte qu'un agent A qui appelle un agent B, peut voir l'agent B atteindre la session limite. IdeA devra savoir gérer ça correctement.

View File

@ -1,6 +1,6 @@
---
issueRef: "#75"
version: 2
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784277530792
version: 3
updatedBy: {"kind":"user"}
updatedAt: 1784449082160
---

View File

@ -2,16 +2,16 @@
id: "d54a1f20-03e6-4925-982b-923a37331749"
number: 75
title: "Écran d'appairage web : clavier numérique sur mobile alors que le code contient des lettres"
status: "qa"
status: "closed"
priority: "medium"
sprint: null
links: [{"target":"#69","kind":"relatesTo"},{"target":"#74","kind":"relatesTo"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1784277353320
updatedAt: 1784277530792
version: 2
updatedAt: 1784449082160
version: 3
---
## Symptôme (constaté live, téléphone)

View File

@ -1,6 +1,6 @@
---
issueRef: "#76"
version: 1
version: 2
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784277540034
updatedAt: 1784287679986
---

View File

@ -2,7 +2,7 @@
id: "c91f547f-96f0-4c9c-9fde-dbe7d2772dbc"
number: 76
title: "Appairage : la comparaison du code est sensible à la casse côté serveur"
status: "open"
status: "closed"
priority: "low"
sprint: null
links: [{"target":"#75","kind":"relatesTo"}]
@ -10,8 +10,8 @@ agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784277540034
updatedAt: 1784277540034
version: 1
updatedAt: 1784287679986
version: 2
---
## Constat

View File

@ -1,8 +1,8 @@
---
issueRef: "#77"
version: 4
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784287453378
version: 5
updatedBy: {"kind":"user"}
updatedAt: 1784449075576
---
# Carnet #77 — cadrage UX + Architecture (2026-07-17)

View File

@ -2,16 +2,16 @@
id: "eecf77ee-dfcb-436c-aa5f-0c7033c7bdfa"
number: 77
title: "Appairage : appareils enregistrés persistants, révocables, et code éphémère à usage unique"
status: "qa"
status: "closed"
priority: "high"
sprint: null
links: [{"target":"#76","kind":"relatesTo"},{"target":"#75","kind":"relatesTo"},{"target":"#68","kind":"relatesTo"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1784279214815
updatedAt: 1784287453378
version: 4
updatedAt: 1784449075576
version: 5
---
## Besoin utilisateur

View File

@ -1,6 +1,70 @@
---
issueRef: "#78"
version: 1
version: 3
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784284746767
updatedAt: 1784379475877
---
## Décision UX — langue de l'UI IdeA
Règle tranchée : l'UI humaine d'IdeA est en français par défaut, de façon uniforme sur toute une surface. Une même surface ne mélange pas des libellés français et anglais pour des éléments de navigation, titres, actions, états ou textes d'aide.
Exceptions admises : noms propres de produits/protocoles (`IdeA`, `OpenAI`, `OpenCode`, `Claude Code`, `HTTPS`, `LAN`, `URL`, `IP/CIDR`, `API`, `CLI`, etc.), chemins techniques, commandes, variables, valeurs de configuration et termes backend affichés comme données. Ces éléments restent dans leur forme technique si la traduction réduit la reconnaissance ou la précision.
Conséquence : pas d'i18n à introduire pour ce ticket. #78 est un correctif frontend pur d'alignement rédactionnel de la surface Settings desktop vers le français. L'i18n multi-langue serait un chantier produit séparé, non requis ici.
## Libellés à renommer pour #78
Périmètre minimal attendu : la surface desktop `Settings` et ses sections actuellement visibles.
- `Settings``Paramètres` quand le mot est affiché à l'utilisateur dans le menu, la surface ou les renvois internes.
- `Close Settings``Fermer les paramètres`.
- `AI Profiles``Profils IA` dans la navigation de sections et comme titre de panneau.
- `Configure profiles``Configurer les profils`.
- `No profiles configured.``Aucun profil configuré.`.
- `Delete``Supprimer` pour les actions de suppression de profil.
- `delete {name}``supprimer {name}` pour le libellé accessible correspondant.
- `Deployment``Déploiement` dans la navigation et comme titre de panneau.
- `deployment settings``paramètres de déploiement` pour les libellés accessibles.
- `Loading…``Chargement…`.
- `Server``Serveur`.
- `Start``Démarrer`.
- `Stop``Arrêter`.
- `Stopped``Arrêté`.
- `Starting…``Démarrage…`.
- `Running``En cours d'exécution`.
- `Stopping…``Arrêt…`.
- `Failed``Échec`.
- `Exposure``Exposition réseau` ou `Accès réseau` ; préférence UX : `Accès réseau`, plus compréhensible pour l'utilisateur.
- `exposure mode``mode d'accès réseau`.
- `This computer only``Cet ordinateur uniquement`.
- `For using IdeA on this desktop only. Remote devices cannot connect.``Pour utiliser IdeA uniquement sur cet ordinateur. Les appareils distants ne peuvent pas se connecter.`
- `Remote access, proxy on this computer``Accès distant, proxy sur cet ordinateur`.
- `Use this when your HTTPS proxy runs on the same machine as IdeA Desktop.``À utiliser quand le proxy HTTPS tourne sur la même machine qu'IdeA Desktop.`
- `Remote access, proxy on another machine``Accès distant, proxy sur une autre machine`.
- `Use this when the HTTPS proxy runs on another machine. IdeA will only accept traffic from that proxy.``À utiliser quand le proxy HTTPS tourne sur une autre machine. IdeA n'acceptera que le trafic provenant de ce proxy.`
- `Public origin``Origine publique`.
- `Example: https://idea.example.com``Exemple : https://idea.example.com`.
- `LAN address to bind``Adresse LAN d'écoute`.
- `The address on this machine that the proxy will connect to.``L'adresse de cette machine à laquelle le proxy se connectera.`
- `Select an address…``Sélectionner une adresse…`.
- `Authorized proxy IP/CIDR``IP/CIDR du proxy autorisé`.
- `This is not where IdeA listens. It is the machine allowed to contact IdeA.``Ce n'est pas l'adresse d'écoute d'IdeA. C'est la machine autorisée à contacter IdeA.`
- `If your proxy is not on this computer, choose this mode. Otherwise the proxy may time out without an IdeA error.``Si votre proxy n'est pas sur cet ordinateur, choisissez ce mode. Sinon, le proxy peut expirer sans erreur IdeA.`
- `Proxy setup``Configuration du proxy`.
- `Point your HTTPS reverse proxy at this upstream.``Faites pointer votre proxy inverse HTTPS vers cet upstream.`
- `copy upstream url``copier l'URL upstream`.
- `Copy``Copier`.
- `Copied``Copié`.
- `Complete the settings above to get the upstream URL.``Complétez les paramètres ci-dessus pour obtenir l'URL upstream.`
- `Pairing``Appairage`.
- `Pairing is managed in Settings → Appareils, where you can generate a code and revoke devices.``L'appairage est géré dans Paramètres → Appareils, où vous pouvez générer un code et révoquer des appareils.`
Libellés déjà conformes : `Appareils`, `Appairer`, `Code d'appairage`, `Chargement des appareils…`, `Aucun appareil appairé.`, `Révoquer...` restent en français.
## Critères d'acceptation UX
- La navigation des paramètres affiche `Profils IA`, `Déploiement`, `Appareils` sans mélange anglais/français.
- Les titres, boutons, états et textes d'aide visibles dans ces sections sont en français, hors exceptions techniques listées plus haut.
- Les libellés accessibles suivent la même langue que le libellé visible correspondant.
- Aucun mécanisme i18n n'est introduit pour #78 ; il s'agit uniquement de remplacer les chaînes frontend existantes.
- Les termes `URL`, `LAN`, `IP/CIDR`, `HTTPS` et `upstream` peuvent rester tels quels car ils désignent des objets techniques reconnus par la cible utilisateur.

View File

@ -2,7 +2,7 @@
id: "8d8bc65a-50f6-4fab-bb02-486b6145205f"
number: 78
title: "Settings desktop : sections en langues mélangées (« Appareils » à côté de « AI Profiles », « Deployment »)"
status: "open"
status: "closed"
priority: "low"
sprint: null
links: [{"target":"#77","kind":"relatesTo"}]
@ -10,8 +10,8 @@ agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784284746767
updatedAt: 1784284746767
version: 1
updatedAt: 1784379475877
version: 3
---
## Constat

View File

@ -1,6 +1,6 @@
---
issueRef: "#79"
version: 1
version: 2
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784287450082
updatedAt: 1784379476483
---

View File

@ -2,7 +2,7 @@
id: "05a70dc5-fd28-4c58-8ba1-be6058ae3cfc"
number: 79
title: "Test flaky : PermissionsPanel « saves project defaults » échoue par intermittence"
status: "open"
status: "closed"
priority: "low"
sprint: null
links: []
@ -10,8 +10,8 @@ agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784287450082
updatedAt: 1784287450082
version: 1
updatedAt: 1784379476483
version: 2
---
## Constat

View File

@ -0,0 +1,6 @@
---
issueRef: "#80"
version: 1
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784287697560
---

View File

@ -0,0 +1,56 @@
---
id: "f36ada98-0ca1-41e6-94c1-24eb81731fee"
number: 80
title: "L'environnement de QA ne peut pas ouvrir de socket — il ne peut pas valider les features réseau"
status: "open"
priority: "medium"
sprint: null
links: [{"target":"#77","kind":"relatesTo"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784287697560
updatedAt: 1784287697560
version: 1
---
## Constat
Relevé pendant #77, confirmé indépendamment par Main et par Git.
L'agent QA exécute ses tests dans un environnement dont le sandbox **interdit les binds réseau** :
```text
failed to bind 127.0.0.1:0: Operation not permitted (os error 1)
bind: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }
Failed building the Runtime: Os { code: 24, kind: Uncategorized, message: "Too many open files" }
```
Sur #77, QA a rapporté :
- `cargo test -p web-server` → 73 passed / **6 failed** (2 binds + 4 tests WebSocket en cascade),
- `cargo test -p domain -p application -p infrastructure -p web-server` → 275 passed / **10 failed** (tous `infrastructure::session::openai_compat`).
**Les mêmes suites passent intégralement hors de ce sandbox** : 79/79 sur `web-server`, exit 0 sur la commande combinée (vérifié par Main, puis rejoué par Git dans son propre environnement). DevBackend observe le même artefact de son côté.
## Pourquoi c'est un problème et pas une curiosité
Le cœur de #77 était précisément **la révocation de WebSockets vivants** — un durcissement de sécurité non négociable. QA ne pouvait structurellement pas le valider de bout en bout : l'environnement qui doit prouver que la feature marche est celui qui ne peut pas l'exécuter.
Le coût réel se paie deux fois :
1. **Faux rouges** : QA a bloqué son verdict sur des échecs qui n'existaient pas, et il a fallu du temps à Main pour établir que c'était l'environnement. C'était le bon réflexe de sa part — le problème n'est pas QA, c'est son bac à sable.
2. **Faux verts potentiels**, plus grave : un test réseau qui ne s'exécute jamais chez QA ne peut pas y échouer non plus. La prochaine feature réseau rejouera la scène, et rien ne garantit qu'on la relira aussi attentivement.
## Attendu
Que l'environnement de QA puisse ouvrir des sockets sur la loopback, ou à défaut que la limitation soit **explicite et connue** (QA sait ce qu'il ne peut pas valider, et le dit dans son verdict au lieu de le rapporter en échec).
À regarder aussi : le `Too many open files` (`code: 24`), qui suggère une limite de descripteurs de fichiers trop basse en plus de la restriction réseau.
## Note
Voir la mémoire `permissions-sandbox-system-state` pour l'état du système de permissions/sandbox et le risque résiduel déjà documenté.
## Périmètre
Infrastructure d'agents / configuration de sandbox. Pas de code applicatif.

223
.ideai/tickets/81/carnet.md Normal file
View File

@ -0,0 +1,223 @@
---
issueRef: "#81"
version: 5
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784565918809
---
# Cadrage — ticket #81
## Objectif
Permettre aux agents IdeA d'administrer les templates d'agents via MCP, sans surface humaine nouvelle : créer, éditer et supprimer des templates globaux IdeA depuis des tools `idea_*`.
UX n'est pas concerné pour ce ticket : il n'y a pas d'écran, de workflow humain ni de libellés UI à concevoir. La seule surface est le catalogue MCP exposé aux agents.
## État réel du système templates
Surfaces inspectées :
- Domaine : `crates/domain/src/template.rs`
- Port : `TemplateStore` dans `crates/domain/src/ports.rs`
- Use cases : `crates/application/src/template/usecases.rs`
- Store : `crates/infrastructure/src/store/template.rs`
- Commands Tauri : `crates/app-tauri/src/commands.rs`
- DTO : `crates/backend/src/dto.rs` / `crates/app-tauri/src/dto.rs`
- Frontend existant : `frontend/src/features/templates/*`, `frontend/src/adapters/template.ts`
- MCP + permissions #82 : `crates/infrastructure/src/orchestrator/mcp/tools.rs`, `server.rs`, `backend/src/openai_tools.rs`, `app-tauri/src/openai_tools.rs`
Templates existants :
```text
AgentTemplate {
id: TemplateId,
name: String,
content_md: MarkdownDoc,
version: TemplateVersion,
default_profile_id: ProfileId,
}
```
Invariants existants :
- `name` non vide ;
- `version` démarre à `1` ;
- `version` est bumpée par `AgentTemplate::with_updated_content`, donc par changement de contenu Markdown ;
- `default_profile_id` sert aux futurs agents créés depuis le template.
Stockage existant : global app-data, pas projet :
```text
<app_data_dir>/templates/
├── index.json
└── md/<template-id>.md
```
`index.json` porte les métadonnées (`id`, `name`, `version`, `contentHash`, `defaultProfileId`) ; le Markdown vit dans `md/<id>.md`. Le store est déjà derrière `TemplateStore`, donc Tauri-agnostique côté infra.
## Synchronisation template → agents
Les agents créés depuis un template copient le `content_md` dans leur propre contexte `.md` projet et gardent dans le manifeste :
- `template_id`
- `synchronized`
- `synced_template_version`
`UpdateTemplate` actuel met à jour le template global, bump la version et publie `DomainEvent::TemplateUpdated`. Il ne modifie pas directement les agents existants.
`DetectAgentDrift` compare `template.version > synced_template_version` pour les agents `synchronized == true` et publie `AgentDriftDetected`.
`SyncAgentWithTemplate` est l'opération explicite qui remplace le `.md` de l'agent synchronisé par le contenu courant du template et met à jour `synced_template_version`.
Conclusion : si un agent édite un template via MCP, les agents synchronisés qui l'utilisent ne sont pas impactés immédiatement. Ils deviennent en drift jusqu'à appel explicite de sync. Au lancement suivant, ils relisent leur `.md` agent existant, pas automatiquement le template global. Ce comportement doit rester tel quel pour #81, sauf arbitrage produit séparé.
## Tools MCP à ajouter
Ajouter une surface templates explicite ; `idea_skill_read`, `idea_context_read` et les tools existants ne couvrent pas les templates. Les templates ne sont ni des skills ni des contextes agent.
Proposition de catalogue :
### Lecture, autorisée par défaut (#82)
- `idea_template_list`
- Rôle : lister les templates globaux disponibles.
- Payload conseillé : templates complets ou résumés. Pour l'ergonomie agent, retour complet acceptable au premier lot (`id`, `name`, `contentMd`, `version`, `defaultProfileId`) car `ListTemplates` renvoie déjà les entités complètes.
- `idea_template_read`
- Rôle : lire un template par id.
- Input : `{ "templateId": "..." }`
- Retour : même DTO qu'un template Tauri.
- Nécessite un thin use case `ReadTemplate` ou un provider qui appelle `TemplateStore::get` via un use case applicatif dédié.
### Écriture/action, refusée par défaut (#82)
- `idea_template_create`
- Input : `{ name, content, defaultProfileId }`
- Réutilise `CreateTemplate`.
- Retour : template créé.
- `idea_template_update`
- Input minimal aligné existant : `{ templateId, content }`.
- Réutilise `UpdateTemplate` actuel, qui met à jour le contenu et bump la version.
- Extension recommandée si on veut couvrir pleinement "éditer" : accepter aussi `name?` et `defaultProfileId?`, avec bump de version seulement si `content` change. Cela nécessite d'étendre le domaine/use case car aujourd'hui l'UI elle-même ne persiste pas le changement de nom/profil en mode edit.
- Option de sûreté à arbitrer : ajouter `expectedVersion` pour éviter les écrasements concurrents entre agents. Le store/use case Tauri actuel n'a pas d'optimistic concurrency sur templates, donc ce serait une extension de contrat, pas une simple exposition MCP.
- `idea_template_delete`
- Input : `{ templateId }`
- Réutilise `DeleteTemplate`.
- Effet existant : supprime de l'index global ; le fichier Markdown orphelin peut rester sur disque car le port FS n'a pas de delete. Les agents créés depuis ce template gardent leur `.md`; drift detection ignore le template absent.
## Intégration obligatoire avec #82
#82 a introduit la classification canonique dans `crates/infrastructure/src/orchestrator/mcp/tools.rs` :
- `READ_ONLY_TOOLS`
- `WRITE_ACTION_TOOLS`
- `tool_access`
- test garde-fou `catalogue_tools_have_explicit_read_or_write_access`
Tout tool ajouté au catalogue doit être classé immédiatement, sinon le test doit échouer.
Classification #81 attendue :
```text
READ_ONLY_TOOLS += [
"idea_template_list",
"idea_template_read",
]
WRITE_ACTION_TOOLS += [
"idea_template_create",
"idea_template_update",
"idea_template_delete",
]
```
Comportement policy attendu :
- agent sans override #82 : peut lire/lister les templates, ne peut pas créer/éditer/supprimer ;
- agent avec override incluant un ou plusieurs tools `idea_template_*` d'écriture : peut seulement appeler ceux explicitement autorisés ;
- le refus doit arriver avant effet applicatif, sur le serveur MCP stdio et sur l'invoker OpenAI-compatible.
Ce comportement est cohérent avec la demande #82 : par défaut seuls les tools de lecture sont autorisés. Pas d'arbitrage produit nécessaire sauf si l'utilisateur veut que certains agents aient une permission template-write préconfigurée par défaut.
## Point d'implémentation recommandé
Ne pas ajouter ces opérations au `OrchestratorCommand` sauf nécessité. Les templates sont une famille CRUD applicative, comme les tickets, pas un protocole d'orchestration inter-agent. Le pattern le plus local est donc de créer un provider MCP dédié, parallèle à `TicketToolProvider` :
- `crates/infrastructure/src/orchestrator/mcp/templates.rs`
- `TemplateToolProvider`
- `TemplateToolError`
- `is_template_tool(name)`
- `catalogue()` des tools templates
- `crates/infrastructure/src/orchestrator/mcp/tools.rs`
- étendre `catalogue()` avec `templates::catalogue()` ;
- ajouter `is_template_tool` ;
- ajouter les 5 tools dans les classifications #82 ;
- si besoin, faire retourner `tool_returns_reply` pour les read/create/update/list/delete selon convention (delete peut retourner un ACK JSON).
- `crates/infrastructure/src/orchestrator/mcp/server.rs`
- ajouter `template_tools: Option<Arc<dyn TemplateToolProvider>>` ;
- dans `tools_call`, après enforcement #82 et avant `map_tool_call`, router `is_template_tool` vers le provider, comme les tickets ;
- garder l'enforcement durable/éphémère avant provider.
- Composition root backend/app-tauri
- créer `LateBoundTemplateToolProvider` si besoin pour casser les cycles comme `LateBoundTicketToolProvider` ;
- binder un `AppTemplateToolProvider` construit avec `create_template`, `list_templates`, `update_template`, `delete_template` et le nouveau `read_template` si ajouté ;
- injecter ce provider dans `McpServer::new(...).with_template_tools(...)` ;
- injecter aussi dans `AppOpenAiToolInvoker` pour parité OpenAI-compatible.
Alternative possible : ajouter des variants `OrchestratorCommand::Template*` et mapper les tools via `map_tool_call`. Je ne la recommande pas en premier choix : cela gonfle l'orchestrateur avec un CRUD global qui n'est pas une coordination agent-agent, alors que le précédent ticket système a déjà accepté le pattern provider pour les tickets.
## Lots proposés
### Lot B1 — Catalogue MCP + classification #82 + provider squelette
- Ajouter `templates.rs` côté MCP infra.
- Ajouter les 5 tool defs.
- Classer immédiatement `idea_template_list/read` en lecture et `create/update/delete` en écriture/action.
- Tests : garde-fou catalogue/classification vert ; `tools/list` expose les templates selon policy #82.
### Lot B2 — Use cases/DTO/provider templates
- Ajouter `ReadTemplate` use case fin si on garde `idea_template_read`.
- Implémenter `AppTemplateToolProvider` avec les use cases existants.
- Mapper erreurs : `notFound`, `invalid`, `store`, `internal`.
- Tests provider : create/list/read/update/delete sur fakes, version bump sur update contenu.
### Lot B3 — Enforcement policy + OpenAI-compatible parity
- Vérifier MCP stdio : un agent default read-only peut `idea_template_list/read`, mais `idea_template_create/update/delete` est refusé avant provider.
- Vérifier override #82 : autoriser seulement `idea_template_update` n'autorise pas create/delete.
- Répliquer le dispatch provider dans `AppOpenAiToolInvoker`, avec même policy durable avant effet.
### Lot B4 — Édition complète optionnelle
À faire seulement si le produit veut que "éditer" couvre autre chose que le contenu Markdown :
- étendre `UpdateTemplateInput` avec `name?`, `defaultProfileId?`, `content?` ;
- préserver l'invariant : bump version uniquement quand `content_md` change ;
- décider si changement de `defaultProfileId` doit avoir un effet sur agents existants (recommandation : non, seulement futurs `CreateAgentFromTemplate`) ;
- ajouter éventuellement `expectedVersion` pour update/delete, avec erreur de conflit.
## Frontières
Frontend/UX : hors périmètre. Aucune surface humaine nouvelle.
Domaine templates : réutiliser `AgentTemplate`, `TemplateVersion`, `TemplateStore`. Extension domaine seulement si B4 est retenu.
Projet `.ideai/agents.json` : hors périmètre pour CRUD templates. Les agents créés depuis template et leur sync existante restent inchangés.
Synchronisation automatique : hors périmètre. #81 ne doit pas auto-écraser les `.md` des agents synchronisés après update template ; laisser `DetectAgentDrift`/`SyncAgentWithTemplate` piloter cela.
Permissions #82 : dans le périmètre obligatoire. Aucun tool template ne doit entrer dans le catalogue sans classification explicite et sans enforcement identique MCP stdio/OpenAI-compatible.
## Critères d'acceptation
- Les agents voient les tools template dans le catalogue MCP selon leur policy #82.
- Les tools lecture template sont accessibles à un agent sans override.
- Les tools create/update/delete sont refusés à un agent sans override, avant mutation.
- Une policy agent qui autorise un write template permet uniquement ce write.
- `idea_template_update` bump la version quand le contenu change et publie `TemplateUpdated` via le use case existant.
- Les agents synchronisés au template passent en drift, mais ne sont pas modifiés tant qu'un sync explicite n'est pas demandé.
- Les chemins MCP stdio et OpenAI-compatible ont la même sémantique et les mêmes refus.

View File

@ -0,0 +1,16 @@
---
id: "e9c936c5-4540-42ab-a482-c64e6f9b9f8c"
number: 81
title: "MCP d'edition de templates"
status: "closed"
priority: "medium"
sprint: null
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784296848609
updatedAt: 1784565918809
version: 5
---
J'aimerais que les agents aient la possibilité de créer, supprimer et editer des templates d'agent IdeA

383
.ideai/tickets/82/carnet.md Normal file
View File

@ -0,0 +1,383 @@
---
issueRef: "#82"
version: 6
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784410760329
---
# Cadrage — ticket #82
## Objectif produit
Donner à chaque agent IdeA une policy explicite d'utilisation des tools MCP IdeA. Par défaut, un agent ne doit pouvoir appeler que les tools de lecture. L'utilisateur doit ensuite pouvoir accorder ou retirer des permissions agent par agent. La surface d'édition est laissée à UX/frontend et n'est pas dans le lot backend de base.
## État réel du code
Surfaces inspectées :
- `crates/infrastructure/src/orchestrator/mcp/tools.rs` : catalogue MCP actuel, 25 tools exposés.
- `crates/infrastructure/src/orchestrator/mcp/server.rs` : `tools/call` applique déjà une policy optionnelle avant dispatch (`tool_policies.get(requester)` puis `enforce_tool_policy`).
- `crates/domain/src/agent_tool_policy.rs` : modèle existant `AgentToolPolicy { allow, bound_issue, deny_others }`.
- `crates/infrastructure/src/orchestrator/mcp/policy.rs` : `ToolPolicyRegistry` in-memory par requester.
- `crates/application/src/ticket_assistant.rs` : l'assistant de ticket pose une allowlist éphémère bornée à un ticket.
- `crates/backend/src/openai_tools.rs` : l'invoker OpenAI-compatible expose le même catalogue et dispatch via `OrchestratorService`, mais ne consulte pas la policy avant appel.
- `crates/domain/src/permission.rs` + `crates/infrastructure/src/store/permission.rs` : système permissions/sandbox existant, persisté dans `.ideai/permissions.json`, orienté fichiers/commandes/sandbox/projections CLI.
Conclusion : il existe déjà un point d'application côté MCP stdio, mais pas de policy durable par agent et pas de default-deny pour les tools d'écriture. Le modèle existant est réutilisable comme base conceptuelle, mais il est actuellement trop spécialisé pour les assistants de ticket et stocké en mémoire.
## Classification lecture / écriture des tools MCP IdeA
Lecture autorisée par défaut :
- `idea_list_agents`
- `idea_context_read`
- `idea_memory_read`
- `idea_skill_read`
- `idea_workstate_read`
- `idea_ticket_read`
- `idea_ticket_list`
- `idea_ticket_read_carnet`
- `idea_sprint_list`
Écriture / action / exécution à refuser par défaut :
- `idea_ask_agent` : délégation active, peut lancer/réattacher une cible et produire des effets indirects.
- `idea_run_in_background` : exécute une commande et crée une tâche.
- `idea_launch_agent` : lance/attache une session agent.
- `idea_stop_agent` : tue une session.
- `idea_update_context` : écrit le contexte d'un agent.
- `idea_context_propose` : écrit/propose du contexte ; avec `target` agent, c'est une écriture directe du `.md` agent.
- `idea_memory_write` : écrit la mémoire projet.
- `idea_workstate_set` : écrit la ligne live-state du requester.
- `idea_create_skill` : crée un skill.
- `idea_ticket_create`
- `idea_ticket_update`
- `idea_ticket_update_status`
- `idea_ticket_update_priority`
- `idea_ticket_update_carnet`
- `idea_ticket_link`
- `idea_ticket_unlink`
Règle de maintenance : la classification doit vivre à côté du catalogue MCP, pas dans l'UI, pour que tout nouveau tool doive choisir explicitement `read` ou `write/action`.
## Cause racine / besoin architectural
Aujourd'hui, l'absence de policy pour un requester signifie "autorisé" sur le serveur MCP, parce que l'enforcement ne s'exécute que si `registry.get(requester)` retourne une policy. C'était acceptable pour l'ancien cas ticket-assistant, où la policy éphémère était posée avant ouverture. Ce n'est pas acceptable pour des agents IdeA généraux : l'absence de configuration doit se résoudre en policy par défaut lecture seule.
Le besoin n'est pas couvert par Landlock/sandbox : Landlock protège le système de fichiers et l'exécution de commandes au niveau OS/PTY/structured process. Les tools MCP IdeA sont des capabilities applicatives internes (`memory`, `context`, `tickets`, `delegation`, `workstate`, etc.) qui doivent être refusées avant dispatch applicatif. Un sandbox peut empêcher certains effets externes, mais ne sait pas qu'un appel `idea_memory_write` ou `idea_ask_agent` est interdit.
## Modèle de données proposé
Créer une policy MCP durable par projet, sparse par agent, distincte du modèle `PermissionSet` fichiers/commandes.
Option recommandée : nouveau document `.ideai/mcp-tool-permissions.json` plutôt qu'étendre `.ideai/permissions.json`.
Raison : `.ideai/permissions.json` a un sens précis et déjà chargé : permissions de fichiers/commandes + projection CLI + compilation Landlock. Mélanger les tools MCP avec ces capabilities risquerait de rendre confus un modèle qui n'a pas le même point d'application ni la même sémantique de sandbox.
Schéma conceptuel :
```text
ProjectMcpToolPermissions {
version: 1,
projectDefault: McpToolPolicy? // absent => default lecture seule canonique
agents: Vec<AgentMcpToolPolicyOverride>
}
AgentMcpToolPolicyOverride {
agentId: AgentId,
policy: McpToolPolicy
}
McpToolPolicy {
allowedTools: Vec<String>, // noms exacts du catalogue MCP
deniedTools: Vec<String>?, // optionnel si UX veut exprimer un retrait explicite
mode: AllowListed | ReadOnlyPlus // à arbitrer avec UX, mais backend peut commencer simple
}
```
Simplification backend acceptable pour un premier lot : stocker directement `allowedTools` par agent, et résoudre ainsi :
1. si override agent existe, il remplace le défaut projet ;
2. sinon si défaut projet existe, l'utiliser ;
3. sinon utiliser `READ_ONLY_TOOLS` canonique ;
4. refuser tout tool absent de l'allowlist effective.
Le modèle doit valider les noms contre le catalogue connu ou au minimum rejeter les chaînes vides/dupliquées. Les tools inconnus ne doivent pas devenir des permissions latentes silencieuses.
## Point d'application de la policy
Point principal : `McpServer::tools_call`, avant tout dispatch, exactement à l'endroit où l'enforcement éphémère existe déjà aujourd'hui.
À faire :
- remplacer/compléter `registry.get(requester)` par une résolution effective : requester handshake -> `AgentId` -> policy durable projet -> fallback lecture seule ;
- si le requester est vide/legacy `"mcp"`, appliquer aussi le fallback lecture seule, ou refuser les écritures fail-closed ;
- refuser via erreur MCP lisible (`isError`/JsonRpcError cohérent) avant `TicketToolProvider` et avant `OrchestratorService::dispatch` ;
- publier éventuellement `OrchestratorRequestProcessed { ok: false }` pour garder une trace UI/diagnostic des refus, sans exécuter le tool.
Point secondaire obligatoire pour éviter le contournement : `AppOpenAiToolInvoker` doit appliquer la même résolution avant `map_tool_call`/`dispatch`. C'est le chevauchement direct avec #62 : #62 demande déjà la parité OpenAI-compatible + identité requester explicite. #82 dépend fonctionnellement de ce point ; sinon un agent utilisant un profil OpenAI-compatible pourrait contourner la policy MCP stdio.
## Relation avec #62 et #60
#60 a fermé le bug de conversation muette et a sorti #62 comme dette sécurité adjacente.
#62 reste pertinent et doit être traité avant ou dans le premier lot de #82 :
- passer une identité requester explicite aux sessions structurées ;
- `LaunchAgent` doit utiliser l'agent id comme requester, pas une dérivation fragile du run dir ;
- l'assistant de ticket doit garder `ticket-assistant:<project>:<issue>` ;
- l'invoker OpenAI-compatible doit consulter la même policy que le serveur MCP stdio.
#82 généralise ensuite la policy à tous les agents déclarés, avec un défaut lecture seule durable. Les policies éphémères du ticket-assistant peuvent rester comme cas spécial plus restrictif/borné à un ticket, mais elles ne doivent pas masquer la policy globale agent si un assistant normal est lancé.
## Frontières avec le système permissions/sandbox existant
À ne pas faire dans #82 :
- ne pas modifier la compilation Landlock ;
- ne pas ajouter de `Capability::McpTool` dans le modèle fichiers/commandes sans arbitrage Architecture ;
- ne pas projeter cette policy dans les settings Claude/Codex ;
- ne pas compter sur les prompts natifs des CLIs pour autoriser/refuser les tools IdeA.
Le système existant reste responsable de ce que le process agent peut faire au niveau OS. #82 est une policy applicative IdeA, appliquée côté serveur/bridge avant use case.
## Découpage recommandé
### Lot B1 — Domaine + catalogue + store durable
- Ajouter un modèle pur `McpToolPermissionPolicy` / `ProjectMcpToolPermissions` avec fallback lecture seule.
- Déplacer la classification read/write dans une source backend canonique proche du catalogue MCP.
- Ajouter un port `McpToolPermissionStore` et un store FS sous `.ideai/mcp-tool-permissions.json`.
- Tests domaine : fallback lecture seule, override agent, refus tool inconnu, nouveau catalogue sans classification explicite détecté par test.
### Lot B2 — Enforcement MCP stdio
- Injecter le resolver/store dans `McpServer` ou dans un service de policy appelé par `tools_call`.
- Appliquer la policy avant ticket provider et avant orchestrator dispatch.
- Remplacer le comportement "pas de policy => tout passe" par "pas de policy => lecture seule" pour les agents généraux.
- Garder la policy ticket-assistant bornée au ticket comme restriction éphémère additionnelle ou cas de requester dédié.
- Tests : agent sans override peut `idea_memory_read`/`idea_ticket_list`, mais pas `idea_memory_write`, `idea_ask_agent`, `idea_ticket_update_carnet`, `idea_run_in_background`.
### Lot B3 — Parité OpenAI-compatible / dépendance #62
- Faire passer l'identité requester explicite jusqu'à tous les appels tools structurés.
- Brancher la même policy resolver dans `AppOpenAiToolInvoker`.
- Tests : un profil OpenAI-compatible refusé sur `idea_memory_write` l'est de la même manière que via MCP stdio ; un tool lecture passe.
### Lot B4 — API backend pour future UI
- Ajouter des use cases read/update de permissions MCP par agent/projet.
- Ajouter DTO/commands Tauri ou endpoints web selon la surface existante.
- Ne pas concevoir l'UI ici ; seulement exposer un contrat stable à UX/DevFrontend.
### Lot UX/F — séparé
- UX décide la surface de modification agent par agent.
- Frontend consomme les APIs B4.
## Rétrocompatibilité
Agents déjà déclarés : aucun champ à ajouter dans `agents.json`. En absence de document `.ideai/mcp-tool-permissions.json` ou d'override agent, ils deviennent lecture seule pour les tools MCP IdeA. C'est un changement volontaire demandé par l'utilisateur.
Attention migration : des workflows existants qui s'appuient sur `idea_ask_agent`, `idea_memory_write`, `idea_context_propose`, `idea_workstate_set` ou `idea_ticket_update_carnet` devront être explicitement autorisés agent par agent après livraison. Pour limiter la casse pendant le développement, prévoir un message de refus clair indiquant le tool refusé et l'agent/requester concerné.
## Critères d'acceptation backend
- Un agent sans override ne peut appeler que les tools listés en lecture.
- Les tools d'écriture/action sont refusés avant effet applicatif.
- Une allowlist agent permet explicitement un tool d'écriture choisi.
- Un retrait/absence d'allowlist retire effectivement le droit au prochain appel, sans relancer l'application si possible.
- Le serveur MCP stdio et l'invoker OpenAI-compatible appliquent la même décision.
- Les permissions filesystem/commandes et le sandbox Landlock restent inchangés.
## Conception UX/F — surface permissions MCP par agent
### Décision de placement
La modification des permissions MCP IdeA vit dans le panneau projet `Permissions`, pas dans `Settings` et pas uniquement dans la fiche d'un agent.
Raison UX : ce réglage est une matrice de capacités applicatives par agent dans le projet courant. Il doit être consultable et comparable au même endroit que les permissions/sandbox existantes, sans polluer les paramètres globaux desktop (`Settings`) ni cacher un droit critique dans une fiche agent isolée. La fiche/liste d'agent peut afficher un raccourci ou un badge, mais l'édition canonique reste `Permissions`.
Le panneau `Permissions` devient une surface à deux onglets internes :
- `Système` : permissions fichiers/commandes/sandbox existantes.
- `Tools MCP IdeA` : nouveau réglage #82.
La colonne de gauche reste le sélecteur de cible : `Défaut projet`, puis les agents. Le panneau de droite change selon l'onglet sélectionné.
### Layout attendu
```text
Permissions
[ Système ] [ Tools MCP IdeA ] [Actualiser]
┌──────────────────────────────┬──────────────────────────────────────────────┐
│ Défaut projet │ Tools MCP IdeA — DevFrontend │
│ Lecture seule │ Hérite du défaut projet │
│ │ [Utiliser le défaut projet v] │
│ Agents │ │
│ Main Hérité │ Résumé effectif │
│ Architect Override │ 9 lecture autorisés · 2 écriture autorisés │
│ DevFrontend Override │ │
│ QA Hérité │ Accord rapide │
│ Git Hérité │ [ ] Déléguer à un agent │
│ │ [x] Modifier les tickets │
│ │ [ ] Écrire la mémoire │
│ │ │
│ │ Détail des tools │
│ │ ▾ Lecture, autorisés par défaut (9) │
│ │ ✓ idea_ticket_read │
│ │ ✓ idea_context_read │
│ │ ▾ Écriture et actions (16) │
│ │ Tickets │
│ │ [x] idea_ticket_update_carnet │
│ │ [ ] idea_ticket_update_status │
│ │ Agents │
│ │ [ ] idea_ask_agent │
│ │ [ ] idea_launch_agent │
│ │ [Réinitialiser l'override] [Enregistrer] │
└──────────────────────────────┴──────────────────────────────────────────────┘
```
Sur desktop large : deux colonnes comme le panneau permissions actuel, avec la liste des cibles à gauche et l'éditeur à droite. Sur largeur contrainte : la cible sélectionnée reste au-dessus de l'éditeur, puis les groupes de tools s'empilent ; les actions restent en bas du panneau, non flottantes.
### Modèle mental affiché
L'utilisateur ne manipule pas une liste brute de 25 cases. Il voit trois niveaux :
1. Cible : `Défaut projet` ou un agent précis.
2. Mode : `Utiliser le défaut projet` ou `Override personnalisé`.
3. Capabilités groupées : lecture, tickets, agents, contexte, mémoire, workstate, skills, exécution.
Pour un agent sans override, l'éditeur est en lecture de l'état hérité jusqu'à ce que l'utilisateur choisisse `Créer un override`. Les contrôles hérités sont visibles mais atténués, avec la mention `Hérité du défaut projet`. Cela permet de comprendre l'état effectif avant de modifier.
Pour un agent avec override, le badge de la colonne gauche affiche `Override`. Le panneau de droite affiche `Override personnalisé` et un bouton `Réinitialiser l'override` qui remet l'agent sur le défaut projet.
### Présentation du catalogue
Les tools sont groupés par domaine fonctionnel, à partir des métadonnées du catalogue retourné par `get_mcp_tool_permissions` si disponibles côté backend, sinon par mapping frontend local strictement présentationnel. La classification `read`/`write` reste backend-canonique.
Groupes UX recommandés :
- `Lecture projet` : `idea_list_agents`, `idea_context_read`, `idea_memory_read`, `idea_skill_read`, `idea_workstate_read`.
- `Lecture tickets` : `idea_ticket_read`, `idea_ticket_list`, `idea_ticket_read_carnet`, `idea_sprint_list`.
- `Délégation agents` : `idea_ask_agent`, `idea_launch_agent`, `idea_stop_agent`.
- `Contexte et mémoire` : `idea_update_context`, `idea_context_propose`, `idea_memory_write`.
- `Tickets` : `idea_ticket_create`, `idea_ticket_update`, `idea_ticket_update_status`, `idea_ticket_update_priority`, `idea_ticket_update_carnet`, `idea_ticket_link`, `idea_ticket_unlink`.
- `Travail et exécution` : `idea_run_in_background`, `idea_workstate_set`.
- `Skills` : `idea_create_skill`.
Chaque groupe affiche un compteur : `3/7 autorisés`, et peut être replié/déplié. Les groupes lecture sont ouverts par défaut dans `Défaut projet`, mais repliés par défaut dans l'édition agent pour réduire le bruit. Les groupes écriture/action sont ouverts par défaut, car ce sont les décisions à risque.
Chaque ligne de tool contient :
- le nom exact monospace (`idea_ticket_update_carnet`) ;
- un libellé humain court (`Modifier le carnet d'un ticket`) ;
- un badge `Lecture` ou `Écriture` ;
- un état `Autorisé`, `Refusé`, ou `Hérité` ;
- une case à cocher uniquement quand la cible est éditable.
Les checkboxes sont réservées aux tools individuels. Les groupes utilisent un bouton discret `Tout autoriser dans ce groupe` / `Tout retirer dans ce groupe`, jamais une checkbox tri-state ambiguë.
### Défaut projet vs overrides agent
Le `Défaut projet` est le point de départ appliqué à tous les agents sans override. Son état initial est `Lecture seule` : tous les tools classifiés lecture sont autorisés, tous les tools écriture/action sont refusés.
Pour un agent, afficher explicitement :
- `Hérite du défaut projet` si aucun override n'existe.
- `Override personnalisé` si une allowlist agent existe.
- `Diffère du défaut : +2 écriture, -1 lecture` quand l'API permet de comparer l'allowlist effective au défaut.
Dans les lignes de tool agent :
- un tool hérité autorisé affiche une coche grisée + `Hérité` ;
- un tool ajouté par override affiche une coche active + badge `Ajouté` ;
- un tool retiré par override affiche une case vide + badge `Retiré` si le backend expose une notion de retrait par remplacement complet ; sinon afficher simplement l'état effectif `Refusé`.
Important : comme l'API `update_agent_mcp_tool_permissions` accepte une allowlist complète de noms de tools, l'UI doit traiter l'override agent comme un remplacement de l'état effectif, pas comme une série de patches implicites. Au moment où l'utilisateur crée un override depuis l'état hérité, la draft est préremplie avec l'allowlist effective courante.
### Parcours principal — accorder un tool d'écriture
1. L'utilisateur ouvre le panneau `Permissions` depuis la barre de panneaux projet.
2. Il sélectionne l'onglet `Tools MCP IdeA`.
3. Il clique l'agent cible dans la colonne gauche, par exemple `DevFrontend`.
4. Si l'agent hérite du défaut, il clique `Créer un override`. La liste devient éditable et reprend l'état effectif actuel.
5. Il ouvre le groupe concerné, par exemple `Tickets`.
6. Il coche `idea_ticket_update_carnet — Modifier le carnet d'un ticket`.
7. Le résumé en haut passe à `9 lecture autorisés · 1 écriture autorisé` et une barre d'actions affiche `Modifications non enregistrées`.
8. Il clique `Enregistrer`.
9. Après succès, le badge de l'agent passe à `Override` et la ligne du tool affiche `Ajouté`.
### Parcours principal — retirer un tool d'écriture
1. L'utilisateur sélectionne un agent avec badge `Override`.
2. Il ouvre le groupe contenant le tool autorisé.
3. Il décoche le tool d'écriture.
4. Le résumé et le compteur du groupe se mettent à jour immédiatement dans la draft.
5. Il clique `Enregistrer`.
6. Si l'override devient identique au défaut projet, proposer après sauvegarde de le nettoyer avec une action secondaire `Supprimer l'override inutile`. Ne pas le faire automatiquement sans retour visuel.
### Défaut projet
Le défaut projet est éditable dans le même onglet, mais avec une friction légère pour les tools d'écriture/action : quand l'utilisateur active un tool d'écriture au niveau défaut projet, afficher une confirmation inline avant sauvegarde :
`Ce tool sera autorisé pour tous les agents sans override. Confirmer cette modification ?`
Cette confirmation ne bloque pas l'édition agent par agent, car le cas utilisateur principal est d'accorder des tools d'écriture spécifiques à un agent donné.
### États et feedback
- Chargement : skeleton compact dans la colonne cible et dans les groupes, pas de spinner plein écran.
- Erreur de chargement : message inline en haut du panneau avec bouton `Réessayer`.
- Erreur de sauvegarde : conserver la draft locale, afficher l'erreur au-dessus des actions, garder `Enregistrer` disponible.
- Aucune agent : état vide dans la colonne gauche `Aucun agent dans ce projet.` ; le défaut projet reste éditable.
- Tool inconnu dans une allowlist existante : afficher dans un groupe `Tools inconnus` avec badge `Inconnu`, désactivé par défaut, et demander à DevFrontend de ne pas permettre de ré-enregistrer silencieusement une permission inconnue comme si elle était valide. Si le backend rejette les inconnus, afficher l'erreur telle quelle.
- Modifications non enregistrées : actions `Annuler` et `Enregistrer` visibles dans l'éditeur ; changement de cible avec draft modifiée demande confirmation.
- Sauvegarde réussie : feedback discret `Permissions enregistrées` pendant environ 2 secondes.
### Accessibilité
- Les onglets `Système` / `Tools MCP IdeA` utilisent `role="tablist"`, `role="tab"`, `aria-selected` et navigation clavier gauche/droite.
- Chaque groupe repliable expose un bouton avec `aria-expanded` et un nom incluant le compteur, par exemple `Tickets, 1 sur 7 autorisé`.
- Chaque checkbox a un label complet incluant le libellé humain et le nom du tool, par exemple `Modifier le carnet d'un ticket, idea_ticket_update_carnet`.
- Les badges couleur (`Lecture`, `Écriture`, `Hérité`, `Override`) ne doivent jamais être le seul signal : le texte doit porter l'information.
- Cibles tactiles et souris : 32 px minimum pour les lignes compactes, 40 px pour les actions principales.
- Focus visible sur onglets, lignes de cible, boutons de groupe, checkboxes et actions.
### Ton et libellés
Conformément à la règle UX #78, les libellés humains sont en français. Les noms exacts des tools restent en anglais/monospace car ce sont des identifiants techniques.
Libellés principaux :
- `Permissions`
- `Système`
- `Tools MCP IdeA`
- `Défaut projet`
- `Lecture seule`
- `Hérite du défaut projet`
- `Override personnalisé`
- `Créer un override`
- `Réinitialiser l'override`
- `Modifications non enregistrées`
- `Annuler`
- `Enregistrer`
- `Autorisé`
- `Refusé`
- `Hérité`
- `Ajouté`
- `Retiré`
### Critères d'acceptation UX/frontend
- Les permissions MCP sont éditables depuis le panneau projet `Permissions`, onglet `Tools MCP IdeA`.
- L'utilisateur peut sélectionner `Défaut projet` ou un agent dans une colonne/listing de cibles.
- Un agent sans override affiche clairement qu'il hérite du défaut projet, et ses contrôles ne deviennent éditables qu'après `Créer un override`.
- Un agent avec override est identifiable dans la liste par un badge textuel `Override`.
- Les ~25 tools ne sont pas affichés comme une liste plate : ils sont groupés par domaine, avec compteurs et sections repliables.
- Les tools lecture et écriture/action sont distingués par badges textuels et par hiérarchie visuelle.
- Le parcours d'autorisation d'un tool d'écriture agent par agent prend au maximum : ouvrir `Permissions`, onglet `Tools MCP IdeA`, choisir l'agent, créer/éditer l'override, cocher le tool, enregistrer.
- Le retrait d'un tool d'écriture existant est symétrique : décocher puis enregistrer.
- Les changements non sauvegardés sont visibles et protégés lors d'un changement de cible.
- L'UI consomme `get_mcp_tool_permissions`, `update_project_mcp_tool_permissions`, `update_agent_mcp_tool_permissions` sans hardcoder la classification lecture/écriture comme source de vérité métier.
- Aucun changement de backend ou d'i18n n'est requis pour ce lot frontend.

View File

@ -0,0 +1,16 @@
---
id: "4904e380-b9a8-49d4-a06f-032024909be4"
number: 82
title: "Ajouter des permissions d'utilisations des tools MCP d'IdeA par agent"
status: "closed"
priority: "critical"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784296923284
updatedAt: 1784410760329
version: 6
---
J'aimerais avoir la possibilité de modifier les permissions d'utilisation des outils MCP IdeA de mes agents IdeA. J'aimerais que par défaut seuls les outils MCP de lectures soient autorisés, puis j'aimerais avoir un moyen (a faire determiner par l'agent UX), de modifier ces permissions agent par agent, et autoriser ou enlever des permissions sur les tools MCP proposés par IdeA

222
.ideai/tickets/83/carnet.md Normal file
View File

@ -0,0 +1,222 @@
---
issueRef: "#83"
version: 5
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784567952845
---
# Cadrage technique — contrainte pour UX
## Constats dans le code existant
La fermeture de la fenêtre principale est déjà interceptée côté Rust/Tauri dans `crates/app-tauri/src/lib.rs` via `window.on_window_event(...)` et `tauri::WindowEvent::CloseRequested`.
Le handler actuel ne bloque pas la fermeture : il exécute directement le teardown applicatif au moment du `CloseRequested` :
- snapshot des fenêtres ouvertes (`SnapshotOpenWindowsInput`) ;
- snapshot des agents en cours (`SnapshotRunningAgentsInput`) pour persister `agent_was_running` avant destruction des PTY ;
- kill de tous les handles PTY vivants ;
- arrêt des serveurs de modèles locaux ;
- arrêt du serveur embedded ;
- fermeture des fenêtres webview secondaires.
Ce flux correspond aux livraisons liées à la fermeture globale de l'application (#39) et aux fenêtres détachées (#50). Les fenêtres détachées ont aussi un handler `CloseRequested`, mais il sert seulement à émettre le lifecycle `closed` pour le panneau concerné ; il ne doit pas porter la confirmation globale.
Point important pour UX/dev : une interception uniquement côté React avec `onCloseRequested` serait fragile tant que le handler Rust actuel continue à faire le teardown immédiatement. Même si le frontend appelle `preventDefault`, le handler Rust peut déjà avoir snapshot/kill les sessions. Il faut donc déplacer/garder la décision de fermeture dans le flux backend, ou au minimum rendre le handler Rust conscient du guard.
## Interception de fermeture recommandée
Le point d'application robuste est le handler Rust de la fenêtre `main` :
1. Sur `WindowEvent::CloseRequested { api, .. }`, calculer si un travail détectable est en cours.
2. Si aucun travail n'est en cours, laisser passer le chemin actuel de shutdown.
3. Si du travail est en cours et qu'aucune confirmation n'a déjà été donnée, appeler `api.prevent_close()` / équivalent Tauri v2, puis demander au frontend d'afficher la popup UX.
4. Si l'utilisateur confirme, relancer une fermeture programmatique via une commande backend dédiée, avec un flag `confirmed_exit`/`exit_guard_bypassed` pour éviter une boucle de confirmation.
5. Le shutdown réel doit réutiliser exactement l'ordre actuel : snapshot fenêtres, snapshot agents, kill PTY, stop model servers, stop embedded server, fermeture des fenêtres secondaires.
Implémentation conseillée : extraire le teardown actuellement inline dans `lib.rs` vers une fonction/service interne réutilisable, par exemple `shutdown_app_after_confirm(app_handle)`. La commande appelée après confirmation doit soit :
- positionner le bypass puis appeler `main.close()` pour repasser par le handler et exécuter le teardown partagé ;
- soit exécuter explicitement le teardown partagé puis quitter l'application.
À éviter : appeler `destroy()` côté frontend ou contourner le handler Rust, car cela risquerait de sauter le snapshot `agent_was_running` et le nettoyage PTY/serveurs.
## Définition technique de « travail en cours »
Définition fiable recommandée pour déclencher la confirmation :
- au moins un agent a `busy.state == "busy"` dans le workstate ;
- ou au moins une tâche de fond non terminale existe : `Queued`, `Running` ou `Waiting` dans `BackgroundTaskState`.
Signaux disponibles et niveau de fiabilité :
- `GetProjectWorkState` / commande Tauri `get_project_work_state(project_id)` : source déjà agrégée par projet. Elle expose les agents du manifeste, leur `live`, leur `busy`, leurs tickets, leurs tâches de fond et les conversations résumées.
- `AgentBusyState` (`crates/domain/src/input.rs`) : signal le plus fiable pour un tour d'agent réellement en vol. `Busy { ticket, since_ms }` signifie qu'un tour a démarré et n'a pas encore rendu la main.
- `BackgroundTaskStore` / `BackgroundTaskState` (`crates/domain/src/background_task.rs`) : fiable pour les travaux asynchrones. Les états non terminaux sont `Queued`, `Running`, `Waiting`. Les états `Completed`, `Failed`, `Cancelled`, `Expired` sont terminaux et ne doivent pas compter comme « travail en cours » pour éviter les faux positifs. Les complétions non livrées peuvent mériter une notification UX, mais elles ne sont plus un travail actif.
- `LiveAgentReadModel` / champ `live` du workstate : indique une session agent vivante, pas nécessairement active. Une session vivante peut être idle ; ne pas l'utiliser seule comme déclencheur, sauf arbitrage produit explicite « prévenir dès qu'une session agent serait arrêtée ».
- PTY actifs (`PtyBridge.active_sessions()` / registre `terminal_sessions`) : utile pour le teardown et le snapshot, mais mauvais critère UX seul. Un terminal shell ouvert peut être idle ; compter tous les PTY provoquerait beaucoup de confirmations inutiles.
- Sessions structurées/chat (`ChatBridge.active_sessions()` ou sessions structurées ouvertes) : une session ouverte seule ne prouve pas un travail actif. À inclure seulement si un état backend indique un tour structuré en cours. Si ce signal n'est pas encore centralisé, il doit être ajouté au même snapshot backend plutôt que déduit depuis l'existence de la session.
Donc, pour la première livraison, le critère technique le plus défendable est :
`has_work_in_progress = any(agent.busy.is_busy()) || any(background_task.state in {Queued, Running, Waiting})`
Option produit à arbitrer avec UX/PO : faut-il aussi prévenir lorsqu'il existe des agents/PTY simplement vivants mais idle, car ils seront arrêtés à la fermeture ? Techniquement possible, mais cela change le sens de la popup de « travail en cours » vers « sessions ouvertes qui vont être interrompues » et augmente les faux positifs.
## Frontend pur ou aller-retour backend ?
Ce n'est pas un changement purement frontend.
La popup et son wording relèvent bien de l'UX/frontend, mais la décision fiable doit faire un aller-retour backend, idéalement depuis le handler de fermeture Rust lui-même, pour trois raisons :
1. Le backend possède la vérité complète sur tous les projets ouverts via `AppState::open_project_ids()`. Le frontend ne voit pas forcément un état frais pour tous les onglets/projets, notamment si seul le projet actif rafraîchit son `useProjectWorkState`.
2. Les tâches de fond et l'état `busy` sont des états applicatifs/backend. Lire un cache frontend peut rater une tâche démarrée hors vue active ou compter un état obsolète.
3. Le handler Rust actuel exécute déjà le teardown au `CloseRequested`. Il doit donc être modifié pour empêcher la fermeture avant teardown lorsque la confirmation est requise.
Surface backend proposée :
- ajouter un use case/commande de lecture `get_app_exit_work_guard_state` ou équivalent, qui agrège tous les `ProjectWorkState` des `open_project_ids()` et retourne un résumé minimal pour la popup : `hasWorkInProgress`, nombre d'agents busy, nombre de tâches de fond actives, éventuellement noms/projets pour affichage si UX le souhaite ;
- utiliser cette même logique dans le handler `CloseRequested` de `main` avant d'appeler le teardown ;
- exposer une commande `confirm_app_exit` / `request_app_exit_after_confirmation` qui bypass le guard et exécute le shutdown existant.
Frontière de lot :
- Backend/Tauri nécessaire : guard `CloseRequested`, agrégation fiable du work in progress, bypass confirmé, factorisation du teardown existant.
- Frontend/UX : rendu de la popup, libellés, hiérarchie d'information, boutons, éventuel détail des agents/tâches concernés.
- Aucun impact backend métier profond attendu : on réutilise `GetProjectWorkState`, `AgentBusyState`, `BackgroundTaskStore`, `open_project_ids()` et le shutdown existant. Pas d'impact DB/schema prévu, sauf si l'on choisit de persister une préférence utilisateur du type « ne plus demander » (hors demande actuelle).
## Critères d'acceptation techniques proposés
- Fermer la fenêtre principale sans travail actif garde le comportement actuel : snapshot, kill PTY, arrêt serveurs, fermeture globale.
- Fermer la fenêtre principale avec au moins un agent `Busy` empêche la fermeture et déclenche la demande de confirmation avant tout kill PTY.
- Fermer la fenêtre principale avec au moins une tâche de fond `Queued`/`Running`/`Waiting` empêche la fermeture et déclenche la confirmation.
- Annuler la popup laisse l'application et les sessions intactes : aucun PTY tué, aucun snapshot de fermeture forcé, serveurs toujours actifs.
- Confirmer exécute le shutdown existant dans le même ordre qu'aujourd'hui.
- Les fenêtres détachées ne déclenchent pas la confirmation globale ; seule la fenêtre `main` porte ce guard.
## Conception UX — confirmation de fermeture avec travail en cours
### Décision produit
Afficher une popup modale uniquement lorsque la fermeture de la fenêtre principale d'IdeA interrompt un travail actif détecté par le guard technique : agents en tour `busy` et/ou tâches de fond non terminales (`Queued`, `Running`, `Waiting`). Ne pas déclencher la popup pour une session agent simplement vivante mais idle dans la première livraison : le libellé demandé parle de « travail en cours », et compter les sessions ouvertes créerait trop de confirmations inutiles.
Il ne doit pas y avoir d'option `Ne plus avertir`. Cette confirmation protège contre une perte ou interruption volontairement coûteuse ; la rendre désactivable localement affaiblit la sécurité produit et crée une préférence à maintenir. Le seul bypass est l'action explicite `Quitter quand même` pour cette tentative de fermeture.
### Rôle de la popup
La popup doit communiquer trois choses, dans cet ordre :
1. IdeA a détecté du travail actif.
2. Quitter maintenant interrompra ce travail.
3. L'utilisateur peut annuler pour revenir à l'application, ou confirmer une fermeture volontaire.
La popup ne doit pas essayer de prédire si le travail est récupérable. Elle ne promet pas de sauvegarde, de reprise ou de livraison du résultat après fermeture. Elle indique seulement l'effet immédiat : les agents et tâches en cours seront interrompus pendant la fermeture.
### Libellé exact
Titre : `Du travail est encore en cours`
Corps, version avec un seul élément actif :
`1 travail actif sera interrompu si vous quittez IdeA maintenant.`
Corps, version plurielle :
`{count} travaux actifs seront interrompus si vous quittez IdeA maintenant.`
Phrase secondaire :
`Annulez la fermeture pour laisser les agents et les tâches se terminer.`
Si l'agrégat distingue les types, préférer une phrase plus informative :
- `1 agent travaille encore.`
- `{agentCount} agents travaillent encore.`
- `1 tâche de fond est encore active.`
- `{taskCount} tâches de fond sont encore actives.`
Exemple combiné :
`2 agents travaillent encore et 1 tâche de fond est encore active. Ces travaux seront interrompus si vous quittez IdeA maintenant.`
Actions :
- Action principale, non destructive : `Annuler`
- Action destructive secondaire : `Quitter quand même`
Ordre visuel recommandé : `Annuler` à gauche ou en premier, `Quitter quand même` à droite ou en dernier avec style danger. Le focus initial va sur `Annuler`.
### Détail affiché
Afficher un résumé court visible immédiatement :
- `{agentCount} agent(s) en cours`
- `{backgroundTaskCount} tâche(s) de fond active(s)`
Afficher ensuite une liste compacte des éléments concernés, limitée à 5 lignes maximum pour garder la popup lisible. Si plus de 5 éléments sont actifs, afficher les 5 premiers puis `+ {remainingCount} autre(s)`.
Format des lignes :
- Agent busy : `Agent {agentName} — {projectName}` ; si un ticket est connu, ajouter ` — #{ticketNumber}`.
- Tâche de fond : `{taskLabel} — {projectName}` ; si aucun libellé humain n'est disponible, utiliser `Tâche de fond {shortTaskId}`.
Exemples :
```text
Du travail est encore en cours
2 agents travaillent encore et 1 tâche de fond est encore active. Ces travaux seront interrompus si vous quittez IdeA maintenant.
Agents
• DevFrontend — IdeA — #82
• QA — IdeA
Tâches de fond
• npm test — IdeA
[Annuler] [Quitter quand même]
```
Si les noms ne sont pas disponibles côté backend au moment du guard, afficher seulement les compteurs. Ne pas bloquer la feature UX sur l'affichage détaillé, mais le contrat backend recommandé est de fournir au moins `projectName`, `agentName` et un label de tâche quand disponibles.
### États et interactions
- Ouverture : la popup apparaît après tentative de fermeture, avant tout teardown.
- `Annuler` : ferme la popup, ne ferme pas l'application, ne tue aucune session, ne modifie aucun état métier.
- `Échap` : équivalent à `Annuler`.
- Clic hors popup : désactivé ou équivalent à `Annuler`, selon le composant modal existant ; préférence UX : ne pas fermer par clic extérieur pour éviter une décision ambiguë.
- `Quitter quand même` : lance le flux backend confirmé. Pendant l'appel, désactiver les deux boutons et afficher l'état `Fermeture…` sur le bouton danger.
- Échec de la fermeture confirmée : rester dans la popup et afficher un message d'erreur inline : `IdeA n'a pas pu quitter correctement. Réessayez ou consultez les logs.` Le bouton `Quitter quand même` redevient disponible.
- Si le travail se termine pendant que la popup est ouverte, ne pas fermer automatiquement la popup. Actualiser le résumé si l'événement arrive facilement ; sinon garder l'état initial. L'utilisateur peut annuler puis fermer à nouveau sans confirmation.
### Hiérarchie visuelle
La popup est une alerte de confirmation destructive, pas un panneau de diagnostic :
- largeur cible : 440 à 520 px ;
- titre en `text-content`, taille modale standard ;
- résumé en texte normal, pas en rouge ;
- action `Quitter quand même` en variante danger ;
- liste des éléments dans un bloc compact `bg-raised` ou équivalent, sans tableau ;
- éviter les grands paragraphes et les détails techniques (`busy.state`, `Queued`, ids longs).
### Accessibilité
- Utiliser un vrai dialogue modal avec `role="alertdialog"` ou le composant modal existant configuré comme confirmation destructive.
- `aria-labelledby` pointe sur le titre `Du travail est encore en cours`.
- `aria-describedby` pointe sur le résumé de l'impact.
- Focus initial sur `Annuler`.
- Tabulation piégée dans la popup tant qu'elle est ouverte.
- `Entrée` active le bouton focusé, pas automatiquement `Quitter quand même`.
- `Échap` annule.
- Les compteurs et détails ne doivent pas dépendre uniquement de la couleur.
### Critères d'acceptation UX/frontend
- La popup n'apparaît que pour la fermeture de la fenêtre principale avec travail actif détecté.
- Le titre exact est `Du travail est encore en cours`.
- La popup indique le nombre total de travaux actifs et, quand disponible, le détail agents/tâches limité à 5 lignes.
- Les actions exactes sont `Annuler` et `Quitter quand même`.
- `Annuler` est l'action par défaut/focus initial et laisse l'application intacte.
- `Quitter quand même` est visuellement destructive et déclenche le flux backend confirmé.
- Aucune option `Ne plus avertir` n'est proposée.
- Les libellés visibles sont en français conformément à la décision UX #78.

View File

@ -0,0 +1,16 @@
---
id: "18a92510-3af4-4aef-84e8-e23cd7422118"
number: 83
title: "Popup pour vérifier la volonté de quitter l'app"
status: "closed"
priority: "medium"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784360573747
updatedAt: 1784567952845
version: 5
---
Dans le cas ou un travail est en cours, j'aimerais qu'une popup qui demande la confirmation qu'on veut quitter IdeA malgré le travail en cours, apparaisse

View File

@ -0,0 +1,6 @@
---
issueRef: "#84"
version: 1
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784377833773
---

View File

@ -0,0 +1,20 @@
---
id: "beab1811-2fd7-4563-ac39-6a354fbd1feb"
number: 84
title: "[Bug] Reprise auto après limite de session ne se déclenche pas pour l'orchestrator (Main, profil Claude)"
status: "open"
priority: "high"
sprint: null
links: [{"target":"#7","kind":"relatesTo"},{"target":"#15","kind":"relatesTo"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784377833773
updatedAt: 1784377833773
version: 1
---
Rapporté par l'utilisateur (2026-07-18) sur l'AppImage courante (buildée depuis develop, contient #7 baseline + #30 fix chemin direct Main) : quand l'orchestrator (Main) atteint une limite de session sur un profil Claude, la reprise automatique au reset n'a PAS lieu en pratique, alors que la feature #7 (mergée d7041c5, 2026-06-17) et le fix #30 dédié précisément au cas "Main qui limite session" (commit 9430c65, 2026-07-13, "brancher le handle de limite sur le chemin direct") sont censés couvrir ce cas.
Écart constaté : design/tests verts ≠ comportement live observé par l'utilisateur. À investiguer : soit régression depuis 9430c65, soit un chemin non couvert par les tests d'intégration existants (session_limit_wiring.rs), soit une limite du "EN MEMOIRE uniquement" (mémoire session-limit-handling-design : le réveil auto ne joue que tant qu'IdeA reste ouvert — si l'utilisateur a fermé/rouvert IdeA entre la limite et le reset, c'est le comportement attendu, pas un bug).
Première question à trancher par Architect : est-ce une vraie régression du chemin direct Main, ou un cas hors-couverture connu (IdeA fermé/rouvert pendant la fenêtre de reset) ? Nécessite de faire préciser à l'utilisateur les conditions exactes de repro (IdeA resté ouvert ou non pendant l'attente du reset).

View File

@ -0,0 +1,6 @@
---
issueRef: "#85"
version: 1
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784379436323
---

View File

@ -0,0 +1,27 @@
---
id: "34b23044-4b82-4e7e-b999-cca7cbef7861"
number: 85
title: "Test flaky : setCellAgent « changing the agent kills the previous PTY (Bug #3) » échoue en exécution shuffled"
status: "open"
priority: "low"
sprint: null
links: [{"target":"#79","kind":"relatesTo"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784379436323
updatedAt: 1784379436323
version: 1
---
Découvert en marge de #79 (DevFrontend puis confirmé par QA sur `npx vitest run --sequence.shuffle`, ~2 échecs sur 3 passages) : `src/features/layout/setCellAgent.test.tsx` → « changing the agent kills the previous PTY (Bug #3) ».
```
FAIL src/features/layout/setCellAgent.test.tsx
changing the agent kills the previous PTY (Bug #3)
AssertionError: expected "closeTerminal" to be called with arguments: [ 'old-session' ]
Number of calls: 0
```
Vert isolément (`npx vitest run src/features/layout/setCellAgent.test.tsx` → 9/9) et vert en suite complète non-shuffled (850/850). Rouge uniquement en ordre shuffled — dépendance d'ordre/état partagé entre fichiers de test, pas un bug du code applicatif a priori (à confirmer). Sans rapport avec #78/#79, ne pas mélanger.
Attendu : identifier la source de la dépendance d'ordre (état partagé, mock non réinitialisé, timer) et rendre le test déterministe, comme pour #79 — ne pas neutraliser ni élargir un timeout pour faire taire.

336
.ideai/tickets/86/carnet.md Normal file
View File

@ -0,0 +1,336 @@
---
issueRef: "#86"
version: 7
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784612705899
---
# Conception UX — tickets et sprints dans le client web
## Intention
La version web doit permettre de consulter et modifier les tickets/sprints sans reproduire le shell desktop. Le client web existant est une surface autonome en colonne verticale unique : après appairage, l'utilisateur choisit un projet, puis voit l'état live et les cellules agents. #86 doit ajouter une surface de pilotage tickets/sprints cohérente avec ce modèle mobile/web, pas importer les docks, fenêtres flottantes et overlays desktop.
Décision UX : réutiliser les contrats métier et les hooks transport-neutres autant que possible (`useTickets`, `useTicketDetail`, recherche, filtres, événements live), mais créer une présentation web dédiée. Ce n'est pas une version appauvrie en capacités, c'est une adaptation de navigation et de densité.
La cible prioritaire web/mobile est l'intervention rapide : lire, trier, changer statut/priorité, corriger titre/description/carnet, créer un ticket simple, assigner/désassigner, déplacer dans un sprint, créer/renommer/supprimer un sprint. Les opérations complexes restent disponibles mais rangées derrière des panneaux dédiés : liens entre tickets, carnet long, gestion complète des sprints.
## Placement dans le client web
Dans `WebWorkspace`, après ouverture d'un projet, ajouter une navigation de projet compacte au-dessus des surfaces projet :
```text
Projet: IdeA [Rafraîchir]
[ Live ] [ Tickets ] [ Sprints ]
```
- `Live` : surface actuelle `État live`, inchangée dans son rôle.
- `Tickets` : nouvelle liste et édition des tickets.
- `Sprints` : gestion des sprints.
Sur mobile, ces onglets sont des boutons segmentés scrollables horizontalement si nécessaire. Sur desktop web large, ils restent dans la même colonne contrainte, pas de dock ni layout grid.
Ne pas placer les tickets sous la liste des agents : ce sont deux surfaces de projet au même niveau. L'utilisateur web doit pouvoir passer de l'état live au backlog sans chercher dans une carte agent.
## Vue Tickets — layout
La vue `Tickets` est une pile verticale : barre d'actions, recherche/filtres compacts, liste groupée par sprint, puis écran de détail lorsqu'un ticket est ouvert.
```text
Tickets [+ Ticket]
[Recherche…]
[Statut ▼] [Priorité ▼] [Assigné ▼] [Tri ▼]
Sprint courant (4)
#86 medium open Ajouter tickets/sprints au web
#83 medium open Confirmation fermeture
Sans sprint (2)
#78 low open Langue uniforme Settings
[Charger plus]
```
Sur petit écran, un ticket s'affiche comme une ligne compacte à deux étages :
```text
#86 Ajouter tickets/sprints au web
open · medium · Sprint courant · Main
```
La ligne entière ouvre le détail. Les badges doivent rester textuels et lisibles ; ne pas empiler 5 pastilles colorées si elles provoquent un retour ligne incohérent à 360 px.
## Actions prioritaires tickets
Priorité haute sur web :
- consulter la liste ;
- rechercher et filtrer ;
- ouvrir un ticket ;
- changer statut et priorité ;
- éditer titre, description et carnet ;
- créer un ticket simple ;
- supprimer un ticket avec confirmation ;
- assigner/désassigner des agents ;
- changer le sprint d'un ticket.
Priorité secondaire, mais à conserver si les endpoints existent :
- gérer les liens entre tickets ;
- utiliser le `TicketPicker` adapté mobile pour lier/ajouter des tickets à un sprint ;
- copier le contexte de délégation.
Hors périmètre recommandé pour la première livraison web : assistant IA de ticket. Le desktop peut garder cette affordance avancée ; sur mobile/web, elle ajoute du coût UI et des permissions sans être nécessaire à la demande utilisateur.
## Création de ticket
Le bouton `+ Ticket` ouvre un panneau plein écran mobile ou une section expansée desktop web, pas un petit formulaire inline permanent. Le formulaire est court :
- `Titre` obligatoire ;
- `Priorité` ;
- `Sprint` avec picker ;
- `Description` optionnelle, textarea repliable ou sous le champ titre ;
- actions `Annuler` et `Créer`.
Après création : ouvrir automatiquement le détail du ticket créé pour permettre de compléter carnet, assignations ou liens. Si l'affectation sprint échoue après création, garder le ticket créé et afficher l'erreur sans perdre le résultat.
Libellés : `Nouveau ticket`, `Titre`, `Description`, `Priorité`, `Sprint`, `Sans sprint`, `Créer`.
## Détail ticket sur petit écran
Sur web/mobile, le détail ticket remplace temporairement la liste dans la colonne, au lieu d'utiliser la fenêtre flottante desktop. Navigation : bouton retour en haut.
```text
[← Tickets] #86 [⋯]
Ajouter tickets/sprints au web
open · medium · Sprint courant
[Résumé]
Titre
Description
[Enregistrer]
[Statut et priorité]
Statut [open ▼]
Priorité [medium ▼]
Sprint [Sprint courant ▼]
[Carnet]
textarea Markdown
[Enregistrer le carnet]
[Agents assignés]
Main DevFrontend [+]
[Liens]
relatesTo #83 [+ Lier]
```
Les sections du détail sont accordéons ou blocs empilés. Ouverts par défaut : `Résumé`, `Statut et priorité`, `Carnet`. Repliés par défaut : `Agents assignés`, `Liens`, `Zone dangereuse`.
Le bouton de suppression vit dans une section `Zone dangereuse` ou dans un menu `⋯`, jamais dans la première ligne d'actions. Confirmation obligatoire :
Titre : `Supprimer ce ticket ?`
Corps : `Le ticket {ref} sera supprimé définitivement. Cette action ne supprime pas les sprints ni les agents assignés.`
Actions : `Annuler` / `Supprimer`
Gestion des changements non enregistrés : si l'utilisateur revient à la liste avec des modifications locales non sauvegardées, afficher la confirmation existante adaptée en français : `Des modifications ne sont pas enregistrées.` avec `Continuer l'édition`, `Ignorer`, `Enregistrer et quitter` si techniquement disponible.
## Édition rapide
Pour les champs à faible risque (`statut`, `priorité`, `sprint`), la modification peut être enregistrée immédiatement au changement de select, avec spinner discret sur la ligne/section. Pour les champs texte (`titre`, `description`, `carnet`), garder une action explicite `Enregistrer` afin d'éviter les sauvegardes involontaires sur mobile.
En cas de conflit de version : afficher un message inline en haut du détail : `Ce ticket a été modifié ailleurs et rechargé. Réappliquez votre modification.` Ne pas écraser silencieusement le brouillon local.
## Vue Sprints — layout
La vue `Sprints` est une surface dédiée, pas un overlay plein écran au-dessus de la liste comme sur desktop. Elle est accessible depuis l'onglet projet `Sprints` et depuis le bouton `Gérer les sprints` dans la vue tickets si ce raccourci est conservé.
```text
Sprints [+ Sprint]
#1 Sprint courant [⋯]
4 tickets
[Voir tickets] [Ajouter tickets]
#2 Backlog client web [⋯]
7 tickets
[Voir tickets] [Ajouter tickets]
```
Créer un sprint : bouton `+ Sprint`, champ `Nom du sprint`, action `Créer`. Si le nom est vide, soit désactiver `Créer`, soit reprendre le comportement desktop de nom automatique `Sprint N`; préférence UX web : désactiver tant qu'un nom n'est pas saisi, car le mobile bénéficie d'une décision explicite.
Renommer : action dans menu `⋯`, ouvre une ligne d'édition inline dans la carte sprint ou un panneau bas sur petit écran. Actions `Annuler` / `Enregistrer`.
Réordonner : boutons `Monter` / `Descendre` dans le menu ou dans une section `Ordre`. Pas de drag-and-drop requis sur mobile.
Supprimer : confirmation obligatoire :
Titre : `Supprimer le sprint « {name} » ?`
Corps : `Les tickets de ce sprint seront conservés et passeront en « Sans sprint ».`
Actions : `Annuler` / `Supprimer`
Ajouter des tickets à un sprint : ouvrir un picker mobile de tickets, avec recherche et multi-sélection. Action finale `Ajouter {count} ticket(s)`. Les tickets déjà dans le sprint sont exclus ou affichés cochés et désactivés ; ne pas laisser l'utilisateur croire qu'ils seront ajoutés une seconde fois.
Retirer un ticket d'un sprint : depuis la carte sprint, dans la liste compacte des tickets, action `Retirer du sprint` accessible via menu ligne. Cela ne supprime pas le ticket.
## Pickers et popups sur mobile/web
Les pickers desktop (`TicketPicker`, `SprintPicker`) ne doivent pas apparaître comme de petites modales centrées sur téléphone. Adapter leur chrome :
- mobile : panneau plein écran ou bottom sheet haute, avec header fixe, recherche en haut, liste scrollable, actions en bas ;
- desktop web large : modal centrée acceptable, mais largeur limitée et focus trap ;
- toujours garder `Échap` / retour / bouton `Fermer` selon plateforme.
Le même contenu métier peut être réutilisé : recherche, filtres, sélection simple ou multiple. Seul le contenant responsive change.
## Cohérence avec le web existant
Respecter les patrons #69 :
- une seule colonne verticale dans `WebWorkspace` ;
- pas de `LayoutGrid`, docks, fenêtres flottantes desktop ou tabs projet desktop ;
- padding avec safe-area ;
- contrôles qui wrap proprement à 360 px ;
- hauteurs basées sur `dvh` pour les panneaux longs ;
- reconnect banner existante conservée au-dessus des surfaces.
Les surfaces tickets/sprints doivent continuer à se resynchroniser sur les événements domaine via le transport web live, comme `État live` le fait déjà. En cas de reconnexion, refetch complet du snapshot/listes plutôt qu'application de deltas manqués.
## Langue et libellés
Conformément à la règle UX #78, les libellés humains sont en français. Les valeurs métier techniques peuvent rester celles du domaine si elles sont déjà exposées comme enums (`open`, `closed`, `medium`) seulement si leur traduction demanderait un chantier transversal ; préférence UX : afficher `Ouvert`, `Fermé`, `Faible`, `Moyenne`, `Haute`, `Critique` dans l'UI.
Libellés principaux :
- `Tickets`
- `Sprints`
- `Nouveau ticket`
- `Créer un ticket`
- `Gérer les sprints`
- `Nouveau sprint`
- `Créer un sprint`
- `Modifier`
- `Enregistrer`
- `Annuler`
- `Supprimer`
- `Sans sprint`
- `Charger plus`
- `Aucun ticket.`
- `Aucun sprint.`
- `Tickets liés`
- `Agents assignés`
- `Carnet`
- `Zone dangereuse`
## États et erreurs
- Chargement liste tickets : `Chargement des tickets…` avec spinner compact.
- Liste vide sans filtre : `Aucun ticket dans ce projet.` + bouton `Créer un ticket`.
- Liste vide avec filtres : `Aucun ticket ne correspond aux filtres.` + `Réinitialiser les filtres`.
- Chargement sprints : `Chargement des sprints…`.
- Aucun sprint : `Aucun sprint.` + bouton `Créer un sprint`.
- Erreur réseau/session : message inline en haut de la surface, avec `Réessayer`.
- Déconnexion live : conserver la bannière existante `Connexion perdue — reconnexion en cours…` ; les formulaires peuvent rester éditables, mais sauvegarder doit afficher l'erreur réelle si le transport est indisponible.
- Suppression réussie : retour à la liste, ticket/sprint retiré après événement ou refresh.
## Accessibilité
- Les onglets `Live`, `Tickets`, `Sprints` utilisent `role="tablist"`, `role="tab"`, `aria-selected` et navigation clavier gauche/droite.
- Chaque ticket de liste est un bouton ou lien avec nom accessible complet : `{ref}, {title}, statut {status}, priorité {priority}`.
- Les formulaires ont labels visibles ou labels accessibles explicites ; ne dépendre d'aucun placeholder comme seul label.
- Les confirmations de suppression utilisent `role="alertdialog"`, focus initial sur `Annuler`, action destructive textuelle.
- Les menus `⋯` ont un label explicite : `Actions du ticket {ref}` ou `Actions du sprint {name}`.
- Les zones scrollables longues conservent le focus et ne piègent pas la navigation clavier hors modal.
## Critères d'acceptation UX/frontend
- Après ouverture d'un projet web, l'utilisateur peut basculer entre `Live`, `Tickets` et `Sprints` sans shell desktop.
- La vue web reste une colonne verticale responsive et ne monte pas le layout grid/docks/floating windows desktop.
- L'utilisateur peut lister, filtrer, créer, ouvrir, modifier et supprimer des tickets depuis le web.
- L'utilisateur peut créer, renommer, réordonner, supprimer des sprints et ajouter/retirer des tickets d'un sprint depuis le web.
- L'édition ticket sur mobile se fait dans une vue détail pleine colonne avec retour explicite, pas dans une petite popup desktop.
- Les suppressions ticket/sprint demandent confirmation et précisent ce qui est supprimé ou conservé.
- Les pickers ticket/sprint sont adaptés mobile : plein écran ou bottom sheet, recherche en haut, actions claires en bas.
- Les changements texte nécessitent `Enregistrer`; les changements statut/priorité/sprint peuvent être immédiats avec feedback de sauvegarde.
- Les libellés visibles sont en français.
- L'assistant IA de ticket n'est pas requis pour la première livraison web de #86.
---
# Cadrage technique — contrat web-server tickets/sprints
## Résultat de l'audit
Le socle applicatif tickets/sprints existe déjà dans `BackendCore` : création, lecture, liste, update, suppression, carnet, liens, assignation agent, création/liste/rename/reorder/suppression sprint, assignation/désassignation ticket→sprint. Le problème #86 n'est donc pas un manque de use case application ni de store : c'est un écart de driving adapter web.
Côté desktop, `crates/app-tauri/src/lib.rs` enregistre les commandes UI implémentées dans `crates/app-tauri/src/tickets.rs`. Côté web, `crates/web-server/src/lib.rs` expose seulement quelques commandes dans le dispatcher `POST /api/invoke` (`health`, `list_projects`, `open_project`, `get_project_work_state`, tâches de fond). Aucune commande `ticket_*` ou `sprint_*` n'y est reconnue aujourd'hui, donc le `HttpTicketGateway` web tombe en `UNKNOWN_COMMAND`.
Le frontend web est déjà prêt côté transport : `frontend/src/adapters/http/streamGateways.ts` contient `HttpTicketGateway`, câblé dans `frontend/src/adapters/http/index.ts`, et il appelle les mêmes noms de commandes que le desktop via `/api/invoke`. Le blocage principal est donc backend/contrat, pas layout/UI.
## Commandes web-server à ajouter
Rester sur le style RPC existant : ajouter des branches à `POST /api/invoke`, pas créer une nouvelle API REST `/api/tickets`.
Tickets :
- `ticket_create``{ request: { projectId, title, description?, priority?, status?, assignedAgentIds?, links? } }``TicketDto`.
- `ticket_read``{ request: { projectId, ref, includeCarnet? } }``TicketDto`.
- `ticket_list``{ request: { projectId, statuses?, priorities?, assignedAgentId?, sprintId?, text?, sort?, limit?, cursor? } }``TicketListDto`.
- `ticket_update``{ request: { projectId, ref, title?, description?, status?, priority?, assignedAgentIds?, expectedVersion } }``TicketDto`. Cette commande couvre l'édition statut/priorité côté UI ; les commandes séparées `idea_ticket_update_status` / `idea_ticket_update_priority` sont des tools MCP agent, pas des commandes UI Tauri.
- `ticket_delete``{ request: { projectId, ref } }` → vide/null.
- `ticket_read_carnet``{ request: { projectId, ref } }``TicketCarnetDto`.
- `ticket_update_carnet``{ request: { projectId, ref, carnet, expectedVersion } }``TicketDto`.
- `ticket_link``{ request: { projectId, ref, targetRef, kind, expectedVersion } }``TicketDto`.
- `ticket_unlink``{ request: { projectId, ref, targetRef, kind?, expectedVersion } }``TicketDto`.
- `ticket_assign``{ request: { projectId, ref, agentId, assigned, expectedVersion } }``TicketDto`.
- `ticket_assign_sprint``{ request: { projectId, ref, sprintId, expectedVersion } }``TicketDto`.
- `ticket_unassign_sprint``{ request: { projectId, ref, expectedVersion } }``TicketDto`.
Sprints :
- `sprint_create``{ request: { projectId, name, status? } }``SprintDto`.
- `sprint_list``{ request: { projectId } }``SprintListDto { items }`.
- `sprint_rename``{ request: { projectId, sprintId, name, expectedVersion } }``SprintDto`.
- `sprint_reorder``{ request: { projectId, orderedIds } }``SprintListDto { items }`.
- `sprint_delete``{ request: { projectId, sprintId } }` → vide/null ; le comportement existant conserve les tickets et les repasse en `Sans sprint`.
Commandes assistant ticket proches mais hors minimum #86 : `open_ticket_chat`, `close_ticket_chat`, puis le flux `sendTicketChat`/`chat.*`. Recommandation : ne pas les inclure dans le lot initial, car la demande vise l'édition tickets/sprints et le flux chat web est une surface live distincte.
## Factorisation nécessaire
Les DTO publics tickets/sprints (`TicketDto`, `TicketSummaryDto`, `TicketListDto`, `TicketCarnetDto`, `SprintDto`, `SprintListDto`, request DTOs, pagination/tri/parse helpers) vivent aujourd'hui dans `crates/app-tauri/src/tickets.rs`. `web-server` ne doit pas dépendre de `app-tauri`.
Option recommandée : déplacer le contrat pur tickets/sprints vers `backend` (`backend::dto` ou `backend::tickets`) puis faire consommer ce module par les deux driving adapters :
- `app-tauri` garde uniquement les wrappers `#[tauri::command]` ;
- `web-server` ajoute des helpers `invoke_ticket_*` / `invoke_sprint_*` ;
- les tests de pagination/tri/conflit actuellement proches du module Tauri migrent avec la logique pure.
Option minimale possible mais moins saine : dupliquer DTO/conversions dans `web-server`. À éviter, car cela crée deux contrats wire desktop/web à maintenir.
## Auth et sécurité web (#77)
Les nouvelles commandes doivent rester derrière le gate existant de `POST /api/invoke` : pairing device, cookie de session HttpOnly, contrôle d'origine/CORS, révocation device et logs sécurité. Ne pas exposer de mutation ticket/sprint en GET ni hors allowlist.
Point à arbitrer produit/sécurité : un device web appairé pourra modifier les fichiers projet `.ideai/tickets` et `.ideai/sprints`. C'est cohérent avec la demande, mais il n'existe pas actuellement de permission fine par device lecture seule/écriture. Si cette granularité est souhaitée, c'est un chantier séparé de policy device, pas un prérequis technique au #86.
## Événements live
Les domain events `issue*` et `sprint*` existent déjà dans `backend::events::DomainEventDto`, et le web-server relaie les événements domaine sur websocket `event.domain` en excluant seulement `PtyOutput`. Après exposition des commandes, les vues web peuvent réutiliser les helpers frontend `isTicketEvent` / `isSprintEvent` et refaire un refetch, sans nouveau canal événementiel.
## Tests backend attendus
- Non authentifié : `ticket_list` et une mutation ticket/sprint retournent `UNAUTHORIZED`.
- Authentifié : `ticket_list` et `sprint_list` retournent le même shape que Tauri.
- Cycle ticket : create → read → update titre/statut/priorité → carnet → link/unlink → delete.
- Cycle sprint : create → list → rename → reorder → assign ticket → unassign ticket → delete.
- Conflit `expectedVersion` propagé en `ErrorDto` cohérent avec le desktop.
- Les commandes hors allowlist restent `UNKNOWN_COMMAND`.
## Découpe proposée
Lot 1 — backend/contrat web : factorisation DTO, ajout des 17 commandes `ticket_*`/`sprint_*` dans `/api/invoke`, tests auth/lecture/mutation/conflit.
Lot 2 — surface web UX/frontend : implémenter les vues décrites ci-dessus sur le `HttpTicketGateway` existant, gérer responsive, confirmations et conflits.
Lot 3 optionnel — assistant ticket web : exposer et finaliser le flux chat seulement si la surface assistant est explicitement demandée.

View File

@ -0,0 +1,16 @@
---
id: "fd0fa9e2-cdf3-4807-919a-f562c9b21026"
number: 86
title: "[UI] ajouter l'affichage et l'dition des tickets et des sprints à la version web"
status: "closed"
priority: "medium"
sprint: "028179b1-eaf4-41e9-9c1f-7c37125117e6"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784449151968
updatedAt: 1784612705899
version: 7
---
J'iamerais que la version web me permette aussi d'editer les tickets, d'en ajouter et d'en supprimer, ainsi que d'editer, ajouter ou supprimer des sprints

View File

@ -0,0 +1,6 @@
---
issueRef: "#87"
version: 7
updatedBy: {"kind":"user"}
updatedAt: 1784650196560
---

View File

@ -0,0 +1,16 @@
---
id: "0fabb0dd-9a83-4bc1-bbad-d96c794faed5"
number: 87
title: "[UI] sur l'affichage Web, la liste des background task polluent l'affchage"
status: "closed"
priority: "high"
sprint: "028179b1-eaf4-41e9-9c1f-7c37125117e6"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
createdAt: 1784451329374
updatedAt: 1784650196560
version: 7
---
Sur l'affichage Web, la liste qui affiche toutes les background task polluent l'affichage. Il faudrait que la liste soit par défaut repliée de façon a n'avoir dans un premier temps que la liste des agents

View File

@ -0,0 +1,6 @@
---
issueRef: "#89"
version: 5
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784661583231
---

View File

@ -0,0 +1,16 @@
---
id: "1f3e913f-0896-43be-818a-614e75d1fe3f"
number: 89
title: "Pouvoir lancer le serveur au lancement IdeA"
status: "closed"
priority: "medium"
sprint: null
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784649907432
updatedAt: 1784661583231
version: 5
---
J'aiemrais avoir une option pour pouvoir lancer le serveur web IdeA au lancement d'IdeA

View File

@ -0,0 +1,6 @@
---
issueRef: "#90"
version: 5
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784661583085
---

View File

@ -0,0 +1,16 @@
---
id: "828e32cc-83af-4a59-99b2-d0dd3e496c5f"
number: 90
title: "Ajouter les menus manquantes à la version Web d'IdeA"
status: "closed"
priority: "high"
sprint: null
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784649988741
updatedAt: 1784661583085
version: 5
---
J'iamerais que les menus qui ne sont pas encore disponibles dans la version Web le deviennent (J'aimerais que tous els menus du menus "Panneaux" soient disponibles dans la version web, ainsi que le setup des profils AI du menu Settings)

View File

@ -0,0 +1,6 @@
---
issueRef: "#91"
version: 4
updatedBy: {"kind":"user"}
updatedAt: 1784994105390
---

View File

@ -0,0 +1,16 @@
---
id: "2d54c254-d2e8-44b3-ac12-3bf20f818d23"
number: 91
title: "Sur la notification de fin de tache backend, mettre plutot les deux agents en conversation"
status: "open"
priority: "medium"
sprint: "5afd6780-0f76-40d7-a10f-32ee52469d74"
links: []
agentRefs: [{"agentId":"a6ced819-b893-4213-b003-9e9dc79b9641","role":"assigned"}]
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
createdAt: 1784652125081
updatedAt: 1784994105390
version: 4
---
Sur la notification de fin de tache backend, mettre plutot les deux agents en conveersation et qui a lancé l'appel (par exemple Main->DevBackend) pour que ça soit un peu plus explicite

View File

@ -0,0 +1,25 @@
---
issueRef: "#92"
version: 9
updatedBy: {"kind":"user"}
updatedAt: 1784993956910
---
## Résumé de livraison
Backend et frontend livrés sur `feature/ticket92-opencode-provider-cloud`, mergée dans `develop` (merge `12c7d10`).
**Backend** (commit `23a3c27`) :
- Domain : `OpenCodeProviderConfig { provider_id, model, api_key_ref: SecretRef }` sur `AgentProfile.opencode_provider`, invariant `opencode_backend_is_consistent()` (llamacpp local XOR provider cloud, jamais les deux).
- Port `SecretStore` + adaptateur `FsSecretStore` (AES-256-GCM via `ring`, clé locale `secret.key` chmod 0600) — la clé API cloud n'est jamais en clair dans `profiles.json`.
- Catalogue de providers **statique** (`application/agent/provider_catalogue.rs`) exposé via la commande Tauri `list_opencode_providers`.
- Use case `SaveOpenCodeProviderProfile` (scelle la clé API dans le `SecretStore`, mint/réutilise un `SecretRef`) exposé via `save_opencode_provider_profile`. Pas de patch partiel : la clé littérale est obligatoire à chaque appel, y compris en édition.
- `DeleteProfile` purge le secret associé si le profil supprimé porte `opencode_provider`.
- Résolution de la clé au lancement dans les deux générateurs de `opencode.json` (`infrastructure/assistant/mod.rs`, `application/agent/lifecycle.rs`) ; échec dur si secret absent.
- QA vert : `cargo build --workspace` + `cargo test --workspace -- --test-threads=1` (tests de couverture SaveOpenCodeProviderProfile/DeleteProfile ajoutés suite à un premier retour QA).
**Frontend** (commit `c181b43`) :
- First-run wizard : segmented control Local/Cloud exclusif dans le formulaire de profil OpenCode, sous-formulaire cloud (provider → modèle en cascade, clé API masquée jamais pré-remplie), validation inline, bannière d'erreur de sauvegarde.
- En édition d'un profil cloud existant : champ clé toujours vide, bouton de sauvegarde désactivé tant que la clé n'est pas ressaisie (contrainte backend, pas de patch partiel).
- QA vert : `npm run build` + `vitest run` (952 tests, y compris un test ajouté pour le parcours d'édition suite à un retour QA).
**Dette hors périmètre signalée par Architect, non traitée ici** : duplication de deux fonctions `opencode_config_json`/`opencode_provider_config_json` entre `infrastructure/assistant/mod.rs` et `application/agent/lifecycle.rs` — à trancher (laquelle est la vraie) dans un ticket séparé si besoin.

View File

@ -0,0 +1,16 @@
---
id: "9ba8d2f4-49fa-4536-a38e-891f1df3e3b7"
number: 92
title: "Configurer OpenCode avec un provider Opencode"
status: "closed"
priority: "high"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
createdAt: 1784729443275
updatedAt: 1784993956910
version: 9
---
On peut actuellement utiliser un profile AI qui utilise OpenCode pour llamacpp. J'aimerais que l'on puisse configurer des profil AI OpenCode avec llamacpp comme actuellement, mais aussi en selectionnant un provider listé dans la commande /connect de OpenCode. Je pense que le mieux serait que lors de la création d'un profile AI OpenCode, il nous soit demandé si on souhaite utiliser LlamaCpp ou un provider OpenCode. Dans le cas d'un provider llamaCpp, on utilise la même chose qu'actuellement, dans l'autre cas on nous demande quel provider et il faudrait être capable de récupérer la liste fournie par OpenCode. Une fois le provider selectionné, il faudra que l'utilisateur puisse entrer une clé API car les providers opencodes en demadnent toujours un. Pour la partie utilisation ensuite d'opencode dans les agent, je pense que cette page peut aider: https://opencode.ai/docs/fr/cli/. On y trouve entre autre la commande pour se connecter avec opencode auth login. Le but est que la partie configuration se fasse dans les profil AI comme jusqu'à présent, et que l'utilisateur puisse directement attribuer un agent à son profile AI et le lancer sans configurer d'autre choses. OpenCode marche déjà correctement avec llamacpp, j'insiste sur le fait qu'on ne fait qu'ajouter la possibilité de configurer un provider autre que llamacpp

View File

@ -0,0 +1,46 @@
---
issueRef: "#93"
version: 3
updatedBy: {"kind":"user"}
updatedAt: 1784934179780
---
# Contexte
Test manuel demandé par l'utilisateur pour valider la conversation inter-agent : Main a appelé `idea_ask_agent(target="Context", task="...")` avec une tâche de simple confirmation d'identité/rôle.
## Observation
Les deux appels (identiques) ont renvoyé, à la place d'une réponse finale en langage naturel, exactement ce fragment JSON brut :
```json
{
"name": "idea_idea_context_read",
"arguments": {}
}
```
- Le nom d'outil est corrompu : préfixe `idea_` dupliqué (`idea_idea_context_read` au lieu de `idea_context_read`).
- Le fragment a la forme d'un appel d'outil (tool_use), pas d'un texte de réponse — semble être une tentative de Context de lire son contexte projet via `idea_context_read`, jamais exécutée, puis remontée telle quelle comme si c'était le Final capturé.
- Reproduit à l'identique sur 2 tentatives consécutives → pas un aléa de génération, plutôt un bug de câblage/capture.
## Détails agent Context
- id: `5d07e4a7-8676-4a71-aea1-5b64caefa944`
- contextPath: `agents/context.md`
- profileId: `a7037cbe-6d04-48fb-ba74-f353c93fc70a` (profil différent de celui des autres agents du manifeste, qui partagent tous `664cc20c-47b8-53ad-9351-dce3c09c0de4` — Context est le seul sur ce profil)
- origin: scratch
## Pistes d'investigation pour Architect / DevBackend
1. Le profil `a7037cbe-...` (LLM local léger dédié à Context, cf. mémoire `agent-context-memory-and-profile-handoff`) semble ne pas capturer correctement le "Final" du tour — le pont MCP/runtime renverrait le premier tool_use brut au lieu d'attendre la réponse texte finale.
2. Vérifier le mapping des noms d'outils exposés à ce profil : la duplication `idea_idea_*` suggère un préfixage appliqué deux fois (une fois côté définition d'outil MCP, une fois côté wrapper du profil/pont).
3. Comparer avec le comportement des autres profils (`664cc20c-...`) qui fonctionnent correctement en inter-agent, pour isoler ce qui diffère structurellement dans le pont pour un profil local/structured.
4. Voir mémoire projet `mcp-bridge-and-delegation-runtime-notes` pour les pièges déjà connus du pont MCP/délégation — possible recoupement.
## Repro
Depuis Main :
```
idea_ask_agent(target="Context", task="<n'importe quelle tâche simple>")
```
→ renvoie le fragment JSON ci-dessus au lieu d'une réponse.

View File

@ -0,0 +1,27 @@
---
id: "40f56f7d-b0ec-4409-bf6b-c010e3003b76"
number: 93
title: "Agent Context : réponse finale non capturée, fragment d'appel d'outil brut renvoyé via idea_ask_agent"
status: "closed"
priority: "medium"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1784817692650
updatedAt: 1784934179780
version: 3
---
Lors d'un test de la conversation inter-agent (idea_ask_agent ciblant Context), l'appel renvoie systématiquement un fragment JSON brut au lieu d'une réponse finale exploitable :
```json
{
"name": "idea_idea_context_read",
"arguments": {}
}
```
Le nom d'outil est corrompu (préfixe "idea_" dupliqué : "idea_idea_context_read" au lieu de "idea_context_read"), et ce fragment ressemble à une tentative d'appel d'outil non exécutée, renvoyée telle quelle comme si c'était la réponse finale du modèle.
Comportement reproduit deux fois de suite à l'identique, donc pas un aléa ponctuel.

View File

@ -0,0 +1,6 @@
---
issueRef: "#94"
version: 2
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784823278030
---

View File

@ -0,0 +1,48 @@
---
id: "33a844e0-6a06-46c7-b800-d3497a7f45f4"
number: 94
title: "Permissions OpenCode ignorées : opencode.json code en dur bash/edit=ask au lieu de projeter EffectivePermissions"
status: "closed"
priority: "high"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784821271815
updatedAt: 1784823278030
version: 2
---
## Bug
Les permissions configurées dans IdeA pour un agent sous profil OpenCode ne sont PAS appliquées dans le `opencode.json` généré. Le bloc `permission` est codé en dur à `{"bash":"ask","edit":"ask"}` quel que soit le réglage IdeA.
## Repro
Config IdeA (agent OpenCode) : Read=Allow, Write=Deny, Delete=Allow, bash=Allow.
`opencode.json` généré (run dir) : `"permission":{"bash":"ask","edit":"ask"}`.
Attendu : `bash` reflète la posture bash (Allow→"allow"), `edit` reflète la posture Write (Deny→"deny").
Claude et Codex appliquent correctement les permissions ; seul OpenCode est touché.
## Cause racine
Le pattern de projection des permissions (`PermissionProjector` + `ProjectorKey::{Claude,Codex}` dans `crates/infrastructure/src/permission/`) est bypassé pour OpenCode. Les générateurs `opencode.json` codent `permission` en dur à 4 endroits :
- `crates/application/src/agent/lifecycle.rs:2728-2734` (`opencode_config_json`, variante llamacpp)
- `crates/application/src/agent/lifecycle.rs:2814-2820` (`opencode_provider_config_json`, variante cloud)
- `crates/infrastructure/src/assistant/mod.rs:406-412` (`opencode_config_json`, doublon)
- `crates/infrastructure/src/assistant/mod.rs:494-500` (`opencode_provider_config_json`, doublon)
Les call sites (`lifecycle.rs:2361-2378` branche `OpenCodeConfig`, et `assistant/mod.rs:247-256`) ne passent pas les `EffectivePermissions` résolues aux générateurs.
## Mapping attendu (à confirmer par Architect)
OpenCode `permission` = map tool→"allow"|"ask"|"deny". IdéA → OpenCode :
- `bash` ← posture bash effective
- `edit` ← posture Write effective
- Read/Delete ne sont pas exprimables dans opencode (pas de clé read/delete) — déjà enforcees par le sandbox Landlock (mémoire `permissions-sandbox-system-state`).
## Périmètre
Backend Rust pur. Validation possible par tests unitaires sur les générateurs (`opencode_provider_config_json` est déjà testé à lifecycle.rs:4413 et assistant/mod.rs:639) sans rebuild AppImage. Validation live = rebuild AppImage + relance IdeA.

View File

@ -0,0 +1,36 @@
---
issueRef: "#95"
version: 6
updatedBy: {"kind":"user"}
updatedAt: 1784934174326
---
## Cause racine RÉELLE (2026-07-24, après investigation code)
### Ce qui se passe
Le `"timeout": 15000` (15 secondes) **hardcoded** dans le bloc `mcp.idea` de l'`opencode.json` généré par IdeA. OpenCode applique ce timeout **par appel `tools/call`** à ses serveurs MCP. `idea_ask_agent` bloque pendant que l'agent cible travaille (potentiellement des minutes) → OpenCode tue la requête à 15s avec l'erreur `-32001: Request timed out`.
**Pourquoi Claude/Codex ne sont pas affectés** : leur config MCP (`.mcp.json` / `config.toml`) n'a **pas** de champ `timeout`. Ce champ est spécifique à la config OpenCode.
### Emplacements du hardcoded 15000 (4 occurrences)
1. `application/src/agent/lifecycle.rs:2728``opencode_config_json()`
2. `application/src/agent/lifecycle.rs:2811``opencode_provider_config_json()`
3. `infrastructure/src/assistant/mod.rs:410``opencode_config_json()` (duplicate)
4. `infrastructure/src/assistant/mod.rs:495``opencode_provider_config_json()` (duplicate)
### Fix appliqué (feature/ticket95-opencode-mcp-timeout)
- Constante `DEFAULT_OPENCODE_MCP_TIMEOUT_MS` = 4h (14_400_000 ms), alignée sur `DEFAULT_RENDEZVOUS_CEILING` — le client MCP ne expire jamais avant le watchdog serveur.
- Fonction `resolve_opencode_mcp_timeout_ms()` — overridable via `IDEA_OPENCODE_MCP_TIMEOUT_MS`, fallback sûr sur 0/parse-error.
- Les 4 occurrences remplacées par `resolve_opencode_mcp_timeout_ms()`.
- Exporté depuis `application::agent` pour réutilisation par `infrastructure`.
- **Claude/Codex non touchés** : aucune modification de leur config MCP.
### Tests
- Application : 115 passed, 0 failed
- Infrastructure : 313 passed, 0 failed
- Compilation : OK, aucun nouveau warning
### Note déploiement
Le `opencode.json` est régénéré à chaque lancement d'agent par le code lifecycle. Nécessite rebuild AppImage pour que le binaire qui tourne génère la nouvelle config.

View File

@ -0,0 +1,54 @@
---
id: "611077a6-f703-44b9-935b-64f8301085bb"
number: 95
title: "Rendez-vous idea_ask_agent : timeout MCP OpenCode trop court (15s), le demandeur OpenCode ne reçoit jamais la réponse"
status: "closed"
priority: "high"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1784822537740
updatedAt: 1784934174326
version: 6
---
## Bug
Une délégation `idea_ask_agent` depuis un agent sous profil **OpenCode** (le **demandeur**) échoue systématiquement en `timeout` (-32001 « Request timed out »), peu importe la cible (Claude, Codex, ou OpenCode). Les agents cibles terminent bien leurs tâches (visible dans le workstate = done), mais leurs réponses ne sont jamais livrées au demandeur OpenCode.
Claude et Codex ne sont **pas** affectés, qu'ils soient demandeur ou cible.
## Cause racine (vérifiée dans les sources)
Le `"timeout": 15000` (15 secondes) **hardcoded** dans le bloc `mcp.idea` de l'`opencode.json` généré par IdeA. OpenCode applique ce timeout **par appel `tools/call`** à ses serveurs MCP. `idea_ask_agent` bloque pendant que l'agent cible travaille (potentiellement des minutes) → OpenCode tue la requête à 15s.
**Pourquoi Claude/Codex ne sont pas affectés** : leur config MCP (`.mcp.json` / `config.toml`) n'a **pas** de champ `timeout`. Ce champ est spécifique à la config OpenCode (`opencode.json``mcp.idea.timeout`).
### Emplacements (4 occurrences de `15000`)
1. `crates/application/src/agent/lifecycle.rs``opencode_config_json()`
2. `crates/application/src/agent/lifecycle.rs``opencode_provider_config_json()`
3. `crates/infrastructure/src/assistant/mod.rs``opencode_config_json()` (duplicate)
4. `crates/infrastructure/src/assistant/mod.rs``opencode_provider_config_json()` (duplicate)
## Ce qui n'est PAS la cause
- Ce n'est pas un problème de sonde de vivacité du rendez-vous (la description originale pointait la sonde Claude-only côté cible — mauvaise piste).
- Ce n'est pas un problème de permissions, de sandbox, ou de taille de payload.
- Ce n'est pas lié au modèle : tout profil `structuredAdapter: "openCode"` est touché en tant que demandeur.
## Fix appliqué (feature/ticket95-opencode-mcp-timeout)
- Constante `DEFAULT_OPENCODE_MCP_TIMEOUT_MS` = 4h (14_400_000 ms), alignée sur `DEFAULT_RENDEZVOUS_CEILING`.
- Fonction `resolve_opencode_mcp_timeout_ms()` — overridable via `IDEA_OPENCODE_MCP_TIMEOUT_MS`, fallback sûr.
- Les 4 occurrences remplacées.
- **Claude/Codex non touchés**.
## Tests
Application : 115 passed. Infrastructure : 313 passed. 0 failed.
## Déploiement
Nécessite rebuild AppImage (l'`opencode.json` est régénéré à chaque lancement d'agent).

View File

@ -0,0 +1,18 @@
---
issueRef: "#96"
version: 3
updatedBy: {"kind":"user"}
updatedAt: 1784994119004
---
## Décision (2026-07-24) : différée — documentée, non codée
Sur instruction utilisateur (#96 mis en pause pour se concentrer sur #95) : **on documente le constat et la décision de fond reste ouverte pour plus tard**. Aucun code ajouté.
### Constat confirmé dans les sources
`crates/infrastructure/src/assistant/mod.rs::TicketAssistantEnvironmentPreparer::materialise_mcp` passe `eff: None` à `opencode_config_json` / `opencode_provider_config_json` (lignes ~253 et ~262). Le commentaire local (l. 246-251) le documente déjà : aucun `PermissionStore`/`EffectivePermissions` n'est résolu sur ce chemin, **pour aucun adaptateur**. Conséquence : les assistants de ticket tournent toujours avec le comportement **natif** d'OpenCode (prompting à chaque action), indépendamment des permissions IdeA configurées.
### Pourquoi c'est différé (la vraie question produit)
Les assistants de ticket sont lancés depuis un **profil** (pas depuis un **agent** avec une politique par-agent). Il n'y a donc **pas de source naturelle** d'`EffectivePermissions` sur ce chemin. Câbler nécessite d'abord de trancher : les permissions viennent-elles du profil ? d'un fallback projet ? d'une posture dédiée aux assistants ? Tant que ce choix produit n'est pas posé, `eff: None` (= prompting natif) reste le défaut **sûr** — ce n'est pas une régression, c'est le comportement historique préservé.
### Suivi
Non bloquant, priorité basse. À reprendre quand un besoin produit le justifie : définir la source d'autorités pour un assistant de ticket, puis la résoudre ici (comme pour les agents normaux dans `application::agent::lifecycle`).

View File

@ -0,0 +1,18 @@
---
id: "d5993d21-307b-4fc8-93f9-2bfbf44223d8"
number: 96
title: "Les assistants de ticket (tous adaptateurs) n'ont aucune résolution EffectivePermissions — permission toujours native/absente"
status: "open"
priority: "low"
sprint: "883534aa-7fc1-4d83-a9c0-17cac4a4eea5"
links: []
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1784823281794
updatedAt: 1784994119004
version: 3
---
Trouvé en implémentant #94 (projection des permissions dans opencode.json). Le call site des assistants de ticket dans `crates/infrastructure/src/assistant/mod.rs` n'a **aucun** `PermissionStore`/`EffectivePermissions` câblé, pour aucun adaptateur (Claude, Codex, OpenCode) — vérifié par grep sur la composition root `crates/backend/src/lib.rs`. Le fix #94 y passe donc `eff: None` (préserve le comportement natif existant, pas de régression), mais ce n'est pas un vrai fix de fond : les permissions IdeA configurées pour un agent n'ont jamais été appliquées aux assistants de ticket, quel que soit l'adaptateur.
À trancher par Architect : soit câbler la résolution `EffectivePermissions` pour ce call site (comme pour les agents normaux), soit documenter que c'est un choix produit intentionnel (permanent) et fermer sans y toucher. Non bloquant, priorité basse.

View File

@ -0,0 +1,28 @@
---
issueRef: "#97"
version: 3
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784915626417
---
## Décision Architect (cadrage validé)
Bug 100% backend, deux défauts composés :
1. `SaveOpenCodeProviderProfile::execute` (`usecases.rs:364-367`) pose `opencode_provider` sans `opencode = None`.
2. Invariant `opencode_backend_is_consistent` (`profile.rs:1220`) existe mais jamais appelé (garde morte).
Lecture priorise `opencode` → cloud écrasé (`lifecycle.rs:2367`, `assistant/mod.rs:252`).
### Périmètre du lot (un seul, parallélisable)
- **DevBackend** :
- Domaine : `with_opencode`/`with_opencode_provider` (`profile.rs:1132,1140`) imposent l'exclusion mutuelle (chacun efface l'autre).
- Infrastructure : `FsProfileStore::save` rejette tout profil incohérent → `AppError::Invalid` (active enfin le prédicat).
- Application : `SaveOpenCodeProviderProfile` reconstruit via builder (`with_opencode_provider`) ; idem pour `SaveProfile`/`ConfigureProfiles`.
- **Migration requise** : à la lecture (ou passe dédiée), quand `opencode` ET `opencodeProvider` présents → dropper `opencode` stale (l'intention est cloud). Sinon ne corrige que les nouveaux profils.
- **DevFrontend** : strip de la config inactive au save selon le mode courant (requis pour SaveProfile/ConfigureProfiles qui ne portent pas d'intention explicite ; backend reste autorité via la garde).
- **QA** : tests unitaires domaine (builders exclusifs) + garde store + intégration création profil cloud + scénario migration profils corrompus.
### Décisions de frontière
- DTO `SaveOpenCodeProviderProfileRequestDto` **inchangé** (whole-profile) ; output = profil normalisé, autorité pour le frontend.
- Priorité lecture `opencode` d'abord **gardée** (irrelevant post-exclusion), commenter comme fallback défensif.
- Duplication de la résolution (lifecycle.rs:2367 + assistant/mod.rs:252) = dette hexagonale préexistante, **hors périmètre de ce lot** (suivre, ne pas refactoriser ici).
Voir mémoire projet `ticket97-opencode-provider-mutual-exclusion`.

View File

@ -0,0 +1,26 @@
---
id: "fd7057dc-0177-41e1-811c-701c802e81d3"
number: 97
title: "Duplication de provider lors de la création de profil AI - llamacpp ET provider cloud inclus simultanément"
status: "closed"
priority: "high"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784899606438
updatedAt: 1784915626417
version: 3
---
Lors de la création d'un profil AI dans le premier-run wizard, le système génère un profiles.json qui contient à la fois la section `opencode` (llamacpp) et `opencodeProvider` (cloud), quel que soit le choix de l'utilisateur.
**Comportement attendu :**
- Si l'utilisateur choisit "llamacpp" : seule la section `opencode` doit être présente
- Si l'utilisateur choisit "provider cloud" : seule la section `opencodeProvider` doit être présente
**Comportement actuel :**
Les deux sections sont présentes simultanément, ce qui fait que llamacpp est priorisé même quand l'utilisateur veut utiliser un provider cloud (ex: ZAI/GLM).
**Cas de test :**
Création du profil GLM5.2 avec provider ZAI Code → profiles.json contient `opencodeProvider` correct MAIS contient aussi une section `opencode` vide ou inutile, ce qui force le fallback sur llamacpp.

View File

@ -0,0 +1,87 @@
---
issueRef: "#98"
version: 5
updatedBy: {"kind":"user"}
updatedAt: 1784983186248
---
# Carnet #98 — Contrat figé (approche **B2**)
> Cadrage ARCHITECT figé et gelé. **Source de vérité pour DevBackend et QA.** Ce carnet remplace la phase d'arbitrage ; les options A/B1/C sont closes (voir §2 pour le rejet motivé).
---
## 1. Cause racine affinée
Le modèle est perdu **au spawn**, pas à la sauvegarde (persistance vérifiée correcte bout en bout, `profiles.json` conserve bien `opencodeProvider.model`). Deux facteurs composés :
1. **Pas de bloc `models`** pour les providers catalogue connus — `opencode_provider_config_json` (`lifecycle.rs:2774-2844` + jumeau `assistant/mod.rs:428-503`) n'émet `npm`/`baseURL`/`models` **que** pour le chemin `custom`. Pour un provider catalogue (zai), seul `provider.<id>.options.apiKey` est écrit. Test fige ce manque : `lifecycle.rs:4439-4440`.
2. **Cache models.dev isolé et vide** — IdeA passe `XDG_CACHE_HOME=<run_dir>/.opencode/cache` vide (`lifecycle.rs:2386,2405` ; `assistant/mod.rs:272,282`) + `HOME` isolé. Or `zai` n'est connu d'OpenCode **que** via ce cache (catalogue lu côté IdeA depuis le vrai `~/.cache/opencode/models.json`, `provider_catalogue.rs:79-84`).
→ OpenCode ne connaît plus `zai`, ne résout pas `zai/<modèle>`**fallback silencieux `glm-5.2`** (fallback interne CLI OpenCode, absent du code IdeA).
Asymétrie confirmée : local llama.cpp et cloud custom marchent car ils émettent un bloc `models` (`assistant/mod.rs:371-379`, `:457-460`).
## 2. Approche retenue : **B2** (seed best-effort du cache hôte)
Copier best-effort le cache models.dev hôte (`opencode_models_cache_path()`) vers `<xdg_cache>/opencode/models.json` dans le cache isolé, juste après le `create_dir_all` du cache dans `apply_mcp_config` (branche commune opencode/opencodeProvider), aux **DEUX** sites.
**Rejet motivé des alternatives :**
- **A (émettre bloc `models` pour providers connus)** : rejeté car `provider_catalogue.rs` ne persiste que `name`+`models` et **ignore `npm`+`baseURL`**. Un bloc `models` seul laisse OpenCode incapable de *joindre* `zai` — il faudrait en plus étendre le domaine (`display_name`/`npm`/`base_url`) → scope bien plus large et risque régression. A n'est viable qu'en révision A-étendue, écartée pour ce lot.
- **B1 (= B sans factorisation, deux sites copiés)** : rejeté pour dette hexagonale — le writer `opencode_provider_config_json` est déjà dupliqué `lifecycle.rs` vs `assistant/mod.rs`. Dupliquer aussi le seed amplifierait la dette et rendrait toute divergence un bug silencieux. B2 impose une factorisation unique.
- **C (hybride : pointer `XDG_CACHE_HOME` vers le vrai cache)** : rejeté — casse l'invariant d'isolation du run (un profil pourrait lire/écrire le cache hôte ou un cache d'un autre run), et OpenCode pourrait y écrire (logs, refresh) → pollution mutuelle.
**Pourquoi B2 :** donne à OpenCode sa connaissance registry complète (npm, baseURL, modèles) sans toucher au domaine ni casser l'isolation écriture — c'est de la donnée publique read-only copiée **dans** l'espace isolé.
## 3. Contrat figé — ports / fichiers / invariants
### 3.1 Fonctions à implémenter (factorisation)
- **Rendre `opencode_models_cache_path()` publique** — source de vérité unique du chemin hôte, **ne pas re-dériver** le chemin dans le seed.
- **`seed_opencode_models_cache(fs, isolated_cache_dir)`** (fonction partagée) : lit le cache hôte via `opencode_models_cache_path()`, copie best-effort vers `<isolated_cache_dir>/opencode/models.json`. Appelée aux **deux** sites après le `create_dir_all` du cache dans `apply_mcp_config` (branche commune `opencode`/`opencodeProvider`).
- **`seed_from_bytes(fs, dest, src_bytes)`** (partie pure testable) : extrait la logique d'écriture du fichier destination à partir de bytes source. C'est le seam de test unitaire.
### 3.2 Sites d'appel (les DEUX)
1. `crates/application/src/agent/lifecycle.rs` — après `create_dir_all` cache dans `apply_mcp_config`.
2. `crates/infrastructure/src/assistant/mod.rs` — après `create_dir_all` cache dans `apply_mcp_config`.
### 3.3 Invariants (à respecter, à tester)
- **Isolation préservée en écriture** : le seed ne fait que copier une donnée read-only **vers** le cache isolé. Jamais de `XDG_CACHE_HOME` pointé vers l'hôte.
- **Best-effort** : le seed **n'échoue jamais le launch**. Toute erreur (fichier hôte absent, IO) est tracée (warn/log) et ignorée — on retombe sur le comportement actuel (fallback glm-5.2), pas sur un crash.
- **Pas de régression** : profils local (llama.cpp), custom et built-ins (anthropic/openai/openrouter) doivent continuer à fonctionner byte-identique au rendu `opencode.json` actuel.
- **Exclusion mutuelle #97 non touchée** : le seed s'exécute sur la branche commune `opencode`/`opencodeProvider`, sans affecter la logique d'exclusion.
- **Cohérence picker↔spawn** : le catalogue vu côté UI (picker) et celui seedé au spawn proviennent du même fichier hôte → l'utilisateur ne peut pas picker un modèle qu'OpenCode ne connaîtra pas au spawn.
### 3.4 Hors périmètre (explicitement exclu)
- Remote SSH/WSL (le seed ne concerne que le spawn local).
- Déduplication du rendu lifecycle↔infra au-delà du seed (la dette du writer `opencode_provider_config_json` reste ouverte — autre lot).
- UI wizard (aucun changement surface).
## 4. Périmètre QA — 2 couches
### Couche 1 — Unitaire pure (obligatoire, rapide, déterministe)
- **`seed_from_bytes`** : assert écriture byte-identique du contenu source vers `dest`, gestion erreurs (fs en échec → pas de panic), idempotence.
- **Non-régression rendu `opencode.json`** : les tests existants `lifecycle.rs:4439-4440` et équivalents infra doivent rester **byte-identiques** pour local/custom/built-ins (le seed ne change pas le rendu config). Mettre à jour l'assert si et seulement si B2 modifie réellement le rendu — sinon la conserver telle quelle.
### Couche 2 — Intégration gated (obligatoire avant fermeture du lot)
- **Spawn réel OpenCode** sur profil `zai` + modèle X choisi.
- **Assert** : OpenCode démarre sur le modèle X (pas de fallback `glm-5.2`).
- Vérifier dans les background-tasks/IO qu'aucune trace `glm-5.2` n'apparaît comme modèle actif.
- **Gate obligatoire à lever** : OpenCode **refresh/écrase-t-il le cache `models.json` au démarrage** ? → si **oui**, le seed est inutile (écrasé avant lecture) et **il faut escalader vers A-étendue** (persistir npm/baseURL/models côté domaine). Consigner le verdict dans le rapport QA.
## 5. Risque résiduel — refresh models.dev par OpenCode
**Risque ouvert, à trancher en QA couche 2.** Si OpenCode rafraîchit/écrase `<xdg_cache>/opencode/models.json` au démarrage (network call ou réécriture locale), le seed B2 est potentiellement **inopérant** :
- Meilleur cas : OpenCode lit le cache avant tout refresh → B2 fonctionne.
- Cas dégradé : OpenCode refresh en premier, écrase le seed, et comme le profil est isolé (pas d'accès réseau garanti / HOME isolé), le refresh peut échouer ou produire un cache incomplet → bug persiste.
- **Plan de contournement si échec** : escalader vers A-étendue (ajouter `npm`+`base_url`+`models` persistés côté `OpenCodeProviderConfig` + émettre le bloc complet au spawn). Ce plan est documenté mais **hors scope B2** — il ferait l'objet d'un lot suivant si QA le confirme nécessaire.
---
## Contexte de mise à jour
- Cadrage figé par Main sur validation Architect (approche B2).
- Version précédente (v1) : phase d'arbitrage A/B/C — clos.
- Prochaine étape cycle : Git (branche) → DevBackend (implémentation 2 sites + factorisation) → QA (2 couches, gate refresh).

View File

@ -0,0 +1,38 @@
---
id: "9db40467-a826-4dac-a679-e7934cbdda81"
number: 98
title: "Profil cloud provider catalogue (ex: zai) : modèle choisi écrasé par glm-5.2 au spawn"
status: "closed"
priority: "high"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1784966899811
updatedAt: 1784983186248
version: 5
---
## Symptôme
Dans le wizard de création de profil AI OpenCode (first-run), quand l'utilisateur choisit un cloud provider du catalogue (ex: **zai / ZAI Code**) puis sélectionne un modèle spécifique, **après sauvegarde l'agent tourne en `glm-5.2`** quel que soit le modèle choisi.
## Périmètre
Bug **backend** (couche application/infrastructure du spawn OpenCode). Suite logique et distincte du ticket #97 (qui réglait l'exclusion mutuelle `opencode` vs `opencodeProvider` — désormais fixée).
## Cause racine (diagnostiquée, à confirmer par Architect)
- La persistance est **correcte** : `profiles.json` conserve bien `opencodeProvider.model` = modèle choisi. `glm-5.2` est **absent de tout le code IdeA** ; c'est le **fallback interne de la CLI OpenCode** quand elle ne sait pas résoudre le modèle demandé.
- Le modèle est perdu **au spawn** :
1. IdeA écrit `opencode.json` avec `model: "zai/<choix>"` mais **sans bloc `models`** pour les providers catalogue connus (`crates/application/src/agent/lifecycle.rs:2796-2821`, `crates/infrastructure/src/assistant/mod.rs:447-466`). Elle suppose OpenCode built-in.
2. Or `zai` n'est connu d'OpenCode **que** via son cache models.dev, et IdeA **isole `XDG_CACHE_HOME` vide** au spawn (`lifecycle.rs:2386,2405` ; `assistant/mod.rs:272,282`). → OpenCode ne résout ni `zai` ni le modèle → fallback silencieux `glm-5.2`.
- Pourquoi ça marche en local (llama.cpp) et custom : ils émettent un bloc `models` (`assistant/mod.rs:371-379`, `:457-460`).
## Exemples impactés
Tout provider cloud **connu uniquement via le cache models.dev** (zai, et vraisemblablement tout provider non built-in dans OpenCode). Les providers hardcodés dans OpenCode (anthropic, openai, openrouter) fonctionnent malgré l'absence de bloc `models`.
## Lié à
- **#97** (relatesTo) : exclusion mutuelle provider — prérequis déjà livré.

128
.ideai/tickets/99/carnet.md Normal file
View File

@ -0,0 +1,128 @@
---
issueRef: "#99"
version: 1
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784987973464
---
# Carnet #99 — Cadrage (prêt pour le cycle)
> Cadrage établi par Main après investigation code + **vérification empirique des binaires
> installés** (`codex-cli 0.145.0`, `Claude Code 2.1.220`). Les faits CLI ci-dessous sont
> **capitalisés et vérifiés** ; ils conditionnent toute l'architecture. Architect doit valider
> les ports/VO et les questions ouvertes (§6) avant l'implémentation.
---
## 1. État des lieux vérifié — comparaison des 3 moteurs
| Moteur | Modèle contrôlé par IdeA ? | Mécanisme | Source |
|---|---|---|---|
| **OpenCode** | ✅ Oui | `model` écrit à la racine du `opencode.json` isolé (`OPENCODE_CONFIG`), lu par la session structured | `lifecycle.rs:2686-2689`, `2786-2788` |
| **Codex** | ❌ Non | aucun `--model`, `codex_config_toml` n'écrit pas `model` → défaut du binaire | `codex.rs:215-239`, `lifecycle.rs:2941-2955` |
| **Claude** | ❌ Non | aucun `--model`, `claude_settings_seed` n'écrit pas `model` → défaut du binaire | `claude.rs:273-284`, `infrastructure/permission/claude.rs:59` |
PTY et headless sont **deux processus OS séparés** pour un même agent
(`allow_structured_alongside_pty: true`, `lifecycle.rs:1512-1517`) ; ils partagent le même
`CODEX_HOME`/cwd mais **aucun état modèle** n'est synchronisé entre eux côté IdeA. Le `/model`
de la TUI ne sort jamais du process TUI (pass-through brut, `terminal/usecases.rs:140`).
## 2. Faits CLI vérifiés (capitalisation) — la clé qui débloque tout
### Codex 0.145.0 — `model` est une clé config.toml **documentée**
- L'exemple littéral du `--help` est `-c model="o3"`. `config.toml` est chargé depuis
`$CODEX_HOME/config.toml`.
- **Honorée par `codex` (TUI) ET `codex exec` (headless)** — même mécanisme de base
(`--ignore-user-config` confirme qu'il est chargé par défaut).
- Bonus : `codex exec` accepte aussi `-m, --model <MODEL>` (seconde porte d'injection).
- **→ Écrire `model = "..."` dans le `$CODEX_HOME/config.toml` isolé fixe le défaut pour les
DEUX canaux.**
### Claude 2.1.220 — `model` est gérable via settings + flag
- `--model <model>` fonctionne en interactif **et** `-p/--print` (headless).
- `--settings <file-or-json>` + la hiérarchie `./.claude/settings.local.json` (project-local)
supportent une clé `model`.
- **→ Écrire `"model": "..."` dans le `.claude/settings.local.json` du run dir fixe le défaut
pour les DEUX canaux** (cwd = run dir, lu par PTY et headless).
### Conclusion d'architecture
Les trois moteurs convergent vers **le même pattern** : écrire le modèle du profil dans le
**fichier de config isolé du run dir**. Le fichier partagé = point de vérité unique pour
headless + TUI au lancement. L'exigence produit « modèle par défaut = modèle headless = modèle
exposé TUI au lancement » est satisfaite **par construction**, sans sync à coder.
## 3. Points d'intégration IdeA précis (avec chemins)
| Moteur | Renderer à modifier | Fichier produit | Ownership | Isolation |
|---|---|---|---|---|
| **Codex** | `codex_config_toml` (`application/agent/lifecycle.rs:2941-2955`) | `$CODEX_HOME/config.toml` | `MergeToml` (préservé aux régénérations) | `CODEX_HOME={runDir}/.codex` déjà isolé (`lifecycle.rs:2361`) |
| **Claude** | `claude_settings_seed` (`infrastructure/src/permission/claude.rs:59`) | `{runDir}/.claude/settings.local.json` | `Replace` (régénéré du profil à chaque lancement) | cwd=run dir ; pas d'iso home mais **project-local override user** → le modèle IdeA gagne |
Champ existant à consommer ou déprécier : **`AgentProfile.model: Option<String>`**
(`domain/src/profile.rs:1008`) — actuellement code mort. Référence OpenCode à répliquer :
`OpenCodeProviderConfig { provider_id, model, api_key_ref }` (`domain/src/profile.rs:372-391`)
+ `SaveOpenCodeProviderProfile` (`application/agent/usecases.rs`).
## 4. Architecture proposée — découpage en lots
- **A. Domaine** — Nouveaux VO miroir d'`OpenCodeProviderConfig` :
`CodexProviderConfig { provider, model, api_key_ref }` et `ClaudeProviderConfig { provider,
model, api_key_ref }`. Rendre les backends mutuellement exclusifs par moteur (cf.
`opencode_backend_is_consistent`, `domain/src/profile.rs:363-364,1224`). **Décision
Architect** : nouveau VO vs réutiliser `AgentProfile.model` (voir §6).
- **B. Renderers de config** — `codex_config_toml` écrit `model` (+ `model_provider` si requis
par la sémantique codex) ; `claude_settings_seed` écrit la clé `model`.
- **C. Use cases / persistance** — `SaveCodexProviderProfile` / `SaveClaudeProviderProfile`
miroirs de `SaveOpenCodeProviderProfile`, même SecretRef pour la clé scellée.
- **D. UI / wizard** — duplication de profils Codex/Claude comme OpenCode (catalogue providers,
sélection modèle, clé scellée). Réutiliser `provider_catalogue` + picker existants.
- **E. QA** — assert headless + TUI utilisent le modèle du profil (voir §5).
## 5. Invariants (à respecter, à tester)
- **Source de vérité unique** : le modèle vient du profil AI, écrit dans le fichier de config
isolé du run dir, lu par headless **et** TUI au lancement.
- **Isolation préservée** : Codex via `CODEX_HOME` (rien ne change) ; Claude via project-local
override (rien ne change). Pas d'accès au home global pour le modèle.
- **`/model` en cours de session TUI ne corrompt pas le headless** (acceptable : ne concerne que
le process PTY en cours ; le prochain lancement réapplique le défaut du profil).
- **Non-régression OpenCode** : rendu `opencode.json` byte-identique (rien ne touche ce chemin).
- **Non-régression permissions** : `codex_config_toml` et `claude_settings_seed` continuent
d'écrire `mcp`/`sandbox`/`trust`/`approval` comme aujourd'hui (le `model` s'ajoute).
## 6. Questions ouvertes pour Architect (à trancher avant DevBackend)
1. **VO** : créer `CodexProviderConfig`/`ClaudeProviderConfig` (homogène à OpenCode) **ou**
consommer le champ existant `AgentProfile.model` (code mort) ? Recommandation Main : nouveaux
VO pour rester isomorphe à OpenCode (provider + clé scellée), déprécier `AgentProfile.model`.
2. **Codex `model_provider`** : la config codex distingue `model` et `model_provider`. Faut-il
exposer les deux au profil, ou `model` seul suffit (provider implicite) ?
3. **Claude** : clé `model` dans `settings.local.json` suffit, ou faut-il gérer aussi la clé
API Anthropic du profil (SecretRef) pour que le modèle soit réellement joignable ?
4. **Catalogue providers** : quels providers exposer pour Codex (lié OpenAI) et Claude
(Anthropic) ? Réutiliser `provider_catalogue.rs` ou catalogue dédié par moteur ?
5. **Sémantique TUI** : confirmer qu'aucune CLI n'écrase `config.toml`/`settings` au démarrage
(risque équivalent au « gate refresh » du ticket #98 côté OpenCode).
## 7. Périmètre QA — 2 couches (cf. méthodologie #98)
- **Couche 1 (unitaire pure)** : le rendu `codex_config_toml` contient la clé `model`
attendue ; `claude_settings_seed` contient `"model"` attendue ; non-régression
byte-identique du reste (mcp/sandbox/trust/approval) ; exclusion mutuelle des backends.
- **Couche 2 (intégration gated)** : spawn réel `codex exec` et `claude -p` sur un profil avec
modèle X → assert le modèle actif est X (pas le défaut binaire). Vérifier côté TUI aussi
(modèle exposé au lancement). **Gate §6.5** à lever.
## 8. Hors périmètre (exclu de ce ticket)
- Override **runtime** du modèle à l'appel (`idea_ask_agent` porterait un modèle) — autre lot.
- Providers distants SSH/WSL (ne concerne que le spawn local).
- Détail de la `provider_catalogue` au-delà de la réutilisation (fast-follow).
---
## Contexte de mise à jour
- Cadrage Main après vérification empirique CLI (codex 0.145.0, claude 2.1.220).
- Lié à #98 (relatesTo) — même famille « modèle au spawn ».
- Prochaine étape cycle : **Architect** (valide ports/VO + tranche §6) → **Git** (branche) →
**DevBackend** (lots A-C) + **DevFrontend** (lot D) → **QA** (2 couches) → **Git** (commit).

View File

@ -0,0 +1,53 @@
---
id: "45733f3f-5ef5-4f33-96e7-25afddcd1ce6"
number: 99
title: "Modèle par agent contrôlable pour Codex & Claude (headless + TUI) — égaler le pattern OpenCode"
status: "open"
priority: "high"
sprint: null
links: [{"target":"#98","kind":"relatesTo"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1784987973464
updatedAt: 1784987973464
version: 1
---
## Problème
Aujourd'hui IdeA **ne contrôle pas le modèle** pour les moteurs **Codex** et **Claude** :
le modèle utilisé par le headless inter-agent (`codex exec` / `claude -p`) **et** par la TUI
interactive est le **défaut du binaire CLI installé**, indépendamment du profil AI de l'agent.
- Aucun flag `--model` n'est passé au spawn (`codex.rs:215-239`, `claude.rs:273-284`).
- `codex_config_toml` (`lifecycle.rs:2941-2955`) **n'écrit jamais de clé `model`**.
- `AgentProfile.model` (`profile.rs:1008`) existe mais est du **code mort** (jamais consommé).
- Le `/model` tapé dans la TUI ne se propage **pas** au headless : ce sont deux processus OS
séparés (PTY vs `codex exec`/`claude -p`) avec des threads distincts. Le `CODEX_HOME`/cwd est
partagé mais rien n'écrit le modèle sur disque côté IdeA.
**Contraste** : pour **OpenCode**, le modèle du profil (`OpenCodeConfig.model` /
`OpenCodeProviderConfig.model`) **est** celui utilisé par le headless, via le `opencode.json`
isolé pointé par `OPENCODE_CONFIG` (`lifecycle.rs:2686-2689`). Un profil IdeA = un
(provider, modèle, clé scellée).
## Objectif produit
Qu'il existe un **modèle par défaut par agent**, défini dans le **profil AI**, qui soit :
1. utilisé par le **headless inter-agent** (`idea_ask_agent`) ;
2. **exposé par la TUI au lancement** (valeur initiale du modèle dans la CLI) ;
pour les **trois** moteurs — OpenCode (déjà OK), Codex et Claude (à égaler).
## Périmètre
- **Backend** : nouveau VO domaine miroir d'`OpenCodeProviderConfig`, renderers de config
écrivant le modèle, use cases de persistance.
- **UI** : duplication / création de profils Codex et Claude comme OpenCode (catalogue de
providers, sélection du modèle, clé scellée via SecretRef).
## Lié à
- **#98** (relatesTo) : même famille « contrôle du modèle au spawn » (côté OpenCode, résolveur
zai/glm-5.2). Indépendant mais cohérent.

View File

@ -1,3 +1,3 @@
{
"nextNumber": 80
"nextNumber": 104
}

View File

@ -29,13 +29,13 @@
"issueRef": "#3",
"path": "3",
"title": "[Différé — design à mûrir] Tâches de fond — dette B : retry durable après reboot",
"status": "open",
"status": "closed",
"priority": "low",
"sprint": "e28a4d53-8bd2-446a-b0ac-2a017373b8b2",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784093835732
"updatedAt": 1784377533777
},
{
"issueRef": "#4",
@ -77,13 +77,13 @@
"issueRef": "#7",
"path": "7",
"title": "Gestion des session limite",
"status": "qa",
"status": "closed",
"priority": "high",
"sprint": "d8f3f37b-87ca-4509-9116-45a99bc711df",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1783329459094
"updatedAt": 1784718581033
},
{
"issueRef": "#8",
@ -161,13 +161,13 @@
"issueRef": "#14",
"path": "14",
"title": "Integration de profils IA locaux/LAN comme profils IdeA canoniques",
"status": "qa",
"status": "closed",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1783456579694
"updatedAt": 1784449099987
},
{
"issueRef": "#15",
@ -465,11 +465,11 @@
"issueRef": "#43",
"path": "43",
"title": "Systeme de plugins",
"status": "open",
"status": "closed",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1783879315858
"updatedAt": 1784707900405
},
{
"issueRef": "#44",
@ -591,13 +591,13 @@
"issueRef": "#55",
"path": "55",
"title": "[Bug] Error on loading local model",
"status": "inProgress",
"status": "closed",
"priority": "high",
"sprint": "e28a4d53-8bd2-446a-b0ac-2a017373b8b2",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784097757001
"updatedAt": 1784649948611
},
{
"issueRef": "#56",
@ -625,33 +625,33 @@
"issueRef": "#60",
"path": "60",
"title": "[Bug] Assistant IA d'édition de ticket : aucune réponse dans la conversation (stream sans Final non signalé)",
"status": "inProgress",
"status": "closed",
"priority": "high",
"sprint": "e28a4d53-8bd2-446a-b0ac-2a017373b8b2",
"assignedAgentIds": [],
"updatedAt": 1784194089605
"updatedAt": 1784360139966
},
{
"issueRef": "#61",
"path": "61",
"title": "[UI] rafraichissement des cellule",
"status": "open",
"status": "closed",
"priority": "low",
"sprint": "e28a4d53-8bd2-446a-b0ac-2a017373b8b2",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784182133142
"updatedAt": 1784661823324
},
{
"issueRef": "#62",
"path": "62",
"title": "[Sécurité/Cohérence] Identité requester explicite pour les sessions structurées + policy des tools OpenAI-compatible",
"status": "open",
"status": "closed",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784095187734
"updatedAt": 1784406936659
},
{
"issueRef": "#63",
@ -667,11 +667,11 @@
"issueRef": "#64",
"path": "64",
"title": "Web : créer/ajouter un projet depuis le navigateur (folder browser serveur)",
"status": "open",
"status": "closed",
"priority": "medium",
"sprint": "028179b1-eaf4-41e9-9c1f-7c37125117e6",
"assignedAgentIds": [],
"updatedAt": 1784193766706
"updatedAt": 1784718418070
},
{
"issueRef": "#65",
@ -719,13 +719,13 @@
"issueRef": "#69",
"path": "69",
"title": "Adaptibilité client téléphone",
"status": "qa",
"status": "closed",
"priority": "medium",
"sprint": "028179b1-eaf4-41e9-9c1f-7c37125117e6",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784207736267
"updatedAt": 1784449061397
},
{
"issueRef": "#70",
@ -783,51 +783,299 @@
"issueRef": "#75",
"path": "75",
"title": "Écran d'appairage web : clavier numérique sur mobile alors que le code contient des lettres",
"status": "qa",
"status": "closed",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784277530792
"updatedAt": 1784449082160
},
{
"issueRef": "#76",
"path": "76",
"title": "Appairage : la comparaison du code est sensible à la casse côté serveur",
"status": "open",
"status": "closed",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784277540034
"updatedAt": 1784287679986
},
{
"issueRef": "#77",
"path": "77",
"title": "Appairage : appareils enregistrés persistants, révocables, et code éphémère à usage unique",
"status": "qa",
"status": "closed",
"priority": "high",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784287453378
"updatedAt": 1784449075576
},
{
"issueRef": "#78",
"path": "78",
"title": "Settings desktop : sections en langues mélangées (« Appareils » à côté de « AI Profiles », « Deployment »)",
"status": "open",
"status": "closed",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784284746767
"updatedAt": 1784379475877
},
{
"issueRef": "#79",
"path": "79",
"title": "Test flaky : PermissionsPanel « saves project defaults » échoue par intermittence",
"status": "closed",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784379476483
},
{
"issueRef": "#80",
"path": "80",
"title": "L'environnement de QA ne peut pas ouvrir de socket — il ne peut pas valider les features réseau",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784287697560
},
{
"issueRef": "#81",
"path": "81",
"title": "MCP d'edition de templates",
"status": "closed",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784565918809
},
{
"issueRef": "#82",
"path": "82",
"title": "Ajouter des permissions d'utilisations des tools MCP d'IdeA par agent",
"status": "closed",
"priority": "critical",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784410760329
},
{
"issueRef": "#83",
"path": "83",
"title": "Popup pour vérifier la volonté de quitter l'app",
"status": "closed",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784567952845
},
{
"issueRef": "#84",
"path": "84",
"title": "[Bug] Reprise auto après limite de session ne se déclenche pas pour l'orchestrator (Main, profil Claude)",
"status": "open",
"priority": "high",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784377833773
},
{
"issueRef": "#85",
"path": "85",
"title": "Test flaky : setCellAgent « changing the agent kills the previous PTY (Bug #3) » échoue en exécution shuffled",
"status": "open",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784287450082
"updatedAt": 1784379436323
},
{
"issueRef": "#86",
"path": "86",
"title": "[UI] ajouter l'affichage et l'dition des tickets et des sprints à la version web",
"status": "closed",
"priority": "medium",
"sprint": "028179b1-eaf4-41e9-9c1f-7c37125117e6",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784612705899
},
{
"issueRef": "#87",
"path": "87",
"title": "[UI] sur l'affichage Web, la liste des background task polluent l'affchage",
"status": "closed",
"priority": "high",
"sprint": "028179b1-eaf4-41e9-9c1f-7c37125117e6",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784650196560
},
{
"issueRef": "#89",
"path": "89",
"title": "Pouvoir lancer le serveur au lancement IdeA",
"status": "closed",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784661583231
},
{
"issueRef": "#90",
"path": "90",
"title": "Ajouter les menus manquantes à la version Web d'IdeA",
"status": "closed",
"priority": "high",
"sprint": null,
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784661583085
},
{
"issueRef": "#91",
"path": "91",
"title": "Sur la notification de fin de tache backend, mettre plutot les deux agents en conversation",
"status": "open",
"priority": "medium",
"sprint": "5afd6780-0f76-40d7-a10f-32ee52469d74",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784994105390
},
{
"issueRef": "#92",
"path": "92",
"title": "Configurer OpenCode avec un provider Opencode",
"status": "closed",
"priority": "high",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784993956910
},
{
"issueRef": "#93",
"path": "93",
"title": "Agent Context : réponse finale non capturée, fragment d'appel d'outil brut renvoyé via idea_ask_agent",
"status": "closed",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784934179780
},
{
"issueRef": "#94",
"path": "94",
"title": "Permissions OpenCode ignorées : opencode.json code en dur bash/edit=ask au lieu de projeter EffectivePermissions",
"status": "closed",
"priority": "high",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784823278030
},
{
"issueRef": "#95",
"path": "95",
"title": "Rendez-vous idea_ask_agent : timeout MCP OpenCode trop court (15s), le demandeur OpenCode ne reçoit jamais la réponse",
"status": "closed",
"priority": "high",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784934174326
},
{
"issueRef": "#96",
"path": "96",
"title": "Les assistants de ticket (tous adaptateurs) n'ont aucune résolution EffectivePermissions — permission toujours native/absente",
"status": "open",
"priority": "low",
"sprint": "883534aa-7fc1-4d83-a9c0-17cac4a4eea5",
"assignedAgentIds": [],
"updatedAt": 1784994119004
},
{
"issueRef": "#97",
"path": "97",
"title": "Duplication de provider lors de la création de profil AI - llamacpp ET provider cloud inclus simultanément",
"status": "closed",
"priority": "high",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784915626417
},
{
"issueRef": "#98",
"path": "98",
"title": "Profil cloud provider catalogue (ex: zai) : modèle choisi écrasé par glm-5.2 au spawn",
"status": "closed",
"priority": "high",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784983186248
},
{
"issueRef": "#99",
"path": "99",
"title": "Modèle par agent contrôlable pour Codex & Claude (headless + TUI) — égaler le pattern OpenCode",
"status": "open",
"priority": "high",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784987973464
},
{
"issueRef": "#100",
"path": "100",
"title": "[Bug] Problème sur le scroll des agents OpenCode",
"status": "open",
"priority": "high",
"sprint": "e28a4d53-8bd2-446a-b0ac-2a017373b8b2",
"assignedAgentIds": [],
"updatedAt": 1784993984569
},
{
"issueRef": "#101",
"path": "101",
"title": "[Bug] Soucis de retour de notification sur les taches backend",
"status": "closed",
"priority": "critical",
"sprint": "e28a4d53-8bd2-446a-b0ac-2a017373b8b2",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1785011116365
},
{
"issueRef": "#102",
"path": "102",
"title": "[Bug] Devoir resize les cellule pour afficher la TUI d'un agent",
"status": "open",
"priority": "medium",
"sprint": "e28a4d53-8bd2-446a-b0ac-2a017373b8b2",
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1784993980505
},
{
"issueRef": "#103",
"path": "103",
"title": "Exposer et piloter la permission réseau des agents/commandes dans IdeA",
"status": "qa",
"priority": "high",
"sprint": null,
"assignedAgentIds": [
"a6ced819-b893-4213-b003-9e9dc79b9641"
],
"updatedAt": 1785013507979
}
]
}

5
Cargo.lock generated
View File

@ -98,6 +98,7 @@ name = "application"
version = "0.3.0"
dependencies = [
"async-trait",
"dirs",
"domain",
"serde",
"serde_json",
@ -159,6 +160,7 @@ version = "0.3.0"
dependencies = [
"application",
"async-trait",
"base64 0.22.1",
"domain",
"infrastructure",
"interprocess",
@ -1970,13 +1972,16 @@ dependencies = [
"fastembed",
"futures-util",
"git2",
"hex",
"landlock",
"notify",
"portable-pty",
"regex",
"reqwest 0.12.28",
"ring",
"serde",
"serde_json",
"sha2",
"thiserror 2.0.18",
"tokio",
"uuid",

View File

@ -24,8 +24,10 @@ futures-util = "0.3"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "fs", "io-util", "time"] }
hex = "0.4"
sha2 = "0.10"
dirs = "6"
subtle = "2"
getrandom = "0.3"
http = "1"
# Local git via libgit2. Network features (https/ssh → openssl) are off for L8:
# only local operations (status/commit/branch/checkout/log) are in scope; remote
# push/pull and static vendoring for the AppImage are deferred to L9/L11.

View File

@ -32,12 +32,12 @@ tauri-plugin-dialog = { workspace = true }
tokio = { workspace = true, features = ["io-std", "rt", "net"] }
serde = { workspace = true }
serde_json = { workspace = true }
http = { workspace = true }
thiserror = { workspace = true }
uuid = { workspace = true }
base64 = "0.22"
bytes = "1.11"
cookie = "0.18"
http = "1.4"
http-body-util = "0.1"
# `AppAgentResumer` implements the application's async `AgentResumer` port (LS7).
async-trait = { workspace = true }

Some files were not shown because too many files have changed in this diff Show More