mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-07 00:24:50 +08:00
Keep resumed /status JSON aligned with live status output
The resumed slash-command path built a reduced status JSON payload by hand, so it drifted from the fresh status schema and dropped metadata like model, permission mode, workspace counters, and sandbox details. Reuse a shared status JSON builder for both code paths and tighten the resume regression tests to lock parity in place. Constraint: Resume mode does not carry an active runtime model, so restored sessions continue to report the existing restored-session sentinel value Rejected: Copy the fresh status JSON shape into the resume path again | would recreate the same schema drift risk Confidence: high Scope-risk: narrow Directive: Keep resumed and fresh /status JSON on the same helper so future schema changes stay in parity Tested: Reproduced failure in temporary HEAD worktree with strengthened resumed_status_command_emits_structured_json_when_requested Tested: cargo test -p rusty-claude-cli resumed_status_command_emits_structured_json_when_requested --test resume_slash_commands -- --exact --nocapture Tested: cargo test -p rusty-claude-cli doctor_and_resume_status_emit_json_when_requested --test output_format_contract -- --exact --nocapture Tested: cargo test --workspace Tested: cargo fmt --check Tested: cargo clippy --workspace --all-targets -- -D warnings
This commit is contained in:
@@ -1570,24 +1570,19 @@ fn resume_session(session_path: &Path, commands: &[String], output_format: CliOu
|
|||||||
&& matches!(command, SlashCommand::Status)
|
&& matches!(command, SlashCommand::Status)
|
||||||
{
|
{
|
||||||
let tracker = UsageTracker::from_session(&session);
|
let tracker = UsageTracker::from_session(&session);
|
||||||
let usage = tracker.cumulative_usage();
|
|
||||||
let context = status_context(Some(&resolved_path)).expect("status context");
|
let context = status_context(Some(&resolved_path)).expect("status context");
|
||||||
let value = json!({
|
let value = status_json_value(
|
||||||
"kind": "status",
|
"restored-session",
|
||||||
"messages": session.messages.len(),
|
StatusUsage {
|
||||||
"turns": tracker.turns(),
|
message_count: session.messages.len(),
|
||||||
"latest_total": tracker.current_turn_usage().total_tokens(),
|
turns: tracker.turns(),
|
||||||
"cumulative_input": usage.input_tokens,
|
latest: tracker.current_turn_usage(),
|
||||||
"cumulative_output": usage.output_tokens,
|
cumulative: tracker.cumulative_usage(),
|
||||||
"cumulative_total": usage.total_tokens(),
|
estimated_tokens: 0,
|
||||||
"workspace": {
|
},
|
||||||
"cwd": context.cwd,
|
default_permission_mode().as_str(),
|
||||||
"project_root": context.project_root,
|
&context,
|
||||||
"git_branch": context.git_branch,
|
);
|
||||||
"git_state": context.git_summary.headline(),
|
|
||||||
"session": context.session_path.as_ref().map_or_else(|| "live-repl".to_string(), |path| path.display().to_string()),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
serde_json::to_string_pretty(&value).expect("status json")
|
serde_json::to_string_pretty(&value).expect("status json")
|
||||||
@@ -3845,54 +3840,68 @@ fn print_status_snapshot(
|
|||||||
),
|
),
|
||||||
CliOutputFormat::Json => println!(
|
CliOutputFormat::Json => println!(
|
||||||
"{}",
|
"{}",
|
||||||
serde_json::to_string_pretty(&json!({
|
serde_json::to_string_pretty(&status_json_value(
|
||||||
"kind": "status",
|
model,
|
||||||
"model": model,
|
usage,
|
||||||
"permission_mode": permission_mode.as_str(),
|
permission_mode.as_str(),
|
||||||
"usage": {
|
&context,
|
||||||
"messages": usage.message_count,
|
))?
|
||||||
"turns": usage.turns,
|
|
||||||
"latest_total": usage.latest.total_tokens(),
|
|
||||||
"cumulative_input": usage.cumulative.input_tokens,
|
|
||||||
"cumulative_output": usage.cumulative.output_tokens,
|
|
||||||
"cumulative_total": usage.cumulative.total_tokens(),
|
|
||||||
"estimated_tokens": usage.estimated_tokens,
|
|
||||||
},
|
|
||||||
"workspace": {
|
|
||||||
"cwd": context.cwd,
|
|
||||||
"project_root": context.project_root,
|
|
||||||
"git_branch": context.git_branch,
|
|
||||||
"git_state": context.git_summary.headline(),
|
|
||||||
"changed_files": context.git_summary.changed_files,
|
|
||||||
"staged_files": context.git_summary.staged_files,
|
|
||||||
"unstaged_files": context.git_summary.unstaged_files,
|
|
||||||
"untracked_files": context.git_summary.untracked_files,
|
|
||||||
"session": context.session_path.as_ref().map_or_else(|| "live-repl".to_string(), |path| path.display().to_string()),
|
|
||||||
"loaded_config_files": context.loaded_config_files,
|
|
||||||
"discovered_config_files": context.discovered_config_files,
|
|
||||||
"memory_file_count": context.memory_file_count,
|
|
||||||
},
|
|
||||||
"sandbox": {
|
|
||||||
"enabled": context.sandbox_status.enabled,
|
|
||||||
"active": context.sandbox_status.active,
|
|
||||||
"supported": context.sandbox_status.supported,
|
|
||||||
"in_container": context.sandbox_status.in_container,
|
|
||||||
"requested_namespace": context.sandbox_status.requested.namespace_restrictions,
|
|
||||||
"active_namespace": context.sandbox_status.namespace_active,
|
|
||||||
"requested_network": context.sandbox_status.requested.network_isolation,
|
|
||||||
"active_network": context.sandbox_status.network_active,
|
|
||||||
"filesystem_mode": context.sandbox_status.filesystem_mode.as_str(),
|
|
||||||
"filesystem_active": context.sandbox_status.filesystem_active,
|
|
||||||
"allowed_mounts": context.sandbox_status.allowed_mounts,
|
|
||||||
"markers": context.sandbox_status.container_markers,
|
|
||||||
"fallback_reason": context.sandbox_status.fallback_reason,
|
|
||||||
}
|
|
||||||
}))?
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn status_json_value(
|
||||||
|
model: &str,
|
||||||
|
usage: StatusUsage,
|
||||||
|
permission_mode: &str,
|
||||||
|
context: &StatusContext,
|
||||||
|
) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"kind": "status",
|
||||||
|
"model": model,
|
||||||
|
"permission_mode": permission_mode,
|
||||||
|
"usage": {
|
||||||
|
"messages": usage.message_count,
|
||||||
|
"turns": usage.turns,
|
||||||
|
"latest_total": usage.latest.total_tokens(),
|
||||||
|
"cumulative_input": usage.cumulative.input_tokens,
|
||||||
|
"cumulative_output": usage.cumulative.output_tokens,
|
||||||
|
"cumulative_total": usage.cumulative.total_tokens(),
|
||||||
|
"estimated_tokens": usage.estimated_tokens,
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"cwd": context.cwd,
|
||||||
|
"project_root": context.project_root,
|
||||||
|
"git_branch": context.git_branch,
|
||||||
|
"git_state": context.git_summary.headline(),
|
||||||
|
"changed_files": context.git_summary.changed_files,
|
||||||
|
"staged_files": context.git_summary.staged_files,
|
||||||
|
"unstaged_files": context.git_summary.unstaged_files,
|
||||||
|
"untracked_files": context.git_summary.untracked_files,
|
||||||
|
"session": context.session_path.as_ref().map_or_else(|| "live-repl".to_string(), |path| path.display().to_string()),
|
||||||
|
"loaded_config_files": context.loaded_config_files,
|
||||||
|
"discovered_config_files": context.discovered_config_files,
|
||||||
|
"memory_file_count": context.memory_file_count,
|
||||||
|
},
|
||||||
|
"sandbox": {
|
||||||
|
"enabled": context.sandbox_status.enabled,
|
||||||
|
"active": context.sandbox_status.active,
|
||||||
|
"supported": context.sandbox_status.supported,
|
||||||
|
"in_container": context.sandbox_status.in_container,
|
||||||
|
"requested_namespace": context.sandbox_status.requested.namespace_restrictions,
|
||||||
|
"active_namespace": context.sandbox_status.namespace_active,
|
||||||
|
"requested_network": context.sandbox_status.requested.network_isolation,
|
||||||
|
"active_network": context.sandbox_status.network_active,
|
||||||
|
"filesystem_mode": context.sandbox_status.filesystem_mode.as_str(),
|
||||||
|
"filesystem_active": context.sandbox_status.filesystem_active,
|
||||||
|
"allowed_mounts": context.sandbox_status.allowed_mounts,
|
||||||
|
"markers": context.sandbox_status.container_markers,
|
||||||
|
"fallback_reason": context.sandbox_status.fallback_reason,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn status_context(
|
fn status_context(
|
||||||
session_path: Option<&Path>,
|
session_path: Option<&Path>,
|
||||||
) -> Result<StatusContext, Box<dyn std::error::Error>> {
|
) -> Result<StatusContext, Box<dyn std::error::Error>> {
|
||||||
|
|||||||
@@ -130,7 +130,10 @@ fn doctor_and_resume_status_emit_json_when_requested() {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
assert_eq!(resumed["kind"], "status");
|
assert_eq!(resumed["kind"], "status");
|
||||||
assert_eq!(resumed["messages"], 1);
|
assert_eq!(resumed["model"], "restored-session");
|
||||||
|
assert_eq!(resumed["usage"]["messages"], 1);
|
||||||
|
assert!(resumed["workspace"]["cwd"].as_str().is_some());
|
||||||
|
assert!(resumed["sandbox"]["filesystem_mode"].as_str().is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_json_command(current_dir: &Path, args: &[&str]) -> Value {
|
fn assert_json_command(current_dir: &Path, args: &[&str]) -> Value {
|
||||||
|
|||||||
@@ -261,11 +261,18 @@ fn resumed_status_command_emits_structured_json_when_requested() {
|
|||||||
let parsed: Value =
|
let parsed: Value =
|
||||||
serde_json::from_str(stdout.trim()).expect("resume status output should be json");
|
serde_json::from_str(stdout.trim()).expect("resume status output should be json");
|
||||||
assert_eq!(parsed["kind"], "status");
|
assert_eq!(parsed["kind"], "status");
|
||||||
assert_eq!(parsed["messages"], 1);
|
assert_eq!(parsed["model"], "restored-session");
|
||||||
|
assert_eq!(parsed["permission_mode"], "danger-full-access");
|
||||||
|
assert_eq!(parsed["usage"]["messages"], 1);
|
||||||
|
assert!(parsed["usage"]["turns"].is_number());
|
||||||
|
assert!(parsed["workspace"]["cwd"].as_str().is_some());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed["workspace"]["session"],
|
parsed["workspace"]["session"],
|
||||||
session_path.to_str().expect("utf8 path")
|
session_path.to_str().expect("utf8 path")
|
||||||
);
|
);
|
||||||
|
assert!(parsed["workspace"]["changed_files"].is_number());
|
||||||
|
assert_eq!(parsed["workspace"]["loaded_config_files"].as_u64(), Some(0));
|
||||||
|
assert!(parsed["sandbox"]["filesystem_mode"].as_str().is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_claw(current_dir: &Path, args: &[&str]) -> Output {
|
fn run_claw(current_dir: &Path, args: &[&str]) -> Output {
|
||||||
|
|||||||
Reference in New Issue
Block a user