diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 7194e32443..121a378566 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -128,6 +128,7 @@ fn sample_thread_with_metadata( ) -> Thread { Thread { id: thread_id.to_string(), + session_id: format!("session-{thread_id}"), forked_from_id: None, preview: "first prompt".to_string(), ephemeral, @@ -154,7 +155,6 @@ fn sample_thread_start_response( model: &str, ) -> ClientResponsePayload { ClientResponsePayload::ThreadStart(ThreadStartResponse { - session_id: format!("session-{thread_id}"), thread: sample_thread_with_metadata( thread_id, ephemeral, @@ -216,7 +216,6 @@ fn sample_thread_resume_response_with_source( thread_source: Option, ) -> ClientResponsePayload { ClientResponsePayload::ThreadResume(ThreadResumeResponse { - session_id: format!("session-{thread_id}"), thread: sample_thread_with_metadata(thread_id, ephemeral, source, thread_source), model: model.to_string(), model_provider: "openai".to_string(), diff --git a/codex-rs/analytics/src/client_tests.rs b/codex-rs/analytics/src/client_tests.rs index 510378c20d..3021d558d6 100644 --- a/codex-rs/analytics/src/client_tests.rs +++ b/codex-rs/analytics/src/client_tests.rs @@ -76,6 +76,7 @@ fn sample_thread_archive_request() -> ClientRequest { fn sample_thread(thread_id: &str) -> Thread { Thread { id: thread_id.to_string(), + session_id: format!("session-{thread_id}"), forked_from_id: None, preview: "first prompt".to_string(), ephemeral: false, @@ -102,7 +103,6 @@ fn sample_permission_profile() -> AppServerPermissionProfile { fn sample_thread_start_response() -> ClientResponsePayload { ClientResponsePayload::ThreadStart(ThreadStartResponse { - session_id: "session-1".to_string(), thread: sample_thread("thread-1"), model: "gpt-5".to_string(), model_provider: "openai".to_string(), @@ -120,7 +120,6 @@ fn sample_thread_start_response() -> ClientResponsePayload { fn sample_thread_resume_response() -> ClientResponsePayload { ClientResponsePayload::ThreadResume(ThreadResumeResponse { - session_id: "session-2".to_string(), thread: sample_thread("thread-2"), model: "gpt-5".to_string(), model_provider: "openai".to_string(), @@ -138,7 +137,6 @@ fn sample_thread_resume_response() -> ClientResponsePayload { fn sample_thread_fork_response() -> ClientResponsePayload { ClientResponsePayload::ThreadFork(ThreadForkResponse { - session_id: "session-3".to_string(), thread: sample_thread("thread-3"), model: "gpt-5".to_string(), model_provider: "openai".to_string(), diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index f5de1f456c..883ab8e68a 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -3072,6 +3072,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -3120,6 +3124,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", 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 20279bf010..c4f69f47ea 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 @@ -15345,6 +15345,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -15393,6 +15397,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", @@ -15664,11 +15669,6 @@ } ] }, - "sessionId": { - "default": "", - "description": "Session id shared by threads that belong to the same session tree.", - "type": "string" - }, "thread": { "$ref": "#/definitions/v2/Thread" } @@ -17176,11 +17176,6 @@ } ] }, - "sessionId": { - "default": "", - "description": "Session id shared by threads that belong to the same session tree.", - "type": "string" - }, "thread": { "$ref": "#/definitions/v2/Thread" } @@ -17504,11 +17499,6 @@ } ] }, - "sessionId": { - "default": "", - "description": "Session id shared by threads that belong to the same session tree.", - "type": "string" - }, "thread": { "$ref": "#/definitions/v2/Thread" } 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 50a19200a5..fdd5ba9084 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 @@ -13231,6 +13231,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -13279,6 +13283,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", @@ -13550,11 +13555,6 @@ } ] }, - "sessionId": { - "default": "", - "description": "Session id shared by threads that belong to the same session tree.", - "type": "string" - }, "thread": { "$ref": "#/definitions/Thread" } @@ -15062,11 +15062,6 @@ } ] }, - "sessionId": { - "default": "", - "description": "Session id shared by threads that belong to the same session tree.", - "type": "string" - }, "thread": { "$ref": "#/definitions/Thread" } @@ -15390,11 +15385,6 @@ } ] }, - "sessionId": { - "default": "", - "description": "Session id shared by threads that belong to the same session tree.", - "type": "string" - }, "thread": { "$ref": "#/definitions/Thread" } diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 902a6f4c9f..93f5ee18b1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -1403,6 +1403,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -1451,6 +1455,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", @@ -2619,11 +2624,6 @@ } ] }, - "sessionId": { - "default": "", - "description": "Session id shared by threads that belong to the same session tree.", - "type": "string" - }, "thread": { "$ref": "#/definitions/Thread" } diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index aaeb3a5ab2..f78fbaf27e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -853,6 +853,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -901,6 +905,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index 6237f8b746..4268ad203a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -853,6 +853,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -901,6 +905,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index 4a64c50252..fb0d80a047 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -853,6 +853,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -901,6 +905,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index b00fd513e7..bb1290ecbe 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -1403,6 +1403,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -1451,6 +1455,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", @@ -2619,11 +2624,6 @@ } ] }, - "sessionId": { - "default": "", - "description": "Session id shared by threads that belong to the same session tree.", - "type": "string" - }, "thread": { "$ref": "#/definitions/Thread" } diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index f495e4cdfe..204828c732 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -853,6 +853,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -901,6 +905,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 7451d08977..f1354d70b1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -1403,6 +1403,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -1451,6 +1455,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", @@ -2619,11 +2624,6 @@ } ] }, - "sessionId": { - "default": "", - "description": "Session id shared by threads that belong to the same session tree.", - "type": "string" - }, "thread": { "$ref": "#/definitions/Thread" } diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 98c163a41e..759b5990be 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -853,6 +853,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -901,6 +905,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index 5e26982d51..f64400129a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -853,6 +853,10 @@ "description": "Usually the first user message in the thread, if available.", "type": "string" }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "source": { "allOf": [ { @@ -901,6 +905,7 @@ "id", "modelProvider", "preview", + "sessionId", "source", "status", "turns", diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts index 99e622565a..d917094e36 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts @@ -9,6 +9,10 @@ import type { ThreadStatus } from "./ThreadStatus"; import type { Turn } from "./Turn"; export type Thread = { id: string, +/** + * Session id shared by threads that belong to the same session tree. + */ +sessionId: string, /** * Source thread id when this thread was created by forking another thread. */ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts index b0bb9caea6..ddcef104e9 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts @@ -9,10 +9,7 @@ import type { AskForApproval } from "./AskForApproval"; import type { SandboxPolicy } from "./SandboxPolicy"; import type { Thread } from "./Thread"; -export type ThreadForkResponse = {/** - * Session id shared by threads that belong to the same session tree. - */ -sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** +export type ThreadForkResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** * Instruction source files currently loaded for this thread. */ instructionSources: Array, approvalPolicy: AskForApproval, /** diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts index cc4c2440f7..f7627c07ae 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts @@ -9,10 +9,7 @@ import type { AskForApproval } from "./AskForApproval"; import type { SandboxPolicy } from "./SandboxPolicy"; import type { Thread } from "./Thread"; -export type ThreadResumeResponse = {/** - * Session id shared by threads that belong to the same session tree. - */ -sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** +export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** * Instruction source files currently loaded for this thread. */ instructionSources: Array, approvalPolicy: AskForApproval, /** diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts index 962ed2437e..ce28a4a1d7 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts @@ -9,10 +9,7 @@ import type { AskForApproval } from "./AskForApproval"; import type { SandboxPolicy } from "./SandboxPolicy"; import type { Thread } from "./Thread"; -export type ThreadStartResponse = {/** - * Session id shared by threads that belong to the same session tree. - */ -sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** +export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** * Instruction source files currently loaded for this thread. */ instructionSources: Array, approvalPolicy: AskForApproval, /** diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 8fd267d014..682556b976 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -2176,9 +2176,9 @@ mod tests { let response = ClientResponse::ThreadStart { request_id: RequestId::Integer(7), response: v2::ThreadStartResponse { - session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(), thread: v2::Thread { id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(), + session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(), forked_from_id: None, preview: "first prompt".to_string(), ephemeral: true, @@ -2218,9 +2218,9 @@ mod tests { "method": "thread/start", "id": 7, "response": { - "sessionId": "67e55044-10b1-426f-9247-bb680e5fe0c7", "thread": { "id": "67e55044-10b1-426f-9247-bb680e5fe0c8", + "sessionId": "67e55044-10b1-426f-9247-bb680e5fe0c7", "forkedFromId": null, "preview": "first prompt", "ephemeral": true, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index dba9c24108..9b82120c93 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -3437,10 +3437,11 @@ fn thread_start_params_preserve_explicit_null_service_tier() { } #[test] -fn thread_lifecycle_responses_default_missing_compat_fields() { +fn thread_lifecycle_responses_default_missing_optional_fields() { let response = json!({ "thread": { "id": "thread-id", + "sessionId": "thread-id", "forkedFromId": null, "preview": "", "ephemeral": false, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs index 5a09c2e665..578ef9193f 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs @@ -189,9 +189,6 @@ pub struct MockExperimentalMethodResponse { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartResponse { - /// Session id shared by threads that belong to the same session tree. - #[serde(default)] - pub session_id: String, pub thread: Thread, pub model: String, pub model_provider: String, @@ -307,9 +304,6 @@ pub struct ThreadResumeParams { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadResumeResponse { - /// Session id shared by threads that belong to the same session tree. - #[serde(default)] - pub session_id: String, pub thread: Thread, pub model: String, pub model_provider: String, @@ -419,9 +413,6 @@ pub struct ThreadForkParams { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadForkResponse { - /// Session id shared by threads that belong to the same session tree. - #[serde(default)] - pub session_id: String, pub thread: Thread, pub model: String, pub model_provider: String, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs index cb02705849..f0c518adf8 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs @@ -104,6 +104,8 @@ pub struct GitInfo { #[ts(export_to = "v2/")] pub struct Thread { pub id: String, + /// Session id shared by threads that belong to the same session tree. + pub session_id: String, /// Source thread id when this thread was created by forking another thread. pub forked_from_id: Option, /// Usually the first user message in the thread, if available. diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 8aeeb7df4f..ddc3817952 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -303,11 +303,11 @@ Example: { "id": 12, "result": { "thread": { "id": "thr_123", "turns": [], … } } } ``` -To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. The response includes the forked thread's `sessionId`, so clients do not need to infer it from the new thread id. When the source history includes persisted token usage, the server also emits `thread/tokenUsage/updated` for the new thread immediately after the response. If the source thread is actively running, the fork snapshots it as if the current turn had been interrupted first. Pass `ephemeral: true` when the fork should stay in-memory only: +To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. The returned `thread.sessionId` identifies the current live session tree root. Root threads use their own `thread.id` as `thread.sessionId`; stored threads that are not loaded also report their own `thread.id`, because resuming one makes it the root of a new live session tree. When the source history includes persisted token usage, the server also emits `thread/tokenUsage/updated` for the new thread immediately after the response. If the source thread is actively running, the fork snapshots it as if the current turn had been interrupted first. Pass `ephemeral: true` when the fork should stay in-memory only: ```json { "method": "thread/fork", "id": 12, "params": { "threadId": "thr_123", "ephemeral": true } } -{ "id": 12, "result": { "sessionId": "thr_456", "thread": { "id": "thr_456", … } } } +{ "id": 12, "result": { "thread": { "id": "thr_456", "sessionId": "thr_456", … } } } { "method": "thread/started", "params": { "thread": { … } } } ``` diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index 6ff7f5f03f..e2d8fd587d 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -1190,6 +1190,7 @@ pub(crate) async fn apply_bespoke_event_handling( .await; let response = match thread_rollback_response_from_stored_thread( stored_thread, + conversation.session_configured().session_id.to_string(), fallback_model_provider.as_str(), &fallback_cwd, loaded_status, @@ -1543,6 +1544,7 @@ async fn handle_thread_rollback_failed( fn thread_rollback_response_from_stored_thread( stored_thread: codex_thread_store::StoredThread, + session_id: String, fallback_model_provider: &str, fallback_cwd: &AbsolutePathBuf, loaded_status: ThreadStatus, @@ -1550,6 +1552,7 @@ fn thread_rollback_response_from_stored_thread( let thread_id = stored_thread.thread_id; let (mut thread, history) = thread_from_stored_thread(stored_thread, fallback_model_provider, fallback_cwd); + thread.session_id = session_id; let Some(history) = history else { return Err(format!( "thread {thread_id} did not include persisted history after rollback" @@ -2200,6 +2203,7 @@ mod tests { let response = thread_rollback_response_from_stored_thread( stored_thread, + thread_id.to_string(), "fallback-provider", &fallback_cwd, ThreadStatus::NotLoaded, diff --git a/codex-rs/app-server/src/request_processors/thread_lifecycle.rs b/codex-rs/app-server/src/request_processors/thread_lifecycle.rs index bd93893b79..81e09c7d5c 100644 --- a/codex-rs/app-server/src/request_processors/thread_lifecycle.rs +++ b/codex-rs/app-server/src/request_processors/thread_lifecycle.rs @@ -599,9 +599,9 @@ pub(super) async fn handle_pending_thread_resume_request( let active_permission_profile = thread_response_active_permission_profile(active_permission_profile); let session_id = conversation.session_configured().session_id.to_string(); + thread.session_id = session_id; let response = ThreadResumeResponse { - session_id, thread, model, model_provider: model_provider_id, 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 7cef88b14b..aaf7fb1de1 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor.rs @@ -1096,6 +1096,7 @@ impl ThreadRequestProcessor { .await; let mut thread = build_thread_from_snapshot( thread_id, + session_configured.session_id.to_string(), &config_snapshot, session_configured.rollout_path.clone(), ); @@ -1148,7 +1149,6 @@ impl ThreadRequestProcessor { thread_response_active_permission_profile(config_snapshot.active_permission_profile); let response = ThreadStartResponse { - session_id: session_configured.session_id.to_string(), thread: thread.clone(), model: config_snapshot.model, model_provider: config_snapshot.model_provider_id, @@ -1529,9 +1529,9 @@ impl ThreadRequestProcessor { ..Default::default() }; + let loaded_thread = self.thread_manager.get_thread(thread_uuid).await.ok(); let updated_thread = { let _thread_list_state_permit = self.acquire_thread_list_state_permit().await?; - let loaded_thread = self.thread_manager.get_thread(thread_uuid).await.ok(); if let Some(loaded_thread) = loaded_thread.as_ref() { if loaded_thread.config_snapshot().await.ephemeral { return Err(invalid_request(format!( @@ -1552,12 +1552,14 @@ impl ThreadRequestProcessor { } .map_err(|err| thread_store_write_error("update thread metadata", err))? }; - let (mut thread, _) = thread_from_stored_thread( updated_thread, self.config.model_provider_id.as_str(), &self.config.cwd, ); + if let Some(loaded_thread) = loaded_thread.as_ref() { + thread.session_id = loaded_thread.session_configured().session_id.to_string(); + } self.attach_thread_name(thread_uuid, &mut thread).await; thread.status = resolve_thread_status( self.thread_watch_manager @@ -1603,18 +1605,16 @@ impl ThreadRequestProcessor { .map_err(|err| invalid_request(format!("invalid thread id: {err}")))?; let fallback_provider = self.config.model_provider_id.clone(); - let mut thread = self + let stored_thread = self .thread_store .unarchive_thread(StoreArchiveThreadParams { thread_id }) .await - .map_err(|err| thread_store_archive_error("unarchive", err)) - .and_then(|stored_thread| { - summary_from_stored_thread(stored_thread, fallback_provider.as_str()) - .map(|summary| summary_to_thread(summary, &self.config.cwd)) - .ok_or_else(|| { - internal_error(format!("failed to read unarchived thread {thread_id}")) - }) + .map_err(|err| thread_store_archive_error("unarchive", err))?; + let summary = summary_from_stored_thread(stored_thread, fallback_provider.as_str()) + .ok_or_else(|| { + internal_error(format!("failed to read unarchived thread {thread_id}")) })?; + let mut thread = summary_to_thread(summary, &self.config.cwd); thread.status = resolve_thread_status( self.thread_watch_manager @@ -2040,6 +2040,7 @@ impl ThreadRequestProcessor { if thread.path.is_none() { thread.path = fallback_thread.path.clone(); } + thread.session_id.clone_from(&fallback_thread.session_id); thread.ephemeral = fallback_thread.ephemeral; thread } else { @@ -2223,8 +2224,12 @@ impl ThreadRequestProcessor { ) { if let Ok(thread) = self.thread_manager.get_thread(thread_id).await { let config_snapshot = thread.config_snapshot().await; - let loaded_thread = - build_thread_from_snapshot(thread_id, &config_snapshot, thread.rollout_path()); + let loaded_thread = build_thread_from_snapshot( + thread_id, + thread.session_configured().session_id.to_string(), + &config_snapshot, + thread.rollout_path(), + ); self.thread_watch_manager.upsert_thread(loaded_thread).await; } @@ -2477,7 +2482,6 @@ impl ThreadRequestProcessor { ); let response = ThreadResumeResponse { - session_id: session_configured.session_id.to_string(), thread, model: session_configured.model, model_provider: session_configured.model_provider_id, @@ -2651,11 +2655,12 @@ impl ThreadRequestProcessor { } let mut summary_source_thread = source_thread; summary_source_thread.history = None; - let thread_summary = self.stored_thread_to_api_thread( + let mut thread_summary = self.stored_thread_to_api_thread( summary_source_thread, config_snapshot.model_provider_id.as_str(), /*include_turns*/ false, ); + thread_summary.session_id = existing_thread.session_configured().session_id.to_string(); let mut config_for_instruction_sources = self.config.as_ref().clone(); config_for_instruction_sources.cwd = config_snapshot.cwd.clone(); let instruction_sources = @@ -2824,6 +2829,7 @@ impl ThreadRequestProcessor { include_turns: bool, ) -> std::result::Result { let config_snapshot = thread.config_snapshot().await; + let session_id = thread.session_configured().session_id.to_string(); let thread = match thread_history { InitialHistory::Resumed(resumed) => { let fallback_provider = config_snapshot.model_provider_id.as_str(); @@ -2885,6 +2891,7 @@ impl ThreadRequestProcessor { InitialHistory::Forked(items) => { let mut thread = build_thread_from_snapshot( thread_id, + session_id.clone(), &config_snapshot, Some(rollout_path.into()), ); @@ -2897,6 +2904,7 @@ impl ThreadRequestProcessor { }; let mut thread = thread?; thread.id = thread_id.to_string(); + thread.session_id = session_id; thread.path = Some(rollout_path.to_path_buf()); if include_turns { let history_items = thread_history.get_rollout_items(); @@ -3078,8 +3086,12 @@ impl ThreadRequestProcessor { } else { let config_snapshot = forked_thread.config_snapshot().await; // forked thread names do not inherit the source thread name - let mut thread = - build_thread_from_snapshot(thread_id, &config_snapshot, /*path*/ None); + let mut thread = build_thread_from_snapshot( + thread_id, + session_configured.session_id.to_string(), + &config_snapshot, + /*path*/ None, + ); thread.preview = preview_from_rollout_items(&history_items); thread.forked_from_id = Some(source_thread_id.to_string()); if include_turns { @@ -3091,6 +3103,7 @@ impl ThreadRequestProcessor { } thread }; + thread.session_id = session_configured.session_id.to_string(); thread.thread_source = forked_thread .config_snapshot() .await @@ -3116,7 +3129,6 @@ impl ThreadRequestProcessor { thread_response_active_permission_profile(config_snapshot.active_permission_profile); let response = ThreadForkResponse { - session_id: session_configured.session_id.to_string(), thread: thread.clone(), model: session_configured.model, model_provider: session_configured.model_provider_id, @@ -3680,8 +3692,10 @@ pub(crate) fn thread_from_stored_thread( thread.agent_role.clone(), ); let history = thread.history; + let thread_id = thread.thread_id.to_string(); let thread = Thread { - id: thread.thread_id.to_string(), + id: thread_id.clone(), + session_id: thread_id, forked_from_id: thread.forked_from_id.map(|id| id.to_string()), preview: thread.first_user_message.unwrap_or(thread.preview), ephemeral: false, @@ -3878,12 +3892,14 @@ fn permission_profile_trusts_project( fn build_thread_from_snapshot( thread_id: ThreadId, + session_id: String, config_snapshot: &ThreadConfigSnapshot, path: Option, ) -> Thread { let now = time::OffsetDateTime::now_utc().unix_timestamp(); Thread { id: thread_id.to_string(), + session_id, forked_from_id: None, preview: String::new(), ephemeral: config_snapshot.ephemeral, @@ -3909,7 +3925,12 @@ fn build_thread_from_loaded_snapshot( config_snapshot: &ThreadConfigSnapshot, loaded_thread: &CodexThread, ) -> Thread { - build_thread_from_snapshot(thread_id, config_snapshot, loaded_thread.rollout_path()) + build_thread_from_snapshot( + thread_id, + loaded_thread.session_configured().session_id.to_string(), + config_snapshot, + loaded_thread.rollout_path(), + ) } #[cfg(test)] diff --git a/codex-rs/app-server/src/request_processors/thread_summary.rs b/codex-rs/app-server/src/request_processors/thread_summary.rs index be3000e36a..f2de590a9a 100644 --- a/codex-rs/app-server/src/request_processors/thread_summary.rs +++ b/codex-rs/app-server/src/request_processors/thread_summary.rs @@ -263,8 +263,10 @@ pub(crate) fn summary_to_thread( fallback_cwd.clone() }); + let thread_id = conversation_id.to_string(); Thread { - id: conversation_id.to_string(), + id: thread_id.clone(), + session_id: thread_id, forked_from_id: None, preview, ephemeral: false, diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index a3783f995d..ad084f16c3 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -935,6 +935,7 @@ impl TurnRequestProcessor { Ok(stored_thread) => { let (mut thread, _) = thread_from_stored_thread(stored_thread, fallback_provider, &self.config.cwd); + thread.session_id = review_thread.session_configured().session_id.to_string(); self.thread_watch_manager .upsert_thread_silently(thread.clone()) .await; diff --git a/codex-rs/app-server/src/thread_status.rs b/codex-rs/app-server/src/thread_status.rs index 47da7e8cad..7315a13c02 100644 --- a/codex-rs/app-server/src/thread_status.rs +++ b/codex-rs/app-server/src/thread_status.rs @@ -889,6 +889,7 @@ mod tests { fn test_thread(thread_id: &str, source: codex_app_server_protocol::SessionSource) -> Thread { Thread { id: thread_id.to_string(), + session_id: thread_id.to_string(), forked_from_id: None, preview: String::new(), ephemeral: false, diff --git a/codex-rs/app-server/tests/suite/v2/review.rs b/codex-rs/app-server/tests/suite/v2/review.rs index 277885c5a8..bf0271f821 100644 --- a/codex-rs/app-server/tests/suite/v2/review.rs +++ b/codex-rs/app-server/tests/suite/v2/review.rs @@ -363,6 +363,7 @@ async fn review_start_with_detached_delivery_returns_new_thread_id() -> Result<( let started: ThreadStartedNotification = serde_json::from_value(notification.params.expect("params must be present"))?; assert_eq!(started.thread.id, review_thread_id); + assert_eq!(started.thread.session_id, review_thread_id); Ok(()) } 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 4a6644e30a..8394f9839e 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_fork.rs @@ -101,20 +101,28 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> { ) .await??; let fork_result = fork_resp.result.clone(); - let ThreadForkResponse { - session_id, thread, .. - } = to_response::(fork_resp)?; + let ThreadForkResponse { thread, .. } = to_response::(fork_resp)?; // Wire contract: thread title field is `name`, serialized as null when unset. let thread_json = fork_result .get("thread") .and_then(Value::as_object) .expect("thread/fork result.thread must be an object"); + assert_eq!( + thread_json.get("sessionId").and_then(Value::as_str), + Some(thread.session_id.as_str()), + "forked threads should serialize `sessionId` on the thread object" + ); assert_eq!( thread_json.get("name"), Some(&Value::Null), "forked threads do not inherit a name; expected `name: null`" ); + assert_eq!( + fork_result.get("sessionId"), + None, + "thread/fork should not serialize a top-level `sessionId`" + ); let after_contents = std::fs::read_to_string(&original_path)?; assert_eq!( @@ -123,7 +131,7 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> { ); assert_ne!(thread.id, conversation_id); - assert_eq!(session_id, thread.id); + assert_eq!(thread.session_id, thread.id); assert_eq!(thread.forked_from_id, Some(conversation_id.clone())); assert_eq!(thread.preview, preview); assert_eq!(thread.model_provider, "mock_provider"); 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..80254d8f47 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_list.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_list.rs @@ -879,6 +879,7 @@ async fn thread_list_filters_by_source_kind_subagent_thread_spawn() -> Result<() assert_eq!(ids, vec![subagent_id.as_str()]); assert_ne!(cli_id, subagent_id); assert!(matches!(data[0].source, SessionSource::SubAgent(_))); + assert_eq!(data[0].session_id, subagent_id); Ok(()) } diff --git a/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs b/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs index 430f8e5392..c78e9b8152 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs @@ -77,6 +77,7 @@ async fn thread_metadata_update_patches_git_branch_and_returns_updated_thread() to_response::(update_resp)?; assert_eq!(updated.id, thread.id); + assert_eq!(updated.session_id, thread.session_id); assert_eq!( updated.git_info, Some(GitInfo { @@ -90,6 +91,10 @@ async fn thread_metadata_update_patches_git_branch_and_returns_updated_thread() .get("thread") .and_then(Value::as_object) .expect("thread/metadata/update result.thread must be an object"); + assert_eq!( + updated_thread_json.get("sessionId").and_then(Value::as_str), + Some(thread.session_id.as_str()) + ); let updated_git_info_json = updated_thread_json .get("gitInfo") .and_then(Value::as_object) diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index 175da83016..7d7de25515 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -270,10 +270,11 @@ async fn thread_resume_tracks_thread_initialized_analytics() -> Result<()> { mcp.read_stream_until_response_message(RequestId::Integer(resume_id)), ) .await??; - let ThreadResumeResponse { - session_id, thread, .. - } = to_response::(resume_resp)?; - assert!(!session_id.is_empty(), "session id should not be empty"); + let ThreadResumeResponse { thread, .. } = to_response::(resume_resp)?; + assert!( + !thread.session_id.is_empty(), + "session id should not be empty" + ); assert_eq!(thread.thread_source, Some(ThreadSource::User)); let payload = wait_for_analytics_payload(&server, DEFAULT_READ_TIMEOUT).await?; diff --git a/codex-rs/app-server/tests/suite/v2/thread_rollback.rs b/codex-rs/app-server/tests/suite/v2/thread_rollback.rs index 3487b9e36a..5f79db0e26 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_rollback.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_rollback.rs @@ -119,11 +119,16 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<() .and_then(Value::as_object) .expect("thread/rollback result.thread must be an object"); assert_eq!(rolled_back_thread.name, None); + assert_eq!(rolled_back_thread.session_id, thread.session_id); assert_eq!( thread_json.get("name"), Some(&Value::Null), "thread/rollback must serialize `name: null` when unset" ); + assert_eq!( + thread_json.get("sessionId").and_then(Value::as_str), + Some(thread.session_id.as_str()) + ); assert_eq!(rolled_back_thread.turns.len(), 1); assert_eq!(rolled_back_thread.status, ThreadStatus::Idle); diff --git a/codex-rs/app-server/tests/suite/v2/thread_start.rs b/codex-rs/app-server/tests/suite/v2/thread_start.rs index e300e562d9..4da6b54ed1 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_start.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_start.rs @@ -121,12 +121,14 @@ async fn thread_start_creates_thread_and_emits_started() -> Result<()> { .await??; let resp_result = resp.result.clone(); let ThreadStartResponse { - session_id, thread, model_provider, .. } = to_response::(resp)?; - assert!(!session_id.is_empty(), "session id should not be empty"); + assert!( + !thread.session_id.is_empty(), + "session id should not be empty" + ); assert!(!thread.id.is_empty(), "thread id should not be empty"); assert!( thread.preview.is_empty(), @@ -155,11 +157,21 @@ async fn thread_start_creates_thread_and_emits_started() -> Result<()> { .get("thread") .and_then(Value::as_object) .expect("thread/start result.thread must be an object"); + assert_eq!( + thread_json.get("sessionId").and_then(Value::as_str), + Some(thread.session_id.as_str()), + "new threads should serialize `sessionId` on the thread object" + ); assert_eq!( thread_json.get("name"), Some(&Value::Null), "new threads should serialize `name: null`" ); + assert_eq!( + resp_result.get("sessionId"), + None, + "thread/start should not serialize a top-level `sessionId`" + ); assert_eq!( thread_json.get("ephemeral").and_then(Value::as_bool), Some(false), diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 5ff19c744b..b4bbb2c1d5 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -1060,7 +1060,7 @@ fn session_configured_from_thread_start_response( config: &Config, ) -> Result { session_configured_from_thread_response( - &response.session_id, + &response.thread.session_id, &response.thread.id, response.thread.name.clone(), response.thread.path.clone(), @@ -1085,7 +1085,7 @@ fn session_configured_from_thread_resume_response( config: &Config, ) -> Result { session_configured_from_thread_response( - &response.session_id, + &response.thread.session_id, &response.thread.id, response.thread.name.clone(), response.thread.path.clone(), diff --git a/codex-rs/exec/src/lib_tests.rs b/codex-rs/exec/src/lib_tests.rs index ad528a194d..caa2921d19 100644 --- a/codex-rs/exec/src/lib_tests.rs +++ b/codex-rs/exec/src/lib_tests.rs @@ -244,6 +244,7 @@ async fn resume_lookup_model_providers_filters_only_last_lookup() { fn turn_items_for_thread_returns_matching_turn_items() { let thread = AppServerThread { id: "thread-1".to_string(), + session_id: "thread-1".to_string(), forked_from_id: None, preview: String::new(), ephemeral: false, @@ -473,9 +474,9 @@ async fn session_configured_from_thread_response_uses_permission_profile_from_re fn sample_thread_start_response() -> ThreadStartResponse { ThreadStartResponse { - session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(), thread: codex_app_server_protocol::Thread { id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(), + session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(), forked_from_id: None, preview: String::new(), ephemeral: false, diff --git a/codex-rs/tui/src/app/loaded_threads.rs b/codex-rs/tui/src/app/loaded_threads.rs index 49e5568596..0ab8e14ee3 100644 --- a/codex-rs/tui/src/app/loaded_threads.rs +++ b/codex-rs/tui/src/app/loaded_threads.rs @@ -118,6 +118,7 @@ mod tests { fn test_thread(thread_id: ThreadId, source: SessionSource) -> Thread { Thread { id: thread_id.to_string(), + session_id: thread_id.to_string(), forked_from_id: None, preview: String::new(), ephemeral: false, diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index 3f061fa516..8598c035a9 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -2940,6 +2940,7 @@ async fn inactive_thread_started_notification_initializes_replay_session() -> Re ServerNotification::ThreadStarted(ThreadStartedNotification { thread: Thread { id: agent_thread_id.to_string(), + session_id: agent_thread_id.to_string(), forked_from_id: None, preview: "agent thread".to_string(), ephemeral: false, @@ -3022,6 +3023,7 @@ async fn inactive_thread_started_notification_preserves_primary_model_when_path_ ServerNotification::ThreadStarted(ThreadStartedNotification { thread: Thread { id: agent_thread_id.to_string(), + session_id: agent_thread_id.to_string(), forked_from_id: None, preview: "agent thread".to_string(), ephemeral: false, @@ -3077,6 +3079,7 @@ async fn thread_read_session_state_does_not_reuse_primary_permission_profile() { let thread = Thread { id: read_thread_id.to_string(), + session_id: read_thread_id.to_string(), forked_from_id: None, preview: "read thread".to_string(), ephemeral: false, @@ -5041,6 +5044,7 @@ async fn thread_rollback_response_discards_queued_active_thread_events() { &ThreadRollbackResponse { thread: Thread { id: thread_id.to_string(), + session_id: thread_id.to_string(), forked_from_id: None, preview: String::new(), ephemeral: false, diff --git a/codex-rs/tui/src/app/thread_session_state.rs b/codex-rs/tui/src/app/thread_session_state.rs index ac6f6311ca..15d034cc3d 100644 --- a/codex-rs/tui/src/app/thread_session_state.rs +++ b/codex-rs/tui/src/app/thread_session_state.rs @@ -322,6 +322,7 @@ mod tests { }; let read_thread = Thread { id: read_thread_id.to_string(), + session_id: read_thread_id.to_string(), forked_from_id: None, preview: "read thread".to_string(), ephemeral: false, diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 12f2fdcf62..16f2f05d0c 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -1813,9 +1813,9 @@ mod tests { let forked_from_id = ThreadId::new(); let read_only_profile = PermissionProfile::read_only(); let response = ThreadResumeResponse { - session_id: ThreadId::new().to_string(), thread: codex_app_server_protocol::Thread { id: thread_id.to_string(), + session_id: ThreadId::new().to_string(), forked_from_id: Some(forked_from_id.to_string()), preview: "hello".to_string(), ephemeral: false, diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index 171a1c3513..08bd6657bf 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -5684,6 +5684,7 @@ session_picker_view = "dense" let thread_id = ThreadId::new(); let thread = Thread { id: thread_id.to_string(), + session_id: thread_id.to_string(), forked_from_id: None, preview: String::from("remote thread"), ephemeral: false, @@ -5717,6 +5718,7 @@ session_picker_view = "dense" let thread_id = ThreadId::new(); let thread = Thread { id: thread_id.to_string(), + session_id: thread_id.to_string(), forked_from_id: None, preview: String::from("preview"), ephemeral: false, @@ -5783,6 +5785,7 @@ session_picker_view = "dense" let thread_id = ThreadId::new(); let thread = Thread { id: thread_id.to_string(), + session_id: thread_id.to_string(), forked_from_id: None, preview: String::from("preview"), ephemeral: false, @@ -5839,6 +5842,7 @@ session_picker_view = "dense" let thread_id = ThreadId::new(); let thread = Thread { id: thread_id.to_string(), + session_id: thread_id.to_string(), forked_from_id: None, preview: String::from("preview"), ephemeral: false,