mirror of
https://github.com/openai/codex.git
synced 2026-04-29 02:41:12 +03:00
app-server: expose loaded thread status via read/list and notifications (#11786)
Motivation - Today, a newly connected client has no direct way to determine the current runtime status of threads from read/list responses alone. - This forces clients to infer state from transient events, which can lead to stale or inconsistent UI when reconnecting or attaching late. Changes - Add `status` to `thread/read` responses. - Add `statuses` to `thread/list` responses. - Emit `thread/status/changed` notifications with `threadId` and the new status. - Track runtime status for all loaded threads and default unknown threads to `idle`. - Update protocol/docs/tests/schema fixtures for the revised API. Testing - Validated protocol API changes with automated protocol tests and regenerated schema/type fixtures. - Validated app-server behavior with unit and integration test suites, including status transitions and notifications.
This commit is contained in:
committed by
GitHub
parent
216fe7f2ef
commit
1f54496c48
@@ -14,6 +14,7 @@ use codex_app_server_protocol::ThreadResumeParams;
|
||||
use codex_app_server_protocol::ThreadResumeResponse;
|
||||
use codex_app_server_protocol::ThreadStartParams;
|
||||
use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_app_server_protocol::ThreadStatus;
|
||||
use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStatus;
|
||||
use codex_app_server_protocol::UserInput;
|
||||
@@ -129,6 +130,7 @@ async fn thread_resume_returns_rollout_history() -> Result<()> {
|
||||
assert_eq!(thread.cli_version, "0.0.0");
|
||||
assert_eq!(thread.source, SessionSource::Cli);
|
||||
assert_eq!(thread.git_info, None);
|
||||
assert_eq!(thread.status, ThreadStatus::Idle);
|
||||
|
||||
assert_eq!(
|
||||
thread.turns.len(),
|
||||
@@ -178,6 +180,7 @@ async fn thread_resume_without_overrides_does_not_change_updated_at_or_mtime() -
|
||||
let ThreadResumeResponse { thread, .. } = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
|
||||
assert_eq!(thread.updated_at, rollout.expected_updated_at);
|
||||
assert_eq!(thread.status, ThreadStatus::Idle);
|
||||
|
||||
let after_modified = std::fs::metadata(&rollout.rollout_file_path)?.modified()?;
|
||||
assert_eq!(after_modified, rollout.before_modified);
|
||||
@@ -283,11 +286,16 @@ async fn thread_resume_keeps_in_flight_turn_streaming() -> Result<()> {
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
timeout(
|
||||
let resume_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
secondary.read_stream_until_response_message(RequestId::Integer(resume_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadResumeResponse {
|
||||
thread: resumed_thread,
|
||||
..
|
||||
} = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
assert_ne!(resumed_thread.status, ThreadStatus::NotLoaded);
|
||||
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
@@ -582,8 +590,15 @@ async fn thread_resume_rejoins_running_thread_even_with_override_mismatch() -> R
|
||||
primary.read_stream_until_response_message(RequestId::Integer(resume_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadResumeResponse { model, .. } = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
let ThreadResumeResponse { thread, model, .. } =
|
||||
to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
assert_eq!(model, "gpt-5.1-codex-max");
|
||||
assert_eq!(
|
||||
thread.status,
|
||||
ThreadStatus::Active {
|
||||
active_flags: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
@@ -630,6 +645,7 @@ async fn thread_resume_with_overrides_defers_updated_at_until_turn_start() -> Re
|
||||
} = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
|
||||
assert_eq!(resumed_thread.updated_at, expected_updated_at);
|
||||
assert_eq!(resumed_thread.status, ThreadStatus::Idle);
|
||||
|
||||
let after_resume_modified = std::fs::metadata(&rollout_file_path)?.modified()?;
|
||||
assert_eq!(after_resume_modified, before_modified);
|
||||
@@ -761,6 +777,7 @@ async fn thread_resume_prefers_path_over_thread_id() -> Result<()> {
|
||||
} = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
assert_eq!(resumed.id, thread.id);
|
||||
assert_eq!(resumed.path, thread.path);
|
||||
assert_eq!(resumed.status, ThreadStatus::Idle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -809,6 +826,7 @@ async fn thread_resume_supports_history_and_overrides() -> Result<()> {
|
||||
assert!(!resumed.id.is_empty());
|
||||
assert_eq!(model_provider, "mock_provider");
|
||||
assert_eq!(resumed.preview, history_text);
|
||||
assert_eq!(resumed.status, ThreadStatus::Idle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -951,6 +969,7 @@ async fn thread_resume_accepts_personality_override() -> Result<()> {
|
||||
)
|
||||
.await??;
|
||||
let resume: ThreadResumeResponse = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
assert_eq!(resume.thread.status, ThreadStatus::Idle);
|
||||
|
||||
let turn_id = secondary
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
|
||||
Reference in New Issue
Block a user