feat(agent): bind transport S-MCP — outils idea_* vivants de bout en bout (M5a-e) — §14.3.1

Dernier kilomètre de l'orchestration native : une CLI MCP réellement lancée
joint le serveur MCP du projet et ses outils idea_* aboutissent au vrai dispatch.

- M5a endpoint loopback par projet (interprocess UDS/named pipe, source unique
  mcp_endpoint, cleanup au close) — zéro port réseau, AppImage/SSH-safe.
- M5b sous-commande `idea mcp-server` : pont stdio↔loopback headless (avant init
  Tauri), handshake {"project","requester"}, endpoint-absent borné, EOF propre.
- M5c McpServerHandle boucle accept + serve_peer par pair ; requester réel
  propagé jusqu'à OrchestratorRequestProcessed (fin du "mcp" figé) ; isolation
  des pairs ; terminaison propre.
- M5d apply_mcp_config écrit la déclaration réelle (current_exe + --endpoint
  mcp_endpoint + --project simple-uuid + --requester) ; McpRuntime injecté comme
  donnée (application ne dépend pas de app-tauri).
- M5e smoke e2e sur vrai loopback : list/ask inline, cible PTY → erreur typée,
  JSON malformé → pas de panic, requester propagé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 18:09:41 +02:00
parent 6ca519b815
commit cf89b3b9a5
17 changed files with 3281 additions and 44 deletions

View File

@ -16,6 +16,7 @@ use application::{
DetectAgentDriftInput, GetMemoryInput, GitBranchesInput, GitCheckoutInput, GitCommitInput,
GitGraphInput, GitInitInput, GitLogInput, GitStagePathInput, GitStatusInput,
InspectConversationInput, LaunchAgentInput, ListAgentsInput, ListLayoutsInput, LiveSessions,
McpRuntime,
ListMemoriesInput, ListResumableAgentsInput, ListSkillsInput, LoadLayoutInput, MutateLayoutInput,
OpenProjectInput,
ReadAgentContextInput, ReadMemoryIndexInput, ReadProjectContextInput, RecallMemoryInput,
@ -1029,6 +1030,22 @@ pub async fn launch_agent(
// already-live agent is refused).
let node_id = request.node_id.as_deref().map(parse_node_id).transpose()?;
// Compose the MCP runtime (M5d): inject the OS/runtime facts the application
// layer cannot compute. The endpoint comes from the **single source of truth**
// (`mcp_endpoint`), the same value `ensure_mcp_server` binds the listener on
// (cadrage v5 §2). The `--project` is the hyphen-free 32-hex `simple` form the
// M5c handshake guard (`serve_peer`) compares against; the `--requester` is the
// launching agent's id. A missing executable path (should not happen) degrades
// to no runtime ⇒ apply_mcp_config writes the minimal declaration.
let mcp_runtime = std::env::current_exe().ok().map(|exe| McpRuntime {
exe: exe.to_string_lossy().into_owned(),
endpoint: crate::mcp_endpoint::mcp_endpoint(&project.id)
.as_cli_arg()
.to_owned(),
project_id: project.id.as_uuid().simple().to_string(),
requester: agent_id.to_string(),
});
let output = state
.launch_agent
.execute(LaunchAgentInput {
@ -1040,6 +1057,7 @@ pub async fn launch_agent(
// Resume id is a property of the hosting cell; the frontend passes the
// leaf's current conversation id here.
conversation_id: request.conversation_id.clone(),
mcp_runtime,
})
.await
.map_err(ErrorDto::from)?;