From b41f26f4843678e6f20cb57b37549736e50aea0c Mon Sep 17 00:00:00 2001 From: Ryan Ragona Date: Sat, 26 Apr 2025 07:05:19 -0700 Subject: [PATCH] metadata --- codex-rs/Cargo.lock | 88 ++++++++++++++++++++++++++++++++++- codex-rs/session/Cargo.toml | 3 ++ codex-rs/session/src/cli.rs | 48 ++++++++++++++----- codex-rs/session/src/store.rs | 20 ++++---- 4 files changed, 137 insertions(+), 22 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 7614ccbbfc..8fc396b249 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -495,7 +495,7 @@ dependencies = [ "bytes", "clap", "codex-apply-patch", - "dirs", + "dirs 6.0.0", "env-flags", "eventsource-stream", "fs-err", @@ -587,11 +587,14 @@ dependencies = [ "clap", "codex-core", "codex-exec", + "comfy-table", "directories", + "dirs 5.0.1", "libc", "nix 0.27.1", "serde", "serde_json", + "sysinfo", "tokio", "tracing", "tracing-subscriber", @@ -651,6 +654,17 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "comfy-table" +version = "7.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width 0.2.0", +] + [[package]] name = "compact_str" version = "0.8.1" @@ -708,6 +722,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -883,6 +916,15 @@ dependencies = [ "dirs-sys 0.4.1", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "dirs" version = "6.0.0" @@ -2152,6 +2194,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2566,6 +2617,26 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.11" @@ -3300,6 +3371,21 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "sysinfo" +version = "0.29.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "system-configuration" version = "0.6.1" diff --git a/codex-rs/session/Cargo.toml b/codex-rs/session/Cargo.toml index 0e8fc13b48..27d8af7d31 100644 --- a/codex-rs/session/Cargo.toml +++ b/codex-rs/session/Cargo.toml @@ -31,6 +31,9 @@ chrono = { version = "0.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1" directories = "5" +dirs = "5" +comfy-table = "7" +sysinfo = "0.29" # Re-use the codex-exec library for its CLI definition codex_exec = { package = "codex-exec", path = "../exec" } diff --git a/codex-rs/session/src/cli.rs b/codex-rs/session/src/cli.rs index a07c5e0068..0a06f81c07 100644 --- a/codex-rs/session/src/cli.rs +++ b/codex-rs/session/src/cli.rs @@ -62,21 +62,21 @@ impl CreateCmd { .id .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()); - // Persist basic metadata & directory skeleton *before* spawning the process. - let meta = store::SessionMeta { - id: id.clone(), - created_at: chrono::Utc::now(), - }; - let paths = store::paths_for(&id)?; - store::materialise(&paths, &meta)?; + store::prepare_dirs(&paths)?; - // Convert exec_cli back into a Vec so we can forward them verbatim. let exec_args = build_exec_args(&self.exec_cli); - // Spawn the background agent and immediately detach – we never hold on to the - // Child handle. - let _child = spawn::spawn_agent(&paths, &exec_args)?; + // Spawn the background agent and immediately detach. + let child = spawn::spawn_agent(&paths, &exec_args)?; + + // Record metadata (with PID) *after* successful spawn. + let meta = store::SessionMeta { + id: id.clone(), + pid: child.id().unwrap_or_default(), + created_at: chrono::Utc::now(), + }; + store::write_meta(&paths, &meta)?; println!("{id}"); Ok(()) } @@ -200,10 +200,34 @@ pub struct ListCmd; impl ListCmd { pub async fn run(self) -> Result<()> { + use comfy_table::{Cell, Table}; + use sysinfo::{SystemExt, Pid, PidExt}; + let sessions = store::list_sessions()?; + let mut sys = sysinfo::System::new_all(); + sys.refresh_processes(); + + let mut table = Table::new(); + table.set_header(["ID", "PID", "STATUS", "CREATED"]); + for meta in sessions { - println!("{}\t{}", meta.id, meta.created_at); + let status: &str = if meta.pid == 0 { + "unknown" + } else if sys.process(Pid::from_u32(meta.pid)).is_some() { + "running" + } else { + "exited" + }; + + table.add_row([ + Cell::new(&meta.id), + Cell::new(meta.pid), + Cell::new(status), + Cell::new(meta.created_at.to_rfc3339()), + ]); } + + println!("{table}"); Ok(()) } } diff --git a/codex-rs/session/src/store.rs b/codex-rs/session/src/store.rs index 0f13a8d8e0..f8f95274df 100644 --- a/codex-rs/session/src/store.rs +++ b/codex-rs/session/src/store.rs @@ -1,7 +1,6 @@ //! Session bookkeeping – on-disk layout and simple helpers. use anyhow::{Context, Result}; -use directories::ProjectDirs; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -25,32 +24,35 @@ pub fn paths_for(id: &str) -> Result { } fn base_dir() -> Result { - let dirs = ProjectDirs::from("dev", "codex", "codex-session") - .context("unable to resolve data directory")?; - Ok(dirs.data_dir().to_owned()) + // ~/.codex/sessions + let home = dirs::home_dir().context("could not resolve home directory")?; + Ok(home.join("codex").join("sessions")) } #[derive(Serialize, Deserialize, Debug)] pub struct SessionMeta { pub id: String, + pub pid: u32, pub created_at: chrono::DateTime, } /// Create the on-disk directory structure and write metadata + empty log files. -pub fn materialise(paths: &Paths, meta: &SessionMeta) -> Result<()> { +/// Create directory & empty log files. Does **not** write metadata; caller should write that +/// once the child process has actually been spawned so we can record its PID. +pub fn prepare_dirs(paths: &Paths) -> Result<()> { std::fs::create_dir_all(&paths.dir)?; - // Metadata (pretty-printed for manual inspection). - std::fs::write(&paths.meta, serde_json::to_vec_pretty(meta)?)?; - - // Touch stdout/stderr so they exist even before the agent writes. for p in [&paths.stdout, &paths.stderr] { std::fs::OpenOptions::new() .create(true) .append(true) .open(p)?; } + Ok(()) +} +pub fn write_meta(paths: &Paths, meta: &SessionMeta) -> Result<()> { + std::fs::write(&paths.meta, serde_json::to_vec_pretty(meta)?)?; Ok(()) }