feat(mcp,orchestrator,frontend): tool idea_restart_self, redemarrage autonome d'un agent — #288 (tool core self-only via requester MCP handshake, retour immediat {restartId, status: scheduled} puis orchestration detachee: snapshot reprise, stop PTY/structured, liberation Busy/mailbox, relance LaunchAgent meme conversation_id fresh_start=false, injection interne invisible; events domain/wire agentRestartRequested/PhaseChanged/Completed/Failed phases stopping/releasingInput/launching/resuming; CLI custom: separateurs exacts redemarrage de l'agent + etat termine/echoue, deduplication (restartId, event), verrouillage composer; TUI native: overlay AgentRestartOverlay sans injection xterm, progress/annonces projetes jusqu'a idle; QA verte: mcp_server restart_self 1/1, restart-self 3/3, backend self_restart_contract + events wire 2/2, frontend 3 suites 92/92, npm build, cargo check --workspace, fmt --check, diff --check; reserves hors perimetre: web-server 103/110 fixture PTY preexistante, 34 echecs npm test globaux listProjectSettings/web hors #288; scripts/ hors perimetre)

This commit is contained in:
2026-09-08 22:04:18 +02:00
parent ee259dc93a
commit 63537572b8
23 changed files with 1207 additions and 37 deletions

View File

@ -260,6 +260,13 @@ pub struct OrchestratorRequest {
/// proof the request was well-formed (Parse, don't validate).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OrchestratorCommand {
/// Schedule a restart of the MCP requester itself.
RestartSelf {
/// Requesting agent, strictly parsed from the MCP handshake identity.
agent: AgentId,
/// Optional diagnostic reason. It is never used as an identity or target.
reason: Option<String>,
},
/// Create the agent if unknown (with `profile` + optional initial context),
/// then launch it — exactly as the UI would.
SpawnAgent {
@ -476,6 +483,15 @@ impl OrchestratorRequest {
pub fn validate(&self) -> Result<OrchestratorCommand, OrchestratorError> {
let action = self.action_name()?;
match action {
"agent.restart_self" => Ok(OrchestratorCommand::RestartSelf {
agent: self.require_from(action)?,
reason: self
.task
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_owned),
}),
"spawn_agent" => Ok(OrchestratorCommand::SpawnAgent {
name: self.require_name(action)?,
profile: Some(self.require("profile", action, self.profile.as_deref())?),