mirror of
https://github.com/RightNow-AI/openfang.git
synced 2026-08-14 08:52:02 +00:00
hand stop
This commit is contained in:
@@ -7077,6 +7077,10 @@ pub async fn compact_session(
|
||||
}
|
||||
|
||||
/// POST /api/agents/{id}/stop — Cancel an agent's current LLM run.
|
||||
///
|
||||
/// If the agent is owned by an active hand instance, the hand instance is
|
||||
/// also deactivated. Otherwise the hand stays registered as `Active` and the
|
||||
/// user cannot re-activate it via the wizard (issue #1164).
|
||||
pub async fn stop_agent(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
@@ -7090,6 +7094,33 @@ pub async fn stop_agent(
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// If this agent is the agent of an active hand instance, deactivate the
|
||||
// hand entirely — which also kills the agent and cancels the run. This
|
||||
// matches what users expect when they click Stop on a hand-owned agent.
|
||||
if let Some(instance) = state.kernel.hand_registry.find_by_agent(agent_id) {
|
||||
match state.kernel.deactivate_hand(instance.instance_id) {
|
||||
Ok(()) => {
|
||||
return (
|
||||
StatusCode::OK,
|
||||
Json(serde_json::json!({
|
||||
"status": "ok",
|
||||
"message": "Hand deactivated",
|
||||
"hand_deactivated": true,
|
||||
"hand_id": instance.hand_id,
|
||||
"instance_id": instance.instance_id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({"error": format!("{e}")})),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match state.kernel.stop_agent_run(agent_id) {
|
||||
Ok(true) => (
|
||||
StatusCode::OK,
|
||||
|
||||
@@ -1025,15 +1025,30 @@ async fn handle_command(
|
||||
serde_json::json!({"type": "error", "content": format!("Compaction failed: {e}")})
|
||||
}
|
||||
},
|
||||
"stop" => match state.kernel.stop_agent_run(agent_id) {
|
||||
Ok(true) => {
|
||||
serde_json::json!({"type": "command_result", "command": cmd, "message": "Run cancelled."})
|
||||
"stop" => {
|
||||
// If this agent is owned by an active hand instance, deactivate the
|
||||
// hand entirely so the user can re-activate it (issue #1164).
|
||||
if let Some(instance) = state.kernel.hand_registry.find_by_agent(agent_id) {
|
||||
match state.kernel.deactivate_hand(instance.instance_id) {
|
||||
Ok(()) => serde_json::json!({
|
||||
"type": "command_result",
|
||||
"command": cmd,
|
||||
"message": format!("Hand '{}' deactivated.", instance.hand_id),
|
||||
}),
|
||||
Err(e) => serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")}),
|
||||
}
|
||||
} else {
|
||||
match state.kernel.stop_agent_run(agent_id) {
|
||||
Ok(true) => {
|
||||
serde_json::json!({"type": "command_result", "command": cmd, "message": "Run cancelled."})
|
||||
}
|
||||
Ok(false) => {
|
||||
serde_json::json!({"type": "command_result", "command": cmd, "message": "No active run to cancel."})
|
||||
}
|
||||
Err(e) => serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")}),
|
||||
}
|
||||
}
|
||||
Ok(false) => {
|
||||
serde_json::json!({"type": "command_result", "command": cmd, "message": "No active run to cancel."})
|
||||
}
|
||||
Err(e) => serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")}),
|
||||
},
|
||||
}
|
||||
"model" => {
|
||||
if args.is_empty() {
|
||||
if let Some(entry) = state.kernel.registry.get(agent_id) {
|
||||
|
||||
@@ -8125,6 +8125,69 @@ mod tests {
|
||||
kernel.shutdown();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Issue #1164: Agent Stop on a hand-owned agent must also deactivate the
|
||||
// hand instance, otherwise the hand stays Active and the user cannot
|
||||
// re-activate it (wizard fails with 400 "Hand already active").
|
||||
// ----------------------------------------------------------------------
|
||||
#[test]
|
||||
fn test_hand_owned_agent_stop_clears_hand_for_reactivation() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let home_dir = tmp.path().join("openfang-kernel-hand-stop-test");
|
||||
std::fs::create_dir_all(&home_dir).unwrap();
|
||||
|
||||
let config = KernelConfig {
|
||||
home_dir: home_dir.clone(),
|
||||
data_dir: home_dir.join("data"),
|
||||
..KernelConfig::default()
|
||||
};
|
||||
let kernel = OpenFangKernel::boot_with_config(config).expect("kernel boots");
|
||||
|
||||
// Activate a hand and grab its agent id (mirrors what the wizard does).
|
||||
let instance = kernel
|
||||
.activate_hand("lead", HashMap::new(), None)
|
||||
.expect("lead hand should activate");
|
||||
let agent_id = instance.agent_id.expect("lead hand agent id");
|
||||
let first_instance_id = instance.instance_id;
|
||||
|
||||
// Sanity: hand is Active and re-activation is rejected.
|
||||
assert!(kernel
|
||||
.activate_hand("lead", HashMap::new(), None)
|
||||
.is_err());
|
||||
|
||||
// Simulate what POST /api/agents/{id}/stop now does for a hand-owned
|
||||
// agent: look up the instance and deactivate the hand (which also
|
||||
// kills the agent and cancels any running task).
|
||||
let owning = kernel
|
||||
.hand_registry
|
||||
.find_by_agent(agent_id)
|
||||
.expect("active hand owning the agent");
|
||||
assert_eq!(owning.instance_id, first_instance_id);
|
||||
kernel
|
||||
.deactivate_hand(owning.instance_id)
|
||||
.expect("deactivate via stop path");
|
||||
|
||||
// The hand instance must be gone now — re-activation must succeed.
|
||||
assert!(kernel.hand_registry.find_by_agent(agent_id).is_none());
|
||||
let active: Vec<_> = kernel
|
||||
.hand_registry
|
||||
.list_instances()
|
||||
.into_iter()
|
||||
.filter(|i| i.hand_id == "lead")
|
||||
.collect();
|
||||
assert!(
|
||||
active.is_empty(),
|
||||
"no lead instances should remain after stop",
|
||||
);
|
||||
|
||||
let second = kernel
|
||||
.activate_hand("lead", HashMap::new(), None)
|
||||
.expect("hand must be re-activatable after stop");
|
||||
assert_ne!(second.instance_id, first_instance_id);
|
||||
|
||||
kernel.shutdown();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Issue #890: activate_agent — wake up inactive agents
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user