Expose retained command sessions in incremental world state (#59633)

## Why

A Code Mode script can start a command without returning its session handle. Make retained sessions visible to the model so it can find running commands and collect exited results with `write_stdin`.

## What changed

- Add the disabled-by-default `command_sessions_world_state` feature.
- Publish snapshots of up to 16 retained sessions with their IDs, running or exited status, and whether they are in use. Report omitted sessions and suppress unchanged updates.
- Preserve session visibility when `write_stdin` is unavailable and refresh snapshots after compaction. Clear stale session handles after restart or when the feature is disabled.

## Testing

Add an end-to-end scenario and model-visible history snapshot covering a dropped command handle, unchanged state, unavailable polling, compaction, exit and result collection, and stale-handle cleanup after restart.

GitOrigin-RevId: 45704d8c6403149997231aafb8496c12879aff23
This commit is contained in:
jif
2026-11-05 11:14:26 +00:00
committed by copyberry
parent c0100ad4ec
commit d4902cedbf
12 changed files with 675 additions and 1 deletions
@@ -0,0 +1,29 @@
//! Read-only snapshots of retained command sessions, including uncollected exits.
//! Handles stay private until their initial exec_command collection finishes.
use super::UnifiedExecProcessManager;
use crate::context::world_state::CommandSession;
use crate::context::world_state::CommandSessionStatus;
use crate::context::world_state::CommandSessionsState;
use std::sync::atomic::Ordering;
pub(crate) async fn command_sessions_state(
manager: &UnifiedExecProcessManager,
) -> CommandSessionsState {
let store = manager.process_store.lock().await;
CommandSessionsState::new(
store
.processes
.values()
.filter(|entry| !entry.initial_exec_command_active.load(Ordering::Acquire))
.map(|entry| CommandSession {
session_id: entry.process_id,
status: if entry.process.has_exited() {
CommandSessionStatus::Exited
} else {
CommandSessionStatus::Running
},
in_use: entry.process.interaction_lock().try_lock_owned().is_err(),
}),
)
}
+2
View File
@@ -44,6 +44,7 @@ use crate::tools::network_approval::DeferredNetworkApproval;
use codex_core_plugins::PluginMetricsSidecar;
mod async_watcher;
mod command_sessions;
mod errors;
mod oneshot;
pub(crate) mod output;
@@ -58,6 +59,7 @@ pub(crate) fn set_deterministic_process_ids_for_tests(enabled: bool) {
process_manager::set_deterministic_process_ids_for_tests(enabled);
}
pub(crate) use command_sessions::command_sessions_state;
pub(crate) use errors::UnifiedExecError;
pub(crate) use process::UnifiedExecProcess;
pub(crate) use stdin_approval::TerminalPermissions;