Commit Graph

3310 Commits

  • chore: make token usage async (#23305)
    Make the `TokenUsageContributor` async. This will be required for future
    extension and it's basically free
  • chore: goal resumed metrics (#23301)
    Add metrics for goal resume
  • chore: isolate thread goal storage behind GoalStore (#23295)
    ## Why
    
    Thread goal persistence is being prepared for a dedicated storage
    boundary. Before that split, goal-specific reads, writes, accounting,
    and cleanup were exposed directly on `StateRuntime`, so core and
    app-server callsites stayed coupled to the full runtime instead of a
    goal-specific store.
    
    This PR introduces that boundary without changing the goal wire API or
    current persistence behavior. Callers now go through
    `StateRuntime::thread_goals()` and the new `GoalStore`, while
    `GoalStore` still uses the existing state DB pool underneath.
    
    ## What changed
    
    - Added `GoalStore` in `state/src/runtime/goals.rs` and exposed it from
    `StateRuntime` via `thread_goals()`.
    - Moved thread-goal reads, writes, status updates, pause, delete, and
    usage accounting onto `GoalStore`.
    - Updated core session goal handling, app-server goal RPCs, resume
    snapshots, and goal tests to use the store boundary.
    - Kept thread deletion responsible for cascading goal cleanup by
    deleting the goal through the store only after a thread row is removed.
    
    ## Testing
    
    - Existing goal persistence, resume, and accounting tests were updated
    to exercise the new `GoalStore` access path.
  • Make extension lifecycle hooks async (#23291)
    ## Why
    
    Extension lifecycle hooks sit on the host/extension boundary, but the
    current trait surface only allows synchronous callbacks. That forces
    extensions that need to seed, rehydrate, observe, or flush
    extension-owned state during thread and turn transitions to either block
    inside the callback or move async work into separate host plumbing.
    
    This PR makes those lifecycle callbacks awaitable so extension
    implementations can perform async work directly at the lifecycle point
    where the host already has the relevant session, thread, or turn stores
    available.
    
    ## What changed
    
    - Makes `ThreadLifecycleContributor` and `TurnLifecycleContributor`
    async in `codex-extension-api`.
    - Awaits thread start/resume/stop and turn start/stop/abort lifecycle
    callbacks from `codex-core`.
    - Updates the guardian and memories extensions to implement the async
    lifecycle trait surface.
    - Updates the existing lifecycle tests to use async contributor
    implementations.
    - Adds `async-trait` to the crates that now expose or implement these
    async object-safe lifecycle traits.
    
    ## Testing
    
    - Existing `codex-core` lifecycle tests were updated to cover async
    implementations for thread stop and turn abort ordering.
  • test: reduce core sandbox policy test setup (#23036)
    ## Why
    
    `SandboxPolicy` is a legacy compatibility shape, but several core tests
    still used it for ordinary turn setup even when the runtime path now
    carries `PermissionProfile`. With the first cleanup PR merged, this
    follow-up trims more core test scaffolding so remaining `SandboxPolicy`
    matches are easier to classify as production compatibility,
    legacy-boundary coverage, or explicit conversion tests.
    
    ## What Changed
    
    - Updated apply-patch handler and runtime tests to pass
    `PermissionProfile` directly.
    - Changed sandboxing test helpers to build permission profiles without
    first creating `SandboxPolicy` values.
    - Converted request-permissions integration turns to pass
    `PermissionProfile` through the test helper, leaving legacy sandbox
    projection at the `Op::UserTurn` boundary.
    - Converted unified exec integration helpers and direct turn submissions
    to use `PermissionProfile` values instead of `SandboxPolicy` setup.
    - Removed now-unused `SandboxPolicy` imports from the touched core
    tests.
    
    ## Test Plan
    
    - `just fmt`
    - `cargo test -p codex-core --lib tools::sandboxing::tests`
    - `cargo test -p codex-core --lib tools::runtimes::apply_patch::tests`
    - `cargo test -p codex-core --lib tools::handlers::apply_patch::tests`
    - `cargo test -p codex-core --lib unified_exec::process_manager::tests`
    - `cargo test -p codex-core --test all request_permissions::`
    - `cargo test -p codex-core --test all unified_exec::`
    - `just fix -p codex-core`
  • Make multi-agent v2 tool namespace configurable (#23147)
    ## Summary
    - Add `features.multi_agent_v2.tool_namespace` with config/schema
    validation for Responses-compatible namespace values.
    - Thread the resolved namespace into `ToolsConfig` for normal turns and
    review turns.
    - Wrap MultiAgentV2 tool specs and registry names in the configured
    namespace when namespace tools are supported, while falling back to the
    plain tool names when they are not.
    
    ## Validation
    - `just fmt`
    - `just write-config-schema`
    - `cargo test -p codex-features multi_agent_v2_feature_config --
    --nocapture`
    - `cargo test -p codex-core test_build_specs_multi_agent_v2 --
    --nocapture`
    - `cargo test -p codex-core multi_agent_v2_config -- --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_rejects_invalid_tool_namespace -- --nocapture`
    - `cargo test -p codex-tools`
    - `git diff --check`
  • multiagent: trim model-visible description, cap to 5 models (#23069)
    ## Why
    
    The `spawn_agent` model override guidance is uncapped and bloating
    context. We need to trim down each entry and cap total entries.
    
    picked 5 as cap, we can change
    
    ## What changed
    
    - Cap the model override summaries shown in `spawn_agent` to the first 5
    picker-visible models, preserving the existing priority ordering from
    the models manager.
    - Condense each rendered entry to the actionable pieces the model needs:
      - use the model slug as the label
      - render compact reasoning effort lists with the default marked inline
    - render only service tier IDs, and omit the clause when no tiers are
    available
    - Update coverage so the compact formatter shape and the top-5 cap are
    exercised, and keep the end-to-end request assertion aligned with real
    model metadata.
    
    ## Example
    
    Before:
    
    `- gpt-5.4 ('gpt-5.4\'): Strong model for everyday coding. Default
    reasoning effort: medium. Supported reasoning efforts: low (Fast
    responses with lighter reasoning), medium (Balances speed and reasoning
    depth for everyday tasks), high (Greater reasoning depth for complex
    problems), xhigh (Extra high reasoning depth for complex problems).
    Supported service tiers: priority (Fast: 1.5x speed, increased usage).`
    
    After:
    
    `- 'gpt-5.4': Strong model for everyday coding. Reasoning efforts: low,
    medium (default), high, xhigh. Service tiers: priority.`
  • test: construct permission profiles directly (#23030)
    ## Why
    
    `SandboxPolicy` is now a legacy compatibility shape, but several tests
    still built a `SandboxPolicy` only to immediately convert it into
    `PermissionProfile` for APIs that already accept canonical runtime
    permissions. Those detours make it harder to audit where legacy sandbox
    policy is still required, because boundary-only usages are mixed
    together with ordinary test setup.
    
    ## What Changed
    
    - Updated tests in `codex-core`, `codex-exec`, `codex-analytics`, and
    `codex-config` to construct `PermissionProfile` values directly when the
    code under test takes a permission profile.
    - Changed exec-policy, request-permissions, session, and sandbox test
    helpers to pass `PermissionProfile` through instead of converting from
    `SandboxPolicy` internally.
    - Left `SandboxPolicy` in place where tests are explicitly exercising
    legacy compatibility or request/response boundaries.
    
    ## Test Plan
    
    - `cargo test -p codex-analytics -p codex-config`
    - `cargo test -p codex-core --lib safety::tests`
    - `cargo test -p codex-core --lib exec_policy::tests::`
    - `cargo test -p codex-core --lib exec::tests`
    - `cargo test -p codex-core --lib guardian_review_session_config`
    - `cargo test -p codex-core --lib tools::network_approval::tests`
    - `cargo test -p codex-core --lib
    tools::runtimes::shell::unix_escalation::tests`
    - `cargo test -p codex-core --lib managed_network`
    - `cargo test -p codex-core --test all request_permissions::`
    - `cargo test -p codex-exec sandbox`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23030).
    * #23036
    * __->__ #23030
  • Improve goal completion usage reporting (#22907)
    ## Why
    
    Goal completion follow-up turns currently receive a preformatted English
    usage sentence such as `time used: 2586 seconds`. That nudges the model
    to echo an awkward raw seconds count in the final reply, even though the
    tool result already exposes structured usage fields like
    `goal.timeUsedSeconds`, `goal.tokensUsed`, and `goal.tokenBudget`.
    
    ## What changed
    
    - Replace the preformatted completion usage sentence with guidance to
    read the structured goal fields from the tool result.
    - Preserve token-budget reporting while allowing the model to phrase
    elapsed time in a concise, human-friendly way that fits the response
    language.
    - Update core coverage for both the generated completion guidance and
    the session flow that forwards it back to the model.
    
    ## Verification
    
    Previously, it would have output a final message indicating that it
    "worked for 303 seconds". Now it shows the following:
    
    <img width="286" height="35" alt="image"
    src="https://github.com/user-attachments/assets/d7011880-9449-46a7-856f-4e50ae00eb45"
    />
  • core: set permission profiles from snapshots (#22920)
    ## Why
    
    #22891 moved the TUI turn-command path to pass `ActivePermissionProfile`
    instead of the full `PermissionProfile`, but the remaining
    config/session bridge still accepted the concrete `PermissionProfile`
    and active profile id as separate arguments. That shape made it too easy
    for future callers to update the concrete profile and active profile id
    out of sync.
    
    This PR makes the trusted session snapshot path pass one coherent value
    into `Permissions`, while keeping `requirements.toml` enforcement owned
    by the existing constrained permission state.
    
    ## What Changed
    
    - Added `PermissionProfileSnapshot` as the public snapshot value for
    trusted session/config synchronization.
    - Changed `Permissions::set_permission_profile_from_session_snapshot()`
    and `replace_permission_profile_from_session_snapshot()` to take a
    `PermissionProfileSnapshot`.
    - Updated the replacement path to derive its constrained
    `PermissionProfile` from the snapshot, so callers cannot pass a separate
    profile that disagrees with the snapshot.
    - Removed the internal tuple-style
    `PermissionProfileState::set_active_permission_profile()` mutation path.
    - Updated core session projection and TUI call sites to construct
    explicit legacy or active snapshots.
    - Documented the snapshot constructors so legacy use and id/profile
    mismatch hazards are called out at the API boundary.
    - Added a focused config test that verifies snapshot updates still
    respect existing permission constraints.
    
    ## How To Review
    
    1. Start with `codex-rs/core/src/config/resolved_permission_profile.rs`;
    `PermissionProfileSnapshot` is the public wrapper, while
    `ResolvedPermissionProfile` stays internal.
    2. Check `codex-rs/core/src/config/mod.rs` to confirm both
    session-snapshot setters validate through `PermissionProfileState` and
    no longer accept loose profile/id pairs.
    3. Skim `codex-rs/core/src/session/session.rs` for the session
    projection path; it now builds the snapshot before installing it.
    4. Skim the TUI changes as call-site migration from loose argument pairs
    to explicit snapshot construction.
    
    ## Verification
    
    - `cargo test -p codex-core
    permission_snapshot_setter_preserves_permission_constraints`
    - `cargo test -p codex-tui status_permissions_`
    - `cargo test -p codex-tui
    session_configured_preserves_profile_workspace_roots`
    - `just fix -p codex-core -p codex-tui`
  • Preserve image detail in app-server inputs (#20693)
    ## Summary
    
    - Add optional image detail to user image inputs across core, app-server
    v2, thread history/event mapping, and the generated app-server
    schemas/types.
    - Preserve requested detail when serializing Responses image inputs:
    omitted detail stays on the existing `high` default, while explicit
    `original` keeps local images on the original-resolution path.
    - Support `high`/`original` consistently for tool image outputs,
    including MCP `codex/imageDetail`, code-mode image helpers, and
    `view_image`.
  • core: construct test permission profiles directly (#22795)
    ## Why
    
    The core migration is trying to make `PermissionProfile` the shape tests
    and runtime code reason about, leaving `SandboxPolicy` only where legacy
    behavior is explicitly under test. The local
    `permission_profile_for_sandbox_policy()` test helpers kept new
    permission-profile tests mentally tied to the old sandbox model even
    when the equivalent profile is straightforward.
    
    ## What Changed
    
    - Removed the `permission_profile_for_sandbox_policy()` helper from the
    network proxy spec tests and session tests.
    - Replaced legacy conversions for read-only, workspace-write, and
    full-access cases with `PermissionProfile::read_only()`,
    `PermissionProfile::workspace_write()`, and
    `PermissionProfile::Disabled`.
    - Constructed the external-sandbox session test's
    `PermissionProfile::External` directly, while preserving the legacy
    `SandboxPolicy` only where the test still exercises legacy config update
    behavior.
    
    ## How To Review
    
    This PR is intentionally test-only. Review the two touched files and
    check that each replacement preserves the old legacy mapping:
    
    - `SandboxPolicy::new_read_only_policy()` ->
    `PermissionProfile::read_only()`
    - `SandboxPolicy::new_workspace_write_policy()` ->
    `PermissionProfile::workspace_write()`
    - `SandboxPolicy::DangerFullAccess` -> `PermissionProfile::Disabled`
    - `SandboxPolicy::ExternalSandbox { network_access: Restricted }` ->
    `PermissionProfile::External { network: Restricted }`
    
    ## Verification
    
    - `cargo test -p codex-core
    requirements_allowed_domains_are_a_baseline_for_user_allowlist`
    - `cargo test -p codex-core
    start_managed_network_proxy_applies_execpolicy_network_rules`
    - `cargo test -p codex-core
    session_configured_reports_permission_profile_for_external_sandbox`
    - `cargo test -p codex-core
    managed_network_proxy_decider_survives_full_access_start`
    - `just fix -p codex-core`
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22795).
    * #22891
    * __->__ #22795
  • Forward apps MCP product SKU from Codex config (#22872)
    This adds `apps_mcp_product_sku` as a toplevel config.toml key. We pass
    the given value as a header when listing MCPs for the client, allowing
    connectors to be filtered per product entry point.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • telemetry: tag sandboxes from permission profiles (#22791)
    ## Why
    
    Sandbox telemetry tags should be derived from the active permission
    profile, not from a legacy `SandboxPolicy`, so the tagging code stays
    aligned with the permissions migration and does not preserve a
    policy-shaped production helper only for tests.
    
    ## What Changed
    
    - Removed the production `sandbox_tag(&SandboxPolicy, ...)` helper.
    - Updated sandbox tag tests to construct the relevant
    `PermissionProfile` values directly.
    - Kept the platform-specific sandbox tag behavior under the existing
    `permission_profile_sandbox_tag` path.
    
    ## How To Review
    
    The production change is in `codex-rs/core/src/sandbox_tags.rs`. Most of
    the diff is test cleanup that replaces legacy policy setup with
    permission profiles, so review the expected tag assertions rather than
    the old helper mechanics.
    
    ## Verification
    
    - `cargo test -p codex-core sandbox_tag`
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22791).
    * #22795
    * #22792
    * __->__ #22791
  • context: remove legacy permissions instructions helper (#22790)
    ## Why
    
    The permissions instruction builder should consume the new permissions
    model directly. Keeping a `SandboxPolicy` conversion helper in this path
    encourages new code to route through legacy sandbox policy values even
    when the caller already has a `PermissionProfile`.
    
    ## What Changed
    
    - Removed `PermissionsInstructions::from_policy`.
    - Removed the test that exercised that legacy helper.
    - Left the existing profile-based instruction coverage in place.
    
    ## How To Review
    
    Review `codex-rs/core/src/context/permissions_instructions.rs` first.
    This PR is intentionally narrow: the production behavior should be
    unchanged for profile callers, and the deleted surface was only a
    convenience adapter from `SandboxPolicy`.
    
    ## Verification
    
    - `cargo test -p codex-core builds_permissions_from_profile`
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22790).
    * #22795
    * #22792
    * #22791
    * __->__ #22790
  • Ignore configured hooks in git helpers (#22843)
    ## What
    - Internal Git helper commands now ignore configured hook directories
    during repository bookkeeping.
    
    ## Why
    - These helper flows should stay consistent even when a repository has
    hook-directory configuration of its own.
    
    ## How
    - Pass a command-local `core.hooksPath` override in the shared helper
    path and the Git-info helper path.
    - Add regressions for the baseline index rewrite flow and the metadata
    status flow.
    
    ## Validation
    - `cargo fmt --manifest-path
    /Users/bookholt/code/codex/codex-rs/Cargo.toml --all --check`
    - `cargo test --manifest-path
    /Users/bookholt/code/codex/codex-rs/Cargo.toml -p codex-git-utils`
    - `cargo test --manifest-path
    /Users/bookholt/code/codex/codex-rs/Cargo.toml -p codex-core
    test_get_has_changes_`
  • guardian: use permission profile for review sandbox (#22789)
    ## Why
    
    `SandboxPolicy` is being pushed back toward legacy config loading and
    compatibility boundaries. Guardian review sessions already want the
    built-in read-only permission behavior; carrying that as an active
    `PermissionProfile` makes the review sandbox follow the new permissions
    path instead of configuring the child session through the legacy policy
    API.
    
    ## What Changed
    
    - Configure the guardian review session with
    `PermissionProfile::read_only()`.
    - Send the read-only profile through the guardian child `Op::UserTurn`.
    - Keep the legacy `sandbox_policy` field populated with
    `SandboxPolicy::new_read_only_policy()` declared next to the profile so
    the two remain visibly in sync until the compatibility field goes away.
    
    ## How To Review
    
    Start in `codex-rs/core/src/guardian/review_session.rs`. The important
    check is that both the guardian config and the child turn now use the
    read-only permission profile, while the remaining
    `SandboxPolicy::ReadOnly` assignment is only the compatibility field
    required by the current turn protocol.
    
    ## Verification
    
    - `cargo test -p codex-core
    guardian_review_session_config_clears_parent_developer_instructions`
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22789).
    * #22795
    * #22792
    * #22791
    * #22790
    * __->__ #22789
  • Move memory prompt injection to app-server extension (#22841)
    ## Why
    
    Memory prompt injection should be owned by the extension path that
    app-server composes at runtime, not by an inlined special case inside
    `codex-core`. This keeps `codex-core` focused on session orchestration
    while allowing the memories extension to own its app-server prompt
    behavior.
    
    ## What Changed
    
    - Registers `codex-memories-extension` in the app-server extension
    registry.
    - Moves the memory developer-instruction injection out of
    `core/src/session/mod.rs` and into the memories extension prompt
    contributor.
    - Adds config-change handling so the extension keeps its per-thread
    memory settings in sync after startup.
    - Leaves memories read/retrieval tools unregistered for now so this PR
    only changes prompt injection.
    - Removes the stale `cargo-shear` ignore now that app-server depends on
    the extension crate.
    
    ## Validation
    
    Not run locally; validation is left to CI.
  • Run compact hooks for remote compaction v2 (#22828)
    ## Why
    
    Remote compaction v2 is the `/responses` implementation of
    session-history compaction, but it still needs to preserve the
    observable contract of the legacy `/responses/compact` path. In
    particular, users and integrations that rely on `PreCompact` and
    `PostCompact` hooks should not see different behavior when
    `remote_compaction_v2` is enabled.
    
    ## What Changed
    
    - Runs `PreCompact` before issuing the remote compaction v2 request,
    including `Interrupted` analytics when a pre-hook stops execution.
    - Runs `PostCompact` after a successful v2 compaction and aborts the
    turn if the post-hook stops execution.
    - Adds `compact_remote_parity` coverage that compares legacy and v2
    compaction across manual transcript shapes, automatic pre-turn
    compaction, automatic mid-turn compaction, hook payloads, replacement
    history, follow-up request payloads, and API-key `service_tier=fast`
    behavior.
    - Registers the new parity suite under `core/tests/suite`.
    
    Relevant code:
    
    -
    [`compact_remote_v2.rs`](https://github.com/openai/codex/blob/af63745cb502183a6fc447d0240f8150934d70b7/codex-rs/core/src/compact_remote_v2.rs)
    -
    [`compact_remote_parity.rs`](https://github.com/openai/codex/blob/af63745cb502183a6fc447d0240f8150934d70b7/codex-rs/core/tests/suite/compact_remote_parity.rs)
    
    ## Verification
    
    - Added `core/tests/suite/compact_remote_parity.rs` to assert parity
    between legacy remote compaction and remote compaction v2 for the
    affected request, hook, rollout-history, and follow-up paths.
    - Existing `compact_remote_v2` unit coverage still exercises v2
    replacement-history retention and compaction-output collection.
  • Remove zombie tools spec module (#22820)
    ## Summary
    
    - move tool_user_shell_type out of the old tools::spec module and call
    it from tools directly
    - attach the remaining spec planning model tests under spec_plan
    - delete core/src/tools/spec.rs
    
    ## Tests
    
    - just fmt
    - cargo test -p codex-core tools::spec_plan
    
    Note: a broader cargo test -p codex-core run on the earlier PR-head
    worktree still hit the pre-existing stack overflow in
    agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns.
  • Simplify tool executor and registry plumbing (#22636)
    ## Why
    
    The tool runtime path still had a typed output associated type on
    `ToolExecutor`, plus a core-only `RegisteredTool` adapter and
    extension-only executor aliases. That made every new shared tool runtime
    carry extra adapter plumbing before it could participate in core
    dispatch, extension tools, hook payloads, telemetry, and model-visible
    spec generation.
    
    This PR moves output erasure to the shared executor boundary so core and
    extension tools can use the same execution contract directly.
    
    ## What Changed
    
    - Changed `codex_tools::ToolExecutor` to return `Box<dyn ToolOutput>`
    instead of an associated `Output` type.
    - Removed the extension-specific `ExtensionToolExecutor` /
    `ExtensionToolOutput` aliases and exposed `ToolExecutor<ToolCall>` plus
    `ToolOutput` through `codex-extension-api`.
    - Reworked core tool registration around `CoreToolRuntime` and
    `ToolRegistry::from_tools`, removing the extra `RegisteredTool` /
    `ToolRegistryBuilder` layer.
    - Consolidated model-visible spec planning and registry construction in
    `core/src/tools/spec_plan.rs`, including deferred tool search and
    code-mode-only filtering.
    - Added `ToolOutput` helpers for post-tool-use hook ids and inputs so
    MCP, unified exec, extension, and other boxed outputs preserve the same
    hook payload behavior.
    - Updated core handlers, memories tools, and the related
    registry/spec/router tests to use the simplified contract.
    
    ## Test Coverage
    
    - Updated coverage for tool spec planning, registry lookup, deferred
    tool search registration, extension tool routing, post-tool-use hook
    payloads, dispatch tracing, guardian output extraction, and memories
    extension tool execution.
  • [codex] Use compaction_trigger item for remote compaction v2 (#22809)
    ## Why
    
    Remote compaction v2 was still using `context_compaction` as both the
    request trigger and the compacted output shape. The Responses API now
    has the landed contract for this flow: Codex sends a dedicated `{
    "type": "compaction_trigger" }` input item, and the backend returns the
    standard `compaction` output item with encrypted content.
    
    This aligns the v2 path with that wire contract while preserving the
    existing local compacted-history post-processing behavior.
    
    ## What changed
    
    - Add `ResponseItem::CompactionTrigger` and regenerate the app-server
    protocol schema fixtures.
    - Send `compaction_trigger` from `remote_compaction_v2` instead of a
    payload-less `context_compaction`.
    - Collect exactly one backend `compaction` output item, then reuse the
    existing compacted-history rebuilding path.
    - Treat the trigger item as a transient request marker rather than model
    output or persisted rollout/memory content.
    
    ## Verification
    
    - `cargo test -p codex-protocol compaction_trigger`
    - `cargo test -p codex-core remote_compact_v2`
    - `cargo test -p codex-core compact_remote_v2`
    - `cargo test -p codex-core
    responses_websocket_sends_response_processed_after_remote_compaction_v2`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol schema_fixtures`
  • app-server: use permission ids and runtime workspace roots (#22611)
    ## Why
    
    This PR builds on [#22610](https://github.com/openai/codex/pull/22610)
    and is the app-server side of the migration from mutable per-turn
    `SandboxPolicy` replacement toward selecting immutable permission
    profiles by id plus mutable runtime workspace roots.
    
    Once permission profiles can carry their own immutable
    `workspace_roots`, app-server no longer needs to mutate the selected
    `PermissionProfile` just to represent thread-specific filesystem
    context. The mutable part now lives on the thread as explicit
    `runtimeWorkspaceRoots`, while `:workspace_roots` remains symbolic until
    the sandbox is realized for a turn.
    
    ## What Changed
    
    - Replaced the v2 permission-selection wrapper surface with plain
    profile ids for `thread/start`, `thread/resume`, `thread/fork`, and
    `turn/start`.
    - Removed the API surface for profile modifications
    (`PermissionProfileSelectionParams`,
    `PermissionProfileModificationParams`,
    `ActivePermissionProfileModification`).
    - Added experimental `runtimeWorkspaceRoots` fields to the thread
    lifecycle and turn-start APIs.
    - Threaded runtime workspace roots through core session/thread
    snapshots, turn overrides, app-server request handling, and command
    execution permission resolution.
    - Kept session permission state symbolic so later runtime root updates
    and cwd-only implicit-root retargeting rebind `:workspace_roots`
    correctly.
    - Updated the embedded clients just enough to send and restore the new
    thread state.
    - Refreshed the generated schema/TypeScript artifacts and the app-server
    README to match the new contract.
    
    ## Verification
    
    Targeted coverage for this layer lives in:
    
    - `codex-rs/app-server-protocol/src/protocol/v2/tests.rs`
    - `codex-rs/app-server/tests/suite/v2/thread_start.rs`
    - `codex-rs/app-server/tests/suite/v2/thread_resume.rs`
    - `codex-rs/app-server/tests/suite/v2/turn_start.rs`
    - `codex-rs/core/src/session/tests.rs`
    
    The key regression checks exercise that:
    
    - `runtimeWorkspaceRoots` resolve against the effective cwd on thread
    start.
    - Profile-declared workspace roots are excluded from the runtime
    workspace roots returned by app-server.
    - A turn-level runtime workspace-root update persists onto the thread
    and is returned by `thread/resume`.
    - A named permission profile selected on one turn remains symbolic so a
    later runtime-root-only turn update changes the actual sandbox writes.
    - A cwd-only turn update retargets the implicit runtime cwd root while
    preserving additional runtime roots.
    - The protocol fixtures and generated client artifacts stay in sync with
    the string-based permission selection contract.
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22611).
    * #22612
    * __->__ #22611
  • [codex] Add opaque desktop config namespace (#22584)
    ## Summary
    - reserve an explicit opaque `desktop` namespace in `ConfigToml`
    - expose `desktop` directly in the app-server v2 `config/read` response
    - keep `config/value/write` and `config/batchWrite` as the only mutation
    seam for paths like `desktop.someKey`
    - regenerate the config/app-server schema outputs and document the new
    contract
    
    ## Why
    The desktop settings work wants one durable, user-editable home for
    app-owned preferences in `~/.codex/config.toml`, without forcing Rust to
    model every individual desktop setting key.
    
    This PR is only the enabling Rust/app-server layer. It gives the
    Electron app a first-class config namespace it can read and write
    through the existing config APIs, while leaving the actual desktop
    migration to the app PR.
    
    ## Behavior and design notes
    - **Opaque but explicit:** `desktop` is first-class at the typed config
    root, while its children remain app-owned and open-ended.
    - **Strict validation still works:** arbitrary nested `desktop.*` keys
    are accepted instead of being rejected as unknown config.
    - **Existing config APIs stay the seam:** `config/read` returns the bag,
    and dotted writes such as `desktop.someKey` continue to flow through
    `config/value/write` / `config/batchWrite` rather than a bespoke RPC.
    - **No new consumer behavior:** Core/TUI do not start depending on
    desktop preferences. This only preserves and exposes the namespace for
    callers that intentionally use it.
    - **Same persistence machinery:** hand-edited `config.toml` keeps using
    the existing TOML edit/write path; this PR does not introduce a second
    serializer or side channel.
    - **TOML-friendly values:** the namespace is intended for ordinary
    JSON-shaped setting values that map cleanly into TOML: strings, numbers,
    booleans, arrays, and nested object/table values. This PR does not add
    special handling for TOML-only edge cases such as datetimes.
    
    ## Layering semantics
    Reads keep using the ordinary effective config pipeline, so `desktop`
    participates in the same layered `config/read` behavior as the rest of
    `ConfigToml`. Writes still target user config through the existing
    config service.
    
    ## Why this is the shape
    The alternative would be teaching Rust about each desktop setting as it
    is added. That would make ordinary app preferences into a cross-repo
    change, which is exactly the coupling we want to avoid.
    
    This keeps the contract small:
    1. Rust owns one opaque `desktop` namespace in `config.toml`.
    2. The desktop app owns the schema and meaning of individual keys inside
    it.
    3. The existing config APIs remain the transport and mutation surface.
    
    That is the piece the desktop settings PR needs in order to move forward
    cleanly.
    
    ## Verification
    - `cargo test -p codex-config strict_config_accepts_opaque_desktop_keys`
    - `cargo test -p codex-core
    desktop_toml_round_trips_opaque_nested_values`
    - `cargo test -p codex-core config_schema_matches_fixture`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server --test all desktop_settings`
  • permissions: resolve profile identity with constraints (#22683)
    ## Why
    
    This PR is the invariant-cleanup layer that follows the workspace-roots
    base merged in [#22610](https://github.com/openai/codex/pull/22610).
    
    #22610 adds `[permissions.<id>.workspace_roots]` and keeps runtime
    workspace roots separate from the raw permission profile, but its
    in-memory representation is intentionally transitional: `Permissions`
    still carries the selected profile identity next to a constrained
    `PermissionProfile`. That makes APIs such as
    `set_constrained_permission_profile_with_active_profile()` fragile
    because the id and value only mean the right thing when every caller
    keeps them in sync.
    
    This PR introduces a single resolved profile state so profile identity,
    `extends`, the profile value, and profile-declared workspace roots
    travel together. The next PR,
    [#22611](https://github.com/openai/codex/pull/22611), builds on this by
    changing the app-server turn API to select permission profiles by id
    plus runtime workspace roots.
    
    ## Stack Context
    
    - #22610, now merged: adds profile-declared `workspace_roots`, runtime
    workspace roots, and `:workspace_roots` materialization.
    - This PR: replaces the parallel active-profile/profile-value fields
    with `PermissionProfileState`.
    - #22611: switches app-server turn updates toward profile ids plus
    runtime workspace roots.
    - #22612: updates TUI/exec summaries to show the effective workspace
    roots.
    
    Keeping this separate from #22611 is deliberate: reviewers can validate
    the internal state invariant before reviewing the app-server protocol
    migration.
    
    ## What Changed
    
    - Added `ResolvedPermissionProfile::{Legacy, BuiltIn, Named}` and
    `PermissionProfileState`.
    - Typed built-in profile ids with `BuiltInPermissionProfileId`.
    - Moved selected profile identity and profile-declared workspace roots
    into the resolved state.
    - Replaced `Permissions` parallel profile fields with one
    `permission_profile_state`.
    - Removed `set_constrained_permission_profile_with_active_profile()`
    from session sync paths.
    - Kept trusted session replay/`SessionConfigured` compatibility through
    explicit session snapshot helpers.
    - Updated session configuration, MCP initialization, app-server, exec,
    TUI, and guardian call sites to consume `&PermissionProfile` directly.
    
    ## Review Guide
    
    Start with `codex-rs/core/src/config/resolved_permission_profile.rs`; it
    is the new invariant boundary. Then review
    `codex-rs/core/src/config/mod.rs` to see how config loading records
    active profile identity and profile workspace roots. The remaining
    call-site changes are mostly mechanical fallout from
    `Permissions::permission_profile()` returning `&PermissionProfile`
    instead of `&Constrained<PermissionProfile>`.
    
    ## Verification
    
    The existing config/session coverage now constructs and asserts through
    `PermissionProfileState`. The workspace-root config test also asserts
    that profile-declared roots are preserved in the resolved state, which
    is the behavior #22611 relies on when runtime roots become mutable
    through the app-server API.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22683).
    * #22612
    * #22611
    * __->__ #22683
  • Stabilize compact rollback follow-up test (#22303)
    ## Summary
    - add the missing response.created event to the mocked empty follow-up
    response in the compact rollback test
    - keep the fix scoped to the flaky mocked stream shape, without
    increasing timeouts
    
    ## Recent flakes on main
    - `snapshot_rollback_followup_turn_trims_context_updates` failed in
    `rust-ci-full` on `main` in the Ubuntu remote test job on 2026-05-14:
    https://github.com/openai/codex/actions/runs/25891434395/job/76095284830
    - The same `compact_resume_fork` suite also failed recently on `main`
    with `snapshot_rollback_past_compaction_replays_append_only_history`,
    which has the same mocked Responses stream shape sensitivity this PR is
    tightening:
    https://github.com/openai/codex/actions/runs/25892437363/job/76098329098
    
    ## Verification
    - env -u CODEX_SANDBOX_NETWORK_DISABLED cargo test -p codex-core --test
    all snapshot_rollback_followup_turn_trims_context_updates -- --nocapture
    - repeated the same focused test 3 consecutive times locally
    - UV_CACHE_DIR=/private/tmp/uv-cache-codex-fmt just fmt
  • Add user_input_requested_during_turn to MCP turn metadata (#22237)
    ## Why
    - Similar change as https://github.com/openai/codex/pull/21219
    - Without change: MCP tool calls receive
    `_meta["x-codex-turn-metadata"]` with various key values.
    - Issue: MCP servers currently do not know if user input was requested
    during the turn (Ex: Model decides to prompt the user for approval
    mid-turn before making a possibly risky tool call). MCP servers may want
    to know this when tracking latency metrics because these instances are
    inflated.
    
    ## What Changed
    - With change: MCP turn metadata now includes
    `user_input_requested_during_turn` when a model-visible
    `request_user_input` call happened earlier in the turn, propagated in
    `_meta["x-codex-turn-metadata"]`.
    - `mark_turn_user_input_requested()` is called when user input is
    requested through either MCP elicitation (`mcp.rs`) or the
    `request_user_input` tool (`mod.rs`).
    - MCP tool call `_meta` is now built immediately before execution
    (`mcp_tool_call.rs`) so user input requested earlier in the same turn,
    including within the same tool call via elicitation, is reflected in the
    metadata.
    - Normal `/responses` turn metadata headers are unchanged.
    
    ## Verification
    - `codex-rs/core/src/session/mcp_tests.rs`
    - `codex-rs/core/src/tools/handlers/request_user_input_tests.rs`
    - `codex-rs/core/src/turn_metadata_tests.rs`
    - `codex-rs/core/tests/suite/search_tool.rs`
  • permissions: support workspace roots in profiles (#22610)
    ## Why
    
    This is the configuration/model half of the alternative permissions
    migration we discussed as a comparison point for
    [#22401](https://github.com/openai/codex/pull/22401) and
    [#22402](https://github.com/openai/codex/pull/22402).
    
    The old `workspace-write` model mixes three concerns that we want to
    keep separate:
    - reusable profile rules that should stay immutable once selected
    - user/runtime workspace roots from `cwd`, `--add-dir`, and legacy
    workspace-write config
    - internal Codex writable roots such as memories, which should not be
    shown as user workspace roots
    
    This PR gives permission profiles first-class `workspace_roots` so users
    can opt multiple repositories into the same `:workspace_roots` rules
    without using broad absolute-path write grants. It also starts
    separating the raw selected profile from the effective runtime profile
    by making `Permissions` expose explicit accessors instead of public
    mutable fields.
    
    A representative `config.toml` looks like this:
    
    ```toml
    default_permissions = "dev"
    
    [permissions.dev.workspace_roots]
    "~/code/openai" = true
    "~/code/developers-website" = true
    
    [permissions.dev.filesystem.":workspace_roots"]
    "." = "write"
    ".codex" = "read"
    ".git" = "read"
    ".vscode" = "read"
    ```
    
    If Codex starts in `~/code/codex` with that profile selected, the
    effective workspace-root set becomes:
    - `~/code/codex` from the runtime `cwd`
    - `~/code/openai` from the profile
    - `~/code/developers-website` from the profile
    
    The `:workspace_roots` rules are materialized across each root, so
    `.git`, `.codex`, and `.vscode` stay scoped the same way everywhere.
    Runtime additions such as `--add-dir` can still layer on later stack
    entries without mutating the selected profile.
    
    ## Stack Shape
    
    This PR intentionally stops before the profile-identity cleanup in
    [#22683](https://github.com/openai/codex/pull/22683) so the base review
    stays focused on config loading, workspace-root materialization, and
    compatibility with legacy `workspace-write`.
    
    The representation in this PR is therefore transitional: `Permissions`
    carries enough state to distinguish the raw constrained profile from the
    effective runtime profile, and there are still call sites that must keep
    the active profile identity and constrained profile value in sync. The
    follow-up PR replaces that with a single resolved profile state
    (`ResolvedPermissionProfile` / `PermissionProfileState`) that keeps the
    profile id, immutable `PermissionProfile`, and profile-declared
    workspace roots together. That follow-up removes APIs such as
    `set_constrained_permission_profile_with_active_profile()` where
    separate arguments could drift out of sync.
    
    Downstream PRs then build on this base to switch app-server turn updates
    to profile ids plus runtime workspace roots and to finish the
    user-visible summary behavior. Reviewers should judge this PR as the
    workspace-roots foundation, not as the final in-memory shape of selected
    permission profiles.
    
    ## Review Guide
    
    Suggested review order:
    
    1. Start with `codex-rs/core/src/config/mod.rs`.
    This is the main shape change in the base slice. `Permissions` now
    stores a private raw `Constrained<PermissionProfile>` plus runtime
    `workspace_roots`. Callers use `permission_profile()` when they need the
    raw constrained value and `effective_permission_profile()` when they
    need a materialized runtime profile. As noted above,
    [#22683](https://github.com/openai/codex/pull/22683) replaces this
    transitional shape with a resolved profile state that keeps identity and
    profile data together.
    
    2. Review `codex-rs/config/src/permissions_toml.rs` and
    `codex-rs/core/src/config/permissions.rs`.
    These add `[permissions.<id>.workspace_roots]`, resolve enabled entries
    relative to the policy cwd, and keep `:workspace_roots` deny-read glob
    patterns symbolic until the actual roots are known.
    
    3. Review `codex-rs/protocol/src/permissions.rs` and
    `codex-rs/protocol/src/models.rs`.
    These add the policy/profile materialization helpers that expand exact
    `:workspace_roots` entries and scoped deny-read globs over every
    workspace root. This is also where `ActivePermissionProfileModification`
    is removed from the core model.
    
    4. Review the legacy bridge in
    `Config::load_from_base_config_with_overrides` and
    `Config::set_legacy_sandbox_policy`.
    This is where legacy `workspace-write` roots become runtime workspace
    roots, while Codex internal writable roots stay internal and do not
    appear as user-facing workspace roots.
    
    5. Then skim downstream call sites.
    The interesting pattern is raw-vs-effective access: state/proxy/bwrap
    paths keep the raw constrained profile, while execution, summaries, and
    user-visible status use the effective profile and workspace-root list.
    
    ## What Changed
    
    - added `[permissions.<id>.workspace_roots]` to the config model and
    schema
    - added runtime `workspace_roots` state to `Config`/`Permissions` and
    `ConfigOverrides`
    - made `Permissions` profile fields private and replaced direct mutation
    with accessors/setters
    - added `PermissionProfile` and `FileSystemSandboxPolicy` helpers for
    materializing `:workspace_roots` exact paths and deny-read globs across
    all roots
    - moved legacy additional writable roots into runtime workspace-root
    state instead of active profile modifications
    - removed `ActivePermissionProfileModification` and its app-server
    protocol/schema export
    - updated sandbox/status summary paths so internal writable roots are
    not reported as user workspace roots
    
    ## Verification Strategy
    
    The targeted tests cover the behavior at the layers where regressions
    are most likely:
    - `codex-rs/core/src/config/config_tests.rs` verifies config loading,
    legacy workspace-root seeding, effective profile materialization, and
    memory-root handling.
    - `codex-rs/core/src/config/permissions_tests.rs` verifies profile
    `workspace_roots` parsing and `:workspace_roots` scoped/glob
    compilation.
    - `codex-rs/protocol/src/permissions.rs` unit tests verify exact and
    glob materialization over multiple workspace roots.
    - `codex-rs/tui/src/status/tests.rs` and
    `codex-rs/utils/sandbox-summary/src/sandbox_summary.rs` verify the
    user-facing summaries show effective workspace roots and hide internal
    writes.
    
    I also ran `cargo check --tests` locally after the latest stack refresh
    to catch cross-crate API breakage from the private-field/accessor
    changes.
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22610).
    * #22612
    * #22611
    * #22683
    * __->__ #22610
  • [codex] Remove experimental instructions file config (#22724)
    ## Summary
    
    Remove the deprecated `experimental_instructions_file` config setting
    from the typed config surface and the remaining deprecation-notice
    plumbing. `model_instructions_file` remains the supported setting and
    its loading path is unchanged.
    
    The setting was deprecated when it was renamed to
    `model_instructions_file` on January 20, 2026 in
    https://github.com/openai/codex/pull/9555.
    
    ## Changes
    
    - Remove `experimental_instructions_file` from `ConfigToml` and
    `ConfigProfile`.
    - Delete the custom config-layer scan and session deprecation notice for
    the removed setting.
    - Stop clearing the removed field from generated session config locks.
    - Remove the obsolete deprecation-notice test case while keeping
    `model_instructions_file` coverage intact.
    
    ## Validation
    
    - `just write-config-schema`
    - `just fmt`
    - `cargo test -p codex-config`
    - `cargo test -p codex-core model_instructions_file`
    - `just fix -p codex-core`
    - `git diff --check`
    
    Co-authored-by: Codex <noreply@openai.com>
  • Remove SSE fixture loaders (#22684)
    ## Why
    
    The Responses API test support already has structured SSE event
    builders. Keeping separate JSON fixture loaders made small mock streams
    harder to read and left an on-disk fixture for a single event.
    
    ## What changed
    
    - Removed `load_sse_fixture` and `load_sse_fixture_with_id_from_str`
    from `core_test_support`.
    - Deleted the one `tests/fixtures/incomplete_sse.json` Responses API
    fixture.
    - Replaced the remaining call sites with `responses::sse(...)` and
    existing event helpers.
    
    ## Validation
    
    - `cargo test -p codex-core --test all
    stream_no_completed::retries_on_early_close`
    - `cargo test -p codex-core --test all
    history_dedupes_streamed_and_final_messages_across_turns`
    - `cargo test -p codex-core --test all review::`
  • Trim TUI legacy core helper usage (#22695)
    ## Why
    
    The TUI still had a few low-risk dependencies flowing through the
    transitional `legacy_core` namespace after the app-server migration.
    These helpers either already have clearer non-core owners or are
    presentation logic that does not belong in `codex-core`, so moving them
    out reduces the compatibility surface without changing product behavior.
    
    ## What changed
    
    This is a low-risk change, almost completely mechanical in nature.
    
    - Route TUI Codex-home lookup through `codex-utils-home-dir`, use
    `Config::log_dir` directly, and call
    `codex-sandboxing::system_bwrap_warning` without going through
    `legacy_core`.
    - Move shared `codex resume` hint formatting from `codex-core` into
    `codex-utils-cli`.
    - Update CLI and TUI call sites to use the shared CLI utility, and keep
    the resume-command behavior covered by tests in its new home.
    
    ## Verification
    
    - `cargo test -p codex-utils-cli`
    - `cargo test -p codex-utils-cli resume_command`
  • chore(config) rm windows_wsl_setup_acknowledged (#22717)
    ## Summary
    Remove dead code from a notice that no longer exists.
    
    ## Testing
    - [x] Unit tests pass.
  • chore(features) rm Feature::ApplyPatchFreeform (#22711)
    ## Summary
    Removes the feature since this is effectively on by default in all cases
    where we should use it, or can be configured via models.json.
    
    ## Testing
    - [x] unit tests pass
  • [codex] Support multiple forced ChatGPT workspaces (#18161)
    ## Summary
    
    This change lets `forced_chatgpt_workspace_id` accept multiple workspace
    IDs instead of a single value.
    
    It keeps the existing config key name, adds backward-compatible parsing
    for a single string in `config.toml`, and normalizes the setting into an
    allowed workspace list across login enforcement, app-server config
    surfaces, and local ChatGPT auth helpers.
    
    ## Why
    
    Workspace-restricted deployments may need to allow more than one ChatGPT
    workspace without dropping the guardrail entirely.
    
    ## Server-side impact
    
    Codex's local server and app-server protocol needed changes because they
    previously assumed a single workspace ID. The local login flow now
    matches the auth backend interface by sending the allowed workspace list
    as a single comma-separated `allowed_workspace_id` query parameter.
    
    ## Validation
    
    This was tested with:
    
    - A single workspace config
    - With multi-workspace configs
    - With multiple workspaces in the config
    - The user only being a part of a subset of them
    
    All were successful.
    
    Automated coverage:
    
    - `cargo test -p codex-login`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-tui local_chatgpt_auth`
    - `cargo test --locked -p codex-app-server
    login_account_chatgpt_includes_forced_workspace_allowlist_query_param`
  • tests: isolate codex home for live cli (#22563)
    ## Why
    
    Some core integration-test paths were creating Codex state under ambient
    `~/.codex`. In environments where `HOME=/tmp`, that showed up as
    `/tmp/.codex`, which is host-level shared state and makes these tests
    environment/order sensitive.
    
    The affected paths were:
    
    - `core/tests/suite/live_cli.rs`: `run_live()` spawned the real CLI with
    a temp cwd, but without an isolated home, so the child resolved Codex
    home from ambient `HOME`.
    - core / exec-server integration test binaries using
    `configure_test_binary_dispatch(...)`: their startup ctor installs arg0
    helper aliases like `apply_patch` and `codex-linux-sandbox`. Full
    `arg0_dispatch()` also installs aliases from ambient Codex-home
    resolution, so test-binary startup could create `CODEX_HOME/tmp/arg0`;
    with `HOME=/tmp`, that became `/tmp/.codex/tmp/arg0/...`.
    
    ## What changed
    
    - `live_cli` now gives the spawned CLI a temp `HOME` and temp
    `CODEX_HOME`.
    - arg0 alias setup now has an explicit-home form,
    `prepend_path_entry_for_codex_aliases_in(...)`, so test helpers can
    place alias state under a temp directory without relying on ambient
    `CODEX_HOME`.
    - helper re-entry behavior is preserved with
    `dispatch_arg0_if_needed()`, so aliases like `apply_patch` and
    `codex-linux-sandbox` still dispatch correctly before test alias
    installation.
    - core test support keeps the temp Codex home alive for the lifetime of
    the test binary, matching the alias lifetime.
    
    ## Verification
    
    Verified on `dev2` with `HOME=/tmp` that the focused core test-binary
    startup path no longer recreates `/tmp/.codex`.
    
    Also checked the exact `live_cli` test path under `HOME=/tmp`; on `dev2`
    it still hits the existing remote-only `cargo_bin("codex-rs")`
    resolution failure before spawning the child, but `/tmp/.codex` remains
    absent after the run.
  • Fix remote environment test fixtures (#22572)
    ## Why
    The Docker remote-env coverage was failing before it reached the
    behavior those tests are meant to exercise. The remote-aware test
    fixture only registered the remote environment, so tests that
    intentionally select both `local` and `remote` could not start a turn.
    After that was fixed, two tests exposed stale fixtures: the approval
    test was auto-approving under workspace-write, and the remote
    `view_image` test was writing invalid PNG bytes.
    
    ## What Changed
    - Added `EnvironmentManager::create_for_tests_with_local(...)` so tests
    can keep the provider default while also selecting `local` explicitly.
    - Updated `build_remote_aware()` to use that test-only manager when a
    remote exec-server URL is present.
    - Changed the remote apply-patch approval helper to use
    `SandboxPolicy::new_read_only_policy()` so the test actually exercises
    approval caching per environment.
    - Replaced the hardcoded remote `view_image` PNG blob with the existing
    `png_bytes(...)` helper so the test uses a valid image fixture.
    
    ## Validation
    Ran these isolated Docker remote-env tests on the devbox with
    `$remote-tests` setup:
    -
    `suite::remote_env::apply_patch_freeform_routes_to_selected_remote_environment`
    -
    `suite::remote_env::apply_patch_approvals_are_remembered_per_environment`
    -
    `suite::remote_env::apply_patch_intercepted_exec_command_routes_to_selected_remote_environment`
    -
    `suite::remote_env::exec_command_routes_to_selected_remote_environment`
    - `suite::view_image::view_image_routes_to_selected_remote_environment`
    
    All five pass.
  • Support explicit MCP OAuth client IDs (#22575)
    ## Why
    Some MCP OAuth providers require a pre-registered public client ID and
    cannot rely on dynamic client registration. Codex already supports MCP
    OAuth, but it had no way to supply that client ID from config into the
    PKCE flow.
    
    ## What changed
    - add `oauth.client_id` under `[mcp_servers.<server>]` config, including
    config editing and schema generation
    - thread the configured client ID through CLI, app-server, plugin login,
    and MCP skill dependency OAuth entrypoints
    - configure RMCP authorization with the explicit client when present,
    while preserving the existing dynamic-registration path when it is
    absent
    - add focused coverage for config parsing/serialization and OAuth URL
    generation
    
    ## Verification
    - `cargo test -p codex-config -p codex-rmcp-client -p codex-mcp -p
    codex-core-plugins`
    - `cargo test -p codex-core blocking_replace_mcp_servers_round_trips
    --lib`
    - `cargo test -p codex-core
    replace_mcp_servers_streamable_http_serializes_oauth_resource --lib`
    - `cargo test -p codex-core config_schema_matches_fixture --lib`
    
    ## Notes
    Broader local package runs still hit unrelated pre-existing stack
    overflows in:
    - `codex-app-server::in_process_start_clamps_zero_channel_capacity`
    -
    `codex-core::resume_agent_from_rollout_uses_edge_data_when_descendant_metadata_source_is_stale`
  • [codex] Ignore fsmonitor config in Git metadata reads (#22652)
    ## Summary
    - keep Git metadata/status subprocesses independent of repository
    `core.fsmonitor` configuration
    - preserve existing working-tree state reporting while making the helper
    behavior more predictable
    - add regression coverage for `get_has_changes` when a repository
    defines an fsmonitor command
    
    ## Validation
    - `cargo fmt --all`
    - `cargo test -p codex-core test_get_has_changes_`
    - `cargo test -p codex-git-utils`
  • tests: avoid ambient temp sandbox roots (#22576)
    ## Why
    Some sandboxed integration tests enabled both ambient temp roots
    (`TMPDIR` and literal `/tmp`) even though they were not testing
    temp-root behavior. On Linux bwrap, making `/tmp` writable causes
    protected metadata mount targets such as `/tmp/.git`, `/tmp/.agents`,
    and `/tmp/.codex` to be synthesized. If a run is interrupted, those
    top-level markers can be left behind and contaminate later tests.
    
    ## What changed
    For the incidental integration tests that do not need ambient temp-root
    access, set `exclude_tmpdir_env_var` and `exclude_slash_tmp` to `true`.
    Dedicated protected-metadata coverage remains in the lower-level sandbox
    tests that use isolated temp roots.
    
    ## Verification
    Focused remote devbox repros passed with a watcher polling `/tmp/.git`,
    `/tmp/.agents`, and `/tmp/.codex`; no leaked markers were observed.
  • permissions: canonicalize workspace_roots and danger-full-access names (#22624)
    ## Why
    
    This is a small precursor to the larger permissions-migration work. Both
    the comparison stack in
    [#22401](https://github.com/openai/codex/pull/22401) /
    [#22402](https://github.com/openai/codex/pull/22402) and the alternate
    stack in [#22610](https://github.com/openai/codex/pull/22610) /
    [#22611](https://github.com/openai/codex/pull/22611) /
    [#22612](https://github.com/openai/codex/pull/22612) are easier to
    review if the terminology is already settled underneath them.
    
    Because `:project_roots` and `:danger-no-sandbox` have not shipped as
    stable user-facing surface area, carrying them forward as aliases would
    just add more migration logic to the later stacks. This PR removes that
    ambiguity now so the follow-on work can rely on one spelling for each
    built-in concept.
    
    ## What Changed
    
    - renamed the config-facing special filesystem key from `:project_roots`
    to `:workspace_roots`
    - dropped unpublished `:project_roots` parsing support in
    `core/src/config/permissions.rs`, so new config only recognizes
    `:workspace_roots`
    - renamed the built-in full-access permission profile id from
    `:danger-no-sandbox` to `:danger-full-access`
    - dropped unpublished `:danger-no-sandbox` support entirely, including
    the old active-profile canonicalization path, and added explicit
    rejection coverage for the legacy id
    - introduced shared built-in permission-profile id constants in
    `codex-rs/protocol/src/models.rs`
    - updated `core`, `app-server`, and `tui` call sites that special-case
    built-in profiles to use the shared constants and canonical ids
    - updated tests and the Linux sandbox README to use `:workspace_roots` /
    `:danger-full-access`
    
    ## Verification
    
    I focused verification on the three places this rename can regress:
    config parsing, active-profile identity surfaced back out of `core`, and
    user/server call sites that special-case built-in profiles.
    
    Targeted checks:
    
    -
    `config::tests::default_permissions_can_select_builtin_profile_without_permissions_table`
    -
    `config::tests::default_permissions_read_only_applies_additional_writable_roots_as_modifications`
    -
    `config::tests::default_permissions_can_select_builtin_full_access_profile`
    - `config::tests::legacy_danger_no_sandbox_is_rejected`
    - `workspace_root` filtered `codex-core` tests
    -
    `request_processors::thread_processor::thread_processor_tests::thread_processor_behavior_tests::requested_permissions_trust_project_uses_permission_profile_intent`
    -
    `suite::v2::turn_start::turn_start_rejects_invalid_permission_selection_before_starting_turn`
    - `status::tests::status_snapshot_shows_auto_review_permissions`
    -
    `status::tests::status_permissions_full_disk_managed_with_network_is_danger_full_access`
    -
    `app_server_session::tests::embedded_turn_permissions_use_active_profile_selection`
  • Fix turn extension data task plumbing (#22646)
    ## Summary
    - carry the per-turn extension data through RunningTask so abort
    handling can rebuild SessionTaskContext
    - update stale test ExtensionData::new() callsites to pass the turn id
    
    ## Testing
    - Not run after PR branch creation; CI will cover.
  • feat: add layered --profile-v2 config files (#17141)
    ## Why
    
    `--profile-v2 <name>` gives launchers and runtime entry points a named
    profile config without making each profile duplicate the base user
    config. The base `$CODEX_HOME/config.toml` still loads first, then
    `$CODEX_HOME/<name>.config.toml` layers above it and becomes the active
    writable user config for that session.
    
    That keeps shared defaults, plugin/MCP setup, and managed/user
    constraints in one place while letting a named profile override only the
    pieces that need to differ.
    
    ## What Changed
    
    - Added the shared `--profile-v2 <name>` runtime option with validated
    plain names, now represented by `ProfileV2Name`.
    - Extended config layer state so the base user config and selected
    profile config are both `User` layers; APIs expose the active user layer
    and merged effective user config.
    - Threaded profile selection through runtime entry points: `codex`,
    `codex exec`, `codex review`, `codex resume`, `codex fork`, and `codex
    debug prompt-input`.
    - Made user-facing config writes go to the selected profile file when
    active, including TUI/settings persistence, app-server config writes,
    and MCP/app tool approval persistence.
    - Made plugin, marketplace, MCP, hooks, and config reload paths read
    from the merged user config so base and profile layers both participate.
    - Updated app-server config layer schemas to mark profile-backed user
    layers.
    
    ## Limits
    
    `--profile-v2` is still rejected for config-management subcommands such
    as feature, MCP, and marketplace edits. Those paths remain tied to the
    base `config.toml` until they have explicit profile-selection semantics.
    
    Some adjacent background writes may still update base or global state
    rather than the selected profile:
    
    - marketplace auto-upgrade metadata
    - automatic MCP dependency installs from skills
    - remote plugin sync or uninstall config edits
    - personality migration marker/default writes
    
    ## Verification
    
    Added targeted coverage for profile name validation, layer
    ordering/merging, selected-profile writes, app-server config writes,
    session hot reload, plugin config merging, hooks/config fixture updates,
    and MCP/app approval persistence.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Wire turn item contributors into stream output (#22494)
    ## Summary
    - run registered TurnItemContributor hooks for parsed stream output
    items
    - plumb the active turn extension store into stream item handling
    - preserve existing memory citation parsing as fallback after
    contributors run
    
    ## Tests
    - cargo test -p codex-core stream_events_utils -- --nocapture
    - just fmt
    - just fix -p codex-core
    - git diff --check
  • feat: make ToolExecutor an async trait (#22560)
    ## Why
    
    `codex_tools::ToolExecutor` keeps a tool spec attached to its runtime
    handler, but extension tools still carried a parallel
    `ExtensionToolFuture` / `ExtensionToolExecutor` shape. That made
    extension-owned tools look different from host tools even though
    routing, registration, and execution need the same abstraction.
    
    This PR makes the shared executor contract directly async and lets
    extension tools implement it too, so host tools and extension tools can
    move through the same registration path.
    
    ## What changed
    
    - Changed `ToolExecutor::handle` to an `async fn` using `async-trait`,
    and updated built-in tool handlers to implement the async trait
    directly.
    - Replaced the bespoke `ExtensionToolFuture` contract with a marker
    `ExtensionToolExecutor` over `ToolExecutor<ToolCall, Output =
    JsonToolOutput>`, re-exporting `ToolExecutor` from
    `codex-extension-api`.
    - Updated the memories extension tools to implement the shared executor
    trait.
    - Split tool-router construction into collected executors plus hosted
    model specs, keeping hosted tools like web search and image generation
    separate from executable handlers.
    - Updated spec/router tests and extension-tool stubs for the new
    executor shape.
    
    ## Verification
    
    - Not run locally.
  • Simplify TUI startup test coverage (#22573)
    ## Why
    
    The TUI startup test surface had drifted into expensive, brittle
    coverage:
    
    - `tui/tests/suite/no_panic_on_startup.rs` was already ignored as flaky
    while still spawning a PTY to exercise malformed exec-policy rules.
    - `tui/tests/suite/model_availability_nux.rs` used a seeded session,
    cursor-query spoofing, and repeated interrupts to verify a narrow
    resume-path invariant.
    - `app/tests.rs` had started accumulating unrelated startup and summary
    coverage in one flat module even after the surrounding app code was
    split into feature modules.
    
    This keeps those behaviors covered while making the tests cheaper to
    understand and less likely to rot. It also preserves the malformed-rules
    regression from #8803 without requiring a terminal orchestration test.
    
    ## What changed
    
    - Replaced the malformed `rules` startup PTY case with a direct
    exec-policy loader regression:
    
    [`rules_path_file_returns_read_dir_error`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/core/src/exec_policy_tests.rs#L264-L284)
    - Made the existing fresh-session-only startup tooltip behavior explicit
    with
    
    [`should_prepare_startup_tooltip_override`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/tui/src/app/thread_routing.rs#L1272-L1279),
    then added focused coverage for the resume/fork gate and the persisted
    NUX counter.
    - Split startup and session-summary coverage out of
    `tui/src/app/tests.rs` into dedicated modules so the test layout better
    mirrors the current app architecture.
    - Converted one single-message goal validation snapshot into semantic
    assertions where layout was not the behavior under test.
    - Removed the two PTY-heavy suite files that the narrower tests now
    supersede.
    
    ## Verification
    
    - `cargo test -p codex-core rules_path_file_returns_read_dir_error`
    - `cargo test -p codex-tui startup_`
    - `cargo test -p codex-tui session_summary_`
    - `cargo test -p codex-tui
    goal_slash_command_rejects_oversized_objective`
  • chore(config) rm experimental_use_freeform_apply_patch (#22565)
    ## Summary
    Get rid of the `experimental_use_freeform_apply_patch` config option,
    since it is now encoded in model config. No deprecation message since it
    has been experimental this entire time.
    
    ## Testing
    - [x] Updated unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Remove connector_openai prefix filtering (#22555)
    Remove unnecessary prefix filtering from codex
    
    ## Test Plan
    
    Test local cli build + make sure backend returns appropriate apps 
    
    ```
    cd ~/code/codex/codex-rs
    cargo build -p codex-cli --bin codex
    ./target/debug/codex
    ```
    
    Appropriate apps show up in my list
  • Spill oversized PreToolUse additionalContext (#22529)
    # Why
    
    `PreToolUse.additionalContext` became model-visible after #20692, but
    the hook-output spilling path from #21069 never picked up that newer
    lane. As a result, oversized `PreToolUse` context could bypass the
    truncation/spill treatment that already applies to the other hook
    outputs Codex forwards to the model.
    
    # What
    
    - Run `PreToolUseOutcome.additional_contexts` through
    `maybe_spill_texts(...)`
    - Add an integration test proving a large `PreToolUse.additionalContext`
    is replaced with a truncated preview plus spill-file pointer, while the
    full text is preserved on disk.
  • Make multi_agent_v2 wait_agent timeouts configurable (#22528)
    ## Why
    
    `multi_agent_v2` already allowed configuring the minimum `wait_agent`
    timeout, but the default timeout and upper bound were still hard-coded.
    That made it hard to tune waits for subagent mailbox activity in
    sessions that need either faster wakeups or longer waits, and it meant
    the model-visible `wait_agent` schema could not fully reflect the
    resolved runtime limits.
    
    ## What Changed
    
    - Added `features.multi_agent_v2.max_wait_timeout_ms` and
    `features.multi_agent_v2.default_wait_timeout_ms` alongside the existing
    `min_wait_timeout_ms` setting.
    - Validated all three timeouts in config as `0..=3_600_000`, with
    `min_wait_timeout_ms <= default_wait_timeout_ms <= max_wait_timeout_ms`.
    - Thread and review session tool config now passes the resolved
    min/default/max values into the `wait_agent` tool schema.
    - `wait_agent` now uses the configured default when `timeout_ms` is
    omitted and rejects explicit values outside the configured min/max range
    instead of silently clamping them.
    - Updated the generated config schema and config-lock test coverage for
    the new fields.