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:
@ -72,6 +72,7 @@ pub const WRITE_ACTION_TOOLS: &[&str] = &[
|
||||
"idea_run_in_background",
|
||||
"idea_launch_agent",
|
||||
"idea_stop_agent",
|
||||
"idea_restart_self",
|
||||
"idea_update_context",
|
||||
"idea_context_propose",
|
||||
"idea_context_update",
|
||||
@ -155,6 +156,7 @@ pub fn tool_returns_reply(tool: &str) -> bool {
|
||||
| "idea_memory_read"
|
||||
| "idea_skill_read"
|
||||
| "idea_run_in_background"
|
||||
| "idea_restart_self"
|
||||
| "idea_workstate_read"
|
||||
) || is_ticket_tool(tool)
|
||||
|| is_template_tool(tool)
|
||||
@ -193,6 +195,17 @@ pub fn is_plugin_tool(tool: &str) -> bool {
|
||||
#[must_use]
|
||||
pub fn catalogue() -> Vec<ToolDef> {
|
||||
let mut tools = vec![
|
||||
ToolDef {
|
||||
name: "idea_restart_self",
|
||||
description: "Restart this requesting agent and resume its current work with freshly resolved capabilities.",
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reason": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}),
|
||||
},
|
||||
ToolDef {
|
||||
name: "idea_project_list",
|
||||
description: "List known IdeA projects for cross-project agent discovery.",
|
||||
@ -543,6 +556,15 @@ pub fn map_tool_call(
|
||||
// Translate the tool into the wire-level request shape the watcher already
|
||||
// uses (v2 `type`), then let `validate` enforce the invariants once.
|
||||
let request = match name {
|
||||
"idea_restart_self" => {
|
||||
reject_unknown_keys(name, args, &["reason"])?;
|
||||
OrchestratorRequest {
|
||||
request_type: Some("agent.restart_self".to_owned()),
|
||||
requested_by: Some(requester.to_owned()),
|
||||
task: s("reason"),
|
||||
..base()
|
||||
}
|
||||
}
|
||||
"idea_list_agents" => OrchestratorRequest {
|
||||
request_type: Some("agent.list".to_owned()),
|
||||
..base()
|
||||
@ -845,6 +867,51 @@ mod tests {
|
||||
assert!(tool_returns_reply("idea_ask_agents"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restart_self_is_catalogued_and_uses_only_handshake_requester() {
|
||||
let definition = catalogue()
|
||||
.into_iter()
|
||||
.find(|tool| tool.name == "idea_restart_self")
|
||||
.expect("restart-self tool advertised");
|
||||
assert_eq!(
|
||||
tool_access(definition.name),
|
||||
Some(McpToolAccess::WriteAction)
|
||||
);
|
||||
assert!(tool_returns_reply(definition.name));
|
||||
|
||||
let expected = domain::AgentId::from_uuid(uuid::Uuid::parse_str(REQ).unwrap());
|
||||
assert_eq!(
|
||||
map_tool_call(
|
||||
"idea_restart_self",
|
||||
&json!({ "reason": "plugin installed" }),
|
||||
REQ,
|
||||
)
|
||||
.unwrap(),
|
||||
OrchestratorCommand::RestartSelf {
|
||||
agent: expected,
|
||||
reason: Some("plugin installed".to_owned()),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
map_tool_call(
|
||||
"idea_restart_self",
|
||||
&json!({ "target": "someone-else" }),
|
||||
REQ,
|
||||
),
|
||||
Err(ToolMapError::BadArguments("idea_restart_self".to_owned()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restart_self_rejects_missing_or_malformed_requester() {
|
||||
for requester in ["", "not-an-agent-id"] {
|
||||
assert!(matches!(
|
||||
map_tool_call("idea_restart_self", &json!({}), requester),
|
||||
Err(ToolMapError::Invalid(_))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_tools_are_classified_and_return_inline_replies() {
|
||||
for tool in [
|
||||
|
||||
Reference in New Issue
Block a user