subagent: add status tool, cut docs down to operator-facing + no cli flags
This commit is contained in:
parent
e64639c4d0
commit
c2fb3c6e3e
7 changed files with 136 additions and 195 deletions
|
|
@ -15,20 +15,21 @@ use crate::session::{self, State};
|
|||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct StartArgs {
|
||||
/// Session name — becomes both this daemon's tracking key and claude's
|
||||
/// own `--name`/`--resume` session title. Same identifier rules as the
|
||||
/// `bash` server's task names: lowercase, digits, hyphen, max 63 chars.
|
||||
/// Reusable once a prior *finished* session under that name is done —
|
||||
/// rejected while one under the same name is still running.
|
||||
/// Session name — this daemon's tracking key while it's alive, and the
|
||||
/// identity to `continue`/`status`/`interrupt` it by afterward. Same
|
||||
/// identifier rules as the `bash` server's task names: lowercase,
|
||||
/// digits, hyphen, max 63 chars. Reusable once a prior *finished*
|
||||
/// session under that name is done — rejected while one under the same
|
||||
/// name is still running.
|
||||
name: String,
|
||||
/// `--model` for the subagent's own claude invocation. Omit for
|
||||
/// claude's own default. The `base:claude-subagents` skill's
|
||||
/// "cheaper-than-you" guidance still applies here.
|
||||
/// Which model the subagent's own session runs. Omit for claude's own
|
||||
/// default. The `base:claude-subagents` skill's "cheaper-than-you"
|
||||
/// guidance still applies here.
|
||||
#[serde(default)]
|
||||
model: Option<String>,
|
||||
/// Path to a file passed as `--append-system-prompt-file` — the
|
||||
/// subagent's actual task instructions. A file, not an inline string,
|
||||
/// to avoid `ARG_MAX` on a large recipe.
|
||||
/// Path to a file holding the subagent's actual task instructions. A
|
||||
/// file, not an inline string, so a large recipe can't blow past a
|
||||
/// shell argument length limit.
|
||||
prompt_file: String,
|
||||
/// Written to the subagent's stdin as its first turn's prompt. Default:
|
||||
/// a generic "carry out your instructions" nudge — the real task detail
|
||||
|
|
@ -47,12 +48,18 @@ struct ContinueArgs {
|
|||
name: String,
|
||||
/// The new turn's prompt, written to the subagent's stdin.
|
||||
prompt: String,
|
||||
/// `--model` for this turn. Omit to let claude fall back to its own
|
||||
/// Which model this turn runs. Omit to let claude fall back to its own
|
||||
/// default — this does not have to match whatever model `start` used.
|
||||
#[serde(default)]
|
||||
model: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct StatusArgs {
|
||||
/// The subagent name to check.
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct InterruptArgs {
|
||||
/// The running session's name to signal.
|
||||
|
|
@ -73,13 +80,12 @@ impl SubagentMcp {
|
|||
#[tool(
|
||||
description = "Start a fresh claude subagent session under `name`, running in the \
|
||||
background. Returns as soon as the process is confirmed running — not once it \
|
||||
finishes; poll for completion via the todo this daemon pushes when the turn ends, \
|
||||
or use `continue` later to give it another turn. A prior *finished* session under \
|
||||
the same name is archived first (real fresh start, not a silent resume); a \
|
||||
*currently running* one is refused. Always runs with \
|
||||
`--dangerously-skip-permissions --strict-mcp-config` (no `--mcp-config` override — \
|
||||
that's a safety property, not a knob). See the `base:claude-subagents` skill for \
|
||||
when to reach for this."
|
||||
finishes; this daemon pushes a todo when the turn ends, or use `continue` later to \
|
||||
give it another turn. A prior *finished* session under the same name is archived \
|
||||
first (real fresh start, not a silent resume); a *currently running* one is \
|
||||
refused. Runs unattended — every tool-call permission prompt is pre-approved rather \
|
||||
than interactively confirmed — with its MCP server set fixed to what this daemon \
|
||||
configures for it. See the `base:claude-subagents` skill for when to reach for this."
|
||||
)]
|
||||
fn start(&self, Parameters(args): Parameters<StartArgs>) -> String {
|
||||
match session::start(
|
||||
|
|
@ -120,6 +126,19 @@ impl SubagentMcp {
|
|||
Err(e) => format!("interrupt error: {e:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Report whether a subagent is currently running — a zero-cost check that \
|
||||
never launches a process, unlike `continue`. Distinguishes running, idle (a session \
|
||||
exists but nothing is in flight — `continue` to give it another turn), and no such \
|
||||
session at all."
|
||||
)]
|
||||
fn status(&self, Parameters(args): Parameters<StatusArgs>) -> String {
|
||||
match session::status(&self.state, &args.name) {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => format!("status error: {e:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tool_handler]
|
||||
|
|
|
|||
|
|
@ -235,6 +235,30 @@ fn spawn_and_track(
|
|||
Ok(format!("subagent `{name}` started"))
|
||||
}
|
||||
|
||||
/// Report whether `name` is currently running — a zero-cost check that
|
||||
/// never launches a process, unlike `continue`. Distinguishes three
|
||||
/// states: running, idle (a session exists but nothing is in flight), and
|
||||
/// no such session at all.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An invalid name, or no session — running or on disk — under `name`.
|
||||
pub fn status(state: &State, name: &str) -> anyhow::Result<String> {
|
||||
validate_name(name)?;
|
||||
if state.is_running(name) {
|
||||
return Ok(format!("subagent `{name}` is running"));
|
||||
}
|
||||
let config = build_config(name, None, None);
|
||||
let store = build_store(&config)?;
|
||||
if store.find_by_title(name).is_some() {
|
||||
Ok(format!(
|
||||
"subagent `{name}` is idle — its last turn finished; `continue` to give it another"
|
||||
))
|
||||
} else {
|
||||
anyhow::bail!("no subagent named `{name}` exists — `start` creates one")
|
||||
}
|
||||
}
|
||||
|
||||
/// Signal `name`'s running process — `force` picks SIGKILL over SIGINT (see
|
||||
/// `hive_claude::Cancel::cancel`). Refuses a name with nothing running:
|
||||
/// there's no queued/pending state to cancel pre-emptively any more (see
|
||||
|
|
|
|||
Loading…
Reference in a new issue