app-server: expose thread permission profiles (#18278)

## Why

The `PermissionProfile` migration needs app-server clients to see the
same constrained permission model that core is using at runtime. Before
this PR, thread lifecycle responses only exposed the legacy
`SandboxPolicy` shape, so clients still had to infer active permissions
from sandbox fields. That makes downstream resume, fork, and override
flows harder to make `PermissionProfile`-first.

External sandbox policies are intentionally excluded from this canonical
view. External enforcement cannot be round-tripped as a
`PermissionProfile`, and exposing a lossy root-write profile would let
clients accidentally change sandbox semantics if they echo the profile
back later.

## What changed

- Adds the app-server v2 `PermissionProfile` wire shape, including
filesystem permissions and glob scan depth metadata.
- Adds `PermissionProfileNetworkPermissions` so the profile response
does not expose active network state through the older
additional-permissions naming.
- Returns `permissionProfile` from thread start, resume, and fork
responses when the active sandbox can be represented as a
`PermissionProfile`.
- Keeps legacy `sandbox` in those responses for compatibility and
documents `permissionProfile` as canonical when present.
- Makes lifecycle `permissionProfile` nullable and returns `null` for
`ExternalSandbox` to avoid exposing a lossy profile.
- Regenerates the app-server JSON schema and TypeScript fixtures.

## Verification

- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server
thread_response_permission_profile_omits_external_sandbox --
--nocapture`
- `cargo check --tests -p codex-analytics -p codex-exec -p codex-tui`
- `just fix -p codex-app-server-protocol -p codex-app-server -p
codex-analytics -p codex-exec -p codex-tui`

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18278).
* #18279
* __->__ #18278
This commit is contained in:
Michael Bolin
2026-04-21 23:52:56 -07:00
committed by GitHub
parent 3a451b6321
commit 5eab9ff8ca
18 changed files with 1416 additions and 15 deletions

View File

@@ -1435,6 +1435,7 @@ mod tests {
#[test]
fn serialize_client_response() -> Result<()> {
let cwd = absolute_path("/tmp");
let response = ClientResponse::ThreadStart {
request_id: RequestId::Integer(7),
response: v2::ThreadStartResponse {
@@ -1448,7 +1449,7 @@ mod tests {
updated_at: 2,
status: v2::ThreadStatus::Idle,
path: None,
cwd: absolute_path("/tmp"),
cwd: cwd.clone(),
cli_version: "0.0.0".to_string(),
source: v2::SessionSource::Exec,
agent_nickname: None,
@@ -1460,11 +1461,18 @@ mod tests {
model: "gpt-5".to_string(),
model_provider: "openai".to_string(),
service_tier: None,
cwd: absolute_path("/tmp"),
cwd: cwd.clone(),
instruction_sources: vec![absolute_path("/tmp/AGENTS.md")],
approval_policy: v2::AskForApproval::OnFailure,
approvals_reviewer: v2::ApprovalsReviewer::User,
sandbox: v2::SandboxPolicy::DangerFullAccess,
permission_profile: Some(
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
&codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
cwd.as_path(),
)
.into(),
),
reasoning_effort: None,
},
};
@@ -1507,6 +1515,24 @@ mod tests {
"sandbox": {
"type": "dangerFullAccess"
},
"permissionProfile": {
"network": {
"enabled": true,
},
"fileSystem": {
"entries": [
{
"path": {
"type": "special",
"value": {
"kind": "root",
},
},
"access": "write",
},
],
},
},
"reasoningEffort": null
}
}),