From 48659e216aeceaff484e94e7a5e02030075d3bd9 Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Tue, 7 Apr 2026 19:54:55 -0700 Subject: [PATCH] Support clear SessionStart source --- .../schema/json/ClientRequest.json | 17 ++++++++++ .../codex_app_server_protocol.schemas.json | 17 ++++++++++ .../codex_app_server_protocol.v2.schemas.json | 17 ++++++++++ .../schema/json/v2/ThreadStartParams.json | 17 ++++++++++ .../schema/typescript/v2/ThreadStartParams.ts | 3 +- .../schema/typescript/v2/ThreadStartSource.ts | 5 +++ .../schema/typescript/v2/index.ts | 1 + .../app-server-protocol/src/protocol/v2.rs | 10 ++++++ codex-rs/app-server/README.md | 3 +- .../app-server/src/codex_message_processor.rs | 13 ++++++-- .../app-server/tests/suite/v2/skills_list.rs | 1 + codex-rs/core/src/codex.rs | 11 ++++--- codex-rs/core/src/thread_manager.rs | 7 +++-- codex-rs/hooks/src/events/session_start.rs | 2 ++ codex-rs/protocol/src/protocol.rs | 13 ++++---- codex-rs/tui/src/app.rs | 21 ++++++++++--- codex-rs/tui/src/app_server_session.rs | 31 +++++++++++++++++++ 17 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartSource.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 4ca884cb553f..45fee218c4bf 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -3186,10 +3186,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 2d81f6268459..08c4e370b3b5 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 @@ -14167,6 +14167,16 @@ "type": "null" } ] + }, + "sessionStartSource": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadStartSource" + }, + { + "type": "null" + } + ] } }, "title": "ThreadStartParams", @@ -14234,6 +14244,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 c58326c6f86a..22f6ae0e79fc 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 @@ -12022,6 +12022,16 @@ "type": "null" } ] + }, + "sessionStartSource": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadStartSource" + }, + { + "type": "null" + } + ] } }, "title": "ThreadStartParams", @@ -12089,6 +12099,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 b4391c7ab508..21f4d7ef7449 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 61f501ad6074..904487e81cf4 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 000000000000..ea1b839c6baf --- /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 3874280e476c..3dbec39127c2 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -310,6 +310,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 2bf04a2655cd..5fcfa4217682 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -409,6 +409,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/")] @@ -2611,6 +2619,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 69176b0b264b..940ac54c8e04 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. @@ -212,6 +212,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 1e10a5696d40..3457c3ee791c 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -2083,6 +2083,7 @@ impl CodexMessageProcessor { experimental_raw_events, personality, ephemeral, + session_start_source, persist_extended_history, } = params; let mut typesafe_overrides = self.build_thread_config_overrides( @@ -2124,6 +2125,7 @@ impl CodexMessageProcessor { config, typesafe_overrides, dynamic_tools, + session_start_source, persist_extended_history, service_name, experimental_raw_events, @@ -2199,6 +2201,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, @@ -2322,6 +2325,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, @@ -4239,7 +4248,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" )), }; @@ -8856,7 +8865,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 6bd862eb62a7..0a2bbe0df829 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 66f7c020a5ca..24efe79e418a 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -591,7 +591,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) => { @@ -1530,7 +1530,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, @@ -1571,14 +1571,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. @@ -2111,6 +2111,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. @@ -2245,7 +2246,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 2dd4ad553213..a622d91dea82 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -469,6 +469,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, @@ -480,6 +481,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, @@ -487,7 +489,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, @@ -663,6 +665,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), }; @@ -1064,7 +1067,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 057c5d0e7c72..b7be9a9f66e3 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 6912243ab2f7..36c1a92761ab 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -2274,6 +2274,7 @@ pub struct ResumedHistory { #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] pub enum InitialHistory { New, + Cleared, Resumed(ResumedHistory), Forked(Vec), } @@ -2281,7 +2282,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, @@ -2297,7 +2298,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), } @@ -2305,7 +2306,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(), } @@ -2313,7 +2314,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 @@ -2339,7 +2340,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(), @@ -2355,7 +2356,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 dad10fa76628..d5c659c669ad 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -80,6 +80,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; @@ -3271,6 +3272,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. @@ -3292,7 +3294,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) @@ -4019,15 +4024,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 9f3a4d83dd7f..6b71e1ae5436 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -55,6 +55,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; @@ -300,6 +301,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 @@ -309,6 +319,7 @@ impl AppServerSession { config, self.thread_params_mode(), self.remote_cwd_override.as_deref(), + session_start_source, ), }) .await @@ -869,6 +880,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(), @@ -879,6 +891,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() } @@ -1185,12 +1198,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"); @@ -1201,6 +1230,7 @@ mod tests { &config, ThreadParamsMode::Remote, /*remote_cwd_override*/ None, + /*session_start_source*/ None, ); let resume = thread_resume_params_from_config( config.clone(), @@ -1234,6 +1264,7 @@ mod tests { &config, ThreadParamsMode::Remote, Some(remote_cwd.as_path()), + /*session_start_source*/ None, ); let resume = thread_resume_params_from_config( config.clone(),