Watch
0
0
Fork
You've already forked hyperhive
0

swarm: cancel an agent's turn from the swarm queue

A separate $SWARM.control.<agent> subject carries commands, apart from
the text subject. swarm-controller exposes POST /api/agents/{name}/cancel
as a NATS request; the harness answers whether a turn was actually
stopped, through the same cancel path as the dashboard button. The agent
credential gets subscribe on its control subject and allow_responses to
answer; only the controller may publish there.

Refs #3803
This commit is contained in:
damocles 2026-10-14 23:08:57 +02:00 • committed by mara
commit fefc565794
11 changed files with 558 additions and 69 deletions

View file

@ -10,7 +10,7 @@ use axum::{
use serde::{Deserialize, Serialize};
use std::future::Future;
use super::{AppState, SigintOutcome, error_response};
use super::{AppState, error_response};
#[derive(Deserialize)]
pub(super) struct SendForm {
@ -53,41 +53,7 @@ pub(super) async fn post_send(
}
pub(super) async fn post_cancel_turn(State(state): State<AppState>) -> Response {
if let Some(canceller) = state.bus.turn_canceller() {
let note = if canceller.cancel() {
// Same as a signalled claude turn: the next wake prompt says it
// was cut off.
state
.interrupted
.store(true, std::sync::atomic::Ordering::Relaxed);
"operator: /cancel — sent session/cancel to the ACP agent"
} else {
"operator: /cancel — no ACP turn to cancel"
};
state
.bus
.emit(crate::events::LiveEvent::Note { text: note.into() });
return (axum::http::StatusCode::OK, "ok").into_response();
}
let out = super::sigint_claude();
let note = match out {
SigintOutcome::Signalled => {
// A process actually got signalled — the *next* turn's wake
// prompt should tell the agent it was cut off mid-work. Only
// set on an actual signal: `NoProcess` means /cancel raced an
// already-finished turn, so there's nothing to flag as
// interrupted.
state
.interrupted
.store(true, std::sync::atomic::Ordering::Relaxed);
"operator: /cancel — sent SIGINT to claude".to_owned()
}
SigintOutcome::NoProcess => "operator: /cancel — no claude process to interrupt".to_owned(),
SigintOutcome::Failed(e) => format!("operator: /cancel — kill failed: {e}"),
};
state
.bus
.emit(crate::events::LiveEvent::Note { text: note });
super::cancel_turn(&state.bus, &state.interrupted, "operator: /cancel");
(axum::http::StatusCode::OK, "ok").into_response()
}