mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-06 08:04: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)
|
||||
{
|
||||
let tracker = UsageTracker::from_session(&session);
|
||||
let usage = tracker.cumulative_usage();
|
||||
let context = status_context(Some(&resolved_path)).expect("status context");
|
||||
let value = json!({
|
||||
"kind": "status",
|
||||
"messages": session.messages.len(),
|
||||
"turns": tracker.turns(),
|
||||
"latest_total": tracker.current_turn_usage().total_tokens(),
|
||||
"cumulative_input": usage.input_tokens,
|
||||
"cumulative_output": usage.output_tokens,
|
||||
"cumulative_total": usage.total_tokens(),
|
||||
"workspace": {
|
||||
"cwd": context.cwd,
|
||||
"project_root": context.project_root,
|
||||
"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()),
|
||||
}
|
||||
});
|
||||
let value = status_json_value(
|
||||
"restored-session",
|
||||
StatusUsage {
|
||||
message_count: session.messages.len(),
|
||||
turns: tracker.turns(),
|
||||
latest: tracker.current_turn_usage(),
|
||||
cumulative: tracker.cumulative_usage(),
|
||||
estimated_tokens: 0,
|
||||
},
|
||||
default_permission_mode().as_str(),
|
||||
&context,
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&value).expect("status json")
|
||||
@@ -3845,54 +3840,68 @@ fn print_status_snapshot(
|
||||
),
|
||||
CliOutputFormat::Json => println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"kind": "status",
|
||||
"model": model,
|
||||
"permission_mode": permission_mode.as_str(),
|
||||
"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,
|
||||
}
|
||||
}))?
|
||||
serde_json::to_string_pretty(&status_json_value(
|
||||
model,
|
||||
usage,
|
||||
permission_mode.as_str(),
|
||||
&context,
|
||||
))?
|
||||
),
|
||||
}
|
||||
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(
|
||||
session_path: Option<&Path>,
|
||||
) -> 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["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 {
|
||||
|
||||
@@ -261,11 +261,18 @@ fn resumed_status_command_emits_structured_json_when_requested() {
|
||||
let parsed: Value =
|
||||
serde_json::from_str(stdout.trim()).expect("resume status output should be json");
|
||||
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!(
|
||||
parsed["workspace"]["session"],
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user