mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Honor the configured SQLite home across state consumers (#34994)
## Why Codex and SQLite data can use separate home directories, but state consumers could reconstruct database paths from the Codex home instead of consistently using the resolved SQLite configuration. ## What changed - Pass `SqliteConfig` through the core, rollout, state runtime, and thread store instead of passing a directory and rebuilding the configuration downstream. - Use that shared configuration for state, logs, memories, goals, and paginated thread-history database access, including integrity checks and cleanup. - Reject state database handles whose SQLite configuration does not match the requesting store. ## Testing Add coverage with separate Codex and SQLite homes that verifies startup backfill, thread listing, and paginated history all use the configured SQLite directory. GitOrigin-RevId: 1de1cdd1d6ff1d70bbb6c360c8352e6543fb8ebf
This commit is contained in:
committed by
copyberry
parent
74e9d7efc4
commit
c769a05340
Generated
+3
@@ -1853,6 +1853,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-protocol",
|
||||
"codex-state",
|
||||
"codex-utils-absolute-path",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2766,6 +2767,7 @@ dependencies = [
|
||||
"codex-model-provider-info",
|
||||
"codex-models-manager",
|
||||
"codex-protocol",
|
||||
"codex-state",
|
||||
"codex-utils-absolute-path",
|
||||
]
|
||||
|
||||
@@ -3167,6 +3169,7 @@ dependencies = [
|
||||
"codex-protocol",
|
||||
"codex-state",
|
||||
"codex-tools",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-template",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
|
||||
@@ -19,6 +19,7 @@ serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct LocalAgentGraphStore {
|
||||
impl std::fmt::Debug for LocalAgentGraphStore {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LocalAgentGraphStore")
|
||||
.field("codex_home", &self.state_db.codex_home())
|
||||
.field("sqlite", self.state_db.sqlite())
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,7 @@ fn internal_error(err: impl std::fmt::Display) -> AgentGraphStoreError {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_state::DirectionalThreadSpawnEdgeStatus;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
@@ -141,10 +142,12 @@ mod tests {
|
||||
|
||||
async fn state_runtime() -> TestRuntime {
|
||||
let codex_home = TempDir::new().expect("tempdir should be created");
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
TestRuntime {
|
||||
state_db,
|
||||
_codex_home: codex_home,
|
||||
|
||||
@@ -432,6 +432,7 @@ mod tests {
|
||||
use crate::transport::remote_control::protocol::normalize_remote_control_url;
|
||||
use crate::transport::remote_control::server_api::enroll_remote_control_server;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
@@ -445,9 +446,12 @@ mod tests {
|
||||
use tokio::time::timeout;
|
||||
|
||||
async fn remote_control_state_runtime(codex_home: &TempDir) -> Arc<StateRuntime> {
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state runtime should initialize")
|
||||
StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state runtime should initialize")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -39,6 +39,7 @@ use codex_login::token_data::parse_chatgpt_jwt_claims;
|
||||
use codex_protocol::auth::AuthMode;
|
||||
use codex_state::RemoteControlEnrollmentRecord;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use gethostname::gethostname;
|
||||
@@ -126,9 +127,12 @@ fn remote_control_auth_dot_json(account_id: Option<&str>) -> AuthDotJson {
|
||||
}
|
||||
|
||||
async fn remote_control_state_runtime(codex_home: &TempDir) -> Arc<StateRuntime> {
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state runtime should initialize")
|
||||
StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state runtime should initialize")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1836,6 +1836,7 @@ mod tests {
|
||||
use codex_login::token_data::parse_chatgpt_jwt_claims;
|
||||
use codex_protocol::auth::AuthMode;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use futures::StreamExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::sync::Arc;
|
||||
@@ -1994,9 +1995,12 @@ mod tests {
|
||||
}
|
||||
|
||||
pub(super) async fn remote_control_state_runtime(codex_home: &TempDir) -> Arc<StateRuntime> {
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state runtime should initialize")
|
||||
StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state runtime should initialize")
|
||||
}
|
||||
|
||||
pub(super) fn remote_control_auth_manager() -> Arc<AuthManager> {
|
||||
|
||||
@@ -569,7 +569,7 @@ pub async fn run_main_with_transport_options(
|
||||
Err(err) => {
|
||||
return Err(std::io::Error::other(format!(
|
||||
"failed to initialize sqlite state runtime under {}: {err}",
|
||||
config.sqlite_home.display()
|
||||
config.sqlite_config().home().display()
|
||||
)));
|
||||
}
|
||||
};
|
||||
@@ -1196,16 +1196,8 @@ async fn init_sqlite_state_db_with_fresh_start_on_corruption(
|
||||
}
|
||||
Err(err) => err,
|
||||
};
|
||||
let database_path =
|
||||
codex_state::runtime_db_path_for_corruption_error(&err).unwrap_or_else(|| {
|
||||
codex_state::SqliteConfig::from_sqlite_home(
|
||||
codex_utils_absolute_path::AbsolutePathBuf::resolve_path_against_base(
|
||||
config.sqlite_home.clone(),
|
||||
&config.codex_home,
|
||||
),
|
||||
)
|
||||
.state_db_path()
|
||||
});
|
||||
let database_path = codex_state::runtime_db_path_for_corruption_error(&err)
|
||||
.unwrap_or_else(|| config.sqlite_config().state_db_path());
|
||||
if !codex_state::is_sqlite_corruption_error(&err)
|
||||
&& !sqlite_home_is_blocking_file(database_path.as_path())
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -538,9 +539,11 @@ async fn external_agent_config_import_sends_completion_notification_for_sync_onl
|
||||
)
|
||||
.await??;
|
||||
assert_eq!(completed.import_id, import_id);
|
||||
let state_db =
|
||||
codex_state::StateRuntime::init(sqlite_home.path().to_path_buf(), "mock_provider".into())
|
||||
.await?;
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(sqlite_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
let details_record = state_db
|
||||
.external_agent_config_import_details_record(&import_id)
|
||||
.await?
|
||||
|
||||
@@ -9,6 +9,7 @@ use codex_protocol::protocol::SessionSource;
|
||||
use codex_state::Stage1JobClaimOutcome;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_state::ThreadMetadataBuilder;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -120,7 +121,11 @@ async fn seed_stage1_output(state_db: &Arc<StateRuntime>, codex_home: &Path) ->
|
||||
}
|
||||
|
||||
async fn init_state_db(codex_home: &Path) -> Result<Arc<StateRuntime>> {
|
||||
let state_db = StateRuntime::init(codex_home.to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::OsString;
|
||||
use std::io::ErrorKind;
|
||||
@@ -254,8 +255,11 @@ async fn listen_off_honors_persisted_remote_control_enable() -> Result<()> {
|
||||
"ws://{}/backend-api/wham/remote/control/server",
|
||||
listener.local_addr()?
|
||||
);
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord {
|
||||
websocket_url,
|
||||
@@ -298,8 +302,11 @@ async fn listen_off_ignores_persisted_enable_when_disabled_by_requirements() ->
|
||||
"ws://{}/backend-api/wham/remote/control/server",
|
||||
listener.local_addr()?
|
||||
);
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord {
|
||||
websocket_url: websocket_url.clone(),
|
||||
@@ -348,9 +355,11 @@ async fn listen_off_exits_without_persisted_remote_control_enable() -> Result<()
|
||||
"ws://{}/backend-api/wham/remote/control/server",
|
||||
listener.local_addr()?
|
||||
);
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string())
|
||||
.await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord {
|
||||
websocket_url,
|
||||
@@ -460,8 +469,11 @@ async fn disable_waits_for_in_flight_durable_enable() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut backend = BlockingRemoteControlBackend::start(codex_home.path()).await?;
|
||||
let websocket_url = backend.websocket_url().to_string();
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.without_auto_env()
|
||||
@@ -494,8 +506,11 @@ async fn rpc_updates_durable_preference_but_ephemeral_does_not() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut backend = BlockingRemoteControlBackend::start(codex_home.path()).await?;
|
||||
let websocket_url = backend.websocket_url().to_string();
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
|
||||
@@ -25,6 +25,7 @@ use codex_core::find_thread_path_by_id_str;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_state::DirectionalThreadSpawnEdgeStatus;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
use tempfile::TempDir;
|
||||
@@ -186,8 +187,11 @@ async fn thread_archive_archives_spawned_descendants() -> Result<()> {
|
||||
let parent_thread_id = ThreadId::from_string(&parent_id)?;
|
||||
let child_thread_id = ThreadId::from_string(&child_id)?;
|
||||
let grandchild_thread_id = ThreadId::from_string(&grandchild_id)?;
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
@@ -292,8 +296,11 @@ async fn thread_archive_succeeds_when_descendant_archive_fails() -> Result<()> {
|
||||
let parent_thread_id = ThreadId::from_string(&parent_id)?;
|
||||
let child_thread_id = ThreadId::from_string(&child_id)?;
|
||||
let grandchild_thread_id = ThreadId::from_string(&grandchild_id)?;
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
@@ -408,8 +415,11 @@ async fn thread_archive_succeeds_when_spawned_descendant_is_missing() -> Result<
|
||||
let parent_thread_id = ThreadId::from_string(&parent_id)?;
|
||||
let missing_child_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000901")?;
|
||||
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
|
||||
@@ -15,7 +15,9 @@ use codex_core::find_thread_path_by_id_str;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::HistoryPosition;
|
||||
use codex_state::DirectionalThreadSpawnEdgeStatus;
|
||||
use codex_state::SqliteConfig;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
use tempfile::TempDir;
|
||||
@@ -32,8 +34,11 @@ async fn thread_delete_deletes_spawned_descendants() -> Result<()> {
|
||||
let grandchild_id =
|
||||
create_delete_test_rollout(codex_home.path(), /*minute*/ 2, "grandchild")?;
|
||||
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
let parent_thread_id = ThreadId::from_string(&parent_id)?;
|
||||
let child_thread_id = ThreadId::from_string(&child_id)?;
|
||||
let grandchild_thread_id = ThreadId::from_string(&grandchild_id)?;
|
||||
@@ -139,8 +144,11 @@ async fn thread_delete_preflights_external_fork_references_for_spawned_subtrees(
|
||||
})?;
|
||||
std::fs::write(external_path.as_path(), format!("{external_meta}\n"))?;
|
||||
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.upsert_thread_spawn_edge(
|
||||
parent_thread_id,
|
||||
|
||||
@@ -46,6 +46,7 @@ use codex_rollout::append_rollout_item_to_path;
|
||||
use codex_rollout::append_thread_name;
|
||||
use codex_rollout::read_session_meta_line;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use core_test_support::responses;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
@@ -522,8 +523,11 @@ async fn thread_fork_defers_inherited_active_goal_until_next_turn() -> Result<()
|
||||
}
|
||||
mcp.clear_message_buffer();
|
||||
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
let source_goal = state_db
|
||||
.thread_goals()
|
||||
.replace_thread_goal(
|
||||
|
||||
@@ -36,6 +36,7 @@ use codex_protocol::protocol::RolloutLine;
|
||||
use codex_protocol::protocol::SessionSource as CoreSessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_state::DirectionalThreadSpawnEdgeStatus;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use core_test_support::responses;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::cmp::Reverse;
|
||||
@@ -608,15 +609,17 @@ sqlite = true
|
||||
// `thread/list` applies `search_term` on the sqlite fast path. This test creates
|
||||
// rollouts manually, so mark the DB backfill complete and then run an unsearched
|
||||
// list large enough to repair every rollout the searched list should find.
|
||||
let state_db =
|
||||
codex_state::StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into())
|
||||
.await?;
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
let rollout_config = codex_rollout::RolloutConfig {
|
||||
codex_home: codex_home.path().to_path_buf(),
|
||||
sqlite_home: codex_home.path().to_path_buf(),
|
||||
sqlite: codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
cwd: codex_home.path().to_path_buf(),
|
||||
model_provider_id: "mock_provider".to_string(),
|
||||
generate_memories: false,
|
||||
@@ -831,9 +834,11 @@ sqlite = true
|
||||
Some("mock_provider"),
|
||||
/*git_info*/ None,
|
||||
)?;
|
||||
let state_db =
|
||||
codex_state::StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into())
|
||||
.await?;
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
@@ -938,7 +943,7 @@ async fn thread_list_relation_filters_read_spawn_graph_from_state_db() -> Result
|
||||
let newer_child_id = ThreadId::new();
|
||||
let grandchild_id = ThreadId::new();
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
codex_home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
@@ -1722,15 +1727,17 @@ async fn thread_list_sort_recency_at_uses_state_db_order_with_provider_filter()
|
||||
"2025-01-03T00:00:00Z",
|
||||
)?;
|
||||
|
||||
let state_db =
|
||||
codex_state::StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into())
|
||||
.await?;
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
let rollout_config = codex_rollout::RolloutConfig {
|
||||
codex_home: codex_home.path().to_path_buf(),
|
||||
sqlite_home: codex_home.path().to_path_buf(),
|
||||
sqlite: codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
cwd: codex_home.path().to_path_buf(),
|
||||
model_provider_id: "mock_provider".to_string(),
|
||||
generate_memories: false,
|
||||
|
||||
@@ -12,6 +12,7 @@ use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -99,7 +100,11 @@ async fn thread_memory_mode_set_updates_stored_thread_state() -> Result<()> {
|
||||
}
|
||||
|
||||
async fn init_state_db(codex_home: &Path) -> Result<Arc<StateRuntime>> {
|
||||
let state_db = StateRuntime::init(codex_home.to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
|
||||
@@ -29,6 +29,7 @@ use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::GitInfo as RolloutGitInfo;
|
||||
use codex_rollout::state_db::reconcile_rollout;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use std::fs;
|
||||
@@ -681,7 +682,11 @@ async fn thread_metadata_update_can_clear_stored_git_fields() -> Result<()> {
|
||||
}
|
||||
|
||||
async fn init_state_db(codex_home: &Path) -> Result<Arc<StateRuntime>> {
|
||||
let state_db = StateRuntime::init(codex_home.to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
|
||||
@@ -471,8 +471,7 @@ async fn thread_search_occurrences_reads_paginated_projection() -> Result<()> {
|
||||
let thread_id = codex_protocol::ThreadId::default();
|
||||
let sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
let state_db =
|
||||
codex_state::StateRuntime::init(sqlite.home().to_path_buf(), "mock_provider".to_string())
|
||||
.await?;
|
||||
codex_state::StateRuntime::init(sqlite.clone(), "mock_provider".to_string()).await?;
|
||||
let store = LocalThreadStore::new(
|
||||
LocalThreadStoreConfig {
|
||||
codex_home: codex_home.path().to_path_buf(),
|
||||
@@ -1450,8 +1449,7 @@ async fn paginated_history_lists_use_projected_turns_and_items() -> Result<()> {
|
||||
let thread_id = codex_protocol::ThreadId::default();
|
||||
let sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
let state_db =
|
||||
codex_state::StateRuntime::init(sqlite.home().to_path_buf(), "mock_provider".to_string())
|
||||
.await?;
|
||||
codex_state::StateRuntime::init(sqlite.clone(), "mock_provider".to_string()).await?;
|
||||
let store = LocalThreadStore::new(
|
||||
LocalThreadStoreConfig {
|
||||
codex_home: codex_home.path().to_path_buf(),
|
||||
|
||||
@@ -98,6 +98,7 @@ use codex_rollout::append_rollout_item_to_path;
|
||||
use codex_rollout::read_session_meta_line;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use codex_utils_path_uri::LegacyAppPathString;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
@@ -1890,8 +1891,11 @@ async fn thread_goal_set_edits_objective_without_resetting_usage() -> Result<()>
|
||||
)
|
||||
.await??;
|
||||
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
let thread_id = ThreadId::from_string(&thread_id)?;
|
||||
let thread_metadata = state_db
|
||||
.get_thread(thread_id)
|
||||
@@ -2589,8 +2593,11 @@ async fn thread_resume_prefers_persisted_git_metadata_for_local_threads() -> Res
|
||||
.join("\n")
|
||||
+ "\n",
|
||||
)?;
|
||||
let state_db =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
"mock_provider".into(),
|
||||
)
|
||||
.await?;
|
||||
state_db
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await?;
|
||||
|
||||
+16
-11
@@ -1089,7 +1089,10 @@ fn config_check(config: &Config) -> DoctorCheck {
|
||||
));
|
||||
details.push(format!("model provider: {}", config.model_provider_id));
|
||||
details.push(format!("log dir: {}", config.log_dir.display()));
|
||||
details.push(format!("sqlite home: {}", config.sqlite_home.display()));
|
||||
details.push(format!(
|
||||
"sqlite home: {}",
|
||||
config.sqlite_config().home().display()
|
||||
));
|
||||
details.push(format!("mcp servers: {}", config.mcp_servers.get().len()));
|
||||
feature_flag_details(config, &mut details);
|
||||
config_toml_details(config, &mut details);
|
||||
@@ -2163,17 +2166,18 @@ async fn state_check(config: &Config) -> DoctorCheck {
|
||||
let mut details = Vec::new();
|
||||
path_readiness(&mut details, "CODEX_HOME", &config.codex_home);
|
||||
path_readiness(&mut details, "log dir", &config.log_dir);
|
||||
path_readiness(&mut details, "sqlite home", &config.sqlite_home);
|
||||
path_readiness(&mut details, "sqlite home", config.sqlite_config().home());
|
||||
let mut integrity_failures = Vec::new();
|
||||
let sqlite = codex_state::SqliteConfig::from_sqlite_home(
|
||||
codex_utils_absolute_path::AbsolutePathBuf::resolve_path_against_base(
|
||||
&config.sqlite_home,
|
||||
&config.codex_home,
|
||||
),
|
||||
);
|
||||
for db in sqlite.runtime_db_paths() {
|
||||
for db in config.sqlite_config().runtime_db_paths() {
|
||||
path_readiness(&mut details, db.label, &db.path);
|
||||
sqlite_integrity_detail(&mut details, &mut integrity_failures, db.label, &db.path).await;
|
||||
sqlite_integrity_detail(
|
||||
config.sqlite_config(),
|
||||
&mut details,
|
||||
&mut integrity_failures,
|
||||
db.label,
|
||||
&db.path,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
rollout_stats_details(&mut details, &config.codex_home);
|
||||
standalone_release_cache_details(&mut details);
|
||||
@@ -2198,6 +2202,7 @@ async fn state_check(config: &Config) -> DoctorCheck {
|
||||
}
|
||||
|
||||
async fn sqlite_integrity_detail(
|
||||
sqlite: &codex_state::SqliteConfig,
|
||||
details: &mut Vec<String>,
|
||||
integrity_failures: &mut Vec<String>,
|
||||
label: &str,
|
||||
@@ -2208,7 +2213,7 @@ async fn sqlite_integrity_detail(
|
||||
return;
|
||||
}
|
||||
|
||||
match codex_state::sqlite_integrity_check(path).await {
|
||||
match codex_state::sqlite_integrity_check(sqlite, path).await {
|
||||
Ok(rows) if rows.iter().all(|row| row == "ok") => {
|
||||
details.push(format!("{label} integrity: ok"));
|
||||
}
|
||||
|
||||
@@ -91,15 +91,9 @@ impl RolloutScan {
|
||||
}
|
||||
|
||||
pub(super) async fn thread_inventory_check(config: &Config) -> DoctorCheck {
|
||||
let sqlite = codex_state::SqliteConfig::from_sqlite_home(
|
||||
codex_utils_absolute_path::AbsolutePathBuf::resolve_path_against_base(
|
||||
&config.sqlite_home,
|
||||
&config.codex_home,
|
||||
),
|
||||
);
|
||||
thread_inventory_check_for_roots(
|
||||
config.codex_home.as_path(),
|
||||
&sqlite,
|
||||
config.sqlite_config(),
|
||||
config.model_provider_id.as_str(),
|
||||
)
|
||||
.await
|
||||
@@ -142,7 +136,7 @@ async fn thread_inventory_check_for_roots(
|
||||
return missing_state_db_check(scan, details);
|
||||
}
|
||||
|
||||
let rows = match codex_state::read_thread_state_audit_rows(&state_db_path).await {
|
||||
let rows = match codex_state::read_thread_state_audit_rows(sqlite).await {
|
||||
Ok(rows) => rows,
|
||||
Err(err) => {
|
||||
details.push(format!("rollout DB read error: {err}"));
|
||||
@@ -1310,7 +1304,7 @@ mod tests {
|
||||
let codex_home = TempDir::new().expect("codex home");
|
||||
let sqlite_home = TempDir::new().expect("sqlite home");
|
||||
let _runtime = codex_state::StateRuntime::init(
|
||||
sqlite_home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(sqlite_home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
@@ -1359,9 +1353,8 @@ mod tests {
|
||||
|
||||
async fn insert_thread_row(&self, id: &str, rollout_path: &Path, archived: bool) {
|
||||
let sqlite = self.sqlite();
|
||||
let state_db_path = sqlite.state_db_path();
|
||||
let pool = sqlite
|
||||
.open_read_write_pool(&state_db_path)
|
||||
.open_read_write_pool(&sqlite.state_db_path())
|
||||
.await
|
||||
.expect("sqlite pool");
|
||||
sqlx::query(
|
||||
|
||||
@@ -26,7 +26,6 @@ use codex_execpolicy::ExecPolicyCheckCommand;
|
||||
use codex_responses_api_proxy::Args as ResponsesApiProxyArgs;
|
||||
use codex_rollout_trace::REDUCED_STATE_FILE_NAME;
|
||||
use codex_rollout_trace::replay_bundle;
|
||||
use codex_state::SqliteConfig;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_tui::AppExitInfo;
|
||||
use codex_tui::Cli as TuiCli;
|
||||
@@ -2062,11 +2061,9 @@ async fn run_debug_clear_memories_command(
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let sqlite_home =
|
||||
AbsolutePathBuf::resolve_path_against_base(&config.sqlite_home, &config.codex_home);
|
||||
let memories_path = SqliteConfig::from_sqlite_home(sqlite_home).memories_db_path();
|
||||
let memories_path = config.sqlite_config().memories_db_path();
|
||||
let cleared_memories_db =
|
||||
StateRuntime::clear_memory_data_in_sqlite_home(config.sqlite_home.as_path()).await?;
|
||||
StateRuntime::clear_memory_data_in_sqlite_home(config.sqlite_config()).await?;
|
||||
|
||||
clear_memory_roots_contents(&config.codex_home).await?;
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@ fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
|
||||
async fn debug_clear_memories_resets_state_and_removes_memory_dir() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
let runtime =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;
|
||||
let runtime = StateRuntime::init(sqlite.clone(), "test-provider".to_string()).await?;
|
||||
drop(runtime);
|
||||
|
||||
let thread_id = "00000000-0000-0000-0000-000000000123";
|
||||
@@ -139,8 +138,7 @@ INSERT INTO jobs (
|
||||
async fn debug_clear_memories_resets_memories_db_without_state_db() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
let runtime =
|
||||
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;
|
||||
let runtime = StateRuntime::init(sqlite.clone(), "test-provider".to_string()).await?;
|
||||
runtime.close().await;
|
||||
|
||||
let db_path = sqlite.state_db_path();
|
||||
|
||||
@@ -28,4 +28,5 @@ codex-login = { workspace = true }
|
||||
codex-model-provider-info = { workspace = true }
|
||||
codex-models-manager = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-state = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
|
||||
@@ -108,4 +108,5 @@ pub use codex_protocol::protocol::SessionSource;
|
||||
pub use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
pub use codex_protocol::protocol::W3cTraceContext;
|
||||
pub use codex_protocol::user_input::UserInput;
|
||||
pub use codex_state::SqliteConfig;
|
||||
pub use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
@@ -5414,7 +5414,7 @@ async fn sqlite_home_defaults_to_codex_home_for_workspace_write() -> std::io::Re
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(config.sqlite_home, codex_home.path().to_path_buf());
|
||||
assert_eq!(config.sqlite.home(), codex_home.path());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -11933,7 +11933,7 @@ sandbox_private_desktop = false
|
||||
);
|
||||
let config = load_with_enterprise_requirement(&codex_home, requirements).await?;
|
||||
|
||||
assert_eq!(config.sqlite_home, required_sqlite_home);
|
||||
assert_eq!(config.sqlite.home(), required_sqlite_home.as_path());
|
||||
assert_eq!(config.log_dir, required_log_dir);
|
||||
assert_eq!(config.model_catalog, Some(catalog));
|
||||
assert!(!config.check_for_update_on_startup);
|
||||
|
||||
@@ -272,18 +272,16 @@ const LOCAL_DEV_BUILD_VERSION: &str = "0.0.0";
|
||||
pub const CONFIG_TOML_FILE: &str = "config.toml";
|
||||
const CONFIG_PROFILE_V2_SUFFIX: &str = ".config.toml";
|
||||
|
||||
fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option<PathBuf> {
|
||||
fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option<AbsolutePathBuf> {
|
||||
let raw = std::env::var(codex_state::SQLITE_HOME_ENV).ok()?;
|
||||
let trimmed = raw.trim();
|
||||
if trimmed.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let path = PathBuf::from(trimmed);
|
||||
if path.is_absolute() {
|
||||
Some(path)
|
||||
} else {
|
||||
Some(resolved_cwd.join(path))
|
||||
}
|
||||
Some(AbsolutePathBuf::resolve_path_against_base(
|
||||
trimmed,
|
||||
resolved_cwd,
|
||||
))
|
||||
}
|
||||
|
||||
fn resolve_cli_auth_credentials_store_mode(
|
||||
@@ -893,8 +891,8 @@ pub struct Config {
|
||||
/// overridden by the `CODEX_HOME` environment variable).
|
||||
pub codex_home: AbsolutePathBuf,
|
||||
|
||||
/// Directory where Codex stores the SQLite state DB.
|
||||
pub sqlite_home: PathBuf,
|
||||
/// Resolved configuration shared by all Codex SQLite databases.
|
||||
pub sqlite: codex_state::SqliteConfig,
|
||||
|
||||
/// Directory where Codex writes log files (defaults to `$CODEX_HOME/log`).
|
||||
pub log_dir: PathBuf,
|
||||
@@ -1437,6 +1435,10 @@ impl ConfigBuilder {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn sqlite_config(&self) -> &codex_state::SqliteConfig {
|
||||
&self.sqlite
|
||||
}
|
||||
|
||||
pub(crate) fn multi_agent_version_override(&self) -> Option<MultiAgentVersion> {
|
||||
if self.features.enabled(Feature::MultiAgentV2) {
|
||||
Some(MultiAgentVersion::V2)
|
||||
@@ -3822,9 +3824,9 @@ impl Config {
|
||||
let sqlite_home = cfg
|
||||
.sqlite_home
|
||||
.as_ref()
|
||||
.map(AbsolutePathBuf::to_path_buf)
|
||||
.cloned()
|
||||
.or(sqlite_home_env)
|
||||
.unwrap_or_else(|| codex_home.to_path_buf());
|
||||
.unwrap_or_else(|| codex_home.clone());
|
||||
let original_permission_profile = permission_profile.clone();
|
||||
apply_requirement_constrained_value(
|
||||
"approval_policy",
|
||||
@@ -4019,7 +4021,7 @@ impl Config {
|
||||
memories: memories_config,
|
||||
agent_interrupt_message_enabled,
|
||||
codex_home,
|
||||
sqlite_home,
|
||||
sqlite: codex_state::SqliteConfig::from_sqlite_home(sqlite_home),
|
||||
log_dir,
|
||||
config_lock_export_dir: cfg
|
||||
.debug
|
||||
|
||||
@@ -28,8 +28,8 @@ impl codex_rollout::RolloutConfigView for Config {
|
||||
self.codex_home.as_path()
|
||||
}
|
||||
|
||||
fn sqlite_home(&self) -> &std::path::Path {
|
||||
self.sqlite_home.as_path()
|
||||
fn sqlite_config(&self) -> &codex_state::SqliteConfig {
|
||||
self.sqlite_config()
|
||||
}
|
||||
|
||||
fn cwd(&self) -> &std::path::Path {
|
||||
|
||||
@@ -5776,7 +5776,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
|
||||
codex_thread_store::LocalThreadStoreConfig::from_config(config.as_ref()),
|
||||
Some(
|
||||
codex_state::StateRuntime::init(
|
||||
config.sqlite_home.clone(),
|
||||
config.sqlite.clone(),
|
||||
config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
@@ -66,9 +67,12 @@ async fn upsert_thread_metadata(
|
||||
thread_id: ThreadId,
|
||||
rollout_path: PathBuf,
|
||||
) -> StateDbHandle {
|
||||
let runtime = StateRuntime::init(codex_home.to_path_buf(), "test-provider".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let runtime = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
runtime
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await
|
||||
|
||||
@@ -24,7 +24,6 @@ use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::UserMessageEvent;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_web_search_extension::install as install_web_search_extension;
|
||||
use core_test_support::PathExt;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_function_call;
|
||||
@@ -67,9 +66,7 @@ async fn new_thread_is_recorded_in_state_db() -> Result<()> {
|
||||
|
||||
let thread_id = test.session_configured.thread_id;
|
||||
let rollout_path = test.codex.rollout_path().expect("rollout path");
|
||||
let db_path =
|
||||
codex_state::SqliteConfig::new_for_testing(test.config.sqlite_home.as_path().abs())
|
||||
.state_db_path();
|
||||
let db_path = test.config.sqlite.state_db_path();
|
||||
|
||||
for _ in 0..100 {
|
||||
if tokio::fs::try_exists(&db_path).await.unwrap_or(false) {
|
||||
@@ -426,9 +423,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> {
|
||||
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
let db_path =
|
||||
codex_state::SqliteConfig::new_for_testing(test.config.sqlite_home.as_path().abs())
|
||||
.state_db_path();
|
||||
let db_path = test.config.sqlite.state_db_path();
|
||||
let rollout_path = test.config.codex_home.join(&rollout_rel_path);
|
||||
let default_provider = test.config.model_provider_id.clone();
|
||||
|
||||
@@ -479,9 +474,7 @@ async fn user_messages_persist_in_state_db() -> Result<()> {
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
let db_path =
|
||||
codex_state::SqliteConfig::new_for_testing(test.config.sqlite_home.as_path().abs())
|
||||
.state_db_path();
|
||||
let db_path = test.config.sqlite.state_db_path();
|
||||
for _ in 0..100 {
|
||||
if tokio::fs::try_exists(&db_path).await.unwrap_or(false) {
|
||||
break;
|
||||
|
||||
@@ -30,6 +30,7 @@ tracing = { workspace = true }
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![recursion_limit = "256"]
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::PoisonError;
|
||||
@@ -1358,7 +1359,11 @@ fn tool_call(tool_name: &str, call_id: &str, arguments: serde_json::Value) -> To
|
||||
|
||||
async fn test_runtime() -> anyhow::Result<Arc<codex_state::StateRuntime>> {
|
||||
let tempdir = TempDir::new()?;
|
||||
codex_state::StateRuntime::init(tempdir.keep(), "test-provider".to_string()).await
|
||||
codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(tempdir.keep().as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn test_thread_id() -> anyhow::Result<ThreadId> {
|
||||
@@ -1372,7 +1377,8 @@ async fn seed_thread_metadata(
|
||||
let builder = codex_state::ThreadMetadataBuilder::new(
|
||||
thread_id,
|
||||
runtime
|
||||
.codex_home()
|
||||
.sqlite()
|
||||
.home()
|
||||
.join(format!("rollout-{thread_id}.jsonl")),
|
||||
chrono::Utc::now(),
|
||||
SessionSource::Cli,
|
||||
|
||||
@@ -30,6 +30,7 @@ use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::protocol::RolloutLine;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_state::Phase2JobClaimOutcome;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use core_test_support::responses::ResponseMock;
|
||||
use core_test_support::responses::ResponsesRequest;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
@@ -596,8 +597,11 @@ async fn build_test_codex_with_memories_config(
|
||||
}
|
||||
|
||||
async fn init_state_db(home: &Arc<TempDir>) -> anyhow::Result<Arc<codex_state::StateRuntime>> {
|
||||
let db =
|
||||
codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".into()).await?;
|
||||
let db = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
"test-provider".into(),
|
||||
)
|
||||
.await?;
|
||||
db.mark_backfill_complete(/*last_watermark*/ None).await?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
@@ -47,4 +47,5 @@ uuid = { workspace = true }
|
||||
zstd = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use std::fs;
|
||||
use std::fs::FileTimes;
|
||||
#[cfg(unix)]
|
||||
@@ -392,7 +393,7 @@ async fn resume_materializes_compressed_rollout_path() -> anyhow::Result<()> {
|
||||
let home = TempDir::new()?;
|
||||
let config = RolloutConfig {
|
||||
codex_home: home.path().to_path_buf(),
|
||||
sqlite_home: home.path().to_path_buf(),
|
||||
sqlite: codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
cwd: home.path().to_path_buf(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
generate_memories: true,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use codex_state::SqliteConfig;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait RolloutConfigView {
|
||||
fn codex_home(&self) -> &Path;
|
||||
fn sqlite_home(&self) -> &Path;
|
||||
fn sqlite_config(&self) -> &SqliteConfig;
|
||||
fn cwd(&self) -> &Path;
|
||||
fn model_provider_id(&self) -> &str;
|
||||
fn generate_memories(&self) -> bool;
|
||||
@@ -13,7 +14,7 @@ pub trait RolloutConfigView {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RolloutConfig {
|
||||
pub codex_home: PathBuf,
|
||||
pub sqlite_home: PathBuf,
|
||||
pub sqlite: SqliteConfig,
|
||||
pub cwd: PathBuf,
|
||||
pub model_provider_id: String,
|
||||
pub generate_memories: bool,
|
||||
@@ -25,7 +26,7 @@ impl RolloutConfig {
|
||||
pub fn from_view(view: &impl RolloutConfigView) -> Self {
|
||||
Self {
|
||||
codex_home: view.codex_home().to_path_buf(),
|
||||
sqlite_home: view.sqlite_home().to_path_buf(),
|
||||
sqlite: view.sqlite_config().clone(),
|
||||
cwd: view.cwd().to_path_buf(),
|
||||
model_provider_id: view.model_provider_id().to_string(),
|
||||
generate_memories: view.generate_memories(),
|
||||
@@ -38,8 +39,8 @@ impl RolloutConfigView for RolloutConfig {
|
||||
self.codex_home.as_path()
|
||||
}
|
||||
|
||||
fn sqlite_home(&self) -> &Path {
|
||||
self.sqlite_home.as_path()
|
||||
fn sqlite_config(&self) -> &SqliteConfig {
|
||||
&self.sqlite
|
||||
}
|
||||
|
||||
fn cwd(&self) -> &Path {
|
||||
@@ -60,8 +61,8 @@ impl<T: RolloutConfigView + ?Sized> RolloutConfigView for &T {
|
||||
(*self).codex_home()
|
||||
}
|
||||
|
||||
fn sqlite_home(&self) -> &Path {
|
||||
(*self).sqlite_home()
|
||||
fn sqlite_config(&self) -> &SqliteConfig {
|
||||
(*self).sqlite_config()
|
||||
}
|
||||
|
||||
fn cwd(&self) -> &Path {
|
||||
@@ -82,8 +83,8 @@ impl<T: RolloutConfigView + ?Sized> RolloutConfigView for Arc<T> {
|
||||
self.as_ref().codex_home()
|
||||
}
|
||||
|
||||
fn sqlite_home(&self) -> &Path {
|
||||
self.as_ref().sqlite_home()
|
||||
fn sqlite_config(&self) -> &SqliteConfig {
|
||||
self.as_ref().sqlite_config()
|
||||
}
|
||||
|
||||
fn cwd(&self) -> &Path {
|
||||
|
||||
@@ -16,6 +16,7 @@ use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_state::BackfillStatus;
|
||||
use codex_state::ThreadMetadataBuilder;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
@@ -249,9 +250,12 @@ async fn backfill_sessions_resumes_from_watermark_and_marks_complete() {
|
||||
/*git*/ None,
|
||||
);
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let first_watermark = backfill_watermark_for_path(codex_home.as_path(), first_path.as_path());
|
||||
runtime.mark_backfill_running().await.expect("mark running");
|
||||
runtime
|
||||
@@ -314,9 +318,12 @@ async fn backfill_sessions_preserves_existing_git_branch_and_fills_missing_git_f
|
||||
}),
|
||||
);
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let thread_id = ThreadId::from_string(&thread_uuid.to_string()).expect("thread id");
|
||||
let mut existing = extract_metadata_from_rollout(&rollout_path, "test-provider")
|
||||
.await
|
||||
@@ -360,9 +367,12 @@ async fn backfill_sessions_preserves_existing_paginated_memory_mode() {
|
||||
ThreadHistoryMode::Paginated,
|
||||
);
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let thread_id = ThreadId::from_string(&thread_uuid.to_string()).expect("thread id");
|
||||
let existing = extract_metadata_from_rollout(&rollout_path, "test-provider")
|
||||
.await
|
||||
@@ -407,9 +417,12 @@ async fn backfill_sessions_normalizes_cwd_before_upsert() {
|
||||
ThreadHistoryMode::Legacy,
|
||||
);
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
backfill_sessions(runtime.as_ref(), codex_home.as_path(), "test-provider").await;
|
||||
|
||||
|
||||
@@ -437,6 +437,7 @@ impl RolloutRecorder {
|
||||
search_term: Option<&str>,
|
||||
) -> std::io::Result<ThreadsPage> {
|
||||
let codex_home = config.codex_home();
|
||||
let sqlite = config.sqlite_config();
|
||||
let archived = match archive_filter {
|
||||
ThreadListArchiveFilter::Active => false,
|
||||
ThreadListArchiveFilter::Archived => true,
|
||||
@@ -448,7 +449,7 @@ impl RolloutRecorder {
|
||||
if matches!(repair_mode, ThreadListRepairMode::StateDbOnly) {
|
||||
return Ok(state_db::list_threads_db(
|
||||
state_db_ctx.as_deref(),
|
||||
codex_home,
|
||||
sqlite,
|
||||
page_size,
|
||||
cursor,
|
||||
sort_key,
|
||||
@@ -558,7 +559,7 @@ impl RolloutRecorder {
|
||||
|
||||
let db_page = state_db::list_threads_db(
|
||||
state_db_ctx.as_deref(),
|
||||
codex_home,
|
||||
sqlite,
|
||||
page_size,
|
||||
cursor,
|
||||
sort_key,
|
||||
@@ -588,7 +589,7 @@ impl RolloutRecorder {
|
||||
}
|
||||
if let Some(repaired_db_page) = state_db::list_threads_db(
|
||||
state_db_ctx.as_deref(),
|
||||
codex_home,
|
||||
sqlite,
|
||||
page_size,
|
||||
cursor,
|
||||
sort_key,
|
||||
@@ -629,7 +630,7 @@ impl RolloutRecorder {
|
||||
if sort_key == ThreadSortKey::RecencyAt {
|
||||
if let Some(repaired_db_page) = state_db::list_threads_db(
|
||||
state_db_ctx.as_deref(),
|
||||
codex_home,
|
||||
sqlite,
|
||||
page_size,
|
||||
cursor,
|
||||
sort_key,
|
||||
@@ -701,6 +702,7 @@ impl RolloutRecorder {
|
||||
filter_cwd: Option<&Path>,
|
||||
) -> std::io::Result<Option<PathBuf>> {
|
||||
let codex_home = config.codex_home();
|
||||
let sqlite = config.sqlite_config();
|
||||
let cwd_filter = filter_cwd.map(Path::to_path_buf);
|
||||
let mut fallback_reason = state_db_ctx.is_none().then_some("db_unavailable");
|
||||
if state_db_ctx.is_some() {
|
||||
@@ -708,7 +710,7 @@ impl RolloutRecorder {
|
||||
loop {
|
||||
let Some(db_page) = state_db::list_threads_db(
|
||||
state_db_ctx.as_deref(),
|
||||
codex_home,
|
||||
sqlite,
|
||||
page_size,
|
||||
db_cursor.as_ref(),
|
||||
sort_key,
|
||||
|
||||
@@ -18,6 +18,7 @@ use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_protocol::protocol::TurnContextItem;
|
||||
use codex_protocol::protocol::UserMessageEvent;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
@@ -31,7 +32,7 @@ use uuid::Uuid;
|
||||
fn test_config(codex_home: &Path) -> RolloutConfig {
|
||||
RolloutConfig {
|
||||
codex_home: codex_home.to_path_buf(),
|
||||
sqlite_home: codex_home.to_path_buf(),
|
||||
sqlite: codex_state::SqliteConfig::new_for_testing(codex_home.abs()),
|
||||
cwd: codex_home.to_path_buf(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
generate_memories: true,
|
||||
@@ -943,7 +944,7 @@ async fn list_threads_db_enabled_drops_missing_rollout_paths() -> std::io::Resul
|
||||
));
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1009,7 +1010,7 @@ async fn list_threads_db_enabled_repairs_stale_rollout_paths() -> std::io::Resul
|
||||
));
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1070,7 +1071,7 @@ async fn list_threads_state_db_only_skips_jsonl_repair_scan() -> std::io::Result
|
||||
let config = test_config(home.path());
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1174,7 +1175,7 @@ async fn list_threads_default_filter_returns_filesystem_scan_results() -> std::i
|
||||
let stale_cwd = home.path().join("stale-cwd");
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1264,7 +1265,7 @@ async fn list_threads_metadata_filter_overlays_state_db_list_metadata() -> std::
|
||||
let rollout_path = write_session_file(home.path(), "2025-01-03T16-00-00", uuid)?;
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1407,7 +1408,7 @@ async fn list_threads_search_repairs_stale_state_db_hits_before_returning() -> s
|
||||
let real_path = write_session_file(home.path(), "2025-01-03T15-00-00", uuid)?;
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -13,8 +13,8 @@ use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
pub use codex_state::LogEntry;
|
||||
use codex_state::SqliteConfig;
|
||||
use codex_state::ThreadMetadataBuilder;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path::normalize_for_path_comparison;
|
||||
use serde_json::Value;
|
||||
use std::path::Path;
|
||||
@@ -44,13 +44,7 @@ const STARTUP_BACKFILL_WAIT_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
/// initialized handle.
|
||||
pub async fn init(config: &impl RolloutConfigView) -> Option<StateDbHandle> {
|
||||
let config = RolloutConfig::from_view(config);
|
||||
match try_init_with_roots(
|
||||
config.codex_home,
|
||||
config.sqlite_home,
|
||||
config.model_provider_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
match try_init_with_roots(config.codex_home, config.sqlite, config.model_provider_id).await {
|
||||
Ok(runtime) => Some(runtime),
|
||||
Err(err) => {
|
||||
emit_startup_warning(&format!("failed to initialize state runtime: {err:#}"));
|
||||
@@ -65,22 +59,17 @@ pub async fn init(config: &impl RolloutConfigView) -> Option<StateDbHandle> {
|
||||
/// tracing or UI setup has completed.
|
||||
pub async fn try_init(config: &impl RolloutConfigView) -> anyhow::Result<StateDbHandle> {
|
||||
let config = RolloutConfig::from_view(config);
|
||||
try_init_with_roots(
|
||||
config.codex_home,
|
||||
config.sqlite_home,
|
||||
config.model_provider_id,
|
||||
)
|
||||
.await
|
||||
try_init_with_roots(config.codex_home, config.sqlite, config.model_provider_id).await
|
||||
}
|
||||
|
||||
async fn try_init_with_roots(
|
||||
codex_home: PathBuf,
|
||||
sqlite_home: PathBuf,
|
||||
sqlite: SqliteConfig,
|
||||
default_model_provider_id: String,
|
||||
) -> anyhow::Result<StateDbHandle> {
|
||||
try_init_with_roots_inner(
|
||||
codex_home,
|
||||
sqlite_home,
|
||||
sqlite,
|
||||
default_model_provider_id,
|
||||
/*backfill_lease_seconds*/ None,
|
||||
)
|
||||
@@ -90,13 +79,13 @@ async fn try_init_with_roots(
|
||||
#[cfg(test)]
|
||||
async fn try_init_with_roots_and_backfill_lease(
|
||||
codex_home: PathBuf,
|
||||
sqlite_home: PathBuf,
|
||||
sqlite: SqliteConfig,
|
||||
default_model_provider_id: String,
|
||||
backfill_lease_seconds: i64,
|
||||
) -> anyhow::Result<StateDbHandle> {
|
||||
try_init_with_roots_inner(
|
||||
codex_home,
|
||||
sqlite_home,
|
||||
sqlite,
|
||||
default_model_provider_id,
|
||||
Some(backfill_lease_seconds),
|
||||
)
|
||||
@@ -105,17 +94,17 @@ async fn try_init_with_roots_and_backfill_lease(
|
||||
|
||||
async fn try_init_with_roots_inner(
|
||||
codex_home: PathBuf,
|
||||
sqlite_home: PathBuf,
|
||||
sqlite: SqliteConfig,
|
||||
default_model_provider_id: String,
|
||||
backfill_lease_seconds: Option<i64>,
|
||||
) -> anyhow::Result<StateDbHandle> {
|
||||
let runtime =
|
||||
codex_state::StateRuntime::init(sqlite_home.clone(), default_model_provider_id.clone())
|
||||
codex_state::StateRuntime::init(sqlite.clone(), default_model_provider_id.clone())
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to initialize state runtime at {}",
|
||||
sqlite_home.display()
|
||||
sqlite.home().display()
|
||||
)
|
||||
})?;
|
||||
let backfill_gate_started = Instant::now();
|
||||
@@ -217,8 +206,7 @@ fn emit_startup_warning(message: &str) {
|
||||
/// Unlike [`init`], this helper does not run rollout backfill. It is for
|
||||
/// optional local reads from non-owning contexts such as remote app-server mode.
|
||||
pub async fn get_state_db(config: &impl RolloutConfigView) -> Option<StateDbHandle> {
|
||||
let sqlite_home = AbsolutePathBuf::try_from(config.sqlite_home()).ok()?;
|
||||
let state_path = codex_state::SqliteConfig::from_sqlite_home(sqlite_home).state_db_path();
|
||||
let state_path = config.sqlite_config().state_db_path();
|
||||
if !tokio::fs::try_exists(&state_path).await.unwrap_or(false) {
|
||||
codex_state::record_fallback(
|
||||
"get_state_db",
|
||||
@@ -228,7 +216,7 @@ pub async fn get_state_db(config: &impl RolloutConfigView) -> Option<StateDbHand
|
||||
return None;
|
||||
}
|
||||
let runtime = match codex_state::StateRuntime::init(
|
||||
config.sqlite_home().to_path_buf(),
|
||||
config.sqlite_config().clone(),
|
||||
config.model_provider_id().to_string(),
|
||||
)
|
||||
.await
|
||||
@@ -243,7 +231,7 @@ pub async fn get_state_db(config: &impl RolloutConfigView) -> Option<StateDbHand
|
||||
return None;
|
||||
}
|
||||
};
|
||||
require_backfill_complete(runtime, config.sqlite_home()).await
|
||||
require_backfill_complete(runtime, config.sqlite_config().home()).await
|
||||
}
|
||||
|
||||
/// Build a SQLite telemetry recorder backed by an OTEL metrics client.
|
||||
@@ -307,7 +295,7 @@ pub fn normalize_cwd_for_state_db(cwd: &Path) -> PathBuf {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn list_thread_ids_db(
|
||||
context: Option<&codex_state::StateRuntime>,
|
||||
codex_home: &Path,
|
||||
sqlite: &codex_state::SqliteConfig,
|
||||
page_size: usize,
|
||||
cursor: Option<&Cursor>,
|
||||
sort_key: ThreadSortKey,
|
||||
@@ -317,12 +305,13 @@ pub async fn list_thread_ids_db(
|
||||
stage: &str,
|
||||
) -> Option<Vec<ThreadId>> {
|
||||
let ctx = context?;
|
||||
if ctx.codex_home() != codex_home {
|
||||
if ctx.sqlite() != sqlite {
|
||||
warn!(
|
||||
"state db codex_home mismatch: expected {}, got {}",
|
||||
ctx.codex_home().display(),
|
||||
codex_home.display()
|
||||
"state db SQLite home mismatch: expected {}, got {}",
|
||||
sqlite.home().display(),
|
||||
ctx.sqlite().home().display()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let anchor = cursor_to_anchor(cursor);
|
||||
@@ -362,7 +351,7 @@ pub async fn list_thread_ids_db(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn list_threads_db(
|
||||
context: Option<&codex_state::StateRuntime>,
|
||||
codex_home: &Path,
|
||||
sqlite: &codex_state::SqliteConfig,
|
||||
page_size: usize,
|
||||
cursor: Option<&Cursor>,
|
||||
sort_key: ThreadSortKey,
|
||||
@@ -376,12 +365,13 @@ pub async fn list_threads_db(
|
||||
search_term: Option<&str>,
|
||||
) -> Option<codex_state::ThreadsPage> {
|
||||
let ctx = context?;
|
||||
if ctx.codex_home() != codex_home {
|
||||
if ctx.sqlite() != sqlite {
|
||||
warn!(
|
||||
"state db codex_home mismatch: expected {}, got {}",
|
||||
ctx.codex_home().display(),
|
||||
codex_home.display()
|
||||
"state db SQLite home mismatch: expected {}, got {}",
|
||||
sqlite.home().display(),
|
||||
ctx.sqlite().home().display()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let anchor = cursor_to_anchor(cursor);
|
||||
|
||||
@@ -12,6 +12,7 @@ use codex_protocol::protocol::SessionMeta;
|
||||
use codex_protocol::protocol::SessionMetaLine;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_protocol::protocol::UserMessageEvent;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
use tempfile::TempDir;
|
||||
@@ -47,12 +48,59 @@ fn cursor_to_anchor_preserves_recency_tie_breaker() {
|
||||
);
|
||||
}
|
||||
|
||||
/// A runtime for another SQLite home must not be queried or clean up rows when
|
||||
/// a caller supplies a mismatched configuration.
|
||||
#[tokio::test]
|
||||
async fn list_threads_db_rejects_mismatched_sqlite_config_without_cleanup() -> anyhow::Result<()> {
|
||||
let root = TempDir::new().expect("temp dir");
|
||||
let runtime_sqlite = codex_state::SqliteConfig::new_for_testing(
|
||||
root.path().join("runtime-sqlite").as_path().abs(),
|
||||
);
|
||||
let requested_sqlite = codex_state::SqliteConfig::new_for_testing(
|
||||
root.path().join("requested-sqlite").as_path().abs(),
|
||||
);
|
||||
let runtime =
|
||||
codex_state::StateRuntime::init(runtime_sqlite, "test-provider".to_string()).await?;
|
||||
let thread_id = ThreadId::new();
|
||||
let metadata = ThreadMetadataBuilder::new(
|
||||
thread_id,
|
||||
root.path().join("missing-rollout.jsonl"),
|
||||
Utc::now(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
.build("test-provider");
|
||||
runtime.upsert_thread(&metadata).await?;
|
||||
|
||||
let page = list_threads_db(
|
||||
Some(runtime.as_ref()),
|
||||
&requested_sqlite,
|
||||
/*page_size*/ 10,
|
||||
/*cursor*/ None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
SortDirection::Desc,
|
||||
&[],
|
||||
/*model_providers*/ None,
|
||||
/*cwd_filters*/ None,
|
||||
/*relation_filter*/ None,
|
||||
/*archived*/ false,
|
||||
/*is_pinned*/ None,
|
||||
/*search_term*/ None,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(page.is_none());
|
||||
assert_eq!(runtime.get_thread(thread_id).await?, Some(metadata));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn try_init_waits_for_concurrent_startup_backfill() -> anyhow::Result<()> {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let runtime =
|
||||
codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".to_string())
|
||||
.await?;
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
let claimed = runtime.try_claim_backfill(/*lease_seconds*/ 60).await?;
|
||||
assert!(claimed);
|
||||
let runtime_for_completion = runtime.clone();
|
||||
@@ -65,7 +113,7 @@ async fn try_init_waits_for_concurrent_startup_backfill() -> anyhow::Result<()>
|
||||
|
||||
let initialized = try_init_with_roots_and_backfill_lease(
|
||||
home.path().to_path_buf(),
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
/*backfill_lease_seconds*/ 60,
|
||||
)
|
||||
@@ -82,15 +130,17 @@ async fn try_init_waits_for_concurrent_startup_backfill() -> anyhow::Result<()>
|
||||
#[tokio::test]
|
||||
async fn try_init_times_out_waiting_for_stuck_startup_backfill() -> anyhow::Result<()> {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let runtime =
|
||||
codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".to_string())
|
||||
.await?;
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
let claimed = runtime.try_claim_backfill(/*lease_seconds*/ 60).await?;
|
||||
assert!(claimed);
|
||||
|
||||
let result = try_init_with_roots_and_backfill_lease(
|
||||
home.path().to_path_buf(),
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
/*backfill_lease_seconds*/ 60,
|
||||
)
|
||||
@@ -114,9 +164,11 @@ async fn reconcile_rollout_preserves_existing_explicit_title() -> anyhow::Result
|
||||
let thread_id = ThreadId::new();
|
||||
let rollout_path =
|
||||
write_rollout_with_user_message(home.path(), thread_id, "Hey", ThreadHistoryMode::Legacy)?;
|
||||
let runtime =
|
||||
codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".to_string())
|
||||
.await?;
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut metadata =
|
||||
metadata::extract_metadata_from_rollout(rollout_path.as_path(), "test-provider")
|
||||
@@ -157,9 +209,11 @@ async fn reconcile_rollout_preserves_existing_paginated_memory_mode() -> anyhow:
|
||||
"Hey",
|
||||
ThreadHistoryMode::Paginated,
|
||||
)?;
|
||||
let runtime =
|
||||
codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".to_string())
|
||||
.await?;
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
reconcile_rollout(
|
||||
Some(runtime.as_ref()),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![allow(warnings, clippy::all)]
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
@@ -62,9 +63,12 @@ async fn insert_state_db_thread(
|
||||
rollout_path: &Path,
|
||||
archived: bool,
|
||||
) -> crate::state_db::StateDbHandle {
|
||||
let runtime = codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(home.abs()),
|
||||
TEST_PROVIDER.to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
runtime
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await
|
||||
@@ -227,9 +231,12 @@ async fn find_thread_path_repairs_missing_db_row_after_filesystem_fallback() {
|
||||
let fs_rollout_path = home.join(format!("sessions/2025/01/03/rollout-{ts}-{uuid}.jsonl"));
|
||||
|
||||
// Create an empty state DB so lookup takes the DB-first path and then falls back to files.
|
||||
let runtime = codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(home.abs()),
|
||||
TEST_PROVIDER.to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
runtime
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await
|
||||
@@ -280,9 +287,12 @@ async fn assert_state_db_rollout_path(
|
||||
thread_id: ThreadId,
|
||||
expected_path: Option<&Path>,
|
||||
) {
|
||||
let runtime = codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(home.abs()),
|
||||
TEST_PROVIDER.to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let path = runtime
|
||||
.find_rollout_path_by_id(thread_id, Some(false))
|
||||
.await
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! Read-only state database queries for diagnostics.
|
||||
|
||||
use anyhow::Result;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use sqlx::Row;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::SqliteConfig;
|
||||
|
||||
/// Minimal thread metadata used by read-only state database audits.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ThreadStateAuditRow {
|
||||
@@ -17,11 +17,10 @@ pub struct ThreadStateAuditRow {
|
||||
}
|
||||
|
||||
/// Read persisted thread rows from a state DB without creating, migrating, or repairing it.
|
||||
pub async fn read_thread_state_audit_rows(path: &Path) -> Result<Vec<ThreadStateAuditRow>> {
|
||||
let sqlite = crate::SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(
|
||||
path.parent().unwrap_or(path),
|
||||
)?);
|
||||
let pool = sqlite.open_read_only_pool(path).await?;
|
||||
pub async fn read_thread_state_audit_rows(
|
||||
sqlite: &SqliteConfig,
|
||||
) -> Result<Vec<ThreadStateAuditRow>> {
|
||||
let pool = sqlite.open_read_only_pool(&sqlite.state_db_path()).await?;
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT id, rollout_path, archived, source, model_provider
|
||||
|
||||
@@ -113,7 +113,11 @@ async fn main() -> anyhow::Result<()> {
|
||||
.parent()
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_else(|| PathBuf::from("."));
|
||||
let runtime = StateRuntime::init(codex_home, "logs-client".to_string()).await?;
|
||||
let runtime = StateRuntime::init(
|
||||
SqliteConfig::from_sqlite_home(AbsolutePathBuf::relative_to_current_dir(codex_home)?),
|
||||
"logs-client".to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut last_id =
|
||||
print_backfill(runtime.as_ref(), &filter, args.backfill, args.compact).await?;
|
||||
|
||||
@@ -493,6 +493,7 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tracing_subscriber::filter::Targets;
|
||||
use tracing_subscriber::fmt::writer::MakeWriter;
|
||||
@@ -583,9 +584,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn sqlite_feedback_logs_match_feedback_formatter_shape() {
|
||||
let codex_home = temp_codex_home();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let writer = SharedWriter::default();
|
||||
let layer = start(runtime.clone());
|
||||
|
||||
@@ -641,9 +645,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn flush_persists_logs_for_query() {
|
||||
let codex_home = temp_codex_home();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let layer = start(runtime.clone());
|
||||
|
||||
let guard = tracing_subscriber::registry()
|
||||
@@ -672,9 +679,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn configured_batch_size_flushes_without_explicit_flush() {
|
||||
let codex_home = temp_codex_home();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let layer = LogDbLayer::start_with_config(
|
||||
runtime.clone(),
|
||||
LogSinkQueueConfig {
|
||||
@@ -721,9 +731,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn configured_flush_interval_persists_buffered_logs() {
|
||||
let codex_home = temp_codex_home();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let layer = LogDbLayer::start_with_config(
|
||||
runtime.clone(),
|
||||
LogSinkQueueConfig {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
@@ -9,9 +10,12 @@ use super::*;
|
||||
async fn sqlite_sink_drops_low_level_opentelemetry_sdk_logs() {
|
||||
let codex_home =
|
||||
std::env::temp_dir().join(format!("codex-state-log-db-filter-{}", Uuid::new_v4()));
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let layer = start(runtime.clone());
|
||||
|
||||
let guard = tracing_subscriber::registry()
|
||||
|
||||
@@ -24,7 +24,6 @@ use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde_json::Value;
|
||||
use sqlx::QueryBuilder;
|
||||
use sqlx::Row;
|
||||
@@ -80,7 +79,7 @@ const LOG_PARTITION_ROW_LIMIT: i64 = 1_000;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StateRuntime {
|
||||
codex_home: PathBuf,
|
||||
sqlite: SqliteConfig,
|
||||
default_provider: String,
|
||||
pool: Arc<sqlx::SqlitePool>,
|
||||
logs_pool: Arc<sqlx::SqlitePool>,
|
||||
@@ -91,36 +90,31 @@ pub struct StateRuntime {
|
||||
}
|
||||
|
||||
impl StateRuntime {
|
||||
/// Initialize the state runtime using the provided Codex home and default provider.
|
||||
/// Initialize the state runtime using the provided SQLite configuration and default provider.
|
||||
///
|
||||
/// This opens (and migrates) the SQLite databases under `codex_home`.
|
||||
/// This opens (and migrates) the SQLite databases under the configured
|
||||
/// `sqlite_home`.
|
||||
/// Logs and paginated thread history live in dedicated files to reduce
|
||||
/// lock contention with the rest of the state store.
|
||||
pub async fn init(codex_home: PathBuf, default_provider: String) -> anyhow::Result<Arc<Self>> {
|
||||
Self::init_inner(
|
||||
codex_home,
|
||||
default_provider,
|
||||
/*telemetry_override*/ None,
|
||||
)
|
||||
.await
|
||||
pub async fn init(sqlite: SqliteConfig, default_provider: String) -> anyhow::Result<Arc<Self>> {
|
||||
Self::init_inner(sqlite, default_provider, /*telemetry_override*/ None).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn init_with_telemetry_for_tests(
|
||||
codex_home: PathBuf,
|
||||
sqlite: SqliteConfig,
|
||||
default_provider: String,
|
||||
telemetry_override: &dyn DbTelemetry,
|
||||
) -> anyhow::Result<Arc<Self>> {
|
||||
Self::init_inner(codex_home, default_provider, Some(telemetry_override)).await
|
||||
Self::init_inner(sqlite, default_provider, Some(telemetry_override)).await
|
||||
}
|
||||
|
||||
async fn init_inner(
|
||||
codex_home: PathBuf,
|
||||
sqlite: SqliteConfig,
|
||||
default_provider: String,
|
||||
telemetry_override: Option<&dyn DbTelemetry>,
|
||||
) -> anyhow::Result<Arc<Self>> {
|
||||
let sqlite = SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(codex_home.clone())?);
|
||||
tokio::fs::create_dir_all(&codex_home).await?;
|
||||
tokio::fs::create_dir_all(sqlite.home()).await?;
|
||||
let state_migrator = runtime_state_migrator();
|
||||
let logs_migrator = runtime_logs_migrator();
|
||||
let goals_migrator = runtime_goals_migrator();
|
||||
@@ -230,7 +224,7 @@ impl StateRuntime {
|
||||
memories: MemoryStore::new(Arc::clone(&memories_pool), Arc::clone(&pool)),
|
||||
pool,
|
||||
logs_pool,
|
||||
codex_home,
|
||||
sqlite,
|
||||
default_provider,
|
||||
thread_updated_at_millis: Arc::new(AtomicI64::new(thread_updated_at_millis)),
|
||||
thread_recency_at_millis: Arc::new(AtomicI64::new(thread_recency_at_millis)),
|
||||
@@ -244,9 +238,9 @@ impl StateRuntime {
|
||||
Ok(runtime)
|
||||
}
|
||||
|
||||
/// Return the configured Codex home directory for this runtime.
|
||||
pub fn codex_home(&self) -> &Path {
|
||||
self.codex_home.as_path()
|
||||
/// Return the SQLite configuration for this runtime.
|
||||
pub fn sqlite(&self) -> &SqliteConfig {
|
||||
&self.sqlite
|
||||
}
|
||||
|
||||
pub fn thread_goals(&self) -> &GoalStore {
|
||||
@@ -265,8 +259,7 @@ impl StateRuntime {
|
||||
self.pool.close().await;
|
||||
}
|
||||
|
||||
pub async fn clear_memory_data_in_sqlite_home(sqlite_home: &Path) -> anyhow::Result<bool> {
|
||||
let sqlite = SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(sqlite_home)?);
|
||||
pub async fn clear_memory_data_in_sqlite_home(sqlite: &SqliteConfig) -> anyhow::Result<bool> {
|
||||
let memories_path = sqlite.memories_db_path();
|
||||
if !tokio::fs::try_exists(&memories_path).await? {
|
||||
return Ok(false);
|
||||
@@ -289,8 +282,7 @@ async fn close_sqlite_pools(pools: &[&SqlitePool]) {
|
||||
}
|
||||
|
||||
/// Open and migrate the rebuildable paginated thread-history database.
|
||||
pub async fn open_thread_history_db(sqlite_home: &Path) -> anyhow::Result<SqlitePool> {
|
||||
let sqlite = SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(sqlite_home)?);
|
||||
pub async fn open_thread_history_db(sqlite: &SqliteConfig) -> anyhow::Result<SqlitePool> {
|
||||
let migrator = runtime_thread_history_migrator();
|
||||
sqlite
|
||||
.open_thread_history_db(&migrator, /*telemetry_override*/ None)
|
||||
@@ -326,9 +318,10 @@ ON CONFLICT(id) DO NOTHING
|
||||
}
|
||||
|
||||
/// Run SQLite's built-in integrity check against an existing database file.
|
||||
pub async fn sqlite_integrity_check(path: &Path) -> anyhow::Result<Vec<String>> {
|
||||
let sqlite =
|
||||
SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(path.parent().unwrap_or(path))?);
|
||||
pub async fn sqlite_integrity_check(
|
||||
sqlite: &SqliteConfig,
|
||||
path: &Path,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let pool = sqlite.open_read_only_pool(path).await?;
|
||||
let rows = sqlx::query_scalar::<_, String>("PRAGMA integrity_check")
|
||||
.fetch_all(&pool)
|
||||
@@ -431,7 +424,7 @@ mod tests {
|
||||
.expect("create sample table");
|
||||
pool.close().await;
|
||||
|
||||
let result = sqlite_integrity_check(&path)
|
||||
let result = sqlite_integrity_check(&sqlite, &path)
|
||||
.await
|
||||
.expect("integrity check should run");
|
||||
|
||||
@@ -477,7 +470,7 @@ mod tests {
|
||||
strict_pool.close().await;
|
||||
|
||||
let tolerant_migrator = runtime_state_migrator();
|
||||
let tolerant_pool = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs())
|
||||
let tolerant_pool = sqlite
|
||||
.open_state_db(&tolerant_migrator, /*telemetry_override*/ None)
|
||||
.await
|
||||
.expect("runtime migrator should tolerate newer applied migrations");
|
||||
@@ -492,7 +485,7 @@ mod tests {
|
||||
let telemetry = TestTelemetry::default();
|
||||
|
||||
let runtime = StateRuntime::init_with_telemetry_for_tests(
|
||||
codex_home.clone(),
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
&telemetry,
|
||||
)
|
||||
|
||||
@@ -119,9 +119,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn backfill_state_persists_progress_and_completion() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let initial = runtime
|
||||
.get_backfill_state()
|
||||
@@ -172,10 +175,10 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn get_backfill_state_succeeds_while_another_connection_holds_writer_slot() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
let sqlite = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs());
|
||||
let runtime = StateRuntime::init(sqlite.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let sqlite = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs());
|
||||
let write_pool = sqlite
|
||||
.open_read_write_pool(&sqlite.state_db_path())
|
||||
.await
|
||||
@@ -202,9 +205,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn get_backfill_state_repairs_a_missing_singleton_row() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
sqlx::query("DELETE FROM backfill_state WHERE id = 1")
|
||||
.execute(runtime.pool.as_ref())
|
||||
.await
|
||||
@@ -228,9 +234,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn backfill_claim_is_singleton_until_stale_and_blocked_when_complete() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let claimed = runtime
|
||||
.try_claim_backfill(/*lease_seconds*/ 3600)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use super::*;
|
||||
use crate::runtime::test_support::unique_temp_dir;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
async fn records_completion_by_import_id() -> anyhow::Result<()> {
|
||||
let runtime = StateRuntime::init(unique_temp_dir(), "test-provider".to_string()).await?;
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(unique_temp_dir().as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
runtime
|
||||
.record_external_agent_config_import_completed(
|
||||
@@ -127,7 +132,11 @@ async fn records_completion_by_import_id() -> anyhow::Result<()> {
|
||||
|
||||
#[tokio::test]
|
||||
async fn reads_all_history_records() -> anyhow::Result<()> {
|
||||
let runtime = StateRuntime::init(unique_temp_dir(), "test-provider".to_string()).await?;
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(unique_temp_dir().as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
runtime
|
||||
.record_external_agent_config_import_completed(
|
||||
|
||||
@@ -634,12 +634,16 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::runtime::test_support::test_thread_metadata;
|
||||
use crate::runtime::test_support::unique_temp_dir;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
async fn test_runtime() -> std::sync::Arc<StateRuntime> {
|
||||
StateRuntime::init(unique_temp_dir(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize")
|
||||
StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(unique_temp_dir().as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize")
|
||||
}
|
||||
|
||||
fn test_thread_id() -> ThreadId {
|
||||
@@ -647,11 +651,8 @@ mod tests {
|
||||
}
|
||||
|
||||
async fn upsert_test_thread(runtime: &StateRuntime, thread_id: ThreadId) {
|
||||
let metadata = test_thread_metadata(
|
||||
runtime.codex_home(),
|
||||
thread_id,
|
||||
runtime.codex_home().join("workspace"),
|
||||
);
|
||||
let sqlite_home = runtime.sqlite().home();
|
||||
let metadata = test_thread_metadata(sqlite_home, thread_id, sqlite_home.join("workspace"));
|
||||
runtime
|
||||
.upsert_thread(&metadata)
|
||||
.await
|
||||
|
||||
@@ -574,9 +574,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_use_dedicated_log_database() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.insert_logs(&[LogEntry {
|
||||
@@ -648,9 +651,12 @@ mod tests {
|
||||
pool.close().await;
|
||||
drop(pool);
|
||||
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let rows = runtime
|
||||
.query_logs(&LogQuery::default())
|
||||
@@ -704,9 +710,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn init_configures_logs_db_with_incremental_auto_vacuum() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let _runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let _runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let logs_path =
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()).logs_db_path();
|
||||
@@ -750,9 +759,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn query_logs_with_search_matches_rendered_body_substring() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.insert_logs(&[
|
||||
@@ -803,9 +815,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn query_logs_filters_level_set_without_rewriting_stored_level() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.insert_logs(&[
|
||||
@@ -888,9 +903,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_prunes_old_rows_when_thread_exceeds_size_limit() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let six_mebibytes = "a".repeat(6 * 1024 * 1024);
|
||||
runtime
|
||||
@@ -942,9 +960,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_prunes_single_thread_row_when_it_exceeds_size_limit() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let eleven_mebibytes = "d".repeat(11 * 1024 * 1024);
|
||||
runtime
|
||||
@@ -980,9 +1001,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_prunes_threadless_rows_per_process_uuid_only() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let six_mebibytes = "b".repeat(6 * 1024 * 1024);
|
||||
runtime
|
||||
@@ -1049,9 +1073,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_prunes_single_threadless_process_row_when_it_exceeds_size_limit() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let eleven_mebibytes = "e".repeat(11 * 1024 * 1024);
|
||||
runtime
|
||||
@@ -1087,9 +1114,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_prunes_threadless_rows_with_null_process_uuid() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let six_mebibytes = "c".repeat(6 * 1024 * 1024);
|
||||
runtime
|
||||
@@ -1155,9 +1185,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_prunes_single_threadless_null_process_row_when_it_exceeds_limit() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let eleven_mebibytes = "f".repeat(11 * 1024 * 1024);
|
||||
runtime
|
||||
@@ -1193,9 +1226,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_prunes_old_rows_when_thread_exceeds_row_limit() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let entries: Vec<LogEntry> = (1..=1_001)
|
||||
.map(|ts| LogEntry {
|
||||
@@ -1236,9 +1272,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_prunes_old_threadless_rows_when_process_exceeds_row_limit() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let entries: Vec<LogEntry> = (1..=1_001)
|
||||
.map(|ts| LogEntry {
|
||||
@@ -1283,9 +1322,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_logs_prunes_old_threadless_null_process_rows_when_row_limit_exceeded() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let entries: Vec<LogEntry> = (1..=1_001)
|
||||
.map(|ts| LogEntry {
|
||||
@@ -1330,9 +1372,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn query_feedback_logs_returns_newest_lines_within_limit_in_order() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.insert_logs(&[
|
||||
@@ -1400,9 +1445,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn query_feedback_logs_excludes_oversized_newest_row() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let eleven_mebibytes = "z".repeat(11 * 1024 * 1024);
|
||||
|
||||
runtime
|
||||
@@ -1450,9 +1498,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn query_feedback_logs_includes_threadless_rows_from_same_process() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.insert_logs(&[
|
||||
@@ -1548,9 +1599,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn query_feedback_logs_excludes_threadless_rows_from_prior_processes() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.insert_logs(&[
|
||||
@@ -1646,9 +1700,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn query_feedback_logs_keeps_newest_suffix_across_thread_and_threadless_logs() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let thread_marker = "thread-scoped-oldest";
|
||||
let threadless_older_marker = "threadless-older";
|
||||
let threadless_newer_marker = "threadless-newer";
|
||||
@@ -1721,9 +1778,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn query_feedback_logs_for_threads_merges_requested_threads_and_threadless_rows() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.insert_logs(&[
|
||||
@@ -1841,9 +1901,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn query_feedback_logs_for_threads_returns_empty_for_empty_thread_list() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let bytes = runtime
|
||||
.query_feedback_logs_for_threads(&[])
|
||||
|
||||
@@ -1681,6 +1681,7 @@ mod tests {
|
||||
use chrono::Utc;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use sqlx::Row;
|
||||
use std::sync::Arc;
|
||||
@@ -1707,9 +1708,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn stage1_claim_skips_when_up_to_date() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.join("a"));
|
||||
@@ -1775,9 +1779,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn stage1_running_stale_can_be_stolen_but_fresh_running_is_skipped() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -1830,9 +1837,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn stage1_concurrent_claim_for_same_thread_is_conflict_safe() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
runtime
|
||||
@@ -1898,9 +1908,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn stage1_concurrent_claims_respect_running_cap() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let thread_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
@@ -1966,9 +1979,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn claim_stage1_jobs_filters_by_age_idle_and_current_thread() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let now = Utc::now();
|
||||
let fresh_at = now - Duration::hours(1);
|
||||
@@ -2056,9 +2072,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn claim_stage1_jobs_bounds_state_scan_before_memory_probes() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let now = Utc::now();
|
||||
let eligible_newer_at = now - Duration::hours(13);
|
||||
@@ -2171,9 +2190,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn claim_stage1_jobs_skips_threads_without_enabled_memory() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let now = Utc::now();
|
||||
let eligible_at = now - Duration::hours(13);
|
||||
@@ -2267,9 +2289,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn clear_memory_data_clears_rows_and_preserves_thread_memory_modes() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let now = Utc::now() - Duration::hours(13);
|
||||
let worker_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("worker id");
|
||||
@@ -2376,9 +2401,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn claim_stage1_jobs_enforces_global_running_cap() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let current_thread_id =
|
||||
ThreadId::from_string(&Uuid::new_v4().to_string()).expect("current thread id");
|
||||
@@ -2503,9 +2531,12 @@ WHERE kind = 'memory_stage1'
|
||||
#[tokio::test]
|
||||
async fn claim_stage1_jobs_processes_two_full_batches_across_startup_passes() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let current_thread_id =
|
||||
ThreadId::from_string(&Uuid::new_v4().to_string()).expect("current thread id");
|
||||
@@ -2590,9 +2621,12 @@ WHERE kind = 'memory_stage1'
|
||||
#[tokio::test]
|
||||
async fn delete_thread_removes_stage1_output_and_enqueues_phase2_when_selected() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -2715,9 +2749,12 @@ WHERE kind = ? AND job_key = ?
|
||||
#[tokio::test]
|
||||
async fn mark_stage1_job_succeeded_no_output_skips_phase2_when_output_was_already_absent() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -2790,9 +2827,12 @@ WHERE kind = ? AND job_key = ?
|
||||
#[tokio::test]
|
||||
async fn mark_stage1_job_succeeded_no_output_enqueues_phase2_when_deleting_output() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -2915,9 +2955,12 @@ WHERE kind = ? AND job_key = ?
|
||||
#[tokio::test]
|
||||
async fn stage1_retry_exhaustion_does_not_block_newer_watermark() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -3011,9 +3054,12 @@ WHERE kind = ? AND job_key = ?
|
||||
#[tokio::test]
|
||||
async fn phase2_global_lock_respects_success_cooldown() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
|
||||
@@ -3073,9 +3119,12 @@ WHERE kind = ? AND job_key = ?
|
||||
#[tokio::test]
|
||||
async fn phase2_global_lock_can_be_claimed_after_retry_budget_is_exhausted() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.enqueue_global_consolidation(/*input_watermark*/ 100)
|
||||
@@ -3143,9 +3192,12 @@ WHERE kind = ? AND job_key = ?
|
||||
#[tokio::test]
|
||||
async fn list_stage1_outputs_for_global_returns_latest_outputs() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let thread_id_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
@@ -3246,9 +3298,12 @@ WHERE kind = ? AND job_key = ?
|
||||
#[tokio::test]
|
||||
async fn list_stage1_outputs_for_global_skips_empty_payloads() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id_non_empty =
|
||||
ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
@@ -3315,9 +3370,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn list_stage1_outputs_for_global_includes_paginated_and_skips_polluted_threads() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id_enabled =
|
||||
ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
@@ -3382,9 +3440,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn get_phase2_input_selection_returns_current_selected_rows() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id_a = stable_thread_id("00000000-0000-4000-8000-000000000001");
|
||||
let thread_id_b = stable_thread_id("00000000-0000-4000-8000-000000000002");
|
||||
@@ -3497,9 +3558,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn get_phase2_input_selection_excludes_polluted_previous_selection() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id_enabled =
|
||||
ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
@@ -3590,9 +3654,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn mark_thread_memory_mode_polluted_enqueues_phase2_for_selected_threads() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -3679,9 +3746,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn mark_thread_memory_mode_polluted_enqueues_phase2_when_already_polluted() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -3774,9 +3844,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn get_phase2_input_selection_returns_regenerated_selected_rows() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -3893,9 +3966,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn get_phase2_input_selection_uses_current_ranking_after_refreshes() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id_a = stable_thread_id("00000000-0000-4000-8000-000000000001");
|
||||
let thread_id_b = stable_thread_id("00000000-0000-4000-8000-000000000002");
|
||||
@@ -4036,9 +4112,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn mark_global_phase2_job_succeeded_updates_selected_snapshot_timestamp() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -4186,9 +4265,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn mark_global_phase2_job_succeeded_only_marks_exact_selected_snapshots() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -4306,9 +4388,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn record_stage1_output_usage_updates_usage_metadata() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id a");
|
||||
let thread_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id b");
|
||||
@@ -4425,9 +4510,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn get_phase2_input_selection_prioritizes_usage_count_then_recent_usage() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let now = Utc::now();
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -4521,9 +4609,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn get_phase2_input_selection_excludes_stale_used_memories_but_keeps_fresh_never_used() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let now = Utc::now();
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
@@ -4617,9 +4708,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn get_phase2_input_selection_prefers_recent_thread_updates_over_recent_generation() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
let older_thread =
|
||||
@@ -4703,9 +4797,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn prune_stage1_outputs_for_retention_prunes_stale_unselected_rows_only() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
let stale_unused =
|
||||
@@ -4845,9 +4942,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn prune_stage1_outputs_for_retention_respects_batch_limit() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
let thread_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread a");
|
||||
@@ -4923,9 +5023,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn mark_stage1_job_succeeded_enqueues_global_consolidation() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id a");
|
||||
let thread_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id b");
|
||||
@@ -5018,9 +5121,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn phase2_global_lock_allows_only_one_fresh_runner() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.enqueue_global_consolidation(/*input_watermark*/ 200)
|
||||
@@ -5051,9 +5157,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn phase2_global_lock_creates_missing_job_row() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let owner_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner a");
|
||||
let owner_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner b");
|
||||
@@ -5101,9 +5210,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn phase2_global_lock_stale_lease_allows_takeover() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.enqueue_global_consolidation(/*input_watermark*/ 300)
|
||||
@@ -5176,9 +5288,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn enqueue_global_consolidation_keeps_phase2_input_watermark_monotonic() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.enqueue_global_consolidation(/*input_watermark*/ 500)
|
||||
@@ -5240,9 +5355,12 @@ VALUES (?, ?, ?, ?, ?)
|
||||
#[tokio::test]
|
||||
async fn phase2_failure_fallback_updates_unowned_running_job() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.enqueue_global_consolidation(/*input_watermark*/ 400)
|
||||
|
||||
@@ -86,7 +86,12 @@ async fn runtime_db_path_for_corruption_error_returns_failed_database_path() ->
|
||||
let path = crate::SqliteConfig::new_for_testing(sqlite_home.as_path().abs()).state_db_path();
|
||||
tokio::fs::write(path.as_path(), b"not sqlite").await?;
|
||||
|
||||
let err = match super::super::StateRuntime::init(sqlite_home, "openai".to_string()).await {
|
||||
let err = match super::super::StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(sqlite_home.as_path().abs()),
|
||||
"openai".to_string(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => panic!("malformed sqlite should fail to initialize"),
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
@@ -165,9 +165,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn remote_control_enrollment_round_trips_by_target_and_account() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord {
|
||||
@@ -245,9 +248,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn delete_remote_control_enrollment_removes_only_matching_entry() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord {
|
||||
@@ -365,9 +371,12 @@ mod tests {
|
||||
.expect("insert legacy enrollment");
|
||||
pool.close().await;
|
||||
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
let actual = runtime
|
||||
.get_remote_control_enrollment(
|
||||
"wss://example.com/backend-api/wham/remote/control/server",
|
||||
|
||||
@@ -1418,15 +1418,19 @@ mod tests {
|
||||
use codex_protocol::protocol::SessionMetaLine;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tokio::test]
|
||||
async fn upsert_thread_keeps_creation_memory_mode_for_existing_rows() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000123").expect("valid thread id");
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -1462,9 +1466,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn thread_metadata_round_trips_history_mode() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000124").expect("valid thread id");
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -1486,9 +1493,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn thread_pin_updates_round_trip_and_survive_rollout_reconciliation() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id = ThreadId::new();
|
||||
let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
runtime
|
||||
@@ -1557,9 +1567,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn list_threads_filters_pins_before_recency_pagination_and_uses_index() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let oldest_pinned = ThreadId::from_string("00000000-0000-0000-0000-000000000041").unwrap();
|
||||
let newest_unpinned =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000042").unwrap();
|
||||
@@ -1653,7 +1666,11 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn delete_thread_cleans_associated_state() -> Result<()> {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()).await?;
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000401")?;
|
||||
let child_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000402")?;
|
||||
runtime
|
||||
@@ -1699,7 +1716,11 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn delete_thread_keeps_retry_graph_on_cleanup_failure() -> Result<()> {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()).await?;
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await?;
|
||||
let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000405")?;
|
||||
let child_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000406")?;
|
||||
runtime
|
||||
@@ -1782,9 +1803,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn list_threads_updated_after_returns_oldest_changes_first() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let older_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000001").expect("valid thread id");
|
||||
let middle_id =
|
||||
@@ -1870,9 +1894,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn list_threads_filters_by_cwd() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let first_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000101").expect("valid thread id");
|
||||
let second_id =
|
||||
@@ -1981,9 +2008,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn list_threads_uses_indexes_matching_cwd_filters() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home, "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
|
||||
let model_providers = ["test-provider".to_string()];
|
||||
let cwd_filters = [
|
||||
@@ -2068,9 +2098,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn list_threads_by_relation_filters_spawn_graph_with_keyset_pagination() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let parent_id = ThreadId::new();
|
||||
let first_child_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000001").expect("valid thread id");
|
||||
@@ -2264,9 +2297,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn apply_rollout_items_restores_memory_mode_from_session_meta() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000456").expect("valid thread id");
|
||||
let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -2329,9 +2365,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn apply_rollout_items_preserves_existing_git_branch_and_fills_missing_git_fields() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000457").expect("valid thread id");
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -2406,9 +2445,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn upsert_thread_preserves_existing_git_fields_atomically() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000458").expect("valid thread id");
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -2447,9 +2489,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn upsert_thread_preserves_existing_preview_when_incoming_preview_is_empty() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000459").expect("valid thread id");
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -2480,9 +2525,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn set_thread_preview_if_empty_only_fills_blank_preview() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000460").expect("valid thread id");
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -2521,9 +2569,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn update_thread_git_info_preserves_newer_non_git_metadata() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000789").expect("valid thread id");
|
||||
let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -2583,9 +2634,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn insert_thread_if_absent_preserves_existing_metadata() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000791").expect("valid thread id");
|
||||
|
||||
@@ -2631,9 +2685,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn update_thread_git_info_can_clear_fields() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000790").expect("valid thread id");
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -2665,9 +2722,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn touch_thread_updated_at_updates_only_updated_at() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000791").expect("valid thread id");
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -2704,9 +2764,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn touch_thread_recency_at_is_monotonic_and_survives_stale_upsert() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000792").expect("valid thread id");
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -2763,9 +2826,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn list_threads_orders_and_pages_by_recency_at() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let first_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000793").expect("valid thread id");
|
||||
let second_id =
|
||||
@@ -2887,9 +2953,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn thread_updated_at_uses_unique_epoch_millis_and_reads_legacy_seconds() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let first_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000901").expect("valid thread id");
|
||||
let second_id =
|
||||
@@ -2995,9 +3064,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn apply_rollout_items_uses_override_updated_at_when_provided() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000792").expect("valid thread id");
|
||||
let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone());
|
||||
@@ -3055,9 +3127,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn thread_spawn_edges_track_directional_status() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home, "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let parent_thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000900").expect("valid thread id");
|
||||
let child_thread_id =
|
||||
@@ -3151,9 +3226,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn thread_spawn_children_without_status_filter_lists_all_statuses() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home, "test-provider".to_string())
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let runtime = StateRuntime::init(
|
||||
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("state db should initialize");
|
||||
let parent_thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000910").expect("valid thread id");
|
||||
let open_child_thread_id =
|
||||
|
||||
@@ -45,6 +45,7 @@ use codex_core_api::RealtimeAudioConfig;
|
||||
use codex_core_api::RealtimeConfig;
|
||||
use codex_core_api::SessionPickerViewMode;
|
||||
use codex_core_api::SessionSource;
|
||||
use codex_core_api::SqliteConfig;
|
||||
use codex_core_api::StartThreadOptions;
|
||||
use codex_core_api::TerminalResizeReflowConfig;
|
||||
use codex_core_api::ThreadManager;
|
||||
@@ -251,7 +252,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
|
||||
agent_max_depth: 1,
|
||||
agent_roles: BTreeMap::new(),
|
||||
memories: MemoriesConfig::default(),
|
||||
sqlite_home: codex_home.to_path_buf(),
|
||||
sqlite: SqliteConfig::from_sqlite_home(codex_home.clone()),
|
||||
log_dir: codex_home.join("log").to_path_buf(),
|
||||
config_lock_export_dir: None,
|
||||
config_lock_allow_codex_version_mismatch: false,
|
||||
|
||||
@@ -33,6 +33,7 @@ tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use chrono::Utc;
|
||||
use codex_rollout::find_thread_path_by_id_str;
|
||||
|
||||
use super::LocalThreadStore;
|
||||
use super::helpers::matching_rollout_file_name;
|
||||
use super::helpers::scoped_rollout_path;
|
||||
use crate::ArchiveThreadParams;
|
||||
use crate::ThreadStoreError;
|
||||
use crate::ThreadStoreResult;
|
||||
use chrono::Utc;
|
||||
use codex_rollout::find_thread_path_by_id_str;
|
||||
|
||||
pub(super) async fn archive_thread(
|
||||
store: &LocalThreadStore,
|
||||
@@ -66,6 +65,7 @@ mod tests {
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_rollout::ARCHIVED_SESSIONS_SUBDIR;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use uuid::Uuid;
|
||||
@@ -134,7 +134,7 @@ mod tests {
|
||||
let active_path =
|
||||
write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -20,7 +20,7 @@ pub(super) async fn create_thread(
|
||||
})?;
|
||||
let config = RolloutConfig {
|
||||
codex_home: store.config.codex_home.clone(),
|
||||
sqlite_home: store.config.sqlite.home().to_path_buf(),
|
||||
sqlite: store.config.sqlite.clone(),
|
||||
cwd,
|
||||
model_provider_id: params.metadata.model_provider.clone(),
|
||||
generate_memories: matches!(params.metadata.memory_mode, ThreadMemoryMode::Enabled),
|
||||
|
||||
@@ -223,6 +223,7 @@ mod tests {
|
||||
use codex_protocol::protocol::HistoryPosition;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_protocol::protocol::ThreadMemoryMode;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use uuid::Uuid;
|
||||
@@ -420,9 +421,11 @@ mod tests {
|
||||
ThreadHistoryMode::Paginated,
|
||||
)
|
||||
.expect("session file");
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(
|
||||
&codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
)
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let thread_id_string = thread_id.to_string();
|
||||
sqlx::query(
|
||||
"INSERT INTO thread_turns (thread_id, turn_id, rollout_ordinal, status) VALUES (?, 'turn-1', 1, 'completed')",
|
||||
|
||||
@@ -41,7 +41,7 @@ pub(super) async fn list_threads(
|
||||
let state_db = store.state_db().await;
|
||||
let rollout_config = RolloutConfig {
|
||||
codex_home: store.config.codex_home.clone(),
|
||||
sqlite_home: store.config.sqlite.home().to_path_buf(),
|
||||
sqlite: store.config.sqlite.clone(),
|
||||
cwd: store.config.codex_home.clone(),
|
||||
model_provider_id: store.config.default_model_provider_id.clone(),
|
||||
generate_memories: false,
|
||||
@@ -110,7 +110,7 @@ pub(super) async fn list_rollout_threads(
|
||||
});
|
||||
let page = codex_rollout::state_db::list_threads_db(
|
||||
state_db.as_deref(),
|
||||
config.codex_home.as_path(),
|
||||
&config.sqlite,
|
||||
params.page_size,
|
||||
cursor,
|
||||
sort_key,
|
||||
@@ -202,6 +202,7 @@ mod tests {
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
@@ -262,7 +263,7 @@ mod tests {
|
||||
fs::write(&rollout_path, "").expect("placeholder rollout file");
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -331,7 +332,7 @@ mod tests {
|
||||
fs::write(&rollout_path, "").expect("placeholder rollout file");
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -98,7 +98,7 @@ pub(super) async fn resume_thread(
|
||||
})?;
|
||||
let config = RolloutConfig {
|
||||
codex_home: store.config.codex_home.clone(),
|
||||
sqlite_home: store.config.sqlite.home().to_path_buf(),
|
||||
sqlite: store.config.sqlite.clone(),
|
||||
cwd,
|
||||
model_provider_id: params.metadata.model_provider.clone(),
|
||||
generate_memories: matches!(params.metadata.memory_mode, ThreadMemoryMode::Enabled),
|
||||
|
||||
@@ -24,7 +24,6 @@ use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_rollout::RolloutRecorder;
|
||||
use codex_rollout::StateDbHandle;
|
||||
use codex_state::SqliteConfig;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::path::PathBuf;
|
||||
@@ -128,17 +127,10 @@ pub struct LocalThreadStoreConfig {
|
||||
}
|
||||
|
||||
impl LocalThreadStoreConfig {
|
||||
#[expect(
|
||||
clippy::expect_used,
|
||||
reason = "resolved Codex configuration paths must be absolute"
|
||||
)]
|
||||
pub fn from_config(config: &impl codex_rollout::RolloutConfigView) -> Self {
|
||||
Self {
|
||||
codex_home: config.codex_home().to_path_buf(),
|
||||
sqlite: SqliteConfig::from_sqlite_home(
|
||||
AbsolutePathBuf::from_absolute_path_checked(config.sqlite_home())
|
||||
.expect("sqlite home from resolved config should be absolute"),
|
||||
),
|
||||
sqlite: config.sqlite_config().clone(),
|
||||
default_model_provider_id: config.model_provider_id().to_string(),
|
||||
}
|
||||
}
|
||||
@@ -174,7 +166,7 @@ impl LocalThreadStore {
|
||||
async fn thread_history_db(&self) -> ThreadStoreResult<&sqlx::SqlitePool> {
|
||||
self.thread_history_db
|
||||
.get_or_try_init(|| async {
|
||||
codex_state::open_thread_history_db(self.config.sqlite.home()).await
|
||||
codex_state::open_thread_history_db(&self.config.sqlite).await
|
||||
})
|
||||
.await
|
||||
.map_err(|err| ThreadStoreError::Internal {
|
||||
@@ -517,7 +509,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -552,7 +544,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -587,7 +579,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -655,7 +647,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -744,7 +736,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -780,7 +772,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -820,7 +812,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -864,7 +856,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -919,7 +911,7 @@ mod tests {
|
||||
let external_home = TempDir::new().expect("external temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -612,7 +612,7 @@ mod tests {
|
||||
let active_path =
|
||||
write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -771,7 +771,7 @@ mod tests {
|
||||
let rollout_path =
|
||||
write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -816,7 +816,7 @@ mod tests {
|
||||
)
|
||||
.expect("session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -859,7 +859,7 @@ mod tests {
|
||||
let rollout_path =
|
||||
write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -900,7 +900,7 @@ mod tests {
|
||||
let rollout_path =
|
||||
write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -934,7 +934,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1070,7 +1070,7 @@ mod tests {
|
||||
writeln!(file, "{meta}").expect("write session meta");
|
||||
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1124,7 +1124,7 @@ mod tests {
|
||||
write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file");
|
||||
let stale_path = external.path().join("missing-rollout.jsonl");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1175,7 +1175,7 @@ mod tests {
|
||||
let stale_path = write_session_file(external.path(), "2025-01-04T12-00-00", other_uuid)
|
||||
.expect("other session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1274,7 +1274,7 @@ mod tests {
|
||||
.path()
|
||||
.join(format!("rollout-2025-01-03T12-00-00-{uuid}.jsonl"));
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1336,7 +1336,7 @@ mod tests {
|
||||
.path()
|
||||
.join(format!("rollout-2025-01-03T12-00-00-{uuid}.jsonl"));
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1391,7 +1391,7 @@ mod tests {
|
||||
let archived_path = write_archived_session_file(home.path(), "2025-01-03T12-00-00", uuid)
|
||||
.expect("archived session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -60,7 +60,7 @@ pub(super) async fn search_threads(
|
||||
let state_db = store.state_db().await;
|
||||
let rollout_config = RolloutConfig {
|
||||
codex_home: store.config.codex_home.clone(),
|
||||
sqlite_home: store.config.sqlite.home().to_path_buf(),
|
||||
sqlite: store.config.sqlite.clone(),
|
||||
cwd: store.config.codex_home.clone(),
|
||||
model_provider_id: store.config.default_model_provider_id.clone(),
|
||||
generate_memories: false,
|
||||
|
||||
@@ -507,7 +507,7 @@ async fn store_with_mode(history_mode: ThreadHistoryMode) -> (TempDir, LocalThre
|
||||
);
|
||||
}
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -20,22 +20,218 @@ use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_protocol::protocol::ThreadMemoryMode;
|
||||
use codex_protocol::protocol::TurnCompleteEvent;
|
||||
use codex_protocol::protocol::TurnStartedEvent;
|
||||
use codex_protocol::protocol::UserMessageEvent;
|
||||
use codex_rollout::RolloutConfig;
|
||||
use codex_rollout::RolloutRecorder;
|
||||
use codex_rollout::RolloutRecorderParams;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use super::super::LocalThreadStore;
|
||||
use super::super::LocalThreadStoreConfig;
|
||||
use super::super::test_support::test_config;
|
||||
use crate::AppendThreadItemsParams;
|
||||
use crate::CreateThreadParams;
|
||||
use crate::DeleteThreadParams;
|
||||
use crate::ListThreadsParams;
|
||||
use crate::ListTurnsParams;
|
||||
use crate::ResumeThreadParams;
|
||||
use crate::SortDirection;
|
||||
use crate::StoredTurnItemsView;
|
||||
use crate::StoredTurnStatus;
|
||||
use crate::ThreadPersistenceMetadata;
|
||||
use crate::ThreadSortKey;
|
||||
use crate::ThreadStore;
|
||||
|
||||
/// Separate Codex and SQLite homes must work together across startup backfill,
|
||||
/// thread listing, and projection-backed paginated history reads.
|
||||
#[tokio::test]
|
||||
async fn split_homes_support_backfill_listing_and_paginated_history() {
|
||||
let root = TempDir::new().expect("temp dir");
|
||||
let codex_home = root.path().join("codex");
|
||||
let sqlite_home = root.path().join("sqlite");
|
||||
let thread_id = ThreadId::new();
|
||||
let sqlite = codex_state::SqliteConfig::new_for_testing(sqlite_home.as_path().abs());
|
||||
let rollout_config = RolloutConfig {
|
||||
codex_home: codex_home.clone(),
|
||||
sqlite: sqlite.clone(),
|
||||
cwd: codex_home.clone(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
generate_memories: false,
|
||||
};
|
||||
let recorder = RolloutRecorder::new(
|
||||
&rollout_config,
|
||||
RolloutRecorderParams::new(
|
||||
thread_id,
|
||||
/*forked_from_id*/ None,
|
||||
/*parent_thread_id*/ None,
|
||||
SessionSource::Exec,
|
||||
/*thread_source*/ None,
|
||||
"test-originator".to_string(),
|
||||
BaseInstructions::default(),
|
||||
Vec::new(),
|
||||
)
|
||||
.with_history_mode(ThreadHistoryMode::Paginated)
|
||||
.with_initial_window_id("window-1".to_string()),
|
||||
)
|
||||
.await
|
||||
.expect("create paginated rollout");
|
||||
recorder
|
||||
.record_canonical_items(&[RolloutItem::EventMsg(EventMsg::UserMessage(
|
||||
UserMessageEvent {
|
||||
message: "existing thread".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
))])
|
||||
.await
|
||||
.expect("record existing user message");
|
||||
recorder.persist().await.expect("persist paginated rollout");
|
||||
let rollout_path = recorder.rollout_path().to_path_buf();
|
||||
recorder.shutdown().await.expect("close paginated rollout");
|
||||
|
||||
let runtime = codex_rollout::state_db::try_init(&rollout_config)
|
||||
.await
|
||||
.expect("backfill state from Codex home");
|
||||
assert!(
|
||||
runtime
|
||||
.get_thread(thread_id)
|
||||
.await
|
||||
.expect("read backfilled thread")
|
||||
.is_some(),
|
||||
"startup backfill should index the rollout"
|
||||
);
|
||||
let store = LocalThreadStore::new(
|
||||
LocalThreadStoreConfig::from_config(&rollout_config),
|
||||
Some(runtime),
|
||||
);
|
||||
|
||||
let threads = store
|
||||
.list_threads(ListThreadsParams {
|
||||
page_size: 10,
|
||||
cursor: None,
|
||||
sort_key: ThreadSortKey::CreatedAt,
|
||||
sort_direction: SortDirection::Desc,
|
||||
allowed_sources: Vec::new(),
|
||||
model_providers: None,
|
||||
cwd_filters: None,
|
||||
archived: false,
|
||||
search_term: None,
|
||||
relation_filter: None,
|
||||
is_pinned: None,
|
||||
use_state_db_only: true,
|
||||
})
|
||||
.await
|
||||
.expect("list backfilled threads");
|
||||
assert_eq!(threads.items.len(), 1);
|
||||
assert_eq!(
|
||||
(
|
||||
threads.items[0].thread_id,
|
||||
threads.items[0].rollout_path.as_deref(),
|
||||
threads.items[0].history_mode,
|
||||
),
|
||||
(
|
||||
thread_id,
|
||||
Some(rollout_path.as_path()),
|
||||
ThreadHistoryMode::Paginated,
|
||||
)
|
||||
);
|
||||
|
||||
store
|
||||
.resume_thread(ResumeThreadParams {
|
||||
thread_id,
|
||||
rollout_path: Some(rollout_path),
|
||||
history: None,
|
||||
include_archived: false,
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: Some(codex_home.clone()),
|
||||
model_provider: "test-provider".to_string(),
|
||||
memory_mode: ThreadMemoryMode::Enabled,
|
||||
},
|
||||
})
|
||||
.await
|
||||
.expect("resume backfilled thread");
|
||||
store
|
||||
.append_items(AppendThreadItemsParams {
|
||||
thread_id,
|
||||
items: vec![
|
||||
turn_started("turn-1"),
|
||||
completed_item(
|
||||
thread_id,
|
||||
"turn-1",
|
||||
TurnItem::UserMessage(UserMessageItem {
|
||||
id: "user-1".to_string(),
|
||||
client_id: None,
|
||||
content: Vec::new(),
|
||||
}),
|
||||
),
|
||||
completed_item(
|
||||
thread_id,
|
||||
"turn-1",
|
||||
TurnItem::AgentMessage(AgentMessageItem {
|
||||
id: "agent-1".to_string(),
|
||||
content: vec![AgentMessageContent::Text {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
memory_citation: None,
|
||||
}),
|
||||
),
|
||||
turn_completed("turn-1"),
|
||||
],
|
||||
})
|
||||
.await
|
||||
.expect("append paginated history");
|
||||
|
||||
let turns = store
|
||||
.list_turns(ListTurnsParams {
|
||||
thread_id,
|
||||
include_archived: false,
|
||||
cursor: None,
|
||||
page_size: 10,
|
||||
sort_direction: SortDirection::Asc,
|
||||
items_view: StoredTurnItemsView::Summary,
|
||||
})
|
||||
.await
|
||||
.expect("list paginated history");
|
||||
assert_eq!(
|
||||
turns
|
||||
.turns
|
||||
.iter()
|
||||
.map(|turn| {
|
||||
(
|
||||
turn.turn_id.as_str(),
|
||||
turn.status,
|
||||
turn.items
|
||||
.iter()
|
||||
.map(|item| item.item_id.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(
|
||||
"turn-1",
|
||||
StoredTurnStatus::Completed,
|
||||
vec!["user-1", "agent-1"],
|
||||
)]
|
||||
);
|
||||
|
||||
let state_db_path = sqlite.state_db_path();
|
||||
let thread_history_db_path = sqlite.thread_history_db_path();
|
||||
for sqlite_path in [&state_db_path, &thread_history_db_path] {
|
||||
assert!(
|
||||
sqlite_path.exists(),
|
||||
"expected SQLite database at {}",
|
||||
sqlite_path.display()
|
||||
);
|
||||
let filename = sqlite_path.file_name().expect("SQLite database filename");
|
||||
assert!(
|
||||
!codex_home.join(filename).exists(),
|
||||
"SQLite database should not be created under Codex home"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn paginated_live_append_materializes_turn_items_and_state() {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
@@ -87,9 +283,11 @@ async fn paginated_live_append_materializes_turn_items_and_state() {
|
||||
rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 1);
|
||||
let (_, turn_end_byte_offset) =
|
||||
rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 4);
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let turn = sqlx::query_as::<
|
||||
_,
|
||||
(
|
||||
@@ -196,9 +394,11 @@ async fn active_turn_stores_only_its_start_position() {
|
||||
.expect("rollout path");
|
||||
let (turn_start_byte_offset, _) =
|
||||
rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 1);
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let turn_position = sqlx::query_as::<_, (Option<i64>, Option<i64>, Option<i64>)>(
|
||||
"SELECT rollout_byte_offset, rollout_end_ordinal, rollout_end_byte_offset FROM thread_turns WHERE thread_id = ? AND turn_id = ?",
|
||||
)
|
||||
@@ -263,9 +463,11 @@ async fn subagent_prefix_advances_projection_without_materializing_history() {
|
||||
.expect("rollout path");
|
||||
let (child_start_byte_offset, _) =
|
||||
rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 4);
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let turns = sqlx::query_as::<_, (String, i64, Option<i64>)>(
|
||||
"SELECT turn_id, rollout_ordinal, rollout_byte_offset FROM thread_turns WHERE thread_id = ?",
|
||||
)
|
||||
@@ -313,9 +515,11 @@ async fn replayed_item_snapshot_updates_content_without_reordering() {
|
||||
})
|
||||
.await
|
||||
.expect("append first item snapshot");
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let first_created_at_ms = sqlx::query_scalar::<_, i64>(
|
||||
"SELECT created_at_ms FROM thread_items WHERE thread_id = ? AND turn_id = ? AND item_id = ?",
|
||||
)
|
||||
@@ -399,9 +603,11 @@ async fn terminal_turn_does_not_change_after_later_records() {
|
||||
.await
|
||||
.expect("append later records");
|
||||
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let rollout_path = store
|
||||
.live_rollout_path(thread_id)
|
||||
.await
|
||||
@@ -457,7 +663,7 @@ async fn summary_items_use_final_answers_and_ignore_commentary() {
|
||||
let config = test_config(home.path());
|
||||
let thread_id = ThreadId::default();
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -582,9 +788,11 @@ async fn next_write_catches_up_unprojected_durable_suffix() {
|
||||
.await
|
||||
.expect("persist session metadata");
|
||||
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let checkpoint = projection_state(&pool, thread_id).await;
|
||||
store
|
||||
.append_items(AppendThreadItemsParams {
|
||||
@@ -667,9 +875,11 @@ async fn synchronized_catch_up_does_not_replay_old_rows() {
|
||||
.await
|
||||
.expect("append turn start");
|
||||
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let before = projection_state(&pool, thread_id).await;
|
||||
sqlx::query("UPDATE thread_turns SET status = 'sentinel' WHERE thread_id = ?")
|
||||
.bind(thread_id.to_string())
|
||||
@@ -705,9 +915,11 @@ async fn catch_up_preserves_trailing_partial_line_boundaries() {
|
||||
.await
|
||||
.expect("persist session metadata");
|
||||
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let before = projection_state(&pool, thread_id).await;
|
||||
let complete_line = rollout_line(Some(1), turn_started("turn-1"));
|
||||
let partial_line = rollout_line(Some(2), turn_completed("turn-1"));
|
||||
@@ -782,9 +994,11 @@ async fn catch_up_rejects_invalid_complete_suffixes_without_advancing_state() {
|
||||
.await
|
||||
.expect("persist session metadata");
|
||||
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(
|
||||
&codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
)
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let before = projection_state(&pool, thread_id).await;
|
||||
let rollout_path = store
|
||||
.live_rollout_path(thread_id)
|
||||
@@ -940,9 +1154,11 @@ async fn blank_and_rejected_rollout_lines_do_not_poison_projection() {
|
||||
.await
|
||||
.expect("project valid retry after rejected line");
|
||||
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let (expected_start_byte_offset, _) =
|
||||
rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 1);
|
||||
let start_byte_offset = sqlx::query_scalar::<_, Option<i64>>(
|
||||
@@ -980,9 +1196,11 @@ async fn shutdown_materializes_items_queued_without_a_flush() {
|
||||
.await
|
||||
.expect("shutdown live thread");
|
||||
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let projected_turns = sqlx::query_scalar::<_, i64>(
|
||||
"SELECT COUNT(*) FROM thread_turns WHERE thread_id = ? AND turn_id = ?",
|
||||
)
|
||||
@@ -1032,9 +1250,11 @@ async fn delete_waits_for_in_flight_projection_before_removing_rows() {
|
||||
.expect("finish in-flight append");
|
||||
delete.await.expect("join delete").expect("delete thread");
|
||||
|
||||
let pool = codex_state::open_thread_history_db(home.path())
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing(
|
||||
home.path().abs(),
|
||||
))
|
||||
.await
|
||||
.expect("open thread history db");
|
||||
let counts = sqlx::query_as::<_, (i64, i64, i64)>(
|
||||
r#"
|
||||
SELECT
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use codex_rollout::find_archived_thread_path_by_id_str;
|
||||
use codex_rollout::rollout_date_parts;
|
||||
|
||||
use super::LocalThreadStore;
|
||||
use super::helpers::matching_rollout_file_name;
|
||||
use super::helpers::scoped_rollout_path;
|
||||
@@ -10,6 +7,8 @@ use crate::ReadThreadParams;
|
||||
use crate::StoredThread;
|
||||
use crate::ThreadStoreError;
|
||||
use crate::ThreadStoreResult;
|
||||
use codex_rollout::find_archived_thread_path_by_id_str;
|
||||
use codex_rollout::rollout_date_parts;
|
||||
|
||||
pub(super) async fn unarchive_thread(
|
||||
store: &LocalThreadStore,
|
||||
@@ -94,6 +93,7 @@ mod tests {
|
||||
use chrono::Utc;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use uuid::Uuid;
|
||||
@@ -143,7 +143,7 @@ mod tests {
|
||||
let archived_path = write_archived_session_file(home.path(), "2025-01-03T13-00-00", uuid)
|
||||
.expect("archived session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -842,6 +842,7 @@ mod tests {
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
@@ -900,7 +901,7 @@ mod tests {
|
||||
write_session_file(home.path(), "2025-01-03T14-20-00", uuid).expect("session file");
|
||||
let original_rollout = std::fs::read_to_string(&rollout_path).expect("read rollout");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1009,7 +1010,7 @@ mod tests {
|
||||
.expect("session file");
|
||||
let original_rollout = std::fs::read_to_string(&path).expect("read rollout");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1101,7 +1102,7 @@ mod tests {
|
||||
let path =
|
||||
write_session_file(home.path(), "2025-01-03T14-30-00", uuid).expect("session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1147,7 +1148,7 @@ mod tests {
|
||||
.expect("session file");
|
||||
let original_rollout = std::fs::read_to_string(&path).expect("read rollout");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1244,7 +1245,7 @@ mod tests {
|
||||
let path =
|
||||
write_session_file(home.path(), "2025-01-03T18-30-00", uuid).expect("session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1349,7 +1350,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1392,7 +1393,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1447,7 +1448,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1505,7 +1506,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
config.sqlite.home().to_path_buf(),
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1708,7 +1709,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1809,7 +1810,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1852,7 +1853,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1910,7 +1911,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1957,7 +1958,7 @@ mod tests {
|
||||
)
|
||||
.expect("session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -1997,7 +1998,7 @@ mod tests {
|
||||
write_archived_session_file(home.path(), "2025-01-03T19-30-00", uuid)
|
||||
.expect("archived session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -2033,7 +2034,7 @@ mod tests {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -2103,7 +2104,7 @@ mod tests {
|
||||
let archived_path = write_archived_session_file(home.path(), "2025-01-03T16-00-00", uuid)
|
||||
.expect("archived session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -2166,7 +2167,7 @@ mod tests {
|
||||
let archived_path = write_archived_session_file(home.path(), "2025-01-03T16-30-00", uuid)
|
||||
.expect("archived session file");
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -18,6 +18,7 @@ use crate::app_backtrack::BacktrackSelection;
|
||||
use crate::app_backtrack::BacktrackState;
|
||||
use crate::app_backtrack::user_count;
|
||||
use crate::app_event::HistoryBatchEntryResponse;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
|
||||
use crate::chatwidget::ChatWidgetInit;
|
||||
use crate::chatwidget::create_initial_user_message;
|
||||
@@ -2249,7 +2250,7 @@ fn update_memory_settings_updates_current_thread_memory_mode() -> Result<()> {
|
||||
let (mut app, _app_event_rx, _op_rx) = Box::pin(make_test_app_with_channels()).await;
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf().abs();
|
||||
app.config.sqlite_home = codex_home.path().to_path_buf();
|
||||
app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
// Seed the previous setting so this test exercises the thread-mode update path.
|
||||
app.config.memories.generate_memories = true;
|
||||
|
||||
@@ -2267,7 +2268,7 @@ fn update_memory_settings_updates_current_thread_memory_mode() -> Result<()> {
|
||||
.await;
|
||||
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
codex_home.path().to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()),
|
||||
app.config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -2289,7 +2290,7 @@ async fn reset_memories_clears_local_memory_directories() -> Result<()> {
|
||||
let (mut app, _app_event_rx, _op_rx) = Box::pin(make_test_app_with_channels()).await;
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf().abs();
|
||||
app.config.sqlite_home = codex_home.path().to_path_buf();
|
||||
app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
|
||||
let memory_root = codex_home.path().join("memories");
|
||||
let extensions_root = memory_root.join("extensions");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use app_test_support::create_fake_rollout;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -7,7 +8,7 @@ async fn fork_current_session_preserves_conversation_ultra() -> Result<()> {
|
||||
let mut app = make_test_app().await;
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf().abs();
|
||||
app.config.sqlite_home = codex_home.path().to_path_buf();
|
||||
app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
let source_thread_id = ThreadId::from_string(
|
||||
&create_fake_rollout(
|
||||
codex_home.path(),
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::app::session_lifecycle::ThreadAttachPresentation;
|
||||
use crate::chatwidget::UserMessage;
|
||||
use codex_app_server_client::AppServerEvent;
|
||||
use codex_app_server_protocol::ModelSafetyBufferingUpdatedNotification;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
@@ -268,7 +269,7 @@ goals = true
|
||||
),
|
||||
)?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf().abs();
|
||||
app.config.sqlite_home = codex_home.path().to_path_buf();
|
||||
app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
app.config.model = Some(CURRENT_MODEL.to_string());
|
||||
app.config.model_provider_id = MODEL_PROVIDER_ID.to_string();
|
||||
app.config.model_provider = ModelProviderInfo {
|
||||
@@ -320,7 +321,7 @@ goals = true
|
||||
}
|
||||
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
app.config.sqlite_home.clone(),
|
||||
app.config.sqlite.clone(),
|
||||
app.config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -8,6 +8,7 @@ use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCMessage;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_state::SqliteConfig;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -152,7 +153,7 @@ fn fresh_session_applies_requested_name() -> Result<()> {
|
||||
let mut app = make_test_app().await;
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf().abs();
|
||||
app.config.sqlite_home = codex_home.path().to_path_buf();
|
||||
app.config.sqlite = SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
let (mut app_server, requests, proxy) =
|
||||
start_recording_app_server(&app.config).await?;
|
||||
let mut tui = crate::tui::test_support::make_test_tui()?;
|
||||
@@ -208,7 +209,8 @@ fn session_lifecycle_avoids_redundant_subagent_metadata_reads() -> Result<()> {
|
||||
let mut app = make_test_app().await;
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf().abs();
|
||||
app.config.sqlite_home = codex_home.path().to_path_buf();
|
||||
app.config.sqlite =
|
||||
codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||
let root_timestamp = "2026-01-01T00-00-00";
|
||||
let root_thread_id = ThreadId::from_string(
|
||||
&create_fake_rollout(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::*;
|
||||
use codex_app_server_protocol::ImageGenerationItem;
|
||||
use codex_app_server_protocol::PluginAvailability;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
pub(super) async fn test_config() -> Config {
|
||||
@@ -15,7 +16,7 @@ pub(super) async fn test_config() -> Config {
|
||||
.await
|
||||
.expect("config");
|
||||
config.codex_home = codex_home.abs();
|
||||
config.sqlite_home = codex_home.clone();
|
||||
config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs());
|
||||
config.log_dir = codex_home.join("log");
|
||||
config.cwd = PathBuf::from(test_path_display("/tmp/project")).abs();
|
||||
config.config_layer_stack = ConfigLayerStack::default();
|
||||
|
||||
+9
-17
@@ -290,15 +290,7 @@ async fn init_state_db_for_app_server_target(
|
||||
match app_server_target {
|
||||
AppServerTarget::Embedded => state_db::try_init(config).await.map(Some).map_err(|err| {
|
||||
let database_path = codex_state::runtime_db_path_for_corruption_error(&err)
|
||||
.unwrap_or_else(|| {
|
||||
codex_state::SqliteConfig::from_sqlite_home(
|
||||
AbsolutePathBuf::resolve_path_against_base(
|
||||
&config.sqlite_home,
|
||||
&config.codex_home,
|
||||
),
|
||||
)
|
||||
.state_db_path()
|
||||
});
|
||||
.unwrap_or_else(|| config.sqlite_config().state_db_path());
|
||||
std::io::Error::other(LocalStateDbStartupError::new(
|
||||
database_path,
|
||||
format!("{err:#}"),
|
||||
@@ -3024,7 +3016,7 @@ mod tests {
|
||||
std::fs::write(&rollout_path, "")?;
|
||||
|
||||
let state_runtime = codex_state::StateRuntime::init(
|
||||
config.codex_home.to_path_buf(),
|
||||
codex_state::SqliteConfig::new_for_testing(config.codex_home.as_path().abs()),
|
||||
config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -3111,7 +3103,9 @@ mod tests {
|
||||
let mut config = build_config(&temp_dir).await?;
|
||||
let occupied_sqlite_home = temp_dir.path().join("sqlite-home");
|
||||
std::fs::write(&occupied_sqlite_home, "occupied")?;
|
||||
config.sqlite_home = occupied_sqlite_home.clone();
|
||||
let sqlite =
|
||||
codex_state::SqliteConfig::new_for_testing(occupied_sqlite_home.as_path().abs());
|
||||
config.sqlite = sqlite.clone();
|
||||
|
||||
let err =
|
||||
match init_state_db_for_app_server_target(&config, &AppServerTarget::Embedded).await {
|
||||
@@ -3125,9 +3119,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
startup_error.state_db_path(),
|
||||
codex_state::SqliteConfig::new_for_testing(config.sqlite_home.as_path().abs())
|
||||
.state_db_path()
|
||||
.as_path()
|
||||
sqlite.state_db_path().as_path()
|
||||
);
|
||||
assert!(
|
||||
startup_error
|
||||
@@ -3145,10 +3137,10 @@ mod tests {
|
||||
let mut config = build_config(&temp_dir).await?;
|
||||
let sqlite_home = temp_dir.path().join("sqlite-home");
|
||||
std::fs::create_dir_all(&sqlite_home)?;
|
||||
let logs_db_path =
|
||||
codex_state::SqliteConfig::new_for_testing(sqlite_home.as_path().abs()).logs_db_path();
|
||||
let sqlite = codex_state::SqliteConfig::new_for_testing(sqlite_home.as_path().abs());
|
||||
let logs_db_path = sqlite.logs_db_path();
|
||||
std::fs::write(&logs_db_path, "not a sqlite database")?;
|
||||
config.sqlite_home = sqlite_home;
|
||||
config.sqlite = sqlite;
|
||||
|
||||
let err =
|
||||
match init_state_db_for_app_server_target(&config, &AppServerTarget::Embedded).await {
|
||||
|
||||
@@ -243,6 +243,7 @@ async fn read_rollout_resume_state(path: &Path) -> io::Result<RolloutResumeState
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
@@ -435,10 +436,12 @@ mod tests {
|
||||
),
|
||||
],
|
||||
)?;
|
||||
let state_runtime =
|
||||
StateRuntime::init(temp_dir.path().to_path_buf(), "test-provider".to_string())
|
||||
.await
|
||||
.map_err(std::io::Error::other)?;
|
||||
let state_runtime = StateRuntime::init(
|
||||
codex_state::SqliteConfig::new_for_testing(temp_dir.path().abs()),
|
||||
"test-provider".to_string(),
|
||||
)
|
||||
.await
|
||||
.map_err(std::io::Error::other)?;
|
||||
let created_at = chrono::DateTime::parse_from_rfc3339("2025-01-05T12:00:00Z")
|
||||
.expect("timestamp should parse")
|
||||
.with_timezone(&chrono::Utc);
|
||||
|
||||
Reference in New Issue
Block a user