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:
YeonGyu-Kim 2026-04-21 12:55:06 +09:00
parent f55612ea47
commit 2b7095e4ae
2 changed files with 111 additions and 5 deletions

View File

@ -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"));

View File

@ -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()