diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 3372758cd..4533afd06 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -3221,10 +3221,27 @@ "type": "null" } ] + }, + "sessionStartSource": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadStartSource" + }, + { + "type": "null" + } + ] } }, "type": "object" }, + "ThreadStartSource": { + "enum": [ + "startup", + "clear" + ], + "type": "string" + }, "ThreadUnarchiveParams": { "properties": { "threadId": { 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 e4ced5070..c432c742e 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 @@ -14426,6 +14426,16 @@ "type": "null" } ] + }, + "sessionStartSource": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadStartSource" + }, + { + "type": "null" + } + ] } }, "title": "ThreadStartParams", @@ -14493,6 +14503,13 @@ "title": "ThreadStartResponse", "type": "object" }, + "ThreadStartSource": { + "enum": [ + "startup", + "clear" + ], + "type": "string" + }, "ThreadStartedNotification": { "$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 d1f3cbda5..0322866eb 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 @@ -12281,6 +12281,16 @@ "type": "null" } ] + }, + "sessionStartSource": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadStartSource" + }, + { + "type": "null" + } + ] } }, "title": "ThreadStartParams", @@ -12348,6 +12358,13 @@ "title": "ThreadStartResponse", "type": "object" }, + "ThreadStartSource": { + "enum": [ + "startup", + "clear" + ], + "type": "string" + }, "ThreadStartedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json index b4391c7ab..21f4d7ef7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json @@ -101,6 +101,13 @@ "flex" ], "type": "string" + }, + "ThreadStartSource": { + "enum": [ + "startup", + "clear" + ], + "type": "string" } }, "properties": { @@ -210,6 +217,16 @@ "type": "null" } ] + }, + "sessionStartSource": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadStartSource" + }, + { + "type": "null" + } + ] } }, "title": "ThreadStartParams", diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts index 61f501ad6..904487e81 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts @@ -7,12 +7,13 @@ import type { JsonValue } from "../serde_json/JsonValue"; import type { ApprovalsReviewer } from "./ApprovalsReviewer"; import type { AskForApproval } from "./AskForApproval"; import type { SandboxMode } from "./SandboxMode"; +import type { ThreadStartSource } from "./ThreadStartSource"; export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /** * Override where approval requests are routed for review on this thread * and subsequent turns. */ -approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, /** +approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null, /** * If true, opt into emitting raw Responses API items on the event stream. * This is for internal use only (e.g. Codex Cloud). */ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartSource.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartSource.ts new file mode 100644 index 000000000..ea1b839c6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartSource.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 ThreadStartSource = "startup" | "clear"; 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 d8dd15c97..f541aed1b 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -316,6 +316,7 @@ export type { ThreadSortKey } from "./ThreadSortKey"; export type { ThreadSourceKind } from "./ThreadSourceKind"; export type { ThreadStartParams } from "./ThreadStartParams"; export type { ThreadStartResponse } from "./ThreadStartResponse"; +export type { ThreadStartSource } from "./ThreadStartSource"; export type { ThreadStartedNotification } from "./ThreadStartedNotification"; export type { ThreadStatus } from "./ThreadStatus"; export type { ThreadStatusChangedNotification } from "./ThreadStatusChangedNotification"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 66ac1a602..a93fdab15 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -413,6 +413,14 @@ v2_enum_from_core!( } ); +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(rename_all = "camelCase", export_to = "v2/")] +pub enum ThreadStartSource { + Startup, + Clear, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -2633,6 +2641,8 @@ pub struct ThreadStartParams { pub personality: Option, #[ts(optional = nullable)] pub ephemeral: Option, + #[ts(optional = nullable)] + pub session_start_source: Option, #[experimental("thread/start.dynamicTools")] #[ts(optional = nullable)] pub dynamic_tools: Option>, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index eeaf9ef23..15d62c8a5 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -133,7 +133,7 @@ Example with notification opt-out: ## API Overview -- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. +- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. Pass `sessionStartSource: "clear"` when starting a replacement thread after clearing the current session so `SessionStart` hooks receive `source: "clear"` instead of the default `"startup"`. - `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it. - `thread/fork` — fork an existing thread into a new thread id by copying the stored history; if the source thread is currently mid-turn, the fork records the same interruption marker as `turn/interrupt` instead of inheriting an unmarked partial turn suffix. The returned `thread.forkedFromId` points at the source thread when known. Accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread. - `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `cwd`, and `searchTerm` filters. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded. @@ -213,6 +213,7 @@ Start a fresh thread when you need a new Codex conversation. "sandbox": "workspaceWrite", "personality": "friendly", "serviceName": "my_app_server_client", // optional metrics tag (`service_name`) + "sessionStartSource": "startup", // optional: "startup" (default) or "clear" // Experimental: requires opt-in "dynamicTools": [ { diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 795723f9d..98becc9a6 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -2236,6 +2236,7 @@ impl CodexMessageProcessor { experimental_raw_events, personality, ephemeral, + session_start_source, persist_extended_history, } = params; let mut typesafe_overrides = self.build_thread_config_overrides( @@ -2277,6 +2278,7 @@ impl CodexMessageProcessor { config, typesafe_overrides, dynamic_tools, + session_start_source, persist_extended_history, service_name, experimental_raw_events, @@ -2352,6 +2354,7 @@ impl CodexMessageProcessor { config_overrides: Option>, typesafe_overrides: ConfigOverrides, dynamic_tools: Option>, + session_start_source: Option, persist_extended_history: bool, service_name: Option, experimental_raw_events: bool, @@ -2475,6 +2478,12 @@ impl CodexMessageProcessor { .thread_manager .start_thread_with_tools_and_service_name( config, + match session_start_source + .unwrap_or(codex_app_server_protocol::ThreadStartSource::Startup) + { + codex_app_server_protocol::ThreadStartSource::Startup => InitialHistory::New, + codex_app_server_protocol::ThreadStartSource::Clear => InitialHistory::Cleared, + }, core_dynamic_tools, persist_extended_history, service_name, @@ -4443,7 +4452,7 @@ impl CodexMessageProcessor { thread.preview = preview_from_rollout_items(items); Ok(thread) } - InitialHistory::New => Err(format!( + InitialHistory::New | InitialHistory::Cleared => Err(format!( "failed to build resume response for thread {thread_id}: initial history missing" )), }; @@ -9114,7 +9123,7 @@ pub(crate) async fn read_rollout_items_from_rollout( path: &Path, ) -> std::io::Result> { let items = match RolloutRecorder::get_rollout_history(path).await? { - InitialHistory::New => Vec::new(), + InitialHistory::New | InitialHistory::Cleared => Vec::new(), InitialHistory::Forked(items) => items, InitialHistory::Resumed(resumed) => resumed.history, }; diff --git a/codex-rs/app-server/tests/suite/v2/skills_list.rs b/codex-rs/app-server/tests/suite/v2/skills_list.rs index 6bd862eb6..0a2bbe0df 100644 --- a/codex-rs/app-server/tests/suite/v2/skills_list.rs +++ b/codex-rs/app-server/tests/suite/v2/skills_list.rs @@ -241,6 +241,7 @@ async fn skills_changed_notification_is_emitted_after_skill_change() -> Result<( developer_instructions: None, personality: None, ephemeral: None, + session_start_source: None, dynamic_tools: None, mock_experimental_field: None, experimental_raw_events: false, diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index ea0375b14..70a2883c2 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -599,7 +599,7 @@ impl Codex { let thread_id = match &conversation_history { InitialHistory::Resumed(resumed) => Some(resumed.conversation_id), InitialHistory::Forked(_) => conversation_history.forked_from_id(), - InitialHistory::New => None, + InitialHistory::New | InitialHistory::Cleared => None, }; match thread_id { Some(thread_id) => { @@ -1619,7 +1619,7 @@ impl Session { let forked_from_id = initial_history.forked_from_id(); let (conversation_id, rollout_params) = match &initial_history { - InitialHistory::New | InitialHistory::Forked(_) => { + InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_) => { let conversation_id = ThreadId::default(); ( conversation_id, @@ -1660,14 +1660,14 @@ impl Session { .count(), ) .unwrap_or(u64::MAX), - InitialHistory::New | InitialHistory::Forked(_) => 0, + InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_) => 0, }; let state_builder = match &initial_history { InitialHistory::Resumed(resumed) => metadata::builder_from_items( resumed.history.as_slice(), resumed.rollout_path.as_path(), ), - InitialHistory::New | InitialHistory::Forked(_) => None, + InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_) => None, }; // Kick off independent async setup tasks in parallel to reduce startup latency. @@ -2199,6 +2199,7 @@ impl Session { InitialHistory::New | InitialHistory::Forked(_) => { codex_hooks::SessionStartSource::Startup } + InitialHistory::Cleared => codex_hooks::SessionStartSource::Clear, }; // record_initial_history can emit events. We record only after the SessionConfiguredEvent is emitted. @@ -2339,7 +2340,7 @@ impl Session { ) }; match conversation_history { - InitialHistory::New => { + InitialHistory::New | InitialHistory::Cleared => { // Defer initial context insertion until the first real turn starts so // turn/start overrides can be merged before we write model-visible context. self.set_previous_turn_settings(/*previous_turn_settings*/ None) diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index af83785c5..d8a38a858 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -474,6 +474,7 @@ impl ThreadManager { ) -> CodexResult { Box::pin(self.start_thread_with_tools_and_service_name( config, + InitialHistory::New, dynamic_tools, persist_extended_history, /*metrics_service_name*/ None, @@ -485,6 +486,7 @@ impl ThreadManager { pub async fn start_thread_with_tools_and_service_name( &self, config: Config, + initial_history: InitialHistory, dynamic_tools: Vec, persist_extended_history: bool, metrics_service_name: Option, @@ -492,7 +494,7 @@ impl ThreadManager { ) -> CodexResult { Box::pin(self.state.spawn_thread( config, - InitialHistory::New, + initial_history, Arc::clone(&self.state.auth_manager), self.agent_control(), dynamic_tools, @@ -668,6 +670,7 @@ impl ThreadManager { ForkSnapshot::Interrupted => { let history = match history { InitialHistory::New => InitialHistory::New, + InitialHistory::Cleared => InitialHistory::Cleared, InitialHistory::Forked(history) => InitialHistory::Forked(history), InitialHistory::Resumed(resumed) => InitialHistory::Forked(resumed.history), }; @@ -1070,7 +1073,7 @@ fn append_interrupted_boundary(history: InitialHistory, turn_id: Option) })); match history { - InitialHistory::New => InitialHistory::Forked(vec![ + InitialHistory::New | InitialHistory::Cleared => InitialHistory::Forked(vec![ RolloutItem::ResponseItem(interrupted_turn_history_marker()), aborted_event, ]), diff --git a/codex-rs/hooks/src/events/session_start.rs b/codex-rs/hooks/src/events/session_start.rs index 057c5d0e7..b7be9a9f6 100644 --- a/codex-rs/hooks/src/events/session_start.rs +++ b/codex-rs/hooks/src/events/session_start.rs @@ -20,6 +20,7 @@ use crate::schema::SessionStartCommandInput; pub enum SessionStartSource { Startup, Resume, + Clear, } impl SessionStartSource { @@ -27,6 +28,7 @@ impl SessionStartSource { match self { Self::Startup => "startup", Self::Resume => "resume", + Self::Clear => "clear", } } } diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 44693033d..54eb8e8bd 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -2448,6 +2448,7 @@ pub struct ResumedHistory { #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] pub enum InitialHistory { New, + Cleared, Resumed(ResumedHistory), Forked(Vec), } @@ -2455,7 +2456,7 @@ pub enum InitialHistory { impl InitialHistory { pub fn forked_from_id(&self) -> Option { match self { - InitialHistory::New => None, + InitialHistory::New | InitialHistory::Cleared => None, InitialHistory::Resumed(resumed) => { resumed.history.iter().find_map(|item| match item { RolloutItem::SessionMeta(meta_line) => meta_line.meta.forked_from_id, @@ -2471,7 +2472,7 @@ impl InitialHistory { pub fn session_cwd(&self) -> Option { match self { - InitialHistory::New => None, + InitialHistory::New | InitialHistory::Cleared => None, InitialHistory::Resumed(resumed) => session_cwd_from_items(&resumed.history), InitialHistory::Forked(items) => session_cwd_from_items(items), } @@ -2479,7 +2480,7 @@ impl InitialHistory { pub fn get_rollout_items(&self) -> Vec { match self { - InitialHistory::New => Vec::new(), + InitialHistory::New | InitialHistory::Cleared => Vec::new(), InitialHistory::Resumed(resumed) => resumed.history.clone(), InitialHistory::Forked(items) => items.clone(), } @@ -2487,7 +2488,7 @@ impl InitialHistory { pub fn get_event_msgs(&self) -> Option> { match self { - InitialHistory::New => None, + InitialHistory::New | InitialHistory::Cleared => None, InitialHistory::Resumed(resumed) => Some( resumed .history @@ -2513,7 +2514,7 @@ impl InitialHistory { pub fn get_base_instructions(&self) -> Option { // TODO: SessionMeta should (in theory) always be first in the history, so we can probably only check the first item? match self { - InitialHistory::New => None, + InitialHistory::New | InitialHistory::Cleared => None, InitialHistory::Resumed(resumed) => { resumed.history.iter().find_map(|item| match item { RolloutItem::SessionMeta(meta_line) => meta_line.meta.base_instructions.clone(), @@ -2529,7 +2530,7 @@ impl InitialHistory { pub fn get_dynamic_tools(&self) -> Option> { match self { - InitialHistory::New => None, + InitialHistory::New | InitialHistory::Cleared => None, InitialHistory::Resumed(resumed) => { resumed.history.iter().find_map(|item| match item { RolloutItem::SessionMeta(meta_line) => meta_line.meta.dynamic_tools.clone(), diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index c7c9e79c7..5bbdb90e7 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -82,6 +82,7 @@ use codex_app_server_protocol::SkillsListResponse; use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::ThreadLoadedListParams; use codex_app_server_protocol::ThreadRollbackResponse; +use codex_app_server_protocol::ThreadStartSource; use codex_app_server_protocol::Turn; use codex_app_server_protocol::TurnError as AppServerTurnError; use codex_app_server_protocol::TurnStatus; @@ -3310,6 +3311,7 @@ impl App { &mut self, tui: &mut tui::Tui, app_server: &mut AppServerSession, + session_start_source: Option, ) { // Start a fresh in-memory session while preserving resumability via persisted rollout // history. @@ -3331,7 +3333,10 @@ impl App { } } self.config = config.clone(); - match app_server.start_thread(&config).await { + match app_server + .start_thread_with_session_start_source(&config, session_start_source) + .await + { Ok(started) => { if let Err(err) = self .replace_chat_widget_with_app_server_thread(tui, app_server, started) @@ -4175,15 +4180,21 @@ impl App { ) -> Result { match event { AppEvent::NewSession => { - self.start_fresh_session_with_summary_hint(tui, app_server) - .await; + self.start_fresh_session_with_summary_hint( + tui, app_server, /*session_start_source*/ None, + ) + .await; } AppEvent::ClearUi => { self.clear_terminal_ui(tui, /*redraw_header*/ false)?; self.reset_app_ui_state_after_clear(); - self.start_fresh_session_with_summary_hint(tui, app_server) - .await; + self.start_fresh_session_with_summary_hint( + tui, + app_server, + Some(ThreadStartSource::Clear), + ) + .await; } AppEvent::OpenResumePicker => { let picker_app_server = match crate::start_app_server_for_picker( diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 3bfbc8404..5d1c30f6e 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -57,6 +57,7 @@ use codex_app_server_protocol::ThreadShellCommandParams; use codex_app_server_protocol::ThreadShellCommandResponse; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadStartResponse; +use codex_app_server_protocol::ThreadStartSource; use codex_app_server_protocol::ThreadUnsubscribeParams; use codex_app_server_protocol::ThreadUnsubscribeResponse; use codex_app_server_protocol::Turn; @@ -304,6 +305,15 @@ impl AppServerSession { } pub(crate) async fn start_thread(&mut self, config: &Config) -> Result { + self.start_thread_with_session_start_source(config, /*session_start_source*/ None) + .await + } + + pub(crate) async fn start_thread_with_session_start_source( + &mut self, + config: &Config, + session_start_source: Option, + ) -> Result { let request_id = self.next_request_id(); let response: ThreadStartResponse = self .client @@ -313,6 +323,7 @@ impl AppServerSession { config, self.thread_params_mode(), self.remote_cwd_override.as_deref(), + session_start_source, ), }) .await @@ -895,6 +906,7 @@ fn thread_start_params_from_config( config: &Config, thread_params_mode: ThreadParamsMode, remote_cwd_override: Option<&std::path::Path>, + session_start_source: Option, ) -> ThreadStartParams { ThreadStartParams { model: config.model.clone(), @@ -905,6 +917,7 @@ fn thread_start_params_from_config( sandbox: sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()), config: config_request_overrides_from_config(config), ephemeral: Some(config.ephemeral), + session_start_source, persist_extended_history: true, ..ThreadStartParams::default() } @@ -1222,12 +1235,28 @@ mod tests { &config, ThreadParamsMode::Embedded, /*remote_cwd_override*/ None, + /*session_start_source*/ 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_start_params_can_mark_clear_source() { + let temp_dir = tempfile::tempdir().expect("tempdir"); + let config = build_config(&temp_dir).await; + + let params = thread_start_params_from_config( + &config, + ThreadParamsMode::Embedded, + /*remote_cwd_override*/ None, + Some(ThreadStartSource::Clear), + ); + + assert_eq!(params.session_start_source, Some(ThreadStartSource::Clear)); + } + #[tokio::test] async fn thread_lifecycle_params_omit_cwd_without_remote_override_for_remote_sessions() { let temp_dir = tempfile::tempdir().expect("tempdir"); @@ -1238,6 +1267,7 @@ mod tests { &config, ThreadParamsMode::Remote, /*remote_cwd_override*/ None, + /*session_start_source*/ None, ); let resume = thread_resume_params_from_config( config.clone(), @@ -1271,6 +1301,7 @@ mod tests { &config, ThreadParamsMode::Remote, Some(remote_cwd.as_path()), + /*session_start_source*/ None, ); let resume = thread_resume_params_from_config( config.clone(),