mirror of
https://github.com/ultraworkers/claw-code.git
synced 2026-04-25 05:38:10 +08:00
feat: surface active session status and session id
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
f55612ea47
commit
2b7095e4ae
@ -1556,6 +1556,8 @@ fn render_doctor_report() -> Result<DoctorReport, Box<dyn std::error::Error>> {
|
||||
project_root,
|
||||
git_branch,
|
||||
git_summary,
|
||||
active_session: false,
|
||||
session_id: None,
|
||||
sandbox_status: resolve_sandbox_status(sandbox_config.sandbox(), &cwd),
|
||||
};
|
||||
Ok(DoctorReport {
|
||||
@ -2376,6 +2378,8 @@ struct ResumeCommandOutcome {
|
||||
struct StatusContext {
|
||||
cwd: PathBuf,
|
||||
session_path: Option<PathBuf>,
|
||||
active_session: bool,
|
||||
session_id: Option<String>,
|
||||
loaded_config_files: usize,
|
||||
discovered_config_files: usize,
|
||||
memory_file_count: usize,
|
||||
@ -2385,6 +2389,16 @@ struct StatusContext {
|
||||
sandbox_status: runtime::SandboxStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct WorkerStateSnapshot {
|
||||
#[serde(default)]
|
||||
status: Option<String>,
|
||||
#[serde(default)]
|
||||
session_id: Option<String>,
|
||||
#[serde(default)]
|
||||
prompt_in_flight: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct StatusUsage {
|
||||
message_count: usize,
|
||||
@ -4993,6 +5007,8 @@ fn status_json_value(
|
||||
"kind": "status",
|
||||
"model": model,
|
||||
"permission_mode": permission_mode,
|
||||
"active_session": context.active_session,
|
||||
"session_id": context.session_id,
|
||||
"usage": {
|
||||
"messages": usage.message_count,
|
||||
"turns": usage.turns,
|
||||
@ -5051,9 +5067,12 @@ fn status_context(
|
||||
parse_git_status_metadata(project_context.git_status.as_deref());
|
||||
let git_summary = parse_git_workspace_summary(project_context.git_status.as_deref());
|
||||
let sandbox_status = resolve_sandbox_status(runtime_config.sandbox(), &cwd);
|
||||
let worker_state = read_worker_state_snapshot(&cwd);
|
||||
Ok(StatusContext {
|
||||
cwd,
|
||||
session_path: session_path.map(Path::to_path_buf),
|
||||
active_session: worker_state.as_ref().is_some_and(worker_state_is_active),
|
||||
session_id: worker_state.and_then(|snapshot| snapshot.session_id),
|
||||
loaded_config_files: runtime_config.loaded_entries().len(),
|
||||
discovered_config_files,
|
||||
memory_file_count: project_context.instruction_files.len(),
|
||||
@ -5064,6 +5083,20 @@ fn status_context(
|
||||
})
|
||||
}
|
||||
|
||||
fn read_worker_state_snapshot(cwd: &Path) -> Option<WorkerStateSnapshot> {
|
||||
let state_path = cwd.join(".claw").join("worker-state.json");
|
||||
let raw = fs::read_to_string(state_path).ok()?;
|
||||
serde_json::from_str(&raw).ok()
|
||||
}
|
||||
|
||||
fn worker_state_is_active(snapshot: &WorkerStateSnapshot) -> bool {
|
||||
snapshot.prompt_in_flight
|
||||
|| matches!(
|
||||
snapshot.status.as_deref(),
|
||||
Some("spawning" | "trust_required" | "ready_for_prompt" | "running")
|
||||
)
|
||||
}
|
||||
|
||||
fn format_status_report(
|
||||
model: &str,
|
||||
usage: StatusUsage,
|
||||
@ -5116,10 +5149,7 @@ fn format_status_report(
|
||||
context.git_summary.staged_files,
|
||||
context.git_summary.unstaged_files,
|
||||
context.git_summary.untracked_files,
|
||||
context.session_path.as_ref().map_or_else(
|
||||
|| "live-repl".to_string(),
|
||||
|path| path.display().to_string()
|
||||
),
|
||||
format_active_session(context),
|
||||
context.loaded_config_files,
|
||||
context.discovered_config_files,
|
||||
context.memory_file_count,
|
||||
@ -5133,6 +5163,17 @@ fn format_status_report(
|
||||
)
|
||||
}
|
||||
|
||||
fn format_active_session(context: &StatusContext) -> String {
|
||||
if context.active_session {
|
||||
match context.session_id.as_deref() {
|
||||
Some(session_id) => format!("active ({session_id})"),
|
||||
None => "active".to_string(),
|
||||
}
|
||||
} else {
|
||||
"idle".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_sandbox_report(status: &runtime::SandboxStatus) -> String {
|
||||
format!(
|
||||
"Sandbox
|
||||
@ -10346,6 +10387,8 @@ mod tests {
|
||||
&super::StatusContext {
|
||||
cwd: PathBuf::from("/tmp/project"),
|
||||
session_path: Some(PathBuf::from("session.jsonl")),
|
||||
active_session: true,
|
||||
session_id: Some("boot-status-test".to_string()),
|
||||
loaded_config_files: 2,
|
||||
discovered_config_files: 3,
|
||||
memory_file_count: 4,
|
||||
@ -10374,10 +10417,10 @@ mod tests {
|
||||
status.contains("Git state dirty · 3 files · 1 staged, 1 unstaged, 1 untracked")
|
||||
);
|
||||
assert!(status.contains("Changed files 3"));
|
||||
assert!(status.contains("Session active (boot-status-test)"));
|
||||
assert!(status.contains("Staged 1"));
|
||||
assert!(status.contains("Unstaged 1"));
|
||||
assert!(status.contains("Untracked 1"));
|
||||
assert!(status.contains("Session session.jsonl"));
|
||||
assert!(status.contains("Config files loaded 2/3"));
|
||||
assert!(status.contains("Memory files 4"));
|
||||
assert!(status.contains("Suggested flow /status → /diff → /commit"));
|
||||
|
||||
@ -39,6 +39,8 @@ fn status_and_sandbox_emit_json_when_requested() {
|
||||
|
||||
let status = assert_json_command(&root, &["--output-format", "json", "status"]);
|
||||
assert_eq!(status["kind"], "status");
|
||||
assert_eq!(status["active_session"], false);
|
||||
assert!(status["session_id"].is_null());
|
||||
assert!(status["workspace"]["cwd"].as_str().is_some());
|
||||
|
||||
let sandbox = assert_json_command(&root, &["--output-format", "json", "sandbox"]);
|
||||
@ -384,6 +386,47 @@ fn resumed_version_and_init_emit_structured_json_when_requested() {
|
||||
assert!(root.join("CLAUDE.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_json_surfaces_active_session_and_boot_session_id_from_worker_state() {
|
||||
let root = unique_temp_dir("status-worker-state-json");
|
||||
fs::create_dir_all(&root).expect("temp dir should exist");
|
||||
write_worker_state_fixture(&root, "running", "boot-fixture-123");
|
||||
|
||||
let status = assert_json_command(&root, &["--output-format", "json", "status"]);
|
||||
assert_eq!(status["kind"], "status");
|
||||
assert_eq!(status["active_session"], true);
|
||||
assert_eq!(status["session_id"], "boot-fixture-123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_text_surfaces_active_session_and_boot_session_id_from_worker_state() {
|
||||
let root = unique_temp_dir("status-worker-state-text");
|
||||
fs::create_dir_all(&root).expect("temp dir should exist");
|
||||
write_worker_state_fixture(&root, "running", "boot-fixture-456");
|
||||
|
||||
let output = run_claw(&root, &["status"], &[]);
|
||||
assert!(output.status.success());
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(stdout.contains("Session active (boot-fixture-456)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worker_state_fixture_round_trips_session_id_across_status_surface() {
|
||||
let root = unique_temp_dir("status-worker-state-roundtrip");
|
||||
fs::create_dir_all(&root).expect("temp dir should exist");
|
||||
let session_id = "boot-roundtrip-789";
|
||||
write_worker_state_fixture(&root, "running", session_id);
|
||||
|
||||
let status = assert_json_command(&root, &["--output-format", "json", "status"]);
|
||||
assert_eq!(status["active_session"], true);
|
||||
assert_eq!(status["session_id"], session_id);
|
||||
|
||||
let raw = fs::read_to_string(root.join(".claw").join("worker-state.json"))
|
||||
.expect("worker state should exist");
|
||||
let state: Value = serde_json::from_str(&raw).expect("worker state should be valid json");
|
||||
assert_eq!(state["session_id"], session_id);
|
||||
}
|
||||
|
||||
fn assert_json_command(current_dir: &Path, args: &[&str]) -> Value {
|
||||
assert_json_command_with_env(current_dir, args, &[])
|
||||
}
|
||||
@ -431,6 +474,26 @@ fn write_upstream_fixture(root: &Path) -> PathBuf {
|
||||
upstream
|
||||
}
|
||||
|
||||
fn write_worker_state_fixture(root: &Path, status: &str, session_id: &str) {
|
||||
let claw_dir = root.join(".claw");
|
||||
fs::create_dir_all(&claw_dir).expect("worker state dir should exist");
|
||||
fs::write(
|
||||
claw_dir.join("worker-state.json"),
|
||||
serde_json::to_string_pretty(&serde_json::json!({
|
||||
"worker_id": "worker-test",
|
||||
"session_id": session_id,
|
||||
"status": status,
|
||||
"is_ready": status == "ready_for_prompt",
|
||||
"trust_gate_cleared": false,
|
||||
"prompt_in_flight": status == "running",
|
||||
"updated_at": 1,
|
||||
"seconds_since_update": 0
|
||||
}))
|
||||
.expect("worker state json should serialize"),
|
||||
)
|
||||
.expect("worker state fixture should write");
|
||||
}
|
||||
|
||||
fn write_session_fixture(root: &Path, session_id: &str, user_text: Option<&str>) -> PathBuf {
|
||||
let session_path = root.join("session.jsonl");
|
||||
let mut session = Session::new()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user