Commit Graph

4205 Commits

  • config: enforce enterprise feature requirements (#13388)
    ## Why
    
    Enterprises can already constrain approvals, sandboxing, and web search
    through `requirements.toml` and MDM, but feature flags were still only
    configurable as managed defaults. That meant an enterprise could suggest
    feature values, but it could not actually pin them.
    
    This change closes that gap and makes enterprise feature requirements
    behave like the other constrained settings. The effective feature set
    now stays consistent with enterprise requirements during config load,
    when config writes are validated, and when runtime code mutates feature
    flags later in the session.
    
    It also tightens the runtime API for managed features. `ManagedFeatures`
    now follows the same constraint-oriented shape as `Constrained<T>`
    instead of exposing panic-prone mutation helpers, and production code
    can no longer construct it through an unconstrained `From<Features>`
    path.
    
    The PR also hardens the `compact_resume_fork` integration coverage on
    Windows. After the feature-management changes,
    `compact_resume_after_second_compaction_preserves_history` was
    overflowing the libtest/Tokio thread stacks on Windows, so the test now
    uses an explicit larger-stack harness as a pragmatic mitigation. That
    may not be the ideal root-cause fix, and it merits a parallel
    investigation into whether part of the async future chain should be
    boxed to reduce stack pressure instead.
    
    ## What Changed
    
    Enterprises can now pin feature values in `requirements.toml` with the
    requirements-side `features` table:
    
    ```toml
    [features]
    personality = true
    unified_exec = false
    ```
    
    Only canonical feature keys are allowed in the requirements `features`
    table; omitted keys remain unconstrained.
    
    - Added a requirements-side pinned feature map to
    `ConfigRequirementsToml`, threaded it through source-preserving
    requirements merge and normalization in `codex-config`, and made the
    TOML surface use `[features]` (while still accepting legacy
    `[feature_requirements]` for compatibility).
    - Exposed `featureRequirements` from `configRequirements/read`,
    regenerated the JSON/TypeScript schema artifacts, and updated the
    app-server README.
    - Wrapped the effective feature set in `ManagedFeatures`, backed by
    `ConstrainedWithSource<Features>`, and changed its API to mirror
    `Constrained<T>`: `can_set(...)`, `set(...) -> ConstraintResult<()>`,
    and result-returning `enable` / `disable` / `set_enabled` helpers.
    - Removed the legacy-usage and bulk-map passthroughs from
    `ManagedFeatures`; callers that need those behaviors now mutate a plain
    `Features` value and reapply it through `set(...)`, so the constrained
    wrapper remains the enforcement boundary.
    - Removed the production loophole for constructing unconstrained
    `ManagedFeatures`. Non-test code now creates it through the configured
    feature-loading path, and `impl From<Features> for ManagedFeatures` is
    restricted to `#[cfg(test)]`.
    - Rejected legacy feature aliases in enterprise feature requirements,
    and return a load error when a pinned combination cannot survive
    dependency normalization.
    - Validated config writes against enterprise feature requirements before
    persisting changes, including explicit conflicting writes and
    profile-specific feature states that normalize into invalid
    combinations.
    - Updated runtime and TUI feature-toggle paths to use the constrained
    setter API and to persist or apply the effective post-constraint value
    rather than the requested value.
    - Updated the `core_test_support` Bazel target to include the bundled
    core model-catalog fixtures in its runtime data, so helper code that
    resolves `core/models.json` through runfiles works in remote Bazel test
    environments.
    - Renamed the core config test coverage to emphasize that effective
    feature values are normalized at runtime, while conflicting persisted
    config writes are rejected.
    - Ran `compact_resume_after_second_compaction_preserves_history` inside
    an explicit 8 MiB test thread and Tokio runtime worker stack, following
    the existing larger-stack integration-test pattern, to keep the Windows
    `compact_resume_fork` test slice from aborting while a parallel
    investigation continues into whether some of the underlying async
    futures should be boxed.
    
    ## Verification
    
    - `cargo test -p codex-config`
    - `cargo test -p codex-core feature_requirements_ -- --nocapture`
    - `cargo test -p codex-core
    load_requirements_toml_produces_expected_constraints -- --nocapture`
    - `cargo test -p codex-core
    compact_resume_after_second_compaction_preserves_history -- --nocapture`
    - `cargo test -p codex-core compact_resume_fork -- --nocapture`
    - Re-ran the built `codex-core` `tests/all` binary with
    `RUST_MIN_STACK=262144` for
    `compact_resume_after_second_compaction_preserves_history` to confirm
    the explicit-stack harness fixes the deterministic low-stack repro.
    - `cargo test -p codex-core`
    - This still fails locally in unrelated integration areas that expect
    the `codex` / `test_stdio_server` binaries or hit existing `search_tool`
    wiremock mismatches.
    
    ## Docs
    
    `developers.openai.com/codex` should document the requirements-side
    `[features]` table for enterprise and MDM-managed configuration,
    including that it only accepts canonical feature keys and that
    conflicting config writes are rejected.
  • Feat: Preserve network access on read-only sandbox policies (#13409)
    ## Summary
    
    `PermissionProfile.network` could not be preserved when additional or
    compiled permissions resolved to
    `SandboxPolicy::ReadOnly`, because `ReadOnly` had no network_access
    field. This change makes read-only + network
    enabled representable directly and threads that through the protocol,
    app-server v2 mirror, and permission-
      merging logic.
    
    ## What changed
    
    - Added `network_access: bool` to `SandboxPolicy::ReadOnly` in the core
    protocol and app-server v2 protocol.
    - Kept backward compatibility by defaulting the new field to false, so
    legacy read-only payloads still
        deserialize unchanged.
    - Updated `has_full_network_access()` and sandbox summaries to respect
    read-only network access.
      - Preserved PermissionProfile.network when:
          - compiling skill permission profiles into sandbox policies
          - normalizing additional permissions
          - merging additional permissions into existing sandbox policies
    - Updated the approval overlay to show network in the rendered
    permission rule when requested.
      - Regenerated app-server schema fixtures for the new v2 wire shape.
  • [bazel] Bump rules_rs and llvm (#13366)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • copy command-runner to CODEX_HOME so sandbox users can always execute it (#13413)
    • Keep Windows sandbox runner launches working from packaged installs by
    running the helper from a user-owned runtime location.
    
    On some Windows installs, the packaged helper location is difficult to
    use reliably for sandboxed runner launches even though the binaries are
    present. This change works around that by copying codex-
    command-runner.exe into CODEX_HOME/.sandbox-bin/, reusing that copy
    across launches, and falling back to the existing packaged-path lookup
    if anything goes wrong.
    
    The runtime copy lives in a dedicated directory with tighter ACLs than
    .sandbox: sandbox users can read and execute the runner there, but they
    cannot modify it. This keeps the workaround focused on the
    command runner, leaves the setup helper on its trusted packaged path,
    and adds logging so it is clear which runner path was selected at
    launch.
  • feat(app-server): propagate app-server trace context into core (#13368)
    ### Summary
    Propagate trace context originating at app-server RPC method handlers ->
    codex core submission loop (so this includes spans such as `run_turn`!).
    This implements PR 2 of the app-server tracing rollout.
    
    This also removes the old lower-level env-based reparenting in core so
    explicit request/submission ancestry wins instead of being overridden by
    ambient `TRACEPARENT` state.
    
    ### What changed
    - Added `trace: Option<W3cTraceContext>` to codex_protocol::Submission
    - Taught `Codex::submit()` / `submit_with_id()` to automatically capture
    the current span context when constructing or forwarding a submission
    - Wrapped the core submission loop in a submission_dispatch span
    parented from Submission.trace
    - Warn on invalid submission trace carriers and ignore them cleanly
    - Removed the old env-based downstream reparenting path in core task
    execution
    - Stopped OTEL provider init from implicitly attaching env trace context
    process-wide
    - Updated mcp-server Submission call sites for the new field
    
    Added focused unit tests for:
    - capturing trace context into Submission
    - preferring `Submission.trace` when building the core dispatch span
    
    ### Why
    PR 1 gave us consistent inbound request spans in app-server, but that
    only covered the transport boundary. For long-running work like turns
    and reviews, the important missing piece was preserving ancestry after
    the request handler returns and core continues work on a different async
    path.
    
    This change makes that handoff explicit and keeps the parentage rules
    simple:
    - app-server request span sets the current context
    - `Submission.trace` snapshots that context
    - core restores it once, at the submission boundary
    - deeper core spans inherit naturally
    
    That also lets us stop relying on env-based reparenting for this path,
    which was too ambient and could override explicit ancestry.
  • feat(app-server): add a skills/changed v2 notification (#13414)
    This adds a first-class app-server v2 `skills/changed` notification for
    the existing skills live-reload signal.
    
    Before this change, clients only had the legacy raw
    `codex/event/skills_update_available` event. With this PR, v2 clients
    can listen for a typed JSON-RPC notification instead of depending on the
    legacy `codex/event/*` stream, which we want to remove soon.
  • [feedback] diagnostics (#13292)
    - added header logic to display diagnostics on cli
    - added logic for collecting env vars
    
    <img width="606" height="327" alt="Screenshot 2026-03-03 at 3 49 31 PM"
    src="https://github.com/user-attachments/assets/05e78c56-8cb3-47fa-abaf-3e57f1fdd8e2"
    />
    
    <img width="690" height="353" alt="Screenshot 2026-03-02 at 6 47 54 PM"
    src="https://github.com/user-attachments/assets/e470b559-13f4-44d9-897f-bc398943c6d1"
    />
  • feat: load plugin apps (#13401)
    load plugin-apps from `.app.json`.
    
    make apps runtime-mentionable iff `codex_apps` MCP actually exposes
    tools for that `connector_id`.
    
    if the app isn't available, it's filtered out of runtime connector set,
    so no tools are added and no app-mentions resolve.
    
    right now we don't have a clean cli-side error for an app not being
    installed. can look at this after.
    
    ### Tests
    Added tests, tested locally that using a plugin that bundles an app
    picks up the app.
  • Make js_repl image output controllable (#13331)
    ## Summary
    
    Instead of always adding inner function call outputs to the model
    context, let js code decide which ones to return.
    
    - Stop auto-hoisting nested tool outputs from `codex.tool(...)` into the
    outer `js_repl` function output.
    - Keep `codex.tool(...)` return values unchanged as structured JS
    objects.
    - Add `codex.emitImage(...)` as the explicit path for attaching an image
    to the outer `js_repl` function output.
    - Support emitting from a direct image URL, a single `input_image` item,
    an explicit `{ bytes, mimeType }` object, or a raw tool response object
    containing exactly one image.
    - Preserve existing `view_image` original-resolution behavior when JS
    emits the raw `view_image` tool result.
    - Suppress the special `ViewImageToolCall` event for `js_repl`-sourced
    `view_image` calls so nested inspection stays side-effect free until JS
    explicitly emits.
    - Update the `js_repl` docs and generated project instructions with both
    recommended patterns:
      - `await codex.emitImage(codex.tool("view_image", { path }))`
    - `await codex.emitImage({ bytes: await page.screenshot({ type: "jpeg",
    quality: 85 }), mimeType: "image/jpeg" })`
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    -  `1` https://github.com/openai/codex/pull/13050
    - 👉 `2` https://github.com/openai/codex/pull/13331
    -  `3` https://github.com/openai/codex/pull/13049
  • Add under-development original-resolution view_image support (#13050)
    ## Summary
    
    Add original-resolution support for `view_image` behind the
    under-development `view_image_original_resolution` feature flag.
    
    When the flag is enabled and the target model is `gpt-5.3-codex` or
    newer, `view_image` now preserves original PNG/JPEG/WebP bytes and sends
    `detail: "original"` to the Responses API instead of using the legacy
    resize/compress path.
    
    ## What changed
    
    - Added `view_image_original_resolution` as an under-development feature
    flag.
    - Added `ImageDetail` to the protocol models and support for serializing
    `detail: "original"` on tool-returned images.
    - Added `PromptImageMode::Original` to `codex-utils-image`.
      - Preserves original PNG/JPEG/WebP bytes.
      - Keeps legacy behavior for the resize path.
    - Updated `view_image` to:
    - use the shared `local_image_content_items_with_label_number(...)`
    helper in both code paths
      - select original-resolution mode only when:
        - the feature flag is enabled, and
        - the model slug parses as `gpt-5.3-codex` or newer
    - Kept local user image attachments on the existing resize path; this
    change is specific to `view_image`.
    - Updated history/image accounting so only `detail: "original"` images
    use the docs-based GPT-5 image cost calculation; legacy images still use
    the old fixed estimate.
    - Added JS REPL guidance, gated on the same feature flag, to prefer JPEG
    at 85% quality unless lossless is required, while still allowing other
    formats when explicitly requested.
    - Updated tests and helper code that construct
    `FunctionCallOutputContentItem::InputImage` to carry the new `detail`
    field.
    
    ## Behavior
    
    ### Feature off
    - `view_image` keeps the existing resize/re-encode behavior.
    - History estimation keeps the existing fixed-cost heuristic.
    
    ### Feature on + `gpt-5.3-codex+`
    - `view_image` sends original-resolution images with `detail:
    "original"`.
    - PNG/JPEG/WebP source bytes are preserved when possible.
    - History estimation uses the GPT-5 docs-based image-cost calculation
    for those `detail: "original"` images.
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    - 👉 `1` https://github.com/openai/codex/pull/13050
    -  `2` https://github.com/openai/codex/pull/13331
    -  `3` https://github.com/openai/codex/pull/13049
  • Add thread metadata update endpoint to app server (#13280)
    ## Summary
    - add the v2 `thread/metadata/update` API, including
    protocol/schema/TypeScript exports and app-server docs
    - patch stored thread `gitInfo` in sqlite without resuming the thread,
    with validation plus support for explicit `null` clears
    - repair missing sqlite thread rows from rollout data before patching,
    and make those repairs safe by inserting only when absent and updating
    only git columns so newer metadata is not clobbered
    - keep sqlite authoritative for mutable thread git metadata by
    preserving existing sqlite git fields during reconcile/backfill and only
    using rollout `SessionMeta` git fields to fill gaps
    - add regression coverage for the endpoint, repair paths, concurrent
    sqlite writes, clearing git fields, and rollout/backfill reconciliation
    - fix the login server shutdown race so cancelling before the waiter
    starts still terminates `block_until_done()` correctly
    
    ## Testing
    - `cargo test -p codex-state
    apply_rollout_items_preserves_existing_git_branch_and_fills_missing_git_fields`
    - `cargo test -p codex-state
    update_thread_git_info_preserves_newer_non_git_metadata`
    - `cargo test -p codex-core
    backfill_sessions_preserves_existing_git_branch_and_fills_missing_git_fields`
    - `cargo test -p codex-app-server thread_metadata_update`
    - `cargo test`
    - currently fails in existing `codex-core` grep-files tests with
    `unsupported call: grep_files`:
        - `suite::grep_files::grep_files_tool_collects_matches`
        - `suite::grep_files::grep_files_tool_reports_empty_results`
  • tui: align pending steers with core acceptance (#12868)
    ## Summary
    - submit `Enter` steers immediately while a turn is already running
    instead of routing them through `queued_user_messages`
    - keep those submitted steers visible in the footer as `pending_steers`
    until core records them as a user message or aborts the turn
    - reconcile pending steers on `ItemCompleted(UserMessage)`, not
    `RawResponseItem`
    - emit user-message item lifecycle for leftover pending input at task
    finish, then remove the TUI `TurnComplete` fallback
    - keep `queued_user_messages` for actual queued drafts, rendered below
    pending steers
    
    ## Problem
    While the assistant was generating, pressing `Enter` could send the
    input into `queued_user_messages`. That queue only drains after the turn
    ends, so ordinary steers behaved like queued drafts instead of landing
    at the next core sampling boundary.
    
    The first version of this fix also used `RawResponseItem` to decide when
    a steer had landed. Review feedback was that this is the wrong
    abstraction for client behavior.
    
    There was also a late edge case in core: if pending steer input was
    accepted after the final sampling decision but before `TurnComplete`,
    core would record that user message into history at task finish without
    emitting `ItemStarted(UserMessage)` / `ItemCompleted(UserMessage)`. TUI
    had a fallback to paper over that gap locally.
    
    ## Approach
    - `Enter` during an active turn now submits a normal `Op::UserTurn`
    immediately
    - TUI keeps a local pending-steer preview instead of rendering that user
    message into history immediately
    - when core records the steer as `ItemCompleted(UserMessage)`, TUI
    matches and removes the corresponding pending preview, then renders the
    committed user message
    - core now emits the same user-message lifecycle when
    `on_task_finished(...)` drains leftover pending user input, before
    `TurnComplete`
    - with that lifecycle gap closed in core, TUI no longer needs to flush
    pending steers into history on `TurnComplete`
    - if the turn is interrupted, pending steers and queued drafts are both
    restored into the composer, with pending steers first
    
    ## Notes
    - `Tab` still uses the real queued-message path
    - `queued_user_messages` and `pending_steers` are separate state with
    separate semantics
    - the pending-steer matching key is built directly from `UserInput`
    - this removes the new TUI dependency on `RawResponseItem`
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-core
    task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input --
    --nocapture`
    - `cargo test -p codex-tui`
  • fix(network-proxy): reject mismatched host headers (#13275)
    ## Summary
    - reject plain HTTP absolute-form requests whose Host header does not
    match the request target authority
    - add host/port-aware Host header validation for non-default ports
    - add regression coverage for mismatched Host forwarding and validator
    edge cases
  • Refactor plugin config and cache path (#13333)
    Update config.toml plugin entries to use
    <plugin_name>@<marketplace_name> as the key.
    Plugin now stays in
    [plugins/cache/marketplace-name/plugin-name/$version/]
    Clean up the plugin code structure.
    Add plugin install functionality (not used yet).
  • Require deduplicator success before commenting (#13399)
    Fixed recent regression in issue dedup action
  • Build delegated realtime handoff text from all messages (#13395)
    ## Summary
    - Route delegated realtime handoff turns from all handoff message texts,
    preserving order
    - Fallback to input_transcript only when no messages are present
    - Add regression coverage for multi-message handoff requests
  • chore(app-server): restore EventMsg TS types (#13397)
    Realized EventMsg generated types were unintentionally removed as part
    of this PR: https://github.com/openai/codex/pull/13375
    
    Turns out our TypeScript export pipeline relied on transitively reaching
    `EventMsg`. We should still export `EventMsg` explicitly since we're
    still emitting `codex/event/*` events (for now, but getting dropped soon
    as well).
  • chore(app-server): delete v1 RPC methods and notifications (#13375)
    ## Summary
    This removes the old app-server v1 methods and notifications we no
    longer need, while keeping the small set the main codex app client still
    depends on for now.
    
    The remaining legacy surface is:
    - `initialize`
    - `getConversationSummary`
    - `getAuthStatus`
    - `gitDiffToRemote`
    - `fuzzyFileSearch`
    - `fuzzyFileSearch/sessionStart`
    - `fuzzyFileSearch/sessionUpdate`
    - `fuzzyFileSearch/sessionStop`
    
    And the raw `codex/event/*` notifications emitted from core. These
    notifications will be removed in a followup PR.
    
    ## What changed
    - removed deprecated v1 request variants from the protocol and
    app-server dispatcher
    - removed deprecated typed notifications: `authStatusChange`,
    `loginChatGptComplete`, and `sessionConfigured`
    - updated the app-server test client to use v2 flows instead of deleted
    v1 flows
    - deleted legacy-only app-server test suites and added focused coverage
    for `getConversationSummary`
    - regenerated app-server schema fixtures and updated the MCP interface
    docs to match the remaining compatibility surface
    
    ## Testing
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server`
  • fix (#13389)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • Collapse parsed command summaries when any stage is unknown (#13043)
    ## Summary
    - collapse parsed command output to a single `Unknown` whenever the
    normal parse includes any unknown entry
    - preserve the existing parsing flow and existing `cd` handling,
    including the current `cd && ...` collapse behavior
    - trim redundant tests and add focused coverage for collapse-on-unknown
    cases
    
    ## Testing
    - `cargo test -p codex-shell-command`
  • chore: rm --all-features flag from rust-analyzer (#13381)
    follows up on #12429; rm `--all-features` from flags used with
    `rust-analyzer` on save to prevent disk space bloat under `target/`.
  • app-server: source /feedback logs from sqlite at trace level (#12969)
    ## Summary
    - write app-server SQLite logs at TRACE level when SQLite is enabled
    - source app-server `/feedback` log attachments from SQLite for the
    requested thread when available
    - flush buffered SQLite log writes before `/feedback` queries them so
    newly emitted events are not lost behind the async inserter
    - include same-process threadless SQLite rows in those `/feedback` logs
    so the attachment matches the process-wide feedback buffer more closely
    - keep the existing in-memory ring buffer fallback unchanged, including
    when the SQLite query returns no rows
    
    ## Details
    - add a byte-bounded `query_feedback_logs` helper in `codex-state` so
    `/feedback` does not fetch all rows before truncating
    - scope SQLite feedback logs to the requested thread plus threadless
    rows from the same `process_uuid`
    - format exported SQLite feedback lines with the log level prefix to
    better match the in-memory feedback formatter
    - add an explicit `LogDbLayer::flush()` control path and await it in
    app-server before querying SQLite for feedback logs
    - pass optional SQLite log bytes through `codex-feedback` as the
    `codex-logs.log` attachment override
    - leave TUI behavior unchanged apart from the updated `upload_feedback`
    call signature
    - add regression coverage for:
      - newest-within-budget ordering
      - excluding oversized newest rows
      - including same-process threadless rows
      - keeping the newest suffix across mixed thread and threadless rows
      - matching the feedback formatter shape aside from span prefixes
      - falling back to the in-memory snapshot when SQLite returns no logs
      - flushing buffered SQLite rows before querying
    
    ## Follow-up
    - SQLite feedback exports still do not reproduce span prefixes like
    `feedback-thread{thread_id=...}:`; there is a `TODO(ccunningham)` in
    `codex-rs/state/src/log_db.rs` for that follow-up.
    
    ## Testing
    - `cd codex-rs && cargo test -p codex-state`
    - `cd codex-rs && cargo test -p codex-app-server`
    - `cd codex-rs && just fmt`
  • app-server-protocol: export flat v2 schema bundle (#13324)
    ## Summary
    - add an `--experimental` flag to the export binary and thread the
    option through TypeScript and JSON schema generation
    - flatten the v2 schema bundle into a datamodel-code-generator-friendly
    `codex_app_server_protocol.v2.schemas.json` export
    - retarget shared helper refs to namespaced v2 definitions, add coverage
    for the new export behavior, and vendor the generated schema fixtures
    
    ## Validation
    - `cargo test -p codex-app-server-protocol` (71 unit tests and bin
    targets passed locally; the final schema fixture integration target was
    revalidated via fresh schema regeneration and a tree diff)
    - `./target/debug/write_schema_fixtures --schema-root <tmpdir>`
    - `diff -rq app-server-protocol/schema <tmpdir>`
    
    ## Tickets
    - None
  • realtime prompt changes (#13376)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • Add Windows direct install script (#12741)
    ## Summary
    - add a direct install script for Windows at
    `scripts/install/install.ps1`
    - extend release staging so `install.ps1` is published alongside
    `install.sh`
    - install the Windows runtime payload (`codex.exe`, `rg.exe`, and helper
    binaries) from the existing platform npm package
    
    ## Dependencies
    - Depends on https://github.com/openai/codex/pull/12740
    
    ## Testing
    - Smoke-tested with powershell
  • feat: pres artifact part 5 (#13355)
    Mostly written by Codex
  • feat: presentation artifact p1 (#13341)
    Part 1 of presentation tool artifact
  • app-server service tier plumbing (plus some cleanup) (#13334)
    followup to https://github.com/openai/codex/pull/13212 to expose fast
    tier controls to app server
    (majority of this PR is generated schema jsons - actual code is +69 /
    -35 and +24 tests )
    
    - add service tier fields to the app-server protocol surfaces used by
    thread lifecycle, turn start, config, and session configured events
    - thread service tier through the app-server message processor and core
    thread config snapshots
    - allow runtime config overrides to carry service tier for app-server
    callers
    
    cleanup:
    - Removing useless "legacy" code supporting "standard" - we moved to
    None | "fast", so "standard" is not needed.
  • fix: agent when profile (#13235)
    Co-authored-by: Josh McKinney <joshka@openai.com>
    Co-authored-by: Codex <noreply@openai.com>
  • fix(core): scope file search gitignore to repository context (#13250)
    Closes #3493
    
    ## Problem
    
    When a user's home directory (or any ancestor) contains a broad
    `.gitignore` (e.g. `*` + `!.gitignore`), the `@` file mention picker in
    Codex silently hides valid repository files like `package.json`. The
    picker returns `no matches` for searches that should succeed. This is
    surprising because manually typed paths still work, making the failure
    hard to diagnose.
    
    ## Mental model
    
    Git itself never walks above the repository root to assemble its ignore
    list. Its `.gitignore` resolution is strictly scoped: it reads
    `.gitignore` files from the repo root downward, the per-repo
    `.git/info/exclude`, and the user's global excludes file (via
    `core.excludesFile`). A `.gitignore` sitting in a parent directory above
    the repo root has no effect on `git status`, `git ls-files`, or any
    other git operation. Our file search should replicate this contract
    exactly.
    
    The `ignore` crate's `WalkBuilder` has a `require_git` flag that
    controls whether it follows this contract:
    
    - `require_git(false)` (the previous setting): the walker reads
    `.gitignore` files from _all_ ancestor directories, even those above or
    outside the repository root. This is a deliberate divergence from git's
    behavior in the `ignore` crate, intended for non-git use cases. It means
    a `~/.gitignore` with `*` will suppress every file in the walk—something
    git itself would never do.
    
    - `require_git(true)` (this fix): the walker only applies `.gitignore`
    semantics when it detects a `.git` directory, scoping ignore resolution
    to the repository boundary. This matches git's own behavior: parent
    `.gitignore` files above the repo root have no effect.
    
    The fix is a one-line change: `require_git(false)` becomes
    `require_git(true)`.
    
    ## How `require_git(false)` got here
    
    The setting was introduced in af338cc (#2981, "Improve @ file search:
    include specific hidden dirs such as .github, .gitlab"). That PR's goal
    was to make hidden directories like `.github` and `.vscode` discoverable
    by setting `.hidden(false)` on the walker. The `require_git(false)` was
    added alongside it with the comment _"Don't require git to be present to
    apply git-related ignore rules"_—the author likely intended gitignore
    rules to still filter results even when no `.git` directory exists (e.g.
    searching an extracted tarball that has a `.gitignore` but no `.git`).
    
    The unintended consequence: with `require_git(false)`, the `ignore`
    crate walks _above_ the search root to find `.gitignore` files in
    ancestor directories. This is a side effect the original author almost
    certainly didn't anticipate. The PR message says "Preserve `.gitignore`
    semantics," but `require_git(false)` actually _breaks_ git's semantics
    by applying ancestor ignore files that git itself would never read.
    
    In short: the intent was "apply gitignore even without `.git`" but the
    effect was "apply gitignore from every ancestor directory." This fix
    restores git-correct scoping.
    
    ## Non-goals
    
    - This PR does not change behavior when `respect_gitignore` is `false`
    (that path already disables all git-related ignore rules).
    - The first test
    (`parent_gitignore_outside_repo_does_not_hide_repo_files`) intentionally
    omits `git init`. The `ignore` crate's `require_git(true)` causes it to
    skip gitignore processing entirely when no `.git` exists, which is the
    desired behavior for that scenario. A second test
    (`git_repo_still_respects_local_gitignore_when_enabled`) covers the
    complementary case with a real git repo.
    
    ## Tradeoffs
    
    **Behavioral shift**: With `require_git(true)`, directories that contain
    `.gitignore` files but are _not_ inside a git repository will no longer
    have those ignore rules applied during `@` search. This is a correctness
    improvement for the primary use case (searching inside repos), but
    changes behavior for the edge case of searching non-repo directories
    that happen to have `.gitignore` files. In practice, Codex is
    overwhelmingly used inside git repositories, so this tradeoff strongly
    favors the fix.
    
    **Two test strategies**: The first test omits `git init` to verify
    parent ignore leakage is blocked; the second runs `git init` to verify
    the repo's own `.gitignore` is still honored. Together they cover both
    sides of the `require_git(true)` contract.
    
    ## Architecture
    
    The change is in `walker_worker()` within
    `codex-rs/file-search/src/lib.rs`, which configures the
    `ignore::WalkBuilder` used by the file search walker thread. The walker
    feeds discovered file paths into `nucleo` for fuzzy matching. The
    `require_git` flag controls whether the walker consults `.gitignore`
    files at all—it sits upstream of all ignore processing.
    
    ```
    walker_worker
      └─ WalkBuilder::new(root)
           ├─ .hidden(false)         — include dotfiles
           ├─ .follow_links(true)    — follow symlinks
           ├─ .require_git(true)     — ← THE FIX: only apply gitignore in git repos
           └─ (conditional) git_ignore(false), git_global(false), etc.
                └─ applied when respect_gitignore == false
    ```
    
    ## Tests
    
    - `parent_gitignore_outside_repo_does_not_hide_repo_files`: creates a
    temp directory tree with a parent `.gitignore` containing `*`, a child
    "repo" directory with `package.json` and `.vscode/settings.json`, and
    asserts that both files are discoverable via `run()` with
    `respect_gitignore: true`.
    - `git_repo_still_respects_local_gitignore_when_enabled`: the
    complementary test—runs `git init` inside the child directory and
    verifies that the repo's own `.gitignore` exclusions still work (e.g.
    `.vscode/extensions.json` is excluded while `.vscode/settings.json` is
    whitelisted). Confirms that `require_git(true)` does not disable
    gitignore processing inside actual git repositories.
  • add fast mode toggle (#13212)
    - add a local Fast mode setting in codex-core (similar to how model id
    is currently stored on disk locally)
    - send `service_tier=priority` on requests when Fast is enabled
    - add `/fast` in the TUI and persist it locally
    - feature flag
  • tui: preserve kill buffer across submit and slash-command clears (#12006)
    ## Problem
    
    Before this change, composer paths that cleared the textarea after
    submit or slash-command dispatch
    also cleared the textarea kill buffer. That meant a user could `Ctrl+K`
    part of a draft, trigger a
    composer action that cleared the visible draft, and then lose the
    ability to `Ctrl+Y` the killed
    text back.
    
    This was especially awkward for workflows where the user wants to
    temporarily remove text, run a
    composer action such as changing reasoning level or dispatching a slash
    command, and then restore
    the killed text into the now-empty draft.
    
    ## Mental model
    
    This change separates visible draft state from editing-history state.
    
    The visible draft includes the current textarea contents and text
    elements that should be cleared
    when the composer submits or dispatches a command. The kill buffer is
    different: it represents the
    most recent killed text and should survive those composer-driven clears
    so the user can still yank
    it back afterward.
    
    After this change, submit and slash-command dispatch still clear the
    visible textarea contents, but
    they no longer erase the most recent kill.
    
    ## Non-goals
    
    This does not implement a multi-entry kill ring or change the semantics
    of `Ctrl+K` and `Ctrl+Y`
    beyond preserving the existing yank target across these clears.
    
    It also does not change how submit, slash-command parsing, prompt
    expansion, or attachment handling
    work, except that those flows no longer discard the textarea kill buffer
    as a side effect of
    clearing the draft.
    
    ## Tradeoffs
    
    The main tradeoff is that clearing the visible textarea is no longer
    equivalent to fully resetting
    all editing state. That is intentional here, because submit and
    slash-command dispatch are composer
    actions, not requests to forget the user's most recent kill.
    
    The benefit is better editing continuity. The cost is that callers must
    understand that full-buffer
    replacement resets visible draft state but not the kill buffer.
    
    ## Architecture
    
    The behavioral change is in `TextArea`: full-buffer replacement now
    rebuilds text and elements
    without clearing `kill_buffer`.
    
    `ChatComposer` already clears the textarea after successful submit and
    slash-command dispatch by
    calling into those textarea replacement paths. With this change, those
    existing composer flows
    inherit the new behavior automatically: the visible draft is cleared,
    but the last killed text
    remains available for `Ctrl+Y`.
    
    The tests cover both layers:
    
    - `TextArea` verifies that the kill buffer survives full-buffer
    replacement.
    - `ChatComposer` verifies that it survives submit.
    - `ChatComposer` also verifies that it survives slash-command dispatch.
    
    ## Observability
    
    There is no dedicated logging for kill-buffer preservation. The most
    direct way to reason about the
    behavior is to inspect textarea-wide replacement paths and confirm
    whether they treat the kill
    buffer as visible-buffer state or as editing-history state.
    
    If this regresses in the future, the likely failure mode is simple and
    user-visible: `Ctrl+Y` stops
    restoring text after submit or slash-command clears even though ordinary
    kill/yank still works
    within a single uninterrupted draft.
    
    ## Tests
    
    Added focused regression coverage for the new contract:
    
    - `kill_buffer_persists_across_set_text`
    - `kill_buffer_persists_after_submit`
    - `kill_buffer_persists_after_slash_command_dispatch`
    
    Local verification:
    - `just fmt`
    - `cargo test -p codex-tui`
    
    ---------
    
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • chore: remove SkillMetadata.permissions and derive skill sandboxing from permission_profile (#13061)
    ## Summary
    
    This change removes the compiled permissions field from skill metadata
    and keeps permission_profile as the single source of truth.
    
    Skill loading no longer compiles skill permissions eagerly. Instead, the
    zsh-fork skill escalation path compiles `skill.permission_profile` when
    it needs to determine the sandbox to apply for a skill script.
    
      ## Behavior change
    
      For skills that declare:
    ```
      permissions: {}
    ```
    we now treat that the same as having no skill permissions override,
    instead of creating and using a default readonly sandbox. This change
    makes the behavior more intuitive:
    
      - only non-empty skill permission profiles affect sandboxing
    - omitting permissions and writing permissions: {} now mean the same
    thing
    - skill metadata keeps a single permissions representation instead of
    storing derived state too
    
    Overall, this makes skill sandbox behavior easier to understand and more
    predictable.