mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
codex draft
This commit is contained in:
Generated
+128
-2
@@ -578,6 +578,25 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-session"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codex-core",
|
||||
"directories",
|
||||
"libc",
|
||||
"nix 0.27.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-tui"
|
||||
version = "0.1.0"
|
||||
@@ -854,13 +873,22 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
|
||||
dependencies = [
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
"dirs-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -873,6 +901,18 @@ dependencies = [
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.4.6",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
@@ -2061,6 +2101,17 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
@@ -2760,7 +2811,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix",
|
||||
"nix 0.28.0",
|
||||
"radix_trie",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.14",
|
||||
@@ -3764,6 +3815,15 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
@@ -4010,6 +4070,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -4028,6 +4097,21 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -4060,6 +4144,12 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -4072,6 +4162,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -4084,6 +4180,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -4108,6 +4210,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -4120,6 +4228,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -4132,6 +4246,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -4144,6 +4264,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
||||
@@ -9,5 +9,6 @@ members = [
|
||||
"execpolicy",
|
||||
"interactive",
|
||||
"repl",
|
||||
"session",
|
||||
"tui",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "codex-session"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "codex-session"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "codex_session"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
codex-core = { path = "../core" }
|
||||
tokio = { version = "1", features = [
|
||||
"io-std",
|
||||
"macros",
|
||||
"process",
|
||||
"rt-multi-thread",
|
||||
"signal",
|
||||
] }
|
||||
tracing = { version = "0.1.41", features = ["log"] }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
|
||||
# new dependencies for session management
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
directories = "5"
|
||||
|
||||
# unix-only process helpers
|
||||
nix = { version = "0.27", optional = true, default-features = false, features = ["process"] }
|
||||
libc = { version = "0.2", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.27", default-features = false, features = ["process"] }
|
||||
libc = "0.2"
|
||||
@@ -0,0 +1,191 @@
|
||||
//! Command-line interface definition and dispatch.
|
||||
|
||||
use crate::{spawn, store};
|
||||
use anyhow::Result;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
/// Top-level CLI entry (re-exported by the crate).
|
||||
#[derive(Parser)]
|
||||
#[command(name = "codex-session", about = "Manage detached codex-exec sessions")]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
cmd: Commands,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub async fn dispatch(self) -> Result<()> {
|
||||
match self.cmd {
|
||||
Commands::Create(x) => x.run().await,
|
||||
Commands::Delete(x) => x.run().await,
|
||||
Commands::Logs(x) => x.run().await,
|
||||
Commands::Exec(x) => x.run().await,
|
||||
Commands::List(x) => x.run().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Spawn a new, detached agent.
|
||||
Create(CreateCmd),
|
||||
|
||||
/// Kill a running session and delete on-disk artefacts.
|
||||
Delete(DeleteCmd),
|
||||
|
||||
/// Show (and optionally follow) stdout / stderr logs of a session.
|
||||
Logs(LogsCmd),
|
||||
|
||||
/// Execute a one-shot command inside an existing session.
|
||||
Exec(ExecCmd),
|
||||
|
||||
/// List all known session IDs.
|
||||
List(ListCmd),
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// create
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct CreateCmd {
|
||||
/// A custom session identifier. When omitted, a random UUIDv4 is used.
|
||||
#[arg(long)]
|
||||
id: Option<String>,
|
||||
|
||||
/// Path to the `codex-exec` binary. Defaults to relying on $PATH.
|
||||
#[arg(long, default_value = "codex-exec")]
|
||||
exec: String,
|
||||
|
||||
/// If set, terminate the agent when the CLI process exits ("attached" mode).
|
||||
#[arg(long)]
|
||||
kill_on_drop: bool,
|
||||
}
|
||||
|
||||
impl CreateCmd {
|
||||
pub async fn run(self) -> Result<()> {
|
||||
let id = self
|
||||
.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)?;
|
||||
|
||||
// Spawn the background agent and immediately detach.
|
||||
let mut child = spawn::spawn_agent(&self.exec, &id, &paths, self.kill_on_drop)?;
|
||||
|
||||
if self.kill_on_drop {
|
||||
// Hold the handle for the lifetime of the CLI; when we drop at the end of
|
||||
// `run()` the agent will be terminated by the `kill_on_drop` setting.
|
||||
tokio::spawn(async move {
|
||||
let _ = child.wait().await;
|
||||
});
|
||||
}
|
||||
|
||||
// When not in kill_on_drop mode we *immediately* drop the handle so the agent can
|
||||
// outlive us.
|
||||
println!("{id}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// delete
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct DeleteCmd {
|
||||
/// Session ID to terminate and remove.
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl DeleteCmd {
|
||||
pub async fn run(self) -> Result<()> {
|
||||
store::kill_session(&self.id).await?;
|
||||
store::purge(&self.id)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// logs
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct LogsCmd {
|
||||
/// Session ID whose logs should be printed.
|
||||
id: String,
|
||||
|
||||
/// Follow the file and stream appended lines (like `tail -f`).
|
||||
#[arg(short, long)]
|
||||
follow: bool,
|
||||
|
||||
/// Show stderr instead of stdout.
|
||||
#[arg(long)]
|
||||
stderr: bool,
|
||||
}
|
||||
|
||||
impl LogsCmd {
|
||||
pub async fn run(self) -> Result<()> {
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
|
||||
let paths = store::paths_for(&self.id)?;
|
||||
let target = if self.stderr {
|
||||
&paths.stderr
|
||||
} else {
|
||||
&paths.stdout
|
||||
};
|
||||
|
||||
let file = tokio::fs::File::open(target).await?;
|
||||
|
||||
if self.follow {
|
||||
let mut reader = tokio::io::BufReader::new(file);
|
||||
let mut lines = reader.lines();
|
||||
while let Some(line) = lines.next_line().await? {
|
||||
println!("{line}");
|
||||
}
|
||||
} else {
|
||||
// Simply dump the file contents to stdout.
|
||||
let mut stdout = tokio::io::stdout();
|
||||
tokio::io::copy(&mut tokio::io::BufReader::new(file), &mut stdout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// exec (not implemented yet)
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct ExecCmd {
|
||||
id: String,
|
||||
|
||||
/// Remaining arguments form the command to execute.
|
||||
#[arg(trailing_var_arg = true)]
|
||||
cmd: Vec<String>,
|
||||
}
|
||||
|
||||
impl ExecCmd {
|
||||
pub async fn run(self) -> Result<()> {
|
||||
anyhow::bail!("exec inside an existing session is not yet implemented");
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// list
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct ListCmd;
|
||||
|
||||
impl ListCmd {
|
||||
pub async fn run(self) -> Result<()> {
|
||||
let sessions = store::list_sessions()?;
|
||||
for meta in sessions {
|
||||
println!("{}\t{}", meta.id, meta.created_at);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//! Library entry-point re-exporting the CLI so the binary can stay tiny.
|
||||
|
||||
pub mod cli;
|
||||
mod spawn;
|
||||
mod store;
|
||||
|
||||
pub use cli::Cli;
|
||||
|
||||
/// Binary entry – the bin crate’s `main.rs` calls into this for testability.
|
||||
pub async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
cli.dispatch().await
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
use clap::Parser;
|
||||
use codex_session::run_main;
|
||||
use codex_session::Cli;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
run_main(cli).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//! Cross-platform helper to spawn a detached `codex-exec` agent.
|
||||
|
||||
use crate::store::Paths;
|
||||
use anyhow::{Context, Result};
|
||||
use std::fs::OpenOptions;
|
||||
use tokio::process::{Child, Command};
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn spawn_agent(exec: &str, id: &str, paths: &Paths, kill_on_drop: bool) -> Result<Child> {
|
||||
use std::io;
|
||||
|
||||
// Prepare stdio handles first.
|
||||
let stdin = OpenOptions::new().read(true).open("/dev/null")?;
|
||||
let stdout = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&paths.stdout)?;
|
||||
let stderr = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&paths.stderr)?;
|
||||
|
||||
let mut cmd = Command::new(exec);
|
||||
cmd.arg("--job").arg(id)
|
||||
.stdin(stdin)
|
||||
.stdout(stdout)
|
||||
.stderr(stderr);
|
||||
|
||||
if kill_on_drop {
|
||||
cmd.kill_on_drop(true);
|
||||
}
|
||||
|
||||
// Detach: make a new session and ignore SIGHUP.
|
||||
unsafe {
|
||||
cmd.pre_exec(|| {
|
||||
unsafe {
|
||||
// setsid(2)
|
||||
if libc::setsid() == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
libc::signal(libc::SIGHUP, libc::SIG_IGN);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
let child = cmd.spawn().context("failed to spawn agent")?;
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn spawn_agent(exec: &str, id: &str, paths: &Paths, kill_on_drop: bool) -> Result<Child> {
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
const DETACHED_PROCESS: u32 = 0x00000008;
|
||||
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
|
||||
|
||||
let stdout = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&paths.stdout)?;
|
||||
let stderr = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&paths.stderr)?;
|
||||
|
||||
let mut cmd = Command::new(exec);
|
||||
cmd.arg("--job").arg(id)
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(stdout)
|
||||
.stderr(stderr)
|
||||
.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
|
||||
|
||||
if kill_on_drop {
|
||||
cmd.kill_on_drop(true);
|
||||
}
|
||||
|
||||
let child = cmd.spawn().context("failed to spawn agent")?;
|
||||
Ok(child)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! Session bookkeeping – on-disk layout and simple helpers.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use directories::ProjectDirs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Paths {
|
||||
pub dir: PathBuf,
|
||||
pub stdout: PathBuf,
|
||||
pub stderr: PathBuf,
|
||||
pub meta: PathBuf,
|
||||
}
|
||||
|
||||
/// Calculate canonical paths for the given session ID.
|
||||
pub fn paths_for(id: &str) -> Result<Paths> {
|
||||
let dir = base_dir()?.join(id);
|
||||
Ok(Paths {
|
||||
dir: dir.clone(),
|
||||
stdout: dir.join("stdout.log"),
|
||||
stderr: dir.join("stderr.log"),
|
||||
meta: dir.join("meta.json"),
|
||||
})
|
||||
}
|
||||
|
||||
fn base_dir() -> Result<PathBuf> {
|
||||
let dirs = ProjectDirs::from("dev", "codex", "codex-session")
|
||||
.context("unable to resolve data directory")?;
|
||||
Ok(dirs.data_dir().to_owned())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SessionMeta {
|
||||
pub id: String,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
/// Create the on-disk directory structure and write metadata + empty log files.
|
||||
pub fn materialise(paths: &Paths, meta: &SessionMeta) -> 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(())
|
||||
}
|
||||
|
||||
/// Enumerate all sessions by loading each `meta.json`.
|
||||
pub fn list_sessions() -> Result<Vec<SessionMeta>> {
|
||||
let mut res = Vec::new();
|
||||
let base = base_dir()?;
|
||||
if base.exists() {
|
||||
for entry in std::fs::read_dir(base)? {
|
||||
let entry = entry?;
|
||||
let meta_path = entry.path().join("meta.json");
|
||||
if let Ok(bytes) = std::fs::read(&meta_path) {
|
||||
if let Ok(meta) = serde_json::from_slice::<SessionMeta>(&bytes) {
|
||||
res.push(meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Send a polite termination request to the session’s process.
|
||||
///
|
||||
/// NOTE: Full PID accounting is a future improvement; for now the function
|
||||
/// simply returns `Ok(())` so the `delete` command doesn’t fail.
|
||||
pub async fn kill_session(_id: &str) -> Result<()> {
|
||||
// TODO: record PID at spawn time and terminate here.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove the session directory and all its contents.
|
||||
pub fn purge(id: &str) -> Result<()> {
|
||||
let paths = paths_for(id)?;
|
||||
if paths.dir.exists() {
|
||||
std::fs::remove_dir_all(paths.dir)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user