From e8cdf68bdda229e66c5ba649596be669f49e1dc2 Mon Sep 17 00:00:00 2001 From: Jeremy lewi Date: Tue, 14 Apr 2026 14:21:15 -0700 Subject: [PATCH] refactor: make rollout and app protocol wasm-tolerant --- codex-rs/app-server-protocol/Cargo.toml | 10 +- .../app-server-protocol/src/protocol/v2.rs | 5 + codex-rs/core/Cargo.toml | 4 +- codex-rs/core/src/lib.rs | 4 +- codex-rs/core/src/rollout.rs | 509 ++++++++++++++++-- codex-rs/core/src/state_db_bridge.rs | 196 ++++++- 6 files changed, 659 insertions(+), 69 deletions(-) diff --git a/codex-rs/app-server-protocol/Cargo.toml b/codex-rs/app-server-protocol/Cargo.toml index a9d90831ef..65e2f73b95 100644 --- a/codex-rs/app-server-protocol/Cargo.toml +++ b/codex-rs/app-server-protocol/Cargo.toml @@ -25,16 +25,18 @@ serde_with = { workspace = true } shlex = { workspace = true } strum_macros = { workspace = true } thiserror = { workspace = true } +ts-rs = { workspace = true } +inventory = { workspace = true } +tracing = { workspace = true } +uuid = { workspace = true, features = ["serde", "v7"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] rmcp = { workspace = true, default-features = false, features = [ "base64", "macros", "schemars", "server", ] } -ts-rs = { workspace = true } -inventory = { workspace = true } -tracing = { workspace = true } -uuid = { workspace = true, features = ["serde", "v7"] } [dev-dependencies] anyhow = { workspace = true } diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 9373c852d8..d1a1d685f1 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -5295,6 +5295,7 @@ impl McpServerElicitationAction { } } +#[cfg(not(target_arch = "wasm32"))] impl From for rmcp::model::ElicitationAction { fn from(value: McpServerElicitationAction) -> Self { match value { @@ -5305,6 +5306,7 @@ impl From for rmcp::model::ElicitationAction { } } +#[cfg(not(target_arch = "wasm32"))] impl From for McpServerElicitationAction { fn from(value: rmcp::model::ElicitationAction) -> Self { match value { @@ -5722,6 +5724,7 @@ pub struct McpServerElicitationRequestResponse { pub meta: Option, } +#[cfg(not(target_arch = "wasm32"))] impl From for rmcp::model::CreateElicitationResult { fn from(value: McpServerElicitationRequestResponse) -> Self { Self { @@ -5731,6 +5734,7 @@ impl From for rmcp::model::CreateElicitatio } } +#[cfg(not(target_arch = "wasm32"))] impl From for McpServerElicitationRequestResponse { fn from(value: rmcp::model::CreateElicitationResult) -> Self { Self { @@ -7186,6 +7190,7 @@ mod tests { assert_eq!(reason, Some("askForApproval.granular")); } + #[cfg(not(target_arch = "wasm32"))] #[test] fn mcp_server_elicitation_response_round_trips_rmcp_result() { let rmcp_result = rmcp::model::CreateElicitationResult { diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 56c6f98784..280f6b382e 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -47,10 +47,8 @@ codex-network-proxy = { workspace = true } codex-otel = { workspace = true } codex-plugin = { workspace = true } codex-protocol = { workspace = true } -codex-rollout = { workspace = true } codex-rmcp-client = { workspace = true } codex-sandboxing = { workspace = true } -codex-state = { workspace = true } codex-terminal-detection = { workspace = true } codex-tools = { workspace = true } codex-utils-absolute-path = { workspace = true } @@ -124,6 +122,8 @@ seccompiler = { workspace = true } core-foundation = "0.9" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +codex-rollout = { workspace = true } +codex-state = { workspace = true } tokio-tungstenite = { workspace = true } # Build OpenSSL from source for musl builds. diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 40af3a032a..d8e9efea3d 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -153,7 +153,9 @@ pub mod shell; pub mod shell_snapshot; pub mod spawn; pub mod state_db_bridge; -pub use codex_rollout::state_db; +pub mod state_db { + pub use crate::state_db_bridge::*; +} mod thread_rollout_truncation; mod tools; pub mod turn_diff_tracker; diff --git a/codex-rs/core/src/rollout.rs b/codex-rs/core/src/rollout.rs index c3a7218710..2bb2798f7d 100644 --- a/codex-rs/core/src/rollout.rs +++ b/codex-rs/core/src/rollout.rs @@ -1,63 +1,476 @@ -use crate::config::Config; -pub use codex_rollout::ARCHIVED_SESSIONS_SUBDIR; -pub use codex_rollout::INTERACTIVE_SESSION_SOURCES; -pub use codex_rollout::RolloutRecorder; -pub use codex_rollout::RolloutRecorderParams; -pub use codex_rollout::SESSIONS_SUBDIR; -pub use codex_rollout::SessionMeta; -pub use codex_rollout::append_thread_name; -pub use codex_rollout::find_archived_thread_path_by_id_str; -#[deprecated(note = "use find_thread_path_by_id_str")] -pub use codex_rollout::find_conversation_path_by_id_str; -pub use codex_rollout::find_thread_name_by_id; -pub use codex_rollout::find_thread_path_by_id_str; -pub use codex_rollout::find_thread_path_by_name_str; -pub use codex_rollout::rollout_date_parts; +#[cfg(not(target_arch = "wasm32"))] +mod native { + use crate::config::Config; -impl codex_rollout::RolloutConfigView for Config { - fn codex_home(&self) -> &std::path::Path { - self.codex_home.as_path() + pub use codex_rollout::ARCHIVED_SESSIONS_SUBDIR; + pub use codex_rollout::INTERACTIVE_SESSION_SOURCES; + pub use codex_rollout::RolloutRecorder; + pub use codex_rollout::RolloutRecorderParams; + pub use codex_rollout::SESSIONS_SUBDIR; + pub use codex_rollout::SessionMeta; + pub use codex_rollout::append_thread_name; + pub use codex_rollout::find_archived_thread_path_by_id_str; + #[deprecated(note = "use find_thread_path_by_id_str")] + pub use codex_rollout::find_conversation_path_by_id_str; + pub use codex_rollout::find_thread_name_by_id; + pub use codex_rollout::find_thread_path_by_id_str; + pub use codex_rollout::find_thread_path_by_name_str; + pub use codex_rollout::rollout_date_parts; + + impl codex_rollout::RolloutConfigView for Config { + fn codex_home(&self) -> &std::path::Path { + self.codex_home.as_path() + } + + fn sqlite_home(&self) -> &std::path::Path { + self.sqlite_home.as_path() + } + + fn cwd(&self) -> &std::path::Path { + self.cwd.as_path() + } + + fn model_provider_id(&self) -> &str { + self.model_provider_id.as_str() + } + + fn generate_memories(&self) -> bool { + self.memories.generate_memories + } } - fn sqlite_home(&self) -> &std::path::Path { - self.sqlite_home.as_path() + pub mod list { + pub use codex_rollout::list::*; } - fn cwd(&self) -> &std::path::Path { - self.cwd.as_path() + pub(crate) mod metadata { + pub(crate) use codex_rollout::metadata::builder_from_items; } - fn model_provider_id(&self) -> &str { - self.model_provider_id.as_str() + pub mod policy { + pub use codex_rollout::policy::*; } - fn generate_memories(&self) -> bool { - self.memories.generate_memories + pub mod recorder { + pub use codex_rollout::recorder::*; + } + + pub mod session_index { + pub use codex_rollout::session_index::*; + } + + pub(crate) use crate::session_rollout_init_error::map_session_init_error; + + pub(crate) mod truncation { + pub(crate) use crate::thread_rollout_truncation::*; } } -pub mod list { - pub use codex_rollout::list::*; +#[cfg(target_arch = "wasm32")] +mod wasm { + use std::collections::HashMap; + use std::collections::HashSet; + use std::ffi::OsStr; + use std::io; + use std::path::Path; + use std::path::PathBuf; + use std::sync::LazyLock; + + use codex_protocol::ThreadId; + use codex_protocol::dynamic_tools::DynamicToolSpec; + use codex_protocol::models::BaseInstructions; + use codex_protocol::protocol::InitialHistory; + use codex_protocol::protocol::RolloutItem; + use codex_protocol::protocol::SessionMeta; + use codex_protocol::protocol::SessionMetaLine; + use codex_protocol::protocol::SessionSource; + + use crate::config::Config; + use crate::state_db::StateDbHandle; + + pub const SESSIONS_SUBDIR: &str = "sessions"; + pub const ARCHIVED_SESSIONS_SUBDIR: &str = "archived_sessions"; + pub static INTERACTIVE_SESSION_SOURCES: LazyLock> = LazyLock::new(|| { + vec![ + SessionSource::Cli, + SessionSource::VSCode, + SessionSource::Custom("atlas".to_string()), + SessionSource::Custom("chatgpt".to_string()), + ] + }); + + pub use session_index::append_thread_name; + pub use session_index::find_thread_name_by_id; + pub use session_index::find_thread_path_by_name_str; + + #[deprecated(note = "use find_thread_path_by_id_str")] + pub async fn find_conversation_path_by_id_str( + codex_home: &Path, + id_str: &str, + ) -> io::Result> { + find_thread_path_by_id_str(codex_home, id_str).await + } + + pub async fn find_thread_path_by_id_str( + _codex_home: &Path, + _id_str: &str, + ) -> io::Result> { + Ok(None) + } + + pub async fn find_archived_thread_path_by_id_str( + _codex_home: &Path, + _id_str: &str, + ) -> io::Result> { + Ok(None) + } + + pub fn rollout_date_parts(_file_name: &OsStr) -> Option<(String, String, String)> { + None + } + + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] + pub enum EventPersistenceMode { + #[default] + Limited, + Extended, + } + + pub mod policy { + pub use super::EventPersistenceMode; + } + + #[derive(Clone)] + pub struct RolloutRecorder { + rollout_path: PathBuf, + state_db: Option, + _event_persistence_mode: EventPersistenceMode, + } + + #[derive(Clone)] + pub enum RolloutRecorderParams { + Create { + conversation_id: ThreadId, + forked_from_id: Option, + source: SessionSource, + base_instructions: BaseInstructions, + dynamic_tools: Vec, + event_persistence_mode: EventPersistenceMode, + }, + Resume { + path: PathBuf, + event_persistence_mode: EventPersistenceMode, + }, + } + + impl RolloutRecorderParams { + pub fn new( + conversation_id: ThreadId, + forked_from_id: Option, + source: SessionSource, + base_instructions: BaseInstructions, + dynamic_tools: Vec, + event_persistence_mode: EventPersistenceMode, + ) -> Self { + Self::Create { + conversation_id, + forked_from_id, + source, + base_instructions, + dynamic_tools, + event_persistence_mode, + } + } + + pub fn resume(path: PathBuf, event_persistence_mode: EventPersistenceMode) -> Self { + Self::Resume { + path, + event_persistence_mode, + } + } + } + + impl RolloutRecorder { + #[allow(clippy::too_many_arguments)] + pub async fn list_threads( + _config: &Config, + _page_size: usize, + _cursor: Option<&list::Cursor>, + _sort_key: list::ThreadSortKey, + _allowed_sources: &[SessionSource], + _model_providers: Option<&[String]>, + _default_provider: &str, + _search_term: Option<&str>, + ) -> io::Result { + Ok(list::ThreadsPage::default()) + } + + #[allow(clippy::too_many_arguments)] + pub async fn list_archived_threads( + _config: &Config, + _page_size: usize, + _cursor: Option<&list::Cursor>, + _sort_key: list::ThreadSortKey, + _allowed_sources: &[SessionSource], + _model_providers: Option<&[String]>, + _default_provider: &str, + _search_term: Option<&str>, + ) -> io::Result { + Ok(list::ThreadsPage::default()) + } + + pub async fn new( + config: &Config, + params: RolloutRecorderParams, + state_db: Option, + _state_builder: Option, + ) -> io::Result { + let rollout_path = match params { + RolloutRecorderParams::Create { + conversation_id, .. + } => config + .codex_home + .join(SESSIONS_SUBDIR) + .join(format!("browser-{}.jsonl", conversation_id)), + RolloutRecorderParams::Resume { path, .. } => path, + }; + Ok(Self { + rollout_path, + state_db, + _event_persistence_mode: EventPersistenceMode::Limited, + }) + } + + pub fn rollout_path(&self) -> &Path { + self.rollout_path.as_path() + } + + pub fn state_db(&self) -> Option { + self.state_db.clone() + } + + pub async fn record_items(&self, _items: &[RolloutItem]) -> io::Result<()> { + Ok(()) + } + + pub async fn persist(&self) -> io::Result<()> { + Ok(()) + } + + pub async fn flush(&self) -> io::Result<()> { + Ok(()) + } + + pub async fn load_rollout_items( + _path: &Path, + ) -> io::Result<(Vec, Option, usize)> { + Err(io::Error::other( + "rollout loading is unavailable on wasm32 without a browser persistence backend", + )) + } + + pub async fn get_rollout_history(_path: &Path) -> io::Result { + Err(io::Error::other( + "rollout history is unavailable on wasm32 without a browser persistence backend", + )) + } + + pub async fn shutdown(&self) -> io::Result<()> { + Ok(()) + } + } + + pub mod recorder { + pub use super::RolloutRecorder; + pub use super::RolloutRecorderParams; + } + + pub mod metadata { + use std::path::Path; + + use codex_protocol::protocol::RolloutItem; + + #[derive(Clone, Debug)] + pub struct ThreadMetadataBuilder; + + pub fn builder_from_items( + _items: &[RolloutItem], + _rollout_path: &Path, + ) -> Option { + None + } + } + + pub mod list { + use std::io; + use std::path::Path; + use std::path::PathBuf; + + use codex_protocol::ThreadId; + use codex_protocol::protocol::SessionMetaLine; + use codex_protocol::protocol::SessionSource; + + #[derive(Debug, Default, PartialEq)] + pub struct ThreadsPage { + pub items: Vec, + pub next_cursor: Option, + pub num_scanned_files: usize, + pub reached_scan_cap: bool, + } + + #[derive(Debug, PartialEq, Default)] + pub struct ThreadItem { + pub path: PathBuf, + pub thread_id: Option, + pub first_user_message: Option, + pub cwd: Option, + pub git_branch: Option, + pub git_sha: Option, + pub git_origin_url: Option, + pub source: Option, + pub agent_nickname: Option, + pub agent_role: Option, + pub model_provider: Option, + pub cli_version: Option, + pub created_at: Option, + pub updated_at: Option, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum ThreadSortKey { + CreatedAt, + UpdatedAt, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum ThreadListLayout { + NestedByDate, + Flat, + } + + pub struct ThreadListConfig<'a> { + pub allowed_sources: &'a [SessionSource], + pub model_providers: Option<&'a [String]>, + pub default_provider: &'a str, + pub layout: ThreadListLayout, + } + + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Cursor(String); + + pub fn parse_cursor(cursor: &str) -> Option { + if cursor.is_empty() { + None + } else { + Some(Cursor(cursor.to_string())) + } + } + + pub async fn get_threads( + _codex_home: &Path, + _page_size: usize, + _cursor: Option<&Cursor>, + _sort_key: ThreadSortKey, + _allowed_sources: &[SessionSource], + _model_providers: Option<&[String]>, + _default_provider: &str, + ) -> io::Result { + Ok(ThreadsPage::default()) + } + + pub async fn get_threads_in_root( + _root: PathBuf, + _page_size: usize, + _cursor: Option<&Cursor>, + _sort_key: ThreadSortKey, + _config: ThreadListConfig<'_>, + ) -> io::Result { + Ok(ThreadsPage::default()) + } + + pub async fn read_head_for_summary(_path: &Path) -> io::Result> { + Ok(Vec::new()) + } + + pub async fn read_session_meta_line(_path: &Path) -> io::Result { + Ok(SessionMetaLine { + meta: Default::default(), + git: None, + }) + } + + pub async fn find_thread_path_by_id_str( + _codex_home: &Path, + _id_str: &str, + ) -> io::Result> { + Ok(None) + } + + pub async fn find_archived_thread_path_by_id_str( + _codex_home: &Path, + _id_str: &str, + ) -> io::Result> { + Ok(None) + } + + pub fn rollout_date_parts( + _file_name: &std::ffi::OsStr, + ) -> Option<(String, String, String)> { + None + } + } + + pub mod session_index { + use std::collections::HashMap; + use std::collections::HashSet; + use std::io; + use std::path::Path; + use std::path::PathBuf; + + use codex_protocol::ThreadId; + + pub async fn append_thread_name( + _codex_home: &Path, + _thread_id: ThreadId, + _name: &str, + ) -> io::Result<()> { + Ok(()) + } + + pub async fn find_thread_name_by_id( + _codex_home: &Path, + _thread_id: &ThreadId, + ) -> io::Result> { + Ok(None) + } + + pub async fn find_thread_names_by_ids( + _codex_home: &Path, + _thread_ids: &HashSet, + ) -> io::Result> { + Ok(HashMap::new()) + } + + pub async fn find_thread_path_by_name_str( + _codex_home: &Path, + _name: &str, + ) -> io::Result> { + Ok(None) + } + } + + pub(crate) fn map_session_init_error( + err: &anyhow::Error, + _codex_home: &Path, + ) -> crate::error::CodexErr { + crate::error::CodexErr::Fatal(err.to_string()) + } + + pub(crate) mod truncation {} } -pub(crate) mod metadata { - pub(crate) use codex_rollout::metadata::builder_from_items; -} +#[cfg(not(target_arch = "wasm32"))] +pub use native::*; -pub mod policy { - pub use codex_rollout::policy::*; -} - -pub mod recorder { - pub use codex_rollout::recorder::*; -} - -pub mod session_index { - pub use codex_rollout::session_index::*; -} - -pub(crate) use crate::session_rollout_init_error::map_session_init_error; - -pub(crate) mod truncation { - pub(crate) use crate::thread_rollout_truncation::*; -} +#[cfg(target_arch = "wasm32")] +pub use wasm::*; diff --git a/codex-rs/core/src/state_db_bridge.rs b/codex-rs/core/src/state_db_bridge.rs index f073c498b5..b160a54301 100644 --- a/codex-rs/core/src/state_db_bridge.rs +++ b/codex-rs/core/src/state_db_bridge.rs @@ -1,21 +1,189 @@ +#[cfg(not(target_arch = "wasm32"))] use codex_rollout::state_db as rollout_state_db; -pub use codex_rollout::state_db::StateDbHandle; -pub use codex_rollout::state_db::apply_rollout_items; -pub use codex_rollout::state_db::find_rollout_path_by_id; -pub use codex_rollout::state_db::get_dynamic_tools; -pub use codex_rollout::state_db::list_thread_ids_db; -pub use codex_rollout::state_db::list_threads_db; -pub use codex_rollout::state_db::mark_thread_memory_mode_polluted; -pub use codex_rollout::state_db::normalize_cwd_for_state_db; -pub use codex_rollout::state_db::open_if_present; -pub use codex_rollout::state_db::persist_dynamic_tools; -pub use codex_rollout::state_db::read_repair_rollout_path; -pub use codex_rollout::state_db::reconcile_rollout; -pub use codex_rollout::state_db::touch_thread_updated_at; -pub use codex_state::LogEntry; use crate::config::Config; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::StateDbHandle; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::apply_rollout_items; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::find_rollout_path_by_id; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::get_dynamic_tools; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::init; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::list_thread_ids_db; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::list_threads_db; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::mark_thread_memory_mode_polluted; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::normalize_cwd_for_state_db; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::open_if_present; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::persist_dynamic_tools; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::read_repair_rollout_path; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::reconcile_rollout; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_rollout::state_db::touch_thread_updated_at; +#[cfg(not(target_arch = "wasm32"))] +pub use codex_state::LogEntry; + +#[cfg(not(target_arch = "wasm32"))] pub async fn get_state_db(config: &Config) -> Option { rollout_state_db::get_state_db(config).await } + +#[cfg(target_arch = "wasm32")] +pub mod wasm { + use std::path::Path; + use std::path::PathBuf; + use std::sync::Arc; + + use chrono::DateTime; + use chrono::Utc; + use codex_protocol::ThreadId; + use codex_protocol::dynamic_tools::DynamicToolSpec; + use codex_protocol::protocol::RolloutItem; + use codex_protocol::protocol::SessionSource; + + use crate::config::Config; + use crate::rollout::list::Cursor; + use crate::rollout::list::ThreadSortKey; + + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct StateRuntimeStub; + + pub type StateDbHandle = Arc; + + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct LogEntry; + + pub async fn init(_config: &Config) -> Option { + None + } + + pub async fn get_state_db(_config: &Config) -> Option { + None + } + + pub async fn open_if_present( + _codex_home: &Path, + _default_provider: &str, + ) -> Option { + None + } + + pub async fn find_rollout_path_by_id( + _context: Option<&StateRuntimeStub>, + _thread_id: ThreadId, + _archived_only: Option, + _stage: &str, + ) -> Option { + None + } + + pub async fn get_dynamic_tools( + _context: Option<&StateRuntimeStub>, + _thread_id: ThreadId, + _stage: &str, + ) -> Option> { + None + } + + pub async fn list_thread_ids_db( + _context: Option<&StateRuntimeStub>, + _codex_home: &Path, + _page_size: usize, + _cursor: Option<&Cursor>, + _sort_key: ThreadSortKey, + _allowed_sources: &[SessionSource], + _model_providers: Option<&[String]>, + _archived_only: bool, + _stage: &str, + ) -> Option> { + None + } + + pub async fn list_threads_db( + _context: Option<&StateRuntimeStub>, + _codex_home: &Path, + _page_size: usize, + _cursor: Option<&Cursor>, + _sort_key: ThreadSortKey, + _allowed_sources: &[SessionSource], + _model_providers: Option<&[String]>, + _archived: bool, + _search_term: Option<&str>, + ) -> Option { + None + } + + pub fn normalize_cwd_for_state_db(cwd: &Path) -> PathBuf { + cwd.to_path_buf() + } + + pub async fn persist_dynamic_tools( + _context: Option<&StateRuntimeStub>, + _thread_id: ThreadId, + _tools: Option<&[DynamicToolSpec]>, + _stage: &str, + ) { + } + + pub async fn mark_thread_memory_mode_polluted( + _context: Option<&StateRuntimeStub>, + _thread_id: ThreadId, + _stage: &str, + ) { + } + + pub async fn reconcile_rollout( + _context: Option<&StateRuntimeStub>, + _rollout_path: &Path, + _default_provider: &str, + _builder: Option<&()>, + _items: &[RolloutItem], + _archived_only: Option, + _new_thread_memory_mode: Option<&str>, + ) { + } + + pub async fn read_repair_rollout_path( + _context: Option<&StateRuntimeStub>, + _thread_id: Option, + _archived_only: Option, + _rollout_path: &Path, + ) { + } + + #[allow(clippy::too_many_arguments)] + pub async fn apply_rollout_items( + _context: Option<&StateRuntimeStub>, + _rollout_path: &Path, + _default_provider: &str, + _builder: Option<&()>, + _items: &[RolloutItem], + _stage: &str, + _new_thread_memory_mode: Option<&str>, + _updated_at_override: Option>, + ) { + } + + pub async fn touch_thread_updated_at( + _context: Option<&StateRuntimeStub>, + _thread_id: Option, + _updated_at: DateTime, + _stage: &str, + ) -> bool { + false + } +} + +#[cfg(target_arch = "wasm32")] +pub use wasm::*;