Add remote --cd forwarding for app-server sessions (#16700)

Addresses #16124

Problem: `codex --remote --cd <path>` canonicalized the path locally and
then omitted it from remote thread lifecycle requests, so remote-only
working directories failed or were ignored.

Solution: Keep remote startup on the local cwd, forward explicit `--cd`
values verbatim to `thread/start`, `thread/resume`, and `thread/fork`,
and cover the behavior with `codex-tui` tests.

Testing: I manually tested `--remote --cd` with both absolute and
relative paths and validated correct behavior.


---

Update based on code review feedback:

Problem: Remote `--cd` was forwarded to `thread/resume` and
`thread/fork`, but not to `thread/list` lookups, so `--resume --last`
and picker flows could select a session from the wrong cwd; relative cwd
filters also failed against stored absolute paths.

Solution: Apply explicit remote `--cd` to `thread/list` lookups for
`--last` and picker flows, normalize relative cwd filters on the
app-server before exact matching, and document/test the behavior.
This commit is contained in:
Eric Traut
2026-04-03 11:26:45 -07:00
committed by GitHub
Unverified
parent a71fc47cf8
commit 0ab8eda375
6 changed files with 317 additions and 63 deletions
+1 -1
View File
@@ -273,7 +273,7 @@ Experimental API: `thread/start`, `thread/resume`, and `thread/fork` accept `per
- `modelProviders` — restrict results to specific providers; unset, null, or an empty array will include all providers.
- `sourceKinds` — restrict results to specific sources; omit or pass `[]` for interactive sessions only (`cli`, `vscode`).
- `archived` — when `true`, list archived threads only. When `false` or `null`, list non-archived threads (default).
- `cwd` — restrict results to threads whose session cwd exactly matches this path.
- `cwd` — restrict results to threads whose session cwd exactly matches this path. Relative paths are resolved against the app-server process cwd before matching.
- `searchTerm` — restrict results to threads whose extracted title contains this substring (case-sensitive).
- Responses include `agentNickname` and `agentRole` for AgentControl-spawned thread sub-agents when available.
@@ -3350,6 +3350,13 @@ impl CodexMessageProcessor {
cwd,
search_term,
} = params;
let cwd = match normalize_thread_list_cwd_filter(cwd) {
Ok(cwd) => cwd,
Err(error) => {
self.outgoing.send_error(request_id, error).await;
return;
}
};
let requested_page_size = limit
.map(|value| value as usize)
@@ -3368,7 +3375,7 @@ impl CodexMessageProcessor {
model_providers,
source_kinds,
archived: archived.unwrap_or(false),
cwd: cwd.map(PathBuf::from),
cwd,
search_term,
},
)
@@ -7707,6 +7714,57 @@ impl CodexMessageProcessor {
}
}
fn normalize_thread_list_cwd_filter(
cwd: Option<String>,
) -> Result<Option<PathBuf>, JSONRPCErrorError> {
let Some(cwd) = cwd else {
return Ok(None);
};
AbsolutePathBuf::relative_to_current_dir(cwd.as_str())
.map(AbsolutePathBuf::into_path_buf)
.map(Some)
.map_err(|err| JSONRPCErrorError {
code: INVALID_PARAMS_ERROR_CODE,
message: format!("invalid thread/list cwd filter `{cwd}`: {err}"),
data: None,
})
}
#[cfg(test)]
mod thread_list_cwd_filter_tests {
use super::normalize_thread_list_cwd_filter;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
#[test]
fn normalize_thread_list_cwd_filter_preserves_absolute_paths() {
let cwd = if cfg!(windows) {
String::from(r"C:\srv\repo-b")
} else {
String::from("/srv/repo-b")
};
assert_eq!(
normalize_thread_list_cwd_filter(Some(cwd.clone())).expect("cwd filter should parse"),
Some(PathBuf::from(cwd))
);
}
#[test]
fn normalize_thread_list_cwd_filter_resolves_relative_paths_against_server_cwd()
-> std::io::Result<()> {
let expected = AbsolutePathBuf::relative_to_current_dir("repo-b")?.to_path_buf();
assert_eq!(
normalize_thread_list_cwd_filter(Some(String::from("repo-b")))
.expect("cwd filter should parse"),
Some(expected)
);
Ok(())
}
}
#[allow(clippy::too_many_arguments)]
async fn handle_thread_listener_command(
conversation_id: ThreadId,
+88 -12
View File
@@ -104,6 +104,7 @@ pub(crate) struct AppServerBootstrap {
pub(crate) struct AppServerSession {
client: AppServerClient,
next_request_id: i64,
remote_cwd_override: Option<PathBuf>,
}
#[derive(Debug, Clone, PartialEq)]
@@ -150,9 +151,19 @@ impl AppServerSession {
Self {
client,
next_request_id: 1,
remote_cwd_override: None,
}
}
pub(crate) fn with_remote_cwd_override(mut self, remote_cwd_override: Option<PathBuf>) -> Self {
self.remote_cwd_override = remote_cwd_override;
self
}
pub(crate) fn remote_cwd_override(&self) -> Option<&std::path::Path> {
self.remote_cwd_override.as_deref()
}
pub(crate) fn is_remote(&self) -> bool {
matches!(self.client, AppServerClient::Remote(_))
}
@@ -290,7 +301,11 @@ impl AppServerSession {
.client
.request_typed(ClientRequest::ThreadStart {
request_id,
params: thread_start_params_from_config(config, self.thread_params_mode()),
params: thread_start_params_from_config(
config,
self.thread_params_mode(),
self.remote_cwd_override.as_deref(),
),
})
.await
.wrap_err("thread/start failed during TUI bootstrap")?;
@@ -311,6 +326,7 @@ impl AppServerSession {
config.clone(),
thread_id,
self.thread_params_mode(),
self.remote_cwd_override.as_deref(),
),
})
.await
@@ -332,6 +348,7 @@ impl AppServerSession {
config.clone(),
thread_id,
self.thread_params_mode(),
self.remote_cwd_override.as_deref(),
),
})
.await
@@ -839,11 +856,12 @@ fn sandbox_mode_from_policy(
fn thread_start_params_from_config(
config: &Config,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<&std::path::Path>,
) -> ThreadStartParams {
ThreadStartParams {
model: config.model.clone(),
model_provider: thread_params_mode.model_provider_from_config(config),
cwd: thread_cwd_from_config(config, thread_params_mode),
cwd: thread_cwd_from_config(config, thread_params_mode, remote_cwd_override),
approval_policy: Some(config.permissions.approval_policy.value().into()),
approvals_reviewer: approvals_reviewer_override_from_config(config),
sandbox: sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()),
@@ -858,12 +876,13 @@ fn thread_resume_params_from_config(
config: Config,
thread_id: ThreadId,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<&std::path::Path>,
) -> ThreadResumeParams {
ThreadResumeParams {
thread_id: thread_id.to_string(),
model: config.model.clone(),
model_provider: thread_params_mode.model_provider_from_config(&config),
cwd: thread_cwd_from_config(&config, thread_params_mode),
cwd: thread_cwd_from_config(&config, thread_params_mode, remote_cwd_override),
approval_policy: Some(config.permissions.approval_policy.value().into()),
approvals_reviewer: approvals_reviewer_override_from_config(&config),
sandbox: sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()),
@@ -877,12 +896,13 @@ fn thread_fork_params_from_config(
config: Config,
thread_id: ThreadId,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<&std::path::Path>,
) -> ThreadForkParams {
ThreadForkParams {
thread_id: thread_id.to_string(),
model: config.model.clone(),
model_provider: thread_params_mode.model_provider_from_config(&config),
cwd: thread_cwd_from_config(&config, thread_params_mode),
cwd: thread_cwd_from_config(&config, thread_params_mode, remote_cwd_override),
approval_policy: Some(config.permissions.approval_policy.value().into()),
approvals_reviewer: approvals_reviewer_override_from_config(&config),
sandbox: sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()),
@@ -893,10 +913,16 @@ fn thread_fork_params_from_config(
}
}
fn thread_cwd_from_config(config: &Config, thread_params_mode: ThreadParamsMode) -> Option<String> {
fn thread_cwd_from_config(
config: &Config,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<&std::path::Path>,
) -> Option<String> {
match thread_params_mode {
ThreadParamsMode::Embedded => Some(config.cwd.to_string_lossy().to_string()),
ThreadParamsMode::Remote => None,
ThreadParamsMode::Remote => {
remote_cwd_override.map(|cwd| cwd.to_string_lossy().to_string())
}
}
}
@@ -1143,22 +1169,39 @@ mod tests {
let temp_dir = tempfile::tempdir().expect("tempdir");
let config = build_config(&temp_dir).await;
let params = thread_start_params_from_config(&config, ThreadParamsMode::Embedded);
let params = thread_start_params_from_config(
&config,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
);
assert_eq!(params.cwd, Some(config.cwd.to_string_lossy().to_string()));
assert_eq!(params.model_provider, Some(config.model_provider_id));
}
#[tokio::test]
async fn thread_lifecycle_params_omit_local_overrides_for_remote_sessions() {
async fn thread_lifecycle_params_omit_cwd_without_remote_override_for_remote_sessions() {
let temp_dir = tempfile::tempdir().expect("tempdir");
let config = build_config(&temp_dir).await;
let thread_id = ThreadId::new();
let start = thread_start_params_from_config(&config, ThreadParamsMode::Remote);
let resume =
thread_resume_params_from_config(config.clone(), thread_id, ThreadParamsMode::Remote);
let fork = thread_fork_params_from_config(config, thread_id, ThreadParamsMode::Remote);
let start = thread_start_params_from_config(
&config,
ThreadParamsMode::Remote,
/*remote_cwd_override*/ None,
);
let resume = thread_resume_params_from_config(
config.clone(),
thread_id,
ThreadParamsMode::Remote,
/*remote_cwd_override*/ None,
);
let fork = thread_fork_params_from_config(
config,
thread_id,
ThreadParamsMode::Remote,
/*remote_cwd_override*/ None,
);
assert_eq!(start.cwd, None);
assert_eq!(resume.cwd, None);
@@ -1168,6 +1211,39 @@ mod tests {
assert_eq!(fork.model_provider, None);
}
#[tokio::test]
async fn thread_lifecycle_params_forward_explicit_remote_cwd_override_for_remote_sessions() {
let temp_dir = tempfile::tempdir().expect("tempdir");
let config = build_config(&temp_dir).await;
let thread_id = ThreadId::new();
let remote_cwd = PathBuf::from("repo/on/server");
let start = thread_start_params_from_config(
&config,
ThreadParamsMode::Remote,
Some(remote_cwd.as_path()),
);
let resume = thread_resume_params_from_config(
config.clone(),
thread_id,
ThreadParamsMode::Remote,
Some(remote_cwd.as_path()),
);
let fork = thread_fork_params_from_config(
config,
thread_id,
ThreadParamsMode::Remote,
Some(remote_cwd.as_path()),
);
assert_eq!(start.cwd.as_deref(), Some("repo/on/server"));
assert_eq!(resume.cwd.as_deref(), Some("repo/on/server"));
assert_eq!(fork.cwd.as_deref(), Some("repo/on/server"));
assert_eq!(start.model_provider, None);
assert_eq!(resume.model_provider, None);
assert_eq!(fork.model_provider, None);
}
#[tokio::test]
async fn resume_response_restores_turns_from_thread_items() {
let temp_dir = tempfile::tempdir().expect("tempdir");
+1
View File
@@ -95,6 +95,7 @@ pub struct Cli {
pub dangerously_bypass_approvals_and_sandbox: bool,
/// Tell the agent to use the specified directory as its working root.
/// In remote mode, the path is forwarded to the server and resolved there.
#[clap(long = "cd", short = 'C', value_name = "DIR")]
pub cwd: Option<PathBuf>,
+143 -44
View File
@@ -577,15 +577,42 @@ fn latest_session_lookup_params(
source_kinds: (!include_non_interactive)
.then_some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode]),
archived: Some(false),
cwd: if is_remote {
None
} else {
cwd_filter.map(|cwd| cwd.to_string_lossy().to_string())
},
cwd: cwd_filter.map(|cwd| cwd.to_string_lossy().to_string()),
search_term: None,
}
}
fn config_cwd_for_app_server_target(
cwd: Option<&Path>,
app_server_target: &AppServerTarget,
) -> std::io::Result<AbsolutePathBuf> {
if matches!(app_server_target, AppServerTarget::Remote { .. }) {
return AbsolutePathBuf::current_dir();
}
match cwd {
Some(path) => AbsolutePathBuf::from_absolute_path(path.canonicalize()?),
None => AbsolutePathBuf::current_dir(),
}
}
fn latest_session_cwd_filter<'a>(
remote_mode: bool,
remote_cwd_override: Option<&'a Path>,
config: &'a Config,
show_all: bool,
) -> Option<&'a Path> {
if show_all {
return None;
}
if remote_mode {
remote_cwd_override
} else {
Some(config.cwd.as_path())
}
}
pub async fn run_main(
mut cli: Cli,
arg0_paths: Arg0DispatchPaths,
@@ -604,6 +631,10 @@ pub async fn run_main(
auth_token: remote_auth_token.clone(),
})
.unwrap_or(AppServerTarget::Embedded);
let remote_cwd_override = cli
.cwd
.clone()
.filter(|_| matches!(app_server_target, AppServerTarget::Remote { .. }));
let (sandbox_mode, approval_policy) = if cli.full_auto {
(
Some(SandboxMode::WorkspaceWrite),
@@ -654,10 +685,7 @@ pub async fn run_main(
};
let cwd = cli.cwd.clone();
let config_cwd = match cwd.as_deref() {
Some(path) => AbsolutePathBuf::from_absolute_path(path.canonicalize()?)?,
None => AbsolutePathBuf::current_dir()?,
};
let config_cwd = config_cwd_for_app_server_target(cwd.as_deref(), &app_server_target)?;
#[allow(clippy::print_stderr)]
let config_toml = match load_config_as_toml_with_cli_overrides(
@@ -745,7 +773,11 @@ pub async fn run_main(
model,
approval_policy,
sandbox_mode,
cwd,
cwd: if matches!(app_server_target, AppServerTarget::Remote { .. }) {
None
} else {
cwd
},
model_provider: model_provider_override.clone(),
config_profile: cli.config_profile.clone(),
codex_self_exe: arg0_paths.codex_self_exe.clone(),
@@ -907,6 +939,7 @@ pub async fn run_main(
arg0_paths,
loader_overrides,
app_server_target,
remote_cwd_override,
config,
overrides,
cli_kv_overrides,
@@ -925,6 +958,7 @@ async fn run_ratatui_app(
arg0_paths: Arg0DispatchPaths,
loader_overrides: LoaderOverrides,
app_server_target: AppServerTarget,
remote_cwd_override: Option<PathBuf>,
initial_config: Config,
overrides: ConfigOverrides,
cli_kv_overrides: Vec<(String, toml::Value)>,
@@ -983,18 +1017,21 @@ async fn run_ratatui_app(
let needs_onboarding_app_server =
should_show_trust_screen_flag || initial_config.model_provider.requires_openai_auth;
let mut onboarding_app_server = if needs_onboarding_app_server {
Some(AppServerSession::new(
start_app_server(
&app_server_target,
arg0_paths.clone(),
initial_config.clone(),
cli_kv_overrides.clone(),
loader_overrides.clone(),
cloud_requirements.clone(),
feedback.clone(),
Some(
AppServerSession::new(
start_app_server(
&app_server_target,
arg0_paths.clone(),
initial_config.clone(),
cli_kv_overrides.clone(),
loader_overrides.clone(),
cloud_requirements.clone(),
feedback.clone(),
)
.await?,
)
.await?,
))
.with_remote_cwd_override(remote_cwd_override.clone()),
)
} else {
None
};
@@ -1097,18 +1134,21 @@ async fn run_ratatui_app(
|| cli.resume_picker
|| cli.fork_picker;
let mut session_lookup_app_server = if needs_app_server_session_lookup {
Some(AppServerSession::new(
start_app_server(
&app_server_target,
arg0_paths.clone(),
config.clone(),
cli_kv_overrides.clone(),
loader_overrides.clone(),
cloud_requirements.clone(),
feedback.clone(),
Some(
AppServerSession::new(
start_app_server(
&app_server_target,
arg0_paths.clone(),
config.clone(),
cli_kv_overrides.clone(),
loader_overrides.clone(),
cloud_requirements.clone(),
feedback.clone(),
)
.await?,
)
.await?,
))
.with_remote_cwd_override(remote_cwd_override.clone()),
)
} else {
None
};
@@ -1127,12 +1167,21 @@ async fn run_ratatui_app(
}
}
} else if cli.fork_last {
let filter_cwd = if remote_mode {
latest_session_cwd_filter(
remote_mode,
remote_cwd_override.as_deref(),
&config,
cli.fork_show_all,
)
} else {
None
};
let Some(app_server) = session_lookup_app_server.as_mut() else {
unreachable!("session lookup app server should be initialized for --fork --last");
};
match lookup_latest_session_target_with_app_server(
app_server, &config, /*cwd_filter*/ None,
/*include_non_interactive*/ false,
app_server, &config, filter_cwd, /*include_non_interactive*/ false,
)
.await?
{
@@ -1179,11 +1228,12 @@ async fn run_ratatui_app(
}
}
} else if cli.resume_last {
let filter_cwd = if cli.resume_show_all {
None
} else {
Some(config.cwd.as_path())
};
let filter_cwd = latest_session_cwd_filter(
remote_mode,
remote_cwd_override.as_deref(),
&config,
cli.resume_show_all,
);
let Some(app_server) = session_lookup_app_server.as_mut() else {
unreachable!("session lookup app server should be initialized for --resume --last");
};
@@ -1334,7 +1384,7 @@ async fn run_ratatui_app(
let app_result = App::run(
&mut tui,
AppServerSession::new(app_server),
AppServerSession::new(app_server).with_remote_cwd_override(remote_cwd_override),
config,
cli_kv_overrides.clone(),
overrides.clone(),
@@ -1795,12 +1845,9 @@ mod tests {
-> std::io::Result<()> {
let temp_dir = TempDir::new()?;
let config = build_config(&temp_dir).await?;
let cwd = temp_dir.path().join("project");
let params = latest_session_lookup_params(
/*is_remote*/ true,
&config,
Some(cwd.as_path()),
/*is_remote*/ true, &config, /*cwd_filter*/ None,
/*include_non_interactive*/ false,
);
@@ -1809,6 +1856,58 @@ mod tests {
Ok(())
}
#[tokio::test]
async fn latest_session_lookup_params_keep_explicit_cwd_filter_for_remote_sessions()
-> std::io::Result<()> {
let temp_dir = TempDir::new()?;
let config = build_config(&temp_dir).await?;
let cwd = Path::new("repo/on/server");
let params = latest_session_lookup_params(
/*is_remote*/ true,
&config,
Some(cwd),
/*include_non_interactive*/ false,
);
assert_eq!(params.model_providers, None);
assert_eq!(params.cwd.as_deref(), Some("repo/on/server"));
Ok(())
}
#[test]
fn config_cwd_for_app_server_target_uses_current_dir_for_remote_sessions() -> std::io::Result<()>
{
let remote_only_cwd = if cfg!(windows) {
Path::new(r"C:\definitely\not\local\to\this\test")
} else {
Path::new("/definitely/not/local/to/this/test")
};
let target = AppServerTarget::Remote {
websocket_url: "ws://127.0.0.1:1234/".to_string(),
auth_token: None,
};
let config_cwd = config_cwd_for_app_server_target(Some(remote_only_cwd), &target)?;
assert_eq!(config_cwd, AbsolutePathBuf::current_dir()?);
Ok(())
}
#[test]
fn config_cwd_for_app_server_target_canonicalizes_embedded_cli_cwd() -> std::io::Result<()> {
let temp_dir = TempDir::new()?;
let target = AppServerTarget::Embedded;
let config_cwd = config_cwd_for_app_server_target(Some(temp_dir.path()), &target)?;
assert_eq!(
config_cwd,
AbsolutePathBuf::from_absolute_path(temp_dir.path().canonicalize()?)?
);
Ok(())
}
#[tokio::test]
async fn read_session_cwd_returns_none_without_sqlite_or_rollout_path() -> std::io::Result<()> {
let temp_dir = TempDir::new()?;
+25 -5
View File
@@ -172,13 +172,18 @@ pub async fn run_resume_picker_with_app_server(
) -> Result<SessionSelection> {
let (bg_tx, bg_rx) = mpsc::unbounded_channel();
let is_remote = app_server.is_remote();
let cwd_filter = if show_all {
None
} else {
app_server.remote_cwd_override().map(Path::to_path_buf)
};
run_session_picker_with_loader(
tui,
config,
show_all,
SessionPickerAction::Resume,
is_remote,
spawn_app_server_page_loader(app_server, include_non_interactive, bg_tx),
spawn_app_server_page_loader(app_server, cwd_filter, include_non_interactive, bg_tx),
bg_rx,
)
.await
@@ -192,13 +197,20 @@ pub async fn run_fork_picker_with_app_server(
) -> Result<SessionSelection> {
let (bg_tx, bg_rx) = mpsc::unbounded_channel();
let is_remote = app_server.is_remote();
let cwd_filter = if show_all {
None
} else {
app_server.remote_cwd_override().map(Path::to_path_buf)
};
run_session_picker_with_loader(
tui,
config,
show_all,
SessionPickerAction::Fork,
is_remote,
spawn_app_server_page_loader(app_server, /*include_non_interactive*/ false, bg_tx),
spawn_app_server_page_loader(
app_server, cwd_filter, /*include_non_interactive*/ false, bg_tx,
),
bg_rx,
)
.await
@@ -242,8 +254,8 @@ async fn run_session_picker_with_loader(
let codex_home = config.codex_home.as_path();
let filter_cwd = if show_all || is_remote {
// Remote sessions live in the server's filesystem namespace, so the client
// process cwd is not a meaningful default filter. A real remote cwd filter
// would need an explicit server-side target cwd instead of current_dir().
// process cwd is not a meaningful row filter. If the user provided an
// explicit remote --cd, filtering is handled server-side in thread/list.
None
} else {
std::env::current_dir().ok()
@@ -341,6 +353,7 @@ fn spawn_rollout_page_loader(
fn spawn_app_server_page_loader(
app_server: AppServerSession,
cwd_filter: Option<PathBuf>,
include_non_interactive: bool,
bg_tx: mpsc::UnboundedSender<BackgroundEvent>,
) -> PageLoader {
@@ -357,6 +370,7 @@ fn spawn_app_server_page_loader(
let page = load_app_server_page(
&mut app_server,
cursor,
cwd_filter.as_deref(),
request.provider_filter,
request.sort_key,
include_non_interactive,
@@ -467,6 +481,7 @@ impl LoadingState {
async fn load_app_server_page(
app_server: &mut AppServerSession,
cursor: Option<String>,
cwd_filter: Option<&Path>,
provider_filter: ProviderFilter,
sort_key: ThreadSortKey,
include_non_interactive: bool,
@@ -474,6 +489,7 @@ async fn load_app_server_page(
let response = app_server
.thread_list(thread_list_params(
cursor,
cwd_filter,
provider_filter,
sort_key,
include_non_interactive,
@@ -1101,6 +1117,7 @@ fn row_from_app_server_thread(thread: Thread) -> Option<Row> {
fn thread_list_params(
cursor: Option<String>,
cwd_filter: Option<&Path>,
provider_filter: ProviderFilter,
sort_key: ThreadSortKey,
include_non_interactive: bool,
@@ -1119,7 +1136,7 @@ fn thread_list_params(
source_kinds: (!include_non_interactive)
.then_some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode]),
archived: Some(false),
cwd: None,
cwd: cwd_filter.map(|cwd| cwd.to_string_lossy().to_string()),
search_term: None,
}
}
@@ -1836,6 +1853,7 @@ mod tests {
fn remote_thread_list_params_omit_provider_filter() {
let params = thread_list_params(
Some(String::from("cursor-1")),
Some(Path::new("repo/on/server")),
ProviderFilter::Any,
ThreadSortKey::UpdatedAt,
/*include_non_interactive*/ false,
@@ -1847,12 +1865,14 @@ mod tests {
params.source_kinds,
Some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode])
);
assert_eq!(params.cwd.as_deref(), Some("repo/on/server"));
}
#[test]
fn remote_thread_list_params_can_include_non_interactive_sources() {
let params = thread_list_params(
Some(String::from("cursor-1")),
/*cwd_filter*/ None,
ProviderFilter::Any,
ThreadSortKey::UpdatedAt,
/*include_non_interactive*/ true,