feat(agent): orchestration v3 — surface MCP model-agnostic (M0→M4) — §14.3

Expose l'orchestration IdeA comme serveur MCP par-dessus le même
OrchestratorService::dispatch, avec repli fichier .ideai/requests pour les
CLI sans MCP. v3 réduite à la surface MCP : la messagerie inter-agents et la
corrélation requête↔réponse étaient déjà résolues par §17 (send_blocking).

- M0 capacité MCP sur le profil (McpCapability/McpConfigStrategy/McpTransport)
- M1 injection conf MCP au LaunchAgent + prose adaptée selon la surface
- M2 serveur/adapter MCP (JSON-RPC 2.0 maison ; outils idea_*) + ListAgents
- M3 câblage par projet (registre mcp_servers jumeau du watcher)
- M4 observabilité UI : OrchestratorRequestProcessed.source = file|mcp + badge

Trois portes d'entrée (fichier, MCP, UI) → un seul dispatch ; aucun nouveau
port applicatif ; MCP confiné à l'adapter infra. Tous lots verts (cycle §3).
Cadrage : .ideai/briefs/orchestration-v3-cadrage.md ; ARCHITECTURE.md §14.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 12:53:31 +02:00
parent 97daf3fae5
commit 37e72747d3
30 changed files with 3646 additions and 27 deletions

View File

@ -138,6 +138,64 @@ fn domain_event_dto_maps_pty_output_bytes() {
assert_eq!(v["bytes"], json!([1, 2, 3]));
}
// ---------------------------------------------------------------------------
// M4 — orchestration source on `orchestratorRequestProcessed`
// ---------------------------------------------------------------------------
#[test]
fn orchestration_source_dto_serialises_lowercase() {
use app_tauri_lib::events::OrchestrationSourceDto;
use domain::events::OrchestrationSource;
// File → "file"; Mcp → "mcp" (camelCase rename on a unit enum = lowercase).
let file = serde_json::to_value(OrchestrationSourceDto::from(OrchestrationSource::File)).unwrap();
assert_eq!(file, json!("file"));
let mcp = serde_json::to_value(OrchestrationSourceDto::from(OrchestrationSource::Mcp)).unwrap();
assert_eq!(mcp, json!("mcp"));
}
#[test]
fn domain_event_dto_maps_orchestrator_request_processed_with_file_source() {
use domain::events::OrchestrationSource;
let dto = DomainEventDto::from(&DomainEvent::OrchestratorRequestProcessed {
requester_id: "Main".into(),
action: "spawn_agent".into(),
ok: true,
source: OrchestrationSource::File,
});
let v = serde_json::to_value(&dto).unwrap();
assert_eq!(
v,
json!({
"type": "orchestratorRequestProcessed",
"requesterId": "Main",
"action": "spawn_agent",
"ok": true,
"source": "file",
})
);
}
#[test]
fn domain_event_dto_maps_orchestrator_request_processed_with_mcp_source() {
use domain::events::OrchestrationSource;
let dto = DomainEventDto::from(&DomainEvent::OrchestratorRequestProcessed {
requester_id: "mcp".into(),
action: "idea_ask_agent".into(),
ok: false,
source: OrchestrationSource::Mcp,
});
let v = serde_json::to_value(&dto).unwrap();
assert_eq!(v["type"], "orchestratorRequestProcessed");
assert_eq!(v["requesterId"], "mcp");
assert_eq!(v["action"], "idea_ask_agent");
assert_eq!(v["ok"], json!(false));
assert_eq!(v["source"], "mcp", "MCP door must badge as `mcp`");
}
// ---------------------------------------------------------------------------
// Terminal DTOs (L3)
// ---------------------------------------------------------------------------