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:
@ -53,8 +53,9 @@ use domain::{
|
||||
use uuid::Uuid;
|
||||
|
||||
use application::{
|
||||
CloseTerminal, CreateAgentFromScratch, CreateSkill, LaunchAgent, ListAgents,
|
||||
OrchestratorService, StructuredSessions, TerminalSessions, UpdateAgentContext,
|
||||
AgentRestartScheduled, AgentSelfRestartScheduler, AppError, CloseTerminal,
|
||||
CreateAgentFromScratch, CreateSkill, LaunchAgent, ListAgents, OrchestratorService,
|
||||
StructuredSessions, TerminalSessions, UpdateAgentContext,
|
||||
};
|
||||
use infrastructure::orchestrator::mcp::jsonrpc::error_codes;
|
||||
use infrastructure::orchestrator::mcp::tools::classified_tool_names;
|
||||
@ -578,6 +579,25 @@ fn capturing_events() -> (
|
||||
(publish, captured)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FakeSelfRestart {
|
||||
calls: Mutex<Vec<(ProjectId, AgentId, Option<String>)>>,
|
||||
}
|
||||
|
||||
impl AgentSelfRestartScheduler for FakeSelfRestart {
|
||||
fn schedule(
|
||||
&self,
|
||||
project: &Project,
|
||||
agent: AgentId,
|
||||
reason: Option<String>,
|
||||
) -> Result<AgentRestartScheduled, AppError> {
|
||||
self.calls.lock().unwrap().push((project.id, agent, reason));
|
||||
Ok(AgentRestartScheduled {
|
||||
restart_id: "restart-test-1".to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --- JSON-RPC framing helpers ---------------------------------------------
|
||||
|
||||
/// A `tools/call` request envelope for `tool` with `arguments`.
|
||||
@ -762,6 +782,7 @@ async fn tools_list_advertises_the_idea_tools_with_schemas() {
|
||||
"idea_ask_agent",
|
||||
"idea_ask_agents",
|
||||
"idea_run_in_background",
|
||||
"idea_restart_self",
|
||||
"idea_launch_agent",
|
||||
"idea_stop_agent",
|
||||
"idea_update_context",
|
||||
@ -812,8 +833,8 @@ async fn tools_list_advertises_the_idea_tools_with_schemas() {
|
||||
assert!(!names.contains(&"idea_reply"));
|
||||
assert_eq!(
|
||||
tools.len(),
|
||||
49,
|
||||
"exactly the forty-nine exposed idea_* tools; got {names:?}"
|
||||
50,
|
||||
"exactly the fifty exposed idea_* tools; got {names:?}"
|
||||
);
|
||||
|
||||
// Every tool advertises an object input schema.
|
||||
@ -988,6 +1009,33 @@ async fn cross_project_actions_are_advertised_only_when_allowed_and_denial_is_ac
|
||||
// 2. tools/call → the right OrchestratorCommand (observed through the fakes)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[tokio::test]
|
||||
async fn restart_self_returns_scheduled_payload_and_uses_handshake_identity() {
|
||||
let (base, _sessions) = build_service(FakeContexts::new());
|
||||
let restart = Arc::new(FakeSelfRestart::default());
|
||||
let service = Arc::try_unwrap(base)
|
||||
.unwrap_or_else(|_| panic!("freshly built service must be uniquely owned"))
|
||||
.with_self_restart(Arc::clone(&restart) as Arc<dyn AgentSelfRestartScheduler>);
|
||||
let requester = AgentId::from_uuid(Uuid::from_u128(288));
|
||||
let response = server(Arc::new(service))
|
||||
.for_requester(requester.to_string())
|
||||
.handle_raw(&tools_call(
|
||||
288,
|
||||
"idea_restart_self",
|
||||
json!({ "reason": "new plugin" }),
|
||||
))
|
||||
.await
|
||||
.expect("restart call replies before detached teardown");
|
||||
let result = response.result.expect("successful tool result");
|
||||
let payload: Value = serde_json::from_str(result_text(&result)).unwrap();
|
||||
assert_eq!(payload["restartId"], "restart-test-1");
|
||||
assert_eq!(payload["status"], "scheduled");
|
||||
assert_eq!(
|
||||
restart.calls.lock().unwrap().as_slice(),
|
||||
&[(project().id, requester, Some("new plugin".to_owned()))]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn general_agent_without_mcp_override_is_read_only_for_tools_call() {
|
||||
let contexts = FakeContexts::new();
|
||||
|
||||
Reference in New Issue
Block a user