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:
@ -113,6 +113,34 @@ pub struct AgentLaunchFailedDto {
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum DomainEventDto {
|
||||
/// A requester-scoped agent self-restart was accepted.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentRestartRequested {
|
||||
agent_id: String,
|
||||
restart_id: String,
|
||||
reason: Option<String>,
|
||||
},
|
||||
/// A self-restart changed phase.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentRestartPhaseChanged {
|
||||
agent_id: String,
|
||||
restart_id: String,
|
||||
phase: String,
|
||||
},
|
||||
/// A self-restart completed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentRestartCompleted {
|
||||
agent_id: String,
|
||||
restart_id: String,
|
||||
},
|
||||
/// A self-restart failed after scheduling.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentRestartFailed {
|
||||
agent_id: String,
|
||||
restart_id: String,
|
||||
phase: String,
|
||||
message: String,
|
||||
},
|
||||
/// A project was created.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ProjectCreated {
|
||||
@ -787,6 +815,42 @@ fn rendezvous_conversation_id(rendezvous: &Option<RendezvousContext>) -> Option<
|
||||
impl From<&DomainEvent> for DomainEventDto {
|
||||
fn from(e: &DomainEvent) -> Self {
|
||||
match e {
|
||||
DomainEvent::AgentRestartRequested {
|
||||
agent_id,
|
||||
restart_id,
|
||||
reason,
|
||||
} => Self::AgentRestartRequested {
|
||||
agent_id: agent_id.to_string(),
|
||||
restart_id: restart_id.clone(),
|
||||
reason: reason.clone(),
|
||||
},
|
||||
DomainEvent::AgentRestartPhaseChanged {
|
||||
agent_id,
|
||||
restart_id,
|
||||
phase,
|
||||
} => Self::AgentRestartPhaseChanged {
|
||||
agent_id: agent_id.to_string(),
|
||||
restart_id: restart_id.clone(),
|
||||
phase: restart_phase_label(*phase).to_owned(),
|
||||
},
|
||||
DomainEvent::AgentRestartCompleted {
|
||||
agent_id,
|
||||
restart_id,
|
||||
} => Self::AgentRestartCompleted {
|
||||
agent_id: agent_id.to_string(),
|
||||
restart_id: restart_id.clone(),
|
||||
},
|
||||
DomainEvent::AgentRestartFailed {
|
||||
agent_id,
|
||||
restart_id,
|
||||
phase,
|
||||
message,
|
||||
} => Self::AgentRestartFailed {
|
||||
agent_id: agent_id.to_string(),
|
||||
restart_id: restart_id.clone(),
|
||||
phase: restart_phase_label(*phase).to_owned(),
|
||||
message: message.clone(),
|
||||
},
|
||||
DomainEvent::ProjectCreated { project_id } => Self::ProjectCreated {
|
||||
project_id: project_id.to_string(),
|
||||
},
|
||||
@ -1312,6 +1376,15 @@ impl From<&DomainEvent> for DomainEventDto {
|
||||
}
|
||||
}
|
||||
|
||||
fn restart_phase_label(phase: domain::events::AgentRestartPhase) -> &'static str {
|
||||
match phase {
|
||||
domain::events::AgentRestartPhase::Stopping => "stopping",
|
||||
domain::events::AgentRestartPhase::ReleasingInput => "releasingInput",
|
||||
domain::events::AgentRestartPhase::Launching => "launching",
|
||||
domain::events::AgentRestartPhase::Resuming => "resuming",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -1336,6 +1409,32 @@ mod tests {
|
||||
ConversationId::from_uuid(uuid::Uuid::from_u128(n))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_restart_events_expose_stable_camel_case_contract() {
|
||||
let id = agent(288);
|
||||
let phase = DomainEventDto::from(&DomainEvent::AgentRestartPhaseChanged {
|
||||
agent_id: id,
|
||||
restart_id: "restart-288".to_owned(),
|
||||
phase: domain::events::AgentRestartPhase::ReleasingInput,
|
||||
});
|
||||
let phase = serde_json::to_value(phase).expect("serialisable");
|
||||
assert_eq!(phase["type"], "agentRestartPhaseChanged");
|
||||
assert_eq!(phase["agentId"], id.to_string());
|
||||
assert_eq!(phase["restartId"], "restart-288");
|
||||
assert_eq!(phase["phase"], "releasingInput");
|
||||
|
||||
let failed = DomainEventDto::from(&DomainEvent::AgentRestartFailed {
|
||||
agent_id: id,
|
||||
restart_id: "restart-288".to_owned(),
|
||||
phase: domain::events::AgentRestartPhase::Launching,
|
||||
message: "launch failed".to_owned(),
|
||||
});
|
||||
let failed = serde_json::to_value(failed).expect("serialisable");
|
||||
assert_eq!(failed["type"], "agentRestartFailed");
|
||||
assert_eq!(failed["phase"], "launching");
|
||||
assert_eq!(failed["message"], "launch failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_server_status_changed_relays_ready_to_dto_and_wire() {
|
||||
let dto = DomainEventDto::from(&DomainEvent::ModelServerStatusChanged {
|
||||
|
||||
Reference in New Issue
Block a user