Compare commits
58 Commits
0295e1b94a
...
wip/p8c-ch
| Author | SHA1 | Date | |
|---|---|---|---|
| b05d04ab7a | |||
| 27597eb64e | |||
| 46492506e1 | |||
| aa2f67ae89 | |||
| 0f8ba38d51 | |||
| fdcf16c387 | |||
| 4509f0db9d | |||
| d87b8f6ed2 | |||
| 9b053216e3 | |||
| 09cc8f0902 | |||
| e583b2b49f | |||
| 19ba77824f | |||
| c1411d3b69 | |||
| 0bf7a5c43c | |||
| 2a5873dcf0 | |||
| f3046f3dd8 | |||
| 75e4f57a71 | |||
| eca2ba95c4 | |||
| 5f45c22941 | |||
| 2f20fdbab4 | |||
| cf89b3b9a5 | |||
| 6ca519b815 | |||
| 37e72747d3 | |||
| 97daf3fae5 | |||
| 6de4e5a6e0 | |||
| dd1194abe8 | |||
| 5059f37890 | |||
| f4d5727a69 | |||
| 050afa7d24 | |||
| 56913b9053 | |||
| f104682477 | |||
| 751d94dd89 | |||
| 5e10b5eb42 | |||
| 7375f706da | |||
| b82e3e1a40 | |||
| 2433e173a1 | |||
| 62bd5130fb | |||
| 785e9935fd | |||
| 32398827fb | |||
| b39c11a64d | |||
| f3bc3f20d8 | |||
| 2435857cbf | |||
| 98a8b7292a | |||
| 3ed0f6b45f | |||
| d11eaaa8c0 | |||
| b9fd2fb925 | |||
| 9b92259429 | |||
| fbcf7bd436 | |||
| e0c7e1403d | |||
| 480e7c7bbe | |||
| 3be55795a6 | |||
| 2332b7f815 | |||
| 9736c42424 | |||
| 0638ce7c98 | |||
| 0660f52e2b | |||
| 33edbad713 | |||
| 307ae71857 | |||
| 55b3bee2c8 |
8
.cargo/config.toml
Normal 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"
|
||||||
1
.claude/worktrees/agent-a2650e91d2bd39ca2
Submodule
1
.claude/worktrees/agent-aeb1e862ef04b991b
Submodule
49
.gitignore
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# ─── Rust / Cargo ───────────────────────────────────────────────────────────
|
||||||
|
# Build output for the whole workspace (Cargo.lock IS committed — it's an app).
|
||||||
|
/target/
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# ─── Node / frontend ────────────────────────────────────────────────────────
|
||||||
|
# Dependencies and build output (package-lock.json IS committed).
|
||||||
|
frontend/node_modules/
|
||||||
|
frontend/dist/
|
||||||
|
# npm/yarn/pnpm debug logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
.pnpm-store/
|
||||||
|
# Vite / vitest caches
|
||||||
|
frontend/.vite/
|
||||||
|
frontend/coverage/
|
||||||
|
|
||||||
|
# ─── Tauri ──────────────────────────────────────────────────────────────────
|
||||||
|
# Bundles live under target/ (already ignored). Generated icons are committed.
|
||||||
|
.tauri/
|
||||||
|
|
||||||
|
# ─── 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/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
54
.ideai/agents.json
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"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": "c932c770-cf36-4fb2-a966-71bb1644e4b4",
|
||||||
|
"name": "TestConversation",
|
||||||
|
"mdPath": "agents/testconversation.md",
|
||||||
|
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
|
||||||
|
"synchronized": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agentId": "484eff91-60a1-459f-9ebe-c9552cc70447",
|
||||||
|
"name": "NewTest",
|
||||||
|
"mdPath": "agents/newtest.md",
|
||||||
|
"profileId": "664cc20c-47b8-53ad-9351-dce3c09c0de4",
|
||||||
|
"synchronized": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
604
.ideai/agents/architect.md
Normal 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.*
|
||||||
75
.ideai/agents/devbackend.md
Normal 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.
|
||||||
74
.ideai/agents/devfrontend.md
Normal 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.
|
||||||
187
.ideai/agents/main.md
Normal 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
77
.ideai/agents/qa.md
Normal 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.
|
||||||
0
.ideai/agents/testconversation.md
Normal file
515
.ideai/briefs/conversation-pair-cadrage.md
Normal 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.*
|
||||||
88
.ideai/briefs/d4-commandes-bridge-chat.md
Normal 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**.
|
||||||
99
.ideai/briefs/d5-frontend-chat.md
Normal 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**.
|
||||||
104
.ideai/briefs/d6-messagerie-inter-agents.md
Normal 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**.
|
||||||
95
.ideai/briefs/d7-menu-restreint.md
Normal 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**.
|
||||||
48
.ideai/briefs/option1-terminal-mcp-design.md
Normal 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).
|
||||||
280
.ideai/briefs/orchestration-v3-cadrage.md
Normal 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).
|
||||||
85
.ideai/briefs/orchestration-v3-invocation-native.md
Normal 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.
|
||||||
215
.ideai/briefs/orchestration-v5-transport-bind-cadrage.md
Normal 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)`** où `conn_transport` enveloppe le flux loopback. `McpServer` est **déjà** branché à l'`OrchestratorService` du projet.
|
||||||
|
|
||||||
|
### 1.4 Identité de l'appelant (lève le `requester_id = "mcp"` figé)
|
||||||
|
|
||||||
|
`server.rs::publish_processed` tague aujourd'hui `requester_id: "mcp"` (placeholder). Avec stdio-spawn, le pont **connaît l'agent** (le `LaunchAgent` peut injecter `--requester <agent-id>` dans les `args` de la déclaration, comme `--project`). Le pont passe cette identité dans la **poignée de connexion** (premier message de handshake loopback, hors JSON-RPC MCP), et `McpServer::serve` la porte dans son contexte de connexion ⇒ `OrchestratorRequestProcessed.requester_id` devient l'**agent réel**. Observabilité UI exacte (qui a délégué à qui).
|
||||||
|
|
||||||
|
### 1.5 Socket = TODO derrière le même trait
|
||||||
|
|
||||||
|
Le trait `Transport` (jsonrpc.rs) **isole** déjà le serveur du transport. Un `SocketTransport` (TCP/HTTP-stream) reste un **ajout sans toucher `McpServer`** si une CLI l'exige. Non requis pour Claude/Codex ⇒ **hors périmètre v5**, documenté.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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`.
|
||||||
35
.ideai/briefs/validation-reelle-inter-agents.md
Normal 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`.
|
||||||
@ -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é. ✅
|
||||||
@ -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é. ✅"}
|
||||||
70
.ideai/layouts.json
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"activeId": "1af250f0-65ef-4b78-8905-b1746673aee0",
|
||||||
|
"layouts": [
|
||||||
|
{
|
||||||
|
"id": "1af250f0-65ef-4b78-8905-b1746673aee0",
|
||||||
|
"name": "Default",
|
||||||
|
"kind": "terminal",
|
||||||
|
"tree": {
|
||||||
|
"root": {
|
||||||
|
"type": "split",
|
||||||
|
"node": {
|
||||||
|
"id": "56ffe1e4-636c-458d-9ab2-7e278fd45897",
|
||||||
|
"direction": "row",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"type": "leaf",
|
||||||
|
"node": {
|
||||||
|
"id": "c3319a9a-1345-4fa2-b64e-5f3fe00d13d8",
|
||||||
|
"session": "4965c71a-f69f-4c06-90de-ecb81acff710",
|
||||||
|
"agent": "a6ced819-b893-4213-b003-9e9dc79b9641",
|
||||||
|
"agentWasRunning": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weight": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"type": "split",
|
||||||
|
"node": {
|
||||||
|
"id": "8cf1c06e-2654-45a3-bf17-a9c2507da935",
|
||||||
|
"direction": "column",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"type": "leaf",
|
||||||
|
"node": {
|
||||||
|
"id": "69dc2e23-86f5-4770-84c4-b1b4b2c25299",
|
||||||
|
"session": "11fbf6b4-eb62-4420-95e9-feb2ff667c43",
|
||||||
|
"agent": "c932c770-cf36-4fb2-a966-71bb1644e4b4",
|
||||||
|
"agentWasRunning": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weight": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"type": "leaf",
|
||||||
|
"node": {
|
||||||
|
"id": "b9251e74-3bd5-43ee-90e5-e6bb87faab38",
|
||||||
|
"session": "69a3bf52-05ef-45c0-badf-26b0d8224f0e",
|
||||||
|
"agent": "aefdbd61-e3d4-4bc1-9f42-c259446a97b5",
|
||||||
|
"agentWasRunning": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weight": 1.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weight": 1.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
.ideai/memory/MEMORY.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# 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).
|
||||||
106
.ideai/memory/agent-context-memory-and-profile-handoff.md
Normal 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.
|
||||||
206
.ideai/memory/idea-product-directives-main-handoff.md
Normal 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.
|
||||||
51
.ideai/memory/mcp-bridge-and-delegation-runtime-notes.md
Normal 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]].
|
||||||
273
.ideai/memory/remaining-work-idea-agent-control-ide.md
Normal 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".
|
||||||
9
.ideai/project.json
Normal 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
187
CLAUDE.md
Normal 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*
|
||||||
187
CONTEXT.md
Normal 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*
|
||||||
6126
Cargo.lock
generated
Normal file
35
Cargo.toml
Normal 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"
|
||||||
54
agents-dev/L0-core-domain.md
Normal 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.
|
||||||
44
agents-dev/L1-ipc-bridge.md
Normal 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
@ -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.
|
||||||
45
agents-dev/L11-packaging.md
Normal 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
@ -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).
|
||||||
35
agents-dev/L13-orchestrator.md
Normal 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
@ -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).
|
||||||
25
agents-dev/L3-terminals.md
Normal 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
@ -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.
|
||||||
22
agents-dev/L5-ai-runtime.md
Normal 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
@ -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).
|
||||||
42
agents-dev/L7-templates.md
Normal 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
@ -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
@ -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).
|
||||||
39
agents-dev/LD-design-system.md
Normal 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
@ -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).
|
||||||
49
crates/app-tauri/Cargo.toml
Normal 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 }
|
||||||
3
crates/app-tauri/build.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build();
|
||||||
|
}
|
||||||
7
crates/app-tauri/capabilities/default.json
Normal 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"]
|
||||||
|
}
|
||||||
1
crates/app-tauri/gen/schemas/acl-manifests.json
Normal file
1
crates/app-tauri/gen/schemas/capabilities.json
Normal 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"]}}
|
||||||
2358
crates/app-tauri/gen/schemas/desktop-schema.json
Normal file
2358
crates/app-tauri/gen/schemas/linux-schema.json
Normal file
BIN
crates/app-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
crates/app-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
crates/app-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
crates/app-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
crates/app-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
crates/app-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
crates/app-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
crates/app-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
crates/app-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
crates/app-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
crates/app-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
crates/app-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
crates/app-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
crates/app-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@ -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>
|
||||||
BIN
crates/app-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
BIN
crates/app-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
crates/app-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
BIN
crates/app-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
crates/app-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
BIN
crates/app-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
BIN
crates/app-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
BIN
crates/app-tauri/icons/icon.icns
Normal file
BIN
crates/app-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
crates/app-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
crates/app-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
crates/app-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
crates/app-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
crates/app-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
crates/app-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |