Add app-server background terminal process APIs (#26041)

## Summary

Codex Apps needs app-server as the source of truth for chat-started
background terminals instead of guessing from local process trees.

This PR adds experimental v2 APIs to list and terminate background
terminals for a loaded thread using app-server process ids, so clients
can manage background terminals without local PID discovery.

## Changes

- `thread/backgroundTerminals/list` returns paginated background
terminal records with `itemId`, app-server `processId`, `command`,
`cwd`, nullable `osPid`, nullable `cpuPercent`, and nullable `rssKb`.
- `thread/backgroundTerminals/terminate` terminates one running
background terminal by app-server `processId` and returns whether a
process was terminated.
- Background terminal list and terminate operations use unified-exec
process manager state as their source of truth.
This commit is contained in:
Eric Traut
2026-06-10 11:18:09 -07:00
committed by GitHub
parent 2704ecea9a
commit a1a8807e9d
16 changed files with 773 additions and 36 deletions
@@ -574,6 +574,18 @@ client_request_definitions! {
serialization: thread_id(params.thread_id),
response: v2::ThreadBackgroundTerminalsCleanResponse,
},
#[experimental("thread/backgroundTerminals/list")]
ThreadBackgroundTerminalsList => "thread/backgroundTerminals/list" {
params: v2::ThreadBackgroundTerminalsListParams,
serialization: thread_id(params.thread_id),
response: v2::ThreadBackgroundTerminalsListResponse,
},
#[experimental("thread/backgroundTerminals/terminate")]
ThreadBackgroundTerminalsTerminate => "thread/backgroundTerminals/terminate" {
params: v2::ThreadBackgroundTerminalsTerminateParams,
serialization: thread_id(params.thread_id),
response: v2::ThreadBackgroundTerminalsTerminateResponse,
},
ThreadRollback => "thread/rollback" {
params: v2::ThreadRollbackParams,
serialization: thread_id(params.thread_id),
@@ -2936,6 +2948,54 @@ mod tests {
Ok(())
}
#[test]
fn serialize_thread_background_terminals_list() -> Result<()> {
let request = ClientRequest::ThreadBackgroundTerminalsList {
request_id: RequestId::Integer(8),
params: v2::ThreadBackgroundTerminalsListParams {
thread_id: "thr_123".to_string(),
cursor: None,
limit: None,
},
};
assert_eq!(
json!({
"method": "thread/backgroundTerminals/list",
"id": 8,
"params": {
"threadId": "thr_123",
"cursor": null,
"limit": null
}
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_thread_background_terminals_terminate() -> Result<()> {
let request = ClientRequest::ThreadBackgroundTerminalsTerminate {
request_id: RequestId::Integer(8),
params: v2::ThreadBackgroundTerminalsTerminateParams {
thread_id: "thr_123".to_string(),
process_id: "42".to_string(),
},
};
assert_eq!(
json!({
"method": "thread/backgroundTerminals/terminate",
"id": 8,
"params": {
"threadId": "thr_123",
"processId": "42"
}
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_thread_realtime_start() -> Result<()> {
let request = ClientRequest::ThreadRealtimeStart {
@@ -935,6 +935,57 @@ pub struct ThreadBackgroundTerminalsCleanParams {
#[ts(export_to = "v2/")]
pub struct ThreadBackgroundTerminalsCleanResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadBackgroundTerminalsListParams {
pub thread_id: String,
/// Opaque pagination cursor returned by a previous call.
#[ts(optional = nullable)]
pub cursor: Option<String>,
/// Optional page size.
#[ts(optional = nullable)]
pub limit: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadBackgroundTerminal {
pub item_id: String,
pub process_id: String,
pub command: String,
pub cwd: AbsolutePathBuf,
pub os_pid: Option<u32>,
pub cpu_percent: Option<f64>,
pub rss_kb: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadBackgroundTerminalsListResponse {
pub data: Vec<ThreadBackgroundTerminal>,
/// Opaque cursor to pass to the next call to continue after the last item.
/// If None, there are no more items to return.
pub next_cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadBackgroundTerminalsTerminateParams {
pub thread_id: String,
pub process_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadBackgroundTerminalsTerminateResponse {
pub terminated: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]