mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-28 00:16:45 +00:00
fix(#717): implement agents show/info/describe and list filter commands, mirror skills handler parity
This commit is contained in:
@@ -7599,3 +7599,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
|
||||
715. **Resume-path slash commands (`/compact`, `/clear`, `/cost`, `/stats`, `/history`, `/session exists`, `/session delete`, `memory`) JSON responses missing `action` and `status` fields** — dogfooded 2026-05-26 on `590b5b61`. The `assert_non_empty_action` guardrail added by #3109 only covers `assert_json_command` (top-level CLI surfaces); resume-path commands that emit JSON via `ResumeCommandOutcome.json` were not covered. 8 resume-path JSON sites all lacked `action` and `status`. Fix: added `action` + `status:"ok"` to `compact`, `clear`, `cost`, `stats`, `history`, `session_exists`, `session_delete`, `memory`, and `restored`. Source: Jobdori dogfood on `590b5b61`, 2026-05-26.
|
||||
|
||||
716. **Resume-path error JSON used legacy `{type:"error", error:...}` shape instead of standard `{kind, action, status:"error", error_kind, exit_code}` envelope — 5 error paths affected** — dogfooded 2026-05-26 on `76c8d480`. Session load failure, unsupported command, unsupported resumed command, SlashCommand parse error, and broad-cwd abort all emitted the old two-key shape. Fix: aligned all 5 to `{kind, action:"resume"|"abort", status:"error", error_kind, error, exit_code}`. Updated `resumed_stub_command_emits_not_implemented_json` test to assert `status:"error"` + `kind:"unsupported_command"`. Source: Jobdori dogfood on `76c8d480`, 2026-05-26.
|
||||
|
||||
717. **`claw agents show <name>` missing — `handle_agents_slash_command_json` only accepted `list`; `show/info/describe` was unimplemented unlike skills which had parity** — dogfooded 2026-05-26 on `6a007344`. `claw agents show claw-code --output-format json` returned `unknown_agents_subcommand` error. Fix: added `show/info/describe` and `list <filter>` arms to both `handle_agents_slash_command` and `handle_agents_slash_command_json`, mirroring the skills handler; renamed `render_agents_report_json` → `render_agents_report_json_with_action`; not-found path returns `{kind:"agents", action:"show", status:"error", error_kind:"agent_not_found", requested:"<name>"}` + Ok; added `classify_error_kind` branch for `agent_not_found`. Updated 2 tests. Source: Jobdori dogfood on `6a007344`, 2026-05-26.
|
||||
|
||||
@@ -2323,10 +2323,50 @@ pub fn handle_agents_slash_command(args: Option<&str>, cwd: &Path) -> std::io::R
|
||||
let agents = load_agents_from_roots(&roots)?;
|
||||
Ok(render_agents_report(&agents))
|
||||
}
|
||||
Some(args) if args.starts_with("list ") => {
|
||||
let filter = args["list ".len()..].trim().to_lowercase();
|
||||
let roots = discover_definition_roots(cwd, "agents");
|
||||
let agents = load_agents_from_roots(&roots)?;
|
||||
let filtered: Vec<_> = agents
|
||||
.into_iter()
|
||||
.filter(|a| a.name.to_lowercase().contains(&filter))
|
||||
.collect();
|
||||
Ok(render_agents_report(&filtered))
|
||||
}
|
||||
Some("show" | "info" | "describe") => {
|
||||
let roots = discover_definition_roots(cwd, "agents");
|
||||
let agents = load_agents_from_roots(&roots)?;
|
||||
Ok(render_agents_report(&agents))
|
||||
}
|
||||
Some(args)
|
||||
if args.starts_with("show ")
|
||||
|| args.starts_with("info ")
|
||||
|| args.starts_with("describe ") =>
|
||||
{
|
||||
let name = args
|
||||
.split_once(' ')
|
||||
.map(|(_, name)| name)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_lowercase();
|
||||
let roots = discover_definition_roots(cwd, "agents");
|
||||
let agents = load_agents_from_roots(&roots)?;
|
||||
let matched: Vec<_> = agents
|
||||
.into_iter()
|
||||
.filter(|a| a.name.to_lowercase() == name)
|
||||
.collect();
|
||||
if matched.is_empty() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("agent not found: {name}"),
|
||||
));
|
||||
}
|
||||
Ok(render_agents_report(&matched))
|
||||
}
|
||||
Some(args) if is_help_arg(args) => Ok(render_agents_usage(None)),
|
||||
Some(args) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("unknown agents subcommand: {args}. Supported: list, help"),
|
||||
format!("unknown agents subcommand: {args}. Supported: list, show, help"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -2347,10 +2387,53 @@ pub fn handle_agents_slash_command_json(args: Option<&str>, cwd: &Path) -> std::
|
||||
let agents = load_agents_from_roots(&roots)?;
|
||||
Ok(render_agents_report_json(cwd, &agents))
|
||||
}
|
||||
Some(args) if args.starts_with("list ") => {
|
||||
let filter = args["list ".len()..].trim().to_lowercase();
|
||||
let roots = discover_definition_roots(cwd, "agents");
|
||||
let agents = load_agents_from_roots(&roots)?;
|
||||
let filtered: Vec<_> = agents
|
||||
.into_iter()
|
||||
.filter(|a| a.name.to_lowercase().contains(&filter))
|
||||
.collect();
|
||||
Ok(render_agents_report_json(cwd, &filtered))
|
||||
}
|
||||
Some("show" | "info" | "describe") => {
|
||||
let roots = discover_definition_roots(cwd, "agents");
|
||||
let agents = load_agents_from_roots(&roots)?;
|
||||
Ok(render_agents_report_json_with_action(cwd, &agents, "show"))
|
||||
}
|
||||
Some(args)
|
||||
if args.starts_with("show ")
|
||||
|| args.starts_with("info ")
|
||||
|| args.starts_with("describe ") =>
|
||||
{
|
||||
let name = args
|
||||
.split_once(' ')
|
||||
.map(|(_, name)| name)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_lowercase();
|
||||
let roots = discover_definition_roots(cwd, "agents");
|
||||
let agents = load_agents_from_roots(&roots)?;
|
||||
let matched: Vec<_> = agents
|
||||
.into_iter()
|
||||
.filter(|a| a.name.to_lowercase() == name)
|
||||
.collect();
|
||||
if matched.is_empty() {
|
||||
return Ok(serde_json::json!({
|
||||
"kind": "agents",
|
||||
"action": "show",
|
||||
"status": "error",
|
||||
"error_kind": "agent_not_found",
|
||||
"requested": name,
|
||||
}));
|
||||
}
|
||||
Ok(render_agents_report_json_with_action(cwd, &matched, "show"))
|
||||
}
|
||||
Some(args) if is_help_arg(args) => Ok(render_agents_usage_json(None)),
|
||||
Some(args) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("unknown agents subcommand: {args}. Supported: list, help"),
|
||||
format!("unknown agents subcommand: {args}. Supported: list, show, help"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -3636,6 +3719,14 @@ fn render_agents_report(agents: &[AgentSummary]) -> String {
|
||||
}
|
||||
|
||||
fn render_agents_report_json(cwd: &Path, agents: &[AgentSummary]) -> Value {
|
||||
render_agents_report_json_with_action(cwd, agents, "list")
|
||||
}
|
||||
|
||||
fn render_agents_report_json_with_action(
|
||||
cwd: &Path,
|
||||
agents: &[AgentSummary],
|
||||
action: &str,
|
||||
) -> Value {
|
||||
let active = agents
|
||||
.iter()
|
||||
.filter(|agent| agent.shadowed_by.is_none())
|
||||
@@ -3643,7 +3734,7 @@ fn render_agents_report_json(cwd: &Path, agents: &[AgentSummary]) -> Value {
|
||||
json!({
|
||||
"kind": "agents",
|
||||
"status": "ok",
|
||||
"action": "list",
|
||||
"action": action,
|
||||
"working_directory": cwd.display().to_string(),
|
||||
"count": agents.len(),
|
||||
"summary": {
|
||||
@@ -5360,13 +5451,23 @@ mod tests {
|
||||
assert_eq!(help["status"], "ok");
|
||||
assert_eq!(help["usage"]["direct_cli"], "claw agents [list|help]");
|
||||
|
||||
// Unknown agents subcommands now return Err so CLI layer can exit 1.
|
||||
let unexpected_err = handle_agents_slash_command_json(Some("show planner"), &workspace);
|
||||
// `show <name>` is now valid. Known agent returns ok with matching entry.
|
||||
let show_planner = handle_agents_slash_command_json(Some("show planner"), &workspace)
|
||||
.expect("show planner should return Ok");
|
||||
assert_eq!(show_planner["status"], "ok");
|
||||
let show_agents = show_planner["agents"].as_array().expect("agents array");
|
||||
assert_eq!(show_agents.len(), 1, "show by exact name returns one entry");
|
||||
assert_eq!(show_agents[0]["name"], "planner");
|
||||
// Missing agent returns Ok(json error) with error_kind:agent_not_found.
|
||||
let show_missing =
|
||||
handle_agents_slash_command_json(Some("show nonexistent-xyz"), &workspace)
|
||||
.expect("show missing agent should return Ok");
|
||||
assert_eq!(show_missing["status"], "error");
|
||||
assert_eq!(show_missing["error_kind"], "agent_not_found");
|
||||
assert_eq!(show_missing["requested"], "nonexistent-xyz");
|
||||
// Truly unknown subcommands still Err.
|
||||
let unexpected_err = handle_agents_slash_command_json(Some("frobnicate"), &workspace);
|
||||
assert!(unexpected_err.is_err());
|
||||
assert!(unexpected_err
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("show planner"));
|
||||
|
||||
let _ = fs::remove_dir_all(workspace);
|
||||
let _ = fs::remove_dir_all(user_home);
|
||||
@@ -5518,14 +5619,23 @@ mod tests {
|
||||
assert!(agents_help
|
||||
.contains("Sources .claw/agents, ~/.claw/agents, $CLAW_CONFIG_HOME/agents"));
|
||||
|
||||
// Unknown agents subcommands now return Err (typed error) instead of Ok+help text
|
||||
// so that the CLI layer can exit 1. The error message names the unexpected input.
|
||||
let agents_unexpected_err = super::handle_agents_slash_command(Some("show planner"), &cwd);
|
||||
assert!(agents_unexpected_err.is_err());
|
||||
assert!(agents_unexpected_err
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("show planner"));
|
||||
// `show <name>` is now valid. For an agent that doesn't exist it returns Err(NotFound).
|
||||
let agents_show_missing = super::handle_agents_slash_command(Some("show planner"), &cwd);
|
||||
assert!(
|
||||
agents_show_missing.is_err(),
|
||||
"show of a missing agent should Err"
|
||||
);
|
||||
assert_eq!(
|
||||
agents_show_missing.unwrap_err().kind(),
|
||||
std::io::ErrorKind::NotFound
|
||||
);
|
||||
// Truly unknown subcommands still Err with InvalidInput.
|
||||
let agents_unknown_err = super::handle_agents_slash_command(Some("frobnicate"), &cwd);
|
||||
assert!(agents_unknown_err.is_err());
|
||||
assert_eq!(
|
||||
agents_unknown_err.unwrap_err().kind(),
|
||||
std::io::ErrorKind::InvalidInput
|
||||
);
|
||||
|
||||
let skills_help =
|
||||
super::handle_skills_slash_command(Some("--help"), &cwd).expect("skills help");
|
||||
|
||||
@@ -302,6 +302,8 @@ fn classify_error_kind(message: &str) -> &'static str {
|
||||
"interactive_only"
|
||||
} else if message.starts_with("unknown agents subcommand:") {
|
||||
"unknown_agents_subcommand"
|
||||
} else if message.starts_with("agent not found:") {
|
||||
"agent_not_found"
|
||||
} else if message.contains("is not installed") {
|
||||
"plugin_not_found"
|
||||
} else if (message.contains("skill source") && message.contains("not found"))
|
||||
|
||||
Reference in New Issue
Block a user