Skip to content

Commit cb6e0db

Browse files
committed
Auto-merge upstream instructkr/claw-code
2 parents 5677b82 + 84a0973 commit cb6e0db

5 files changed

Lines changed: 728 additions & 107 deletions

File tree

ROADMAP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,9 @@ Priority order: P0 = blocks CI/green state, P1 = blocks integration wiring, P2 =
309309
20. **Session state classification gap (working vs blocked vs finished vs truly stale)****done**: agent manifests now derive machine states such as `working`, `blocked_background_job`, `blocked_merge_conflict`, `degraded_mcp`, `interrupted_transport`, `finished_pending_report`, and `finished_cleanable`, and terminal-state persistence records commit provenance plus derived state so downstream monitoring can distinguish quiet progress from truly idle sessions.
310310
21. **Resumed `/status` JSON parity gap** — dogfooding shows fresh `claw status --output-format json` now emits structured JSON, but resumed slash-command status still leaks through a text-shaped path in at least one dispatch path. Local CI-equivalent repro fails `rust/crates/rusty-claude-cli/tests/resume_slash_commands.rs::resumed_status_command_emits_structured_json_when_requested` with `expected value at line 1 column 1`, so resumed automation can receive text where JSON was explicitly requested. **Action:** unify fresh vs resumed `/status` rendering through one output-format contract and add regression coverage so resumed JSON output is guaranteed valid.
311311
22. **Opaque failure surface for session/runtime crashes** — repeated dogfood-facing failures can currently collapse to generic wrappers like `Something went wrong while processing your request. Please try again, or use /new to start a fresh session.` without exposing whether the fault was provider auth, session corruption, slash-command dispatch, render failure, or transport/runtime panic. This blocks fast self-recovery and turns actionable clawability bugs into blind retries. **Action:** preserve a short user-safe failure class (`provider_auth`, `session_load`, `command_dispatch`, `render`, `runtime_panic`, etc.), attach a local trace/session id, and ensure operators can jump from the chat-visible error to the exact failure log quickly.
312-
23. **`doctor --output-format json` check-level structure gap**direct dogfooding shows `claw doctor --output-format json` exposes `has_failures` at the top level, but individual check results (`auth`, `config`, `workspace`, `sandbox`, `system`) are buried inside flat prose fields like `message` / `report`. That forces claws to string-scrape human text instead of consuming stable machine-readable diagnostics. **Action:** emit structured per-check JSON (`name`, `status`, `summary`, `details`, and relevant typed fields such as sandbox fallback reason) while preserving the current human-readable report for text mode.
312+
23. **`doctor --output-format json` check-level structure gap****done**: `claw doctor --output-format json` now keeps the human-readable `message`/`report` while also emitting structured per-check diagnostics (`name`, `status`, `summary`, `details`, plus typed fields like workspace paths and sandbox fallback data), with regression coverage in `output_format_contract.rs`.
313313
24. **Plugin lifecycle init/shutdown test flakes under workspace-parallel execution** — dogfooding surfaced that `build_runtime_runs_plugin_lifecycle_init_and_shutdown` can fail under `cargo test --workspace` while passing in isolation because sibling tests race on tempdir-backed shell init script paths. This is test brittleness rather than a code-path regression, but it still destabilizes CI confidence and wastes diagnosis cycles. **Action:** isolate temp resources per test robustly (unique dirs + no shared cwd assumptions), audit cleanup timing, and add a regression guard so the plugin lifecycle test remains stable under parallel workspace execution.
314+
26. **Resumed local-command JSON parity gap****done**: direct `claw --output-format json` already had structured renderers for `sandbox`, `mcp`, `skills`, `version`, and `init`, but resumed `claw --output-format json --resume <session> /…` paths still fell back to prose because resumed slash dispatch only emitted JSON for `/status`. Resumed `/sandbox`, `/mcp`, `/skills`, `/version`, and `/init` now reuse the same JSON envelopes as their direct CLI counterparts, with regression coverage in `rust/crates/rusty-claude-cli/tests/resume_slash_commands.rs` and `rust/crates/rusty-claude-cli/tests/output_format_contract.rs`.
314315
**P3 — Swarm efficiency**
315316
13. Swarm branch-lock protocol — **done**: `branch_lock::detect_branch_lock_collisions()` now detects same-branch/same-scope and nested-module collisions before parallel lanes drift into duplicate implementation
316317
14. Commit provenance / worktree-aware push events — **done**: lane event provenance now includes branch/worktree/superseded/canonical lineage metadata, and manifest persistence de-dupes superseded commit events before downstream consumers render them

rust/crates/commands/src/lib.rs

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,6 +2187,27 @@ pub fn handle_agents_slash_command(args: Option<&str>, cwd: &Path) -> std::io::R
21872187
}
21882188
}
21892189

2190+
pub fn handle_agents_slash_command_json(args: Option<&str>, cwd: &Path) -> std::io::Result<Value> {
2191+
if let Some(args) = normalize_optional_args(args) {
2192+
if let Some(help_path) = help_path_from_args(args) {
2193+
return Ok(match help_path.as_slice() {
2194+
[] => render_agents_usage_json(None),
2195+
_ => render_agents_usage_json(Some(&help_path.join(" "))),
2196+
});
2197+
}
2198+
}
2199+
2200+
match normalize_optional_args(args) {
2201+
None | Some("list") => {
2202+
let roots = discover_definition_roots(cwd, "agents");
2203+
let agents = load_agents_from_roots(&roots)?;
2204+
Ok(render_agents_report_json(cwd, &agents))
2205+
}
2206+
Some(args) if is_help_arg(args) => Ok(render_agents_usage_json(None)),
2207+
Some(args) => Ok(render_agents_usage_json(Some(args))),
2208+
}
2209+
}
2210+
21902211
pub fn handle_mcp_slash_command(
21912212
args: Option<&str>,
21922213
cwd: &Path,
@@ -3039,6 +3060,25 @@ fn render_agents_report(agents: &[AgentSummary]) -> String {
30393060
lines.join("\n").trim_end().to_string()
30403061
}
30413062

3063+
fn render_agents_report_json(cwd: &Path, agents: &[AgentSummary]) -> Value {
3064+
let active = agents
3065+
.iter()
3066+
.filter(|agent| agent.shadowed_by.is_none())
3067+
.count();
3068+
json!({
3069+
"kind": "agents",
3070+
"action": "list",
3071+
"working_directory": cwd.display().to_string(),
3072+
"count": agents.len(),
3073+
"summary": {
3074+
"total": agents.len(),
3075+
"active": active,
3076+
"shadowed": agents.len().saturating_sub(active),
3077+
},
3078+
"agents": agents.iter().map(agent_summary_json).collect::<Vec<_>>(),
3079+
})
3080+
}
3081+
30423082
fn agent_detail(agent: &AgentSummary) -> String {
30433083
let mut parts = vec![agent.name.clone()];
30443084
if let Some(description) = &agent.description {
@@ -3327,6 +3367,19 @@ fn render_agents_usage(unexpected: Option<&str>) -> String {
33273367
lines.join("\n")
33283368
}
33293369

3370+
fn render_agents_usage_json(unexpected: Option<&str>) -> Value {
3371+
json!({
3372+
"kind": "agents",
3373+
"action": "help",
3374+
"usage": {
3375+
"slash_command": "/agents [list|help]",
3376+
"direct_cli": "claw agents [list|help]",
3377+
"sources": [".claw/agents", "~/.claw/agents", "$CLAW_CONFIG_HOME/agents"],
3378+
},
3379+
"unexpected": unexpected,
3380+
})
3381+
}
3382+
33303383
fn render_skills_usage(unexpected: Option<&str>) -> String {
33313384
let mut lines = vec![
33323385
"Skills".to_string(),
@@ -3478,6 +3531,18 @@ fn definition_source_json(source: DefinitionSource) -> Value {
34783531
})
34793532
}
34803533

3534+
fn agent_summary_json(agent: &AgentSummary) -> Value {
3535+
json!({
3536+
"name": &agent.name,
3537+
"description": &agent.description,
3538+
"model": &agent.model,
3539+
"reasoning_effort": &agent.reasoning_effort,
3540+
"source": definition_source_json(agent.source),
3541+
"active": agent.shadowed_by.is_none(),
3542+
"shadowed_by": agent.shadowed_by.map(definition_source_json),
3543+
})
3544+
}
3545+
34813546
fn skill_origin_id(origin: SkillOrigin) -> &'static str {
34823547
match origin {
34833548
SkillOrigin::SkillsDir => "skills_dir",
@@ -3686,8 +3751,9 @@ pub fn handle_slash_command(
36863751
#[cfg(test)]
36873752
mod tests {
36883753
use super::{
3689-
handle_plugins_slash_command, handle_skills_slash_command_json, handle_slash_command,
3690-
load_agents_from_roots, load_skills_from_roots, render_agents_report,
3754+
handle_agents_slash_command_json, handle_plugins_slash_command,
3755+
handle_skills_slash_command_json, handle_slash_command, load_agents_from_roots,
3756+
load_skills_from_roots, render_agents_report, render_agents_report_json,
36913757
render_mcp_report_json_for, render_plugins_report, render_skills_report,
36923758
render_slash_command_help, render_slash_command_help_detail,
36933759
resume_supported_slash_commands, slash_command_specs, suggest_slash_commands,
@@ -4363,6 +4429,72 @@ mod tests {
43634429
let _ = fs::remove_dir_all(user_home);
43644430
}
43654431

4432+
#[test]
4433+
fn renders_agents_reports_as_json() {
4434+
let workspace = temp_dir("agents-json-workspace");
4435+
let project_agents = workspace.join(".codex").join("agents");
4436+
let user_home = temp_dir("agents-json-home");
4437+
let user_agents = user_home.join(".codex").join("agents");
4438+
4439+
write_agent(
4440+
&project_agents,
4441+
"planner",
4442+
"Project planner",
4443+
"gpt-5.4",
4444+
"medium",
4445+
);
4446+
write_agent(
4447+
&project_agents,
4448+
"verifier",
4449+
"Verification agent",
4450+
"gpt-5.4-mini",
4451+
"high",
4452+
);
4453+
write_agent(
4454+
&user_agents,
4455+
"planner",
4456+
"User planner",
4457+
"gpt-5.4-mini",
4458+
"high",
4459+
);
4460+
4461+
let roots = vec![
4462+
(DefinitionSource::ProjectCodex, project_agents),
4463+
(DefinitionSource::UserCodex, user_agents),
4464+
];
4465+
let report = render_agents_report_json(
4466+
&workspace,
4467+
&load_agents_from_roots(&roots).expect("agent roots should load"),
4468+
);
4469+
4470+
assert_eq!(report["kind"], "agents");
4471+
assert_eq!(report["action"], "list");
4472+
assert_eq!(report["working_directory"], workspace.display().to_string());
4473+
assert_eq!(report["count"], 3);
4474+
assert_eq!(report["summary"]["active"], 2);
4475+
assert_eq!(report["summary"]["shadowed"], 1);
4476+
assert_eq!(report["agents"][0]["name"], "planner");
4477+
assert_eq!(report["agents"][0]["model"], "gpt-5.4");
4478+
assert_eq!(report["agents"][0]["active"], true);
4479+
assert_eq!(report["agents"][1]["name"], "verifier");
4480+
assert_eq!(report["agents"][2]["name"], "planner");
4481+
assert_eq!(report["agents"][2]["active"], false);
4482+
assert_eq!(report["agents"][2]["shadowed_by"]["id"], "project_claw");
4483+
4484+
let help = handle_agents_slash_command_json(Some("help"), &workspace).expect("agents help");
4485+
assert_eq!(help["kind"], "agents");
4486+
assert_eq!(help["action"], "help");
4487+
assert_eq!(help["usage"]["direct_cli"], "claw agents [list|help]");
4488+
4489+
let unexpected = handle_agents_slash_command_json(Some("show planner"), &workspace)
4490+
.expect("agents usage");
4491+
assert_eq!(unexpected["action"], "help");
4492+
assert_eq!(unexpected["unexpected"], "show planner");
4493+
4494+
let _ = fs::remove_dir_all(workspace);
4495+
let _ = fs::remove_dir_all(user_home);
4496+
}
4497+
43664498
#[test]
43674499
fn lists_skills_from_project_and_user_roots() {
43684500
let workspace = temp_dir("skills-workspace");

0 commit comments

Comments
 (0)