tui: include exec sessions in resume list (#24503)

## Why

Fixes #24502.

`codex resume --include-non-interactive` should include sessions created
by `codex exec`, but the TUI was sending no `sourceKinds` filter to
`thread/list` for that mode. `thread/list` treats omitted or empty
`sourceKinds` as interactive-only (`cli`, `vscode`), so exec sessions
were still filtered out.

## What Changed

- Added a shared TUI `resume_source_kinds` helper so both resume lookup
paths always pass explicit `sourceKinds` to `thread/list`.
- Kept the default resume behavior scoped to `cli` and `vscode`.
- Made `--include-non-interactive` include `exec` and `appServer`
sessions, while continuing to exclude subagent and unknown sources.

## Verification

Added focused coverage for both affected TUI request builders:

- `latest_session_lookup_params_can_include_non_interactive_sources`
- `remote_thread_list_params_can_include_non_interactive_sources`
This commit is contained in:
Eric Traut
2026-05-26 08:27:10 -07:00
committed by GitHub
Unverified
parent ff7513cd83
commit b84c5898df
2 changed files with 39 additions and 6 deletions
+35 -2
View File
@@ -651,6 +651,17 @@ fn session_target_from_app_server_thread(
}
}
pub(crate) fn resume_source_kinds(include_non_interactive: bool) -> Vec<ThreadSourceKind> {
let mut source_kinds = vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode];
if include_non_interactive {
// `thread/list` treats omitted and empty `sourceKinds` as interactive-only,
// so include-non-interactive has to name the user-resumable non-interactive
// sources explicitly until the API grows an unfiltered request.
source_kinds.extend([ThreadSourceKind::Exec, ThreadSourceKind::AppServer]);
}
source_kinds
}
async fn lookup_session_target_by_name_with_app_server(
app_server: &mut AppServerSession,
name: &str,
@@ -756,8 +767,7 @@ fn latest_session_lookup_params(
} else {
Some(vec![config.model_provider_id.clone()])
},
source_kinds: (!include_non_interactive)
.then_some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode]),
source_kinds: Some(resume_source_kinds(include_non_interactive)),
archived: Some(false),
cwd: cwd_filter.map(|cwd| ThreadListCwdFilter::One(cwd.to_string_lossy().to_string())),
use_state_db_only: false,
@@ -2295,6 +2305,29 @@ mod tests {
Ok(())
}
#[tokio::test]
async fn latest_session_lookup_params_can_include_non_interactive_sources()
-> std::io::Result<()> {
let temp_dir = TempDir::new()?;
let config = build_config(&temp_dir).await?;
let params = latest_session_lookup_params(
/*uses_remote_workspace*/ true, &config, /*cwd_filter*/ None,
/*include_non_interactive*/ true,
);
assert_eq!(
params.source_kinds,
Some(vec![
ThreadSourceKind::Cli,
ThreadSourceKind::VsCode,
ThreadSourceKind::Exec,
ThreadSourceKind::AppServer,
])
);
Ok(())
}
#[tokio::test]
async fn latest_session_lookup_params_keep_explicit_cwd_filter_for_remote_sessions()
-> std::io::Result<()> {
+4 -4
View File
@@ -36,7 +36,6 @@ use codex_app_server_protocol::ThreadItem;
use codex_app_server_protocol::ThreadListCwdFilter;
use codex_app_server_protocol::ThreadListParams;
use codex_app_server_protocol::ThreadSortKey;
use codex_app_server_protocol::ThreadSourceKind;
use codex_config::types::SessionPickerViewMode;
use codex_protocol::ThreadId;
use codex_utils_path as path_utils;
@@ -1829,8 +1828,7 @@ fn thread_list_params(
ProviderFilter::Any => None,
ProviderFilter::MatchDefault(default_provider) => Some(vec![default_provider]),
},
source_kinds: (!include_non_interactive)
.then_some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode]),
source_kinds: Some(crate::resume_source_kinds(include_non_interactive)),
archived: Some(false),
cwd: cwd_filter.map(|cwd| ThreadListCwdFilter::One(cwd.to_string_lossy().into_owned())),
use_state_db_only: false,
@@ -3207,6 +3205,7 @@ fn render_empty_state_line(state: &PickerState) -> Line<'static> {
mod tests {
use super::*;
use chrono::Duration;
use codex_app_server_protocol::ThreadSourceKind;
use codex_config::CONFIG_TOML_FILE;
use codex_protocol::ThreadId;
use codex_utils_absolute_path::test_support::PathBufExt;
@@ -3572,7 +3571,8 @@ mod tests {
assert_eq!(params.cursor, Some(String::from("cursor-1")));
assert_eq!(params.model_providers, None);
assert_eq!(params.source_kinds, None);
let source_kinds = crate::resume_source_kinds(/*include_non_interactive*/ true);
assert_eq!(params.source_kinds, Some(source_kinds));
}
#[test]