From e1f4aa728cee422d2838ddd1178df56eccd63f54 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Mon, 4 May 2026 14:18:48 -0700 Subject: [PATCH] Add exact search mode for session lookup --- .../schema/json/ClientRequest.json | 18 +++++ .../codex_app_server_protocol.schemas.json | 18 +++++ .../codex_app_server_protocol.v2.schemas.json | 18 +++++ .../schema/json/v2/ThreadListParams.json | 18 +++++ .../schema/typescript/v2/ThreadListParams.ts | 7 +- .../typescript/v2/ThreadListSearchMode.ts | 5 ++ .../schema/typescript/v2/index.ts | 1 + .../app-server-protocol/src/protocol/v2.rs | 13 ++++ codex-rs/app-server-test-client/src/lib.rs | 1 + codex-rs/app-server/README.md | 1 + codex-rs/app-server/src/request_processors.rs | 1 + .../request_processors/thread_processor.rs | 10 +++ .../tests/suite/v2/external_agent_config.rs | 6 ++ .../tests/suite/v2/remote_thread_store.rs | 1 + .../app-server/tests/suite/v2/thread_fork.rs | 1 + .../app-server/tests/suite/v2/thread_list.rs | 47 ++++++++++++ .../app-server/tests/suite/v2/thread_read.rs | 2 + codex-rs/debug-client/src/client.rs | 1 + codex-rs/exec/src/lib.rs | 2 + codex-rs/tui/src/lib.rs | 73 ++++++++++++------- codex-rs/tui/src/resume_picker.rs | 1 + 21 files changed, 216 insertions(+), 29 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadListSearchMode.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index fdd4c155b0..205cb5afc0 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -3628,6 +3628,17 @@ "null" ] }, + "searchMode": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadListSearchMode" + }, + { + "type": "null" + } + ], + "description": "Optional search matching mode. Defaults to substring matching." + }, "searchTerm": { "description": "Optional substring filter for the extracted thread title.", "type": [ @@ -3674,6 +3685,13 @@ }, "type": "object" }, + "ThreadListSearchMode": { + "enum": [ + "contains", + "exact" + ], + "type": "string" + }, "ThreadLoadedListParams": { "properties": { "cursor": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 6c2ad69e1b..11b5a79161 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -16123,6 +16123,17 @@ "null" ] }, + "searchMode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadListSearchMode" + }, + { + "type": "null" + } + ], + "description": "Optional search matching mode. Defaults to substring matching." + }, "searchTerm": { "description": "Optional substring filter for the extracted thread title.", "type": [ @@ -16200,6 +16211,13 @@ "title": "ThreadListResponse", "type": "object" }, + "ThreadListSearchMode": { + "enum": [ + "contains", + "exact" + ], + "type": "string" + }, "ThreadLoadedListParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 150b221adf..3f99a2c962 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -14009,6 +14009,17 @@ "null" ] }, + "searchMode": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadListSearchMode" + }, + { + "type": "null" + } + ], + "description": "Optional search matching mode. Defaults to substring matching." + }, "searchTerm": { "description": "Optional substring filter for the extracted thread title.", "type": [ @@ -14086,6 +14097,13 @@ "title": "ThreadListResponse", "type": "object" }, + "ThreadListSearchMode": { + "enum": [ + "contains", + "exact" + ], + "type": "string" + }, "ThreadLoadedListParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListParams.json index 789d9b61fc..8611651f61 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListParams.json @@ -21,6 +21,13 @@ } ] }, + "ThreadListSearchMode": { + "enum": [ + "contains", + "exact" + ], + "type": "string" + }, "ThreadSortKey": { "enum": [ "created_at", @@ -89,6 +96,17 @@ "null" ] }, + "searchMode": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadListSearchMode" + }, + { + "type": "null" + } + ], + "description": "Optional search matching mode. Defaults to substring matching." + }, "searchTerm": { "description": "Optional substring filter for the extracted thread title.", "type": [ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts index ce5b6a79ba..74494e0120 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListParams.ts @@ -2,6 +2,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { SortDirection } from "./SortDirection"; +import type { ThreadListSearchMode } from "./ThreadListSearchMode"; import type { ThreadSortKey } from "./ThreadSortKey"; import type { ThreadSourceKind } from "./ThreadSourceKind"; @@ -51,4 +52,8 @@ useStateDbOnly?: boolean, /** * Optional substring filter for the extracted thread title. */ -searchTerm?: string | null, }; +searchTerm?: string | null, +/** + * Optional search matching mode. Defaults to substring matching. + */ +searchMode?: ThreadListSearchMode | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListSearchMode.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListSearchMode.ts new file mode 100644 index 0000000000..814caf5805 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadListSearchMode.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadListSearchMode = "contains" | "exact"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index d369ba3423..1f690c2115 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -363,6 +363,7 @@ export type { ThreadInjectItemsResponse } from "./ThreadInjectItemsResponse"; export type { ThreadItem } from "./ThreadItem"; export type { ThreadListParams } from "./ThreadListParams"; export type { ThreadListResponse } from "./ThreadListResponse"; +export type { ThreadListSearchMode } from "./ThreadListSearchMode"; export type { ThreadLoadedListParams } from "./ThreadLoadedListParams"; export type { ThreadLoadedListResponse } from "./ThreadLoadedListResponse"; export type { ThreadMetadataGitInfoUpdateParams } from "./ThreadMetadataGitInfoUpdateParams"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index aea80029a8..ac74169b9e 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -4325,6 +4325,9 @@ pub struct ThreadListParams { /// Optional substring filter for the extracted thread title. #[ts(optional = nullable)] pub search_term: Option, + /// Optional search matching mode. Defaults to substring matching. + #[ts(optional = nullable)] + pub search_mode: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)] @@ -4334,6 +4337,14 @@ pub enum ThreadListCwdFilter { Many(Vec), } +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(rename_all = "camelCase", export_to = "v2/")] +pub enum ThreadListSearchMode { + Contains, + Exact, +} + #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase", export_to = "v2/")] @@ -8234,10 +8245,12 @@ mod tests { fn thread_list_params_accepts_state_db_only_flag() { let params = serde_json::from_value::(json!({ "useStateDbOnly": true, + "searchMode": "exact", })) .expect("state db only flag should deserialize"); assert!(params.use_state_db_only); + assert_eq!(params.search_mode, Some(ThreadListSearchMode::Exact)); } #[test] diff --git a/codex-rs/app-server-test-client/src/lib.rs b/codex-rs/app-server-test-client/src/lib.rs index edea431c61..a615e22eb3 100644 --- a/codex-rs/app-server-test-client/src/lib.rs +++ b/codex-rs/app-server-test-client/src/lib.rs @@ -1127,6 +1127,7 @@ async fn thread_list(endpoint: &Endpoint, config_overrides: &[String], limit: u3 cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, })?; println!("< thread/list response: {response:?}"); diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index df41d859cd..d3d139b25f 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -321,6 +321,7 @@ Like `thread/resume`, experimental clients can pass `excludeTurns: true` to `thr - `cwd` — restrict results to threads whose session cwd exactly matches this path, or one of these paths when an array is provided. Relative paths are resolved against the app-server process cwd before matching. - `useStateDbOnly` — when `true`, return from the state DB without scanning JSONL rollouts to repair metadata. Omit or pass `false` to preserve the default scan-and-repair behavior. - `searchTerm` — restrict results to threads whose extracted title contains this substring (case-sensitive). +- `searchMode` — optional search matching mode for `searchTerm`: `contains` (default) or `exact`. - Responses include `nextCursor` to continue in the same direction and `backwardsCursor` to pass as `cursor` when reversing `sortDirection`. - Responses include `agentNickname` and `agentRole` for AgentControl-spawned thread sub-agents when available. diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index f59ea14402..4909b8b480 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -178,6 +178,7 @@ use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::ThreadListCwdFilter; use codex_app_server_protocol::ThreadListParams; use codex_app_server_protocol::ThreadListResponse; +use codex_app_server_protocol::ThreadListSearchMode; use codex_app_server_protocol::ThreadLoadedListParams; use codex_app_server_protocol::ThreadLoadedListResponse; use codex_app_server_protocol::ThreadMemoryModeSetParams; diff --git a/codex-rs/app-server/src/request_processors/thread_processor.rs b/codex-rs/app-server/src/request_processors/thread_processor.rs index 40be4f34ef..8157d60faa 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor.rs @@ -9,6 +9,7 @@ struct ThreadListFilters { archived: bool, cwd_filters: Option>, search_term: Option, + search_mode: Option, use_state_db_only: bool, } @@ -1826,6 +1827,7 @@ impl ThreadRequestProcessor { cwd, use_state_db_only, search_term, + search_mode, } = params; let cwd_filters = normalize_thread_list_cwd_filters(cwd)?; @@ -1850,6 +1852,7 @@ impl ThreadRequestProcessor { archived: archived.unwrap_or(false), cwd_filters, search_term, + search_mode, use_state_db_only, }, ) @@ -3228,6 +3231,7 @@ impl ThreadRequestProcessor { archived, cwd_filters, search_term, + search_mode, use_state_db_only, } = filters; let mut cursor_obj = cursor; @@ -3287,6 +3291,12 @@ impl ThreadRequestProcessor { path_utils::paths_match_after_normalization(&it.cwd, expected_cwd) }) }) + && match (search_mode, search_term.as_deref()) { + (Some(ThreadListSearchMode::Exact), Some(search_term)) => { + it.name.as_deref() == Some(search_term) + } + _ => true, + } { filtered.push(it); if filtered.len() >= remaining { diff --git a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs index e63aad9da4..a0b4054a06 100644 --- a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs +++ b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs @@ -331,6 +331,7 @@ async fn external_agent_config_import_creates_session_rollouts() -> Result<()> { cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let response: JSONRPCResponse = timeout( @@ -500,6 +501,7 @@ async fn external_agent_config_import_accepts_detected_session_payload_after_res cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let response: JSONRPCResponse = timeout( @@ -586,6 +588,7 @@ async fn external_agent_config_import_skips_already_imported_session_versions() cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let response: JSONRPCResponse = timeout( @@ -724,6 +727,7 @@ async fn external_agent_config_import_returns_before_background_session_import_f cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let response: JSONRPCResponse = timeout( @@ -808,6 +812,7 @@ async fn external_agent_config_import_rejects_undetected_session_paths() -> Resu cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let response: JSONRPCResponse = timeout( @@ -931,6 +936,7 @@ async fn external_agent_config_import_compacts_huge_session_before_first_follow_ cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let response: JSONRPCResponse = timeout( diff --git a/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs b/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs index b04eb12a45..6a55567d14 100644 --- a/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs +++ b/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs @@ -153,6 +153,7 @@ async fn thread_start_with_non_local_thread_store_does_not_create_local_persiste cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }, }) .await? diff --git a/codex-rs/app-server/tests/suite/v2/thread_fork.rs b/codex-rs/app-server/tests/suite/v2/thread_fork.rs index fd773f2e30..2c35222783 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_fork.rs @@ -690,6 +690,7 @@ async fn thread_fork_ephemeral_remains_pathless_and_omits_listing() -> Result<() cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let list_resp: JSONRPCResponse = timeout( diff --git a/codex-rs/app-server/tests/suite/v2/thread_list.rs b/codex-rs/app-server/tests/suite/v2/thread_list.rs index ebaba81852..a5a35972ae 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_list.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_list.rs @@ -17,6 +17,7 @@ use codex_app_server_protocol::SessionSource; use codex_app_server_protocol::SortDirection; use codex_app_server_protocol::ThreadListCwdFilter; use codex_app_server_protocol::ThreadListResponse; +use codex_app_server_protocol::ThreadListSearchMode; use codex_app_server_protocol::ThreadSortKey; use codex_app_server_protocol::ThreadSourceKind; use codex_app_server_protocol::ThreadStartParams; @@ -93,6 +94,7 @@ async fn list_threads_with_sort( cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let resp: JSONRPCResponse = timeout( @@ -533,6 +535,7 @@ async fn thread_list_respects_cwd_filters() -> Result<()> { ])), use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let resp: JSONRPCResponse = timeout( @@ -628,6 +631,13 @@ sqlite = true ) .await?; assert_eq!(repaired_page.items.len(), 3); + let exact_thread_id = ThreadId::from_string(&newer_match)?; + let mut exact_metadata = state_db + .get_thread(exact_thread_id) + .await? + .expect("exact match metadata should be present"); + exact_metadata.title = "needle exact".to_string(); + state_db.upsert_thread(&exact_metadata).await?; let mut mcp = init_mcp(codex_home.path()).await?; let request_id = mcp @@ -642,6 +652,7 @@ sqlite = true cwd: None, use_state_db_only: false, search_term: Some("needle".to_string()), + search_mode: None, }) .await?; let resp: JSONRPCResponse = timeout( @@ -657,6 +668,36 @@ sqlite = true let ids: Vec<_> = data.iter().map(|thread| thread.id.as_str()).collect(); assert_eq!(ids, vec![newer_match, older_match]); + let exact_request_id = mcp + .send_thread_list_request(codex_app_server_protocol::ThreadListParams { + cursor: None, + limit: Some(10), + sort_key: None, + sort_direction: None, + model_providers: Some(vec!["mock_provider".to_string()]), + source_kinds: None, + archived: None, + cwd: None, + use_state_db_only: false, + search_term: Some("needle exact".to_string()), + search_mode: Some(ThreadListSearchMode::Exact), + }) + .await?; + let exact_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(exact_request_id)), + ) + .await??; + let ThreadListResponse { + data: exact_data, + next_cursor: exact_next_cursor, + .. + } = to_response::(exact_resp)?; + + assert_eq!(exact_next_cursor, None); + let exact_ids: Vec<_> = exact_data.iter().map(|thread| thread.id.as_str()).collect(); + assert_eq!(exact_ids, vec![newer_match]); + Ok(()) } @@ -703,6 +744,7 @@ sqlite = true cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let resp: JSONRPCResponse = timeout( @@ -741,6 +783,7 @@ sqlite = true )), use_state_db_only: true, search_term: None, + search_mode: None, }) .await?; let resp: JSONRPCResponse = timeout( @@ -770,6 +813,7 @@ sqlite = true )), use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let resp: JSONRPCResponse = timeout( @@ -1464,6 +1508,7 @@ async fn thread_list_backwards_cursor_can_seed_forward_delta_sync() -> Result<() cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let resp: JSONRPCResponse = timeout( @@ -1506,6 +1551,7 @@ async fn thread_list_backwards_cursor_can_seed_forward_delta_sync() -> Result<() cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let resp: JSONRPCResponse = timeout( @@ -1744,6 +1790,7 @@ async fn thread_list_invalid_cursor_returns_error() -> Result<()> { cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let error: JSONRPCError = timeout( diff --git a/codex-rs/app-server/tests/suite/v2/thread_read.rs b/codex-rs/app-server/tests/suite/v2/thread_read.rs index 8c46a5ad95..7f5e89f0ce 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_read.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_read.rs @@ -483,6 +483,7 @@ async fn thread_list_includes_store_thread_without_rollout_path() -> Result<()> cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }, }) .await? @@ -804,6 +805,7 @@ async fn thread_name_set_is_reflected_in_read_list_and_resume() -> Result<()> { cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }) .await?; let list_resp: JSONRPCResponse = timeout( diff --git a/codex-rs/debug-client/src/client.rs b/codex-rs/debug-client/src/client.rs index 2edabfac00..db42554868 100644 --- a/codex-rs/debug-client/src/client.rs +++ b/codex-rs/debug-client/src/client.rs @@ -185,6 +185,7 @@ impl AppServerClient { cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }, }; self.send(&request)?; diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index d61346f1d0..42e99c1f46 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -1349,6 +1349,7 @@ async fn resolve_resume_thread_id( cwd: None, use_state_db_only: false, search_term: None, + search_mode: None, }, }, "thread/list", @@ -1414,6 +1415,7 @@ async fn resolve_resume_thread_id( cwd: None, use_state_db_only: false, search_term: Some(session_id.to_string()), + search_mode: None, }, }, "thread/list", diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 85aeca9bc5..49eebbfd8e 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -32,6 +32,7 @@ use codex_app_server_protocol::ConfigWarningNotification; use codex_app_server_protocol::Thread as AppServerThread; use codex_app_server_protocol::ThreadListCwdFilter; use codex_app_server_protocol::ThreadListParams; +use codex_app_server_protocol::ThreadListSearchMode; use codex_app_server_protocol::ThreadSortKey as AppServerThreadSortKey; use codex_app_server_protocol::ThreadSourceKind; use codex_cloud_requirements::cloud_requirements_loader_for_storage; @@ -546,34 +547,26 @@ async fn lookup_session_target_by_name_with_app_server( app_server: &mut AppServerSession, name: &str, ) -> color_eyre::Result> { - let mut cursor = None; - loop { - let response = app_server - .thread_list(ThreadListParams { - cursor: cursor.clone(), - limit: Some(100), - sort_key: Some(AppServerThreadSortKey::UpdatedAt), - sort_direction: None, - model_providers: None, - source_kinds: Some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode]), - archived: Some(false), - cwd: None, - use_state_db_only: false, - search_term: Some(name.to_string()), - }) - .await?; - if let Some(thread) = response - .data - .into_iter() - .find(|thread| thread.name.as_deref() == Some(name)) - { - return Ok(session_target_from_app_server_thread(thread)); - } - if response.next_cursor.is_none() { - return Ok(None); - } - cursor = response.next_cursor; - } + let response = app_server + .thread_list(ThreadListParams { + cursor: None, + limit: Some(100), + sort_key: Some(AppServerThreadSortKey::UpdatedAt), + sort_direction: None, + model_providers: None, + source_kinds: Some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode]), + archived: Some(false), + cwd: None, + use_state_db_only: false, + search_term: Some(name.to_string()), + search_mode: Some(ThreadListSearchMode::Exact), + }) + .await?; + Ok(response + .data + .into_iter() + .next() + .and_then(session_target_from_app_server_thread)) } async fn lookup_session_target_with_app_server( @@ -653,6 +646,7 @@ fn latest_session_lookup_params( cwd: cwd_filter.map(|cwd| ThreadListCwdFilter::One(cwd.to_string_lossy().to_string())), use_state_db_only: false, search_term: None, + search_mode: None, } } @@ -2047,6 +2041,29 @@ mod tests { .upsert_thread(&metadata) .await .map_err(std::io::Error::other)?; + let newer_thread_id = ThreadId::new(); + let newer_rollout_path = temp_dir.path().join("sessions/2025/02/01").join(format!( + "rollout-2025-02-01T11-00-00-{newer_thread_id}.jsonl" + )); + std::fs::write(&newer_rollout_path, "")?; + let newer_created_at = chrono::DateTime::parse_from_rfc3339("2025-02-01T11:00:00Z") + .expect("timestamp should parse") + .with_timezone(&chrono::Utc); + let mut newer_builder = codex_state::ThreadMetadataBuilder::new( + newer_thread_id, + newer_rollout_path, + newer_created_at, + serde_json::from_value(serde_json::json!("cli")) + .expect("cli session source should deserialize"), + ); + newer_builder.cwd = temp_dir.path().join("other-project"); + let mut newer_metadata = newer_builder.build(config.model_provider_id.as_str()); + newer_metadata.title = "saved-session-copy".to_string(); + newer_metadata.first_user_message = Some("other preview text".to_string()); + state_runtime + .upsert_thread(&newer_metadata) + .await + .map_err(std::io::Error::other)?; let mut app_server = AppServerSession::new(codex_app_server_client::AppServerClient::InProcess( diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index dc148f0059..b158ee5597 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -1017,6 +1017,7 @@ fn thread_list_params( cwd: cwd_filter.map(|cwd| ThreadListCwdFilter::One(cwd.to_string_lossy().into_owned())), use_state_db_only: false, search_term: None, + search_mode: None, } }