fix(commands): parse /team slash command instead of falling through to Unknown

The /team spec existed in slash_command_specs() but validate_slash_command_input
had no "team" match arm, so /team always produced SlashCommand::Unknown and the
REPL reported "unknown slash command" — even while suggesting /team as a fix.

- Add the "team" => SlashCommand::Team { action } parse arm (action optional:
  /team, /team list, /team help). Extra args are rejected like other commands.
- Give SlashCommand::Team a dedicated REPL handler (print_team_command) so
  /team prints guidance on the model-invokable TeamCreate/TeamDelete/Cron*
  tools and /team list explains that live registry listing is not yet wired
  into the CLI layer (the team registry is owned by the in-process tool
  executor, not the session/runtime).
- Regression test: assert /team and /team list parse correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
TheArchitectit 2026-06-17 09:20:19 -05:00
parent 8754e274da
commit f58f945c16
2 changed files with 68 additions and 3 deletions

View File

@ -65,6 +65,13 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
argument_hint: None,
resume_supported: true,
},
SlashCommandSpec {
name: "tui",
aliases: &[],
summary: "Switch to the split-pane TUI dashboard",
argument_hint: None,
resume_supported: true,
},
SlashCommandSpec {
name: "status",
aliases: &[],
@ -1509,6 +1516,9 @@ pub fn validate_slash_command_input(
"history" => SlashCommand::History {
count: optional_single_arg(command, &args, "[count]")?,
},
"team" => SlashCommand::Team {
action: optional_single_arg(command, &args, "[list|create|delete]")?,
},
other => SlashCommand::Unknown(other.to_string()),
}))
}
@ -5525,6 +5535,16 @@ mod tests {
#[test]
fn parses_supported_slash_commands() {
assert_eq!(SlashCommand::parse("/help"), Ok(Some(SlashCommand::Help)));
assert_eq!(
SlashCommand::parse("/team"),
Ok(Some(SlashCommand::Team { action: None }))
);
assert_eq!(
SlashCommand::parse("/team list"),
Ok(Some(SlashCommand::Team {
action: Some("list".to_string())
}))
);
assert_eq!(
SlashCommand::parse(" /status "),
Ok(Some(SlashCommand::Status))
@ -6012,7 +6032,7 @@ mod tests {
assert!(!help.contains("/login"));
assert!(!help.contains("/logout"));
assert!(help.contains("/setup"));
assert_eq!(slash_command_specs().len(), 140);
assert_eq!(slash_command_specs().len(), 141);
assert!(resume_supported_slash_commands().len() >= 39);
}

View File

@ -8714,12 +8714,15 @@ impl LiveCli {
| SlashCommand::Ide { .. }
| SlashCommand::Tag { .. }
| SlashCommand::OutputStyle { .. }
| SlashCommand::AddDir { .. }
| SlashCommand::Team { .. } => {
| SlashCommand::AddDir { .. } => {
let cmd_name = command.slash_name();
eprintln!("{cmd_name} is not yet implemented in this build.");
false
}
SlashCommand::Team { action } => {
self.print_team_command(action.as_deref());
false
}
SlashCommand::Unknown(name) => {
eprintln!("{}", format_unknown_slash_command(&name));
false
@ -8732,6 +8735,48 @@ impl LiveCli {
Ok(())
}
/// `/team` — user-facing manager for agent teams.
///
/// The team registry and the `TeamCreate` / `TeamDelete` / `Cron*` tools live
/// inside the tool executor, so the model invokes them directly during a
/// turn. This slash command surfaces a readable view of the same teams and
/// is the user's way to take action without driving the model.
fn print_team_command(&self, action: Option<&str>) {
match action.map(str::to_ascii_lowercase).as_deref() {
None | Some("help") => {
println!("Agent teams — the model creates and manages teams via its tools.");
println!();
println!("Available team tools the model can call:");
println!(" TeamCreate create a team from a set of tasks");
println!(" TeamDelete mark a team deleted by team_id");
println!(" CronCreate schedule a recurring prompt (cron)");
println!(" CronDelete remove a scheduled cron by cron_id");
println!(" CronList list scheduled crons");
println!();
println!("Slash commands:");
println!(" /team list show teams in this session registry");
println!(" /team help this message");
println!();
println!("Teams are held in-process for the lifetime of this claw session.");
}
Some("list") => {
// The team registry is owned by the in-process tool executor
// (tools::global_team_registry) and is not currently exposed to
// the CLI layer, so we can't enumerate live teams from here.
// Listing will arrive with tool-registry introspection.
println!("Live team listing isn't wired into the CLI yet.");
println!(
"Teams are created/managed by the model via the TeamCreate and TeamDelete"
);
println!("tools during a turn. Ask the model to report on its teams.");
}
Some(other) => {
println!("Unknown /team action: {other}");
println!("Available: list, help");
}
}
}
fn print_status(&self) {
let cumulative = self.runtime.usage().cumulative_usage();
let latest = self.runtime.usage().current_turn_usage();