app-server: use profile ids in v2 permission params (#23360)

## Why

The v2 app-server permission profile fields are experimental, but the
previous migration kept a legacy object payload for profile selection.
That made clients aware of server-owned `activePermissionProfile`
metadata such as `extends`, and it kept a
`legacy_additional_writable_roots` path even though
`runtimeWorkspaceRoots` now owns runtime workspace-root selection.

This PR makes the client contract match the intended model: clients
select a permission profile by id, and the server resolves and reports
active profile provenance in response payloads.

Follow-up to #22611.

## What Changed

- Changed `thread/start`, `thread/resume`, `thread/fork`, and
`turn/start` permission profile selection to plain profile id strings.
- Changed `command/exec.permissionProfile` to a plain profile id string
for the same client/server ownership split.
- Removed `PermissionProfileSelectionParams` and the legacy `{ type:
"profile", modifications: [...] }` compatibility deserializer.
- Updated app-server, TUI, and `codex exec` call sites to send only ids,
while keeping `activePermissionProfile` as server response metadata.
- Updated app-server docs and schema fixtures for the revised
`command/exec.permissionProfile` shape.

## Verification

- `cargo test -p codex-app-server-protocol`
- `RUST_MIN_STACK=8388608 cargo test -p codex-app-server`
- `cargo test -p codex-exec`
- `RUST_MIN_STACK=8388608 cargo test -p codex-tui`

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23360).
* #23368
* __->__ #23360
This commit is contained in:
Michael Bolin
2026-05-18 17:28:50 -07:00
committed by GitHub
Unverified
parent 5696167fe8
commit 3fd79b7986
21 changed files with 79 additions and 337 deletions
+3 -13
View File
@@ -2747,7 +2747,6 @@ export type Config = { stableField: Keep, unstableField: string | null } & ({ [k
let _guard = TempDirGuard(output_dir.clone());
let path = output_dir.join("CommandExecParams.ts");
let content = r#"import type { CommandExecTerminalSize } from "./CommandExecTerminalSize";
import type { ActivePermissionProfile } from "./ActivePermissionProfile";
import type { SandboxPolicy } from "./SandboxPolicy";
export type CommandExecParams = {/**
@@ -2770,12 +2769,12 @@ size?: CommandExecTerminalSize | null, /**
*/
sandboxPolicy?: SandboxPolicy | null,
/**
* Optional active permissions profile for this command.
* Optional active permissions profile id for this command.
*
* Defaults to the user's configured permissions when omitted. Cannot be
* combined with `sandboxPolicy`.
*/
permissionProfile?: ActivePermissionProfile | null};
permissionProfile?: string | null};
"#;
fs::write(&path, content)?;
@@ -2788,16 +2787,7 @@ permissionProfile?: ActivePermissionProfile | null};
filter_experimental_type_fields_ts(&output_dir, &[&CUSTOM_FIELD])?;
let filtered = fs::read_to_string(&path)?;
assert_eq!(
filtered.contains("permissionProfile?: ActivePermissionProfile"),
false
);
assert_eq!(
filtered.contains(
r#"import type { ActivePermissionProfile } from "./ActivePermissionProfile";"#
),
false
);
assert_eq!(filtered.contains("permissionProfile?: string"), false);
assert_eq!(filtered.contains("sandboxPolicy?: SandboxPolicy"), true);
assert_eq!(
filtered.contains(r#"import type { SandboxPolicy } from "./SandboxPolicy";"#),