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:
@@ -103,8 +103,10 @@ mod tests {
|
||||
sort_direction: crate::SortDirection::Desc,
|
||||
allowed_sources: Vec::new(),
|
||||
model_providers: None,
|
||||
cwd_filters: None,
|
||||
archived: true,
|
||||
search_term: None,
|
||||
use_state_db_only: false,
|
||||
})
|
||||
.await
|
||||
.expect("archived listing");
|
||||
|
||||
@@ -68,7 +68,35 @@ async fn list_rollout_threads(
|
||||
sort_key: codex_rollout::ThreadSortKey,
|
||||
sort_direction: codex_rollout::SortDirection,
|
||||
) -> ThreadStoreResult<codex_rollout::ThreadsPage> {
|
||||
let page = if params.archived {
|
||||
let page = if params.use_state_db_only && params.archived {
|
||||
RolloutRecorder::list_archived_threads_from_state_db(
|
||||
config,
|
||||
params.page_size,
|
||||
cursor,
|
||||
sort_key,
|
||||
sort_direction,
|
||||
params.allowed_sources.as_slice(),
|
||||
params.model_providers.as_deref(),
|
||||
params.cwd_filters.as_deref(),
|
||||
config.model_provider_id.as_str(),
|
||||
params.search_term.as_deref(),
|
||||
)
|
||||
.await
|
||||
} else if params.use_state_db_only {
|
||||
RolloutRecorder::list_threads_from_state_db(
|
||||
config,
|
||||
params.page_size,
|
||||
cursor,
|
||||
sort_key,
|
||||
sort_direction,
|
||||
params.allowed_sources.as_slice(),
|
||||
params.model_providers.as_deref(),
|
||||
params.cwd_filters.as_deref(),
|
||||
config.model_provider_id.as_str(),
|
||||
params.search_term.as_deref(),
|
||||
)
|
||||
.await
|
||||
} else if params.archived {
|
||||
RolloutRecorder::list_archived_threads(
|
||||
config,
|
||||
params.page_size,
|
||||
@@ -77,6 +105,7 @@ async fn list_rollout_threads(
|
||||
sort_direction,
|
||||
params.allowed_sources.as_slice(),
|
||||
params.model_providers.as_deref(),
|
||||
params.cwd_filters.as_deref(),
|
||||
config.model_provider_id.as_str(),
|
||||
params.search_term.as_deref(),
|
||||
)
|
||||
@@ -90,6 +119,7 @@ async fn list_rollout_threads(
|
||||
sort_direction,
|
||||
params.allowed_sources.as_slice(),
|
||||
params.model_providers.as_deref(),
|
||||
params.cwd_filters.as_deref(),
|
||||
config.model_provider_id.as_str(),
|
||||
params.search_term.as_deref(),
|
||||
)
|
||||
@@ -140,8 +170,10 @@ mod tests {
|
||||
sort_direction: SortDirection::Desc,
|
||||
allowed_sources: Vec::new(),
|
||||
model_providers: None,
|
||||
cwd_filters: None,
|
||||
archived: false,
|
||||
search_term: None,
|
||||
use_state_db_only: false,
|
||||
})
|
||||
.await
|
||||
.expect("thread listing");
|
||||
@@ -196,8 +228,10 @@ mod tests {
|
||||
sort_direction: SortDirection::Desc,
|
||||
allowed_sources: Vec::new(),
|
||||
model_providers: None,
|
||||
cwd_filters: None,
|
||||
archived: false,
|
||||
search_term: Some("needle".to_string()),
|
||||
use_state_db_only: true,
|
||||
})
|
||||
.await
|
||||
.expect("thread listing");
|
||||
@@ -233,8 +267,10 @@ mod tests {
|
||||
sort_direction: SortDirection::Desc,
|
||||
allowed_sources: Vec::new(),
|
||||
model_providers: None,
|
||||
cwd_filters: None,
|
||||
archived: false,
|
||||
search_term: None,
|
||||
use_state_db_only: false,
|
||||
})
|
||||
.await
|
||||
.expect("active listing");
|
||||
@@ -246,8 +282,10 @@ mod tests {
|
||||
sort_direction: SortDirection::Desc,
|
||||
allowed_sources: Vec::new(),
|
||||
model_providers: None,
|
||||
cwd_filters: None,
|
||||
archived: true,
|
||||
search_term: None,
|
||||
use_state_db_only: false,
|
||||
})
|
||||
.await
|
||||
.expect("archived listing");
|
||||
@@ -295,8 +333,10 @@ mod tests {
|
||||
sort_direction: SortDirection::Desc,
|
||||
allowed_sources: vec![SessionSource::Cli],
|
||||
model_providers: Some(vec!["test-provider".to_string()]),
|
||||
cwd_filters: None,
|
||||
archived: false,
|
||||
search_term: None,
|
||||
use_state_db_only: false,
|
||||
})
|
||||
.await
|
||||
.expect("thread listing");
|
||||
@@ -329,8 +369,10 @@ mod tests {
|
||||
sort_direction: SortDirection::Desc,
|
||||
allowed_sources: Vec::new(),
|
||||
model_providers: None,
|
||||
cwd_filters: None,
|
||||
archived: false,
|
||||
search_term: None,
|
||||
use_state_db_only: false,
|
||||
})
|
||||
.await
|
||||
.expect_err("invalid cursor should fail");
|
||||
|
||||
@@ -30,8 +30,15 @@ pub(super) async fn list_threads(
|
||||
model_provider_filter: params
|
||||
.model_providers
|
||||
.map(|values| proto::ModelProviderFilter { values }),
|
||||
cwd_filter: params.cwd_filters.map(|values| proto::CwdFilter {
|
||||
values: values
|
||||
.into_iter()
|
||||
.map(|cwd| cwd.display().to_string())
|
||||
.collect(),
|
||||
}),
|
||||
archived: params.archived,
|
||||
search_term: params.search_term,
|
||||
use_state_db_only: params.use_state_db_only,
|
||||
};
|
||||
|
||||
let response = store
|
||||
@@ -91,12 +98,19 @@ mod tests {
|
||||
);
|
||||
assert_eq!(request.archived, true);
|
||||
assert_eq!(request.search_term.as_deref(), Some("needle"));
|
||||
assert!(request.use_state_db_only);
|
||||
assert_eq!(
|
||||
request.model_provider_filter,
|
||||
Some(proto::ModelProviderFilter {
|
||||
values: vec!["openai".to_string()],
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
request.cwd_filter,
|
||||
Some(proto::CwdFilter {
|
||||
values: vec!["/workspace".to_string()],
|
||||
})
|
||||
);
|
||||
assert_eq!(request.allowed_sources.len(), 1);
|
||||
assert_eq!(
|
||||
proto::SessionSourceKind::try_from(request.allowed_sources[0].kind),
|
||||
@@ -164,8 +178,10 @@ mod tests {
|
||||
sort_direction: crate::SortDirection::Desc,
|
||||
allowed_sources: vec![SessionSource::Cli],
|
||||
model_providers: Some(vec!["openai".to_string()]),
|
||||
cwd_filters: Some(vec![PathBuf::from("/workspace")]),
|
||||
archived: true,
|
||||
search_term: Some("needle".to_string()),
|
||||
use_state_db_only: true,
|
||||
})
|
||||
.await
|
||||
.expect("list threads");
|
||||
|
||||
@@ -14,12 +14,18 @@ message ListThreadsRequest {
|
||||
optional ModelProviderFilter model_provider_filter = 5;
|
||||
bool archived = 6;
|
||||
optional string search_term = 7;
|
||||
optional CwdFilter cwd_filter = 8;
|
||||
bool use_state_db_only = 9;
|
||||
}
|
||||
|
||||
message ModelProviderFilter {
|
||||
repeated string values = 1;
|
||||
}
|
||||
|
||||
message CwdFilter {
|
||||
repeated string values = 1;
|
||||
}
|
||||
|
||||
enum ThreadSortKey {
|
||||
THREAD_SORT_KEY_CREATED_AT = 0;
|
||||
THREAD_SORT_KEY_UPDATED_AT = 1;
|
||||
|
||||
@@ -17,12 +17,21 @@ pub struct ListThreadsRequest {
|
||||
pub archived: bool,
|
||||
#[prost(string, optional, tag = "7")]
|
||||
pub search_term: ::core::option::Option<::prost::alloc::string::String>,
|
||||
#[prost(message, optional, tag = "8")]
|
||||
pub cwd_filter: ::core::option::Option<CwdFilter>,
|
||||
#[prost(bool, tag = "9")]
|
||||
pub use_state_db_only: bool,
|
||||
}
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct ModelProviderFilter {
|
||||
#[prost(string, repeated, tag = "1")]
|
||||
pub values: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct CwdFilter {
|
||||
#[prost(string, repeated, tag = "1")]
|
||||
pub values: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ListThreadsResponse {
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
|
||||
@@ -128,10 +128,15 @@ pub struct ListThreadsParams {
|
||||
/// Optional model provider filter. `None` means implementation default, while an empty vector
|
||||
/// means all providers.
|
||||
pub model_providers: Option<Vec<String>>,
|
||||
/// Optional cwd filters. `None` means all working directories, while an empty vector matches no
|
||||
/// threads.
|
||||
pub cwd_filters: Option<Vec<PathBuf>>,
|
||||
/// Whether archived threads should be listed instead of active threads.
|
||||
pub archived: bool,
|
||||
/// Optional substring/full-text search term for thread title/preview.
|
||||
pub search_term: Option<String>,
|
||||
/// Return directly from the state DB without scanning JSONL rollouts to repair metadata.
|
||||
pub use_state_db_only: bool,
|
||||
}
|
||||
|
||||
/// A page of stored thread records.
|
||||
|
||||
Reference in New Issue
Block a user