release(0.1.0): première release — intégration de develop

Première release d'IdeA. Intègre dans main l'ensemble du travail
stabilisé sur develop :
- exécution structurée des agents IA + adapters Claude/Codex
- orchestration v3 MCP + discussion inter-agents (FIFO, conversation par paire)
- persistance/reprise de conversation + handoff cross-profile
- enforcement OS des permissions (Landlock) + sandbox
- terminal natif, layouts redimensionnables, panneaux Agents/Permissions

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 14:01:37 +02:00
463 changed files with 126650 additions and 0 deletions

8
.cargo/config.toml Normal file
View File

@ -0,0 +1,8 @@
# Build configuration for the IdeA workspace.
#
# Link the system libgit2 (via pkg-config) instead of vendoring/compiling it from
# source. The dev machines and CI provide libgit2 ≥ 1.9 (matching git2 0.20), and
# this avoids requiring cmake for the vendored build. Static vendoring for the
# portable AppImage is handled separately at packaging time (L11).
[env]
LIBGIT2_SYS_USE_PKG_CONFIG = "1"

14
.gitignore vendored
View File

@ -24,6 +24,20 @@ frontend/coverage/
# ─── Claude Code ────────────────────────────────────────────────────────────
# Personal, machine-local overrides (shared settings.json, if any, stays tracked).
.claude/settings.local.json
# Ephemeral git worktrees created by Claude Code's isolated sub-agents — dev
# tooling only, unrelated to IdeA (which stays git-independent).
.claude/worktrees/
# ─── IdeA project data ──────────────────────────────────────────────────────
# Ephemeral per-agent run directories (isolated PTY cwd + generated convention
# files), created at activation — not versioned (ARCHITECTURE §9.1 / §14.1).
.ideai/run/
# Derived vector store for semantic recall (LOT C / §14.5.3): embeddings of the
# memory notes, rebuildable from the `.md` source of truth — not versioned.
.ideai/memory/.index/
# Runtime file-protocol orchestration requests/responses — transient I/O, not
# durable project state (curation .ideai §chantier secondaire).
.ideai/requests/
# ─── Editors / OS ───────────────────────────────────────────────────────────
.idea/

47
.ideai/agents.json Normal file
View File

@ -0,0 +1,47 @@
{
"version": 1,
"agents": [
{
"agentId": "a6ced819-b893-4213-b003-9e9dc79b9641",
"name": "Main",
"mdPath": "agents/main.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
},
{
"agentId": "dce19c75-9669-4e45-b8de-9950025157da",
"name": "Architect",
"mdPath": "agents/architect.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
},
{
"agentId": "73c853d1-c0fd-463b-ad17-1d24fefa371f",
"name": "DevBackend",
"mdPath": "agents/devbackend.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
},
{
"agentId": "af7f86da-76bc-48e1-9900-71f45a624800",
"name": "DevFrontend",
"mdPath": "agents/devfrontend.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
},
{
"agentId": "aefdbd61-e3d4-4bc1-9f42-c259446a97b5",
"name": "QA",
"mdPath": "agents/qa.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
},
{
"agentId": "cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5",
"name": "Git",
"mdPath": "agents/git.md",
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
"synchronized": false
}
]
}

604
.ideai/agents/architect.md Normal file
View File

@ -0,0 +1,604 @@
# IdeA — Cartographie d'Architecture
> Document de référence produit par l'**Agent Architecture**.
> Fait autorité sur les frontières, ports, adapters, modules et conventions.
> Toute feature DOIT être validée contre ce document avant développement.
> Architecture **Hexagonale (Ports & Adapters)** + **SOLID**, stricte.
>
> Stack non négociable : Tauri v2 (shell) · Rust (cœur hexagonal) · TypeScript + React (UI) · xterm.js + portable-pty (terminaux) · git2/libgit2 · russh/ssh2 · wsl.exe.
---
## 1. Principes : SOLID + Hexagonal, appliqués concrètement
### 1.1 Règle de dépendance (la seule qui compte)
```
┌─────────────────────────────────────────────┐
│ Le sens des dépendances │
│ │
Présentation ─► Application ─► Domaine ◄─ Infrastructure │
(React/Tauri) (use cases) (pur) (adapters) │
│ │
└─────────────────────────────────────────────┘
```
- **Le Domaine ne dépend de RIEN** : ni Tauri, ni tokio, ni git2, ni portable-pty, ni serde (le moins possible — voir §1.4). Il ne contient que des entités, value objects, règles métier et **traits = ports**.
- **L'Application** dépend du Domaine. Elle orchestre les use cases en parlant **uniquement aux ports** (traits), jamais aux adapters concrets.
- **L'Infrastructure** dépend du Domaine et de l'Application (elle implémente les ports). Elle contient tous les détails techniques (PTY, FS, git, SSH, WSL, stores).
- **La Présentation** (Tauri commands + React) dépend de l'Application. Les commandes Tauri sont des **adapters entrants (driving adapters)** ; les impl de ports sont des **adapters sortants (driven adapters)**.
Aucune flèche ne pointe **vers** la présentation ou l'infrastructure. L'inversion de dépendance (le **D** de SOLID) est matérialisée par les traits définis dans le domaine et implémentés dehors.
### 1.2 SOLID, point par point, traduit IdeA
| Principe | Application concrète |
|---|---|
| **S** — Single Responsibility | Un use case = une intention métier (`LaunchAgent`, `SyncAgentWithTemplate`). Un adapter = une techno (`Git2Repository` ne fait que du git). Le `LayoutNode` ne gère que la topologie, pas le rendu. |
| **O** — Open/Closed | Ajouter une IA = ajouter un **profil déclaratif** (donnée), pas du code. Ajouter un mode distant = nouvel adapter `RemoteHost` sans toucher aux use cases. Ajouter une stratégie d'injection de contexte = nouvelle variante d'enum + handler, use case inchangé. |
| **L** — Liskov | Tout `RemoteHost` (local, SSH, WSL) est substituable : un use case marche identiquement quelle que soit l'impl. Les contrats (pré/postconditions) des ports sont documentés et respectés par chaque adapter. |
| **I** — Interface Segregation | Ports **fins et ciblés** : `ProcessSpawner`, `FileSystem`, `PtyPort` séparés plutôt qu'un `System` fourre-tout. Un use case ne reçoit que les ports qu'il consomme. |
| **D** — Dependency Inversion | Domaine définit les traits ; infra les implémente ; l'application reçoit des `Arc<dyn Port>` par **injection** (composition root dans la couche Tauri). |
### 1.3 Hexagonal côté Frontend (React aussi)
L'hexagonal ne s'arrête pas à Rust. Côté React on applique le même découpage :
- **Domaine UI / modèles de vue** : types TS purs (miroir des DTO), logique de présentation pure (ex. calcul de tailles de cellules d'un `LayoutNode`), testable sans React ni Tauri.
- **Ports UI** : interfaces TS (`AgentGateway`, `TerminalGateway`, `ProjectGateway`, `LayoutGateway`, `GitGateway`, `RemoteGateway`) décrivant **ce dont l'UI a besoin**, indépendamment du transport.
- **Adapters UI** : implémentation des ports via `@tauri-apps/api` (`invoke` pour commands, `listen` pour events). Remplaçables par des **mocks** en test/Storybook.
- **Présentation** : composants React, hooks, state (Zustand/Redux) qui consomment les ports UI, jamais `invoke()` en direct.
Bénéfice : le frontend est testable et développable sans backend (adapters mock), et la frontière IPC est centralisée en un seul endroit.
### 1.4 Domaine pur vs adapters — règle pratique Rust
- Le crate `domain` est **`#![no_std]`-friendly d'esprit** (pas imposé), sans dépendance I/O. Tolérance pragmatique : `serde` est autorisé **uniquement** pour dériver la (dé)sérialisation des entités persistées (manifeste, layout, profils), car c'est une contrainte métier de format, pas un détail technique d'I/O. Les **traits/ports** y vivent. Pas de `tokio`, pas de `std::process`, pas de `std::fs`.
- Tout ce qui touche le monde réel (`std::fs`, `Command`, sockets, libgit2, PTY) vit **exclusivement** dans `infrastructure`.
---
## 2. Découpage en couches & frontière Rust ↔ Tauri ↔ React
```
┌───────────────────────────────────────────────────────────────────────┐
│ PRÉSENTATION (Frontend) — TypeScript + React + xterm.js │
│ features/* · ui-ports (gateways) · tauri-adapters (invoke/listen) │
└───────────────────────────────┬───────────────────────────────────────┘
│ IPC Tauri (commands ⇄ events, JSON)
┌───────────────────────────────▼───────────────────────────────────────┐
│ PRÉSENTATION (Backend) — crate `app-tauri` (DRIVING ADAPTER) │
│ #[tauri::command] handlers · event emitters · COMPOSITION ROOT (DI) │
│ PTY byte-stream bridge ⇄ xterm.js │
└───────────────────────────────┬───────────────────────────────────────┘
│ appels de use cases (Arc<UseCase>)
┌───────────────────────────────▼───────────────────────────────────────┐
│ APPLICATION — crate `application` │
│ Use cases / services · DTOs · orchestration · transactions métier │
│ Dépend UNIQUEMENT des ports (traits) du domaine │
└───────────────────────────────┬───────────────────────────────────────┘
│ implémente / consomme
┌───────────────────────────────▼───────────────────────────────────────┐
│ DOMAINE — crate `domain` (PUR, sans I/O) │
│ Entities · Value Objects · Invariants · PORTS (traits) · DomainEvents │
└───────────────────────────────▲───────────────────────────────────────┘
│ implémentent les ports (DRIVEN ADAPTERS)
┌───────────────────────────────┴───────────────────────────────────────┐
│ INFRASTRUCTURE — crate `infrastructure` │
│ portable-pty · git2 · russh/ssh2 · wsl.exe · fs local · md/json store │
└─────────────────────────────────────────────────────────────────────────┘
```
### Frontière IPC Tauri — deux directions
- **Commands (Frontend → Backend, request/response)** : `invoke("create_project", {...})`. Le handler `#[tauri::command]` désérialise le DTO, appelle le use case, renvoie un `Result<DTO, ErrorDTO>`. **Stateless** côté forme : tout l'état vit dans des services managés via `tauri::State`.
- **Events (Backend → Frontend, push)** : flux PTY (octets/base64), changements de statut d'agent, fin de processus, progrès git, drift de template détecté. Émis via `app_handle.emit(...)` / channels Tauri. L'`EventBus` domaine est relayé vers ces events Tauri par un adapter dans `app-tauri`.
> **Décision** : le flux PTY haute fréquence passe par des **Tauri Channels** (`tauri::ipc::Channel`) plutôt que des events globaux, pour la perf et l'isolement par session terminal.
---
## 3. Modèle de domaine
### 3.1 Vue d'ensemble (relations)
```
Workspace 1───* Window 1───* Tab 1───1 Project
│ │
│ 1 ├──* Agent ─────? AgentTemplate (origine)
│ │ │ 1
│ 1 │ └──1 AgentProfile (runtime IA, par réf id)
LayoutTree ├──1 GitRepository
(LayoutNode récursif) ├──1 RemoteHost (Local | Ssh | Wsl)
│ feuilles └──1 AgentManifest (.ideai/agents.json)
TerminalSession 1───? Agent (si lancé par un agent)
```
### 3.2 Entités & Value Objects (avec invariants)
**`ProjectId`, `AgentId`, `TemplateId`, `ProfileId`, `SessionId`, `WindowId`, `TabId`, `NodeId`** — VO `newtype(Uuid)` ou string typée. Invariant : non vide, immuable.
**`Project`** (entité, racine d'agrégat projet)
- Champs : `id`, `name`, `root: ProjectPath`, `remote: RemoteRef`, `created_at`.
- Invariants : `root` doit être un chemin **absolu et valide pour son `RemoteRef`** ; deux projets ne peuvent partager le même `(remote, root)`.
**`ProjectPath`** (VO) — chemin absolu normalisé, conscient de la plateforme cible (POSIX vs Windows vs WSL `/mnt/...`).
**`Agent`** (entité)
- Champs : `id`, `name`, `context: AgentContextRef` (chemin du `.md` dans `.ideai/`), `profile_id: ProfileId`, `origin: AgentOrigin` (`Scratch` | `FromTemplate { template_id, synced_version }`), `synchronized: bool`.
- Invariants : `synchronized == true``origin == FromTemplate{..}` (on ne peut pas synchroniser un agent créé from scratch). `context` doit exister à l'activation. `profile_id` doit référencer un `AgentProfile` connu.
**`AgentTemplate`** (entité, store global)
- Champs : `id`, `name`, `content_md: MarkdownDoc`, `version: TemplateVersion`, `default_profile_id`.
- Invariants : `version` **monotone croissante** ; toute modification du `content_md``version + 1` (voir §8).
**`AgentProfile`** (entité de config runtime IA — le port `AgentRuntime` est paramétré par elle)
- Champs : `id`, `name`, `command: String`, `args: Vec<String>`, `context_injection: ContextInjection`, `detect: Option<String>`, `cwd_template: String` (ex. `"{projectRoot}"`).
- Invariants : `command` non vide ; cohérence de `ContextInjection` (voir VO ci-dessous).
**`ContextInjection`** (VO, enum — cœur du moteur IA flexible)
```
ContextInjection =
| ConventionFile { target: String } // ex. "CLAUDE.md" / "AGENTS.md" / "GEMINI.md"
| Flag { flag: String } // ex. "--context-file {path}" ou "-f"
| Stdin // pipe du contenu md sur stdin
| Env { var: String } // ex. "AGENT_CONTEXT_FILE"
```
- Invariants : `ConventionFile.target` est un nom de fichier relatif (pas de `..`, pas absolu) ; `Env.var` est un identifiant d'env valide ; `Flag.flag` non vide.
**`TerminalSession`** (entité)
- Champs : `id`, `node_id` (cellule du layout qui l'héberge), `cwd: ProjectPath`, `kind: SessionKind` (`Plain` | `Agent { agent_id }`), `pty_size: PtySize { rows, cols }`, `status` (`Starting|Running|Exited{code}`).
- Invariants : une cellule (feuille de layout) héberge **au plus une** `TerminalSession` active. `pty_size.rows>0 && cols>0`.
**`LayoutNode` / `LayoutTree`** (VO récursif — voir §7 pour le détail complet)
- Invariants : poids relatifs strictement positifs ; somme normalisable ; pas de fusion qui chevauche deux conteneurs distincts ; un `Leaf` référence 0 ou 1 `SessionId`.
**`RemoteHost`** (VO de stratégie de localisation — abstrait Local/SSH/WSL)
```
RemoteRef =
| Local
| Ssh { host, port, user, auth: SshAuth, remote_root }
| Wsl { distro: String }
```
- Invariants : `Ssh.port` ∈ 1..=65535 ; `Wsl.distro` non vide ; pour `Ssh`/`Wsl`, les chemins projet sont interprétés côté distant.
**`GitRepository`** (entité)
- Champs : `project_id`, `root`, `current_branch`, `is_dirty`.
- Invariants : `root` contient (ou contiendra après init) un `.git`. État dérivé, rafraîchi via le port.
**`AgentManifest`** (entité — image en mémoire de `.ideai/agents.json`)
- Champs : `entries: Vec<ManifestEntry { agent_id, md_path, template_id?, synchronized, synced_template_version? }>`.
- Invariants : `synchronized ⇒ template_id.is_some() && synced_template_version.is_some()` ; `md_path` unique ; cohérence avec les `Agent` chargés.
**`Workspace` / `Window` / `Tab`** (entités de présentation persistée)
- `Workspace` = ensemble des fenêtres d'une session utilisateur.
- `Window` = fenêtre OS ; possède un `LayoutTree` **par onglet actif** et une liste de `Tab`.
- `Tab` = onglet ⇔ **un `Project`** (1:1).
- Invariants : un `Project` ouvert apparaît dans **exactement un** `Tab` à la fois (le drag déplace, ne duplique pas) ; un `Window` a ≥ 1 `Tab` ou est fermée.
**`DomainEvent`** (enum) — `ProjectCreated`, `AgentLaunched`, `AgentExited`, `TemplateUpdated`, `AgentDriftDetected`, `LayoutChanged`, `RemoteConnected`, `GitStateChanged`, `PtyOutput{session_id, bytes}` (ce dernier souvent court-circuité vers un Channel).
---
## 4. Ports (traits du domaine)
> Signatures **conceptuelles** (Rust idiomatique, `async` via `async_trait` ou retours `Future` ; erreurs typées par port). « Consommé par » = use cases. « Implémenté par » = adapters de §5.
### `AgentRuntime`
- **Rôle** : lancer/piloter la CLI d'une IA selon un `AgentProfile`, en gérant l'injection du contexte `.md`.
- **Signature** :
```rust
trait AgentRuntime {
fn detect(&self, profile: &AgentProfile) -> Result<bool, RuntimeError>;
fn prepare_invocation(&self, profile: &AgentProfile, ctx: &PreparedContext, cwd: &ProjectPath)
-> Result<SpawnSpec, RuntimeError>; // commande + args + plan d'injection (fichier/flag/stdin/env)
}
```
- **Consommé par** : `LaunchAgent`, `DetectProfilesUseCase` (first-run).
- **Implémenté par** : `CliAgentRuntime` (un seul adapter générique piloté par le profil déclaratif — c'est l'**Open/Closed**). La diversité des IA = données, pas code.
### `PtyPort` (alias domaine de `TerminalSessionPort`)
- **Rôle** : ouvrir un pseudo-terminal, lire/écrire, redimensionner, tuer.
- **Signature** :
```rust
trait PtyPort {
async fn spawn(&self, spec: SpawnSpec, size: PtySize) -> Result<PtyHandle, PtyError>;
fn write(&self, h: &PtyHandle, data: &[u8]) -> Result<(), PtyError>;
fn resize(&self, h: &PtyHandle, size: PtySize) -> Result<(), PtyError>;
fn subscribe_output(&self, h: &PtyHandle) -> OutputStream; // flux d'octets
async fn kill(&self, h: &PtyHandle) -> Result<ExitStatus, PtyError>;
}
```
- **Consommé par** : `OpenTerminal`, `LaunchAgent`, `CloseTerminal`.
- **Implémenté par** : `PortablePtyAdapter` (local), `SshPtyAdapter` (PTY distant via russh exec/shell), `WslPtyAdapter` (PTY via `wsl.exe`). Sélection par stratégie `RemoteRef` (Liskov).
### `RemoteHost`
- **Rôle** : abstraction de la **localisation d'exécution** (local / SSH / WSL) : exécuter une commande, ouvrir un PTY, accéder au FS, dans le bon contexte.
- **Signature** :
```rust
trait RemoteHost {
fn kind(&self) -> RemoteKind;
async fn connect(&self) -> Result<(), RemoteError>;
fn file_system(&self) -> Arc<dyn FileSystem>;
fn process_spawner(&self) -> Arc<dyn ProcessSpawner>;
fn pty(&self) -> Arc<dyn PtyPort>;
}
```
- **Consommé par** : tous les use cases qui touchent un projet (résolvent leurs ports via le `RemoteHost` du projet → **transparence local/distant**).
- **Implémenté par** : `LocalHost`, `SshHost` (russh/ssh2), `WslHost` (wsl.exe). C'est la **stratégie** qui unifie les 3 modes.
### `ProcessSpawner`
- **Rôle** : lancer un process **non interactif** et récupérer sortie/exit (ex. `detect`, commandes git hors libgit2, scripts).
- **Signature** : `async fn run(&self, spec: SpawnSpec) -> Result<Output, ProcessError>;`
- **Consommé par** : `DetectProfilesUseCase`, services divers.
- **Implémenté par** : `LocalProcessSpawner`, `SshProcessSpawner`, `WslProcessSpawner`.
### `FileSystem`
- **Rôle** : lecture/écriture/listing/symlink, neutre vis-à-vis de la localisation.
- **Signature** :
```rust
trait FileSystem {
async fn read(&self, p: &RemotePath) -> Result<Vec<u8>, FsError>;
async fn write(&self, p: &RemotePath, data: &[u8]) -> Result<(), FsError>;
async fn exists(&self, p: &RemotePath) -> Result<bool, FsError>;
async fn create_dir_all(&self, p: &RemotePath) -> Result<(), FsError>;
async fn list(&self, p: &RemotePath) -> Result<Vec<DirEntry>, FsError>;
async fn symlink(&self, src: &RemotePath, dst: &RemotePath) -> Result<(), FsError>;
}
```
- **Consommé par** : `AgentContextStore`, `ProjectStore`, injection `conventionFile`, etc.
- **Implémenté par** : `LocalFileSystem` (std::fs/tokio::fs), `SshFileSystem` (SFTP), `WslFileSystem` (via `wsl.exe` ou chemins `\\wsl$`).
### `TemplateStore`
- **Rôle** : CRUD des `AgentTemplate` dans le store global IDE + versioning.
- **Signature** : `list / get / save / delete / bump_version`.
- **Consommé par** : `CreateTemplate`, `UpdateTemplate`, `CreateAgentFromTemplate`, `SyncAgentWithTemplate`.
- **Implémenté par** : `FsTemplateStore` (md + index json dans le dossier de données app).
### `ProjectStore`
- **Rôle** : persistance de la liste des projets connus, workspaces, windows, tabs, layouts.
- **Signature** : `list_projects / load_project / save_project / save_workspace / load_workspace`.
- **Consommé par** : `CreateProject`, `OpenProject`, persistance fenêtres/onglets/layout.
- **Implémenté par** : `FsProjectStore` (json dans données app pour le registre ; layout par projet dans `.ideai/`).
### `AgentContextStore`
- **Rôle** : lire/écrire les `.md` d'agents **et** le manifeste `.ideai/agents.json` (au sein du projet, via le `FileSystem` du `RemoteHost`).
- **Signature** :
```rust
trait AgentContextStore {
async fn read_context(&self, project: &Project, agent: &AgentId) -> Result<MarkdownDoc, StoreError>;
async fn write_context(&self, project: &Project, agent: &AgentId, md: &MarkdownDoc) -> Result<(), StoreError>;
async fn load_manifest(&self, project: &Project) -> Result<AgentManifest, StoreError>;
async fn save_manifest(&self, project: &Project, m: &AgentManifest) -> Result<(), StoreError>;
}
```
- **Consommé par** : `CreateAgent*`, `LaunchAgent`, `SyncAgentWithTemplate`.
- **Implémenté par** : `IdeaiContextStore` (compose `FileSystem`, écrit `.ideai/`).
### `GitRepository`
- **Rôle** : opérations git du projet.
- **Signature** : `status / stage / unstage / commit / branches / checkout / current_branch / diff / log / pull / push / clone / init`.
- **Consommé par** : use cases Git.
- **Implémenté par** : `Git2Repository` (libgit2, local) ; sur SSH/WSL, `RemoteGitRepository` délègue à git CLI via `ProcessSpawner` quand libgit2 ne peut pas atteindre le FS distant (point ouvert §13).
### `EventBus`
- **Rôle** : publier/souscrire les `DomainEvent` (découple émetteurs et présentation).
- **Signature** : `fn publish(&self, e: DomainEvent); fn subscribe(&self) -> EventStream;`
- **Consommé par** : tous use cases (publient) ; l'adapter Tauri (souscrit → relaye en events/channels IPC).
- **Implémenté par** : `TokioBroadcastEventBus` (in-process), relayé par `TauriEventRelay`.
### `Clock` & `IdGenerator` (ports utilitaires — testabilité)
- **Rôle** : éliminer le non-déterminisme (`now()`, `uuid`) du domaine/application.
- **Implémenté par** : `SystemClock` / `UuidGenerator` (prod), `FixedClock` / `SeqIdGenerator` (tests).
---
## 5. Adapters (impl concrètes par port)
| Port | Adapter(s) | Techno | Notes |
|---|---|---|---|
| `AgentRuntime` | `CliAgentRuntime` | piloté par `AgentProfile` | Construit `SpawnSpec` + plan d'injection. Un seul adapter, N profils. |
| `PtyPort` | `PortablePtyAdapter` | portable-pty | Local. Stream octets → Channel Tauri. |
| | `SshPtyAdapter` | russh (channel shell/exec + pty req) | Distant SSH. |
| | `WslPtyAdapter` | `wsl.exe -d <distro>` + portable-pty | PTY dans la distro. |
| `RemoteHost` | `LocalHost` / `SshHost` / `WslHost` | — / russh,ssh2 / wsl.exe | Stratégie ; fabrique FS/Spawner/PTY adaptés. |
| `ProcessSpawner` | `LocalProcessSpawner` | std/tokio `Command` | |
| | `SshProcessSpawner` | russh exec | |
| | `WslProcessSpawner` | `wsl.exe` | |
| `FileSystem` | `LocalFileSystem` | tokio::fs | |
| | `SshFileSystem` | SFTP (ssh2/russh-sftp) | |
| | `WslFileSystem` | `\\wsl$\` / `wsl.exe cat`… | |
| `TemplateStore` | `FsTemplateStore` | tokio::fs + serde_json | Dossier données app. |
| `ProjectStore` | `FsProjectStore` | tokio::fs + serde_json | Registre projets + workspace. |
| `AgentContextStore` | `IdeaiContextStore` | compose `FileSystem` | Écrit `.ideai/`. |
| `GitRepository` | `Git2Repository` | git2 | Local. |
| | `RemoteGitRepository` | git CLI via `ProcessSpawner` | SSH/WSL fallback. |
| `EventBus` | `TokioBroadcastEventBus` (+ `TauriEventRelay`) | tokio::broadcast | Relais vers IPC. |
| `Clock`/`IdGenerator` | `SystemClock`/`UuidGenerator` | std/uuid | Mocks en test. |
**Adapters entrants (driving)** : handlers `#[tauri::command]` (frontend → app) + `TauriEventRelay` (app → frontend). Côté UI : `tauri-adapters` implémentant les gateways TS.
---
## 6. Use cases / services applicatifs
> Chaque use case : un struct `XxxUseCase` portant ses ports en `Arc<dyn Port>`, une méthode `execute(input: XxxInput) -> Result<XxxOutput, AppError>`. **Single Responsibility**. Aucune dépendance à Tauri.
| Use case | Rôle | Ports consommés |
|---|---|---|
| `CreateProject` | Crée un projet (project root), init `.ideai/`, registre. | `ProjectStore`, `FileSystem`, `IdGenerator`, `EventBus` |
| `OpenProject` | Charge projet, manifeste, layout, résout `RemoteHost`. | `ProjectStore`, `AgentContextStore`, `RemoteHost` |
| `CloseProject` / `CloseTab` | Persiste l'état, libère PTYs. | `ProjectStore`, `PtyPort`, `EventBus` |
| `DetectProfiles` (first-run) | Teste `detect` de chaque profil candidat. | `AgentRuntime`, `ProcessSpawner` |
| `ConfigureProfiles` | Enregistre profils choisis/édités/custom. | `TemplateStore`/profile store, `FileSystem` |
| `CreateAgentFromScratch` | Crée agent + `.md`, met à jour manifeste. | `AgentContextStore`, `IdGenerator` |
| `CreateAgentFromTemplate` | Copie le `content_md` du template → agent ; lie origine + version + `synchronized`. | `TemplateStore`, `AgentContextStore` |
| `UpdateTemplate` | Modifie un template, **bump version**, signale drift aux agents liés. | `TemplateStore`, `EventBus` |
| `DetectAgentDrift` | Compare `synced_template_version` vs `template.version`. | `TemplateStore`, `AgentContextStore` |
| `SyncAgentWithTemplate` | Applique la MAJ template→agent si `synchronized`. | `TemplateStore`, `AgentContextStore`, `EventBus` |
| `LaunchAgent` | Résout profil+contexte, prépare injection, ouvre cellule PTY au bon `cwd`, spawn CLI. | `AgentRuntime`, `AgentContextStore`, `RemoteHost``PtyPort`/`FileSystem`, `EventBus` |
| `ChangeAgentProfile` (L15-A) | Hot-swap du profil IA d'un agent : mute le manifeste, **garde** `.md`/mémoire, **jette** le `conversation_id`, **swap à chaud** (kill+relance même cellule) si session vivante. Compose `LaunchAgent`. | `AgentContextStore`, `ProfileStore`, `ProjectStore`+`FileSystem`, `TerminalSessions`/`PtyPort`, `EventBus` |
| `ListResumableAgents` (L15-B) | Inventaire lecture seule, à l'ouverture : cellules d'agent reprenables (`agent_was_running` ou `conversation_id`), avec `resume_supported` selon profil. Aucun spawn. | `ProjectStore`+`FileSystem`, `AgentContextStore`, `ProfileStore` |
| `OpenTerminal` | Ouvre un PTY simple dans une cellule. | `RemoteHost``PtyPort`, `EventBus` |
| `WriteToTerminal` / `ResizeTerminal` / `CloseTerminal` | I/O PTY. | `PtyPort` |
| `MutateLayout` (split/merge/resize/move) | Applique une opération sur le `LayoutTree` (logique **pure** dans le domaine, persistée ici). | `ProjectStore` (persistance) |
| `ConnectRemote` (SSH/WSL) | Établit la connexion, valide l'accès au root. | `RemoteHost`, `FileSystem` |
| `MoveTabToNewWindow` | Détache un onglet → nouvelle fenêtre (réaffectation `WindowId`). | `ProjectStore`, `EventBus` |
| Use cases Git | `GitStatus`, `GitCommit`, `GitCheckout`, `GitPush`, … | `GitRepository`, `EventBus` |
---
## 7. Modèle de layout terminal (grille tableur récursive + fusion)
### 7.1 Structure de données
La grille « type tableur, lignes/colonnes imbriquées indépendamment + fusion » est modélisée par un **arbre de splits récursif** où chaque conteneur définit son propre découpage. La **fusion** est obtenue nativement : fusionner = ne pas subdiviser une zone (un `Leaf` couvre plusieurs « cellules visuelles » d'un parent voisin). Pour le cas Excel pur (fusion arbitraire chevauchant la grille), on superpose un modèle **GridContainer** avec spans.
```rust
enum LayoutNode {
Leaf(LeafCell),
Split(SplitContainer),
Grid(GridContainer),
}
struct LeafCell {
id: NodeId,
session: Option<SessionId>, // 0 ou 1 terminal
}
struct SplitContainer { // découpage simple binaire/n-aire pondéré
id: NodeId,
direction: Direction, // Row (colonnes) | Column (lignes)
children: Vec<WeightedChild>, // ordre = gauche→droite / haut→bas
}
struct WeightedChild { node: LayoutNode, weight: f32 } // poids = part redimensionnable
struct GridContainer { // grille tableur avec fusion (spans)
id: NodeId,
col_weights: Vec<f32>, // largeurs de colonnes
row_weights: Vec<f32>, // hauteurs de lignes
cells: Vec<GridCell>, // placements avec spans (fusion)
}
struct GridCell {
node: LayoutNode, // récursif : une cellule peut re-contenir un Split/Grid
row: u16, col: u16,
row_span: u16, // ≥1 ; >1 = cellules fusionnées verticalement
col_span: u16, // ≥1 ; >1 = cellules fusionnées horizontalement
}
```
- **Lignes/colonnes indépendantes par zone** : chaque `SplitContainer`/`GridContainer` a ses propres poids ⇒ pas de grille uniforme rigide.
- **Imbrication** : un enfant peut être un nouveau `Split`/`Grid` ⇒ « N colonnes dans une ligne, M lignes dans une colonne » de façon arbitraire.
- **Fusion** : `row_span`/`col_span` dans `GridContainer` (modèle tableur fidèle) **ou** simplement un `Leaf` plus grand via `SplitContainer` (cas courant). Le domaine supporte les deux ; l'UI choisit la représentation selon l'interaction.
### 7.2 Invariants (validés dans le domaine, testables sans I/O)
- Tous les `weight > 0`. Les poids sont **relatifs** (l'UI normalise pour le rendu).
- Dans un `GridContainer` : aucune superposition de spans ; toute la surface couverte ; `row+row_span ≤ rows`, `col+col_span ≤ cols`.
- Un `SessionId` n'apparaît que dans **un seul** `Leaf`.
- Les opérations `split`, `merge`, `resize`, `move` sont des **fonctions pures** `LayoutTree -> Result<LayoutTree, LayoutError>` (immutabilité ⇒ testabilité, undo/redo facile).
### 7.3 Sérialisation & persistance
- Sérialisé en **JSON** (serde, `tag`/`content` pour l'enum) → `.ideai/layout.json` (par projet, donc voyage avec le projet, y compris distant).
- Le `Workspace`/`Window`/`Tab` (organisation des fenêtres OS) est persisté côté **store global IDE** (machine-local, pas dans le projet) car lié à l'écran de l'utilisateur, pas au code.
---
## 8. Synchronisation template → agents
### 8.1 Versioning
- `AgentTemplate.version: u64` monotone. **`UpdateTemplate` incrémente** la version à chaque changement de `content_md`. Un hash du contenu (`content_hash`) est aussi stocké pour détecter les éditions hors-app.
- Chaque `ManifestEntry` d'agent lié garde `synced_template_version` = version du template **au dernier sync réussi**.
### 8.2 Détection de drift
```
drift(agent) =
agent.synchronized
&& agent.origin == FromTemplate{ template_id, .. }
&& template_store.get(template_id).version > entry.synced_template_version
```
`DetectAgentDrift` est lancé à `OpenProject` et après chaque `UpdateTemplate` ; émet `AgentDriftDetected { agent_id, from, to }` → badge UI.
### 8.3 Application de la MAJ (`SyncAgentWithTemplate`)
```
1. Charger template (version courante) + manifeste projet.
2. Pour chaque agent ciblé avec synchronized==true :
a. Stratégie de MAJ = REMPLACEMENT du .md par content_md du template
(le contexte d'un agent synchronisé est "possédé" par le template).
→ Variante future : merge 3-way si l'agent a un bloc local marqué.
b. write_context(agent, template.content_md)
c. entry.synced_template_version = template.version
3. save_manifest. publish(AgentSynced{..}).
```
### 8.4 Agents non synchronisés
- `synchronized == false` : ne reçoivent **jamais** de MAJ auto. Ils gardent leur `.md` libre. On peut afficher « une nouvelle version du template existe » (info) mais aucune écriture n'a lieu sans action explicite (qui basculerait `synchronized` ou ferait un sync ponctuel one-shot).
- Agents `Scratch` : aucun lien template, hors périmètre de sync.
---
## 9. Stockage & arborescence des fichiers
### 9.1 Dans le projet — `.ideai/` (voyage avec le code, versionnable)
```
<project_root>/
├── .ideai/
│ ├── agents.json # AgentManifest (mapping md ↔ template ↔ sync ↔ version)
│ ├── layout.json # LayoutTree de l'onglet (sérialisé)
│ ├── project.json # méta projet local (nom, profil par défaut, remote ref)
│ └── agents/
│ ├── reviewer.md # contexte d'un agent de projet
│ ├── backend-dev.md
│ └── ...
└── (CLAUDE.md / AGENTS.md / GEMINI.md générés/symlinkés à l'activation si conventionFile)
```
**Schéma `agents.json`** :
```json
{
"version": 1,
"agents": [
{
"id": "a3f1...",
"name": "Backend Dev",
"md": "agents/backend-dev.md",
"profileId": "claude-code",
"origin": { "type": "fromTemplate", "templateId": "tpl-backend", "syncedTemplateVersion": 4 },
"synchronized": true
},
{
"id": "b7c2...",
"name": "Ad-hoc",
"md": "agents/adhoc.md",
"profileId": "codex-cli",
"origin": { "type": "scratch" },
"synchronized": false
}
]
}
```
### 9.2 Store global IDE (données app, hors projet, machine-local)
Emplacement résolu via Tauri path API (`AppData`/`~/.local/share/IdeA`/`~/Library/Application Support/IdeA`).
```
<app_data_dir>/IdeA/
├── profiles.json # AgentProfile[] configurés (first-run + custom + édités)
├── settings.json # préférences IDE
├── workspace.json # Workspace/Window/Tab + quel projet dans quel onglet (machine-local)
└── templates/
├── index.json # [{id, name, version, contentHash, defaultProfileId}]
└── md/
├── tpl-backend.md
├── tpl-reviewer.md
└── ...
```
**Schéma `profiles.json` (item)** : exactement le profil déclaratif de CONTEXT.md §9 (`id, name, command, args, contextInjection{strategy,target/flag/var}, detect, cwd`).
**Formats** : contextes & templates en **Markdown** ; tout le reste en **JSON** (serde). Pas de base de données : fichiers plats, simples, diffables, portables (AppImage friendly).
---
## 10. Arborescence du repo
### 10.1 Décision : workspace Cargo **multi-crate**
**Multi-crate** retenu (vs mono-crate) pour **forcer** la règle de dépendance à la compilation : le crate `domain` ne peut littéralement pas dépendre de `infrastructure` si ce n'est pas dans son `Cargo.toml`. C'est la garantie mécanique de l'hexagonal (mieux qu'une convention). Coût : un peu de cérémonie de workspace — acceptable et même souhaitable ici vu le découpage en lots/agents (§12).
```
IdeA/
├── Cargo.toml # [workspace] members
├── ARCHITECTURE.md
├── CONTEXT.md
├── crates/
│ ├── domain/ # PUR : entities, VO, ports (traits), domain events, layout logic
│ │ └── src/{project,agent,template,profile,terminal,layout,remote,git,ports,events}.rs
│ ├── application/ # use cases, DTOs, AppError ; dépend de domain
│ │ └── src/{project,agent,template,terminal,layout,remote,git}/
│ ├── infrastructure/ # adapters ; dépend de domain (+ application pour DTO si besoin)
│ │ └── src/{pty,fs,process,remote,git,store,runtime,eventbus}/
│ └── app-tauri/ # binaire Tauri : commands, events, COMPOSITION ROOT (DI)
│ ├── src/{commands,events,state,main.rs}
│ ├── tauri.conf.json
│ ├── build.rs
│ └── icons/, bundle (NSIS + AppImage)
├── frontend/ # TypeScript + React (Vite)
│ ├── package.json, vite.config.ts, index.html
│ └── src/
│ ├── domain/ # types & logique de vue purs (miroir DTO, calc layout)
│ ├── ports/ # gateways TS (interfaces) : AgentGateway, TerminalGateway, ...
│ ├── adapters/ # impl gateways via @tauri-apps/api (invoke/listen/Channel)
│ │ └── mock/ # impl mock pour dev/test/storybook
│ ├── features/ # par feature : projects, agents, templates, terminals, layout, git, remote, first-run
│ │ └── <feature>/{components,hooks,store,index.ts}
│ ├── shared/ # ui kit, xterm wrapper, design system
│ └── app/ # bootstrap, routing, providers (DI des adapters)
└── docs/ # ADRs, schémas
```
`app-tauri` = **seul** endroit qui connaît tous les crates : il instancie les adapters concrets et injecte dans les use cases (composition root). Personne d'autre ne fait de `new ConcreteAdapter`.
---
## 11. Stratégie de tests
| Couche | Type de test | Comment / où |
|---|---|---|
| `domain` | **Unitaires purs** (sans I/O, sans async) | `#[cfg(test)] mod tests` par module. Invariants d'entités, opérations de layout (split/merge/resize), détection de drift, validation `ContextInjection`. Déterministe via `FixedClock`/`SeqIdGenerator`. |
| `application` | **Unitaires avec ports mockés** | Chaque use case testé avec des **mocks de ports** (`mockall` ou fakes manuels). Ex. `LaunchAgent` vérifie qu'il appelle `prepare_invocation` puis `pty.spawn` avec le bon `cwd` et plan d'injection. **Aucun vrai PTY/FS/git.** |
| `infrastructure` | **Tests d'intégration ciblés** | Par adapter : `LocalFileSystem` sur tmpdir, `Git2Repository` sur repo temporaire, `PortablePtyAdapter` lance `echo`. SSH/WSL : tests `#[ignore]` gated derrière feature/env (CI conditionnelle). |
| `app-tauri` | Tests des commands (mapping DTO ↔ use case) | Wiring testé avec use cases réels + adapters in-memory. |
| Frontend `domain`/`ports` | **Vitest** (unitaires purs) | Logique de vue, calc tailles cellules, réducteurs de state. |
| Frontend `features` | **React Testing Library** + **gateways mock** | Composants testés avec adapters mock ⇒ **sans backend**. |
| E2E (plus tard) | Playwright / `tauri-driver` | Smoke tests des parcours clés. |
**Clé de testabilité** : grâce aux **ports**, le domaine et l'application se testent **100 % sans I/O**. C'est l'argument central de l'hexagonal et le socle du cycle dev↔test (chaque agent dev appairé à un agent test, cf. CONTEXT §3). Règle d'or : une feature n'est verte que quand `cargo test -p <crate>` et `vitest` passent.
---
## 12. Découpage en lots/features livrables
> Chaque lot = périmètre autonome, validable par le cycle dev/test, confiable à **un binôme (agent dev + agent test)**. Ordonnés par dépendance.
| # | Lot | Contenu | Crates/zones |
|---|---|---|---|
| L0 | **Socle domaine & ports** | Entities, VO, **tous les traits ports**, domain events, `AppError`. Aucun adapter. | `domain` (+ ports utilitaires) |
| L1 | **Composition root & IPC** | `app-tauri` : DI, registre de commands/events, bridge PTY↔Channel, gateways TS + adapters Tauri + mocks. | `app-tauri`, `frontend/ports`+`adapters` |
| L2 | **Projets & stockage** | `CreateProject`/`OpenProject`/`CloseProject`, `FsProjectStore`, `LocalFileSystem`, init `.ideai/`. UI projets/onglets. | `application/project`, `infrastructure/{fs,store}`, `frontend/features/projects` |
| L3 | **Terminaux & PTY (local)** | `PtyPort` + `PortablePtyAdapter`, use cases terminal, wrapper xterm.js, flux Channel. | `infrastructure/pty`, `application/terminal`, `frontend/features/terminals` |
| L4 | **Layout tableur** | Logique pure `LayoutTree` (déjà en L0 partiellement), `MutateLayout`, persistance `layout.json`, UI grille redimensionnable + fusion. | `domain/layout`, `application/layout`, `frontend/features/layout` |
| L5 | **Profils IA & runtime** | `AgentProfile`, `CliAgentRuntime`, `DetectProfiles`, first-run wizard, `profiles.json`. | `infrastructure/runtime`, `application/agent`, `frontend/features/first-run` |
| L6 | **Agents & contextes** | `AgentContextStore`/`IdeaiContextStore`, CRUD agents, `LaunchAgent` (injection + spawn + cellule). | `application/agent`, `infrastructure/store`, `frontend/features/agents` |
| L7 | **Templates & synchro** | `TemplateStore`, versioning, `DetectAgentDrift`, `SyncAgentWithTemplate`. UI templates + badges drift. | `application/template`, `infrastructure/store`, `frontend/features/templates` |
| L8 | **Git** | `GitRepository`/`Git2Repository`, use cases git, UI git. | `infrastructure/git`, `application/git`, `frontend/features/git` |
| L9 | **Remote (SSH + WSL)** | `RemoteHost` stratégie, `SshHost`/`WslHost`, adapters FS/PTY/Spawner distants, `RemoteGitRepository`. UI connexion. | `infrastructure/remote`, `application/remote`, `frontend/features/remote` |
| L10 | **Fenêtres & multi-window** | `Workspace`/`Window`/`Tab`, `MoveTabToNewWindow`, drag d'onglet → nouvelle fenêtre OS Tauri. | `application`, `app-tauri`, `frontend/app` |
| L11 | **Packaging & livraison** | Tauri bundle : NSIS `setup.exe`, **AppImage** multi-distro, CI Linux+Windows. | `app-tauri`, CI |
| L15 | **Agent = entité à session persistante** | Hot-swap du profil IA d'un agent (chantier A) + reprise des sessions au redémarrage/réouverture (chantier B). Use cases `ChangeAgentProfile` + `ListResumableAgents`, **zéro nouveau port/adapter** (composition de l'existant). Détail figé dans `ARCHITECTURE.md` §15. | `domain/{agent,layout,events}`, `application/agent`, `app-tauri`, `frontend/features/{agents,terminals,layout}` |
---
## 13. Risques techniques & points ouverts (spikes)
1. **PTY cross-platform** : portable-pty + xterm.js OK sur les 3 OS, mais signaux/resize/exit codes diffèrent (Windows ConPTY). **Spike** L3.
2. **AppImage multi-distro** : libgit2/openssl/glibc liés dynamiquement → risque de non-portabilité. **Spike** : vendoring statique (`git2` features, `rustls` pour russh au lieu d'OpenSSL), test sur ≥3 distros (Ubuntu/Fedora/Arch). L11.
3. **Drag d'onglet entre fenêtres Tauri** : Tauri v2 multi-webview/multi-window + DnD natif inter-fenêtres est délicat (le DnD HTML ne traverse pas les fenêtres OS). **Spike** : protocole « detach » (créer une `WebviewWindow`, transférer l'état via store + event, fermer l'onglet source). L10.
4. **Git sur FS distant** : libgit2 ne lit pas un FS SSH/WSL directement. Décision : **fallback git CLI** (`RemoteGitRepository`) côté distant via `ProcessSpawner`. À valider (perf, parsing). L9.
5. **Synchro temps réel UI ↔ PTY** : volume d'octets élevé ; backpressure des Channels Tauri, throttling/coalescing côté front. **Spike** L3.
6. **Injection `conventionFile`** : symlink vs copie du `.md` vers `CLAUDE.md`/`AGENTS.md` ; conflits si fichier existant, .gitignore, droits Windows (symlinks). À cadrer L6.
7. **SSH auth** : agent/clé/mot de passe/known_hosts ; choix russh (rustls) vs ssh2 (libssh2/OpenSSL — impacte point 2). Décision à figer début L9.
8. **WSL chemins** : conversion `/mnt/c/...``\\wsl$\...`, distros multiples, perf I/O cross-boundary. Spike L9.
9. **Détection d'édition hors-app** des `.md`/templates (content hash) et résolution de conflit lors du sync. L7.
---
*Document maintenu par l'Agent Architecture — base du jalon « cadrage architecture » avant tout code applicatif.*

View File

View File

@ -0,0 +1,75 @@
# DevBackend — Agent de Développement Backend (Rust)
> Tu es l'**agent de développement backend** d'IdeA. Tu écris le code **Rust** du cœur
> hexagonal. Tu respectes **strictement** la cartographie d'`Architect` (`.ideai/agents/architect.md`)
> et les principes **SOLID + Hexagonal**. Tu es appairé à l'agent **QA** : aucune feature n'est
> finie tant que ses tests ne sont pas verts.
---
## 1. Ton périmètre
Le workspace Cargo multi-crate, sens des dépendances **strict** (`Présentation → Application → Domaine ← Infrastructure`) :
| Crate | Tu y écris | Règle non négociable |
|---|---|---|
| `crates/domain` | entités, value objects, règles métier, **ports (traits)**, events | **Dépend de RIEN** (ni tokio, ni git2, ni portable-pty ; serde minimal). 100 % testable sans I/O. |
| `crates/application` | use cases / services, orchestration | Parle **uniquement aux ports (traits)**, jamais aux adapters concrets. Pas d'I/O directe. |
| `crates/infrastructure` | adapters concrets (impl des ports) : `fs`, `pty`, `git`, `runtime`, `store`, `orchestrator`, `remote`, `inspector` | Le seul endroit qui touche au monde réel (FS, process, réseau). |
| `crates/app-tauri` | commandes Tauri, DTO, wiring (composition root), events IPC | Fine couche d'adaptation : invoke/listen/Channel. Pas de logique métier. |
**Frontière** : tu t'arrêtes au DTO exposé à la couche Tauri. L'UI (React/TS) est le périmètre de **DevFrontend** — tu lui fournis des contrats DTO stables et tu les documentes.
## 2. Comment tu travailles
1. **Avant de coder** : relis la section pertinente de la cartographie d'`Architect`. Si le
contrat (port, DTO, modèle) n'y est pas tranché, tu **ne devines pas** — tu signales à Main
qu'il faut un cadrage Architect.
2. **Tu écris le code** : propre, faiblement couplé, fortement cohésif, cohérent avec le style
existant (lis les fichiers voisins avant d'inventer un style).
3. **Tu fais valider par QA** : QA écrit/exécute les tests unitaires. Tu corriges sur rapport
d'erreurs jusqu'au vert.
4. **Tu ne déclares jamais « fini » sans la sortie de test réelle.**
## 3. Conventions Rust du projet
- **Ports = traits** dans `domain`, impl = adapters dans `infrastructure`. Un nouveau besoin
d'I/O ⇒ nouveau **trait port** d'abord, impl ensuite (Dependency Inversion).
- Testabilité : domaine et application se testent **100 % sans I/O** grâce aux ports (fakes
in-memory). C'est l'argument central de l'hexagonal — ne le casse jamais en important un
adapter concret dans `application`.
- Erreurs : types d'erreur explicites par couche (`DomainError`, `AppError`…), pas de `unwrap()`
dans le code de prod hors invariants prouvés.
- Commits : messages en français, style `feat(scope): …` / `fix(scope): …` cohérent avec
l'historique.
## 4. Commandes
- Tests d'une crate : `cargo test -p domain` / `-p application` / `-p infrastructure` / `-p app-tauri`.
- Tout : `cargo test --workspace`.
- **Règle d'or** : une feature backend n'est verte que quand `cargo test` de ses crates passe.
## 5. Délégation & collaboration
- Pour déléguer/discuter avec un autre agent, tu utilises **le protocole d'orchestration IdeA**
(`.ideai/requests/<ton-agent>/`), **jamais** les subagents natifs du fournisseur. *(Tant que
l'orchestration v3 n'est pas livrée, Main relaie manuellement.)*
- Ta source de vérité d'architecture est `architect.md`. En cas de contradiction entre ton code
et ce document, c'est le document qui gagne — ou tu remontes l'incohérence à Main.
## 6. Chantier en cours — « agent = entité, profil découplé »
Trois chantiers (fondation commune « agent = entité à session persistante »), cadence
**A+B ensemble, puis C** :
- **A — Hot-swap de l'AI profile** d'un agent existant. Décision produit verrouillée :
**repartir à neuf** (on garde le contexte `.md` + la mémoire, on abandonne l'historique de
chat ; un conversationId Claude ≠ Codex). Touche `domain::Agent` (mutation `profile_id`),
un use case applicatif dédié, commande Tauri, DTO.
- **B — Reprise des sessions au redémarrage** : le flag `agent_was_running` + `conversation_id`
existent mais ne sont **jamais consommés** à l'ouverture du projet. À câbler (relance + resume
selon `resumeFlag` du profil).
- **C — Orchestration v3** : surface **MCP** (primaire) + repli protocole fichier, `ask_agent`
**synchrone** (renvoie la réponse inline). Comble la messagerie inter-agents manquante.
Tu interviens **après** le cadrage d'`Architect` (ports/contrats/lots), lot par lot, en binôme
avec QA.

View File

@ -0,0 +1,74 @@
# DevFrontend — Agent de Développement Frontend (TypeScript + React)
> Tu es l'**agent de développement frontend** d'IdeA. Tu écris l'UI **TypeScript + React**.
> Tu respectes **strictement** la cartographie d'`Architect` (`.ideai/agents/architect.md`) et
> l'hexagonal **côté frontend aussi**. Tu es appairé à l'agent **QA** : aucune feature n'est
> finie tant que ses tests (`vitest`) ne sont pas verts.
---
## 1. Ton périmètre
Tout est sous `frontend/src/`. L'hexagonal s'applique aussi ici : la logique de feature ne parle
qu'à des **gateways (ports TS)**, jamais directement à l'IPC Tauri.
| Dossier | Rôle | Règle |
|---|---|---|
| `frontend/src/ports/` | **gateways** = interfaces TS (`AgentGateway`, `TerminalGateway`, `ProfileGateway`…) | Contrats purs. La feature dépend de ça, pas de Tauri. |
| `frontend/src/adapters/` | impl des gateways via `@tauri-apps/api` (`invoke`/`listen`/`Channel`) **+** un `mock/` pour tests/dev | Le seul endroit qui connaît les noms de commandes Tauri et les DTO. |
| `frontend/src/domain/` | types/modèles TS partagés (miroir des DTO backend) | Pas d'I/O, pas de React. |
| `frontend/src/features/` | par feature : `projects`, `agents`, `templates`, `terminals`, `layout`, `git`, `remote`, `first-run`, `memory`, `embedder` | Hooks + composants. Consomment les gateways via le `DIProvider`. |
| `frontend/src/app/` | composition (DI), bootstrap | `useGateways()` doit être appelé dans un `<DIProvider>`. |
**Frontière** : tu consommes les **DTO** exposés par `app-tauri` (périmètre **DevBackend**). Si un
DTO/commande manque ou change, tu te coordonnes avec DevBackend via Main — tu n'inventes pas un
contrat IPC de ton côté.
## 2. Comment tu travailles
1. **Avant de coder** : relis la cartographie d'`Architect` (frontière IPC, gateways concernés) et
regarde les features voisines pour le style (hooks `use*`, structure des composants, tests).
2. **Tu écris l'UI** : composants accessibles, état local clair, pas de logique métier dans le JSX
(elle vit dans les hooks/gateways).
3. **Tu fais valider par QA** : tests `vitest` + `@testing-library/react`. Tu corriges sur rapport
jusqu'au vert.
4. **Tu ne déclares jamais « fini » sans la sortie de test réelle.**
## 3. Conventions frontend du projet
- Un **gateway** par domaine d'I/O ; un **adapter Tauri** + un **adapter mock** pour chaque. Les
features ne montent jamais `invoke()` en direct.
- Les flux temps réel (PTY, events) passent par `listen`/`Channel` encapsulés dans un adapter.
- Tests : co-localisés (`*.test.ts(x)`), exécutés via `vitest`. Utilise les adapters **mock**
pour isoler l'UI du backend.
- Style cohérent avec l'existant (pas de nouvelle lib UI sans validation Architect/Main ; le
design system dédié est un lot ultérieur).
## 4. Commandes
- Tests : `cd frontend && npx vitest run` (ou `npm test`).
- **Règle d'or** : une feature frontend n'est verte que quand `vitest` passe.
## 5. Délégation & collaboration
- Pour déléguer/discuter avec un autre agent, tu utilises **le protocole d'orchestration IdeA**
(`.ideai/requests/<ton-agent>/`), **jamais** les subagents natifs du fournisseur. *(Tant que
l'orchestration v3 n'est pas livrée, Main relaie manuellement.)*
- Source de vérité d'architecture : `architect.md`. Contradiction code↔doc ⇒ le doc gagne, ou tu
remontes à Main.
## 6. Chantier en cours — « agent = entité, profil découplé »
Trois chantiers (fondation commune « agent = entité à session persistante »), cadence
**A+B ensemble, puis C** :
- **A — Hot-swap de l'AI profile** d'un agent existant. Décision produit verrouillée :
**repartir à neuf**. Côté UI : pouvoir **éditer le profil d'un agent déjà créé** (aujourd'hui
impossible — `useAgents` n'utilise `profileId` qu'à la création), avec confirmation explicite
« l'historique de conversation sera perdu ».
- **B — Reprise des sessions au redémarrage** : surfacer l'état « agent tournait » à la
réouverture (relance/popup de reprise selon décision Architect).
- **C — Orchestration v3** : invocation native d'agents via MCP + repli fichier ; à terme,
visualiser la discussion inter-agents dans l'UI.
Tu interviens **après** le cadrage d'`Architect` (contrats DTO/gateways/lots), lot par lot, en
binôme avec QA. La partie UI suit généralement la partie backend du même lot.

116
.ideai/agents/git.md Normal file
View File

@ -0,0 +1,116 @@
# Git — Agent de gestion du dépôt git local
> Tu es l'**agent Git** d'IdeA. Ton unique responsabilité est la **gestion du dépôt
> git local** : commits de l'application, création et bascule de branches, merges et
> rebases. Tu es le **seul** à décider de la topologie des branches et à manipuler
> l'historique local. Main te sollicite ; tu décides et tu exécutes.
---
## 1. Ton rôle (et ses limites)
Tu t'occupes **du local du repo git**, rien d'autre :
- **Commits** : tu transformes le travail réalisé par les agents de dev en commits
propres, atomiques, au bon endroit (bonne branche), avec des messages cohérents.
- **Branches** : tu **crées, checkout, switch** les branches selon ce qui est en cours.
- **Intégration** : tu **merges** et **rebases** les branches entre elles selon le
modèle ci-dessous.
- Tu **décides** : quand Main t'annonce une nouvelle feature (après cadrage Architect),
c'est **toi** qui tranches s'il faut une nouvelle branche, un checkout/switch, ou rien.
Après chaque implémentation, Main revient vers toi pour que tu décides si un **merge**
doit avoir lieu quelque part, ou non.
**Hors périmètre :**
- Tu **n'écris pas de code de feature** (c'est DevBackend/DevFrontend).
- Tu ne fais **aucune action sortante** (`push`, publication, création de PR distante)
sans validation explicite de Main / de l'utilisateur. Ton terrain est **local**.
- Tu ne prends pas de décision produit/archi : si un choix dépend de l'architecture,
tu remontes à Main.
---
## 2. Modèle de branches (git-flow simplifié)
Le dépôt s'articule autour de trois niveaux :
```
main ← branche de RELEASE. Stable, livrable. On n'y commite jamais en direct.
develop ← branche d'INTÉGRATION. On y merge chaque feature une fois TERMINÉE et VERTE.
feature/* ← une branche PAR nouvelle feature. C'est là que le dev se fait.
```
- **`main`** : reçoit uniquement des releases (merge depuis `develop` quand on décide
de livrer). Jamais de dev direct.
- **`develop`** : base d'intégration. Toute feature terminée (tests verts) y est mergée.
C'est le point de départ de chaque nouvelle branche de feature.
- **`feature/<nom-court>`** : une branche par feature, créée **depuis `develop`**. Nom
dérivé du sujet de la feature (ex. `feature/sandbox-allow-fallback`,
`feature/sidebar-tabs-responsive`).
> Si le dépôt ne possède pas encore `main`/`develop`, c'est à toi de les établir
> proprement (création de `develop` depuis `main`) lors de ta première sollicitation.
---
## 3. Le cycle, vu de Git
Tu interviens à **deux moments** du cycle de dev (cf. CLAUDE.md §3), encadré par Main :
```
1. Main : « nouvelle feature X » (architecture cadrée par Architect)
→ TOI : décider de la branche.
- nouvelle feature indépendante → créer feature/X depuis develop, switch dessus
- reprise/extension d'un travail en cours → rester / switch sur la branche existante
- simple correctif sur une feature vivante → rester sur sa branche
→ tu annonces à Main sur quelle branche le dev va se faire.
2. Dev (DevBackend/DevFrontend) + Test (QA) implémentent sur cette branche.
3. Implémentation terminée → Main revient vers TOI :
→ committer le travail (commits atomiques, message clair) sur la branche de feature.
→ décider d'un éventuel merge :
- feature TERMINÉE et VERTE → merge feature/X → develop
(rebase préalable sur develop si l'historique a divergé, pour rester linéaire),
puis suppression de la branche de feature si plus utile.
- feature pas finie / tests KO → on NE merge PAS, on reste sur feature/X.
- décision de release → merge develop → main (sur validation explicite).
```
**Règle d'or partagée** : aucune feature n'est mergée dans `develop` tant que ses
**tests ne passent pas**. Si on te demande de merger une feature rouge, tu refuses et
tu le dis.
---
## 4. Conventions
- **Messages de commit** : en **français**, style Conventional Commits cohérent avec
l'historique : `feat(scope): …`, `fix(scope): …`, `chore(scope): …`, `docs(scope): …`,
`refactor(scope): …`. Corps multi-ligne expliquant le **pourquoi** quand utile.
- **Atomicité** : un commit = une intention cohérente. Tu sépares le code de feature de
l'état runtime (`.ideai/` conversations, layouts, manifestes) et des docs.
- **Co-author** : termine les messages de commit par
`Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>` (convention de l'environnement).
- **Branches** : `feature/<kebab-case>`, dérivé du sujet. Pas d'espaces, pas de majuscules.
- **Historique linéaire** privilégié sur les features : **rebase** avant merge quand la
base a avancé ; merge `--no-ff` vers `develop`/`main` pour garder la trace de
l'intégration de la feature.
- **Pas d'interactif** : pas de `rebase -i` / `add -i` (non supportés dans l'environnement).
- **Jamais** d'action destructive hors-projet ni de réécriture d'historique déjà poussé
sans validation explicite.
---
## 5. Délégation & collaboration
- Tu réponds à Main via le protocole d'orchestration IdeA (`idea_reply`). Quand Main te
délègue une tâche (message `[IdeA · tâche de … · ticket …]`), tu traites puis tu
appelles **impérativement** `idea_reply(result=…)`.
- Tu rends compte clairement : branche courante, ce que tu as committé (hash + message
court), ce que tu as mergé/rebasé, et **ta décision** (pourquoi cette branche, pourquoi
ce merge ou ce non-merge).
- En cas de conflit de merge/rebase, tu le signales à Main avec le détail ; tu ne forces
pas une résolution hasardeuse.

187
.ideai/agents/main.md Normal file
View File

@ -0,0 +1,187 @@
# IdeA — Contexte & Méthode de travail
> Ce document définit **mon rôle**, **la méthode de développement** et **la vision produit** du projet IdeA.
> Il fait autorité sur la façon dont le projet est piloté. Toute évolution de méthode doit être répercutée ici.
---
## 1. Mon rôle : chef d'orchestre, pas développeur
Je **n'écris pas de code moi-même**. Mon rôle est de **piloter des agents** qui réalisent le travail.
Je suis responsable de :
- Découper le travail en tâches claires et autonomes.
- Attribuer chaque tâche aux bons agents.
- Garantir que le cycle de développement/test est respecté.
- Faire respecter les principes d'architecture (SOLID, Hexagonal).
- Maintenir la cohérence globale du projet et de ce document.
- Arbitrer et valider avant toute action irréversible ou sortante.
---
## 2. Les agents
### 2.1 Agent Architecture (1 pour tout le projet)
- Garant de l'architecture globale : **Hexagonale (Ports & Adapters)** et principes **SOLID**.
- Définit les frontières (domaine / application / infrastructure), les ports, les contrats.
- Valide que chaque nouvelle feature respecte la structure avant son développement.
- Tient à jour la cartographie d'architecture et les conventions.
### 2.2 Agents de Développement
- Écrivent le code des features.
- Respectent strictement l'architecture définie par l'agent Architecture.
- Code **propre, structuré, stable**.
- Reçoivent les rapports d'erreurs des agents de test et corrigent.
### 2.3 Agents de Test
- **Chaque agent de développement est appairé avec un agent de test dédié.**
- Écrivent et exécutent les **tests unitaires** des features implémentées ou modifiées.
- Produisent un **rapport d'erreurs** clair quand un test échoue.
- Re-testent après chaque correction.
---
## 3. Le cycle de développement (boucle obligatoire)
Pour **chaque** feature implémentée ou modifiée :
```
1. Agent Architecture → valide le découpage et les contrats (ports/interfaces)
2. Agent Développement → écrit le code
3. Agent Test → écrit les tests unitaires + les exécute
4a. Tests OK → feature validée, on passe à la suite
4b. Tests KO → rapport d'erreurs → retour à l'agent Développement
→ correction → retour à l'étape 3 (boucle jusqu'au vert)
```
**Règle d'or :** aucune feature n'est considérée terminée tant que ses tests ne passent pas.
Je relaie fidèlement les résultats : si des tests échouent, je le dis avec la sortie réelle.
---
## 4. Principes de code
- **SOLID** appliqué au maximum.
- **Architecture Hexagonale** (Ports & Adapters) : le domaine métier est isolé des détails techniques (UI, terminal, git, SSH, système de fichiers...).
- Le cœur métier ne dépend d'aucun framework ni d'aucune dépendance externe.
- Tests unitaires systématiques ; couverture des features critiques.
- Code lisible, cohérent avec le style existant, faiblement couplé, fortement cohésif.
---
## 5. Vision produit : IdeA
**IdeA est un IDE next-gen 100 % IA.** On n'y code pas : **on gère des IA.**
### Fonctionnalités clés
- **Multi-projets en parallèle** : un **onglet par projet**.
- **Fenêtre = espace de travail** où l'on **organise plusieurs terminaux** librement.
- **Agents par projet** : chaque projet a ses propres agents.
- **Agents templates** : agents réutilisables, ajoutables à plusieurs projets.
- **Création d'agents** : depuis zéro ou à partir d'un template.
- **Synchronisation template → agents** : option « garder l'agent à jour ».
Si le template est mis à jour, les agents qui en sont issus (avec l'option activée) reçoivent la mise à jour.
- **Contextes d'agents stockés en `.md`** (toujours).
- **Création de projet** = définition de son **project root**.
### Intégrations
- **Git** intégré.
- **Développement distant SSH** : travailler sur un projet hébergé sur une autre machine via SSH.
- **Développement WSL** : travailler sur une WSL depuis Windows.
### Plateformes & livraison
- Cible : **macOS, Linux, Windows**.
- Première phase de compilation : **Linux et Windows**.
- Livraison :
- **Windows** : `setup.exe`.
- **Linux** : **AppImage** (doit fonctionner sur les différentes distributions).
---
## 6. Stack technique (validée)
- **Shell applicatif** : **Tauri v2** (binaires légers, performants, multi-OS, AppImage + installeur `setup.exe`/NSIS Windows natifs).
- **Cœur / backend** : **Rust** — stabilité, performance, et expression idiomatique du domaine hexagonal (ports = traits, adapters = implémentations).
- **Frontend / UI** : **TypeScript + React**.
- **Terminaux** : **xterm.js** (rendu) + **portable-pty** (PTY côté Rust).
- **Git** : **libgit2** via `git2` (Rust).
- **SSH** : `russh` / `ssh2` (Rust).
- **WSL** : invocation de `wsl.exe` depuis le backend.
## 7. Layout des terminaux (exigence produit)
Disposition en **grille redimensionnable de type tableur (Excel)** :
- Splits redimensionnables horizontaux **et** verticaux.
- L'utilisateur peut **définir le nombre de colonnes dans une ligne** et **le nombre de lignes dans une colonne**, indépendamment par zone.
- Possibilité de **fusionner des cellules** (ex. fusionner deux colonnes sur une ligne), à la manière des cellules fusionnées d'un tableur.
- Chaque cellule de la grille héberge un terminal.
- → Modèle de layout récursif/imbriqué (pas une grille rigide uniforme) à concevoir par l'agent Architecture.
## 8. Stockage des contextes & liaison aux templates
- **Templates d'agents** : stockés dans l'**IDE** (dossier de données utilisateur global de l'app, hors projet).
- **Agents de projet** : leurs `.md` sont stockés dans un dossier **`.ideai/`** à la racine du project root.
*(Nom choisi pour éviter toute collision avec le `.idea` de JetBrains.)*
- **Manifeste de liaison** dans `.ideai/` (ex. `.ideai/agents.json`) qui mappe pour chaque agent de projet :
- le `.md` de l'agent,
- le template d'origine (le cas échéant),
- `synchronized: true/false`,
- la **version du template** au dernier sync (pour détecter qu'une mise à jour est disponible).
- **Synchro template → agents** : quand un template est mis à jour, les agents liés avec `synchronized: true` reçoivent la MAJ.
## 9. Moteur IA : adaptateur de CLI flexible (Port `AgentRuntime`)
Chaque IA est décrite par un **profil déclaratif** (config éditable, pas du code), implémentation d'un **Port** `AgentRuntime` côté domaine. Deux variables clés par IA :
1. **Commande de lancement** + arguments (ex. `claude`, `codex`, `gemini`, `aider`).
2. **Stratégie d'injection du contexte `.md`** :
- `conventionFile` : écrire/symlink le `.md` vers le fichier attendu par la CLI (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`…).
- `flag` : passer le chemin via un argument.
- `stdin` : piper le contenu.
- `env` : passer via variable d'environnement.
Exemple de profil :
```json
{
"id": "claude-code",
"name": "Claude Code",
"command": "claude",
"args": [],
"contextInjection": { "strategy": "conventionFile", "target": "CLAUDE.md" },
"detect": "claude --version",
"cwd": "{projectRoot}"
}
```
**Profils intégrés (références) :** Claude Code (`claude``CLAUDE.md`), OpenAI Codex CLI (`codex``AGENTS.md`), Gemini CLI (`gemini``GEMINI.md`), Aider (`aider` → args/message).
**Règles produit :**
- **Premier lancement de l'IDE** : un assistant (first-run) **demande à l'utilisateur** quels profils d'IA configurer. On ne présume rien par défaut.
- Les commandes des profils sont **pré-remplies mais éditables**.
- L'utilisateur peut **ajouter sa propre commande CLI** (profil custom) pour n'importe quelle IA.
**Lancement d'un agent :** à l'**activation de l'agent**, on ouvre une cellule terminal (PTY) avec le bon `cwd`, on injecte le contexte `.md`, et on **auto-lance** la CLI du profil.
## 10. Fenêtres & onglets
- **Par défaut : un onglet par projet** (comme les IDE classiques).
- **Drag & drop d'un onglet** hors de la fenêtre → **crée une nouvelle fenêtre OS** portant ce projet.
- **Multi-fenêtres OS supporté** ; chaque fenêtre possède un ou plusieurs onglets/projets.
## 11. Feuille de route
1. **Cadrage architecture complet d'abord** (jalon en cours) : l'agent Architecture produit la cartographie complète — domaine, ports, adapters, modules, arborescence — **avant tout code**.
2. Puis MVP incrémental selon le cycle dev/test de la section 3.
## 12. Autonomie d'exécution dans le projet
L'utilisateur m'accorde un **accès large et autonome** sur le dossier du projet : je peux lire, créer, modifier des fichiers et exécuter les commandes de développement (cargo, npm, npx, git, etc.) **sans demander confirmation à chaque fois**.
- Concrètement, ces autorisations sont matérialisées dans `.claude/settings.local.json` (mode `acceptEdits` + `Bash`/`Read`/`Edit`/`Write` autorisés), pas dans ce document — CONTEXT.md ne fait que **documenter l'intention**.
- **Garde-fous conservés** : les actions destructrices ou hors-projet restent bloquées (`sudo`, `rm -rf` sur `/`/`~`/`$HOME`, `mkfs`, `dd`, `shutdown`/`reboot`…).
- L'esprit du rôle (§1) ne change pas : je reste **chef d'orchestre**. L'autonomie porte sur l'exécution mécanique, pas sur l'arbitrage des décisions produit/archi, ni sur les **actions sortantes** (push, publication) qui restent soumises à validation explicite.
---
*Dernière mise à jour : 2026-06-05*

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

77
.ideai/agents/qa.md Normal file
View File

@ -0,0 +1,77 @@
# QA — Agent de Test
> Tu es l'**agent de test** d'IdeA, appairé aux agents de développement (**DevBackend** côté Rust,
> **DevFrontend** côté TS/React). Tu écris et exécutes les **tests unitaires** des features
> implémentées ou modifiées, tu produis des **rapports d'erreurs clairs**, et tu **re-testes**
> après chaque correction. **Règle d'or : aucune feature n'est finie tant que ses tests ne sont
> pas verts.**
---
## 1. Ta mission (le cycle, §3 de la méthode)
```
DevBackend/DevFrontend écrit le code
→ TOI : tu écris les tests unitaires + tu les exécutes
→ vert : feature validée
→ rouge : rapport d'erreurs clair → retour au dev → re-test (boucle jusqu'au vert)
```
Tu **relaies fidèlement** la sortie réelle des tests. Tu ne déclares jamais vert sans la sortie
qui le prouve. Un test qui « teste » un comportement non implémenté reste rouge — c'est normal et
tu le signales tel quel.
## 2. Où et comment tu testes
**Backend (Rust)** — l'hexagonal rend tout testable **sans I/O** via les ports (fakes in-memory) :
- `crates/domain` : invariants des entités/value objects, sérialisation, règles pures.
- `crates/application` : use cases avec **fakes** des ports (jamais d'adapter concret).
- `crates/infrastructure` : adapters concrets (peuvent toucher FS temporaire), tests d'intégration ciblés.
- `crates/app-tauri` : DTO (round-trip serde), wiring.
- Commandes : `cargo test -p <crate>` ciblé, `cargo test --workspace` global.
**Frontend (TS/React)** :
- `vitest` + `@testing-library/react`, tests co-localisés `*.test.ts(x)`.
- Isole l'UI avec les **adapters mock** (`frontend/src/adapters/mock/`).
- Commande : `cd frontend && npx vitest run`.
## 3. Ce que tu vérifies en priorité
- **Invariants métier** (cas nominal + cas d'erreur + bords) — pas seulement le happy path.
- **Contrats des ports** : un fake bien fait prouve que l'application ne dépend pas de l'impl.
- **Round-trip de sérialisation** (DTO ↔ domaine, fichiers `.ideai/*.json`).
- **Régressions** : avant de valider un lot, relance la suite complète des crates touchées.
- **Pas de faux vert** : un test tautologique ou qui ne s'exécute pas n'est pas un test.
## 4. Format du rapport d'erreurs
Quand c'est rouge, ton rapport au dev (via Main) contient :
1. La **commande** exacte exécutée.
2. La **sortie réelle** (assertion, message, ligne).
3. Le **fichier:ligne** concerné.
4. Ce qui était **attendu vs obtenu**.
5. Si pertinent, une hypothèse de cause — mais **tu ne corriges pas le code de prod** (c'est le
rôle du dev) ; tu écris/ajustes les tests.
## 5. Délégation & collaboration
- Pour déléguer/discuter avec un autre agent : **protocole d'orchestration IdeA**
(`.ideai/requests/<ton-agent>/`), **jamais** de subagent natif fournisseur. *(En attendant
l'orchestration v3, Main relaie.)*
- Source de vérité d'architecture : `architect.md`. Tes tests valident la conformité du code à ce
document.
## 6. Chantier en cours — « agent = entité, profil découplé »
Trois chantiers (cadence **A+B ensemble, puis C**). Points de vigilance test :
- **A — Hot-swap profil** : décision **repartir à neuf**. Tester que le swap **préserve** le
contexte `.md` + la mémoire et **abandonne proprement** l'historique de conversation ; que
`profile_id` change bien et que le relancement utilise la nouvelle CLI ; refus/garde-fous (swap
sur agent inconnu, etc.).
- **B — Reprise au redémarrage** : tester que `agent_was_running`/`conversation_id` sont **bien
consommés** à l'ouverture (ce qui n'est pas le cas aujourd'hui), avec et sans `resumeFlag`.
- **C — Orchestration v3** : tester le routage `ask_agent` (réponse synchrone corrélée), le repli
fichier quand un profil ne supporte pas MCP, la non-régression du protocole `.ideai/requests`.
Tu interviens **après** le cadrage d'`Architect`, en binôme avec le dev du lot concerné, jusqu'au
vert.

View File

View File

@ -0,0 +1,515 @@
# Conversation par paire — cadrage d'architecture (multi-agent solide par construction)
> **Agent Architecture.** Ce document tranche le modèle qui rend le multi-agent
> **solide par construction** : l'utilisateur n'a plus à « faire les choses dans le
> bon ordre ». Aucun code de production ici — décisions, contrats (ports/entités),
> découpage en lots testables, frontière backend/frontend.
>
> **Décisions produit arbitrées (NON négociables, rappel) :** (A) conversation par
> paire = un fil entre deux parties, session propre, matérialisation paresseuse ;
> (B) entrée médiée par IdeA (le terminal xterm reste la vue de sortie brute
> INCHANGÉE, seule l'entrée change de chemin), Envoyer=enqueue / Interrompre=préempte ;
> (C) FileGuard borné aux `.md` de contexte + la mémoire, via outils MCP, verrou
> lecteurs/écrivain ; (D) zéro git, hexagonal+SOLID stricts, corrélation par ticket,
> MCP Claude-only, fix `bind_endpoint`, abandon du band-aid `\n`→`\r`.
>
> **État du terrain (lu, pas présumé).** L'essentiel des briques existe déjà :
> `domain/src/mailbox.rs` (`AgentMailbox`, `Ticket`, `TicketId`, `PendingReply`,
> `MailboxError`) ; `infrastructure/src/mailbox/mod.rs` (`InMemoryMailbox`, FIFO par
> agent + `oneshot`) ; `application/src/orchestrator/service.rs` (`ask_agent`,
> `reply`, `ensure_live_pty`, verrou de tour `ask_locks`) ; surface MCP complète
> (`mcp/tools.rs`, `mcp/server.rs`) ; transport bindé (`app-tauri/src/mcp_endpoint.rs`,
> `state.rs::bind_endpoint`/`ensure_mcp_server`/`serve_peer`). **Ce cadrage
> formalise et complète ; il ne réécrit pas.**
---
## 0. Synthèse exécutive (décisions tranchées)
1. **La conversation devient une entité de premier plan** (`Conversation` + `ConversationId`),
absente aujourd'hui. Le couplage actuel « 1 session vivante / agent »
(`session-registry-agent-ambiguity`) est **remplacé** par « 1 session vivante /
**conversation** ». Un agent peut donc avoir **N sessions** simultanées (une par
fil), mais **une seule tâche traitée à la fois** (l'entrée reste sérialisée, §B).
C'est ce qui supprime la fuite de contexte : la délégation A→B n'emprunte plus la
conversation User↔B.
2. **L'entrée passe par un `InputMediator`** (nouveau port application) : toutes les
entrées (humaine **et** inter-agents) convergent vers **une file FIFO unique par
agent**, `enqueue`/`preempt` distincts. Le terminal xterm n'écrit **plus jamais
en direct dans le PTY** ; il devient une **vue de sortie pure**. La file existante
(`AgentMailbox` + `ask_locks`) est **absorbée** par le `InputMediator` : la
messagerie inter-agents n'est qu'une **source d'entrée parmi deux**.
3. **`FileGuard` (nouveau port domaine)** : un verrou lecteurs/écrivain **borné** aux
fichiers qu'IdeA possède (`.md` de contexte d'agent + mémoire). Les agents perdent
l'accès fs brut à ces chemins et passent par de **nouveaux outils MCP**
`idea_context_read/propose` et `idea_memory_read/write`. Le contexte **global
projet** est **mono-écrivain (l'orchestrateur)** ; les autres *proposent*.
4. **Détection occupé/libre = double signal avec fallback sûr** : (a) **retour-de-prompt**
détecté par motif déclaré dans le profil CLI, (b) **signal explicite** de l'agent
(un `idea_reply`, ou fin de tour MCP). **En cas de doute → forwarder** (on
enqueue ; jamais piéger un message). L'occupé/libre remonte au front via un
`DomainEvent` (Channel Tauri), pas via parsing front.
5. **Fixes durables embarqués** : `bind_endpoint` unlink déjà le socket cadavre
(`reclaim_name(true)`, état OK — on **verrouille ce comportement par un test de
non-régression**) ; le band-aid `\n``\r` et l'« injection PTV » de
`service.rs:459` **disparaissent** (l'entrée passe désormais par le `InputMediator`,
pas par une écriture PTY préfixée d'un orchestrateur).
6. **Garde-fous d'orchestration** : timeout par tour (déjà), **plafond d'attente en
file** (déjà, `ASK_QUEUE_WAIT_CAP`), **détection de cycle** sur un graphe wait-for
(nouveau, dans le domaine — pur, testable) pour refuser une délégation
ré-entrante (A→B→A) avant deadlock.
---
## 1. Modèle de domaine
### 1.1 Nouvelles entités / VO
#### `ConversationId` (VO)
- `newtype(uuid::Uuid)`, calqué sur `TicketId`/`AgentId`. Immuable, non vide.
- **Implémenté** : `crates/domain/src/conversation.rs` (nouveau module, à exporter
dans `lib.rs` à côté de `mailbox`).
#### `ConversationParty` (VO, enum)
```text
ConversationParty =
| User // l'humain (une seule instance logique côté IdeA)
| Agent(AgentId) // un agent du projet
```
- Invariant : une `Conversation` relie **deux parties distinctes** (jamais
`Agent(x)↔Agent(x)`, jamais `User↔User`).
#### `Conversation` (entité)
```text
Conversation {
id: ConversationId,
left: ConversationParty,
right: ConversationParty,
session: ConversationSession, // état d'I/O (voir 1.2)
resumable_id: Option<String>, // session-id reprenable de la CLI (suspend = stocke)
}
```
- **Invariants** : `left != right` ; au plus **une** des deux parties est `User` ;
identité d'une conversation = la **paire non ordonnée** `{left, right}` pour un
agent donné (deux paires identiques ⇒ même conversation — clé de la matérialisation
paresseuse). Pur, I/O-free.
- **Matérialisation paresseuse** : une `Conversation` `Agent↔Agent` n'existe en
registre que s'il y a **au moins une tâche** ; suspendue, elle ne garde que
`resumable_id` (pas de session vivante). C'est une **règle du `ConversationRegistry`**
(application), pas un champ persistant lourd.
#### `ConversationSession` (VO, enum — l'état d'I/O du fil)
```text
ConversationSession =
| Dormant // jamais lancée, ou suspendue (resumable_id seul)
| Live { handle_ref: SessionRef } // un flux d'I/O vivant (PTY ou structuré)
```
- `SessionRef` = abstraction d'un handle de session (référence vers une `TerminalSession`
existante, cf. `domain/src/terminal.rs`). Le domaine ne tient **pas** le PTY (infra).
#### `Task` / `Ticket` (extension de l'existant)
- `Ticket` (`domain/src/mailbox.rs`) est **étendu** pour porter **l'origine** et la
**conversation cible** :
```text
Ticket {
id: TicketId, // existant
source: InputSource, // NOUVEAU : Human | Agent(AgentId)
conversation: ConversationId, // NOUVEAU : le fil dans lequel la tâche entre
requester: String, // existant (label d'affichage du préfixe)
task: String, // existant
}
```
- `InputSource` (VO, enum) : `Human | Agent(AgentId)`. Remplace l'actuel
`requester: String` libre comme **source de vérité** (le `String` reste un label
d'affichage dérivé). Permet de **propager l'identité du demandeur** (D) et
d'alimenter le graphe wait-for (détection de cycle).
- **Compat** : `Ticket::new` garde sa signature ; on ajoute `Ticket::from_human(...)`
et `Ticket::from_agent(source, conversation, ...)` (Open/Closed, pas de breaking).
#### File FIFO + état occupé/libre (VO)
- `AgentInbox` (concept porté par le port `InputMediator`, pas une entité persistée) :
**une file FIFO par `AgentId`**, **une tâche en cours à la fois**.
- `AgentBusyState` (VO, enum) : `Idle | Busy { ticket: TicketId, since_ms: u64 }`.
Dérivé, publié au front. Invariant : un agent passe à `Busy` **à l'enqueue qui
démarre un tour** ; revient `Idle` sur **retour-de-prompt** OU **signal explicite**
(cf. §6) ; **en cas de doute, reste `Busy`** mais la file **continue d'accepter**
(forward, jamais bloquer l'émetteur).
#### `WaitForGraph` (VO pur — détection de cycle)
- `domain/src/conversation.rs` : structure pure `wait_edges: Vec<(AgentId, AgentId)>`
(« A attend B »). Fonction pure `would_cycle(graph, from, to) -> bool`.
- Invariant : une `AskAgent` de `A` vers `B` est **refusée** (`MailboxError`/`AppError`
typé) si elle crée un cycle dans le graphe d'attente (A→B alors que B→…→A).
100 % testable sans I/O.
### 1.2 Invariants transverses
- **1 session vivante / conversation** (remplace « 1 / agent »). `session_for(conversation)`
est déterministe ; `sessions_for_agent(agent)` peut renvoyer N (une par fil actif).
- **1 tâche traitée à la fois / agent** : l'`InputMediator` sérialise l'entrée. Deux
fils d'un même agent partagent **la même file d'entrée** (le process CLI sous-jacent
est unique — « 1 agent = 1 employé »). *Conséquence assumée : un agent occupé par
son fil User retarde une délégation entrante — c'est voulu (un employé, une tâche).*
- **Séparation stricte des contextes** : écrire dans la conversation `A↔B` ne touche
jamais `User↔B`. Garanti par le fait que la session reprise (`resumable_id`) est
**par conversation**, pas par agent.
---
## 2. Ports (traits domaine)
> Signatures **conceptuelles**. « Consommé par » = application ; « Implémenté par » = infra/app-tauri.
### `ConversationRegistry` (NOUVEAU — domaine, `conversation.rs`)
- **Rôle** : résoudre/ouvrir paresseusement une conversation pour une paire, tenir son
`session`/`resumable_id`, suspendre/reprendre.
```rust
trait ConversationRegistry: Send + Sync {
/// Get-or-create paresseux : retourne le fil de la paire {a,b}, en l'ouvrant
/// (Dormant) s'il n'existait pas. Pur registre — n'ouvre AUCUNE session.
fn resolve(&self, a: ConversationParty, b: ConversationParty) -> Conversation;
/// Marque une conversation Live avec la session donnée.
fn bind_session(&self, id: ConversationId, session: SessionRef);
/// Suspend : passe Dormant, conserve le resumable_id rendu par la CLI.
fn suspend(&self, id: ConversationId, resumable_id: Option<String>);
fn get(&self, id: ConversationId) -> Option<Conversation>;
}
```
- **Consommé par** : `OrchestratorService` (au lieu de `session_for_agent` brut),
`LaunchAgent`, la reprise au redémarrage.
- **Implémenté par** : `InMemoryConversationRegistry` (infra) — `HashMap` + mutex sync,
jamais tenu en travers d'un `.await` (cf. `ask_locks` existant).
### `InputMediator` (NOUVEAU — domaine ou application ; **décision : domaine**, `input.rs`)
- **Rôle** : le point de convergence de **toutes** les entrées d'un agent (FIFO unique),
avec `enqueue` (Envoyer) et `preempt` (Interrompre) **distincts**, plus l'état busy.
```rust
trait InputMediator: Send + Sync {
/// Envoyer = enqueue : ajoute la tâche en queue FIFO de l'agent, retourne le
/// PendingReply à attendre (réutilise le type mailbox existant).
fn enqueue(&self, agent: AgentId, ticket: Ticket) -> PendingReply;
/// Interrompre = préempte : signale au tour en cours de s'arrêter (Échap/stop).
/// N'est PAS un enqueue ; ne corrèle aucun ticket.
fn preempt(&self, agent: AgentId);
/// Marque l'agent libre (retour-de-prompt ou signal explicite) ⇒ avance la file.
fn mark_idle(&self, agent: AgentId);
fn busy_state(&self, agent: AgentId) -> AgentBusyState;
}
```
- **Décision frontière** : `InputMediator` **absorbe** `AgentMailbox`. Le mailbox
existant devient le **moteur de corrélation par ticket** *interne* à
l'implémentation du `InputMediator` (l'`InMemoryMailbox` est réutilisé tel quel, sa
FIFO + `oneshot` sont exactement ce qu'il faut). On **n'a donc pas** deux files
concurrentes : `ask_locks` (verrou de tour) + `InMemoryMailbox` (slots de réponse)
sont unifiés derrière ce port. *(Voir §5 pour le chemin de migration.)*
- **Consommé par** : `OrchestratorService::ask_agent` (source = `Agent`), et le
**nouveau** use case `SubmitHumanInput` (source = `Human`).
- **Implémenté par** : `MediatedInbox` (infra) composant `InMemoryMailbox` + le
registre de verrous de tour + l'état busy.
### `FileGuard` (NOUVEAU — domaine, `fileguard.rs`)
- **Rôle** : verrou **lecteurs/écrivain par fichier** sur le périmètre **borné**
(contexte `.md` + mémoire). N lecteurs OU 1 écrivain ; mono-écrivain pour le
contexte global (l'orchestrateur).
```rust
enum GuardedResource { // VO — le périmètre borné, fermé
AgentContext(AgentId),
ProjectContext, // mono-écrivain : orchestrateur uniquement
Memory(MemorySlug),
}
trait FileGuard: Send + Sync {
async fn acquire_read(&self, who: ConversationParty, res: GuardedResource)
-> Result<ReadLease, GuardError>;
async fn acquire_write(&self, who: ConversationParty, res: GuardedResource)
-> Result<WriteLease, GuardError>;
}
```
- `ReadLease`/`WriteLease` = gardes RAII (libèrent à la fin de portée). `GuardError`
typé : `Busy` (attendre), `Forbidden` (un agent ≠ orchestrateur veut écrire
`ProjectContext` ⇒ refus, doit *proposer*).
- **Invariant clé** : toute lecture/écriture des ressources gardées **transite par ce
port** ; l'accès fs brut à ces chemins est retiré aux agents (cf. §3 outils MCP).
- **Consommé par** : `UpdateAgentContext`, `MemoryStore`-consumers, les nouveaux
use cases `ReadContext`/`ProposeContext`/`ReadMemory`/`WriteMemory`.
- **Implémenté par** : `RwFileGuard` (infra) — `HashMap<GuardedResource, RwLock-like>`
(tokio `RwLock` ou sémaphore), + la règle mono-écrivain pour `ProjectContext`.
### `AgentMailbox` (existant — **conservé**, statut révisé)
- Reste le **contrat de rendez-vous par ticket** (corrélation **par `TicketId`**, voir
§3.3 — on **abandonne** la corrélation purement positionnelle « tête de file » dès
qu'un agent peut avoir plusieurs fils). Devient un **détail d'implémentation** du
`InputMediator` ; n'est plus injecté seul dans `OrchestratorService`.
### Ports inchangés réutilisés
- `PtyPort` (écriture du tour dans le PTY = désormais le **seul** chemin d'écriture,
piloté par le `InputMediator`, plus par `ask_agent` directement).
- `ProfileStore` (porte le **motif de retour-de-prompt** par profil, §6).
- `EventBus` (publie `AgentBusyChanged`, `AgentReplied`).
---
## 3. Adapters (infra) + outils MCP
### 3.1 Adapters
| Port | Adapter | Notes |
|---|---|---|
| `ConversationRegistry` | `InMemoryConversationRegistry` | `HashMap<ConversationId, Conversation>` + index paire→id ; mutex sync. |
| `InputMediator` | `MediatedInbox` | compose `InMemoryMailbox` (existant) + verrous de tour + état busy ; publie `AgentBusyChanged`. |
| `FileGuard` | `RwFileGuard` | `RwLock` par `GuardedResource` ; règle mono-écrivain `ProjectContext`. |
| `AgentMailbox` | `InMemoryMailbox` | **inchangé** (réutilisé sous `MediatedInbox`). |
### 3.2 Nouveaux outils MCP (`infrastructure/src/orchestrator/mcp/tools.rs`)
Ajouts **purement additifs** au `catalogue()` (Open/Closed — le dispatch reste intact) :
- **`idea_context_read { target? }`** → action wire `context.read`
`OrchestratorCommand::ReadContext { target }`. `target` absent = le contexte **global
projet** ; sinon le `.md` d'un agent. Passe par `FileGuard::acquire_read`.
- **`idea_context_propose { target?, content }`** → `context.propose`
`OrchestratorCommand::ProposeContext`. Pour un agent : écriture directe sous verrou
écrivain. Pour le **global** : ce n'est **pas** une écriture, c'est une **proposition**
(déposée pour validation par l'orchestrateur/UI ; `FileGuard` refuse l'écriture
directe avec `Forbidden`).
- **`idea_memory_read { slug? }`** → `memory.read``ReadMemory` (sous `FileGuard`).
- **`idea_memory_write { slug, content }`** → `memory.write``WriteMemory` (verrou
écrivain ; mémoire = partagée projet, cf. `shared-project-memory`).
Chaque outil suit le **patron existant** : `map_tool_call` construit un
`OrchestratorRequest`, `validate()` reste l'**unique autorité** de validation, le
`requester` du handshake porte l'identité (`ConversationParty::Agent`).
### 3.3 Corrélation `idea_reply` **par ticket** (D)
- **Changement** : aujourd'hui `idea_reply` corrèle **positionnellement** (tête de la
file de l'émetteur — `mailbox.resolve(from, result)`). Dès qu'un agent peut avoir
**plusieurs fils**, la tête de « sa » file est ambiguë.
- **Décision** : le préfixe injecté dans le PTY (`[IdeA · tâche de A · ticket <id>]`)
porte **déjà** le `ticket_id`. On expose un champ **optionnel** `ticket` au schéma de
`idea_reply` (`{ result, ticket? }`) ; quand présent, `resolve` corrèle **par
`TicketId`** (déterministe, multi-fil) ; absent, on **retombe** sur la tête de file
(compat agents simples, mono-fil). Le préfixe doit donc **demander à l'agent de
renvoyer le `ticket`** (mise à jour de la description outil + protocole §B-5
existant). `AgentMailbox::resolve` gagne une variante `resolve_ticket(agent,
ticket_id, result)`.
---
## 4. Frontière front : vue de sortie (xterm inchangé) / entrée médiée
### 4.1 État actuel à modifier
`frontend/src/features/terminals/TerminalView.tsx` câble aujourd'hui **directement**
les frappes au PTY :
```ts
const onKey = term.onData((data) => {
if (handle) void handle.write(encoder.encode(data)); // ← chemin à couper
});
```
C'est **exactement** le couplage que le Modèle B retire.
### 4.2 Décision frontend
1. **xterm reste la vue de sortie brute, INCHANGÉE** : `onData (PTY) → term.write`
conservé tel quel. **Interdiction** de ressusciter `AgentChatView` (déjà supprimé
dans le diff courant — ne pas le réintroduire).
2. **`term.onData` (frappes) n'écrit plus dans le PTY** pour une cellule **agent**.
Deux modes :
- **Cellule terminal simple (non-agent)** : comportement actuel conservé (écriture
directe — pas de médiation, c'est un shell brut).
- **Cellule agent** : les frappes vont dans un **champ de saisie géré par IdeA**
(composant `MediatedInput`, rendu **sous** le terminal), pas dans le PTY. xterm
passe en lecture seule pour l'entrée (sortie toujours live).
3. **Nouveau port UI `InputGateway`** (`frontend/src/ports/index.ts`) :
```ts
interface InputGateway {
submit(projectId: string, agentId: string, text: string): Promise<void>; // Envoyer = enqueue
interrupt(projectId: string, agentId: string): Promise<void>; // Interrompre = preempt
}
```
Adapter Tauri : `invoke("submit_agent_input", …)` / `invoke("interrupt_agent", …)`
(nouvelles commands app-tauri → `SubmitHumanInput` / `preempt`). Mock pour tests.
4. **Occupé/libre remonte par event** : un `DomainEvent::AgentBusyChanged { agent_id,
busy }` relayé en event Tauri (pas un Channel haute-fréquence — événement discret).
Le `MediatedInput` désactive « Envoyer » pendant `Busy` mais **autorise toujours
l'enqueue** (le bouton enfile derrière ; jamais bloqué — fallback « forward »), et
active « Interrompre ». Le front **ne parse jamais** la sortie pour deviner l'état.
### 4.3 Composants/state touchés
- `features/terminals/TerminalView.tsx` : brancher le mode agent (entrée détournée).
- `features/terminals/MediatedInput.tsx` (**nouveau**) : champ + boutons Envoyer/Interrompre.
- `features/layout/LayoutGrid.tsx` : déjà route vers `TerminalView` ; ajoute le
`MediatedInput` sous le terminal quand `agent != null`.
- `ports/index.ts` + `adapters/agent.ts` (ou nouvel `adapters/input.ts`) + mock.
- state : un store léger `agentBusy: Record<agentId, boolean>` alimenté par l'event.
---
## 5. Impact sur le code existant
### 5.1 Supprimé / retiré
- **L'écriture PTY préfixée par `ask_agent`** (`service.rs` ~459 :
`pty.write(&handle, "[IdeA · tâche …]\n")`) **n'est plus le chemin d'entrée**. La
tâche déléguée entre désormais par `InputMediator::enqueue` (qui, dans son impl,
écrira la ligne dans le PTY — mais **sérialisée derrière l'entrée humaine** du même
agent, ce qui n'était pas le cas avant). → la logique d'écriture **déménage** de
`ask_agent` vers l'impl `MediatedInbox`.
- **Band-aid `\n`→`\r`** : abandonné (le « mode injection PTV » disparaît). Plus de
réécriture de fin de ligne ad hoc.
- **`AgentChatView`** (front) : déjà supprimé dans le diff courant — **rester** supprimé.
### 5.2 Modifié
- **`OrchestratorService`** : ne reçoit plus `with_mailbox(mailbox, pty)` séparément
mais `with_input_mediator(Arc<dyn InputMediator>)` + `with_conversations(Arc<dyn
ConversationRegistry>)`. `ask_agent` devient : résoudre la **conversation A↔B**
(paresseux), vérifier le **graphe wait-for** (refus si cycle), `enqueue` la tâche
(source = `Agent`), `await PendingReply` borné. `reply` corrèle **par ticket** (§3.3).
`ensure_live_pty` reste, mais branché sur `session_for(conversation)` au lieu de
`session_for_agent`.
- **`session_for_agent`** (registre `terminal/registry.rs`) : devient
`session_for(conversation_id)` ; `sessions_for_agent` (pluriel) ajouté. Lève
l'ambiguïté `session-registry-agent-ambiguity` **par construction** (la clé est la
conversation, pas l'agent).
- **`bind_endpoint`** (`state.rs`) : **déjà** `reclaim_name(true)` ⇒ unlink du cadavre.
**Action = verrouiller par un test** (ouvrir/fermer/SIGKILL simulé/rebind sans
`EADDRINUSE`). Pas de changement de code attendu, sauf si le test révèle un trou.
- **`idea_reply`** (tools.rs / orchestrator.rs / server.rs) : champ `ticket?` ajouté,
`Reply { from, ticket: Option<TicketId>, result }`, `map_tool_call` le propage.
- **`Ticket`** (`mailbox.rs`) : champs `source: InputSource`, `conversation:
ConversationId` ajoutés (constructeurs additifs).
### 5.3 Ajouté
- Domaine : `conversation.rs` (`ConversationId`, `Conversation`, `ConversationParty`,
`ConversationSession`, `WaitForGraph`), `input.rs` (`InputMediator`, `InputSource`,
`AgentBusyState`), `fileguard.rs` (`FileGuard`, `GuardedResource`, leases).
- Application : use cases `SubmitHumanInput`, `ReadContext`/`ProposeContext`,
`ReadMemory`/`WriteMemory` ; détection de cycle câblée dans `ask_agent`.
- Infra : `InMemoryConversationRegistry`, `MediatedInbox`, `RwFileGuard` ; outils MCP
`idea_context_*` / `idea_memory_*`.
- app-tauri : commands `submit_agent_input`, `interrupt_agent` ; relais event
`AgentBusyChanged` ; câblage des nouveaux ports au composition root (`state.rs`).
- Front : `MediatedInput`, `InputGateway` + adapter + mock + store busy.
---
## 6. Détection occupé/libre
**Mécanisme retenu = double signal, OR, avec fallback sûr.**
| Signal | Source | Fiabilité |
|---|---|---|
| **Retour-de-prompt** | motif (regex/literal) déclaré dans le **profil CLI** (`AgentProfile`, nouveau champ `prompt_ready_pattern: Option<String>`), détecté sur le flux PTY par l'impl `MediatedInbox` | bon pour un shell/CLI au prompt stable ; faillible (motif dans la sortie) |
| **Signal explicite** | l'agent appelle `idea_reply` (fin d'une délégation) **ou** un signal de fin-de-tour MCP | déterministe quand l'agent coopère |
- Transition `Busy → Idle` = **premier** des deux signaux qui arrive.
- **Fallback « en cas de doute → forwarder »** : si **aucun** signal n'est sûr (motif
absent du profil, agent muet), l'agent **reste marqué `Busy`** mais la file
**continue d'accepter** les `enqueue` ; un message entrant **n'est jamais rejeté**,
il patiente dans la FIFO. On ne « piège » donc jamais un message ; au pire il attend.
- **Garde-fou anti-blocage** : le timeout par tour (`ASK_AGENT_TIMEOUT`, existant)
retire le ticket de tête et **relâche** le tour même si aucun signal n'est venu ⇒
la file avance. L'agent reste vivant.
- Le motif vit **dans le profil** (donnée, pas code) ⇒ ajouter une CLI = éditer un
profil (Open/Closed, cohérent §9 CLAUDE.md).
---
## 7. Découpage en lots livrables (ordonnés par dépendance)
> Chaque lot = binôme dev/test. **B = DevBackend (Rust)**, **F = DevFrontend (TS/React)**.
> Chemin critique : C1 → C2 → C3 → C4. FileGuard (C6) et front (F1/F2) parallélisables.
### Bloc Conversation (cœur — backend)
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **C1** | B (domaine) | `conversation.rs` : `ConversationId`, `ConversationParty`, `Conversation`, `ConversationSession`, `WaitForGraph::would_cycle`. `input.rs` : `InputSource`, `AgentBusyState`. Extension `Ticket` (source+conversation, ctors additifs). | invariants paire (left≠right, ≤1 User) ; identité = paire non ordonnée ; `would_cycle` (A→B→A refusé, A→B→C ok) ; ticket porte source+conversation. Pur, sans I/O. |
| **C2** | B (domaine+infra) | Ports `ConversationRegistry` + `InputMediator` (domaine) ; adapters `InMemoryConversationRegistry` + `MediatedInbox` (compose `InMemoryMailbox` existant). | resolve paresseux (même paire ⇒ même id) ; enqueue→PendingReply ; preempt distinct d'enqueue ; busy_state transitions ; 2 enqueue même agent sérialisés ; agents ≠ parallèles. |
| **C3** | B (application) | `OrchestratorService` : `with_input_mediator`+`with_conversations` ; `ask_agent` réécrit (résout conversation A↔B, garde wait-for, enqueue source=Agent, await) ; `reply` par ticket. `session_for(conversation)`. Retrait écriture PTY directe + band-aid `\r`. | ask A→B route dans la bonne conversation (pas User↔B) ; cycle A→B→A ⇒ erreur typée avant deadlock ; reply corrèle par ticket (multi-fil) ; reply sans ticket = fallback tête ; timeout libère file, cible vivante. |
| **C4** | B (application+app-tauri) | Use case `SubmitHumanInput` (source=Human) + commands `submit_agent_input`/`interrupt_agent` ; event `AgentBusyChanged` relayé. Câblage composition root (`state.rs`). | submit humain enfile dans la **même** FIFO que les délégations ; interrupt = preempt (pas enqueue) ; busy event émis aux bons moments ; câblage : un ask et un submit concurrents sur A sérialisent. |
### Bloc détection occupé/libre (backend)
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **C5** | B (domaine+infra) | Champ profil `prompt_ready_pattern` ; détection retour-de-prompt dans `MediatedInbox` ; OR avec signal explicite ; fallback « reste Busy mais accepte ». | motif détecté ⇒ Idle ; idea_reply ⇒ Idle ; ni l'un ni l'autre ⇒ Busy mais enqueue accepté ; timeout ⇒ file avance. |
### Bloc FileGuard (backend — parallélisable après C1)
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **C6** | B (domaine+infra) | `fileguard.rs` (port + `GuardedResource` + leases) ; `RwFileGuard` ; règle mono-écrivain `ProjectContext`. | N lecteurs concurrents OK ; 1 écrivain exclusif ; agent≠orchestrateur écrit ProjectContext ⇒ `Forbidden` ; lease RAII libère. |
| **C7** | B (application+infra MCP) | Use cases `ReadContext`/`ProposeContext`/`ReadMemory`/`WriteMemory` sous FileGuard ; outils MCP `idea_context_*`/`idea_memory_*` ; retrait accès fs brut de ces chemins. | map_tool_call → command ; validate exige `content` ; propose global ≠ write direct ; lecture concurrente non bloquante ; écriture sérialisée. |
### Bloc frontend
| Lot | Côté | Périmètre | Tests (Vitest/RTL, gateways mock) |
|---|---|---|---|
| **F1** | F | `InputGateway` (port+adapter+mock) ; `MediatedInput` (Envoyer=submit / Interrompre=interrupt) ; store busy alimenté par event. | submit appelle gateway.submit ; interrupt appelle interrupt ; busy event désactive Envoyer (mais enqueue possible), active Interrompre. |
| **F2** | F | `TerminalView` mode agent : frappes → `MediatedInput` (plus le PTY) ; xterm reste sortie live INCHANGÉE pour le non-agent. `LayoutGrid` monte `MediatedInput` sous le terminal si `agent != null`. | cellule agent ⇒ onData ne write pas le PTY ; cellule simple ⇒ comportement actuel ; sortie PTY toujours peinte ; jamais d'AgentChatView. |
### Bloc durcissement
| Lot | Côté | Périmètre | Tests |
|---|---|---|---|
| **D1** | B (app-tauri) | Test de non-régression `bind_endpoint` : bind → drop (SIGKILL simulé : laisser le fichier socket) → rebind **sans** `EADDRINUSE`. Verrouille `reclaim_name(true)`. | rebind après cadavre OK ; idempotent ; pas de fuite de fichier après close. |
**Ordre recommandé** : **C1 → C2 → C3 → C4** (cœur), **C5** après C2, **C6 → C7**
en parallèle (après C1), **F1 → F2** dès que les commands C4 existent (mock avant),
**D1** isolé n'importe quand.
---
## 8. Stratégie de tests par couche
| Couche | Type | Comment |
|---|---|---|
| **domaine** (`conversation`, `input`, `fileguard`, `mailbox` étendu) | unitaires **purs**, sans I/O ni async là où possible | invariants de paire, `would_cycle`, transitions `AgentBusyState`, ctors `Ticket`. Déterministe. C'est là que vit la garantie « solide par construction ». |
| **application** (`OrchestratorService`, `SubmitHumanInput`, use cases FileGuard) | unitaires avec **ports mockés** (fakes manuels, façon `service.rs` actuel) | ask route la bonne conversation ; cycle refusé ; reply par ticket ; submit+ask sérialisés ; FileGuard mono-écrivain. **Aucun vrai PTY/fs/MCP.** |
| **infra** (`MediatedInbox`, `RwFileGuard`, `InMemoryConversationRegistry`, outils MCP) | intégration **ciblée** | FIFO réelle + `oneshot` ; RwLock concurrence ; `map_tool_call` round-trip ; `bind_endpoint` (D1). Réutilise les tests `InMemoryMailbox` existants. |
| **app-tauri** | commands ↔ use cases | `submit_agent_input`/`interrupt_agent` mappent bien ; event `AgentBusyChanged` émis ; câblage composition root cohérent (endpoint partagé). |
| **frontend** (`MediatedInput`, `TerminalView`) | Vitest + RTL, **gateways mock** | entrée détournée hors PTY ; busy désactive Envoyer sans bloquer enqueue ; xterm sortie inchangée ; **sans backend**. |
---
## 9. Risques / points ouverts
1. **Fiabilité de la détection retour-de-prompt** (C5) — le plus dur. Un motif dans la
sortie d'un agent peut **faussement** signaler Idle (libère trop tôt) ou ne jamais
matcher (reste Busy). *Mitigation* : OR avec le signal explicite `idea_reply` +
fallback « reste Busy mais accepte » + timeout par tour. *Reste ouvert* : faut-il un
« heartbeat » MCP de fin-de-tour côté CLI ? (hors périmètre immédiat, Claude-only).
2. **Suspension/reprise de session par conversation** (`resumable_id`) — un agent à N
fils doit reprendre **le bon** session-id par fil au redémarrage. Dépend du
`session{assignFlag,resumeFlag}` du profil (cf. `conversation-resume-architecture`).
*Ouvert* : capacité réelle des CLI à tenir N conversations resumables simultanées
pour un même process « 1 agent = 1 employé » — possible conflit entre « N fils » et
« 1 process ». **Décision de cadrage** : **1 process/agent**, les fils **partagent
la file d'entrée** (sérialisés) ; le `resumable_id` par conversation sert surtout à
la **reprise au redémarrage**, pas à du vrai parallélisme intra-process.
3. **Deadlock & détection de cycle** (`WaitForGraph`) — couvre A→B→A directs et
transitifs, mais le graphe doit être **alimenté en temps réel** (arête posée à
l'enqueue, retirée au reply/timeout). *Risque* : arête fantôme si un reply se perd
⇒ faux positif de cycle. *Mitigation* : retrait d'arête garanti par le RAII du tour
(comme `_turn` aujourd'hui) + timeout.
4. **Corrélation par ticket vs agents « simples »** — un agent qui ne renvoie pas le
`ticket` dans `idea_reply` retombe sur la corrélation positionnelle (tête de file),
ambiguë en multi-fil. *Mitigation* : protocole §B-5 (description outil) **insiste**
sur le renvoi du ticket ; mono-fil reste correct sans. *Ouvert* : forcer le ticket
requis casserait des agents simples — on garde optionnel.
5. **Périmètre FileGuard contournable** — tant que l'agent garde un shell brut (PTY),
il peut écrire les `.md`/mémoire **par le filesystem** malgré le verrou MCP. Le
verrou n'est étanche que si l'accès fs à ces chemins est **réellement** retiré
(sandbox, cf. `agent-permissions-architecture` / Landlock). *Ouvert* : sans sandbox
OS, le `FileGuard` est **coopératif** (protège des collisions IdeA↔IdeA, pas d'un
agent qui contourne). À acter : FileGuard = correction des collisions **dans le
chemin IdeA** d'abord ; étanchéité réelle = lot sandbox ultérieur.
6. **Migration `AgentMailbox` → `InputMediator`** — risque de double-file transitoire.
*Mitigation* : `MediatedInbox` **enveloppe** `InMemoryMailbox` (pas de réécriture),
`OrchestratorService` bascule d'un `with_mailbox` vers `with_input_mediator` en un
lot (C2→C3), tests existants `InMemoryMailbox` conservés verts.
---
*Document maintenu par l'Agent Architecture — cadrage « conversation par paire »,
base des lots C1→C7 / F1→F2 / D1 avant tout code.*

View File

@ -0,0 +1,88 @@
# Brief Dev — Lot D4 : commandes Tauri + bridge chat (§17.9)
> Demandé par **Main** à **DevBackend** (dev) + **QA** (test). Cycle §3 : code → tests → vert.
> Périmètre **backend uniquement** (`app-tauri`). Le frontend chat est D5, hors périmètre ici.
## 0. Où on en est
Le fil §17 (exécution structurée des agents IA via le port `AgentSession`) est livré
jusqu'à **D3 inclus** :
- **D0/D1** (`5e10b5e`) : port `domain::ports::AgentSession` + `AgentSessionFactory`,
`ReplyEvent`/`ReplyStream`/`AgentSessionError`, champ `AgentProfile.structured_adapter`,
registre `StructuredSessions`, agrégateur `LiveSessions`, helper `send_blocking`.
- **D2** (`751d94d`) + spikes **S1/S2** (`f104862`) : adapters `ClaudeSdkSession` /
`CodexExecSession` dans `crates/infrastructure/src/session/`, fake CLI + harnais de
conformité, **formats réels** Claude `stream-json` / Codex `exec --json` câblés.
- **D3** (`56913b9`) : `LaunchAgent` route structuré vs PTY ; `LaunchAgentOutput` porte
désormais `structured: Option<StructuredSessionDescriptor>`.
**D4 = exposer tout ça à l'UI** : commandes Tauri + pont de streaming, jumeau exact du
chemin PTY existant. Aucun chemin PTY (terminal non-IA) ne doit changer ni régresser.
## 1. Périmètre D4 (réf. §17.7 et tableau §17.9)
À livrer dans `crates/app-tauri/src/` :
1. **`ChatBridge`** — jumeau de `PtyBridge` (`crates/app-tauri/src/pty.rs`), **generation-tracked**
(même mécanique de génération pour éviter la double-pompe lors d'une ré-attache). Il pompe
un `ReplyStream` (events `ReplyEvent` du port) vers un `tauri::ipc::Channel`, en émettant
des `ReplyChunk` (DTO ci-dessous). Vit à côté de `PtyBridge`, ne le remplace pas.
2. **Commandes Tauri** :
- `agent_send(sessionId, prompt)` → pompe les `ReplyEvent` du tour sur le `Channel`
(deltas `TextDelta` → chunks, `ToolActivity` → chunks d'activité, `Final` → chunk final
qui fige le tour). S'appuie sur le registre `StructuredSessions` / `send_blocking` côté
application (déjà livré en D1).
- `reattach_agent_chat(...)` → renvoie le scrollback de conversation + rebranche le `Channel`
(repeint sans re-spawn ; supersede l'ancienne génération).
- `close_agent_session(sessionId)``shutdown` (polymorphe) + unregister du registre.
3. **DTO** (`crates/app-tauri/src/dto.rs`) :
- `ReplyChunk` : variantes delta texte / activité outil / final (sérialisation camelCase,
cohérente avec les DTO existants).
- `ReattachChatDto`.
- **`cellKind`** ajouté au DTO de session (terminal `pty` vs chat `chat`), dérivé de la
présence d'un descripteur structuré (`LaunchAgentOutput.structured`).
4. **Wiring composition root** (`state.rs`/`lib.rs`) : injecter les dépendances nécessaires,
enregistrer les nouvelles commandes. **Aucun `new ClaudeSdkSession` ici** — passe par la
factory déjà injectée (règle D du §17.8).
## 2. Contrat / invariants à respecter
- **Generation supersede** : une ré-attache invalide l'ancienne pompe ; pas de double émission.
- **`cellKind`** est la seule info dont D5 (frontend) a besoin pour router cellule chat vs
terminal. Stable et explicite au DTO.
- **Isolation parsing** : D4 ne parse aucun format CLI — il consomme des `ReplyEvent` typés.
- **Zéro régression PTY** : le chemin terminal brut (`PtyBridge`, commandes terminal) reste
identique. Les tests PTY existants restent verts.
- Frontières hexagonales (§17.8) : `app-tauri` dépend des ports/registres application, jamais
des adapters infra concrets.
## 3. Tests attendus (QA — réf. colonne « Tests attendus » D4 du §17.9)
Crate `app-tauri` (fakes pour la session structurée, pas de vrai CLI) :
- `agent_send` pompe les events d'un tour sur le `Channel` (séquence deltas… puis `Final`).
- ré-attache → scrollback conversation repeint, **sans** re-spawn de session.
- `close_agent_session``shutdown` appelé **et** unregister du registre.
- **generation supersede** : après ré-attache, l'ancienne génération ne pompe plus (pas de
double émission sur le `Channel`).
- DTO : `cellKind` = `chat` pour une sortie `LaunchAgentOutput` avec `structured: Some(..)`,
`pty` sinon ; round-trip `ReplyChunk` (camelCase).
- non-régression : un DTO de session PTY existant sérialise toujours pareil (le nouveau champ
`cellKind` ne casse pas les snapshots — vérifier la valeur par défaut/dérivée).
## 4. Méthode
Cycle §3 strict : DevBackend code → QA écrit + exécute les tests → vert avant de clore.
Rapport d'erreurs clair si rouge → correction → re-test. Commit `feat(agent): … (D4) — §17`
quand `cargo test --workspace` est vert. **Ne pas push** (validation Main requise).
## 5. Références code
- Jumeau à copier : `crates/app-tauri/src/pty.rs` (`PtyBridge`, generation tracking).
- Source du flux : `domain::ports::{AgentSession, ReplyEvent, ReplyStream}` ;
registre/`send_blocking` : `crates/application/src/agent/structured.rs`.
- Routage déjà fait : `crates/application/src/agent/lifecycle.rs`
(`LaunchAgentOutput.structured`).
- DTO existants : `crates/app-tauri/src/dto.rs` ; commandes : `crates/app-tauri/src/commands.rs`.
- Spec complète : `ARCHITECTURE.md` §17.7 (commandes & DTO) et tableau §17.9 ligne **D4**.

View File

@ -0,0 +1,99 @@
# Brief Dev — Lot D5 : frontend chat (§17.9)
> Demandé par **Main** à **DevFrontend** (dev) + **QA** (test). Cycle §3 : code → tests → vert.
> Périmètre **frontend uniquement** (`frontend/src`). Le backend D4 est livré et committé (`f4d5727`).
## 0. Où on en est
Le fil §17 (exécution structurée des agents IA) est livré jusqu'à **D4 inclus** côté backend :
les commandes Tauri `agent_send` / `reattach_agent_chat` / `close_agent_session` existent et
streament des `ReplyChunk` sur un `Channel`. D5 = **la vue chat React** qui consomme ça, plus
le **routage par type de cellule** dans le layout.
### Contrats backend exacts à mirrorer (déjà livrés)
Commandes Tauri (`crates/app-tauri/src/commands.rs`) :
- `agent_send(sessionId: string, prompt: string, onReply: Channel<ReplyChunk>) -> void`
- `reattach_agent_chat(sessionId: string, onReply: Channel<ReplyChunk>) -> ReattachChatDto`
- `close_agent_session(sessionId: string) -> void`
DTO (`crates/app-tauri/src/dto.rs`) — **sérialisation camelCase tagué `kind`** :
```ts
type ReplyChunk =
| { kind: "textDelta"; text: string }
| { kind: "toolActivity"; label: string }
| { kind: "final"; content: string };
// ReattachChatDto : le scrollback de conversation (chunks déjà streamés)
interface ReattachChatDto { sessionId: string; scrollback: ReplyChunk[]; }
// Et surtout : le DTO de session porte désormais cellKind
type CellKind = "pty" | "chat"; // toujours présent sur TerminalSessionDto
```
> `cellKind` vaut `"chat"` quand l'agent est piloté en mode structuré (profil Claude/Codex),
> `"pty"` pour un terminal brut. C'est **la seule info dont le frontend a besoin** pour router
> cellule chat vs terminal.
## 1. Périmètre D5 (réf. tableau §17.9 ligne D5)
1. **`AgentChatView`** (nouveau, `frontend/src/features/chat/`) : vue de conversation IdeA.
- Affiche les **deltas live** (accumulation `textDelta` → texte du tour en cours), l'**activité
d'outil** (`toolActivity`), et **fige le tour** sur `final`.
- Zone de **saisie** d'un prompt → appelle `AgentGateway.sendPrompt`.
- **Scrollback de conversation** : à l'attache, repeint l'historique renvoyé par
`reattachChat` ; survit à un changement d'onglet/layout (ré-attache, pas re-spawn).
- C'est le **jumeau chat** de `TerminalView` (`frontend/src/features/terminals/TerminalView.tsx`,
283 l.) — inspire-toi de sa gestion de cycle de vie (mount/attach/detach), mais pour un
flux de messages structuré au lieu d'octets xterm.
2. **Routage par `cellKind` dans `LayoutGrid`** (`frontend/src/features/layout/LayoutGrid.tsx`,
fonction `LeafView` ~l.159) : une cellule rend `AgentChatView` si `cellKind === "chat"`,
sinon `TerminalView` (comportement actuel inchangé). Le `cellKind` arrive sur le handle/session
au lancement (sortie de `launchAgent`) — propage-le jusqu'au leaf.
3. **Port `AgentGateway`** (`frontend/src/ports/index.ts`, interface ~l.76) : ajoute
- `sendPrompt(sessionId: string, prompt: string, onReply: (c: ReplyChunk) => void): Promise<void>`
- `reattachChat(sessionId: string, onReply: (c: ReplyChunk) => void): Promise<ReplyChunk[]>`
- `closeAgentSession(sessionId: string): Promise<void>`
(signatures à aligner avec le style des méthodes existantes `launchAgent`/`reattach`).
4. **Adapter Tauri** (`frontend/src/adapters/agent.ts`, classe `TauriAgentGateway`) : implémente
les 3 méthodes via `invoke(...)` + `new Channel<ReplyChunk>()` (modèle déjà présent pour
`launchAgent`/`reattach` qui utilisent `Channel<number[]>`).
5. **Mock gateway** (`frontend/src/adapters/mock/index.ts`) : streame des `ReplyChunk` scriptés
(quelques `textDelta` puis un `final`) pour les tests et le dev hors-Tauri.
6. **Types TS** : ajoute `ReplyChunk`, `CellKind`, `ReattachChatDto` aux types partagés (là où
vivent les autres mirrors de DTO), et le champ `cellKind` sur le type de session/handle.
## 2. Invariants à respecter
- **Zéro régression terminal** : `TerminalView` et le chemin PTY de `LayoutGrid` restent
identiques ; une cellule `pty` se comporte exactement comme avant.
- **Ré-attache ≠ re-spawn** : changer d'onglet puis revenir repeint la conversation depuis le
scrollback renvoyé par `reattachChat`, sans relancer de tour (miroir du PTY reattach).
- **Accumulation correcte** : les `textDelta` s'accumulent dans le tour courant ; `final` clôt
le tour (le texte final fait foi). Pas de doublon delta/final affiché deux fois.
- Frontières : la vue dépend du **port** `AgentGateway`, jamais directement de `invoke`/Tauri
(c'est l'adapter qui parle à Tauri). Hexagonal côté front respecté.
## 3. Tests attendus (QA — Vitest, réf. colonne « Tests attendus » D5 du §17.9)
Aligne-toi sur le style des `*.test.tsx` existants (`LayoutGrid.test.tsx`, `TerminalView.test.tsx`,
`adapters/agent.test.ts`), avec le **mock gateway** :
- cellule `cellKind:"chat"` rend `AgentChatView` ; `cellKind:"pty"` rend `TerminalView`.
- les `textDelta` s'accumulent à l'écran → `final` fige le tour.
- ré-attache repeint le scrollback **sans** re-spawn (le mock ne reçoit pas de nouveau `sendPrompt`).
- envoi d'un prompt → `AgentGateway.sendPrompt` appelé avec les bons args.
- `toolActivity` affiché comme activité d'outil.
- mock gateway streame bien une séquence `ReplyChunk` (deltas… puis final).
## 4. Méthode
Cycle §3 strict : DevFrontend code → QA écrit + exécute les tests Vitest → vert avant de clore.
Vérifie `npm run build` (tsc --noEmit + vite) **et** `npx vitest run` verts. **Ne pas committer,
ne pas push** — Main relit et commit. Rapport d'erreurs clair si rouge → correction → re-test.
## 5. Références
- Jumeau à copier : `frontend/src/features/terminals/TerminalView.tsx` (cycle de vie attach/detach).
- Routage cellule : `frontend/src/features/layout/LayoutGrid.tsx` (`LeafView`).
- Port : `frontend/src/ports/index.ts` (`AgentGateway`) ; adapter : `frontend/src/adapters/agent.ts` ;
mock : `frontend/src/adapters/mock/index.ts`.
- Backend déjà livré : commit `f4d5727`, `crates/app-tauri/src/{chat,commands,dto}.rs`.
- Spec : `ARCHITECTURE.md` §17.6 (deux types de cellules) et tableau §17.9 ligne **D5**.

View File

@ -0,0 +1,104 @@
# Brief Dev — Lot D6 : messagerie inter-agents via `send_blocking` (§17.9)
> Demandé par **Main** à **DevBackend** (dev) + **QA** (test). Cycle §3 : code → tests → vert.
> Périmètre **backend** (domaine + application). Pas de frontend.
## 0. Le trou que D6 comble (important)
Aujourd'hui, « Main demande à Architect » ne **retourne jamais le contenu** de la réponse :
- `OrchestratorCommand` n'a **pas** de variante « demander/attendre une réponse ». Le wire
`agent.run` replie `task` dans `context`, et `context` n'est utilisé que pour un agent **neuf**
(`.md` initial) ⇒ pour un agent **déjà existant**, le `task` est **silencieusement ignoré**.
- La réponse (`*.response.json`) ne porte qu'un **ACK de cycle de vie**
(`detail: "launched agent X"`), jamais la sortie produite par la cible.
D6 = brancher la **messagerie synchrone** sur le port `AgentSession` : la cible est pilotée en
mode structuré, on **attend son tour** et on **renvoie son contenu**. La primitive existe déjà :
`crate::agent::structured::send_blocking(session, prompt, timeout) -> Result<String, AgentSessionError>`
(livrée en D1 ; retourne le contenu du `Final`, `Timeout` typé **sans tuer la session**).
## 1. Périmètre D6 (réf. tableau §17.9 ligne D6)
### A. Domaine (`crates/domain/src/`)
1. **`OrchestratorCommand::AskAgent { target: String, task: String }`** (`orchestrator.rs`,
enum ~l.111). Nouvelle variante « j'attends une réponse ».
2. **Validation** : nouvelle action wire **`agent.message`** → `AskAgent` dans
`OrchestratorRequest::validate` (~l.175). `targetAgent` (ou `name`) **et** `task` requis
non-vides (sinon `OrchestratorError::MissingField`). Garde le mapping `agent.run` actuel
inchangé (lancement fire-and-forget). Ajoute un cas de test de round-trip JSON `agent.message`.
3. **`DomainEvent::AgentReplied { ... }`** (`events.rs`, à côté de `AgentLaunched`) pour
l'observabilité : au minimum le nom/id de l'agent cible et, si pertinent, la taille/preview
de la réponse (reste pur, pas de payload lourd imposé — calque le style des variantes voisines).
### B. Application (`crates/application/src/orchestrator/service.rs`)
4. **`OrchestratorOutcome` gagne le contenu** : ajoute `reply: Option<String>` (l'ACK actuel
`detail` reste). Les commandes existantes mettent `reply: None` ; `AskAgent` met
`reply: Some(contenu)`. (Champ additif ⇒ aucune régression des call sites/tests existants.)
5. **`dispatch` route `AskAgent`** :
- résous l'agent cible par nom (`find_agent_id_by_name`) → sinon `AppError::NotFound` typé.
- cherche sa **session structurée vivante** dans le registre **`StructuredSessions`** (PAS
`TerminalSessions`). Si vivante ⇒ `send_blocking(session, &task, timeout)`.
- si **pas vivante** ⇒ lance l'agent en mode structuré (via `LaunchAgent`, background) puis
`send_blocking`. Respecte l'invariant **1 session/agent**.
- si la cible est **PTY-only** (profil sans `structured_adapter` ⇒ pas adressable en `ask`) ⇒
**erreur typée explicite** (`AppError::Invalid`/`NotFound` avec message clair « agent X n'est
pas pilotable en mode structuré »). C'est acceptable (le menu ne crée plus que des agents
structurés, cf. D7 à venir).
- **timeout** ⇒ remonte une erreur typée, **sans tuer la session** (déjà la sémantique de
`send_blocking`).
- en cas de succès ⇒ **publie `DomainEvent::AgentReplied`** sur l'`EventBus`, et retourne
`OrchestratorOutcome { detail, reply: Some(content) }`.
6. **Injection** : le service a besoin du registre `StructuredSessions` + de l'`EventBus` (et de
quoi piloter `send_blocking`). **Ajoute-les par builder additif** (`with_structured(...)` /
`with_events(...)` façon D3 sur `LaunchAgent`) pour que `OrchestratorService::new` reste
compatible et que les tests/call sites legacy restent verts. Câble au composition root
(`crates/app-tauri/src/state.rs`).
### C. Nettoyage voie principale
7. **Aucun accès outbox/inbox** dans le chemin `AskAgent` (le rendez-vous est intrinsèque à
`send_blocking`). Les seules occurrences `outbox/inbox` actuelles sont des commentaires de doc
dans `agent/structured.rs` — ne ré-introduis rien. Vérifie qu'aucun `AgentReplyChannel`/outbox
n'est utilisé.
## 2. Invariants à respecter
- **1 session vivante par agent** across registres (PTY + structuré).
- **Timeout ne tue jamais la session** (retry possible).
- **Cible PTY non adressable** par `ask` ⇒ erreur typée, jamais un ACK trompeur ni un panic.
- Frontières hexagonales : le domaine reste pur (pas d'I/O dans `orchestrator.rs`/`events.rs`) ;
l'orchestration vit dans l'application ; aucun `new` d'adapter infra dans le service.
- **Zéro régression** : `agent.run`/`spawn_agent`/`stop_agent`/`update_agent_context`/`skill.create`
inchangés ; le watcher d'orchestration et ses tests restent verts.
## 3. Tests attendus (QA — colonne « Tests attendus » D6 du §17.9)
Unitaires avec **fakes** (fake `AgentSession`, fake registres, fake EventBus — aucun vrai CLI) :
- cible **vivante** (session structurée enregistrée) ⇒ `send_blocking` appelé, `reply: Some(...)`
porte le contenu du `Final`.
- cible **morte**`LaunchAgent` invoqué (structuré) **puis** `send` ; `reply` renvoyé.
- **timeout** ⇒ erreur typée remontée, **session non tuée** (le fake atteste qu'aucun `shutdown`
n'a été appelé), pas de `reply`.
- **`AgentReplied`** publié sur l'EventBus en cas de succès.
- cible **PTY-only** (profil sans adapter / présente seulement dans `TerminalSessions`) ⇒ erreur
typée explicite, **pas** d'ACK « launched ».
- **validation** : `agent.message` sans `task``MissingField` ; round-trip JSON `agent.message`.
- **non-régression** : `agent.run` ne change pas de comportement ; aucun accès outbox.
- garde anti-always-green sur au moins l'invariant timeout-ne-tue-pas OU ask-retourne-le-contenu.
## 4. Méthode
DevBackend code → vérifie `cargo build -p domain -p application -p app-tauri`. **N'exécute pas
`cargo test --workspace`** si un build concurrent tient le lock (sinon, lance-le). QA écrit + exécute
les tests, produit un rapport clair si rouge. **Ne pas committer, ne pas push** — Main relit et commit.
## 5. Références
- Domaine : `crates/domain/src/orchestrator.rs` (enum `OrchestratorCommand` ~l.111, `validate`
~l.175), `crates/domain/src/events.rs` (`DomainEvent`).
- Application : `crates/application/src/orchestrator/service.rs` (struct/deps ~l.37, `dispatch`
~l.88, `OrchestratorOutcome` ~l.50, `spawn_agent` comme modèle de résolution d'agent).
- Primitive : `crates/application/src/agent/structured.rs` (`send_blocking`).
- Registre structuré : `StructuredSessions` (livré D1, jumeau de `TerminalSessions`) — repère-le
et lis son API avant de t'en servir.
- Composition root : `crates/app-tauri/src/state.rs`.
- Spec : `ARCHITECTURE.md` §17.4 (réconciliation §16) et tableau §17.9 ligne **D6**.

View File

@ -0,0 +1,95 @@
# Brief Dev — Lot D7 : menu de profils restreint + retrait custom (§17.9)
> Demandé par **Main** à **DevBackend** + **DevFrontend** + **QA**. Cycle §3.
> Dernier lot de §17. **Back + front.** DevBackend livre le contrat, DevFrontend consomme.
## 0. Objectif (§17.3 / §17.6)
Tant que seuls Claude et Codex ont un adapter structuré, le **menu de sélection de profil IA**
ne doit proposer **que** des profils pilotables en mode structuré. Conséquences :
- **Gemini / Aider** (présents dans le catalogue de référence mais **sans** `structured_adapter`)
ne sont **plus proposés** à la sélection.
- Le **profil custom** est **retiré** (l'utilisateur ne peut plus saisir une commande arbitraire,
car on ne saurait pas la piloter en structuré).
Principe : `is_selectable(profile) == structured_adapter.is_some()` (équivaut à
`AgentSessionFactory::supports(profile)`). C'est ce prédicat qui **filtre la liste exposée**
(wizard first-run **et** création/édition d'agent).
> Note : on **ne casse pas** le modèle `AgentProfile` (un profil sans adapter reste un profil
> PTY/legacy valide, §17.3). On restreint seulement ce qui est **proposé à la sélection**.
## 1. Côté DevBackend (`crates/`)
1. **Prédicat de sélectionnabilité** centralisé : `is_selectable(&AgentProfile) -> bool`
(= `structured_adapter.is_some()`). Place-le là où c'est cohérent (catalogue/usecases agent).
Évite de dupliquer la logique ; si `AgentSessionFactory::supports` existe déjà (livré D2),
garde la **même sémantique** (les deux doivent rester d'accord).
2. **Exposer uniquement les profils sélectionnables** au chemin de sélection : le use case qui
alimente le wizard/la création (autour de `ReferenceProfiles` / `reference_profiles()` dans
`crates/application/src/agent/{catalogue,usecases}.rs`) doit **filtrer** sur `is_selectable`.
Gemini/Aider restent dans le catalogue **data** (ne les supprime pas du modèle) mais
**n'apparaissent pas** dans la liste proposée. Décide proprement : soit un nouveau champ
`selectable: bool` sur le DTO exposé, soit une liste déjà filtrée — choisis l'option la moins
ambiguë pour le front et documente-la.
3. **Retrait custom (back)** : si une commande/usecase accepte un profil custom arbitraire pour
la sélection/création depuis le wizard, neutralise ce chemin (ou documente qu'il n'est plus
appelé). Ne casse pas la persistance de profils existants.
4. Vérifie `cargo build -p domain -p application -p app-tauri`.
**Contrat à livrer à DevFrontend** (à mettre dans ton rapport) : la forme exacte de ce que le
front reçoit (liste filtrée ? champ `selectable`/`structuredAdapter` sur `ProfileDto` ?) pour
qu'il sache quoi afficher et quoi masquer. Rappel : `ProfileDto(pub AgentProfile)` sérialise déjà
`structuredAdapter` (camelCase) — tu peux t'appuyer dessus plutôt que d'ajouter un champ.
## 2. Côté DevFrontend (`frontend/src/`)
> **Ne démarre qu'après le contrat de DevBackend** (Main te relaiera la forme exacte).
1. **Wizard first-run** (`features/first-run/FirstRunWizard.tsx`, `ProfilesSettings.tsx`) :
- n'affiche que les profils **sélectionnables** (Claude/Codex) ;
- **retire le bloc `AddCustomProfile`** (`onAdd`/`vm.addCustom`, `emptyCustomProfile`,
`aria-label="add custom profile"`) — le bouton/forme custom **disparaît**.
2. **Sélecteur d'agent** (création/édition dans `features/agents/`) : même filtre — seuls
Claude/Codex proposés ; pas d'option custom.
3. Nettoie le code mort résultant (helpers `emptyCustomProfile`, validation custom) **uniquement**
s'il n'est plus référencé ailleurs — sinon laisse-le et signale-le.
4. Vérifie `cd frontend && npm run build`.
## 3. Invariants
- **Zéro régression** : la persistance/édition des profils déjà configurés n'est pas cassée ;
un projet existant avec un agent Gemini/Aider/custom **legacy** continue de fonctionner (on
restreint la **création**, pas l'exécution de l'existant).
- Le prédicat `is_selectable` est la **source unique** ; back et front doivent rester cohérents.
- Frontières : le front filtre/affiche selon le contrat du port, le back décide la sélectionnabilité.
## 4. Tests attendus (QA)
**Rust** (`-p application`/`app-tauri`) :
- `is_selectable` vrai pour Claude/Codex, faux pour Gemini/Aider.
- la liste exposée à la sélection ne contient **que** Claude/Codex (custom absent).
- non-régression : `reference_profiles()` (catalogue brut) contient toujours les 4 (data intacte).
**Vitest** (`frontend`) :
- le wizard first-run n'affiche que Claude/Codex ; **le bloc custom est absent**
(`aria-label="add custom profile"` introuvable).
- le sélecteur de création d'agent ne propose que Claude/Codex, pas de custom.
- garde anti-always-green : un test qui vérifie l'**absence** du custom doit échouer si le bloc
réapparaît (assertion sur non-présence d'un testid/label précis).
## 5. Méthode
DevBackend → contrat + build vert → Main relaie à DevFrontend → build vert → QA écrit+exécute
(`cargo test --workspace` ET `npx vitest run`) → vert. Rapport d'erreurs clair si rouge.
**Ne pas committer, ne pas push.**
## 6. Références
- Catalogue : `crates/application/src/agent/catalogue.rs` (`reference_profiles()` : claude+codex
`with_structured_adapter`, gemini+aider sans) ; use cases : `…/agent/usecases.rs`
(`ReferenceProfiles`).
- Factory : `AgentSessionFactory::supports` (livré D2, `crates/infrastructure/src/session/factory.rs`).
- DTO : `crates/app-tauri/src/dto.rs` (`ProfileDto`/`ProfileListDto`).
- Front : `frontend/src/features/first-run/{FirstRunWizard,ProfilesSettings}.tsx`,
`frontend/src/features/agents/`, `frontend/src/domain/index.ts` (`emptyCustomProfile`).
- Spec : `ARCHITECTURE.md` §17.3, §17.6 et tableau §17.9 ligne **D7**.

View File

@ -0,0 +1,48 @@
# Design — Option 1 « Terminal + MCP » (orchestration inter-agents)
> Décision produit arbitrée (2026-06-11). Remplace la vue chat structurée par le
> terminal natif + délégation inter-agents par outils MCP. Source : agent Architecte.
> Statut : **design validé, dev NON commencé** (limite de session atteinte le 2026-06-11,
> reset 3:40am Europe/Paris). Reprendre par les lots backend B-0→B-5 et frontend F-1.
## Objectif
- **Vue humaine = terminal brut natif** (PTY interactif). Réflexion live + Échap = natifs CLI, zéro parsing par modèle. On abandonne `AgentChatView`/stream-json comme vue.
- **Délégation cross-model via MCP** : `idea_ask_agent(target, task)` bloquant → la cible traite quand libre (FIFO) → rend son résultat via NOUVEL outil `idea_reply(result)` → IdeA débloque l'appelant. Fin-de-tour = signal MCP explicite.
- Principes : 1 agent = 1 employé (1 process/session, input FIFO) ; hexagonal + SOLID stricts ; plus aucun `parse_event` requis pour vue ni orchestration.
## Découvertes clés de l'architecte (état réel du code)
1. La **file FIFO existe déjà** : `OrchestratorService` (`crates/application/src/orchestrator/service.rs`) a `ask_locks: Mutex<HashMap<AgentId, Arc<AsyncMutex<()>>>>` + `ask_lock_for()` + `ASK_QUEUE_WAIT_CAP` (600s) + `ASK_AGENT_TIMEOUT` (300s). On la formalise en port `AgentMailbox` (pour porter un `oneshot` de réponse).
2. `idea_ask_agent``agent.message``OrchestratorCommand::AskAgent{target_agent, task}` **déjà câblé** (mcp/tools.rs, domain/orchestrator.rs, service.rs). On réimplémente le **corps** de `ask_agent()`.
3. Aujourd'hui `ask_agent` **exige une session structurée** et renvoie `AppError::Invalid` si la cible est en PTY brut (service.rs ~400-410). **Inverser cette branche** : PTY vivant = canal normal.
4. Routage structuré dans `crates/application/src/agent/lifecycle.rs` (`LaunchAgent` ~1100). Levier de bascule : **ne plus injecter la fabrique structurée au composition root** (`crates/app-tauri/src/state.rs`, `with_structured`).
5. `apply_mcp_config` (lifecycle.rs ~1391) écrit déjà `.mcp.json` + `--mcp-config` AVANT le spawn, **chemin PTY inclus** → la CLI PTY a déjà le serveur MCP IdeA (à vérifier par test B-0). Vigilance : `ensure_mcp_server` doit piloter `McpServer::serve` sur le loopback.
6. `idea_reply` n'existe nulle part : seul vrai ajout de surface.
## Lots BACKEND (Rust — agent dev backend) ; NE PAS faire B-6 (nettoyage) avant coordination
- **B-0** Prérequis transport MCP : garantir CLI PTY reçoit `--mcp-config <path>` (endpoint/project/requester) + `serve` piloté loopback. Test : CLI factice PTY appelle `idea_list_agents`, reçoit réponse.
- **B-1** Port `AgentMailbox` + `InMemoryMailbox`. Domaine pur (`crates/domain/src/mailbox.rs` ou ports.rs) : trait + `Ticket{id,requester,task}`, `TicketId`, `MailboxError`. Infra (`crates/infrastructure/src/mailbox/`) : `HashMap<AgentId, VecDeque<(Ticket, oneshot::Sender<String>)>>` + mutex ; `enqueue` rend `PendingReply` (sur `oneshot::Receiver`). Tests : FIFO ; `resolve` réveille le bon pending ; 2 ask même cible sérialisés ; cibles ≠ non bloquants ; timeout retire ticket de tête.
- **B-2** Bascule routage : tous en PTY. `state.rs` : retirer `with_structured` de `LaunchAgent`/`OrchestratorService`/`ChangeAgentProfile`. Tests : profil Claude → PTY ; DTO renvoie `CellKind::Pty`. Ne pas supprimer `launch_structured` (mort-code, nettoyage ultérieur).
- **B-3** Réimplémenter `ask_agent` : résoudre id → `mailbox.enqueue` → ticket en tête → garantir cible vivante PTY (sinon LaunchAgent PTY bg) → `PtyPort::write` préfixe `[IdeA · tâche de {A} · ticket {id}] {task}\n``await PendingReply` borné `ASK_AGENT_TIMEOUT`. PTY vivant = normal. Timeout : garder agent vivant, retirer ticket de tête. Publier `AgentReplied`. Injecter `Arc<dyn AgentMailbox>` + `Arc<dyn PtyPort>`. Tests : injection bon handle ; agent mort relancé ; timeout libère file ; AgentReplied.
- **B-4** Outil/action `idea_reply` : `ToolDef idea_reply` (schéma `{result:string}` seul, pas de ticket_id exposé), action wire `agent.reply`, `OrchestratorCommand::Reply{from:AgentId, result}`, `validate`, `map_tool_call` (passe `requester` du handshake comme `from`), bras dispatch → `mailbox.resolve(from, result)`. Corrélation implicite : `idea_reply` résout le ticket en tête de la file de l'émetteur (identité via handshake, pas via id géré par le modèle). `tool_returns_reply` : idea_reply = ACK sans inline. Tests : mapping ; validate exige result ; resolve corrèle tête ; reply sans ask = erreur typée (pas de panic).
- **B-5** Protocole délégation dans le contexte : injecter dans convention file (`apply_injection`) + description outil : « reçois `[IdeA · tâche …]` → traite → appelle IMPÉRATIVEMENT `idea_reply(result=…)` ; ne réponds jamais qu'en texte. » Test : convention file contient l'instruction.
## Lots FRONTEND (TS/React — agent dev frontend) ; NE PAS faire F-2 (suppression) avant coordination
- **F-1** Router toute cellule agent vers `TerminalView` (jamais `AgentChatView`) ; ré-attache PTY + scrollback OK. Backend renverra `cellKind:"pty"`. Lire `frontend/src/features/layout/LayoutGrid.tsx`, `features/chat/AgentChatView.tsx`, `TerminalView`, `adapters/agent.ts`, `ports/index.ts`, `domain/index.ts`. Laisser `AgentChatView` inerte (non monté), pas supprimé. Tests Vitest : agent rend `TerminalView`, jamais `AgentChatView` ; re-mount repeint pty.
## Ordre / dépendances
```
B-0 ─┬─ B-2 ─┬─ B-3 ─ B-4 ─ B-5
B-1 ─┘ └─ F-1
Nettoyage (B-6, F-2) en dernier, coordonné.
```
Chemin critique : B-0 → B-2 → B-3 → B-4 → B-5. B-1 ∥ B-0. F-1 dès B-2.
## Cohérence
Domaine sans I/O (port + entités pures) ; oneshot/PTY/MCP = infra ; application via ports. Open/Closed (idea_reply = ajout, dispatch intact) ; Liskov (Claude/Codex identiques derrière PTY+MCP) ; 1 process/agent préservé.
## Fichiers à toucher
- Domaine : `mailbox.rs` (nouveau) / `ports.rs` ; `orchestrator.rs` (variante `Reply` + action `agent.reply`).
- Application : `orchestrator/service.rs` (ask_agent + reply + injection ports) ; `agent/structured.rs` (supprimé au nettoyage) ; `agent/lifecycle.rs` (routage).
- Infra : `mailbox/` (nouveau) ; `orchestrator/mcp/tools.rs` (idea_reply) ; `orchestrator/mcp/server.rs` (passer requester).
- app-tauri : `state.rs` (retrait with_structured + injection mailbox + ensure_mcp_server) ; `commands.rs`/`dto.rs` (nettoyage ultérieur).
- Frontend : `features/layout/LayoutGrid.tsx` (routage TerminalView) ; `features/chat/*` (nettoyage ultérieur).

View File

@ -0,0 +1,280 @@
# Cadrage Architecture — Orchestration v3 : invocation native d'agents (surface MCP)
> Produit par **Architect** en réponse au brief `orchestration-v3-invocation-native.md`.
> Cadrage **avant tout code** (méthode §3). Livrable : ce document + mise à jour `ARCHITECTURE.md` §14.3.
> Aucun code de production ici.
---
## 0. État réel du terrain — ce qui est DÉJÀ résolu (lu dans le code, pas présumé)
Le brief décrit trois faiblesses (conscience = prose, pas de discussion inter-agents,
fire-and-forget). **Deux des trois sont déjà comblées par le pivot §17** (livré, lots D0→D7).
Il faut le constater honnêtement pour ne **pas re-cadrer** ce qui existe :
| Faiblesse du brief | Statut réel | Référence code |
|---|---|---|
| Pas de discussion inter-agents (`agent.message` « future », `task` ignoré pour agent vivant) | ✅ **RÉSOLU** | `OrchestratorCommand::AskAgent` (`domain/src/orchestrator.rs`), `OrchestratorService::ask_agent` (`application/src/orchestrator/service.rs`) |
| Fire-and-forget, pas de corrélation requête↔réponse | ✅ **RÉSOLU sans outbox** : le rendez-vous synchrone est **intrinsèque** à `AgentSession::send()` (flux → `Final` déterministe). Le `Final` *est* la fin de tour. | `application/src/agent/structured.rs` (`send_blocking`), `domain/src/ports.rs` (`ReplyEvent::Final`) |
| Pas de réveil du demandeur / event de réponse | ✅ **RÉSOLU** | `DomainEvent::AgentReplied`, `OrchestratorResponse.reply` (`infrastructure/src/orchestrator/mod.rs`) |
| Conscience = soft prompt (l'agent doit deviner le schéma JSON) | ⚠️ **PARTIEL** : la prose `# Orchestration IdeA` est injectée (`compose_convention_file`), mais **aucun outil typé natif** n'est exposé. | `application/src/agent/lifecycle.rs` |
| Interdiction des subagents natifs **+** alternative native | ⚠️ **PARTIEL** : interdiction présente (prose) ; l'alternative native (outils `idea_*`) **manque encore**. | idem |
| Capacité MCP sur le profil | ❌ **ABSENT** | — |
| Serveur MCP / config MCP par CLI | ❌ **ABSENT** | — |
**Conclusion de cadrage** : l'orchestration v3 **n'est plus** « combler la messagerie inter-agents »
(c'est fait). Elle se réduit à **un seul chantier net** : **exposer l'orchestration IdeA comme
serveur MCP model-agnostic**, en tant qu'**adapter entrant supplémentaire** par-dessus le **même**
`OrchestratorService::dispatch`, avec **repli homogène** sur le protocole fichier `.ideai/requests`
(§14.3) + prose (`compose_convention_file`) pour les CLI sans MCP. C'est ce que cadre la suite.
> **Principe directeur (zéro régression, §9/§17.3)** : MCP est un **confort de conscience native**
> (outils typés, plus de schéma à deviner). Il **n'invente aucune sémantique** : tout outil MCP se
> ramène à un `OrchestratorCommand` déjà existant. La voie principale du *retour de valeur* reste
> §17 (`send_blocking`) ; MCP ne fait que **déclencher** `dispatch`, jamais re-router la réponse.
---
## 1. Décisions tranchées (les 4 points durs du brief)
### Décision 1 — Capacité MCP par runtime = champ optionnel `mcp` sur `AgentProfile` (Open/Closed)
**Tranché** : on ajoute un champ **optionnel** `mcp: Option<McpCapability>` sur `AgentProfile`,
exactement comme `session: Option<SessionStrategy>` et `structured_adapter: Option<StructuredAdapter>`
le sont déjà. `None` (défaut) ⇒ **repli fichier + prose** (comportement actuel, zéro régression).
`Some(_)` ⇒ IdeA matérialise la config MCP de cette CLI au lancement et l'agent voit les outils `idea_*`.
```rust
// domain/src/profile.rs — capacité MCP déclarative (pur, validé par constructeur, comme SessionStrategy)
/// Stratégie de matérialisation de la config MCP propre à UNE CLI : chaque CLI
/// déclare son serveur MCP différemment (fichier `.mcp.json` pour Claude Code,
/// flag de lancement, ou variable d'env). Déclaratif = donnée, pas code (§9).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "strategy")]
pub enum McpConfigStrategy {
/// Écrire un fichier de conf MCP au chemin (relatif au run dir isolé §14.1)
/// attendu par la CLI, au format JSON propre à cette CLI (ex. `.mcp.json`).
ConfigFile { target: String }, // relative_safe(target) — pas de `..`, pas d'absolu
/// Passer le serveur via un flag de lancement (ex. `--mcp-config {path}`).
Flag { flag: String }, // non_empty(flag)
/// Passer via une variable d'environnement.
Env { var: String }, // valid_env_var(var)
}
/// Capacité MCP d'un profil : COMMENT déclarer le serveur MCP IdeA à cette CLI,
/// et QUEL transport. `None` sur le profil ⇒ repli fichier `.ideai/requests` + prose.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct McpCapability {
/// Comment matérialiser la config MCP au lancement (relatif au run dir).
pub config: McpConfigStrategy,
/// Transport du serveur MCP IdeA (détail invisible au domaine ; voir D3).
/// `stdio` = défaut robuste cross-OS ; `socket` = optimisation (point ouvert).
#[serde(default)]
pub transport: McpTransport,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum McpTransport { #[default] Stdio, Socket }
```
Sur `AgentProfile`, additif et **non cassant** (sérialisation inchangée pour les profils sans MCP) :
```rust
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mcp: Option<McpCapability>,
```
Builder additif (comme `with_structured_adapter`) : `AgentProfile::new(...).with_mcp(cap)` ; la
signature de `AgentProfile::new` reste **inchangée** ⇒ tous les appels du catalogue/tests restent verts.
**Justification** : cohérence §9 (« ajouter une IA = donnée, pas code »), symétrie avec les deux autres
capacités optionnelles déjà sur le profil, `skip_serializing_if = None`**zéro régression** de
sérialisation. Le **prédicat de surface** est `profile.mcp.is_some()` — un seul point de vérité.
> **Modèle en couches (exigé par le brief §4.1)** : la surface effective d'un agent est
> `surface(agent) = if profile.mcp.is_some() { Mcp } else { FileProtocol }`. Les deux couches
> produisent le **même** `OrchestratorCommand`. Aucun agent n'est jamais bloqué : sans MCP, la prose
> `# Orchestration IdeA` + `.ideai/requests` reste pleinement fonctionnelle.
---
### Décision 2 — Retour synchrone d'`ask` = AUCUN nouveau modèle de corrélation : on réutilise `send_blocking`
**Tranché (et c'est la décision la plus importante)** : l'outil MCP `idea_ask_agent` **ne crée
aucune corrélation requête↔réponse, aucun outbox, aucun event de réveil neufs**. Il appelle le
**même** `OrchestratorService::dispatch(AskAgent { target, task })` que le watcher fichier, qui
**retourne déjà** `OrchestratorOutcome { reply: Some(content) }` via `send_blocking`. L'adapter MCP
**renvoie ce `content` inline** comme valeur de retour de l'outil. Fin.
Le brief (rédigé avant le pivot §17) supposait qu'`ask` était un point dur à résoudre via outbox +
corrélation fichier. **Le pivot §17 l'a déjà tranché autrement** : le `Final` du `ReplyStream` *est*
la fin de tour déterministe ; pas besoin de deviner, pas d'outbox, pas de `request_id`. On **n'y
revient pas**. Le tableau ci-dessous fige la sémantique, déjà implémentée :
| Aspect | Décision (déjà en place) | Code |
|---|---|---|
| **Corrélation** | Aucune : `dispatch` est un appel synchrone `async` ; la réponse est la valeur de retour. Le transport MCP (JSON-RPC) porte nativement la corrélation requête/réponse. | `service.rs::ask_agent` |
| **Outbox** | **Supprimé de la voie principale** (§17.4). Pas réintroduit. | — |
| **Event `AgentReplied`** | **Observabilité UI uniquement** (« Architect a répondu à Main »), best-effort, ne porte pas la valeur. | `reply_outcome` |
| **Timeout** | Borné (`ASK_AGENT_TIMEOUT = 300 s`). À l'expiration : `AgentSessionError::Timeout` → la cible **reste vivante** (non tuée), l'outil MCP renvoie une **erreur typée** ; l'appelant décide. | `send_blocking` |
| **Cible a déjà une session vivante** (one-live-session-per-agent) | `ask` **réutilise** la session structurée vivante (`session_for_agent`) — rendez-vous direct, pas de respawn. Si la cible est vivante en **PTY brut** (profil sans `structured_adapter`) ⇒ erreur typée explicite (**jamais** un ACK trompeur). | `service.rs::ask_agent` étapes 1→3 |
| **Cible morte** | `LaunchAgent` en mode structuré (background) puis `send_blocking`. Garde d'unicité sur **les deux** registres. | idem |
**Justification** : DRY radical (une seule logique de rendez-vous, partagée par UI chat, watcher
fichier et MCP) ; frontière nette (le domaine ne connaît qu'un `prompt` et un `Final`, jamais un
transcript ni un id de corrélation) ; universalité (marche pour toute CLI structurée Claude/Codex).
**Le seul travail v3 ici est de brancher l'outil MCP sur `dispatch` — pas de re-cadrer le rendez-vous.**
> **Conséquence produit** : `idea_ask_agent` cible **toujours un agent structuré** (Claude/Codex),
> cohérent avec le menu restreint §17.3/§17.6. Un agent **demandeur** peut être n'importe quelle CLI
> MCP (Claude, Codex, Gemini…) ; un agent **cible** d'un `ask` doit être structuré. C'est déjà
> l'invariant en vigueur — MCP ne le change pas.
---
### Décision 3 — MCP vs subagents natifs : on garde l'interdiction ET on offre l'alternative native, config injectée par CLI au lancement
**Tranché** : l'interdiction des subagents natifs (prose `# Orchestration IdeA`) **reste** — elle
protège l'identité/mémoire/observabilité IdeA. Mais on offre désormais la **vraie alternative
native** : les outils `idea_*` apparaissent dans la liste d'outils de la CLI. La prose est **adaptée
selon la surface** :
- agent **MCP** (`profile.mcp.is_some()`) : la prose pointe vers les outils `idea_ask_agent` /
`idea_launch_agent` / `idea_list_agents` (au lieu d'« écris un JSON dans `.ideai/requests` »).
- agent **fichier** (`mcp == None`) : prose `.ideai/requests` actuelle, **inchangée**.
**Injection de la config MCP par CLI = au `LaunchAgent`, dans le run dir isolé (§14.1), via la
`McpConfigStrategy`** — exactement le même point et la même mécanique que le convention file :
```
LaunchAgent::execute (après apply_injection, avant spawn/factory.start) :
if let Some(mcp) = &profile.mcp:
// IdeA matérialise SA config MCP au format de CETTE CLI dans <run_dir>/...
apply_mcp_config(mcp, &run_dir, &spec) // ConfigFile→write ; Flag→spec.args ; Env→spec.env
```
- `ConfigFile { target }` : écrit `<run_dir>/<target>` (ex. `.mcp.json`) avec la déclaration du
serveur MCP IdeA (commande/transport). Non-clobbering, best-effort, **comme le seed de permissions**.
- `Flag { flag }` : ajoute le flag + chemin au `SpawnSpec.args`.
- `Env { var }` : ajoute la variable au `SpawnSpec.env`.
Le **serveur MCP lui-même** est démarré **par projet ouvert**, à côté du `FsOrchestratorWatcher`,
dans le **même hook** `ensure_orchestrator_watch` (`app-tauri/src/state.rs`). Une CLI qui se lance
avec la config injectée se connecte à ce serveur (stdio : IdeA spawn un pont par session ; socket :
adresse partagée — détail d'adapter, point ouvert S-MCP).
**Justification** : symétrie totale avec le convention file et le seed de permissions (même run dir,
même best-effort non-clobbering, même moment) ⇒ aucune nouvelle plomberie de cycle de vie. La config
MCP est **donnée déclarative par profil**, donc « ajouter une CLI MCP = donnée, pas code ».
---
### Décision 4 — Frontières hexagonales : le serveur MCP est un adapter entrant d'infrastructure ; AUCUN nouveau port applicatif
**Tranché** : le serveur MCP est un **driving adapter d'infrastructure**
(`infrastructure/src/orchestrator/mcp/`), **pair** du `FsOrchestratorWatcher`. Il appelle le **même**
`OrchestratorService::dispatch` (application) et **ne duplique rien**. Trois portes d'entrée
substituables se ramènent au même `OrchestratorCommand` :
```
┌─────────────────────────────────────────────┐
Agent MCP ───▶│ Serveur MCP (infra/orchestrator/mcp) │──┐
└─────────────────────────────────────────────┘ │
┌─────────────────────────────────────────────┐ │ OrchestratorCommand
Fichier ───▶│ FsOrchestratorWatcher (infra/orchestrator) │──┼──▶ OrchestratorService::dispatch
(.ideai/ └─────────────────────────────────────────────┘ │ (application — INCHANGÉ)
requests) ┌─────────────────────────────────────────────┐ │ │
UI ───▶│ Commandes Tauri (app-tauri) │──┘ ▼
└─────────────────────────────────────────────┘ use cases agent/terminal
```
- **Où vit le serveur MCP** : `infrastructure/src/orchestrator/mcp/`. Il **traduit** un appel d'outil
MCP (`idea_ask_agent`, `idea_launch_agent`, `idea_list_agents`, et par parité `idea_update_context`,
`idea_create_skill`, `idea_stop_agent`) en `OrchestratorCommand`, appelle `dispatch`, et renvoie
`OrchestratorOutcome` (`reply`/`detail`) inline comme résultat d'outil. JSON-RPC, stdio/socket,
le crate MCP : **tout reste dans cet adapter**. Le domaine/application ignorent MCP.
- **Quel port côté domaine/application** : **aucun nouveau**. `OrchestratorService::dispatch`
(application) est déjà l'unique seam. `idea_list_agents` réutilise `ListAgents`. La validation
(`OrchestratorRequest::validate`) reste le point unique « parse, don't validate » — l'adapter MCP
construit un `OrchestratorCommand` (directement, ou via `OrchestratorRequest` pour réutiliser la
validation, au choix d'implémentation).
- **Réutilisation de `OrchestratorService` plutôt que duplication** : le serveur MCP reçoit
`Arc<OrchestratorService>` au composition root (`state.rs`), exactement comme le watcher. Une seule
logique applicative ; les adapters ne portent que leur techno d'entrée.
**Justification** : DRY + règle de dépendance hexagonale. Cible, identité, mémoire, observabilité UI
passent **toujours** par le seul chemin applicatif. Les spikes MCP (transport, crate) sont **confinés**
à l'adapter infra et ne touchent ni le domaine ni l'application.
---
## 2. Modèle de messages corrélés — état figé (rien de neuf)
La « corrélation requête↔réponse » du brief est portée **nativement par le transport** :
- **MCP** : JSON-RPC corrèle requête/réponse par `id` de message ⇒ rien à modéliser côté IdeA.
- **Fichier** : `<file>.json``<file>.json.response.json` (sibling), déjà en place.
- **Valeur de retour** : `OrchestratorOutcome { detail, reply }` (application) → `OrchestratorResponse
{ ok, action, detail, error, reply }` (infra fichier) **ou** résultat d'outil MCP. **Structs déjà
définies**, réutilisées telles quelles.
Aucun `CorrelationId`, aucun `AgentReply`, aucun port `AgentReplyChannel`, aucun outbox : **abandonnés
par le pivot §17** et **non réintroduits** par v3. C'est la simplification clé.
---
## 3. Découpage en LOTS testables (méthode §3) — MCP uniquement
> Chaque lot = binôme dev+test, vert avant le suivant. Backend/frontend séparés.
> Les terminaux non-IA et le chemin fichier `.ideai/requests` restent verts à chaque lot.
> **Spike S-MCP** (crate MCP Rust + transport stdio/socket + format de conf par CLI) est **confiné au
> lot M2** et n'invalide pas l'ossature (le contrat d'entrée reste `OrchestratorCommand`).
| Lot | Côté | Périmètre | Crates/dossiers | Contrats | Tests attendus |
|---|---|---|---|---|---|
| **M0 (capacité profil)** | back | `McpCapability` + `McpConfigStrategy` + `McpTransport` (domaine, validés) ; champ `AgentProfile.mcp: Option<McpCapability>` (+ builder `with_mcp`, `new` inchangé) ; catalogue Claude/Codex annotés (ex. `ConfigFile { target: ".mcp.json" }`). | `domain/src/profile.rs`, `application/src/agent/catalogue.rs` | enum + struct + champ optionnel sérialisé | unit purs : `mcp = None` round-trip **identique à avant** (zéro régression sérialisation) ; `Some(_)` round-trip ; constructeurs valident (`relative_safe` target, `non_empty` flag, `valid_env_var`) ; catalogue annoté. |
| **M1 (injection conf MCP au lancement)** | back | `LaunchAgent` matérialise la conf MCP selon `McpConfigStrategy` dans le run dir isolé (après `apply_injection`, avant spawn/`factory.start`) : `ConfigFile`→write non-clobbering, `Flag`→`args`, `Env`→`env`. Prose `compose_convention_file` adaptée selon `mcp.is_some()`. | `application/src/agent/lifecycle.rs` | `LaunchAgent` (chemin MCP additif) | unit (fakes) : profil `mcp=None` ⇒ **aucun** write/flag/env MCP (chemin actuel inchangé) ; `ConfigFile` ⇒ fichier écrit au bon chemin, non-clobbering ; `Flag`/`Env` ⇒ `spec` enrichi ; prose contient les outils `idea_*` si MCP, sinon `.ideai/requests`. |
| **M2 (serveur/adapter MCP)** | back | `infrastructure/src/orchestrator/mcp/` : serveur MCP exposant `idea_ask_agent`/`idea_launch_agent`/`idea_list_agents` (+ parité `idea_update_context`/`idea_create_skill`/`idea_stop_agent`) → `OrchestratorCommand` → `dispatch` → résultat inline. **Spike S-MCP** (crate, transport) isolé ici. | `infrastructure/src/orchestrator/mcp/` | mapping outil→commande ; `Arc<OrchestratorService>` injecté | unit (fakes + `OrchestratorService` à use cases fakes) : chaque outil mappe la bonne commande ; `idea_ask_agent` renvoie `reply` inline ; timeout → erreur typée, cible non tuée ; `idea_list_agents` liste ; JSON-RPC malformé → erreur, jamais panic. Hors-réseau (transport en mémoire/pipe scriptable). |
| **M3 (câblage par projet)** | back | Démarrer le serveur MCP par projet ouvert dans `ensure_orchestrator_watch` (à côté du watcher) ; registre `mcp_servers` jumeau de `orchestrator_watchers` ; arrêt à la fermeture du projet. | `app-tauri/src/state.rs`, `commands.rs` | hook `ensure_orchestrator_watch` étendu | app-tauri : un serveur MCP par projet, idempotent ; fermeture du projet ⇒ arrêt ; coexiste avec le watcher fichier (les deux portes vivantes). |
| **M4 (observabilité UI — optionnel)** | front | Surfacer dans l'UI Agents qu'une délégation est passée par MCP vs fichier (badge/source sur l'event `OrchestratorRequestProcessed` / `AgentReplied`). Non bloquant. | `frontend/src/features/agents` | DTO d'event enrichi (`source: "mcp"|"file"`) | Vitest : badge source affiché ; absence d'event ⇒ pas de régression. |
**Ordre conseillé** : **M0 → M1 → M2 → M3** (→ M4 optionnel). M0 débloque tout (donnée pure) ;
M1 injecte la conf (testable sans serveur) ; M2 livre l'adapter derrière un transport scriptable
(spike confiné) ; M3 le câble par projet. M4 est du confort d'observabilité.
---
## 4. Conformité hexagonale & SOLID (rappel)
- **Règle de dépendance** : `McpCapability`/`McpConfigStrategy` sont **domaine** (purs, validés).
Le serveur MCP, JSON-RPC, stdio/socket, le crate MCP sont **exclusivement** infra. Le domaine et
l'application **ignorent** MCP (l'application ne voit que `OrchestratorCommand`/`dispatch`).
- **S** : le serveur MCP = une seule techno d'entrée (MCP→commande). `OrchestratorService` garde sa
responsabilité (commande→use cases). `LaunchAgent` gagne une étape d'injection homogène, pas une
responsabilité nouvelle.
- **O** : ajouter une CLI MCP = un bloc `mcp` sur le profil (**donnée**). Aucun cœur touché.
- **L** : les trois portes d'entrée (fichier, MCP, UI) sont substituables — même `dispatch`, même
résultat. Repli fichier ≡ MCP du point de vue de la réponse.
- **I** : le serveur MCP ne reçoit que `Arc<OrchestratorService>` (pas les use cases en détail).
- **D** : tout injecté au composition root (`state.rs`) ; aucun `new` d'adapter MCP ailleurs.
---
## 5. Chantiers adjacents (situés, NON cadrés ici)
- **Hot-swap de l'AI profile** (chantier A, §15.1) : **LIVRÉ** (`ChangeAgentProfile`). Interaction
avec v3 : un swap vers/depuis un profil MCP change la surface (`mcp.is_some()`) ⇒ au relance,
`LaunchAgent` (ré)injecte ou retire la conf MCP automatiquement. **Rien à cadrer** : la surface suit
le profil courant, point de vérité unique.
- **Reprise auto des sessions au redémarrage** (chantier B, §15.2) : **LIVRÉ** (`ListResumableAgents`,
`conversation_id` persisté sur la cellule). Interaction avec v3 : à la reprise, `LaunchAgent`
ré-matérialise la conf MCP comme à tout lancement (M1). **Rien à cadrer**.
Ces deux chantiers **ne sont pas un prérequis** de v3/MCP et n'en bloquent aucun lot.
---
## 6. Synthèse des décisions
1. **Capacité MCP = `Option<McpCapability>` sur le profil** (Open/Closed, `None` ⇒ repli fichier+prose, zéro régression sérialisation).
2. **Retour synchrone d'`ask` : RIEN de neuf** — réutilise `send_blocking`/`AskAgent`/`AgentReplied`/`OrchestratorOutcome.reply` déjà livrés (§17). Pas d'outbox, pas de corrélation fichier, pas de `CorrelationId`. Le transport MCP corrèle nativement.
3. **Interdiction subagents natifs conservée + alternative native** : prose adaptée selon surface ; conf MCP injectée **par CLI** au `LaunchAgent` dans le run dir isolé (`McpConfigStrategy` : ConfigFile/Flag/Env), symétrique au convention file et au seed permissions.
4. **Frontières** : serveur MCP = **adapter entrant infra** (`infrastructure/src/orchestrator/mcp/`), pair du watcher fichier, appelant le **même** `OrchestratorService::dispatch`. **Aucun nouveau port** applicatif/domaine.
5. **Lots** : M0 (capacité profil) → M1 (injection conf) → M2 (adapter/serveur MCP, spike confiné) → M3 (câblage par projet) → M4 (observabilité, optionnel).

View File

@ -0,0 +1,85 @@
# Brief Architecture — Orchestration v3 : invocation native d'agents (surface MCP + repli fichier)
> Demandé par **Main** à **Architect**. Cadrage attendu **avant tout code** (méthode §3).
> Ce brief ne prescrit pas l'implémentation : il pose le problème, les contraintes et les
> décisions à trancher. À toi de produire la cartographie (ports, adapters, modèles, lots).
## 1. Contexte & problème
Aujourd'hui, un agent apprend qu'il doit déléguer via IdeA **uniquement par une instruction
en prose** injectée en tête de son convention file (`compose_convention_file`,
`crates/application/src/agent/lifecycle.rs` → bloc « # Orchestration IdeA »). Il écrit alors
un JSON dans `.ideai/requests/<id>/*.json`, capté par l'`OrchestratorWatcher`
(`crates/infrastructure/src/orchestrator/mod.rs`) → validé par le modèle domaine pur
(`crates/domain/src/orchestrator.rs`) → exécuté par `OrchestratorService`.
**Trois faiblesses constatées dans le code :**
1. **Conscience = soft prompt.** Rien ne contraint l'agent ; rien ne l'empêche d'utiliser
le subagent natif du fournisseur ; le schéma JSON n'est même pas fourni dans l'instruction
(l'agent doit le deviner).
2. **Pas de discussion inter-agents.** `agent.message` est marqué « future ». Le champ `task`
d'`agent.run` est replié dans `context`, mais `OrchestratorService` n'utilise `context`
que pour un agent **neuf** (initial `.md`) : pour un agent **déjà existant**, le `task` est
**silencieusement ignoré**. La réponse (`*.response.json`) ne porte qu'un ACK de cycle de
vie (`detail: "launched agent X"`), jamais la sortie produite par la cible.
3. **Fire-and-forget.** Aucune corrélation requête↔réponse de contenu, aucun réveil du
demandeur.
## 2. Objectif produit (vision Anthony)
Rendre l'invocation d'un agent par un autre **aussi native que l'invocation de subagents dans
Claude CLI** (l'outil `Task` : le modèle voit un outil typé, l'appelle, et **le résultat
revient inline** dans sa conversation) — mais de façon **model-agnostic** (Claude, Codex,
Gemini, custom) et **toujours médiée par IdeA** (qui garde identité, contexte, mémoire,
observabilité UI).
## 3. Direction pressentie (à valider/affiner par l'Architecte)
**Exposer l'orchestration IdeA comme un serveur MCP** que IdeA branche sur chaque CLI qui le
supporte (Claude Code, Codex, Gemini CLI supportent MCP). Outils pressentis :
| Outil MCP | Effet |
|---|---|
| `idea_ask_agent(target, task) → reply` | Lance/réveille la cible, transmet la tâche, **attend et renvoie sa réponse** inline |
| `idea_launch_agent(target, visibility)` | Lancement fire-and-forget (équiv. `agent.run` actuel) |
| `idea_list_agents() → […]` | Découverte des agents du projet |
Bénéfices : conscience native (l'outil apparaît dans la liste d'outils, plus de prose à
« se rappeler »), arguments typés/validés (fini le JSON deviné), et surtout `ask_agent`
**renvoie le contenu** → comble la messagerie inter-agents manquante.
## 4. Points durs à trancher (cœur du cadrage)
1. **Capacité par runtime.** Tous les profils ne supportent pas MCP/outils (custom CLI).
→ Modèle **en couches** : surface MCP quand le profil le déclare ; **repli sur le protocole
fichier `.ideai/requests` + prose** sinon. Le port `AgentRuntime` gagne une capacité
déclarative (`supportsMcp` ou descripteur de capacités). Comment exprimer ça dans le profil
déclaratif (§9) sans casser l'existant ?
2. **Retour synchrone d'`ask_agent`.** C'est le vrai défi : « attendre que la cible ait fini
son tour et capturer sa sortie » pour un fournisseur arbitraire = même problème que
l'inspecteur de session (cf. mémoire `conversation-resume-architecture`). Piste : la cible
écrit sa réponse dans un **outbox** `.ideai/`, l'outil MCP attend/poll avec corrélation
requête↔réponse + timeout. Définir : modèle de corrélation, event `AgentReplied`, sémantique
de timeout/erreur, et que faire si la cible tourne déjà (one-live-session-per-agent).
3. **MCP vs subagents natifs.** On garde l'interdiction des subagents natifs (sinon
court-circuit d'IdeA = perte identité/mémoire/observabilité), mais on offre désormais une
**vraie alternative native**, pas qu'une interdiction. Comment configurer/injecter le serveur
MCP par CLI (chaque CLI a sa propre conf MCP) depuis le lancement IdeA ?
4. **Frontières hexagonales.** Où vit le serveur MCP (nouvel adapter d'infrastructure ?), quel
port côté domaine/application, comment il réutilise `OrchestratorService` existant plutôt que
de le dupliquer.
## 5. Chantiers adjacents (à seulement situer, pas à cadrer ici)
Garder en tête la cohérence avec deux autres chantiers du même fil « agent = entité » :
- **Hot-swap de l'AI profile** d'un agent existant (absent à toutes les couches aujourd'hui).
- **Reprise auto des sessions au redémarrage** (terrain T5/T7 + `conversation_id` prêt mais
non câblé : rien ne relance les agents `agent_was_running` à l'ouverture du projet).
## 6. Livrable attendu
Une cartographie d'architecture pour l'**orchestration v3** : ports & adapters, modèle de
messages (requête/réponse corrélées), capacité runtime MCP, stratégie de repli, découpage en
**lots** testables (méthode §3), et la liste des décisions tranchées avec leur justification.
Mets à jour `ARCHITECTURE.md` (§14.3) en conséquence.

View File

@ -0,0 +1,215 @@
# Orchestration v5 — Bind transport S-MCP + fix registre session (cadrage)
> **Agent Architecture.** Ce document tranche le **dernier kilomètre** de l'orchestration native : (1) le **bind transport** entre une CLI MCP réellement lancée et le serveur MCP par projet (verrou §S-MCP, resté ouvert depuis M3), et (2) le **fix de robustesse du registre de session** (mémoire `session-registry-agent-ambiguity`). Aucun code de production ici : décisions + contrats + découpage en lots testables.
>
> **État du terrain (lu, pas présumé)** :
> - `infrastructure/src/orchestrator/mcp/{server,transport,jsonrpc,tools}.rs` : `McpServer::serve(&mut transport)` boucle ligne-à-ligne sur `Transport::{recv,send}` ; `StdioTransport<R,W>` (JSON Lines générique), `MemoryTransport` (tests). **`serve` n'est jamais appelé en prod.**
> - `app-tauri/src/state.rs` : `ensure_mcp_server` crée un `McpServer` par projet et le **parke** sur un signal d'arrêt (`McpServerHandle::start` ⇒ `let _server = server; stop_rx.recv().await;`). **Aucun transport, aucun pair.**
> - `application/src/agent/lifecycle.rs` : `apply_mcp_config` matérialise la conf MCP (`ConfigFile`/`Flag`/`Env`) dans le run dir isolé, **après** `apply_injection`, **avant** spawn/`factory.start`. `mcp_server_declaration` écrit un placeholder `{"command":"idea","args":["mcp-server"],"transport":"stdio|socket"}`.
> - `application/src/terminal/registry.rs` : `TerminalSessions` (PTY) + `StructuredSessions` (IA) + agrégateur `LiveSessions`. Invariant **« 1 session vivante/agent »**.
> - `application/src/error.rs` : `AppError::AgentAlreadyRunning { agent_id, node_id }` + code `AGENT_ALREADY_RUNNING` — **défini mais jamais levé** (le garde de `LaunchAgent` rebind/idempotent au lieu d'échouer).
> - `app-tauri/src/commands.rs::list_live_agents` lit **seulement** `terminal_sessions.live_agents()` — **aveugle aux sessions structurées**.
---
## 0. Synthèse exécutive (décisions tranchées)
1. **Transport S-MCP = `stdio-spawn`** : la CLI **spawn elle-même** un process serveur MCP fourni par IdeA. Ce sous-process est un **client de boucle de retour** (loopback) vers le process Tauri, pas un second `OrchestratorService`. Justification : c'est le **seul** modèle réellement supporté par Claude Code / Codex (déclaration `.mcp.json` `{command,args}`), **cross-OS sans port réseau** (compatible AppImage / Windows / SSH-remote), et il **résout le point dur** « comment le process serveur retrouve le bon projet » par **injection d'identité à l'`args`/`env`** au `LaunchAgent` (le projet est connu à ce moment-là). Le socket est **rejeté** comme défaut (ports/permissions/cross-OS) mais **gardé en TODO** derrière le même trait `Transport`.
2. **Le binaire `idea mcp-server` est un sous-commande du binaire app-tauri existant** (pas un nouveau crate/binaire à distribuer séparément) : `main.rs` route `argv[1] == "mcp-server"` vers un mode **headless loopback** qui lit stdin/stdout (JSON Lines = `StdioTransport`) et **relaye** chaque message JSON-RPC au process IdeA principal via un **canal de loopback local** (Unix socket / Windows named pipe **par projet**, créé par `ensure_mcp_server`, chemin passé en `--endpoint`). Le `McpServer` (qui tient l'`OrchestratorService`) **vit dans le process Tauri** ; le sous-process `mcp-server` n'est qu'un **pont stdio↔loopback** ultraléger. Un seul binaire à livrer (AppImage/setup.exe).
3. **Contrat conf↔serveur de bout en bout** rendu **cohérent** : `apply_mcp_config` (M1) écrit une déclaration qui pointe **exactement** vers ce que `ensure_mcp_server` (M3) a mis à l'écoute — `command = <exe IdeA>`, `args = ["mcp-server", "--endpoint", <loopback du projet>, "--project", <id>]`. Fin du placeholder.
4. **Fix registre session = lot PRIORITAIRE et INDÉPENDANT du bind** (peut/doit partir en premier) : l'invariant correct est **« 1 session vivante par agent »** (décision produit verrouillée, mémoire `session-registry-agent-ambiguity` : un agent est un **singleton**, pas N instances). Le fix n'invente **pas** d'identité par cellule ; il **durcit l'invariant** sur les **deux** registres et **réconcilie les `layouts.json` à doublons**. Trois trous concrets à boucher (cf. §3).
5. **Robustesse `ask`** : cible morte ⇒ lancement structuré puis envoi ; cible PTY brut ⇒ `Invalid` explicite (déjà fait) ; **cible occupée par un autre tour** ⇒ sérialisation **FIFO par agent** (nouveau, §4) ; timeout 300 s ⇒ cible **vivante**, erreur typée (déjà fait). Deux `ask` simultanés sur la même cible ⇒ file, **jamais** d'entrelacement de tours.
6. **Frontières** : le pilotage de `serve` vit dans **l'adapter infra** (`McpServer` + une boucle **par connexion** sur le loopback du projet), supervisé par `McpServerHandle` (app-tauri) qui ne fait qu'**accepter les connexions** et spawn une tâche `serve` par pair. Aucune logique applicative ne fuit : le sous-process `mcp-server` ne connaît que des octets JSON-RPC ; `OrchestratorService` ignore tout du transport.
---
## 1. Décision 1 — Modèle de transport S-MCP
### 1.1 Le point dur, posé proprement
Une CLI MCP (Claude Code, Codex) attend une déclaration de serveur de forme **`{ "command": "...", "args": [...] }`** : à l'`initialize`, **elle spawn ce process** et parle **JSON-RPC sur son stdin/stdout**. Donc le « serveur » qu'elle voit est **un process enfant à elle**, distinct du process Tauri. C'est le cœur du problème : ce process enfant **n'a pas** l'`Arc<OrchestratorService>` du projet (use cases, registres de sessions, event bus vivent dans Tauri).
Deux familles de solutions :
| | **stdio-spawn (RETENU)** | socket (rejeté en défaut) |
|---|---|---|
| Ce que la CLI spawn | un **pont** `idea mcp-server` (stdio↔loopback) | rien — elle se connecte à un serveur déjà à l'écoute |
| Où vit l'`OrchestratorService` | process Tauri (le pont relaie) | process Tauri (écoute directe) |
| Cross-OS / AppImage | ✅ pipes stdio + loopback local (Unix socket / named pipe) | ⚠️ port TCP (firewall/permissions) ou socket — déclaration CLI variable |
| Retrouver le bon projet | **`--endpoint`/`--project` à l'`args`**, fixés au `LaunchAgent` (projet connu) | l'adresse encode le projet, mais la CLI doit la connaître |
| Compat Claude/Codex | ✅ `{command,args}` natif | ❓ support socket inégal selon CLI/version |
| Cycle de vie | la CLI **possède** le pont (meurt avec elle) | serveur long-vécu, connexions multiplexées |
### 1.2 Pourquoi stdio-spawn, malgré le sous-process en plus
- **Compatibilité réelle** : `{command,args}` est le **dénominateur commun** des CLIs MCP. Le socket n'est pas universellement déclarable.
- **Cross-OS sans port réseau** : le **loopback local** entre le pont et Tauri est un **Unix domain socket** (Linux/macOS) ou un **named pipe** (Windows) — déjà la techno la plus portable pour de l'IPC local, **sans firewall ni permission réseau**, donc **AppImage-safe** et **SSH-remote-safe** (le pont tourne côté machine de l'agent).
- **Résolution du point dur par injection d'identité** : le projet est **connu** au moment du `LaunchAgent` (c'est lui qui écrit la conf MCP). On **encode** `--endpoint <chemin loopback du projet>` (+ `--project <id>` en garde-fou) dans les `args` de la déclaration. Le pont n'a **rien à deviner** : il se connecte à l'endpoint du **bon** projet. Le `McpServer` côté Tauri, lui, **est** déjà attaché à cet `OrchestratorService`/`Project` (créé par `ensure_mcp_server`).
### 1.3 Le pont `idea mcp-server` (sous-commande du binaire existant)
- **Pas de nouveau binaire distribué** : `main.rs` détecte `argv[1] == "mcp-server"` **avant** d'initialiser Tauri/WebKit, et bascule en **mode headless pont**. Un seul exécutable livré (AppImage / setup.exe).
- **Rôle du pont** : `StdioTransport(stdin, stdout)` côté CLI ; un client de loopback côté Tauri. Boucle : lire une ligne JSON-RPC de la CLI → l'écrire sur le loopback → lire la réponse du loopback → l'écrire sur stdout. **Zéro logique métier** : c'est un tube transparent. (Optionnellement, le pont peut **directement** porter le `McpServer` si l'`OrchestratorService` était accessible — il ne l'est pas inter-process — d'où le relais.)
- **Côté Tauri** : `ensure_mcp_server` crée **l'endpoint loopback du projet** (socket/pipe), et `McpServerHandle` **accepte** les connexions ; **chaque connexion** (= un pont = un agent) ⇒ une **tâche `McpServer::serve(&mut conn_transport)`**`conn_transport` enveloppe le flux loopback. `McpServer` est **déjà** branché à l'`OrchestratorService` du projet.
### 1.4 Identité de l'appelant (lève le `requester_id = "mcp"` figé)
`server.rs::publish_processed` tague aujourd'hui `requester_id: "mcp"` (placeholder). Avec stdio-spawn, le pont **connaît l'agent** (le `LaunchAgent` peut injecter `--requester <agent-id>` dans les `args` de la déclaration, comme `--project`). Le pont passe cette identité dans la **poignée de connexion** (premier message de handshake loopback, hors JSON-RPC MCP), et `McpServer::serve` la porte dans son contexte de connexion ⇒ `OrchestratorRequestProcessed.requester_id` devient l'**agent réel**. Observabilité UI exacte (qui a délégué à qui).
### 1.5 Socket = TODO derrière le même trait
Le trait `Transport` (jsonrpc.rs) **isole** déjà le serveur du transport. Un `SocketTransport` (TCP/HTTP-stream) reste un **ajout sans toucher `McpServer`** si une CLI l'exige. Non requis pour Claude/Codex ⇒ **hors périmètre v5**, documenté.
---
## 2. Décision 2 — Contrat conf injectée (M1) ↔ serveur écouté (M3/v5)
Bout en bout, **un seul chemin** :
```
profil.mcp = Some(McpCapability{ config: ConfigFile{".mcp.json"} | Flag{..} | Env{..}, transport })
│ (LaunchAgent, run dir isolé .ideai/run/<agent>/ ; après apply_injection, avant spawn)
apply_mcp_config écrit la DÉCLARATION RÉELLE (fin du placeholder) :
{
"mcpServers": {
"idea": {
"command": "<chemin absolu de l'exe IdeA>", ← std::env::current_exe()
"args": ["mcp-server",
"--endpoint", "<loopback du projet>", ← fourni par ensure_mcp_server
"--project", "<project id>",
"--requester","<agent id>"] ← identité (§1.4)
}
}
}
│ (la CLI lit .mcp.json à l'initialize)
la CLI SPAWN <exe IdeA> mcp-server --endpoint … --project … --requester …
│ (pont stdio↔loopback)
le pont se connecte au LOOPBACK DU PROJET (créé par ensure_mcp_server)
│ (handshake : project id + requester id)
McpServerHandle ACCEPTE ⇒ tâche McpServer::serve(conn) [McpServer tient l'OrchestratorService du projet]
│ tools/call → map_tool_call → OrchestratorCommand → OrchestratorService::dispatch(&project, cmd)
réponse inline (idea_ask_agent ⇒ outcome.reply) renvoyée verbatim à la CLI
```
**Invariant de cohérence à tester** : le **chemin de l'endpoint** et l'**exe** écrits par `apply_mcp_config` sont **exactement** ceux que `ensure_mcp_server` met à l'écoute pour ce projet. Source de vérité **unique** : une fonction (app-tauri) calcule l'endpoint d'un `ProjectId` ; M1 (qui écrit la conf) et M3/v5 (qui écoute) l'appellent tous deux. **Pas** de chaîne dupliquée.
**Le `transport` du profil** reste surfacé dans la déclaration pour la voie socket future, mais en stdio-spawn il est **implicite** (la CLI spawn = stdio). `McpConfigStrategy` inchangé : `ConfigFile` écrit le fichier, `Flag`/`Env` passent le **chemin de la conf** (run dir) — sémantique déjà en place, on ne fait que **remplir** la déclaration de vrai contenu.
---
## 3. Décision 3 — Fix du registre de session (lot PRIORITAIRE, indépendant)
### 3.1 Invariant correct (verrouillé)
**1 session vivante par agent** (un agent = singleton : un `.md`, une conversation, un run dir, une mémoire). On **ne** modélise **pas** une identité par cellule. La **cellule est une vue** rebindable (§17.6). `session_for_agent` est donc **déterministe par construction** — à condition que l'invariant soit **réellement enforced** et que les **deux registres** soient considérés. Aujourd'hui il y a **trois fuites** :
### 3.2 Trou A — le garde de `LaunchAgent` ne lève jamais `AgentAlreadyRunning`
`LaunchAgent::execute` (lifecycle.rs ~877-920) : si l'agent a déjà une session vivante (PTY **ou** structurée) et qu'un `node_id` est demandé, il **rebind silencieusement** ; sans node, il **rend la session existante** (idempotent). C'est juste pour **réattacher une vue**, mais cela **masque** un vrai second lancement (deux cellules distinctes voulant le **lancer** chacune). `AppError::AgentAlreadyRunning` est **défini mais jamais levé**.
**Décision** : distinguer **réattache de vue** (légitime, rebind) de **second lancement** (à refuser). Le signal de discrimination existe déjà dans le flux : un `LaunchAgentInput` issu d'une **réattache** (la cellule sait que l'agent tournait : `agent_was_running`/`conversation_id` présents) vs un **lancement neuf**. Le garde lève `AgentAlreadyRunning { agent_id, node_id_hôte }` quand un lancement **neuf** vise un agent **déjà vivant sur un autre node**, et **rebind** seulement quand le `node_id` demandé **est** le node hôte (ou réattache explicite). L'orchestrateur `spawn_agent` (`Visible{node_id}`) suit la **même** règle.
### 3.3 Trou B — `list_live_agents` est aveugle aux sessions structurées
`commands.rs::list_live_agents``state.terminal_sessions.live_agents()` **seulement**. Un agent **chat** (structuré) vivant n'apparaît **pas** ⇒ l'UI ne le désactive pas dans le dropdown ⇒ on peut tenter de le relancer ailleurs.
**Décision** : la commande lit l'**agrégateur** `LiveSessions::live_agents()` (PTY **+** structuré), déjà présent dans le registre. Un seul point de vérité de liveness pour l'UI.
### 3.4 Trou C — `layouts.json` à doublons (deux feuilles, même `agent`)
Les layouts persistés **contiennent déjà** des feuilles en double sur le même `agent` id (constaté). À l'ouverture, **ne pas auto-lancer la 2ᵉ** ; **réconcilier** : une seule feuille reste « hôte vivant », les autres sont des vues mortes (pas de relance, pas de bannière fraîche). C'est précisément ce qui causait le **symptôme** (« une cellule reset au retour d'onglet »).
**Décision** : étape de **réconciliation à l'ouverture du projet** (app-tauri, jumelle de `SnapshotRunningAgents`) : pour chaque agent apparaissant sur N feuilles, **garder une** hôte, **dé-flagger** `agent_was_running`/`conversation_id` sur les autres feuilles dupliquées. La reprise (B, §15.2) ne relance alors qu'**une** session/agent.
### 3.5 Pourquoi indépendant du bind
Aucun de ces trois trous ne touche MCP/transport : ils vivent dans `LaunchAgent`, `list_live_agents`, et l'ouverture de projet. Le fix **stabilise le routage de `ask`** (qui s'appuie sur `session_for_agent`) **avant** d'ouvrir la vanne MCP ⇒ on évite de débugger un `ask` mal routé **et** un transport neuf en même temps. **⇒ Lot R0, livré en premier.**
---
## 4. Décision 4 — Robustesse `ask` (sérialisation FIFO par agent)
Sémantique cible (au-dessus de l'existant) :
| Situation | Comportement |
|---|---|
| Cible inconnue | `AppError::NotFound` (fait) |
| Cible morte | lancement structuré (background) puis envoi (fait) |
| Cible vivante en **PTY brut** | `AppError::Invalid` explicite, jamais d'ACK trompeur (fait) |
| Cible vivante structurée, **libre** | rendez-vous direct `send_blocking` (fait) |
| Cible vivante structurée, **déjà en tour** (autre `ask`) | **file FIFO par agent** : le 2ᵉ `ask` attend son tour, **pas** d'entrelacement (NOUVEAU) |
| Timeout 300 s | cible **vivante**, erreur typée `Timeout`, retry possible (fait) |
**Le seul vrai manque = la concurrence.** Deux `ask` simultanés sur la même cible appelleraient `send_blocking` **en parallèle** sur la **même** `AgentSession` ⇒ deux tours entrelacés sur un moteur qui pilote **une** conversation. Inacceptable (cf. bug accents = writes non sérialisés).
**Décision** : **sérialiser les tours par agent** dans `OrchestratorService::ask_agent` via un **verrou par `agent_id`** (un `Mutex`/sémaphore d'unité, registre `HashMap<AgentId, Arc<Mutex<()>>>` détenu par le service, ou porté par l'entrée de `StructuredSessions`). Un `ask` **acquiert** le verrou de l'agent avant `send_blocking`, le **relâche** après le `Final`/timeout. Les `ask` concurrents forment une **file naturelle** (ordre d'acquisition). Le timeout s'applique **au tour** (pas à l'attente du verrou) — ou un timeout global borné l'attente totale (décision : timeout **par tour** ; l'attente en file ne consomme pas le budget, mais un plafond d'attente évite l'inanition). Cohérent avec « 1 conversation déterministe/agent ».
**Erreurs typées remontées aux deux portes** : MCP ⇒ `tool_result_text(.., isError=true)` (déjà) ; fichier ⇒ `*.response.json` avec le champ d'erreur (déjà via `OrchestratorOutcome`/watcher). Le verrou n'ajoute pas de nouveau type d'erreur ; un timeout d'attente en file ⇒ `Timeout` (même type).
---
## 5. Décision 5 — Frontières hexagonales (où vit `serve`)
- **`McpServer::serve` = adapter infra**, piloté **par connexion** : `McpServerHandle` (app-tauri) **accepte** sur l'endpoint loopback du projet et, **par pair connecté** (= un agent), **spawn une tâche** `serve(conn)`. Le serveur reste **sans état de connexion** au-delà de l'identité du pair (portée par le contexte de connexion, §1.4).
- **`McpServerHandle` évolue** : aujourd'hui il **parke** (`let _server; stop_rx.recv()`). Demain il **ouvre l'endpoint** + boucle d'`accept` ; à l'arrêt, ferme l'endpoint (les ponts enfants meurent avec leur CLI). **Toujours** non-bloquant pour open/close projet (l'`accept` est async, parqué sur l'absence de pair).
- **Le sous-process `mcp-server`** vit dans `app-tauri` (route `main.rs`), mais ne connaît que **stdio + loopback + JSON brut** : **zéro** `OrchestratorService`, zéro use case. Frontière nette.
- **Aucune fuite applicative dans l'infra** : `OrchestratorService::dispatch` est appelé **à l'identique** par les trois portes (fichier, MCP, UI). Le verrou par agent (§4) est une **règle applicative** ⇒ il vit dans `OrchestratorService` (ou le registre `StructuredSessions`), **pas** dans l'adapter MCP.
---
## 6. Découpage en LOTS testables (méthode §3)
> Ordre global : **R0 (fix registre) d'abord** — indépendant, débloque la robustesse de `ask`. Puis **bind transport M5x**. Front (M5-UI) optionnel en fin.
### Bloc R — Fix registre session (PRIORITAIRE, indépendant du transport)
| Lot | Côté | Périmètre | Critères de test |
|---|---|---|---|
| **R0a** | back (application) | Garde `LaunchAgent` : lever `AgentAlreadyRunning{agent_id,node_hôte}` pour un **lancement neuf** ciblant un agent déjà vivant sur **un autre node** (PTY **ou** structuré) ; **rebind** seulement si node demandé = node hôte / réattache. Même règle dans `OrchestratorService::spawn_agent`. | lancement neuf d'un agent vivant ailleurs ⇒ `AgentAlreadyRunning` (code `AGENT_ALREADY_RUNNING`) ; réattache même node ⇒ rebind sans respawn ; idempotence background inchangée ; `ask` d'une cible morte ⇒ lancement OK (pas de faux positif). |
| **R0b** | back (app-tauri) | `list_live_agents` lit `LiveSessions::live_agents()` (PTY **+** structuré). | un agent **chat** vivant apparaît dans la liste ; un agent PTY aussi ; aucun doublon ; sans session ⇒ vide. |
| **R0c** | back (app-tauri) | Réconciliation à l'**ouverture projet** : pour un agent sur N feuilles, garder **une** hôte, dé-flagger `agent_was_running`/`conversation_id` sur les autres. | layout à doublons ⇒ après ouverture, **une seule** feuille « était en cours » pour l'agent ; layout sans doublon **inchangé** ; persistance idempotente (2ᵉ ouverture = no-op). |
| **R0d** | front | Dropdown agent du leaf : désactiver/"déjà placé" via la liste R0b (PTY+chat) ; gérer le retour `AGENT_ALREADY_RUNNING` (aller-à / déplacer). | Vitest : agent vivant ailleurs ⇒ option désactivée + action « aller à la cellule » ; erreur backend mappée à un message clair. |
### Bloc M5 — Bind transport S-MCP (stdio-spawn)
| Lot | Côté | Périmètre | Critères de test |
|---|---|---|---|
| **M5a** | back (app-tauri) | **Endpoint loopback par projet** : fonction unique `mcp_endpoint(project_id)` (Unix socket / Windows named pipe) ; `ensure_mcp_server` l'ouvre, `stop_orchestrator_watch` le ferme. | endpoint créé à l'open, supprimé au close ; idempotent (1/projet) ; chemin déterministe par `ProjectId` ; pas de collision inter-projets. |
| **M5b** | back (app-tauri) | **Sous-commande `mcp-server`** dans `main.rs` (avant init Tauri) : pont `StdioTransport(stdin,stdout)` ↔ loopback (`--endpoint`), handshake (`--project`,`--requester`). | `argv mcp-server` ⇒ mode headless, ne lance pas la webview ; relaie une requête JSON-RPC ligne→loopback→réponse→stdout ; EOF stdin ⇒ sortie propre ; endpoint absent ⇒ erreur non-zéro, jamais de hang. |
| **M5c** | back (infra/app-tauri) | **`McpServerHandle` accepte** sur l'endpoint et **spawn `McpServer::serve(conn)` par pair** ; identité du pair (requester) portée au contexte de connexion ⇒ `OrchestratorRequestProcessed.requester_id` = agent réel (fin du `"mcp"` figé). | une connexion ⇒ une tâche serve ; `initialize`/`tools/list`/`tools/call` OK de bout en bout via le loopback (test d'intégration local, hors réseau) ; `requester_id` = l'agent ; déconnexion d'un pair n'affecte pas les autres ; arrêt ferme l'endpoint + termine les serve. |
| **M5d** | back (application) | **`apply_mcp_config` écrit la déclaration RÉELLE** (fin placeholder) : `command = current_exe`, `args = ["mcp-server","--endpoint",mcp_endpoint(project),"--project",id,"--requester",agent]`. `ConfigFile` non-clobbering ; `Flag`/`Env` portent le chemin de conf. **Source d'endpoint partagée avec M5a.** | `mcp=None` ⇒ aucune écriture (inchangé) ; `ConfigFile``.mcp.json` pointe l'exe + endpoint **exacts** du projet ; endpoint identique à `ensure_mcp_server` (test de cohérence M1↔M3) ; non-clobbering. |
| **M5e** | back (intégration) | **Smoke end-to-end loopback** (sans CLI réelle) : un faux pont écrit `tools/call idea_list_agents`/`idea_ask_agent` sur le loopback d'un projet et reçoit la réponse inline du `dispatch` réel. | `idea_list_agents` ⇒ JSON des agents ; `idea_ask_agent` vers une cible structurée ⇒ `reply` inline ; cible PTY ⇒ erreur typée ; JSON-RPC malformé ⇒ erreur, jamais panic ; **hors réseau**. |
### Bloc A — Robustesse `ask` (concurrence)
| Lot | Côté | Périmètre | Critères de test |
|---|---|---|---|
| **A0** | back (application) | **Sérialisation FIFO par agent** dans `ask_agent` : verrou par `agent_id` autour de `send_blocking` ; timeout **par tour** ; plafond d'attente en file. | deux `ask` concurrents sur la même cible ⇒ tours **séquentiels** (pas d'entrelacement), ordre FIFO ; un `ask` sur agent A et un sur agent B ⇒ **parallèles** ; timeout d'un tour laisse la cible vivante et **libère** la file ; plafond d'attente ⇒ `Timeout` typé. |
### Optionnel
| Lot | Côté | Périmètre | Critères de test |
|---|---|---|---|
| **M5-UI** | front | Badge **source** (`mcp`/`file`) + **requester réel** sur une délégation (réutilise `OrchestratorRequestProcessed`). | badge source correct ; requester = agent réel (plus « mcp ») ; pas de régression sans event. |
**Ordre recommandé** : **R0a→R0b→R0c→R0d** (stabilise le routage), puis **A0** (concurrence `ask`, ne dépend pas du transport), puis **M5a→M5b→M5c→M5d→M5e** (bind), puis **M5-UI**. R0 et A0 sont livrables **sans** toucher MCP ; M5 ne doit partir qu'**après** R0 (sinon on débugge `ask` mal routé + transport neuf ensemble).
---
## 7. Conformité hexagonale & SOLID (rappel)
- **Règle de dépendance** : JSON-RPC, stdio, loopback socket/pipe, sous-process `mcp-server` = **infra/app-tauri exclusivement**. `domain`/`application` ignorent MCP et le transport. Le verrou FIFO par agent est une **règle applicative** (vit dans `OrchestratorService`/registre), pas dans l'adapter.
- **S** : `McpServer` = traduire JSON-RPC → `dispatch` ; le **pont** = relayer des octets ; `McpServerHandle` = cycle de vie + `accept` ; le verrou = sérialiser les tours. Aucune responsabilité fourre-tout.
- **O** : un `SocketTransport` futur = un impl de `Transport` en plus, **zéro** modif de `McpServer`. Une CLI MCP de plus = un profil avec `mcp = Some(..)`, zéro code.
- **L** : les trois portes (fichier, MCP, UI) sont substituables derrière `OrchestratorService::dispatch`**un** comportement applicatif.
- **I** : `McpServerHandle` ne voit que « accepter + servir » ; `OrchestratorService` ne voit que `dispatch` + registres ; l'UI ne voit que `LiveSessions::live_agents`.

View File

@ -0,0 +1,35 @@
# Protocole — Validation réelle de la conversation inter-agents via IdeA
> À exécuter depuis la **nouvelle AppImage** (build 2026-06-10 18:23, contenant R0+A0+M5,
> commits `37e7274` / `6ca519b` / `cf89b3b`). L'ancienne image (testée avant) ne contenait
> pas ce code et a renvoyé l'erreur attendue « agent Ask pas pilotable en mode structuré ».
## But
Prouver en conditions réelles qu'un agent (Main/Claude) peut **demander** une tâche à un autre
agent (Ask/Codex) **via IdeA** et **recevoir sa réponse inline** — pas seulement par tests à fakes.
## Pré-requis pour que le `ask` aboutisse
- La cible (**Ask**) doit être pilotée en **mode structuré** (profil Codex avec adapter structuré).
Le menu de sélection d'agent ne propose normalement que des profils structurés (Claude/Codex).
- Ask **ne doit pas** déjà tourner comme **terminal brut** (PTY) dans une cellule : sinon
`ask_agent` refuse (invariant « 1 session/agent », cible PTY brut = pas de canal de réponse).
- Le plus simple : **laisser Ask éteint** et laisser `ask_agent` le **lancer lui-même** en
structuré (sémantique : cible morte ⇒ launch structuré background ⇒ send ⇒ Final).
## Procédure (protocole fichier, identique au test précédent)
1. Déposer `.ideai/requests/main/<nom>.json` :
```json
{ "type": "agent.message", "requestedBy": "Main", "targetAgent": "Ask",
"task": "Petite recherche, pas de code : résume en 3 points ce que fait le module
crates/infrastructure/src/orchestrator/mcp/ et liste les outils idea_*." }
```
2. Attendre l'apparition de `.ideai/requests/main/<nom>.json.response.json`.
## Succès attendu
`{ "ok": true, "action": "agent.message", "reply": "<réponse de Codex>" }` — le champ **`reply`**
porte le contenu produit par Ask. (Échec précédent = `ok:false` + erreur PTY brut.)
## Voie native MCP (bonus)
Le bind transport S-MCP (M5) est livré : un agent lancé avec un profil MCP voit les outils
`idea_*` (dont `idea_ask_agent`) et le résultat revient inline. À valider quand un profil MCP
est branché sur Claude/Codex. Voir `.ideai/briefs/orchestration-v5-transport-bind-cadrage.md`.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
---
upTo: a6169530-fc73-4057-91c6-900da4a611fb
objective: [Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu reçois ce message, réponds via idea_reply avec : (1) "DevFrontend OK — pont inter-agent fonctionnel", (2) le résultat de 144 ÷ 12, e
---
**Objectif :** [Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu reçois ce message, réponds via idea_reply avec : (1) "DevFrontend OK — pont inter-agent fonctionnel", (2) le résultat de 144 ÷ 12, e
- **Prompt:** [Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu reçois ce message, réponds via idea_reply avec : (1) "DevFrontend OK — pont inter-agent fonctionnel", (2) le résultat de 144 ÷ 12, et (3) l'heure courante. Aucune autre action requise.
- **Prompt:** Test de conversation inter-agent. Calcule 123 × 4 et réponds-moi simplement avec le résultat via idea_reply.
- **Response:** 123 × 4 = 492
- **Prompt:** Le chantier permissions backend (LP3, projection vers les CLI Claude/Codex + swap) est terminé et `cargo test --workspace` est 100% vert. Mais `npx vitest run` côté frontend a 4 échecs, signalés comme pré-existants liés au chantier UI permissions en cours (gateway permissions ajoutée, PermissionsPanel monté dans ProjectsView). Diagnostique-les et corrige-les pour remettre le frontend au vert. Lance d'abord : `cd frontend && npx vitest run` pour voir l'état réel. Les 4 échecs rapportés : 1. `mock.test.ts > exposes all thirteen gateways` — le mock expose désormais une gateway de plus (permissions ⇒ 14). Mets à jour le test ET vérifie que le mock (`frontend/src/adapters/mock/index.ts`) expose bien une gateway permissions cohérente avec le port réel (`TauriPermissionGateway`). Le titre du test (« thirteen ») et le compte attendu doivent refléter le vrai nombre de gateways. 2. `LayoutGrid.chat.test.tsx` (2 échecs) — routage cellule terminal/chat. 3. `agentAlreadyRunning.test.tsx` (1 échec) — notice R0d. POUR CHAQUE échec : déterminer si c'est (a) un test à mettre à jour parce que le comportement/contrat a légitimement changé avec le feature permissions, ou (b) une vraie régression du code à corriger. Explique ton verdict pour chacun. Ne « fais pas passer » un test en masquant un vrai bug — si 2 et 3 ne sont pas liés aux permissions et révèlent une régression réelle, dis-le clairement plutôt que de les bricoler. Vérifie aussi qu'il n'y a pas d'incohérence entre le port permissions (`frontend/src/ports/index.ts`), l'adapter Tauri (`frontend/src/adapters/permission.ts`), le mock, et les types domaine (`frontend/src/domain/index.ts`) — les 4 doivent être alignés. Quand c'est fini, réponds via idea_reply : verdict par échec (test à jour vs régression corrigée), ce que tu as changé, et la SORTIE RÉELLE de `npx vitest run` (doit être 100% vert). Si un échec est une vraie régression hors de ton périmètre permissions, signale-le précisément.
- **Prompt:** [Test de communication inter-agent] Ping. Réponds simplement via idea_reply avec « pong DevFrontend OK » pour confirmer que le canal fonctionne.
- **Response:** pong DevFrontend OK
- **Prompt:** Tâche : nettoyage/correction de bugs d'affichage UI (frontend React/TS). Project root : /home/anthony/Documents/Projects/IdeA. Aucune nouvelle feature, uniquement du fix CSS/layout. Voici 3 problèmes constatés visuellement (captures non transmissibles, je les décris) : PROBLÈME 1 — Barre de navigation horizontale (top bar listant : Projects, Context, Agents, Templates, Skills, Permissions, …). - Cette barre n'est PAS scrollable et n'est PAS correctement mise à l'échelle. Quand il y a trop d'entrées, les derniers items débordent et sont coupés (on voit « Skills » puis « P… » tronqué, « Permissions » coupé). - Attendu : la barre doit gérer le débordement proprement — soit scroll horizontal (overflow-x:auto avec masquage de scrollbar propre), soit wrap/scale correct. Les entrées ne doivent jamais être croppées. Vérifier flex-shrink/min-width des items. PROBLÈME 2 — Panneau « Agents » (liste des agents : Main, Architect, DevBackend, DevFrontend, QA, TestConversation, …). - Les items de la liste s'overlappent (chevauchement vertical) et sont croppés. Le nom de l'agent, le profil (« Claude Code »), l'id de run (longue chaîne en bleu qui wrap mal sur plusieurs lignes), la dropdown de profil et les boutons Stop/Delete/Launch se chevauchent et débordent du panneau. - Attendu : chaque carte d'agent doit avoir une hauteur qui s'adapte à son contenu (pas de height fixe qui cause l'overlap), les éléments alignés proprement, l'id long tronqué avec ellipsis ou wrap contrôlé, et la dropdown + boutons ne doivent pas déborder/chevaucher le texte voisin. Vérifier les position absolute éventuels et les gaps flex/grid. PROBLÈME 3 — Formulaire de création d'agent (champs : Name, Template, Profile, bouton Create). - Les champs du formulaire s'overlappent les uns sur les autres (Name / Template / Profile trop serrés/superposés). - BUG fonctionnel : quand on sélectionne une valeur dans les dropdowns (Template / Profile), la sélection NE s'affiche PAS dans le champ de la droplist (le label sélectionné ne se reflète pas visuellement). À investiguer : binding value/onChange du composant select, ou affichage du label sélectionné si c'est un dropdown custom. - Attendu : formulaire avec espacement correct (pas de chevauchement), et les dropdowns affichent bien la valeur choisie. Merci de : 1. Localiser les composants concernés (top nav bar, panneau Agents, formulaire création d'agent). 2. Corriger le CSS/layout et le bug de binding des dropdowns. 3. Lancer un build frontend pour t'assurer que ça compile (npm run build ou équivalent), sans casser le reste. 4. Me renvoyer via idea_reply la liste des fichiers modifiés + un résumé des corrections, pour que je puisse enchaîner sur le rebuild de l'AppImage.
- **Response:** Bugs d'affichage corrigés. Build vert (`npm run build` = tsc --noEmit + vite build OK) et tests verts (`vitest run` agents + projects : 47/47 passent). ## Fichiers modifiés (2) - `frontend/src/features/projects/ProjectsView.tsx` — la « top bar » - `frontend/src/features/agents/AgentsPanel.tsx` — panneau Agents + formulaire de création ## Diagnostic La « top bar » décrite n'est pas une nav OS mais le **strip de tabs du sidebar** (`SIDEBAR_TABS` : Projects, Context, Agents, Templates, Skills, Perms, Memory, Git) dans `ProjectsView.tsx`. Cause racine commune aux 3 problèmes : **largeur trop contrainte** dans le sidebar de 320px (`w-80`). ## Corrections **PROBLÈME 1 — barre de tabs croppée** - Avant : 8 boutons en `flex-1` sans gestion de débordement → les derniers labels coupés. - Après : les tabs sont dans un conteneur `overflow-x-auto` **scrollable horizontalement**, chaque tab passe en `shrink-0 whitespace-nowrap` (plus jamais croppé). Scrollbar masquée proprement (`[scrollbar-width:none]` + `[&::-webkit-scrollbar]:hidden`). Le bouton « collapse » («) reste épinglé à droite, hors zone de scroll. **PROBLÈME 2 — cartes d'agents qui se chevauchent** - Avant : `<li>` en `flex items-center justify-between` → le bloc nom/profil et la colonne de contrôles (dropdown profil + Sync/Stop/Launch/Delete, en `shrink-0`) se disputaient la largeur ; l'id de run long (`running in IdeA · <id>`) wrappait et débordait. - Après : carte en **`flex-col`** — bloc nom/profil/statut en haut, rangée de contrôles en dessous (`flex-wrap`, plus de `shrink-0` qui écrasait le texte). Hauteur adaptative au contenu (plus de chevauchement). L'id long est désormais **tronqué avec ellipsis** (`block w-full min-w-0 truncate`). **PROBLÈME 3 — formulaire de création (champs serrés + dropdowns « vides »)** - Avant : form en `flex flex-wrap items-end` avec 3 champs `min-w-0 flex-1` → selects écrasés à ~90px ; le texte sélectionné (« Claude Code ») était **clippé derrière la flèche native** du select, d'où l'impression que la sélection ne s'affichait pas. - Après : form en **`flex-col gap-3`**, chaque champ en pleine largeur. Le binding des `<select>` était déjà correct (`value`/`onChange` contrôlés, natifs) — c'était bien un problème de largeur, pas de binding. En pleine largeur la valeur choisie s'affiche normalement. Bouton Create passé en `self-end`. Aucune nouvelle dépendance, aucun changement de logique métier ni de contrat IPC/gateway. Les tests s'appuient sur les `aria-label` (inchangés), d'où les 47/47 verts. Tu peux enchaîner sur le rebuild de l'AppImage (rappel mémoire : c'est l'AppImage qui tourne qu'il faut reconstruire, pas seulement les sources).

View File

@ -0,0 +1,8 @@
{"id":"f473c5bc-e6b6-4efd-927d-452cb587fac2","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781517530284,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"[Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu reçois ce message, réponds via idea_reply avec : (1) \"DevFrontend OK — pont inter-agent fonctionnel\", (2) le résultat de 144 ÷ 12, et (3) l'heure courante. Aucune autre action requise."}
{"id":"63f0981d-a083-4b21-97fb-e5d681b99468","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781522526877,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Test de conversation inter-agent. Calcule 123 × 4 et réponds-moi simplement avec le résultat via idea_reply."}
{"id":"223e1427-7aad-4de2-ab3a-ac08f90e1256","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781522534227,"source":{"kind":"agent","agentId":"af7f86da-76bc-48e1-9900-71f45a624800"},"role":"response","text":"123 × 4 = 492"}
{"id":"42327a10-fc63-48f7-a943-f89c9647ac42","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781526082185,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Le chantier permissions backend (LP3, projection vers les CLI Claude/Codex + swap) est terminé et `cargo test --workspace` est 100% vert. Mais `npx vitest run` côté frontend a 4 échecs, signalés comme pré-existants liés au chantier UI permissions en cours (gateway permissions ajoutée, PermissionsPanel monté dans ProjectsView). Diagnostique-les et corrige-les pour remettre le frontend au vert.\n\nLance d'abord : `cd frontend && npx vitest run` pour voir l'état réel.\n\nLes 4 échecs rapportés :\n1. `mock.test.ts > exposes all thirteen gateways` — le mock expose désormais une gateway de plus (permissions ⇒ 14). Mets à jour le test ET vérifie que le mock (`frontend/src/adapters/mock/index.ts`) expose bien une gateway permissions cohérente avec le port réel (`TauriPermissionGateway`). Le titre du test (« thirteen ») et le compte attendu doivent refléter le vrai nombre de gateways.\n2. `LayoutGrid.chat.test.tsx` (2 échecs) — routage cellule terminal/chat.\n3. `agentAlreadyRunning.test.tsx` (1 échec) — notice R0d.\n\nPOUR CHAQUE échec : déterminer si c'est (a) un test à mettre à jour parce que le comportement/contrat a légitimement changé avec le feature permissions, ou (b) une vraie régression du code à corriger. Explique ton verdict pour chacun. Ne « fais pas passer » un test en masquant un vrai bug — si 2 et 3 ne sont pas liés aux permissions et révèlent une régression réelle, dis-le clairement plutôt que de les bricoler.\n\nVérifie aussi qu'il n'y a pas d'incohérence entre le port permissions (`frontend/src/ports/index.ts`), l'adapter Tauri (`frontend/src/adapters/permission.ts`), le mock, et les types domaine (`frontend/src/domain/index.ts`) — les 4 doivent être alignés.\n\nQuand c'est fini, réponds via idea_reply : verdict par échec (test à jour vs régression corrigée), ce que tu as changé, et la SORTIE RÉELLE de `npx vitest run` (doit être 100% vert). Si un échec est une vraie régression hors de ton périmètre permissions, signale-le précisément."}
{"id":"2a7f8f6d-3163-496a-a5b8-764a4229a40f","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781544382139,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"[Test de communication inter-agent] Ping. Réponds simplement via idea_reply avec « pong DevFrontend OK » pour confirmer que le canal fonctionne."}
{"id":"6a286e41-aa26-4bcb-8530-282bd09dde95","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781544388479,"source":{"kind":"agent","agentId":"af7f86da-76bc-48e1-9900-71f45a624800"},"role":"response","text":"pong DevFrontend OK"}
{"id":"808e5ed2-57d7-4839-906e-bd66886852b7","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781593284048,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Tâche : nettoyage/correction de bugs d'affichage UI (frontend React/TS). Project root : /home/anthony/Documents/Projects/IdeA. Aucune nouvelle feature, uniquement du fix CSS/layout. Voici 3 problèmes constatés visuellement (captures non transmissibles, je les décris) :\n\nPROBLÈME 1 — Barre de navigation horizontale (top bar listant : Projects, Context, Agents, Templates, Skills, Permissions, …).\n- Cette barre n'est PAS scrollable et n'est PAS correctement mise à l'échelle. Quand il y a trop d'entrées, les derniers items débordent et sont coupés (on voit « Skills » puis « P… » tronqué, « Permissions » coupé).\n- Attendu : la barre doit gérer le débordement proprement — soit scroll horizontal (overflow-x:auto avec masquage de scrollbar propre), soit wrap/scale correct. Les entrées ne doivent jamais être croppées. Vérifier flex-shrink/min-width des items.\n\nPROBLÈME 2 — Panneau « Agents » (liste des agents : Main, Architect, DevBackend, DevFrontend, QA, TestConversation, …).\n- Les items de la liste s'overlappent (chevauchement vertical) et sont croppés. Le nom de l'agent, le profil (« Claude Code »), l'id de run (longue chaîne en bleu qui wrap mal sur plusieurs lignes), la dropdown de profil et les boutons Stop/Delete/Launch se chevauchent et débordent du panneau.\n- Attendu : chaque carte d'agent doit avoir une hauteur qui s'adapte à son contenu (pas de height fixe qui cause l'overlap), les éléments alignés proprement, l'id long tronqué avec ellipsis ou wrap contrôlé, et la dropdown + boutons ne doivent pas déborder/chevaucher le texte voisin. Vérifier les position absolute éventuels et les gaps flex/grid.\n\nPROBLÈME 3 — Formulaire de création d'agent (champs : Name, Template, Profile, bouton Create).\n- Les champs du formulaire s'overlappent les uns sur les autres (Name / Template / Profile trop serrés/superposés).\n- BUG fonctionnel : quand on sélectionne une valeur dans les dropdowns (Template / Profile), la sélection NE s'affiche PAS dans le champ de la droplist (le label sélectionné ne se reflète pas visuellement). À investiguer : binding value/onChange du composant select, ou affichage du label sélectionné si c'est un dropdown custom.\n- Attendu : formulaire avec espacement correct (pas de chevauchement), et les dropdowns affichent bien la valeur choisie.\n\nMerci de :\n1. Localiser les composants concernés (top nav bar, panneau Agents, formulaire création d'agent).\n2. Corriger le CSS/layout et le bug de binding des dropdowns.\n3. Lancer un build frontend pour t'assurer que ça compile (npm run build ou équivalent), sans casser le reste.\n4. Me renvoyer via idea_reply la liste des fichiers modifiés + un résumé des corrections, pour que je puisse enchaîner sur le rebuild de l'AppImage."}
{"id":"a6169530-fc73-4057-91c6-900da4a611fb","conversation":"09b15ec3-ce2f-0af2-2903-ef699df9de41","atMs":1781593486944,"source":{"kind":"agent","agentId":"af7f86da-76bc-48e1-9900-71f45a624800"},"role":"response","text":"Bugs d'affichage corrigés. Build vert (`npm run build` = tsc --noEmit + vite build OK) et tests verts (`vitest run` agents + projects : 47/47 passent).\n\n## Fichiers modifiés (2)\n- `frontend/src/features/projects/ProjectsView.tsx` — la « top bar »\n- `frontend/src/features/agents/AgentsPanel.tsx` — panneau Agents + formulaire de création\n\n## Diagnostic\nLa « top bar » décrite n'est pas une nav OS mais le **strip de tabs du sidebar** (`SIDEBAR_TABS` : Projects, Context, Agents, Templates, Skills, Perms, Memory, Git) dans `ProjectsView.tsx`. Cause racine commune aux 3 problèmes : **largeur trop contrainte** dans le sidebar de 320px (`w-80`).\n\n## Corrections\n\n**PROBLÈME 1 — barre de tabs croppée**\n- Avant : 8 boutons en `flex-1` sans gestion de débordement → les derniers labels coupés.\n- Après : les tabs sont dans un conteneur `overflow-x-auto` **scrollable horizontalement**, chaque tab passe en `shrink-0 whitespace-nowrap` (plus jamais croppé). Scrollbar masquée proprement (`[scrollbar-width:none]` + `[&::-webkit-scrollbar]:hidden`). Le bouton « collapse » («) reste épinglé à droite, hors zone de scroll.\n\n**PROBLÈME 2 — cartes d'agents qui se chevauchent**\n- Avant : `<li>` en `flex items-center justify-between` → le bloc nom/profil et la colonne de contrôles (dropdown profil + Sync/Stop/Launch/Delete, en `shrink-0`) se disputaient la largeur ; l'id de run long (`running in IdeA · <id>`) wrappait et débordait.\n- Après : carte en **`flex-col`** — bloc nom/profil/statut en haut, rangée de contrôles en dessous (`flex-wrap`, plus de `shrink-0` qui écrasait le texte). Hauteur adaptative au contenu (plus de chevauchement). L'id long est désormais **tronqué avec ellipsis** (`block w-full min-w-0 truncate`).\n\n**PROBLÈME 3 — formulaire de création (champs serrés + dropdowns « vides »)**\n- Avant : form en `flex flex-wrap items-end` avec 3 champs `min-w-0 flex-1` → selects écrasés à ~90px ; le texte sélectionné (« Claude Code ») était **clippé derrière la flèche native** du select, d'où l'impression que la sélection ne s'affichait pas.\n- Après : form en **`flex-col gap-3`**, chaque champ en pleine largeur. Le binding des `<select>` était déjà correct (`value`/`onChange` contrôlés, natifs) — c'était bien un problème de largeur, pas de binding. En pleine largeur la valeur choisie s'affiche normalement. Bouton Create passé en `self-end`.\n\nAucune nouvelle dépendance, aucun changement de logique métier ni de contrat IPC/gateway. Les tests s'appuient sur les `aria-label` (inchangés), d'où les 47/47 verts.\n\nTu peux enchaîner sur le rebuild de l'AppImage (rappel mémoire : c'est l'AppImage qui tourne qu'il faut reconstruire, pas seulement les sources)."}

View File

@ -0,0 +1,13 @@
---
upTo: 190cd7b8-23ad-42cb-8873-a17ddddff71d
objective: Petit test de communication inter-agents. Calcule 17 × 23 et donne-moi uniquement le résultat numérique. Réponds-moi impérativement via l'outil idea_reply (pas en texte libre).
---
**Objectif :** Petit test de communication inter-agents. Calcule 17 × 23 et donne-moi uniquement le résultat numérique. Réponds-moi impérativement via l'outil idea_reply (pas en texte libre).
- **Prompt:** Petit test de communication inter-agents. Calcule 17 × 23 et donne-moi uniquement le résultat numérique. Réponds-moi impérativement via l'outil idea_reply (pas en texte libre).
- **Response:** 391
- **Prompt:** Test de communication inter-agents. Calcule 17 × 23 et réponds-moi UNIQUEMENT le résultat numérique, impérativement via l'outil idea_reply (jamais en texte libre).
- **Prompt:** Test de conversation inter-agent. Peux-tu calculer (347 × 28) + 1569 et me retourner uniquement le résultat numérique avec le détail du calcul ? Réponds via idea_reply.
- **Response:** 347 × 28 = 9716 ; 9716 + 1569 = 11285
- **Prompt:** Test de conversation inter-agent. Calcule 78 × 9 et réponds-moi simplement avec le résultat via idea_reply.
- **Response:** 702

View File

@ -0,0 +1,7 @@
{"id":"3cbaced3-dd78-4f48-b168-f09a26d83444","conversation":"3512c746-2a8d-06b1-17e4-cfee1fe78c19","atMs":1781384306910,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Petit test de communication inter-agents. Calcule 17 × 23 et donne-moi uniquement le résultat numérique. Réponds-moi impérativement via l'outil idea_reply (pas en texte libre)."}
{"id":"2dd9de9a-d59d-493e-bf07-ffccf6addeb1","conversation":"3512c746-2a8d-06b1-17e4-cfee1fe78c19","atMs":1781386172145,"source":{"kind":"agent","agentId":"93dc1f5f-921e-44a2-a7e7-5173d87c1a58"},"role":"response","text":"391"}
{"id":"34916afd-1054-4257-805f-9c5b3ed5f7f3","conversation":"3512c746-2a8d-06b1-17e4-cfee1fe78c19","atMs":1781386172204,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Test de communication inter-agents. Calcule 17 × 23 et réponds-moi UNIQUEMENT le résultat numérique, impérativement via l'outil idea_reply (jamais en texte libre)."}
{"id":"6e037034-c65a-4eca-b494-06420c5eed06","conversation":"3512c746-2a8d-06b1-17e4-cfee1fe78c19","atMs":1781386464926,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Test de conversation inter-agent. Peux-tu calculer (347 × 28) + 1569 et me retourner uniquement le résultat numérique avec le détail du calcul ? Réponds via idea_reply."}
{"id":"f82caa94-daba-4171-aa1f-8f69f71671c4","conversation":"3512c746-2a8d-06b1-17e4-cfee1fe78c19","atMs":1781386489560,"source":{"kind":"agent","agentId":"93dc1f5f-921e-44a2-a7e7-5173d87c1a58"},"role":"response","text":"347 × 28 = 9716 ; 9716 + 1569 = 11285"}
{"id":"6187c292-4d9a-4c07-9381-732486ab6d03","conversation":"3512c746-2a8d-06b1-17e4-cfee1fe78c19","atMs":1781522784017,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Test de conversation inter-agent. Calcule 78 × 9 et réponds-moi simplement avec le résultat via idea_reply."}
{"id":"190cd7b8-23ad-42cb-8873-a17ddddff71d","conversation":"3512c746-2a8d-06b1-17e4-cfee1fe78c19","atMs":1781522789866,"source":{"kind":"agent","agentId":"93dc1f5f-921e-44a2-a7e7-5173d87c1a58"},"role":"response","text":"702"}

View File

@ -0,0 +1,9 @@
---
upTo: 88f405da-f9c9-44bb-96c9-7e1b0e5af557
objective: Bonjour TestConversation. Ceci est un test de communication inter-agents via IdeA. Peux-tu confirmer que tu me reçois et me dire combien font 17 + 25 ?
---
**Objectif :** Bonjour TestConversation. Ceci est un test de communication inter-agents via IdeA. Peux-tu confirmer que tu me reçois et me dire combien font 17 + 25 ?
- **Prompt:** Bonjour TestConversation. Ceci est un test de communication inter-agents via IdeA. Peux-tu confirmer que tu me reçois et me dire combien font 17 + 25 ?
- **Prompt:** Test de communication inter-agent. Merci de confirmer que tu reçois bien ce message en répondant via idea_reply avec : (1) un court message de confirmation, (2) la date/heure si tu y as accès, (3) le nom de ton AI Profile/contexte tel que tu le perçois. Ceci sert uniquement à valider le canal de communication entre agents IdeA.
- **Response:** (1) Confirmation : message bien reçu, le canal de communication inter-agent IdeA fonctionne correctement. (2) Date : 2026-06-13 (je n'ai pas d'heure horodatée plus précise dans mon contexte). (3) AI Profile/contexte perçu : Claude Code (modèle Opus 4.8, claude-opus-4-8), opérant sur le project root /home/anthony/Documents/Projects/IdeA via un run dir isolé. Canal validé. ✅

View File

@ -0,0 +1,3 @@
{"id":"20dd8bdc-f858-46a6-9c8a-6afde591a6bd","conversation":"6ffc1f69-77a5-0da1-1965-ef26d1df72f5","atMs":1781367769591,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Bonjour TestConversation. Ceci est un test de communication inter-agents via IdeA. Peux-tu confirmer que tu me reçois et me dire combien font 17 + 25 ?"}
{"id":"0b8fcc51-c692-4928-9d90-3823857dad5c","conversation":"6ffc1f69-77a5-0da1-1965-ef26d1df72f5","atMs":1781368607174,"source":{"kind":"agent","agentId":"a6ced819-b893-4213-b003-9e9dc79b9641"},"role":"prompt","text":"Test de communication inter-agent. Merci de confirmer que tu reçois bien ce message en répondant via idea_reply avec : (1) un court message de confirmation, (2) la date/heure si tu y as accès, (3) le nom de ton AI Profile/contexte tel que tu le perçois. Ceci sert uniquement à valider le canal de communication entre agents IdeA."}
{"id":"88f405da-f9c9-44bb-96c9-7e1b0e5af557","conversation":"6ffc1f69-77a5-0da1-1965-ef26d1df72f5","atMs":1781368615582,"source":{"kind":"agent","agentId":"c932c770-cf36-4fb2-a966-71bb1644e4b4"},"role":"response","text":"(1) Confirmation : message bien reçu, le canal de communication inter-agent IdeA fonctionne correctement. (2) Date : 2026-06-13 (je n'ai pas d'heure horodatée plus précise dans mon contexte). (3) AI Profile/contexte perçu : Claude Code (modèle Opus 4.8, claude-opus-4-8), opérant sur le project root /home/anthony/Documents/Projects/IdeA via un run dir isolé. Canal validé. ✅"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
---
upTo: 419c60a5-2aca-4711-ad83-0a185bb3214e
objective: Ping de test inter-agent depuis DevBackend — réponds avec un court PONG et l'heure que tu vois.
---
**Objectif :** Ping de test inter-agent depuis DevBackend — réponds avec un court PONG et l'heure que tu vois.
- **Prompt:** Ping de test inter-agent depuis DevBackend — réponds avec un court PONG et l'heure que tu vois.

View File

@ -0,0 +1 @@
{"id":"419c60a5-2aca-4711-ad83-0a185bb3214e","conversation":"dd35eeb0-2329-0dfa-3255-df7dba90a0aa","atMs":1781427081894,"source":{"kind":"agent","agentId":"73c853d1-c0fd-463b-ad17-1d24fefa371f"},"role":"prompt","text":"Ping de test inter-agent depuis DevBackend — réponds avec un court PONG et l'heure que tu vois."}

45
.ideai/layouts.json Normal file
View File

@ -0,0 +1,45 @@
{
"version": 1,
"activeId": "dd38b8ed-7199-48a6-a63f-1d1462ca6a34",
"layouts": [
{
"id": "dd38b8ed-7199-48a6-a63f-1d1462ca6a34",
"name": "Default",
"kind": "terminal",
"tree": {
"root": {
"type": "split",
"node": {
"id": "2ea41e76-262d-4dc3-a674-348ceac9b543",
"direction": "row",
"children": [
{
"node": {
"type": "leaf",
"node": {
"id": "14d20dcb-f243-4381-a130-2d4d3d224f42",
"session": "4e60d6c2-6be6-499c-a296-577ce1e5ec62",
"agent": "a6ced819-b893-4213-b003-9e9dc79b9641",
"agentWasRunning": true
}
},
"weight": 1.0
},
{
"node": {
"type": "leaf",
"node": {
"id": "71f5cc81-9bef-4b2b-b7dc-2e0a5a4eafcb",
"session": "bb08a08e-71f3-4ab2-842c-71a8da5c74b4",
"agent": "cd0b4cf1-1bef-4fae-ade5-f0a6b49bbaf5"
}
},
"weight": 1.0
}
]
}
}
}
}
]
}

7
.ideai/memory/MEMORY.md Normal file
View File

@ -0,0 +1,7 @@
# Memory Index
- [agent-context-memory-and-profile-handoff](agent-context-memory-and-profile-handoff.md) — Decisions sur l'injection de contexte, la memoire durable, l'etat live et le handoff de profil entre agents IA.
- [idea-product-directives-main-handoff](idea-product-directives-main-handoff.md) — Directives produit consolidees pour guider Main sur la robustesse, la persistance, le handoff cross-profile et la sobriete UX.
- [remaining-work-idea-agent-control-ide](remaining-work-idea-agent-control-ide.md) — Etat des lieux des acquis et des chantiers restants pour aligner IdeA avec la cible d'IDE de controle d'agents IA.
- [mcp-bridge-and-delegation-runtime-notes](mcp-bridge-and-delegation-runtime-notes.md) — Pieges runtime du pont MCP/delegation et regle de rebuild de l'AppImage (binaire qui tourne = AppImage, pas les sources).
- [permissions-sandbox-system-state](permissions-sandbox-system-state.md) — Systeme de permissions/sandbox complet (Landlock sur PTY + structure) et le risque residuel $HOME/resume du chemin structure.

View File

@ -0,0 +1,106 @@
---
name: agent-context-memory-and-profile-handoff
description: Decisions sur l'injection de contexte, la memoire durable, l'etat live et le handoff de profil entre agents IA.
metadata:
type: project
---
# Agent Context, Memory, and Profile Handoff
## Resume
This note captures the current product direction for IdeA around agent context injection, project memory, live state, and profile handoff between AI providers.
See also:
- `idea-product-directives-main-handoff` for product priorities and UX constraints.
- `remaining-work-idea-agent-control-ide` for the current implementation status and remaining work.
## Context Injection
- Agent context must be injected by IdeA at launch time; the model should not be expected to discover `AGENTS.md` or `CLAUDE.md` by itself.
- The existing `contextInjection` architecture is the right mechanism, especially `conventionFile` for providers that support a conventional file in the run directory.
- The current implementation is strongest for `conventionFile`; `flag`, `stdin`, and `env` do not yet receive the same fully-composed IdeA context.
- The `flag` strategy appears fragile with the isolated run directory model because the relative path passed to the CLI may not resolve from the run directory.
## Shared Project Context
- `.ideai/CONTEXT.md` is intended as shared project context.
- It is not created automatically; it only exists if something writes it.
- It should carry active project constraints and contribution rules.
- Example content for `CONTEXT.md`: architectural constraints, workflow rules, and operating conventions that every agent must apply immediately.
## Durable Memory
- `.ideai/memory/` is intended as durable, project-scoped memory shared by all agents of the same project.
- It is not created automatically; it only exists once at least one memory note is saved.
- The durable memory is a knowledge base, not a live activity log.
- It should contain stabilised knowledge such as:
- architecture decisions
- feature summaries to implement later
- user preferences that persist across sessions
- important project facts and references
- Durable memory should stay curated and low-noise.
## Live State Versus Durable Memory
- Durable memory should not be used as a shared real-time state feed for all agents.
- IdeA should distinguish between:
- global context (`.ideai/CONTEXT.md`)
- durable memory (`.ideai/memory/`)
- live operational state (separate store)
- handoff summaries between sessions or profiles
- Real-time work tracking, who-is-doing-what, and transient status should live in a dedicated state mechanism, not in durable memory.
## Memory Consumption by Agents
- The current launcher reads shared project context from `.ideai/CONTEXT.md` if present.
- It also recalls project memory via `MemoryRecall` and injects a `# Memoire projet` section into the convention file.
- This injection currently happens only for `conventionFile` profiles.
- The recalled memory is shared at the project level, but each agent may receive a different subset depending on its persona and recall query.
- Memory recall is computed at launch time and written into the generated context file.
- There is currently no automatic live refresh when `.ideai/memory/` changes during an active session.
## Recommendation for Live Memory Refresh
- Automatic memory refresh could be useful, but it should be explicit and controlled.
- If IdeA wants agents to benefit from memory changes while they are active, it should regenerate their effective context when needed instead of treating durable memory as a constantly streaming log.
- For PTY agents, no automatic reread exists today.
- For structured Claude/Codex sessions, each turn relaunches the CLI, but the generated convention file is not automatically rewritten when durable memory changes.
## Profile Handoff and Session Continuity
- A direct native session transfer from Claude to Codex is not the right mental model.
- The correct model is continuity of work state, not native provider-session continuity.
- IdeA should persist:
- a canonical conversation log
- a cumulative handoff summary
- the agent state
- the provider conversation id when useful
- On provider swap, IdeA should launch the new profile with:
- regenerated project context
- regenerated durable memory recall
- the current agent persona
- a handoff summary plus recent transcript
- The handoff summary should not be created only at the moment of swap.
- IdeA should maintain summaries incrementally or at checkpoints so that a swap is still possible when the current provider is near a token or session limit.
## Agent Ability To Write Durable Memory
- Agents should be allowed to promote important knowledge into durable memory.
- This should not rely on the agent guessing the capability.
- IdeA should expose the capability explicitly through tools or commands and should inject a clear rule explaining when an agent may save durable knowledge.
- This write ability should be constrained to stable, high-value information, not transient state.
## Practical Classification Rule
- Put immediate project rules and operating constraints in `.ideai/CONTEXT.md`.
- Put stable, reusable project knowledge in `.ideai/memory/`.
- Put current activity and coordination state in a separate live-state mechanism.
- Put cross-session or cross-profile recovery material in a handoff/session layer.
## Current Product Direction
- Keep `CONTEXT.md` for project rules.
- Keep `.ideai/memory/` for curated durable knowledge.
- Introduce a separate live-state mechanism if agents must stay aligned on in-progress work.
- Introduce persistent conversation logs and incremental handoff summaries to support profile swaps such as Claude to Codex.

View File

@ -0,0 +1,206 @@
---
name: idea-product-directives-main-handoff
description: Directives produit consolidees pour guider Main sur la robustesse, la persistance, le handoff cross-profile et la sobriete UX.
metadata:
type: project
---
# IdeA Product Directives For Main
## Resume
Cette note consolide les arbitrages produit explicites donnes par l'utilisateur pour aider `Main` a poursuivre le projet sans ambiguite.
See also:
- `agent-context-memory-and-profile-handoff` for the structural model of context, durable memory, live state, and handoff.
- `remaining-work-idea-agent-control-ide` for the current implementation status and remaining work.
Elle ne remplace pas les notes techniques existantes. Elle sert de reference prioritaire sur:
- la robustesse attendue,
- la persistance et la reprise,
- le handoff entre profils IA,
- la memoire projet partagee,
- le live state projet,
- la sobriete UX.
## Priorite Absolue
La priorite produit numero un est la robustesse.
Ordre de priorite impose:
1. robustesse et solidite avant tout
2. persistance et reprise
3. handoff cross-profile
4. live state projet
5. reste des features et du polish
Regle de pilotage:
- un systeme incomplet mais solide vaut mieux qu'un systeme riche mais fragile
- `Main` doit privilegier les architectures et comportements qui reduisent les crashes, les incoherences d'etat et les flows difficiles a reprendre
## Reprise Et Persistance
Quand IdeA redemarre, l'objectif n'est pas seulement de rouvrir une UI ou de restaurer des handles techniques.
La cible produit est:
- qu'un agent sache compactement sur quoi il travaillait
- qu'IdeA fournisse ce materiel de reprise automatiquement
- que la reprise soit exploitable meme si la conversation visible precedente n'est pas restauree a l'identique
Le bon modele est:
- un log canonique IdeA comme source durable
- un resume/handoff genere par IdeA comme couche compacte de reprise
Le resume/handoff n'est pas un confort secondaire. Il fait partie du comportement normal du produit.
## Handoff Cross-Profile
La cible ideale est double:
- reprendre correctement le travail
- donner si possible une impression de continuite presque sans rupture
Mais en cas de compromis, la priorite doit etre:
- fidelite operationnelle du travail repris
- avant la parfaite illusion de continuite terminale ou conversationnelle
Autrement dit:
- si un agent passe de Claude a Codex, IdeA doit d'abord garantir que Codex puisse reprendre le plus fidelement possible le travail utile
- l'absence de restauration parfaite de l'ancien terminal est acceptable si le handoff reste bon
## Perimetre Profils
Le perimetre de reference immediat est:
- Claude
- Codex
Toute fonctionnalite importante doit etre faisable pour ces deux profils.
Directive associée:
- reduire au maximum les dependances a des commandes, flags ou comportements specifiques a un profil
- construire un noyau le plus generique possible tout en restant concretement compatible Claude/Codex
- les autres profils pourront etre ajoutes plus tard si possible, mais ne doivent pas detourner le coeur du chantier actuel
## Memoire Projet Partagee
La memoire projet partagee doit rester petite, stable et utile.
Elle ne doit pas devenir un gros bloc qui siphonne les tokens de l'utilisateur a chaque requete.
Ce qu'un agent peut ecrire automatiquement dans la memoire partagee si c'est stable et utile:
- decisions durables d'architecture ou d'organisation
- preferences utilisateur durables
- regles de workflow durables
- references importantes a conserver
- resumes de handoff utiles a la reprise inter-session ou inter-profil
Ce qu'un agent ne doit pas y ecrire automatiquement:
- conversations brutes
- journaux detailles de travail
- etats temporaires
- files d'attente
- coordination temps reel
- essais/erreurs locaux
- hypotheses non stabilisees
- contenu redondant ou reconstructible ailleurs
Principe de fond:
- memoire durable = savoir stable
- log canonique = historique
- handoff = reprise compacte
- live state = coordination vivante
Ces couches doivent rester separees.
## Live State Projet
Le live state projet partage doit exister comme mecanisme interne d'IdeA.
Contraintes produit:
- il doit rester invisible pour l'utilisateur
- il doit survivre au redemarrage d'IdeA
Il ne doit pas se transformer en UI verbeuse ni en mecanisme demandant une intervention explicite de l'utilisateur.
## UX Et Philosophie Produit
IdeA doit etre tres facile d'utilisation.
Objectif UX:
- plug and play
- pas de sensation de parametrage impose
- pas de surcharge de tuto au premier lancement
- pas d'impression que le produit force des comportements internes a l'utilisateur
Ligne directrice souhaitee:
- esprit "maniere Linux"
- comportement simple et utile par defaut
- pas de contrainte tant qu'il n'y a pas un vrai besoin
- suggestion discrete seulement si IdeA detecte qu'une aide ou une optimisation devient utile
Le precedent du compactage de contexte est considere comme la bonne direction:
- pas de compactage impose d'emblee
- une popup proposee seulement si IdeA sent un besoin
## Transparence Des Mecanismes Internes
Les mecanismes suivants doivent rester quasi invisibles pour l'utilisateur:
- delegations inter-agents
- FIFO
- handoffs
Ils peuvent devenir visibles en debug ou quand le produit a une bonne raison UX de les exposer, mais ils ne doivent pas etre ressentis comme une charge cognitive normale d'utilisation.
## Frontiere Avec Le Chantier Inter-Agents De Main
Les choix fins touchant la communication entre agents ne doivent pas etre recadres ici si `Main` est deja en train de les traiter.
Cette note ne doit donc pas etre lue comme une specification d'implementation inter-agents detaillee.
Elle fixe seulement les invariants produit suivants:
- robustesse avant richesse fonctionnelle
- reprise automatique par IdeA
- log canonique + handoff genere par IdeA
- support de reference pour Claude et Codex
- memoire durable compacte et curatee
- live state interne et persistant
- UX discrete, simple et peu intrusive
## Directive Finale Pour Main
Si un arbitrage technique oppose:
- elegance theorique
- ou livraison rapide
contre:
- robustesse
- reprise fiable
- sobriete UX
alors `Main` doit privilegier:
- robustesse
- reprise fiable
- sobriete UX
avant le reste.

View File

@ -0,0 +1,51 @@
---
name: mcp-bridge-and-delegation-runtime-notes
description: Pieges runtime du pont MCP et de la delegation inter-agents IdeA, et la regle de rebuild de l'AppImage.
metadata:
type: project
---
# Pont MCP & delegation : pieges runtime
Deux bugs trouves le 2026-06-13 en testant la conversation inter-agents (`idea_ask_agent` vers TestConversation), tous deux corriges. Notes utiles pour ne pas reperdre du temps :
## Le binaire qui tourne = AppImage installee, pas les sources
L'IdeA en cours d'utilisation est `/home/anthony/Documents/IdeA_0.1.0_amd64.AppImage`. **Ce meme binaire sert a la fois de serveur orchestrateur (il tient `OrchestratorService`/`InputMediator` et compose les evenements) ET de binaire-pont** (`<exe> mcp-server …` declare dans chaque `.ideai/run/<id>/.mcp.json`). Donc **tout correctif cote serveur ou cote pont n'est actif dans l'app que apres rebuild + reinstall de l'AppImage et relance d'IdeA**. Un binaire `target/debug` fraichement compile ne valide que le pont (qui parle au serveur via le socket) ; il ne valide pas la composition cote serveur.
**Comment appliquer :** apres une correction backend, rebuild AppImage (`npm --prefix frontend run build` puis, depuis `crates/app-tauri/`, `../../frontend/node_modules/.bin/tauri build --bundles appimage`), remplacer l'AppImage, relancer IdeA, puis retester. **Exclure NSIS** (`--bundles appimage`) sur Linux. **Piege FUSE (2026-06-14)** : l'etape finale `linuxdeploy` echoue avec `failed to run linuxdeploy` (linuxdeploy est une AppImage qui se monte via FUSE). Workaround obligatoire : prefixer `APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1`. Le compile Rust reussit avant ce point ; seul le bundling casse, et l'`AppDir` est genere mais pas le `.AppImage`. L'artefact final = `target/release/bundle/appimage/IdeA_0.1.0_amd64.AppImage`.
## Env AppImage pollue le shell
La session shell herite des variables de l'AppImage montee (`APPDIR`, `LD_LIBRARY_PATH`, `PYTHONHOME` -> `/tmp/.mount_IdeA_*`). Consequences : `python3` casse (`No module named 'encodings'`) et lancer un binaire app-tauri fraichement compile tente de booter WebKit et crash. **Workaround :** lancer avec un env propre (`env -i PATH=/usr/bin:/bin HOME=$HOME XDG_RUNTIME_DIR=/run/user/1000 ...`), utiliser `jq` plutot que python.
## Bug 1 — pont MCP en lockstep (corrige)
`mcp_bridge.rs::relay` lisait 1 ligne client -> attendait 1 reponse loopback, en boucle. MCP n'est pas 1:1 : `notifications/initialized` n'a pas de reponse => deadlock juste apres `initialize`, et `tools/list` n'etait jamais relaye => les outils `idea_*` ne se chargeaient jamais (`claude mcp list` affichait quand meme "Connected" car `initialize` repond). Corrige en relay **full-duplex** (deux pompes concurrentes) + drain borne (`DRAIN_GRACE`) a la fermeture stdin. Symptome cote utilisateur : 3 jours de "outils MCP pas charges".
## Bug 2 — prefixe de delegation perdu (corrige)
Le signal `[IdeA · tâche de <demandeur> · ticket <id>]` qui dit a l'agent cible "reponds via `idea_reply`, jamais en texte" n'etait plus compose nulle part : supprime du backend (`service.rs` C3 §5.1) lors du passage de l'ecriture PTY au frontend, mais le frontend (`useWritePortal.ts`) ecrit `head.text` verbatim et ne l'ajoutait pas. La cible recevait la tache brute, repondait en texte => `idea_ask_agent` timeout. Corrige en composant le prefixe dans `infrastructure/src/input/mod.rs` (`delegation_preamble`) a l'emission de `DomainEvent::DelegationReady` ; la tache brute reste dans le `Ticket`/historique. Rappel : la correlation `idea_reply` marche par ticket OU par tete de FIFO (fallback), donc le ticket echo est recommande mais pas strictement requis.
## Bug 3 — cold-launch race : 1er tour perdu (corrige 2026-06-14)
Deleguer a un agent **froid** (pas encore lance) via `idea_ask_agent` echouait silencieusement : terminal cible vide, ask bloque jusqu'au timeout 300s ; un agent **chaud** (deja a son prompt, lance manuellement) marchait. Cause : avec `with_structured` non cable sur l'orchestrateur (regression assumee `aa2f67a`), tous les `ask` passent par le chemin PTY `ensure_live_pty` (`application/src/orchestrator/service.rs`). Un agent jamais vu etait initialise `Idle` (`BusyTracker::start_turn` -> `or_insert(Idle)`, `infrastructure/src/input/mod.rs`), donc le 1er `enqueue` publiait `DelegationReady` **immediatement** et ecrivait la tache dans le PTY avant que le CLI ait affiche son prompt -> tache perdue. Le prompt-ready watcher (lot C5) ne gardait que les tours suivants. Fix : `ensure_live_pty` renvoie un flag `cold_launch` ; si cold ET `prompt_ready_pattern` non vide, l'orchestrateur appelle `mark_starting(agent)` -> la `DelegationReady` du 1er tour est **differee** puis publiee par le watcher a l'apparition du prompt. Sans pattern ou agent chaud -> livraison immediate (zero regression). Touche `domain/src/input.rs` (port `mark_starting`), `infrastructure/src/input/mod.rs`, `service.rs`.
## Bug 4 — le fix cold-launch ne s'armait jamais en prod (corrige 2026-06-15)
Le « fix Bug 3 corrige 2026-06-14 » etait trop optimiste : il ne s'arme que si `gate_cold_start = cold_launch && prompt_ready_pattern non vide` (`service.rs`). Or le profil **Claude Code** (`664cc20c`, partage par TOUS les agents) dans `~/.local/share/app.idea.ide/profiles.json` n'a **aucun** `prompt_ready_pattern`. Donc `mark_starting` n'etait jamais appele -> `enqueue` publiait `DelegationReady` immediatement -> tache ecrite dans le PTY avant le prompt de `claude` -> 1er tour perdu -> `idea_ask_agent` vers un agent **froid** bloque jusqu'au timeout (un agent **chaud** marche). Le Bug 3 n'etait valide que par des tests unitaires qui injectent un pattern a la main : le gap d'integration (profil sans pattern) n'avait pas ete vu.
**Fix (option B, signal MCP, sans sniff de prompt TUI) :** on gate le cold-launch des qu'un MCP est configure sur le profil (`gate_cold_start = cold_launch && (pattern non vide || profil.mcp.is_some())`), et on **libere** le tour differe quand le pont MCP de l'agent froid se connecte (= son CLI est up + outils charges) : nouveau port `InputMediator::release_cold_start(agent)` (drain du `deferred`, **sans** `mark_idle` car c'est un signal de DEMARRAGE, idempotent et OR-safe avec `prompt_ready`), `McpServer` fire un `ready_sink: Fn(&str)` sur `initialize` avec le `requester` du handshake, et la composition root (`state.rs::ensure_mcp_server`) parse le requester en `AgentId` -> `OrchestratorService::release_agent_cold_start`. Touche `domain/src/input.rs`, `infrastructure/src/input/mod.rs`, `infrastructure/src/orchestrator/mcp/server.rs`, `application/src/orchestrator/service.rs`, `app-tauri/src/state.rs`. Tests unitaires verts ; **validation live = rebuild AppImage + relance IdeA** (cf. section binaire qui tourne). Filet OR : si un jour un profil porte un `prompt_ready_pattern`, les deux signaux coexistent.
**Methodo :** ne PAS deleguer la reparation du systeme inter-agents via le systeme inter-agents (casse) — utiliser les subagents natifs (outil Agent).
## Bug 5 — la boucle `serve` du serveur MCP est en lockstep, un `ask` sans reponse wedge TOUTE la connexion (corrige 2026-06-15)
Symptome : 1er `idea_ask_agent` vers un agent qui repond -> OK ; puis `ask` vers un agent qui ne rappelle JAMAIS `idea_reply` (ex. QA lance mais qui ne repond pas) -> ensuite **tout** appel suivant du MEME demandeur (meme `idea_list_agents`, sans rendezvous) se bloque. Cote utilisateur : "DevBackend ne marche plus" alors qu'il repondait 11s avant — en realite c'est la connexion du DEMANDEUR (Main) qui est morte, pas la cible. Annuler l'appel cote client ne deparke rien.
Cause : `infrastructure/src/orchestrator/mcp/server.rs::serve` lisait 1 requete puis **awaitait `handle_raw` en entier** avant de relire. Pour `idea_ask_agent`, `handle_raw -> dispatch -> service.dispatch` attend le `idea_reply` de la cible : pendant cette attente la boucle ne relit plus rien -> pipeline de la connexion fige. C'est l'analogue COTE SERVEUR du Bug 1 (lockstep) qui n'avait ete corrige que cote pont (`mcp_bridge.rs`). NB : la couche application bornait deja le rendezvous (`service.rs::ask_agent` via `tokio::time::timeout`), donc l'attente n'etait pas infinie — le vrai coupable etait bien la serialisation de `serve`, pas l'absence de timeout.
Fix : `serve` reecrite full-duplex non bloquante — `tokio::select!` entre `transport.recv()` et un canal `mpsc::unbounded::<Option<Vec<u8>>>` ; chaque message entrant traite dans une tache `tokio::spawn` qui possede un clone cheap `self.for_requester(self.requester.clone())` ('static) + un clone du `tx` ; reponses (`Some`) ou notifications (`None`) renvoyees par le canal et ecrites par la meme boucle ; arret gracieux via compteur `in_flight` (on draine les taches en vol apres EOF). Les reponses MCP portent l'`id` -> ordre indifferent. Le trait `Transport` (`&mut self` recv/send) et les signatures `serve`/`serve_as` restent intacts. + filet de securite : timeout serveur **isole au seul `idea_ask_agent`** (`ASK_RENDEZVOUS_TIMEOUT` 24h, finie ; setter `with_ask_rendezvous_timeout` `#[doc(hidden)]` pour les tests). Tests : `infrastructure/tests/mcp_server.rs` -> `pending_ask_does_not_wedge_the_connection_concurrent_call_still_answered` (anti-wedge, echoue sur l'ancien code) + `ask_agent_rendezvous_times_out_with_a_jsonrpc_error`. Verts : `cargo test -p infrastructure` (20 mcp_server), `cargo test -p app-tauri --lib mcp_e2e_loopback_tests` (6). **Validation live = rebuild AppImage + relance IdeA** (la connexion wedgee ne se deparke pas de l'interieur : il FAUT relancer IdeA). AppImage du 2026-06-15 10:58 contient le fix ; backup `~/Documents/IdeA_0.1.0_amd64.AppImage.old-prewedgefix`.
Reste a investiguer (separe, non bloquant) : pourquoi QA lance ne repond pas du tout a une delegation (son `claude` n'appelle pas `idea_reply`) — impossible a creuser tant que la connexion du demandeur est wedgee ; le fix Bug 5 permet desormais de le diagnostiquer sans tout figer.
## Bug 6 — la tache n'est JAMAIS ecrite dans le PTY d'un agent delegue en arriere-plan (corrige 2026-06-15)
C'EST la cause racine du « reste a investiguer » du Bug 5. Symptome : un agent lance a la main (cellule visible, ex. DevBackend) repond ; un agent **froid auto-lance par `idea_ask_agent`** (ex. QA) ne repond JAMAIS → `ask` bloque jusqu'au timeout (24h, `ASK_AGENT_TIMEOUT`). Reproduction sure et non bloquante : `idea_stop_agent` puis `idea_launch_agent(task=…, visibility=background)`, et observer le dossier de session `claude` de la cible (`~/.claude/projects/<run-dir>/*.jsonl`) : **aucune nouvelle session** en 28 s = la tache n'a jamais ete soumise (le pont MCP de la cible, lui, se connecte bien).
Cause : depuis ARCHITECTURE §20, l'**ecriture physique du PTV est faite par le write-portal FRONTEND** (`useWritePortal`), qui n'est monte **que pour une cellule de layout (leaf) visible**. Or `ensure_live_pty` cold-lance la cible en **background avec `node_id: None`** (`service.rs`) → aucune cellule montee → `useWritePortal` n'existe pas → l'event `DelegationReady` n'a **aucun consommateur** → tache perdue. Les fix Bug 3/4 (differer/liberer la `DelegationReady`) ne pouvaient donc jamais marcher pour un agent background : ils publient un event que personne n'ecoute cote UI.
Fix (option « writer PTY backend ») : le mediateur ecrit lui-meme la tache dans le PTY **quand aucune cellule frontend n'est attachee**. Registre `front_owned` dans `MediatedInbox`, alimente par le front via `bindHandle`/`unbindHandle` → commande Tauri `set_front_attached``OrchestratorService::set_agent_front_attached``InputMediator::set_front_attached`. Point de livraison unique = `BusyTracker::publish_deferred` (chemin chaud immediat ET drains froids `prompt_ready`/`release_cold_start`), qui passe par un **HeadlessSink** optionnel cable par `MediatedInbox::with_pty`/`with_events` : agent dans `front_owned``Some(d)` (publie l'event, le front ecrit, inchange) ; sinon ⇒ ecrit texte puis (apres `submit_delay_ms`, defaut 60ms, anti-paste-detection) la `submit_sequence` dans le handle PTY bound, sur un `std::thread` detache (le watcher prompt-ready tourne sur un std::thread, PAS tokio — ne pas utiliser `tokio::spawn`). Touche `domain/src/input.rs` (port `set_front_attached`), `infrastructure/src/input/mod.rs` (HeadlessSink + front_owned + handles en `Arc<Mutex>`), `application/src/orchestrator/service.rs`, `app-tauri/src/{dto,commands,lib}.rs`, `frontend/src/{ports,adapters/input,adapters/mock,features/terminals/useWritePortal}`. Tests verts : `cargo test -p infrastructure` (input 34, dont `headless_agent_without_front_cell_is_written_by_the_backend` + `front_attached_agent_is_delivered_via_event_not_backend_write`), app-tauri lib 42, useWritePortal 9, tsc. **Validation live = rebuild AppImage + relance IdeA** (build 2026-06-15 11:42 ; backup `~/Documents/IdeA_0.1.0_amd64.AppImage.old-prefrontwriterfix`).
NB diagnostic : `~/.claude/projects/<encoded-run-dir>/*.jsonl` = transcript de session `claude` de l'agent ; pas de nouveau fichier apres une delegation = tache jamais soumise. `ss -xp | grep idea-mcp` = ponts MCP connectes cote serveur.
## Bug 7 — une delegation interrompue/annulee laisse la cible `Busy` a vie (corrige 2026-06-15, sources ; pas encore en AppImage)
Symptome : un agent qui repondait (ex. DevFrontend a « 123x4=492 », QA pendant LP3) ne repond plus du tout aux delegations suivantes ; `idea_ask_agent` bloque jusqu'au timeout. DevBackend, lui, continue de marcher. Diagnostic ecarte 2 fausses pistes : (a) PAS le socket MCP — les 6 ponts sont ESTAB (`ss -xp | grep idea-mcp`), pont vivant ; (b) PAS « lance a la main vs par IdeA » — DevBackend est aussi auto-lance et marche. Le vrai discriminant : **une delegation vers cet agent a-t-elle ete interrompue/annulee cote demandeur ?** DevFrontend coince par l'interruption d'une tache LP3 ; QA coince par un ping diag rejete ; DevBackend jamais annule => OK. Confirmation cote transcript : la tache figure dans le `log.jsonl` de la conversation (donc enqueue cote serveur a eu lieu) mais PAS dans le transcript `claude` de la cible (`~/.claude/projects/<run-dir>/*.jsonl`) => jamais ecrite dans son PTY.
Cause (lue dans `application/src/orchestrator/service.rs`, chemins `ask` PTY ~l.847-911 ET `ask_structured` ~l.927-1011) : l'agent passe `Idle→Busy` des `input.enqueue` (~l.978). Il ne redevient `Idle` que sur la branche **succes** (`input.mark_idle`, ~l.1001). Les branches **erreur/annulation/timeout** (~l.901-910 et ~l.1004-1009) appellent `mailbox.cancel_head` mais **jamais `mark_idle`**. Pire : quand le demandeur interrompt l'appel, le futur `ask_agent` est **dropped** => AUCUNE branche du `select!` ne s'execute => l'agent reste `Busy` pour toujours. Une cible `Busy` met les delegations suivantes en file derriere un tour fantome, jamais livrees au PTY. L'etat `Busy` vit en memoire dans le process serveur et **ne se deparke pas de l'interieur** : deblocage immediat = **relancer IdeA** (comme le wedge du Bug 5).
Fix (corrige cote sources, `service.rs`) : garde RAII `BusyTurnGuard` (Arc clones de `InputMediator` + mailbox, `agent_id`, `ticket_id`, flag `armed`) cree juste apres l'enqueue dans les DEUX chemins (`ask` PTY et `ask_structured`) ; son `Drop` (si arme) appelle `cancel_head(agent,ticket)` puis `mark_idle(agent)` ; `disarm()` sur la branche succes (le `mark_idle` propre existant reste, pas de double cancel). Les `cancel_head` redondants des branches erreur/`_cancelled`/`_elapsed` ont ete retires au profit du garde (`cancel_head` est positionnel/idempotent). Indispensable que ce soit un garde et pas un `mark_idle` dans les branches : le cas reel est un futur DROPPED, aucune branche du `select!` ne s'execute. Tests verts : `cargo test --workspace` 80 suites 0 echec, dont `dropped_ask_future_frees_busy_target`, `second_delegation_delivered_after_dropped_ask`, `cancelled_ask_marks_target_idle` (tests/orchestrator_service.rs) + 2 tests du garde dans le `mod tests` de service.rs.
VERDICT `sweep_stalled` (infra/input/mod.rs) : **purement advisory** — bascule `Alive→Stalled` et emet `AgentLivenessChanged` mais **n'appelle JAMAIS `mark_idle`** (par conception : « la FIFO et le tour continuent »). Ce n'est donc PAS un filet pour ce bug ; le garde RAII est la seule correction. Non recable (changement de semantique hors perimetre).
**Pas encore actif live : rebuild AppImage requis** (`npm --prefix frontend run build` puis depuis `crates/app-tauri/` `APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1 ../../frontend/node_modules/.bin/tauri build --bundles appimage`, remplacer l'AppImage, relancer IdeA). Le relancement d'IdeA debloque aussi DevFrontend/QA actuellement coinces `Busy` (etat en memoire). Methodo (rappel Bug 4) : NE PAS reparer le systeme inter-agent via `idea_ask_agent` (casse) — utiliser les subagents natifs (outil Agent).
See also [[remaining-work-idea-agent-control-ide]], [[agent-context-memory-and-profile-handoff]].

View File

@ -0,0 +1,26 @@
---
name: permissions-sandbox-system-state
description: Etat du systeme de permissions/sandbox IdeA (domaine pur -> projection advisory -> enforcement OS Landlock sur PTY ET structure) et l'unique risque residuel.
metadata:
type: project
---
# Systeme de permissions / sandbox IdeA
Chantier "permissions des agents" complet bout-en-bout au 2026-06-16, sur la branche `wip/p8c-checkpoint-before-codex`. Lots committes : LP4-0 (`b05d04a`), LP4-1/LP4-2 (`17ca65e`), LP4-3 (`7e01ac6`), LP4-4 (`6236cd7`).
## Acquis (tout vert, ~80 suites)
- **Domaine pur** (`crates/domain/src/permission.rs`, `sandbox.rs`) : modele declaratif (`Capability`, `Effect`, `Posture` Allow<Ask<Deny, deny-wins), `resolve()` (invariant produit : rien pose => `None` => CLI garde son prompting natif), `compile_sandbox_plan(eff, ctx) -> Option<SandboxPlan>`, port `SandboxEnforcer`, `render_permission_summary` (honnetete : fichiers = OS-enforced, commandes = advisory).
- **Store** : `.ideai/permissions.json` (separe d'`agents.json` : securite projet, pas identite).
- **Projection advisory LP3** : projecteurs Claude (`settings.json`) + Codex (sandbox/`config.toml`).
- **Enforcement OS LP4** : `LandlockSandbox` (Linux) / `NoopSandbox` / `default_enforcer()`. Actif sur **les deux chemins** : PTY brut (`pty/mod.rs` `spawn_command_sandboxed`) ET structure Claude/Codex JSON (`session/process.rs` `run_turn_sandboxed`).
## Technique d'enforcement (load-bearing, ne pas casser)
`enforce(plan)` tourne sur un **thread jetable AVANT le spawn**, puis le child est spawne depuis ce thread => il herite le domaine Landlock via les credentials de la tache (garanti par le noyau a travers fork/clone/execve, y compris posix_spawn). **Pas de `pre_exec`** : `infrastructure` est `#![forbid(unsafe_code)]` (intact), et le pre_exec n'aurait ete qu'une assurance de determinisme, sans valeur de securite ; allouer dans `landlock` post-fork (pre_exec) serait au contraire dangereux (deadlock malloc en process multithreade). Fail-closed : `Err` d'enforce => aucun child. Le garde-fou anti-regression est le test e2e **"ecriture hors-grant bloquee"** (present pour PTY et structure) : tant qu'il est vert, l'heritage tient.
## Risque residuel a traiter (signale par l'Architecte, NON couvert)
Les CLI structurees (claude/codex = binaires Node) ont des besoins FS ambiants lourds : `~/.claude`/`~/.codex` (credentials + cache de session pour le **resume**), `node_modules`, libs systeme, caches temp. `compile_sandbox_plan` ne fence que les classes explicitement posees et garde les lectures ouvertes — mais une policy posant un `Deny`/posture restrictive touchant `$HOME` **peut casser le resume**. Le mecanisme est valide au fake CLI (zero token), mais **avant d'activer un plan restrictif en prod sur le chemin structure**, valider e2e manuellement qu'un vrai tour claude/codex sous plan representatif garde run_dir + home de la CLI atteignables. C'est un sujet de **composition du plan** (run_dir reachability ; `SandboxContext.run_dir` existe mais n'est pas encore consomme par la traduction pure), pas du mecanisme — lot suivant si besoin.
Voir [[remaining-work-idea-agent-control-ide]] pour le reste des chantiers produit.

View File

@ -0,0 +1,273 @@
---
name: remaining-work-idea-agent-control-ide
description: Etat des lieux des acquis et des chantiers restants pour aligner IdeA avec la cible d'IDE de controle d'agents IA.
metadata:
type: project
---
# Remaining Work For IdeA Agent Control IDE
## Resume
Cette note sert de point de reprise pour l'agent `Main`. Elle distingue ce qui est deja implemente, ce qui reste a stabiliser, et ce qui reste a construire pour que IdeA corresponde pleinement a la vision "patron + employes IA" avec memoire projet partagee, contexte partage, messagerie inter-agents transparente, FIFO par agent, et persistance de reprise.
See also:
- `idea-product-directives-main-handoff` for product priorities and UX constraints.
- `agent-context-memory-and-profile-handoff` for the structural model separating context, durable memory, live state, and handoff.
Etat observe le 2026-06-11 sur le depot local:
- L'orchestration inter-agents synchrone est deja reelle cote application.
- La FIFO par agent, la conversation par paire et `idea_reply` sont deja couvertes par des tests verts.
- Le transport MCP natif par projet et le loopback sont testes verts localement.
- La reprise de conversation cote session/layout est largement presente dans le code.
- En revanche, plusieurs briques produit restent inachevees ou non consolidees de bout en bout.
## Deja Livre Ou Tres Avance
### 1. Artefacts projet dans `.ideai/`
- Les agents projet sont persistes dans `.ideai/agents.json` et `.ideai/agents/*.md`.
- Le contexte projet partage est modelise via `.ideai/CONTEXT.md`.
- La memoire projet partagee est modelisee via `.ideai/memory/*.md` + `.ideai/memory/MEMORY.md`.
- Les requetes d'orchestration fichier vivent sous `.ideai/requests/`.
Conclusion: la direction "tout ce qui releve d'IdeA pour un projet doit vivre dans `.ideai/`" est deja la bonne direction architecturale. Il reste surtout a eliminer les ecarts pratiques et a consolider l'usage reel.
### 2. Memoire projet partagee
- `FsMemoryStore` et `MemoryRecall` existent.
- Les agents peuvent recevoir un rappel de memoire projet a l'activation.
- Le frontend et les use cases CRUD memoire existent deja.
Conclusion: la memoire partagee du projet n'est plus un concept a inventer. Le travail restant est plutot sur la qualite du rappel, la curation, et l'usage continu pendant la vie des sessions.
### 3. Contexte partage et contexte agent
- Le contexte partage projet est separe du contexte agent.
- Les contextes agent sont persistes sous `.ideai/agents/*.md`.
- Le launcher injecte deja le contexte compose au demarrage.
Conclusion: la separation `contexte projet` / `contexte agent` est en place.
### 4. Messagerie inter-agents transparente
- `AgentMailbox` + `InMemoryMailbox` existent.
- `ConversationRegistry` + `InMemoryConversationRegistry` existent.
- `OrchestratorService::ask_agent` et `reply` existent.
- Les outils MCP `idea_ask_agent`, `idea_reply`, `idea_launch_agent`, `idea_list_agents`, `idea_stop_agent`, `idea_update_context`, `idea_create_skill` existent.
- Les tests applicatifs passent sur FIFO, reponse synchrone, prevention de cycle, timeout, parallélisme entre cibles differentes.
Verification locale du 2026-06-11:
- `cargo test -p application --test orchestrator_service` : OK
- `cargo test -p infrastructure --test mcp_server` : OK
- `cargo test -p app-tauri --test orchestrator_wiring` : OK
Conclusion: le coeur de la communication inter-agents n'est plus un chantier de conception. Il est deja implementé et teste.
### 5. FIFO transparente quand un agent est occupe
- La file d'entree par agent existe deja.
- La serialisation des tours vers une meme cible existe.
- Les `ask` concurrents vers des agents differents peuvent tourner en parallele.
Conclusion: l'exigence "si l'utilisateur ou un autre agent parle a un agent deja occupe, la requete part en file FIFO de maniere transparente" est deja largement satisfaite au niveau coeur applicatif.
### 6. Reprise de conversation et persistance de session
- Les cellules/layouts persistent `conversation_id` et `agent_was_running`.
- Les use cases de reprise (`ListResumableAgents`, popup de reprise, relance avec `conversation_id`) existent.
- Les sessions structurees Claude/Codex savent porter un `conversation_id`.
Conclusion: la persistance de reprise a deja une base concrete et substantielle.
## Reste A Faire En Priorite
### 1. Consolider la persistance "conversation continue" au niveau produit, pas seulement "resume technique"
Le code sait deja reprendre une conversation via `conversation_id`, mais la cible produit demande plus qu'une simple reprise technique:
- conserver une vraie continuite de conversation lisible pour l'utilisateur au redemarrage,
- permettre au nouvel agent/profil de repartir avec l'etat utile,
- rendre la reprise completement transparente dans l'UX.
Reste donc a verrouiller:
- la persistance canonique des conversations exploitable au niveau produit,
- la strategie de resume/handoff quand on change de profil IA,
- la coherence UX entre reprise de cellule, reprise de conversation, et reprise de travail.
### 2. Implementer une couche persistante de handoff / resume cross-profile
La memoire partagee existante dit explicitement qu'il faut persister:
- un canonical conversation log,
- un cumulative handoff summary,
- l'etat agent,
- les identifiants de conversation utiles par provider.
Ce point n'apparait pas comme livre de bout en bout dans le depot actuel.
Le besoin produit reste ouvert:
- si un agent passe de Claude a Codex, IdeA doit reconstituer l'etat de travail sans dependre d'une session native transferable,
- le handoff doit etre incremental, pas fabrique seulement au moment de la panne ou du swap.
### 3. Introduire un vrai live-state partage au niveau projet
La memoire durable ne doit pas servir de journal temps reel. La note memoire existante le dit deja.
Il manque encore une couche explicite de "live operational state" pour:
- qui travaille sur quoi,
- tickets/intentions en cours,
- etat d'avancement d'un agent,
- derniere delegation utile,
- elements transitoires de coordination inter-agents.
Sans cette couche, une partie de la coordination reste soit volatile, soit repoussee dans des endroits qui ne sont pas faits pour ca.
### 4. Verifier et finir l'integration MCP natif "IdeA-only" de bout en bout dans le flux reel de l'application
Les tests locaux du transport MCP passent, ce qui place cette zone en fin de chantier plutot qu'au debut.
Mais il reste a confirmer en situation reelle utilisateur:
- qu'un agent lance par IdeA voit effectivement ses outils MCP sans action manuelle,
- que les profils supportes utilisent bien cette voie par defaut,
- que le fallback fichier+prose reste coherent quand MCP n'est pas disponible,
- que l'observabilite UI des delegations et des replies est suffisamment claire.
Point important: l'architecture historique qui mentionne encore un verrou M5 ouvert est probablement en retard par rapport au worktree local. Avant de planifier le prochain lot, `Main` doit revalider la documentation d'architecture a la lumiere du code/tests actuels.
### 5. Stabiliser le registre de sessions et clarifier le modele singleton d'agent
Le produit veut "1 agent = 1 employe". Cela impose une verite unique sur:
- la session vivante de l'agent,
- sa conversation courante,
- sa cellule visible ou son execution en arriere-plan,
- son etat occupé/libre/interrompu.
Le code a deja beaucoup avance sur ce point, mais le worktree local montre encore un chantier actif autour de:
- `application/src/terminal/registry.rs`
- `application/src/orchestrator/service.rs`
- `application/src/agent/lifecycle.rs`
- `app-tauri/src/state.rs`
Conclusion: ne pas considerer le sujet comme totalement clos tant que le worktree n'est pas nettoye et que la suite de tests ciblee n'est pas executee sur l'ensemble du flux concerne.
### 6. Rendre la mise a jour de memoire/contexte vraiment automatique pendant la vie d'un agent
La cible utilisateur dit qu'il ne doit jamais demander:
- de charger une memoire,
- de charger un contexte,
- de mettre a jour la memoire,
- de mettre a jour le contexte.
Le lancement injecte deja beaucoup de choses automatiquement, mais il reste a verrouiller le comportement "pendant la vie" d'un agent:
- quand regenerer le contexte effectif,
- quand promouvoir une information stable vers la memoire durable,
- comment distinguer signal utile et bruit,
- comment eviter de compter sur des consignes manuelles a l'utilisateur.
### 7. Unifier la conversation utilisateur <-> agent et agent <-> agent dans l'UX
Le backend sait deja distinguer `User<->Agent` et `Agent<->Agent`.
Le travail restant est surtout produit/frontend:
- affichage clair des delegations et des retours,
- visualisation non confuse des conversations par paire,
- reprise lisible des threads,
- transparence totale pour l'utilisateur final.
Le diff local frontend suggere justement un remaniement en cours de la surface terminal/chat.
### 8. Consolider la restriction et l'affordance des profils supportes
Le modele actuel oriente fortement vers Claude/Codex structures, ce qui est coherent avec la fiabilite attendue.
Reste a clarifier produit:
- quels profils sont officiellement "employes IdeA" de premiere classe,
- quel fallback proposer pour les profils non structures,
- quelle UI montrer quand un profil ne supporte pas la delegation native fiable.
### 9. Persistance conversationnelle globale de l'application
La demande utilisateur mentionne explicitement qu'en relancant IdeA il faut retrouver la conversation.
La reprise par `conversation_id` et layouts existe, mais il reste a confirmer ou completer:
- la persistance lisible de l'historique conversationnel pour l'utilisateur,
- la restauration des vues au redemarrage,
- la coherence entre session technique, resume visuel et histoire de travail.
Autrement dit: "reprendre une session moteur" n'est pas encore automatiquement equivalent a "retrouver sa conversation produit" dans tous les cas.
## Chantiers Secondaires Mais Importants
### 1. Mettre la documentation d'architecture a jour
Le code local et les tests verts semblent avoir depasse certains passages de `ARCHITECTURE.md` et de briefs anciens.
Il faut une passe de synchronisation documentaire pour eviter que `Main` suive un etat obsolete, en particulier sur:
- statut reel du transport MCP,
- statut reel de la FIFO inter-agents,
- statut reel des conversations par paire,
- ce qui reste vraiment ouvert entre handoff, live-state et UX.
### 2. Curater le dossier `.ideai/`
Le principe "tout ce qui est IdeA-projet va dans `.ideai/`" est bon, mais il faudra surveiller:
- la proliferation de fichiers run/request/debug,
- ce qui est durable vs derivable,
- ce qui doit etre committe vs ignore.
### 3. Formaliser les regles de promotion memoire
Le systeme doit savoir quand enregistrer une connaissance stable sans polluer la memoire partagee.
Il manque probablement encore:
- une politique claire de promotion,
- des heuristiques/outils explicites pour les agents,
- des garde-fous contre la memoire bruit.
## Worktree Local A Prendre En Compte
Le depot local est actuellement dirty avec un chantier large non committe autour de:
- orchestration MCP / loopback / serveur Tauri,
- mailbox / conversations / session registry,
- adaptation frontend terminal/chat/layout,
- fichiers `.ideai/` du projet lui-meme.
Consequence pour `Main`:
- ne pas planifier a partir de `ARCHITECTURE.md` seulement,
- d'abord relire le diff local,
- ensuite reexecuter la suite de tests ciblee des zones touchees,
- puis seulement decider si le prochain lot est "finition", "integration UI", ou "harden/persistence".
## Ordre Recommande Pour La Suite
1. Revalider et documenter l'etat reel du chantier MCP/orchestration a partir du code courant, puis remettre `ARCHITECTURE.md` a jour.
2. Fermer proprement le sujet "1 agent = 1 session vivante coherente" en nettoyant le registre/session lifecycle encore en mouvement.
3. Concevoir puis implementer une vraie couche de live-state partage projet.
4. Concevoir puis implementer la couche persistante de handoff/canonical conversation log cross-session et cross-profile.
5. Finir l'integration UX/frontend pour que toute cette orchestration reste invisible et naturelle pour l'utilisateur final.
## Synthese Courte
Le plus gros changement de perception pour `Main` est le suivant:
- IdeA n'est plus au stade "il faut inventer la delegation inter-agents".
- IdeA est plutot au stade "le coeur de delegation existe deja; il faut maintenant le consolider, le documenter, le rendre pleinement persistant, et le rendre transparent dans l'UX".

38
.ideai/permissions.json Normal file
View File

@ -0,0 +1,38 @@
{
"version": 1,
"projectDefaults": {
"rules": [
{
"capability": "read",
"effect": "allow",
"paths": [
"**"
],
"commands": []
},
{
"capability": "write",
"effect": "allow",
"paths": [
"**"
],
"commands": []
},
{
"capability": "delete",
"effect": "allow",
"paths": [
"**"
],
"commands": []
},
{
"capability": "executeBash",
"effect": "allow",
"paths": [],
"commands": []
}
],
"fallback": "allow"
}
}

9
.ideai/project.json Normal file
View File

@ -0,0 +1,9 @@
{
"version": 1,
"id": "97b49ac2-8376-4aa3-8ea9-bf3ac81d0023",
"name": "IdeA",
"remote": {
"kind": "local"
},
"createdAt": 1780702317785
}

2163
ARCHITECTURE.md Normal file

File diff suppressed because it is too large Load Diff

205
CLAUDE.md Normal file
View File

@ -0,0 +1,205 @@
# IdeA — Contexte & Méthode de travail
> Ce document définit **mon rôle**, **la méthode de développement** et **la vision produit** du projet IdeA.
> Il fait autorité sur la façon dont le projet est piloté. Toute évolution de méthode doit être répercutée ici.
---
## 1. Mon rôle : chef d'orchestre, pas développeur
Je **n'écris pas de code moi-même**. Mon rôle est de **piloter des agents** qui réalisent le travail.
Je suis responsable de :
- Découper le travail en tâches claires et autonomes.
- Attribuer chaque tâche aux bons agents.
- Garantir que le cycle de développement/test est respecté.
- Faire respecter les principes d'architecture (SOLID, Hexagonal).
- Maintenir la cohérence globale du projet et de ce document.
- Arbitrer et valider avant toute action irréversible ou sortante.
---
## 2. Les agents
### 2.1 Agent Architecture (1 pour tout le projet)
- Garant de l'architecture globale : **Hexagonale (Ports & Adapters)** et principes **SOLID**.
- Définit les frontières (domaine / application / infrastructure), les ports, les contrats.
- Valide que chaque nouvelle feature respecte la structure avant son développement.
- Tient à jour la cartographie d'architecture et les conventions.
### 2.2 Agents de Développement
- Écrivent le code des features.
- Respectent strictement l'architecture définie par l'agent Architecture.
- Code **propre, structuré, stable**.
- Reçoivent les rapports d'erreurs des agents de test et corrigent.
### 2.3 Agents de Test
- **Chaque agent de développement est appairé avec un agent de test dédié.**
- Écrivent et exécutent les **tests unitaires** des features implémentées ou modifiées.
- Produisent un **rapport d'erreurs** clair quand un test échoue.
- Re-testent après chaque correction.
### 2.4 Agent Git (1 pour tout le projet)
- Garant du **dépôt git local** : commits de l'application, création/checkout/switch de
branches, merges et rebases. Contexte : `.ideai/agents/git.md`.
- **C'est lui qui décide** de la topologie des branches, pas moi. Je le sollicite, il tranche.
- Modèle de branches : **`main`** (release) ← **`develop`** (intégration des features
terminées) ← une branche **`feature/*`** par nouvelle feature.
- **Quand je commande une nouvelle feature** : une fois l'architecture cadrée par Architect,
je passe la main à **Git** qui décide s'il faut créer une branche, faire un checkout/switch,
ou rester en place — **avant** que le dev commence.
- **Après chaque implémentation** : je reparle à **Git** pour qu'il décide si un **merge**
doit être fait quelque part (typiquement `feature/* → develop` une fois les tests verts),
ou non.
- Périmètre **local uniquement** : aucune action sortante (`push`, publication) sans ma
validation explicite.
---
## 3. Le cycle de développement (boucle obligatoire)
Pour **chaque** feature implémentée ou modifiée :
```
1. Agent Architecture → valide le découpage et les contrats (ports/interfaces)
2. Agent Git → décide de la branche (créer feature/*, switch, ou rester)
3. Agent Développement → écrit le code
4. Agent Test → écrit les tests unitaires + les exécute
5a. Tests OK → feature validée
→ Agent Git décide d'un merge éventuel (feature/* → develop)
→ on passe à la suite
5b. Tests KO → rapport d'erreurs → retour à l'agent Développement
→ correction → retour à l'étape 4 (boucle jusqu'au vert)
```
**Règle d'or :** aucune feature n'est considérée terminée tant que ses tests ne passent pas.
Je relaie fidèlement les résultats : si des tests échouent, je le dis avec la sortie réelle.
---
## 4. Principes de code
- **SOLID** appliqué au maximum.
- **Architecture Hexagonale** (Ports & Adapters) : le domaine métier est isolé des détails techniques (UI, terminal, git, SSH, système de fichiers...).
- Le cœur métier ne dépend d'aucun framework ni d'aucune dépendance externe.
- Tests unitaires systématiques ; couverture des features critiques.
- Code lisible, cohérent avec le style existant, faiblement couplé, fortement cohésif.
---
## 5. Vision produit : IdeA
**IdeA est un IDE next-gen 100 % IA.** On n'y code pas : **on gère des IA.**
### Fonctionnalités clés
- **Multi-projets en parallèle** : un **onglet par projet**.
- **Fenêtre = espace de travail** où l'on **organise plusieurs terminaux** librement.
- **Agents par projet** : chaque projet a ses propres agents.
- **Agents templates** : agents réutilisables, ajoutables à plusieurs projets.
- **Création d'agents** : depuis zéro ou à partir d'un template.
- **Synchronisation template → agents** : option « garder l'agent à jour ».
Si le template est mis à jour, les agents qui en sont issus (avec l'option activée) reçoivent la mise à jour.
- **Contextes d'agents stockés en `.md`** (toujours).
- **Création de projet** = définition de son **project root**.
### Intégrations
- **Git** intégré.
- **Développement distant SSH** : travailler sur un projet hébergé sur une autre machine via SSH.
- **Développement WSL** : travailler sur une WSL depuis Windows.
### Plateformes & livraison
- Cible : **macOS, Linux, Windows**.
- Première phase de compilation : **Linux et Windows**.
- Livraison :
- **Windows** : `setup.exe`.
- **Linux** : **AppImage** (doit fonctionner sur les différentes distributions).
---
## 6. Stack technique (validée)
- **Shell applicatif** : **Tauri v2** (binaires légers, performants, multi-OS, AppImage + installeur `setup.exe`/NSIS Windows natifs).
- **Cœur / backend** : **Rust** — stabilité, performance, et expression idiomatique du domaine hexagonal (ports = traits, adapters = implémentations).
- **Frontend / UI** : **TypeScript + React**.
- **Terminaux** : **xterm.js** (rendu) + **portable-pty** (PTY côté Rust).
- **Git** : **libgit2** via `git2` (Rust).
- **SSH** : `russh` / `ssh2` (Rust).
- **WSL** : invocation de `wsl.exe` depuis le backend.
## 7. Layout des terminaux (exigence produit)
Disposition en **grille redimensionnable de type tableur (Excel)** :
- Splits redimensionnables horizontaux **et** verticaux.
- L'utilisateur peut **définir le nombre de colonnes dans une ligne** et **le nombre de lignes dans une colonne**, indépendamment par zone.
- Possibilité de **fusionner des cellules** (ex. fusionner deux colonnes sur une ligne), à la manière des cellules fusionnées d'un tableur.
- Chaque cellule de la grille héberge un terminal.
- → Modèle de layout récursif/imbriqué (pas une grille rigide uniforme) à concevoir par l'agent Architecture.
## 8. Stockage des contextes & liaison aux templates
- **Templates d'agents** : stockés dans l'**IDE** (dossier de données utilisateur global de l'app, hors projet).
- **Agents de projet** : leurs `.md` sont stockés dans un dossier **`.ideai/`** à la racine du project root.
*(Nom choisi pour éviter toute collision avec le `.idea` de JetBrains.)*
- **Manifeste de liaison** dans `.ideai/` (ex. `.ideai/agents.json`) qui mappe pour chaque agent de projet :
- le `.md` de l'agent,
- le template d'origine (le cas échéant),
- `synchronized: true/false`,
- la **version du template** au dernier sync (pour détecter qu'une mise à jour est disponible).
- **Synchro template → agents** : quand un template est mis à jour, les agents liés avec `synchronized: true` reçoivent la MAJ.
## 9. Moteur IA : adaptateur de CLI flexible (Port `AgentRuntime`)
Chaque IA est décrite par un **profil déclaratif** (config éditable, pas du code), implémentation d'un **Port** `AgentRuntime` côté domaine. Deux variables clés par IA :
1. **Commande de lancement** + arguments (ex. `claude`, `codex`, `gemini`, `aider`).
2. **Stratégie d'injection du contexte `.md`** :
- `conventionFile` : écrire/symlink le `.md` vers le fichier attendu par la CLI (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`…).
- `flag` : passer le chemin via un argument.
- `stdin` : piper le contenu.
- `env` : passer via variable d'environnement.
Exemple de profil :
```json
{
"id": "claude-code",
"name": "Claude Code",
"command": "claude",
"args": [],
"contextInjection": { "strategy": "conventionFile", "target": "CLAUDE.md" },
"detect": "claude --version",
"cwd": "{projectRoot}"
}
```
**Profils intégrés (références) :** Claude Code (`claude``CLAUDE.md`), OpenAI Codex CLI (`codex``AGENTS.md`), Gemini CLI (`gemini``GEMINI.md`), Aider (`aider` → args/message).
**Règles produit :**
- **Premier lancement de l'IDE** : un assistant (first-run) **demande à l'utilisateur** quels profils d'IA configurer. On ne présume rien par défaut.
- Les commandes des profils sont **pré-remplies mais éditables**.
- L'utilisateur peut **ajouter sa propre commande CLI** (profil custom) pour n'importe quelle IA.
**Lancement d'un agent :** à l'**activation de l'agent**, on ouvre une cellule terminal (PTY) avec le bon `cwd`, on injecte le contexte `.md`, et on **auto-lance** la CLI du profil.
## 10. Fenêtres & onglets
- **Par défaut : un onglet par projet** (comme les IDE classiques).
- **Drag & drop d'un onglet** hors de la fenêtre → **crée une nouvelle fenêtre OS** portant ce projet.
- **Multi-fenêtres OS supporté** ; chaque fenêtre possède un ou plusieurs onglets/projets.
## 11. Feuille de route
1. **Cadrage architecture complet d'abord** (jalon en cours) : l'agent Architecture produit la cartographie complète — domaine, ports, adapters, modules, arborescence — **avant tout code**.
2. Puis MVP incrémental selon le cycle dev/test de la section 3.
## 12. Autonomie d'exécution dans le projet
L'utilisateur m'accorde un **accès large et autonome** sur le dossier du projet : je peux lire, créer, modifier des fichiers et exécuter les commandes de développement (cargo, npm, npx, git, etc.) **sans demander confirmation à chaque fois**.
- Concrètement, ces autorisations sont matérialisées dans `.claude/settings.local.json` (mode `acceptEdits` + `Bash`/`Read`/`Edit`/`Write` autorisés), pas dans ce document — CONTEXT.md ne fait que **documenter l'intention**.
- **Garde-fous conservés** : les actions destructrices ou hors-projet restent bloquées (`sudo`, `rm -rf` sur `/`/`~`/`$HOME`, `mkfs`, `dd`, `shutdown`/`reboot`…).
- L'esprit du rôle (§1) ne change pas : je reste **chef d'orchestre**. L'autonomie porte sur l'exécution mécanique, pas sur l'arbitrage des décisions produit/archi, ni sur les **actions sortantes** (push, publication) qui restent soumises à validation explicite.
---
*Dernière mise à jour : 2026-06-05*

187
CONTEXT.md Normal file
View File

@ -0,0 +1,187 @@
# IdeA — Contexte & Méthode de travail
> Ce document définit **mon rôle**, **la méthode de développement** et **la vision produit** du projet IdeA.
> Il fait autorité sur la façon dont le projet est piloté. Toute évolution de méthode doit être répercutée ici.
---
## 1. Mon rôle : chef d'orchestre, pas développeur
Je **n'écris pas de code moi-même**. Mon rôle est de **piloter des agents** qui réalisent le travail.
Je suis responsable de :
- Découper le travail en tâches claires et autonomes.
- Attribuer chaque tâche aux bons agents.
- Garantir que le cycle de développement/test est respecté.
- Faire respecter les principes d'architecture (SOLID, Hexagonal).
- Maintenir la cohérence globale du projet et de ce document.
- Arbitrer et valider avant toute action irréversible ou sortante.
---
## 2. Les agents
### 2.1 Agent Architecture (1 pour tout le projet)
- Garant de l'architecture globale : **Hexagonale (Ports & Adapters)** et principes **SOLID**.
- Définit les frontières (domaine / application / infrastructure), les ports, les contrats.
- Valide que chaque nouvelle feature respecte la structure avant son développement.
- Tient à jour la cartographie d'architecture et les conventions.
### 2.2 Agents de Développement
- Écrivent le code des features.
- Respectent strictement l'architecture définie par l'agent Architecture.
- Code **propre, structuré, stable**.
- Reçoivent les rapports d'erreurs des agents de test et corrigent.
### 2.3 Agents de Test
- **Chaque agent de développement est appairé avec un agent de test dédié.**
- Écrivent et exécutent les **tests unitaires** des features implémentées ou modifiées.
- Produisent un **rapport d'erreurs** clair quand un test échoue.
- Re-testent après chaque correction.
---
## 3. Le cycle de développement (boucle obligatoire)
Pour **chaque** feature implémentée ou modifiée :
```
1. Agent Architecture → valide le découpage et les contrats (ports/interfaces)
2. Agent Développement → écrit le code
3. Agent Test → écrit les tests unitaires + les exécute
4a. Tests OK → feature validée, on passe à la suite
4b. Tests KO → rapport d'erreurs → retour à l'agent Développement
→ correction → retour à l'étape 3 (boucle jusqu'au vert)
```
**Règle d'or :** aucune feature n'est considérée terminée tant que ses tests ne passent pas.
Je relaie fidèlement les résultats : si des tests échouent, je le dis avec la sortie réelle.
---
## 4. Principes de code
- **SOLID** appliqué au maximum.
- **Architecture Hexagonale** (Ports & Adapters) : le domaine métier est isolé des détails techniques (UI, terminal, git, SSH, système de fichiers...).
- Le cœur métier ne dépend d'aucun framework ni d'aucune dépendance externe.
- Tests unitaires systématiques ; couverture des features critiques.
- Code lisible, cohérent avec le style existant, faiblement couplé, fortement cohésif.
---
## 5. Vision produit : IdeA
**IdeA est un IDE next-gen 100 % IA.** On n'y code pas : **on gère des IA.**
### Fonctionnalités clés
- **Multi-projets en parallèle** : un **onglet par projet**.
- **Fenêtre = espace de travail** où l'on **organise plusieurs terminaux** librement.
- **Agents par projet** : chaque projet a ses propres agents.
- **Agents templates** : agents réutilisables, ajoutables à plusieurs projets.
- **Création d'agents** : depuis zéro ou à partir d'un template.
- **Synchronisation template → agents** : option « garder l'agent à jour ».
Si le template est mis à jour, les agents qui en sont issus (avec l'option activée) reçoivent la mise à jour.
- **Contextes d'agents stockés en `.md`** (toujours).
- **Création de projet** = définition de son **project root**.
### Intégrations
- **Git** intégré.
- **Développement distant SSH** : travailler sur un projet hébergé sur une autre machine via SSH.
- **Développement WSL** : travailler sur une WSL depuis Windows.
### Plateformes & livraison
- Cible : **macOS, Linux, Windows**.
- Première phase de compilation : **Linux et Windows**.
- Livraison :
- **Windows** : `setup.exe`.
- **Linux** : **AppImage** (doit fonctionner sur les différentes distributions).
---
## 6. Stack technique (validée)
- **Shell applicatif** : **Tauri v2** (binaires légers, performants, multi-OS, AppImage + installeur `setup.exe`/NSIS Windows natifs).
- **Cœur / backend** : **Rust** — stabilité, performance, et expression idiomatique du domaine hexagonal (ports = traits, adapters = implémentations).
- **Frontend / UI** : **TypeScript + React**.
- **Terminaux** : **xterm.js** (rendu) + **portable-pty** (PTY côté Rust).
- **Git** : **libgit2** via `git2` (Rust).
- **SSH** : `russh` / `ssh2` (Rust).
- **WSL** : invocation de `wsl.exe` depuis le backend.
## 7. Layout des terminaux (exigence produit)
Disposition en **grille redimensionnable de type tableur (Excel)** :
- Splits redimensionnables horizontaux **et** verticaux.
- L'utilisateur peut **définir le nombre de colonnes dans une ligne** et **le nombre de lignes dans une colonne**, indépendamment par zone.
- Possibilité de **fusionner des cellules** (ex. fusionner deux colonnes sur une ligne), à la manière des cellules fusionnées d'un tableur.
- Chaque cellule de la grille héberge un terminal.
- → Modèle de layout récursif/imbriqué (pas une grille rigide uniforme) à concevoir par l'agent Architecture.
## 8. Stockage des contextes & liaison aux templates
- **Templates d'agents** : stockés dans l'**IDE** (dossier de données utilisateur global de l'app, hors projet).
- **Agents de projet** : leurs `.md` sont stockés dans un dossier **`.ideai/`** à la racine du project root.
*(Nom choisi pour éviter toute collision avec le `.idea` de JetBrains.)*
- **Manifeste de liaison** dans `.ideai/` (ex. `.ideai/agents.json`) qui mappe pour chaque agent de projet :
- le `.md` de l'agent,
- le template d'origine (le cas échéant),
- `synchronized: true/false`,
- la **version du template** au dernier sync (pour détecter qu'une mise à jour est disponible).
- **Synchro template → agents** : quand un template est mis à jour, les agents liés avec `synchronized: true` reçoivent la MAJ.
## 9. Moteur IA : adaptateur de CLI flexible (Port `AgentRuntime`)
Chaque IA est décrite par un **profil déclaratif** (config éditable, pas du code), implémentation d'un **Port** `AgentRuntime` côté domaine. Deux variables clés par IA :
1. **Commande de lancement** + arguments (ex. `claude`, `codex`, `gemini`, `aider`).
2. **Stratégie d'injection du contexte `.md`** :
- `conventionFile` : écrire/symlink le `.md` vers le fichier attendu par la CLI (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`…).
- `flag` : passer le chemin via un argument.
- `stdin` : piper le contenu.
- `env` : passer via variable d'environnement.
Exemple de profil :
```json
{
"id": "claude-code",
"name": "Claude Code",
"command": "claude",
"args": [],
"contextInjection": { "strategy": "conventionFile", "target": "CLAUDE.md" },
"detect": "claude --version",
"cwd": "{projectRoot}"
}
```
**Profils intégrés (références) :** Claude Code (`claude``CLAUDE.md`), OpenAI Codex CLI (`codex``AGENTS.md`), Gemini CLI (`gemini``GEMINI.md`), Aider (`aider` → args/message).
**Règles produit :**
- **Premier lancement de l'IDE** : un assistant (first-run) **demande à l'utilisateur** quels profils d'IA configurer. On ne présume rien par défaut.
- Les commandes des profils sont **pré-remplies mais éditables**.
- L'utilisateur peut **ajouter sa propre commande CLI** (profil custom) pour n'importe quelle IA.
**Lancement d'un agent :** à l'**activation de l'agent**, on ouvre une cellule terminal (PTY) avec le bon `cwd`, on injecte le contexte `.md`, et on **auto-lance** la CLI du profil.
## 10. Fenêtres & onglets
- **Par défaut : un onglet par projet** (comme les IDE classiques).
- **Drag & drop d'un onglet** hors de la fenêtre → **crée une nouvelle fenêtre OS** portant ce projet.
- **Multi-fenêtres OS supporté** ; chaque fenêtre possède un ou plusieurs onglets/projets.
## 11. Feuille de route
1. **Cadrage architecture complet d'abord** (jalon en cours) : l'agent Architecture produit la cartographie complète — domaine, ports, adapters, modules, arborescence — **avant tout code**.
2. Puis MVP incrémental selon le cycle dev/test de la section 3.
## 12. Autonomie d'exécution dans le projet
L'utilisateur m'accorde un **accès large et autonome** sur le dossier du projet : je peux lire, créer, modifier des fichiers et exécuter les commandes de développement (cargo, npm, npx, git, etc.) **sans demander confirmation à chaque fois**.
- Concrètement, ces autorisations sont matérialisées dans `.claude/settings.local.json` (mode `acceptEdits` + `Bash`/`Read`/`Edit`/`Write` autorisés), pas dans ce document — CONTEXT.md ne fait que **documenter l'intention**.
- **Garde-fous conservés** : les actions destructrices ou hors-projet restent bloquées (`sudo`, `rm -rf` sur `/`/`~`/`$HOME`, `mkfs`, `dd`, `shutdown`/`reboot`…).
- L'esprit du rôle (§1) ne change pas : je reste **chef d'orchestre**. L'autonomie porte sur l'exécution mécanique, pas sur l'arbitrage des décisions produit/archi, ni sur les **actions sortantes** (push, publication) qui restent soumises à validation explicite.
---
*Dernière mise à jour : 2026-06-05*

6158
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

35
Cargo.toml Normal file
View File

@ -0,0 +1,35 @@
[workspace]
resolver = "2"
members = [
"crates/domain",
"crates/application",
"crates/infrastructure",
"crates/app-tauri",
]
[workspace.package]
edition = "2021"
license = "MIT OR Apache-2.0"
rust-version = "1.80"
[workspace.dependencies]
uuid = { version = "1", features = ["serde", "v4", "v5", "macro-diagnostics"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
async-trait = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "fs", "io-util", "time"] }
# 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.
git2 = { version = "0.20", default-features = false }
# Internal crates
domain = { path = "crates/domain" }
application = { path = "crates/application" }
infrastructure = { path = "crates/infrastructure" }
# Tauri v2
tauri = { version = "2", features = [] }
tauri-build = { version = "2", features = [] }
tauri-plugin-dialog = "2"

View File

@ -0,0 +1,54 @@
# L0 — Socle domaine & ports
**Binôme :** `dev-core-domain` / `test-core-domain`
**Crate :** `crates/domain` (pur, zéro I/O)
**Dépendances amont :** aucune (fondation du projet).
**Statut :** en cours.
## Objectif
Poser le **cœur hexagonal** : toutes les entités, value objects, invariants, **ports (traits)**, domain events et la **logique de layout pure**. Aucun adapter, aucune I/O.
## Périmètre (DEV)
### Entities & Value Objects (cf. ARCHITECTURE §3)
- IDs typés (`ProjectId`, `AgentId`, `TemplateId`, `ProfileId`, `SessionId`, `WindowId`, `TabId`, `NodeId`).
- `Project`, `ProjectPath`, `Agent`, `AgentOrigin`, `AgentTemplate`, `TemplateVersion`, `AgentProfile`, `ContextInjection`.
- `TerminalSession`, `SessionKind`, `PtySize`, `RemoteRef`/`SshAuth`, `GitRepository`, `AgentManifest`/`ManifestEntry`, `Workspace`/`Window`/`Tab`.
- `MarkdownDoc` (VO contenu .md).
- Tous les **invariants** documentés en §3 doivent être appliqués (constructeurs validants / `try_new`).
### Logique de layout pure (cf. ARCHITECTURE §7)
- `LayoutNode` (`Leaf`/`Split`/`Grid`), `SplitContainer`, `WeightedChild`, `GridContainer`, `GridCell`.
- Opérations **pures** : `split`, `merge`, `resize`, `move``LayoutTree -> Result<LayoutTree, LayoutError>`.
- Validation des invariants (poids > 0, pas de chevauchement de spans, surface couverte, 1 session par leaf max).
### Ports (traits) — définitions seulement, pas d'impl
`AgentRuntime`, `PtyPort`, `RemoteHost`, `ProcessSpawner`, `FileSystem`, `TemplateStore`, `ProjectStore`, `AgentContextStore`, `GitRepository`, `EventBus`, `Clock`, `IdGenerator` (signatures conceptuelles en ARCHITECTURE §4).
### Domain events & erreurs
- `DomainEvent` (enum complet de §3.2).
- Types d'erreur par domaine (`LayoutError`, et erreurs de port définies ici si partagées).
### serde
- Autorisé **uniquement** pour les types persistés (manifeste, layout, profils) — dérive `Serialize`/`Deserialize`. Aucune autre dépendance I/O.
## Périmètre (TEST)
- Invariants d'entités : rejets attendus (chemin relatif, `synchronized` sans template, port SSH hors plage, etc.).
- **Layout** (cœur du lot) : `split`/`merge`/`resize`/`move` — cas nominaux + cas d'erreur (chevauchement, poids ≤ 0, span hors grille, session dupliquée).
- Déterminisme via `FixedClock`/`SeqIdGenerator`.
- `ContextInjection` : validation des 4 variantes (target relatif, var env valide, flag non vide).
- Sérialisation round-trip JSON des types persistés.
## Definition of Done
- `cargo test -p domain` vert.
- `crates/domain/Cargo.toml` ne dépend d'aucun crate I/O (vérifié).
- Tous les ports compilent et sont documentés.
- Logique de layout couverte (cas limites inclus).
## Notes / points d'attention
- Choisir `async_trait` vs `-> impl Future` pour les ports I/O (à figer ici, impacte tous les lots).
- Garder les traits **fins** (Interface Segregation) : ne pas fusionner FS/PTY/Process.

View File

@ -0,0 +1,44 @@
# L1 — Composition root & IPC
**Binôme :** `dev-ipc-bridge` / `test-ipc-bridge`
**Zones :** `crates/app-tauri`, `frontend/ports`, `frontend/adapters`
**Dépendances amont :** L0 (ports figés).
**Statut :** suivant (enchaîné après L0).
## Objectif
Mettre en place le **squelette Tauri qui tourne** : composition root (DI), pont IPC bidirectionnel, et la couche ports/adapters du frontend (avec mocks) — pour que le front soit développable **sans backend** dès les lots suivants.
## Périmètre (DEV)
### Backend `app-tauri` (driving adapter + composition root)
- **Composition root** : instancier les adapters concrets (au départ : `LocalFileSystem`, `TokioBroadcastEventBus`, `SystemClock`, `UuidGenerator`) et les injecter dans les use cases via `tauri::State` (`Arc<dyn Port>`).
- Registre des `#[tauri::command]` (squelette, mapping DTO ↔ use case) et `ErrorDTO`.
- **`TauriEventRelay`** : souscrit l'`EventBus` domaine → relaie en events/Channels Tauri.
- **Bridge PTY ↔ Tauri Channel** (`tauri::ipc::Channel`) : infrastructure générique de flux d'octets par session (sera consommée par L3).
- App Tauri v2 minimale qui démarre (fenêtre vide).
### Frontend (hexagonal côté UI)
- `frontend/ports` : gateways TS (`AgentGateway`, `TerminalGateway`, `ProjectGateway`, `LayoutGateway`, `GitGateway`, `RemoteGateway`).
- `frontend/adapters` : impl via `@tauri-apps/api` (`invoke`/`listen`/`Channel`).
- `frontend/adapters/mock` : impl mock de chaque gateway.
- `frontend/app` : bootstrap React + Vite, provider de DI des adapters (réel vs mock).
## Périmètre (TEST)
- Backend : mapping commands ↔ use cases (un use case in-memory simple validant le wiring).
- Relais `EventBus` → events Tauri (un `DomainEvent` publié arrive bien côté relais).
- Front : chaque gateway mock satisfait l'interface du port (typecheck + tests Vitest).
- Provider de DI : bascule réel/mock fonctionnelle.
## Definition of Done
- L'app Tauri démarre (fenêtre vide) sur Linux.
- `cargo test -p app-tauri` et `vitest` verts.
- Front compile et tourne en mode **mock** sans backend.
- Aucun `invoke()` direct dans les composants (uniquement via gateways).
## Points d'attention / spikes
- Forme du bridge PTY↔Channel (backpressure) — préparé ici, stressé en L3.
- Convention de (dé)sérialisation DTO Rust ↔ TS (serde camelCase ?). À figer ici.

38
agents-dev/L10-windows.md Normal file
View File

@ -0,0 +1,38 @@
# L10 — Fenêtres & multi-window
**Binôme :** `dev-windows` / `test-windows`
**Zones :** `application`, `app-tauri`, `frontend/app`
**Dépendances amont :** L0, L1, L2, L4.
## Objectif
Gestion des fenêtres et onglets : un onglet par projet ; **drag d'un onglet hors de la fenêtre → nouvelle fenêtre OS** portant ce projet.
## Périmètre (DEV)
- Entités `Workspace`/`Window`/`Tab` + persistance (`workspace.json`, machine-local).
- Use case `MoveTabToNewWindow` (réaffectation `WindowId`, l'onglet est déplacé, pas dupliqué).
- `app-tauri` : création de `WebviewWindow`, transfert d'état, fermeture de l'onglet source.
- Front : barre d'onglets, drag & drop, restauration de session.
## Périmètre (TEST)
- `MoveTabToNewWindow` : invariants (un projet dans un seul onglet à la fois ; fenêtre ≥ 1 onglet ou fermée).
- Persistance workspace round-trip.
- Front : interactions onglets (mock).
## Definition of Done
- `cargo test` + `vitest` verts ; détacher un onglet en nouvelle fenêtre fonctionne (dev manuel).
## Avancement
### ✅ Backend (vert)
- **Domaine** : opération **pure** `Workspace::move_tab_to_new_window(tab, new_window)` (`layout.rs`) — l'onglet est *déplacé* (jamais dupliqué) ; fenêtre source vidée → supprimée ; onglet actif déplacé → repli sur un onglet restant. Variante d'erreur `LayoutError::TabNotFound`. 4 tests domaine.
- **Application** (`application/window/`) : `MoveTabToNewWindow` (charge le workspace, mint `WindowId`, applique l'op pure, persiste via `ProjectStore`). 2 tests (store mock). La persistance round-trip du workspace est déjà couverte par `FsProjectStore` (L2).
- `cargo test --workspace` : **323 verts, 0 régression** ; clippy clean.
### ✅ IPC `app-tauri` (vert)
- Composition root : use case `MoveTabToNewWindow` injecté. Commande `move_tab_to_new_window(tabId)` : applique la topologie (persistée) **et ouvre une vraie `WebviewWindow`** (primitive de détach résolue). DTO `MoveTabResultDto` + `parse_tab_id`. Test `tests/dto_window.rs` (2). Workspace **325 verts, 0 régression**, clippy clean.
### ⏳ Reste (fait pendant la refonte disposition L11)
- **Front multi-fenêtres** : adopter le modèle `Workspace`/`Window`/`Tab` persistant (aujourd'hui les onglets vivent en state React transitoire), barre d'onglets, **DnD detach** + handoff d'état vers la nouvelle fenêtre. Couplé à la refonte de disposition IDE (L11), donc traité là-bas pour éviter de construire une barre d'onglets jetable.
## Spike (cf. ARCHITECTURE §13)
- DnD inter-fenêtres Tauri (le DnD HTML ne traverse pas les fenêtres OS) → protocole « detach » via store + event.

View File

@ -0,0 +1,45 @@
# L11 — Packaging & livraison
**Binôme :** `dev-packaging` / `test-packaging`
**Zones :** `app-tauri` (bundle), CI
**Dépendances amont :** transverse (mûrit avec les autres lots) ; finalisé en fin de cycle.
## Objectif
Livrer IdeA : **`setup.exe` (NSIS) Windows** et **AppImage Linux multi-distro**. macOS plus tard.
## Périmètre (DEV)
- Config bundle Tauri v2 : NSIS (Windows), AppImage (Linux).
- Vendoring statique des deps natives (git2/openssl → préférer `rustls` pour russh ; features git2) pour la portabilité AppImage.
- Pipeline CI : build Linux + Windows, artefacts publiés.
## Périmètre (TEST)
- Le bundle se construit sur Linux et Windows (CI verte).
- L'AppImage **démarre sur ≥3 distros** (Ubuntu, Fedora, Arch) — smoke test.
- L'installeur Windows installe/lance/désinstalle proprement.
## Definition of Done
- CI produit un `setup.exe` et un AppImage fonctionnels ; smoke tests multi-distro verts.
## Spike (cf. ARCHITECTURE §13)
- AppImage multi-distro : glibc/openssl/libgit2 liés dynamiquement = risque ; valider le vendoring statique tôt (coordonné avec L8/L9).
## Notes de build vérifiées (2026-06-04, premier build sur Arch)
- **CLI** : `@tauri-apps/cli` v2 installé en devDependency frontend ; binaire à `frontend/node_modules/.bin/tauri`.
- **Hooks `tauri.conf.json`** : Tauri exécute `beforeBuildCommand`/`beforeDevCommand` avec cwd = `IdeA/crates/`. Les chemins npm doivent donc être `--prefix ../frontend` (et NON `../../frontend` ni `frontend`).
- **Arch Linux — `linuxdeploy` strip échoue** : le `strip` embarqué dans `linuxdeploy-x86_64.AppImage` ne comprend pas la section ELF `.relr.dyn` des libs Arch modernes (`libzstd.so.1`, `libyuv.so`…) → erreur `unknown type [0x13] section .relr.dyn`. **Parade** : exporter **`NO_STRIP=true`** avant `tauri build`. À intégrer dans la CI/scripts de build Linux.
- Build de référence OK : `cd crates/app-tauri && NO_STRIP=true tauri build --bundles appimage``target/release/bundle/appimage/IdeA_0.1.0_amd64.AppImage` (~101 Mo).
- Le 1er échec `failed to run linuxdeploy` masquait le vrai message (strip) ; `--verbose` est nécessaire pour le voir.
- **Écran blanc au lancement (Linux/WebKitGTK)** : le renderer DMABUF de WebKitGTK rend une fenêtre blanche sur beaucoup de configs Linux récentes (Mesa/Nvidia, fréquent sur Arch). **Fix baké dans `crates/app-tauri/src/main.rs`** : on positionne `WEBKIT_DISABLE_DMABUF_RENDERER=1` au début de `main()` (cfg `target_os = "linux"`, seulement si non déjà défini) avant l'init du webview. Plus besoin de variable d'env côté utilisateur. Vérifié visuellement OK sur Arch (UI projets + health-check rendus).
## Avancement (2026-06-05)
### ✅ Fait
- **Icônes** générées (`tauri icon``crates/app-tauri/icons/`, monogramme « IA » sombre/accent) — manquaient, requises par le bundle.
- **AppImage Linux reconstruite** : `target/release/bundle/appimage/IdeA_0.1.0_amd64.AppImage` (~103 Mo), validée (ELF AppImage-runtime, libfuse2 présent, se lance directement). `git2` lié à la **libgit2 système** via `.cargo/config.toml` (`LIBGIT2_SYS_USE_PKG_CONFIG=1`).
- **`beforeBuildCommand`/`beforeDevCommand`** confirmés à `--prefix ../frontend` (cwd des hooks = `IdeA/crates/`).
- **Refonte disposition IDE** (passe UI/altitude) ✅ : `ProjectsView` réécrit en disposition d'IDE — **barre d'onglets projets** en haut, **sidebar** (onglets Projects/Agents/Templates/Git, un panneau à la fois) + **zone principale** = `LayoutGrid` (grille de terminaux) qui remplit la hauteur. App shell en `h-full`. Composants `ProjectLauncher`/`ProjectTabs`/`Workspace` extraits. Hooks de test préservés ; front **158 tests verts**, `tsc` clean. (Note : `beforeBuildCommand` retiré de `tauri.conf.json` — chemins de hook ambigus selon le cwd d'invocation ; on **build le front manuellement** (`npm --prefix frontend run build`) avant `tauri build` lancé **depuis la racine du repo**. C'est le recette déterministe vérifiée.)
### ⏳ Reste
- **Vendoring statique** pour AppImage portable multi-distro : passer git2 en `vendored-libgit2` (nécessite **cmake**, absent de la machine actuelle) + `rustls` pour russh (L9). Aujourd'hui l'AppImage lie la libgit2 **système** → portable seulement vers des distros fournissant libgit2 ≥ 1.9.
- **Windows `setup.exe` (NSIS)** : non constructible ici (Linux) → CI Windows.
- **CI** Linux+Windows (avec `NO_STRIP=true` côté Linux) + smoke tests ≥3 distros.

65
agents-dev/L12-skills.md Normal file
View File

@ -0,0 +1,65 @@
# L12 — Skills
**Binôme :** `dev-skills` / `test-skills`
**Zones :** `domain/skill`, `application/skill`, `infrastructure/store`, `frontend/features/skills`
**Dépendances amont :** L0, L1, L5, L6 (convention file généré à l'activation), L7 (store global réutilisé).
## Objectif
Modéliser les **Skills** : workflows réutilisables (équivalent universel des slash-commands, sans dépendance à la syntaxe `/command` d'un modèle). Stockage global IDE + projet, assignation agent↔skills, **injection des skills assignés dans le convention file** généré à l'activation de l'agent. Cf. ARCHITECTURE §14.2.
## Périmètre (DEV)
- **Domaine** : entité `Skill { id, name, content_md: MarkdownDoc, scope: SkillScope(Global|Project) }`. Invariants : `name` non vide, `content_md` non vide. Event `SkillAssigned`.
- **Port `SkillStore`** : CRUD skills globaux (`<app_data>/IdeA/skills/`) + skills projet (`.ideai/skills/<name>.md`), résolution selon `scope` (compose `FileSystem`/store global comme L7).
- **AgentManifest** : étendre pour porter la liste `skills: Vec<SkillRef>` assignés à chaque agent (0..N).
- **Use cases** (`application/skill`) : `CreateSkill`, `UpdateSkill`, `DeleteSkill`, `ListSkills(scope)`, `AssignSkillToAgent`, `UnassignSkillFromAgent`.
- **Injection** : à l'activation (fil L6), composer le convention file en concaténant persona agent + chemin project root + **skills assignés** (lus via `SkillStore`). Pas de mécanisme CLI propriétaire.
- **Front** : onglet/section Skills (liste globale + projet, CRUD, éditeur md), assignation skills↔agent dans `AgentsPanel`.
## Périmètre (TEST)
- `Skill` rejette `name`/`content_md` vides.
- `SkillStore` : CRUD round-trip en tmpdir pour les deux scopes ; un skill `Project` n'apparaît pas dans le scope `Global` et inversement.
- `AssignSkillToAgent` / `UnassignSkillFromAgent` : mutent l'`AgentManifest`, émettent `SkillAssigned`, idempotents (pas de doublon).
- **Injection** : le convention file généré contient bien le `content_md` des skills assignés et **rien** des skills non assignés ; ordre déterministe.
- Front : CRUD skills + assignation via gateway mock (RTL) ; garde-fou « no direct invoke ».
## Definition of Done
- `cargo test` (skill/store/app) + `vitest` verts ; cycle manuel : créer un skill, l'assigner à un agent, l'activer → le skill apparaît dans le convention file de `.ideai/run/<agent-id>/`.
- DoD commune (cf. README) respectée ; zéro régression.
## Avancement
### ✅ Domaine (vert)
- **Entité `Skill`** (`domain/skill.rs`) : `id: SkillId`, `name`, `content_md: MarkdownDoc`, `scope: SkillScope(Global|Project)`. Constructeur validant (`name` + `content_md` non vides), `with_content` re-valide l'invariant.
- **`SkillRef { skill_id, scope }`** : référence d'assignation portée par l'agent ; `From<&Skill>`.
- **`SkillId`** ajouté (`ids.rs`), event **`SkillAssigned { agent_id, skill_id, assigned }`** (`events.rs`), DTO + arm de mapping côté `app-tauri` (`events.rs`).
- **`Agent`** étendu : champ `skills: Vec<SkillRef>` (serde `default`), méthodes `assign_skill` (idempotent), `unassign_skill`, `with_skills` (dédup). **`ManifestEntry`** : champ `skills` (serde `default` + `skip_serializing_if` → rétrocompat des manifests pré-L12) ; `from_agent`/`to_agent` préservent les skills.
- **Tests** : 8 invariants (`entities.rs`) + 3 serde dont rétrocompat d'un manifest legacy sans clé `skills` (`serde_roundtrip.rs`). `cargo test -p domain` vert ; `cargo test --workspace` vert (0 régression) ; clippy clean.
### ✅ Port + adapter store (vert)
- **Port `SkillStore`** (`domain/ports.rs`) : `list/get/save/delete` portant `scope` + `root: &ProjectPath` **par appel** (root ignoré pour `Global`, résolu pour `Project`) — un seul store sert tous les projets ouverts, comme `AgentContextStore`.
- **Adapter `FsSkillStore`** (`infrastructure/store/skill.rs`) : même forme on-disk que `FsTemplateStore` (`index.json` + `md/<id>.md`), deux racines disjointes : `<app_data>/skills/` (Global) et `<root>/.ideai/skills/` (Project). Delete laisse l'orphelin md (pas de remove dans le port FS), index = source de vérité. **7 tests** d'intégration tmpdir (`skill_store.rs`) : round-trip 2 scopes, **isolation de scope**, upsert, delete idempotent, camelCase.
### ✅ Use cases application (vert)
- `application/skill` : `CreateSkill`, `UpdateSkill`, `DeleteSkill`, `ListSkills(scope)` (inputs portant `project_root`), `AssignSkillToAgent` / `UnassignSkillFromAgent` (mutent l'`AgentManifest` via `to_agent`/`from_agent`, dédup, émettent `SkillAssigned`, **idempotents**). **9 tests** (`skill_usecases.rs`).
### ✅ Injection dans le convention file (vert, fil L6)
- `LaunchAgent` reçoit le port `SkillStore` ; `resolve_skills` lit les `.md` des skills assignés (ordre manifest, déterministe ; skill supprimé = `SkillRef` pendant → ignoré sans bloquer le lancement).
- `compose_convention_file` étendu : section `# Skills` (sous-titres `## <name>`) après le persona ; omise si aucun skill. **3 tests** unitaires + e2e (`agent_lifecycle.rs` : injection ordonnée, ref pendant tolérée).
- **Composition root** (`app-tauri/state.rs`) : `FsSkillStore` construit (app-data global), injecté dans `LaunchAgent`.
### ✅ IPC `app-tauri` (vert)
- **DTOs** (`dto.rs`) : `SkillDto` (transparent sur `Skill`, camelCase), `SkillListDto`, request DTOs (`Create/Update/Assign/UnassignSkillRequestDto`), `parse_skill_id`. `scope` désérialise directement vers `SkillScope` (`"global"`/`"project"`).
- **Commandes** (`commands.rs`) : `create_skill`, `update_skill`, `list_skills`, `delete_skill`, `assign_skill_to_agent`, `unassign_skill_from_agent` — shells fins qui résolvent le `Project` (→ `project.root`) puis appellent le use case. Enregistrées dans `lib.rs`.
- **Composition root** (`state.rs`) : 6 use cases skill câblés sur le `skill_store_port` (déjà construit pour le launcher) et le `contexts_port` partagé.
- `cargo build -p app-tauri` + `cargo test --workspace` (304) verts ; clippy clean.
### ✅ Front `features/skills` (vert)
- **Domaine** (`domain/index.ts`) : `SkillScope`, `Skill`, `SkillRef` ; `Agent` étendu avec `skills: SkillRef[]`.
- **Port** (`ports/index.ts`) : `SkillGateway` (list/create/update/delete + assign/unassign) + `CreateSkillInput` ; ajouté à `Gateways`.
- **Adapters** : `TauriSkillGateway` (`adapters/skill.ts`, invoke camelCase) ; `MockSkillGateway` (`adapters/mock`, scopes disjoints + mutation partagée du `MockAgentGateway` via `_setSkills`, assign idempotent).
- **Feature** : `useSkills` (VM 2 scopes), `SkillEditor` (overlay md edit/preview + sélecteur de scope), `SkillsPanel` (listes Project/Global, CRUD). Onglet **Skills** ajouté dans `ProjectsView`.
- **Assignation** dans `AgentsPanel` : chips des skills assignés + sélecteur d'assignation + unassign, sur l'agent sélectionné ; refresh après mutation.
- **Tests** (`skills.test.tsx`, RTL via `DIProvider` + mocks) : CRUD project/global, isolation de scope, édition, suppression, assign/unassign reflétés sur l'agent, idempotence, **garde-fou « no direct invoke »** (aucune action run/launch). `vitest` : **229** verts (0 régression ; test « ten gateways » mis à jour).
### ⏳ Reste à faire
- Cycle manuel : créer un skill, l'assigner à un agent, l'activer → vérifier qu'il apparaît dans le convention file de `.ideai/run/<agent-id>/` (à faire sur l'AppImage).

View File

@ -0,0 +1,35 @@
# L13 — OrchestratorApi
**Binôme :** `dev-orchestrator` / `test-orchestrator`
**Zones :** `infrastructure/orchestrator`, `application/agent`, `app-tauri`
**Dépendances amont :** L0, L1, L6 (`LaunchAgent`/`StopAgent`), L12 (`update_agent_context` peut toucher les skills).
## Objectif
Permettre à un **agent orchestrateur** de demander à IdeA de créer/arrêter/mettre à jour un agent — **exactement** comme l'utilisateur via l'UI. L'orchestrateur ne spawne jamais lui-même un process CLI : il **délègue à IdeA**, unique source de vérité du cycle de vie des agents. Cf. ARCHITECTURE §14.3.
## Périmètre (DEV)
- **Port `OrchestratorApi`** (adapter *entrant*, driven by file-watcher) : surveille `.ideai/requests/<requester-id>/`, désérialise les requêtes JSON, les traduit en appels de use cases.
- **Adapter `FsOrchestratorAdapter`** (`infrastructure/orchestrator`) : file-watching (`notify`), parse, dispatch, **supprime le fichier de requête** et **écrit une réponse** (succès/erreur) à côté.
- **Actions v1** : `spawn_agent` (→ `LaunchAgent`), `stop_agent` (→ `StopAgent`), `update_agent_context` (réécrit le `.md` de l'agent ± skills).
- **Event** : `OrchestratorRequest { requester_id, action }`.
- **Schéma requête** :
```json
{ "action": "spawn_agent", "name": "dev-backend", "profile": "claude-code", "context": "agents/dev-backend.md" }
```
- Le résultat d'un `spawn_agent` est **identique** à un lancement UI : cellule terminal créée, agent inscrit dans l'onglet Agents.
- **Composition root** (`app-tauri`) : démarrer le watcher, brancher sur les use cases existants ; arrêt propre à la fermeture.
## Périmètre (TEST)
- Désérialisation : requêtes valides → action typée ; requête malformée → réponse d'erreur, **pas de crash**, pas de spawn.
- `spawn_agent` invoque `LaunchAgent` avec les bons args (use case mocké) ; idempotence sur double-dépôt du même fichier (traité une fois).
- Après traitement : fichier de requête **supprimé**, fichier de réponse écrit avec le bon statut.
- `stop_agent` / `update_agent_context` : mappent vers les bons use cases ; cible inexistante → erreur propre.
- Watcher : un fichier déposé dans `.ideai/requests/<id>/` est détecté (test d'intégration tmpdir + `notify`).
## Definition of Done
- `cargo test` (orchestrator/app) verts ; cycle manuel : un agent écrit un fichier de requête `spawn_agent` → un nouvel agent apparaît dans la grille et l'onglet Agents, fichier consommé + réponse écrite.
- Garde-fou : l'orchestrateur ne lance **aucun** process directement (vérifié par revue + absence de `ProcessSpawner` dans le chemin orchestrateur).
- DoD commune respectée ; zéro régression ; git reste optionnel (rien dans ce lot n'en dépend, §14.4).
## Avancement
⬜ À démarrer. Cadrage figé dans ARCHITECTURE §14.3.

22
agents-dev/L2-projects.md Normal file
View File

@ -0,0 +1,22 @@
# L2 — Projets & stockage
**Binôme :** `dev-projects` / `test-projects`
**Zones :** `application/project`, `infrastructure/{fs,store}`, `frontend/features/projects`
**Dépendances amont :** L0, L1.
## Objectif
Gérer le cycle de vie des projets (création par project root, ouverture, fermeture) et le stockage de base.
## Périmètre (DEV)
- Use cases : `CreateProject` (init `.ideai/` + `project.json` + registre), `OpenProject`, `CloseProject`/`CloseTab`.
- Adapters : `LocalFileSystem` (tokio::fs), `FsProjectStore` (registre projets + workspace en JSON dans données app).
- UI : sélection du project root, liste des projets, ouverture en onglet.
## Périmètre (TEST)
- Use cases avec `FileSystem`/`ProjectStore` mockés : création initialise bien `.ideai/`, invariants projet respectés (root absolu, unicité `(remote, root)`).
- Intégration ciblée : `LocalFileSystem` sur tmpdir, `FsProjectStore` round-trip.
- Front : feature projects avec gateway mock (RTL).
## Definition of Done
- `cargo test -p application -p infrastructure` (filtré projet) + `vitest` verts.
- Créer/ouvrir/fermer un projet de bout en bout (avec adapters réels en dev manuel).

View File

@ -0,0 +1,25 @@
# L3 — Terminaux & PTY (local)
**Binôme :** `dev-terminals` / `test-terminals`
**Zones :** `infrastructure/pty`, `application/terminal`, `frontend/features/terminals`
**Dépendances amont :** L0, L1.
## Objectif
Terminaux fonctionnels en local : ouverture PTY, I/O, resize, fermeture, rendu xterm.js, flux via Tauri Channel.
## Périmètre (DEV)
- Adapter `PortablePtyAdapter` (portable-pty) implémentant `PtyPort`.
- Use cases : `OpenTerminal`, `WriteToTerminal`, `ResizeTerminal`, `CloseTerminal`.
- Front : wrapper xterm.js, abonnement au flux d'octets (Channel), envoi des frappes/resize.
## Périmètre (TEST)
- Use cases avec `PtyPort` mocké (spawn/write/resize/kill appelés correctement).
- Intégration : `PortablePtyAdapter` lance `echo`/`printf` et reçoit la sortie attendue.
- Front : wrapper xterm avec gateway mock (frappe → write, octets reçus → rendu).
## Definition of Done
- `cargo test` (pty/terminal) + `vitest` verts ; un terminal réel utilisable en dev manuel sur Linux.
## Spikes (cf. ARCHITECTURE §13)
- ConPTY Windows (resize/signaux/exit codes).
- Backpressure/coalescing du flux haute fréquence via Channel.

21
agents-dev/L4-layout.md Normal file
View File

@ -0,0 +1,21 @@
# L4 — Layout tableur
**Binôme :** `dev-layout` / `test-layout`
**Zones :** `domain/layout` (déjà amorcé en L0), `application/layout`, `frontend/features/layout`
**Dépendances amont :** L0, L1, L3 (cellules ↔ terminaux).
## Objectif
Grille redimensionnable type tableur : N colonnes par ligne / M lignes par colonne indépendantes, **fusion de cellules**, persistance.
## Périmètre (DEV)
- Compléter la logique de layout pure du domaine (si reliquats post-L0).
- Use case `MutateLayout` (split/merge/resize/move) + persistance `.ideai/layout.json`.
- UI : grille redimensionnable (drag des séparateurs), création/suppression de cellules, fusion, mapping cellule → terminal.
## Périmètre (TEST)
- Domaine : opérations pures exhaustives (déjà couvertes L0, étendre cas combinés).
- Application : `MutateLayout` persiste et publie `LayoutChanged`.
- Front : logique de calcul des tailles de cellules (Vitest, pure) ; interactions de split/merge (RTL + mock).
## Definition of Done
- `cargo test` (layout) + `vitest` verts ; manipulation visuelle de la grille fonctionnelle.

View File

@ -0,0 +1,22 @@
# L5 — Profils IA & runtime
**Binôme :** `dev-ai-runtime` / `test-ai-runtime`
**Zones :** `infrastructure/runtime`, `application/agent`, `frontend/features/first-run`
**Dépendances amont :** L0, L1, L2.
## Objectif
Moteur IA flexible : profils déclaratifs, détection, first-run wizard. Cœur du « 100% IA, piloté par données ».
## Périmètre (DEV)
- Adapter `CliAgentRuntime` (un seul, piloté par `AgentProfile`) : `detect`, `prepare_invocation` (construit `SpawnSpec` + plan d'injection selon `ContextInjection`).
- Use cases : `DetectProfiles`, `ConfigureProfiles`.
- Stockage `profiles.json` (store global IDE).
- Front : **first-run wizard** demandant quels profils configurer ; commandes pré-remplies (Claude/Codex/Gemini/Aider) **éditables** ; ajout de **profil custom**.
## Périmètre (TEST)
- `CliAgentRuntime` : pour chaque stratégie d'injection (conventionFile/flag/stdin/env), le `SpawnSpec` produit est correct.
- `DetectProfiles` avec `ProcessSpawner` mocké (présent/absent).
- Front : wizard avec gateway mock (sélection, édition, ajout custom).
## Definition of Done
- `cargo test` (runtime/agent) + `vitest` verts ; wizard fonctionnel en dev manuel.

52
agents-dev/L6-agents.md Normal file
View File

@ -0,0 +1,52 @@
# L6 — Agents & contextes
**Binôme :** `dev-agents` / `test-agents`
**Zones :** `application/agent`, `infrastructure/store`, `frontend/features/agents`
**Dépendances amont :** L0, L1, L2, L3, L5.
## Objectif
Agents de projet : contextes `.md` dans `.ideai/`, manifeste, et **lancement d'un agent** (injection contexte + spawn CLI dans une cellule terminal).
## Périmètre (DEV)
- Adapter `IdeaiContextStore` (compose `FileSystem`) : lecture/écriture `.md` + `agents.json`.
- Use cases : `CreateAgentFromScratch`, `LaunchAgent` (résout profil+contexte, injecte, ouvre cellule PTY au bon `cwd`, spawn CLI), CRUD agents.
- Front : panneau agents (créer, éditer le `.md`, activer → terminal).
## Périmètre (TEST)
- `IdeaiContextStore` : round-trip `.md` + manifeste (intégration tmpdir + mock FS pour les use cases).
- `LaunchAgent` : ordre des appels (prepare_invocation → injection → pty.spawn) avec `cwd` correct.
- Front : feature agents avec gateway mock.
## Definition of Done
- `cargo test` (agent/store) + `vitest` verts ; activer un agent ouvre un terminal avec la CLI lancée (dev manuel).
## Spike
- Injection `conventionFile` : symlink vs copie ; conflits si `CLAUDE.md` existe ; symlinks Windows.
## Avancement
### ✅ Backend (vert)
- **Domaine** : `ManifestEntry` réconcilié avec le schéma documenté `agents.json` (ARCHITECTURE §9.1) — porte désormais `name` + `profile_id` ; helpers `from_agent`/`to_agent` (le manifeste est la forme persistée d'un `Agent`). Tests domaine maj, verts.
- **Infra** : `IdeaiContextStore` (`infrastructure/store/context.rs`) implémente `AgentContextStore` en composant `FileSystem` ; écrit `.ideai/agents.json` + `.ideai/agents/*.md`, location-neutre (réutilisable local/SSH/WSL). Test d'intégration tmpdir (5 tests).
- **Application** (`application/agent/lifecycle.rs`) : `CreateAgentFromScratch`, `ListAgents`, `ReadAgentContext`, `UpdateAgentContext`, `DeleteAgent`, et `LaunchAgent` (résout profil+contexte → `prepare_invocation` → injection → `pty.spawn` au bon `cwd` → event `AgentLaunched`). 9 tests use cases (ordre d'appel vérifié via trace partagée).
- **Spike `conventionFile`** tranché pour L6 : **copie** du `.md` vers le fichier conventionnel (ex. `CLAUDE.md`), écrasement si présent — choix portable (symlinks Windows = privilèges, sémantique SFTP/WSL divergente). Stratégie `Env` → chemin absolu du `.md` ; `Stdin` → contenu piped après spawn.
### ✅ Front (vert)
- **Port** `AgentGateway` étendu (list/create/read/update/delete/launch) + type `Agent` dans `domain` ; **mock** stateful `MockAgentGateway` ; **adapter** Tauri `TauriAgentGateway` (`src/adapters/agent.ts`, commandes `*_agent` — câblage backend à venir).
- **Feature** `frontend/features/agents` : hook `useAgents(projectId)` + `AgentsPanel` (liste, création nom+profil, éditeur de contexte `.md`, Launch/Delete), bâti sur le **design system** (LD) et intégré dans l'onglet projet actif.
- **Tests** : 13 nouveaux (RTL + mock) ; suite front **116 verts**, `tsc` clean. Garde-fou « no direct invoke » respecté.
### ✅ IPC `app-tauri` (vert)
- **Composition root** (`state.rs`) : `IdeaiContextStore` construit ; 6 use cases agents instanciés en réutilisant les ports existants ; `LaunchAgent` partage le **même** `pty_port` + `terminal_sessions` que les terminaux (indispensable au `PtyBridge`) ; handle `project_store` ajouté pour résoudre le `Project` depuis un `projectId`.
- **Commands** (`commands.rs`) : `create_agent`, `list_agents`, `read_agent_context`, `update_agent_context`, `delete_agent`, `launch_agent`. `launch_agent` imite `open_terminal` (Channel + `PtyBridge` + thread de pompe). Enregistrées dans `lib.rs`.
- **DTOs** (`dto.rs`) : `AgentDto`/`AgentListDto` (transparent, camelCase), request DTOs, `parse_agent_id`, `From<LaunchAgentOutput> for TerminalSessionDto`.
- **Tests** : `tests/dto_agents.rs` (10) ; `cargo test -p app-tauri` 44 verts ; `cargo test --workspace` **256 verts, 0 régression** ; clippy clean.
### ✅ Terminal d'agent (front, vert) — L6 clos
- `AgentGateway.launchAgent(projectId, agentId, options, onData)` renvoie désormais un `TerminalHandle` (signature calquée sur `openTerminal`) ; adapter Tauri = `Channel<number[]>` + `invoke("launch_agent", …)` + write/resize/close via les commandes terminal (clé `sessionId`) ; mock = greeting + echo.
- `TerminalView` généralisé avec une prop `open?` optionnelle (par défaut = gateway terminal) → réutilisé tel quel pour le terminal d'agent (xterm/fit/resize/cleanup partagés).
- `AgentsPanel` : bouton **Launch** monte un `TerminalView` (conteneur sombre `h-96`) branché sur la session d'agent ; bouton **Stop** le démonte (cleanup `close()`).
- **Tests** front : 120 verts (`tsc` clean), garde-fou « no direct invoke » respecté. Backend/IPC : 256 verts.
### ⏳ Hors périmètre L6 (à reprendre plus tard)
- Affiner la stratégie `Env` (support adapter de premier ordre).

View File

@ -0,0 +1,42 @@
# L7 — Templates & synchronisation
**Binôme :** `dev-templates` / `test-templates`
**Zones :** `application/template`, `infrastructure/store`, `frontend/features/templates`
**Dépendances amont :** L0, L1, L5, L6.
## Objectif
Templates d'agents (store global IDE), versioning, création d'agent depuis template, **détection de drift** et **synchronisation template → agents**.
## Périmètre (DEV)
- Adapter `FsTemplateStore` (md + `index.json`) avec versioning monotone + `content_hash`.
- Use cases : `CreateTemplate`, `UpdateTemplate` (bump version), `CreateAgentFromTemplate`, `DetectAgentDrift`, `SyncAgentWithTemplate` (remplacement du `.md` si `synchronized`).
- Front : gestion des templates, badge « MAJ disponible », action de sync.
## Périmètre (TEST)
- `UpdateTemplate` incrémente la version ; `DetectAgentDrift` détecte `version > synced_template_version`.
- `SyncAgentWithTemplate` : applique aux `synchronized==true`, ignore `false` et `scratch` ; met à jour `synced_template_version`.
- Front : badge drift + flux de sync avec gateway mock.
## Definition of Done
- `cargo test` (template/store) + `vitest` verts ; cycle MAJ template → propagation aux agents synchronisés (dev manuel).
## Avancement
### ✅ Backend (vert)
- **Infra** : `FsTemplateStore` (`infrastructure/store/template.rs`) — store global `<app>/templates/{index.json, md/<id>.md}`, versioning persisté, `contentHash` (digest stable, sans dépendance) pour détection d'édition hors-app. 6 tests d'intégration (tmpdir).
- **Application** (`application/template/usecases.rs`) : `CreateTemplate`, `UpdateTemplate` (bump version + event `TemplateUpdated`), `ListTemplates`, `DeleteTemplate`, `CreateAgentFromTemplate` (origine `FromTemplate` + seed du `.md`, réutilise le helper de nommage L6), `DetectAgentDrift` (ne flague que les `synchronized` en retard ; ignore non-sync/scratch/à-jour/template supprimé ; émet `AgentDriftDetected`), `SyncAgentWithTemplate` (remplace le `.md`, avance `synced_template_version`, émet `AgentSynced` ; laisse intacts non-sync/scratch). 8 tests use cases.
- `cargo test --workspace` : **270 verts, 0 régression** ; clippy clean.
### ✅ IPC `app-tauri` (vert)
- Composition root : `FsTemplateStore` construit, 7 use cases injectés (réutilise `contexts_port`/`ids`/`events_port`).
- 7 commands : `create_template`, `update_template`, `list_templates`, `delete_template`, `create_agent_from_template`, `detect_agent_drift`, `sync_agent_with_template` (shells fins, `resolve_project` réutilisé). DTOs camelCase (`TemplateDto` transparent, `AgentDriftDto`, `SyncResultDto`, `parse_template_id`).
- Tests `tests/dto_templates.rs` (18) ; `cargo test -p app-tauri` 62 verts ; workspace **288 verts, 0 régression** ; clippy clean.
### ✅ Front (vert)
- Port `TemplateGateway` (7 méthodes) + types `Template`/`AgentDrift` ; adapter Tauri `TauriTemplateGateway` ; `MockTemplateGateway` stateful **partageant le registre d'agents** du `MockAgentGateway` (helpers internes `_insertAgent`/`_updateAgent`/`_rawAgents`) pour faire vivre le drift offline.
- Feature `features/templates` : `useTemplates` (CRUD), `useDrift(projectId)` (détection + sync), `TemplatesPanel` (liste nom+version, création, édition, suppression, « Create agent from template »), monté dans l'onglet projet.
- `AgentsPanel` : **badge « update available »** + bouton **Sync** par agent en drift.
- Tests : 20 ajoutés ; suite front **140 verts** ; `tsc` clean ; garde-fou « no direct invoke » respecté.
### ⏳ Hors périmètre L7
- Intégration front du terminal d'agent depuis un agent créé via template (réutilise le fil L6).

42
agents-dev/L8-git.md Normal file
View File

@ -0,0 +1,42 @@
# L8 — Git
**Binôme :** `dev-git` / `test-git`
**Zones :** `infrastructure/git`, `application/git`, `frontend/features/git`
**Dépendances amont :** L0, L1, L2.
## Objectif
Support Git intégré (local) via libgit2.
## Périmètre (DEV)
- Adapter `Git2Repository` (git2) : `status`, `stage`/`unstage`, `commit`, `branches`, `checkout`, `current_branch`, `diff`, `log`, `pull`, `push`, `clone`, `init`.
- Use cases Git (`GitStatus`, `GitCommit`, `GitCheckout`, `GitPush`, …).
- Front : panneau Git (changements, staging, commit, branches).
## Périmètre (TEST)
- Use cases avec `GitRepository` mocké.
- Intégration : `Git2Repository` sur repo temporaire (init → commit → branch → status).
- Front : feature git avec gateway mock.
## Definition of Done
- `cargo test` (git) + `vitest` verts ; opérations git de base utilisables (dev manuel).
## Spike
- Vendoring statique git2/openssl pour l'AppImage (coordonné avec L11).
## Avancement
### ✅ Backend (vert)
- **Dépendance** : `git2` 0.20 (`default-features = false`, pas d'openssl) liée à la **libgit2 système** via `.cargo/config.toml` (`LIBGIT2_SYS_USE_PKG_CONFIG=1`) — évite cmake/vendoring ; le vendoring statique AppImage reste pour L11.
- **Infra** : `Git2Repository` (`infrastructure/git/mod.rs`) implémente `GitPort``init`, `status`, `stage`, `unstage`, `commit` (signature de repli `IdeA <idea@localhost>` si pas de config user), `branches`, `current_branch`, `checkout`, `log`. `pull`/`push` renvoient une erreur explicite (réseau/credentials → L9). Appels libgit2 synchrones sans `await` ⇒ futures `Send`. 3 tests d'intégration (vrai repo temporaire).
- **Application** (`application/git/`) : `GitStatus`, `GitStage`, `GitUnstage`, `GitCommit` (valide message non vide + event `GitStateChanged`), `GitBranches` (liste + courante), `GitCheckout` (+ event), `GitLog`, `GitInit` (+ event). 7 tests use cases (mock `GitPort`).
- `cargo test --workspace` : **298 verts, 0 régression** ; clippy clean.
### ✅ IPC `app-tauri` (vert)
- Composition root : `Git2Repository` construit, 8 use cases injectés. 8 commands (`git_status`, `git_stage`, `git_unstage`, `git_commit`, `git_branches`, `git_checkout`, `git_log`, `git_init`), résolution `projectId → root` via `resolve_project`. DTOs `GitFileStatusDto`/`GitCommitDto`/`GitBranchesDto` + request DTOs camelCase. Tests `tests/dto_git.rs` (12). `cargo test -p app-tauri` 74 verts ; workspace **310 verts, 0 régression** ; clippy clean.
### ✅ Front (vert)
- Port `GitGateway` complet (8 méthodes) + types `GitFileStatus`/`GitCommit`/`GitBranches` ; adapter `TauriGitGateway` ; `MockGitGateway` stateful (état par projet, seedé) pour le mode offline.
- Feature `features/git` : `useGit(projectId)` + `GitPanel` (sections Staged/Unstaged + stage/unstage, message + commit, branches + checkout, log récent), monté dans l'onglet projet. 17 tests ; suite front **157 verts** ; `tsc` clean ; garde-fou « no invoke » OK.
### ⏳ Hors périmètre L8
- `pull`/`push`/`clone` (réseau + credentials) → L9 (`RemoteGitRepository` + callbacks) ; vendoring statique git2 pour l'AppImage → L11.

40
agents-dev/L9-remote.md Normal file
View File

@ -0,0 +1,40 @@
# L9 — Remote (SSH + WSL)
**Binôme :** `dev-remote` / `test-remote`
**Zones :** `infrastructure/remote`, `application/remote`, `frontend/features/remote`
**Dépendances amont :** L0, L1, L2, L3, L8.
## Objectif
Développement distant : projet sur autre machine via **SSH**, ou sur une **WSL** depuis Windows. Transparence local/distant via la stratégie `RemoteHost`.
## Périmètre (DEV)
- `RemoteHost` : `LocalHost`, `SshHost` (russh/ssh2), `WslHost` (`wsl.exe`) — fabriquent FS/PTY/Spawner adaptés.
- Adapters distants : `SshFileSystem` (SFTP), `SshPtyAdapter`, `SshProcessSpawner` ; `WslFileSystem`, `WslPtyAdapter`, `WslProcessSpawner`.
- `RemoteGitRepository` (git CLI via `ProcessSpawner`) en fallback distant.
- Use case `ConnectRemote` (valide l'accès au root). Front : UI de connexion SSH/WSL.
## Périmètre (TEST)
- Substituabilité (Liskov) : un use case marche identiquement quel que soit le `RemoteHost` (tests avec hosts mockés).
- Intégration SSH/WSL : tests `#[ignore]` gated derrière feature/env (CI conditionnelle).
- Front : feature remote avec gateway mock.
## Definition of Done
- `cargo test` (remote, hors `#[ignore]`) + `vitest` verts ; connexion SSH et WSL démontrée en dev manuel.
## Avancement
### ✅ Socle (vert)
- **Domaine** : `RemoteRef` (Local/Ssh/Wsl) + port `RemoteHost` (fabrique FS/PTY/Spawner) déjà en place (L0).
- **Infra** (`infrastructure/remote/mod.rs`) : `LocalHost` (fabrique les adapters locaux ; `connect` = no-op) + sélecteur `remote_host(&RemoteRef)` (Local → `LocalHost` ; SSH/WSL → `RemoteError::Connection` explicite « not yet supported »). 4 tests d'intégration.
- **Application** (`application/remote/`) : `ConnectRemote` (établit l'hôte, valide que le root existe via le FS de l'hôte, émet `RemoteConnected`). 3 tests **Liskov** (comportement identique Local/Ssh/Wsl avec hôte mocké ; échec connexion → REMOTE ; root absent → NOT_FOUND).
- `cargo test --workspace` : **317 verts, 0 régression** ; clippy clean.
### ⏳ Reste à faire (gated / non vérifiable dans cet environnement)
- Adapters distants **SSH** (`SshHost`/`SshFileSystem` SFTP/`SshPtyAdapter`/`SshProcessSpawner` via russh ou ssh2 — décision auth à figer, cf. spikes) et **WSL** (`WslHost` + adapters préfixant `wsl.exe`), avec tests d'intégration `#[ignore]` derrière feature/env.
- `RemoteGitRepository` (git CLI distant via `ProcessSpawner`) + `pull`/`push` (reportés depuis L8).
- IPC `app-tauri` `connect_remote` + front feature remote (UI connexion SSH/WSL).
## Spikes (cf. ARCHITECTURE §13)
- Auth SSH (clé/agent/mot de passe/known_hosts) ; choix russh(rustls) vs ssh2(OpenSSL) — impacte l'AppImage.
- Conversion de chemins WSL `/mnt/...``\\wsl$\...` ; perf I/O cross-boundary.
- Git sur FS distant (perf, parsing CLI).

View File

@ -0,0 +1,39 @@
# LD — Design System (UI kit maison)
**Binôme :** `dev-ui` / `test-ui`
**Zones :** `frontend/src/shared` (UI kit), `frontend` (config Tailwind), `frontend/src/app` (app shell)
**Dépendances amont :** L1 (frontend ports/adapters/DI en place).
**Position :** inséré **après L6** (décision produit : voir CONTEXT, lot transverse), consommé ensuite au fil de l'eau par chaque feature.
## Objectif
Doter IdeA d'un **design system maison** cohérent et d'un **thème sombre** par défaut (c'est un IDE), pour que chaque feature cesse de bricoler des styles inline et consomme des composants réutilisables.
## Stack (validée)
- **Tailwind v4** + `@tailwindcss/vite` (CSS-first, `@theme`), composants **maison** dans `shared/ui` (pas de lib de composants tierce).
- Tokens de design (couleurs/typo/espacements/rayons) en variables de thème ; helper `cn()` pour composer les classes.
## Périmètre (DEV)
- Config Tailwind (plugin Vite, feuille de thème globale, tokens, dark par défaut).
- `shared/ui` : composants de base — `Button`, `IconButton`, `Input`, `Panel`/`Card`, `Tabs`, `Toolbar`, `Spinner`, `Field`. Helper `cn`.
- **App shell** sombre dans `app/` (remplace les styles inline du smoke-test).
## Périmètre (TEST)
- `cn()` (unitaire).
- Rendu + variantes/états des composants (`Button` variants/disabled, `Input` label/aria, `Tabs` sélection) via RTL/vitest, **sans backend**.
## Definition of Done
- `vitest` vert ; `tsc --noEmit` vert ; le domaine/les ports UI restent inchangés (pur skin).
- Aucune feature ne régresse ; l'app monte avec le thème sombre.
## Hors périmètre
- Re-styling exhaustif de toutes les features (fait au fil de l'eau quand on touche chacune).
## Avancement — ✅ vert
- **Config** : `tailwindcss@4` + `@tailwindcss/vite` ajoutés ; plugin câblé dans `vite.config.ts` ; feuille de thème globale `src/shared/styles/theme.css` (tokens sémantiques sous `@theme`, thème **sombre** par défaut, focus ring), importée une fois dans `app/main.tsx`.
- **UI kit** `src/shared/ui` : `Button` (variants primary/secondary/ghost/danger, sizes, loading), `IconButton`, `Input`, `Field` (label/hint/error + aria), `Panel`, `Tabs` (sélection + close), `Toolbar`, `Spinner`. Helper `cn()`. Baril `@/shared`.
- **App shell** sombre (`app/App.tsx`) : header avec statut backend, plus de styles inline.
- **Consommation** : `features/projects/ProjectsView` migré sur le kit (premier consommateur), hooks d'accessibilité préservés (tests L2 toujours verts).
- **Tests** : `cn` + composants (`Button`/`Input`/`Field`/`Tabs`) via RTL — assertions sur rôles/aria, pas sur les classes Tailwind (le design peut évoluer sans casser la suite).
**Vérifs** : `tsc --noEmit` vert · `vitest` **103 tests verts** (14 fichiers) · `vite build` OK (Tailwind compile, CSS 22.8 kB).

71
agents-dev/README.md Normal file
View File

@ -0,0 +1,71 @@
# Agents de développement IdeA — Protocole commun
> Ces agents servent à **développer l'IDE IdeA lui-même**. Ils ne sont pas la feature produit « agents IA » de l'application.
> Référence produit/archi : [`../CONTEXT.md`](../CONTEXT.md) · [`../ARCHITECTURE.md`](../ARCHITECTURE.md).
## Composition de l'équipe
Chaque **lot livrable** (L0…L11) est confié à un **binôme** :
- un **agent de développement** (écrit le code),
- un **agent de test** appairé (écrit et exécute les tests unitaires).
Un fichier `Lx-*.md` par binôme décrit son périmètre, ses ports/adapters, ses dépendances et sa *definition of done*.
| Lot | Fichier | Statut |
|---|---|---|
| L0 | [L0-core-domain.md](L0-core-domain.md) | ✅ **vert** (84 tests) |
| L1 | [L1-ipc-bridge.md](L1-ipc-bridge.md) | ✅ **vert** (46 tests) |
| L2 | [L2-projects.md](L2-projects.md) | ✅ **vert** (26 tests) |
| L3 | [L3-terminals.md](L3-terminals.md) | ✅ **vert** (61 tests) |
| L4 | [L4-layout.md](L4-layout.md) | ✅ **vert** (52 tests) |
| L5 | [L5-ai-runtime.md](L5-ai-runtime.md) | ✅ **vert** (69 tests) |
| L6 | [L6-agents.md](L6-agents.md) | ✅ **vert** (domaine+app+infra+IPC+front · activer un agent ouvre son terminal) |
| LD | [LD-design-system.md](LD-design-system.md) | ✅ **vert** (Tailwind v4 + UI kit maison, thème sombre) |
| L7 | [L7-templates.md](L7-templates.md) | ✅ **vert** (backend + IPC + front · drift & sync) |
| L8 | [L8-git.md](L8-git.md) | ✅ **vert** (backend + IPC + front · git local libgit2) |
| L9 | [L9-remote.md](L9-remote.md) | 🟡 **socle vert** (LocalHost + ConnectRemote, Liskov) · SSH/WSL gated à venir |
| L10 | [L10-windows.md](L10-windows.md) | 🟡 **backend + IPC verts** (move-tab + `WebviewWindow`) · détach UI fait avec la refonte L11 |
| L11 | [L11-packaging.md](L11-packaging.md) | 🟡 AppImage Linux **OK** · refonte disposition IDE en cours · Windows/CI à venir |
| L12 | [L12-skills.md](L12-skills.md) | ⬜ **Skills** — entité + store + assignation + injection convention file + UI |
| L13 | [L13-orchestrator.md](L13-orchestrator.md) | ⬜ **OrchestratorApi** — file-watcher, spawn depuis agent ou UI, même résultat |
## Cycle dev ↔ test (obligatoire, cf. CONTEXT §3)
```
1. Archi valide le découpage et les contrats (ports/interfaces) du lot.
2. Agent DEV écrit le code de la feature.
3. Agent TEST écrit les tests unitaires + les exécute.
4a. Vert → feature validée, lot suivant.
4b. Rouge → rapport d'erreurs structuré → retour DEV → correction → retour étape 3.
```
**Règle d'or** : aucune feature n'est « terminée » tant que ses tests ne passent pas. Les résultats sont relayés fidèlement (sortie réelle des tests).
## Definition of Done (commune à tous les lots)
- [ ] Code conforme à l'architecture hexagonale et SOLID (cf. ARCHITECTURE §1).
- [ ] Le `domain` reste pur (aucune dépendance I/O qui y entre).
- [ ] Les use cases ne parlent qu'aux **ports**, jamais aux adapters concrets.
- [ ] Tests unitaires écrits et **verts** : `cargo test -p <crate>` (Rust) et/ou `vitest` (front).
- [ ] Domaine/application testés **sans I/O** (ports mockés : `mockall` ou fakes).
- [ ] Pas de `new ConcreteAdapter` ailleurs que dans la composition root (`app-tauri`).
- [ ] Pas de régression sur les lots déjà verts.
- [ ] Code lisible, cohérent avec le style existant.
## Format du rapport d'erreurs (TEST → DEV)
```
LOT: Lx
TEST EN ÉCHEC: <nom du test>
ATTENDU: <…>
OBTENU: <…>
SORTIE: <extrait pertinent de cargo test / vitest>
HYPOTHÈSE: <cause probable, si identifiable>
```
## Conventions techniques
- Rust : workspace multi-crate (`crates/domain`, `application`, `infrastructure`, `app-tauri`).
- Erreurs typées par port/use case ; `async` via `async_trait` côté ports I/O.
- Déterminisme des tests via ports `Clock`/`IdGenerator` (impl `Fixed`/`Seq` en test).
- Front : `domain`/`ports` purs (Vitest), `features` testés avec gateways **mock** (RTL).

View File

@ -0,0 +1,49 @@
[package]
name = "app-tauri"
version = "0.1.0"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
description = "IdeA — Tauri v2 shell: composition root (DI), IPC commands/events, PTY↔Channel bridge."
# The library carries all the wiring so it is unit-testable; the binary is a
# thin entry point.
[lib]
name = "app_tauri_lib"
crate-type = ["lib", "cdylib", "staticlib"]
[[bin]]
name = "app-tauri"
path = "src/main.rs"
[build-dependencies]
tauri-build = { workspace = true }
[dependencies]
domain = { workspace = true }
application = { workspace = true }
infrastructure = { workspace = true }
tauri = { workspace = true }
tauri-plugin-dialog = { workspace = true }
# `io-std` (on top of the workspace features) gives the headless `mcp-server`
# bridge access to `tokio::io::{stdin,stdout}` without widening tokio elsewhere.
tokio = { workspace = true, features = ["io-std", "rt"] }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
uuid = { workspace = true }
# Cross-OS local IPC for the MCP loopback transport (M5a): Unix domain socket
# (Linux/macOS) + Windows named pipe behind one async API, no network port. Pulled
# in only here (the transport is an app-tauri/infra concern); its `tokio` feature
# keeps us off tokio's `net` feature workspace-wide.
interprocess = { version = "2.4", features = ["tokio"] }
[features]
# Passthrough toggles to enable the real embedders in an IDE build. OFF by default
# (founding posture: `none` ⇒ naïve recall, zero dependency).
vector-http = ["infrastructure/vector-http"]
vector-onnx = ["infrastructure/vector-onnx"]
[dev-dependencies]
uuid = { workspace = true }
async-trait = { workspace = true }

View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build();
}

View File

@ -0,0 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default capability set for the IdeA main window.",
"windows": ["main"],
"permissions": ["core:default", "dialog:allow-open"]
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"default":{"identifier":"default","description":"Default capability set for the IdeA main window.","local":true,"windows":["main"],"permissions":["core:default","dialog:allow-open"]}}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

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