mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Support multiple cwd filters for thread list (#18502)
## Summary - Teach app-server `thread/list` to accept either a single `cwd` or an array of cwd filters, returning threads whose recorded session cwd matches any requested path - Add `useStateDbOnly` as an explicit opt-in fast path for callers that want to answer `thread/list` from SQLite without scanning JSONL rollout files - Preserve backwards compatibility: by default, `thread/list` still scans JSONL rollouts and repairs SQLite state - Wire the new cwd array and SQLite-only options through app-server, local/remote thread-store, rollout listing, generated TypeScript/schema fixtures, proto output, and docs ## Test Plan - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-rollout` - `cargo test -p codex-thread-store` - `cargo test -p codex-app-server thread_list` - `just fmt` - `just fix -p codex-app-server-protocol -p codex-rollout -p codex-thread-store -p codex-app-server` - `cargo build -p codex-cli --bin codex`
This commit is contained in:
@@ -175,6 +175,7 @@ LEFT JOIN jobs
|
||||
archived_only: false,
|
||||
allowed_sources,
|
||||
model_providers: None,
|
||||
cwd_filters: None,
|
||||
anchor: None,
|
||||
sort_key: SortKey::UpdatedAt,
|
||||
sort_direction: SortDirection::Desc,
|
||||
|
||||
@@ -359,6 +359,7 @@ ON CONFLICT(child_thread_id) DO NOTHING
|
||||
archived_only,
|
||||
allowed_sources,
|
||||
model_providers,
|
||||
cwd_filters: None,
|
||||
anchor: None,
|
||||
sort_key: crate::SortKey::UpdatedAt,
|
||||
sort_direction: SortDirection::Desc,
|
||||
@@ -437,6 +438,7 @@ ON CONFLICT(child_thread_id) DO NOTHING
|
||||
archived_only,
|
||||
allowed_sources,
|
||||
model_providers,
|
||||
cwd_filters: None,
|
||||
anchor,
|
||||
sort_key,
|
||||
sort_direction: SortDirection::Desc,
|
||||
@@ -1002,6 +1004,7 @@ pub struct ThreadFilterOptions<'a> {
|
||||
pub archived_only: bool,
|
||||
pub allowed_sources: &'a [String],
|
||||
pub model_providers: Option<&'a [String]>,
|
||||
pub cwd_filters: Option<&'a [PathBuf]>,
|
||||
pub anchor: Option<&'a crate::Anchor>,
|
||||
pub sort_key: SortKey,
|
||||
pub sort_direction: SortDirection,
|
||||
@@ -1016,6 +1019,7 @@ pub(super) fn push_thread_filters<'a>(
|
||||
archived_only,
|
||||
allowed_sources,
|
||||
model_providers,
|
||||
cwd_filters,
|
||||
anchor,
|
||||
sort_key,
|
||||
sort_direction,
|
||||
@@ -1046,6 +1050,20 @@ pub(super) fn push_thread_filters<'a>(
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
}
|
||||
match cwd_filters {
|
||||
Some([]) => {
|
||||
builder.push(" AND 1 = 0");
|
||||
}
|
||||
Some(cwd_filters) => {
|
||||
builder.push(" AND threads.cwd IN (");
|
||||
let mut separated = builder.separated(", ");
|
||||
for cwd in cwd_filters {
|
||||
separated.push_bind(cwd.display().to_string());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
if let Some(search_term) = search_term {
|
||||
builder.push(" AND instr(threads.title, ");
|
||||
builder.push_bind(search_term);
|
||||
@@ -1188,6 +1206,7 @@ mod tests {
|
||||
archived_only: false,
|
||||
allowed_sources: &[],
|
||||
model_providers: Some(&model_providers),
|
||||
cwd_filters: None,
|
||||
anchor: Some(&anchor),
|
||||
sort_key: SortKey::UpdatedAt,
|
||||
sort_direction: SortDirection::Asc,
|
||||
@@ -1214,6 +1233,7 @@ mod tests {
|
||||
archived_only: false,
|
||||
allowed_sources: &[],
|
||||
model_providers: Some(&model_providers),
|
||||
cwd_filters: None,
|
||||
anchor: page.next_anchor.as_ref(),
|
||||
sort_key: SortKey::UpdatedAt,
|
||||
sort_direction: SortDirection::Asc,
|
||||
@@ -1228,6 +1248,77 @@ mod tests {
|
||||
assert_eq!(page.next_anchor, None);
|
||||
}
|
||||
|
||||
#[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 first_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000101").expect("valid thread id");
|
||||
let second_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000102").expect("valid thread id");
|
||||
let other_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000103").expect("valid thread id");
|
||||
let first_cwd = codex_home.join("first");
|
||||
let second_cwd = codex_home.join("second");
|
||||
let other_cwd = codex_home.join("other");
|
||||
|
||||
for (thread_id, cwd, updated_at) in [
|
||||
(first_id, first_cwd.clone(), 1_700_000_100),
|
||||
(second_id, second_cwd.clone(), 1_700_000_300),
|
||||
(other_id, other_cwd, 1_700_000_500),
|
||||
] {
|
||||
let mut metadata = test_thread_metadata(&codex_home, thread_id, cwd);
|
||||
metadata.updated_at =
|
||||
DateTime::<Utc>::from_timestamp(updated_at, 0).expect("valid timestamp");
|
||||
runtime
|
||||
.upsert_thread(&metadata)
|
||||
.await
|
||||
.expect("thread insert should succeed");
|
||||
}
|
||||
|
||||
let cwd_filters = vec![first_cwd, second_cwd];
|
||||
let page = runtime
|
||||
.list_threads(
|
||||
/*page_size*/ 10,
|
||||
ThreadFilterOptions {
|
||||
archived_only: false,
|
||||
allowed_sources: &[],
|
||||
model_providers: None,
|
||||
cwd_filters: Some(cwd_filters.as_slice()),
|
||||
anchor: None,
|
||||
sort_key: SortKey::UpdatedAt,
|
||||
sort_direction: SortDirection::Desc,
|
||||
search_term: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("list should succeed");
|
||||
|
||||
let ids = page.items.iter().map(|item| item.id).collect::<Vec<_>>();
|
||||
assert_eq!(ids, vec![second_id, first_id]);
|
||||
|
||||
let page = runtime
|
||||
.list_threads(
|
||||
/*page_size*/ 10,
|
||||
ThreadFilterOptions {
|
||||
archived_only: false,
|
||||
allowed_sources: &[],
|
||||
model_providers: None,
|
||||
cwd_filters: Some(&[]),
|
||||
anchor: None,
|
||||
sort_key: SortKey::UpdatedAt,
|
||||
sort_direction: SortDirection::Desc,
|
||||
search_term: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("list with empty cwd filters should succeed");
|
||||
|
||||
assert_eq!(page.items, Vec::new());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn apply_rollout_items_restores_memory_mode_from_session_meta() {
|
||||
let codex_home = unique_temp_dir();
|
||||
|
||||
Reference in New Issue
Block a user