swarmctl: agent set-state declares an agent's wanted state
Wraps PUT /api/hives/{hive}/agents/{agent}/state, the route the swarm UI's
state menu uses. States come from swarm_wire::wanted::AgentState; destroyed
needs --yes.
Closes #6159
This commit is contained in:
parent
06b303aff2
commit
71d360c9d2
1 changed files with 187 additions and 1 deletions
|
|
@ -279,6 +279,19 @@ enum AgentVerb {
|
|||
/// Queues and returns, the same way `agent create` does — watch the
|
||||
/// swarm UI's job view for the outcome.
|
||||
MintForgeToken(AgentMintForgeTokenArgs),
|
||||
/// Declare the state a hive converges an agent to: `up`, `offline`,
|
||||
/// `paused` or `destroyed`.
|
||||
///
|
||||
/// Writes the swarm's declaration for the agent, the one the swarm UI's
|
||||
/// state menu writes, and returns once the controller has published it;
|
||||
/// the hive converges on its own clock. The swarm must already know the
|
||||
/// agent on `--hive`: this never creates one.
|
||||
///
|
||||
/// `destroyed` can't be undone, so it needs `--yes`.
|
||||
///
|
||||
/// No approval gate guards this: the caller is root on the controller's
|
||||
/// host, or logged in through the swarm domain's SSO gate.
|
||||
SetState(AgentSetStateArgs),
|
||||
/// Show the model, effort, subagent models and subagent cap the swarm sets
|
||||
/// for an agent, or change the model, effort and subagent models.
|
||||
///
|
||||
|
|
@ -415,6 +428,23 @@ struct InferenceSetArgs {
|
|||
socket: ControllerSocketArg,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
struct AgentSetStateArgs {
|
||||
/// Agent name.
|
||||
agent: String,
|
||||
/// The hive that runs the agent.
|
||||
#[arg(long, value_name = "HIVE")]
|
||||
hive: String,
|
||||
/// The state to declare: `up`, `offline`, `paused` or `destroyed`.
|
||||
#[arg(value_name = "STATE", value_parser = agent::parse_state)]
|
||||
state: swarm_wire::wanted::AgentState,
|
||||
/// Confirm `destroyed`, which can't be undone.
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
#[command(flatten)]
|
||||
socket: ControllerSocketArg,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
struct AgentCreateArgs {
|
||||
/// Name for the new agent: 1–63 characters of `[a-z0-9-]`.
|
||||
|
|
@ -690,6 +720,12 @@ fn main() -> Result<()> {
|
|||
let target = remote.target(args.controller_socket)?;
|
||||
agent::mint_forge_token(&target, &args.name)
|
||||
}
|
||||
Verb::Agent {
|
||||
command: AgentVerb::SetState(args),
|
||||
} => {
|
||||
let target = args.socket.resolve(&remote)?;
|
||||
agent::set_state(&target, &args.agent, &args.hive, args.state, args.yes)
|
||||
}
|
||||
Verb::Agent {
|
||||
command: AgentVerb::Inference { command },
|
||||
} => inference(&remote, command),
|
||||
|
|
@ -1265,6 +1301,40 @@ fn open_new_nofollow(tmp: &Path) -> io::Result<File> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn set_state_takes_the_wire_enum_values() {
|
||||
let cli = Cli::try_parse_from([
|
||||
"swarmctl",
|
||||
"agent",
|
||||
"set-state",
|
||||
"iris",
|
||||
"--hive",
|
||||
"alpha",
|
||||
"paused",
|
||||
])
|
||||
.expect("parses");
|
||||
let Verb::Agent {
|
||||
command: AgentVerb::SetState(args),
|
||||
} = cli.command
|
||||
else {
|
||||
panic!("parsed as another verb");
|
||||
};
|
||||
assert_eq!(args.state, swarm_wire::wanted::AgentState::Paused);
|
||||
assert!(!args.yes);
|
||||
assert!(
|
||||
Cli::try_parse_from([
|
||||
"swarmctl",
|
||||
"agent",
|
||||
"set-state",
|
||||
"iris",
|
||||
"--hive",
|
||||
"alpha",
|
||||
"sleeping",
|
||||
])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
/// Root on the controller host, with no `--swarm` and no login: the verbs
|
||||
/// still go over the unix socket, unchanged by the remote path.
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue