Files
codex/codex-rs/app-server/src/request_processors/thread_input.rs
T
rhan-oai b6333bb1bb Enforce subagent ownership across app-server inputs (#40464)
## Why

Parent-owned Multi-Agent V2 subagents must remain controlled by their parent,
but several app-server endpoints could still mutate them directly.

## What changed

- Centralize the direct-input ownership check and apply it to turn injection,
  MCP calls, reviews, compaction, rollback and revert, shell and Guardian
  actions, realtime requests, and goal mutations.
- Enforce the same policy for goal mutations on unloaded threads while keeping
  goal reads and turn interruption available.
- Document the affected endpoint behavior and continue exposing
  `canAcceptDirectInput: false` for parent-owned subagents.

## Testing

- Cover direct-input rejection for both legacy and paginated thread history.
- Cover goal mutation ownership checks for unloaded V2 subagents, V1
  subagents, and non-subagent V2 threads.

GitOrigin-RevId: 791b7af98490aed5eab4e672b90661acad11ec23
2026-08-24 19:44:14 +00:00

38 lines
1.2 KiB
Rust

use super::invalid_request;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_core::CodexThread;
use codex_protocol::protocol::MultiAgentVersion;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
pub(super) const DIRECT_INPUT_TO_MULTI_AGENT_V2_SUBAGENT_ERROR: &str =
"direct app-server input is not allowed for multi-agent v2 sub-agents";
/// Mirrors the ownership policy in request validation and thread capability responses.
pub(super) fn can_accept_direct_input(
multi_agent_version: Option<MultiAgentVersion>,
session_source: &SessionSource,
) -> bool {
multi_agent_version != Some(MultiAgentVersion::V2)
|| !matches!(
session_source,
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. })
)
}
pub(super) async fn ensure_direct_input_allowed(
thread: &CodexThread,
) -> Result<(), JSONRPCErrorError> {
let config_snapshot = thread.config_snapshot().await;
if !can_accept_direct_input(
thread.multi_agent_version(),
&config_snapshot.session_source,
) {
return Err(invalid_request(
DIRECT_INPUT_TO_MULTI_AGENT_V2_SUBAGENT_ERROR,
));
}
Ok(())
}