relayburn_cli/harnesses/codex.rs
1//! Codex `HarnessAdapter` — Rust port of `packages/cli/src/harnesses/codex.ts`.
2//!
3//! Codex shares the pending-stamp + watch-loop shape with OpenCode, so the
4//! adapter is constructed via [`super::pending_stamp::session_store_adapter`]
5//! instead of re-implementing the trait. The only codex-specific bits are:
6//!
7//! * `name = "codex"` — the dispatch key and log-line label.
8//! * `session_root` — `$HOME/.codex/sessions`, resolved lazily so tests
9//! that override `$HOME` see the override.
10//! * `ingest_sessions` — defers to [`relayburn_sdk::ingest_codex_sessions`],
11//! the codex-only ingest pass. The factory opens a fresh ledger handle
12//! per call (mirrors the TS lock-then-write-then-close shape; SQLite WAL
13//! keeps the per-tick open cheap).
14
15use std::future::Future;
16use std::path::PathBuf;
17use std::pin::Pin;
18
19use relayburn_sdk::{ingest_codex_sessions, IngestReport, RawIngestOptions, RawLedger};
20
21use super::pending_stamp;
22use super::HarnessAdapter;
23use crate::util::home::home_dir;
24
25/// `$HOME/.codex/sessions`. Mirrors the TS sibling
26/// (`path.join(homedir(), '.codex', 'sessions')`) and the SDK's internal
27/// `codex_sessions_dir` default.
28fn codex_sessions_dir() -> PathBuf {
29 home_dir().join(".codex").join("sessions")
30}
31
32/// Box-pin the SDK's `async fn ingest_codex_sessions` into a fn pointer
33/// the [`pending_stamp::SessionIngestor`] type alias accepts.
34fn codex_ingest<'a>(
35 ledger: &'a mut RawLedger,
36 opts: &'a RawIngestOptions,
37) -> Pin<Box<dyn Future<Output = anyhow::Result<IngestReport>> + Send + 'a>> {
38 Box::pin(ingest_codex_sessions(ledger, opts))
39}
40
41/// Hand out a `&'static dyn HarnessAdapter` for codex. The registry calls
42/// this once at lazy-init time. See
43/// [`pending_stamp::session_store_adapter`] for the leak semantics.
44pub fn adapter() -> &'static dyn HarnessAdapter {
45 pending_stamp::session_store_adapter("codex", codex_sessions_dir, codex_ingest)
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use crate::harnesses::test_env::with_test_home;
52
53 /// `adapter()` round-trips through the trait surface — name, session
54 /// root, and the `&'static` lifetime the registry requires. Mirrors
55 /// the registry's `pending_stamp_adapter_static_fits_runtime_registry`
56 /// check, but pinned to the codex configuration specifically.
57 #[test]
58 fn adapter_round_trip() {
59 let a: &'static dyn HarnessAdapter = adapter();
60 assert_eq!(a.name(), "codex");
61 with_test_home("/tmp/burn-codex-test-home", || {
62 assert_eq!(
63 a.session_root(),
64 PathBuf::from("/tmp/burn-codex-test-home/.codex/sessions")
65 );
66 });
67 }
68}