Add field to Thread object for the latest rename set for a given thread (#12301)

Exposes through the app server updated names set for a thread. This
enables other surfaces to use the core as the source of truth for thread
naming. `threadName` is gathered using the helper functions used to
interact with `session_index.jsonl`, and is hydrated in:
- `thread/list`
- `thread/read`
- `thread/resume`
- `thread/unarchive`
- `thread/rollback`

We don't do this for `thread/start` and `thread/fork`.
This commit is contained in:
natea-oai
2026-02-20 18:26:57 -08:00
committed by GitHub
parent 53bcfaf42d
commit 936e744c93
21 changed files with 386 additions and 7 deletions

View File

@@ -18,6 +18,7 @@ use codex_app_server_protocol::ThreadStatus;
use codex_app_server_protocol::TurnStatus;
use codex_app_server_protocol::UserInput;
use pretty_assertions::assert_eq;
use serde_json::Value;
use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
@@ -70,8 +71,20 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(fork_id)),
)
.await??;
let fork_result = fork_resp.result.clone();
let ThreadForkResponse { thread, .. } = to_response::<ThreadForkResponse>(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("name"),
Some(&Value::Null),
"forked threads do not inherit a name; expected `name: null`"
);
let after_contents = std::fs::read_to_string(&original_path)?;
assert_eq!(
after_contents, original_contents,
@@ -87,6 +100,7 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> {
assert_ne!(thread_path, original_path);
assert!(thread.cwd.is_absolute());
assert_eq!(thread.source, SessionSource::VsCode);
assert_eq!(thread.name, None);
assert_eq!(
thread.turns.len(),
@@ -115,6 +129,16 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> {
mcp.read_stream_until_notification_message("thread/started"),
)
.await??;
let started_params = notif.params.clone().expect("params must be present");
let started_thread_json = started_params
.get("thread")
.and_then(Value::as_object)
.expect("thread/started params.thread must be an object");
assert_eq!(
started_thread_json.get("name"),
Some(&Value::Null),
"thread/started must serialize `name: null` when unset"
);
let started: ThreadStartedNotification =
serde_json::from_value(notif.params.expect("params must be present"))?;
assert_eq!(started.thread, thread);