Commit Graph

58 Commits

  • Tweak /plugin menu wording (#15676)
    - Updated `/plugin` UI messaging for clearer wording.
    - Synced the same copy changes across `tui` and `tui_app_server`.
  • tui_app_server: cancel active login before Ctrl+C exit (#15673)
    ## Summary
    
    Fixes slow `Ctrl+C` exit from the ChatGPT browser-login screen in
    `tui_app_server`.
    
    ## Root cause
    
    Onboarding-level `Ctrl+C` quit bypassed the auth widget's cancel path.
    That let the active ChatGPT login keep running, and in-process
    app-server shutdown then waited on the stale login attempt before
    finishing.
    
    ## Changes
    
    - Extract a shared `cancel_active_attempt()` path in the auth widget
    - Use that path from onboarding-level `Ctrl+C` before exiting the TUI
    - Add focused tests for canceling browser-login and device-code attempts
    - Add app-server shutdown cleanup that explicitly drops any active login
    before draining background work
  • tui_app_server: open ChatGPT login in the local browser (#15672)
    ## Summary
    
    Fixes ChatGPT login in `tui_app_server` so the local browser opens again
    during in-process login flows.
    
    ## Root cause
    
    The app-server backend intentionally starts ChatGPT login with browser
    auto-open disabled, expecting the TUI client to open the returned
    `auth_url`. The app-server TUI was not doing that, so the login URL was
    shown in the UI but no browser window opened.
    
    ## Changes
    
    - Add a helper that opens the returned ChatGPT login URL locally
    - Call it from the main ChatGPT login flow
    - Call it from the device-code fallback-to-browser path as well
    - Limit auto-open to in-process app-server handles so remote sessions do
    not try to open a browser against a remote localhost callback
  • tui: always restore the terminal on early exit (#15671)
    ## Summary
    
    Fixes early TUI exit paths that could leave the terminal in a dirty
    state and cause a stray `%` prompt marker after the app quit.
    
    ## Root cause
    
    Both `tui` and `tui_app_server` had early returns after `tui::init()`
    that did not guarantee terminal restore. When that happened, shells like
    `zsh` inherited the altered terminal state.
    
    ## Changes
    
    - Add a restore guard around `run_ratatui_app()` in both `tui` and
    `tui_app_server`
    - Route early exits through the guard instead of relying on scattered
    manual restore calls
    - Ensure terminal restore still happens on normal shutdown
  • Clean up TUI /plugins row allignment (#15669)
    - Remove marketplace from left column.
    - Change `Can be installed` to `Available`
    - Align right-column marketplace + selected-row hint text across states.
    - Changes applied to both `tui` and `tui_app_server`.
    - Update related snapshots/tests.
    
    
    <img width="2142" height="590" alt="image"
    src="https://github.com/user-attachments/assets/6e60b783-2bea-46d4-b353-f2fd328ac4d0"
    />
  • Move git utilities into a dedicated crate (#15564)
    - create `codex-git-utils` and move the shared git helpers into it with
    file moves preserved for diff readability
    - move the `GitInfo` helpers out of `core` so stacked rollout work can
    depend on the shared crate without carrying its own git info module
    
    ---------
    
    Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
    Co-authored-by: Codex <noreply@openai.com>
  • tui_app_server: tolerate missing rate limits while logged out (#15670)
    ## Summary
    
    Fixes a `tui_app_server` bootstrap failure when launching the CLI while
    logged out.
    
    ## Root cause
    
    During TUI bootstrap, `tui_app_server` fetched `account/rateLimits/read`
    unconditionally and treated failures as fatal. When the user was logged
    out, there was no ChatGPT account available, so that RPC failed and
    aborted startup with:
    
    ```
    Error: account/rateLimits/read failed during TUI bootstrap
    ```
    
    ## Changes
    
    - Only fetch bootstrap rate limits when OpenAI auth is required and a
    ChatGPT account is present
    - Treat bootstrap rate-limit fetch failures as non-fatal and fall back
    to empty snapshots
    - Log the fetch failure at debug level instead of aborting startup
  • Hide numeric prefixes on disabled TUI list rows (#15660)
    - Remove numeric prefixes for disabled rows in shared list rendering.
    These numbers are shortcuts, Ex: Pressing "2" selects option `#2`.
    Disabled items can not be selected, so keeping numbers on these items is
    misleading.
    - Apply the same behavior in both tui and tui_app_server.
    - Update affected snapshots for apps/plugins loading and plugin detail
    rows.
    
    _**This is a global change.**_
    
    Before:
    <img width="1680" height="488" alt="image"
    src="https://github.com/user-attachments/assets/4bcf94ad-285f-48d3-a235-a85b58ee58e2"
    />
    
    After:
    <img width="1706" height="484" alt="image"
    src="https://github.com/user-attachments/assets/76bb6107-a562-42fe-ae94-29440447ca77"
    />
  • Increase voice space hold timeout to 1s (#15579)
    Increase the space-hold delay to 1 second before voice capture starts,
    and mirror the change in tui_app_server.
  • Refresh mentions list after plugin install/uninstall (#15598)
    Refresh mentions list after plugin install/uninstall to that $mentions
    are updated without requiring exiting/launching the client.
  • [Codex TUI] - Sort /plugins TUI menu by installed status first, alpha second (#15558)
    Updates plugin ordering so installed plugins are listed first, with
    alphabetical sorting applied within the installed and uninstalled
    groups. The behavior is now consistent across both `tui` and
    `tui_app_server`, and related tests/snapshots were updated.
  • Finish moving codex exec to app-server (#15424)
    This PR completes the conversion of non-interactive `codex exec` to use
    app server rather than directly using core events and methods.
    
    ### Summary
    - move `codex-exec` off exec-owned `AuthManager` and `ThreadManager`
    state
    - route exec bootstrap, resume, and auth refresh through existing
    app-server paths
    - replace legacy `codex/event/*` decoding in exec with typed app-server
    notification handling
    - update human and JSONL exec output adapters to translate existing
    app-server notifications only
    - clean up "app server client" layer by eliminating support for legacy
    notifications; this is no longer needed
    - remove exposure of `authManager` and `threadManager` from "app server
    client" layer
    
    ### Testing
    - `exec` has pretty extensive unit and integration tests already, and
    these all pass
    - In addition, I asked Codex to put together a comprehensive manual set
    of tests to cover all of the `codex exec` functionality (including
    command-line options), and it successfully generated and ran these tests
  • Remove filter from plugins/list result (#15580)
    Show all plugin marketplaces in the /plugins popup by removing the
    `openai-curated` marketplace filter, and update plugin popup
    copy/tests/snapshots to match the new behavior in both TUI codepaths.
  • [codex-cli][app-server] Update self-serve business usage limit copy in error returned (#15478)
    ## Summary
    - update the self-serve business usage-based limit message to direct
    users to their admin for additional credits
    - add a focused unit test for the self_serve_business_usage_based plan
    branch
    
    Added also: 
    
    If you are at a rate limit but you still have credits, codex cli would
    tell you to switch the model. We shouldnt do this if you have credits so
    fixed this.
    
    ## Test
    - launched the source-built CLI and verified the updated message is
    shown for the self-serve business usage-based plan
    
    ![Test
    screenshot](https://raw.githubusercontent.com/openai/codex/5cc3c013ef17ac5c66dfd9395c0d3c4837602231/docs/images/self-serve-business-usage-limit.png)
  • chore(core) Add approvals reviewer to UserTurn (#15426)
    ## Summary
    Adds support for approvals_reviewer to `Op::UserTurn` so we can migrate
    `[CodexMessageProcessor::turn_start]` to use Op::UserTurn
    
    ## Testing
    - [x] Adds quick test for the new field
    
    Co-authored-by: Codex <noreply@openai.com>
  • [hooks] add non-streaming (non-stdin style) shell-only PreToolUse support (#15211)
    - add `PreToolUse` hook for bash-like tool execution only at first
    - block shell execution before dispatch with deny-only hook behavior
    - introduces common.rs matcher framework for matching when hooks are run
    
    example run:
    
    ```
    › run three parallel echo commands, and the second one should echo "[block-pre-tool-use]" as a test
    
    
    • Running the three echo commands in parallel now and I’ll report the output directly.
    
    • Running PreToolUse hook: name for demo pre tool use hook
    
    • Running PreToolUse hook: name for demo pre tool use hook
    
    • Running PreToolUse hook: name for demo pre tool use hook
    
    PreToolUse hook (completed)
      warning: wizard-tower PreToolUse demo inspected Bash: echo "first parallel echo"
      
    PreToolUse hook (blocked)
      warning: wizard-tower PreToolUse demo blocked a Bash command on purpose.
      feedback: PreToolUse demo blocked the command. Remove [block-pre-tool-use] to continue.
    
    PreToolUse hook (completed)
      warning: wizard-tower PreToolUse demo inspected Bash: echo "third parallel echo"
    
    • Ran echo "first parallel echo"
      └ first parallel echo
    
    • Ran echo "third parallel echo"
      └ third parallel echo
    
    • Three little waves went out in parallel.
    
      1. printed first parallel echo
      2. was blocked before execution because it contained the exact test string [block-pre-tool-use]
      3. printed third parallel echo
    
      There was also an unrelated macOS defaults warning around the successful commands, but the echoes
      themselves worked fine. If you want, I can rerun the second one with a slightly modified string so
      it passes cleanly.
    ```
  • Unify realtime stop handling in TUI (#15529)
    ## Summary
    - route /realtime, Ctrl+C, and deleted realtime meters through the same
    realtime stop path
    - keep generic transcription placeholder cleanup free of realtime
    shutdown side effects
    
    ## Testing
    - Ran 
    - Relied on CI for verification; did not run local tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Plugins TUI install/uninstall (#15342)
    - Add install/uninstall actions to the TUI plugins menu
    - Wire plugin install/uninstall through both TUI and `tui_app_server`
    - Refresh config/plugin state after changes so the UI updates
    immediately
    - Add a post-install app setup flow for plugins that require additional
    app auth
    
    <img width="1567" height="300" alt="Screenshot 2026-03-20 at 4 08 44 PM"
    src="https://github.com/user-attachments/assets/366bd31b-2ffd-4e80-b4a3-3a9a9c674a5f"
    />
    <img width="445" height="240" alt="Screenshot 2026-03-20 at 4 08 54 PM"
    src="https://github.com/user-attachments/assets/613999ab-269a-4758-ab59-7c057a1742dc"
    />
    <img width="797" height="219" alt="Screenshot 2026-03-20 at 4 09 07 PM"
    src="https://github.com/user-attachments/assets/b9679e60-40f5-49bb-ade0-2e40449c3fbf"
    />
    <img width="499" height="235" alt="Screenshot 2026-03-20 at 4 09 24 PM"
    src="https://github.com/user-attachments/assets/261ce2fe-f356-4e99-8ac9-f29ed850bc75"
    />
    
    
    
    
    Note/known issue: The /plugin install flow fails in `tui_app_server`
    because after a successful install it tries to trigger a
    ReloadUserConfig operation, but `tui_app_server` has not yet implemented
    transport for that operation, so it falls through to the generic “Not
    available in app-server TUI yet” stub.
  • tui: queue follow-ups during manual /compact (#15259)
    ## Summary
    - queue input after the user submits `/compact` until that manual
    compact turn ends
    - mirror the same behavior in the app-server TUI
    - add regressions for input queued before compact starts and while it is
    running
    
    Co-authored-by: Codex <noreply@openai.com>
  • Label plugins as plugins, and hide skills/apps for given plugin (#15279)
    - Duplicate app mentions are now suppressed when they’re plugin-backed
    with the same display name.
    - Remaining connector mentions now label category as [Plugin] when
    plugin metadata is present, otherwise [App].
    - Mention result lists are now capped to 8 rows after filtering.
    - Updates both tui and tui_app_server with the same changes.
  • fix: fall back to vendored bubblewrap when system bwrap lacks --argv0 (#15338)
    ## Why
    
    Fixes [#15283](https://github.com/openai/codex/issues/15283), where
    sandboxed tool calls fail on older distro `bubblewrap` builds because
    `/usr/bin/bwrap` does not understand `--argv0`. The upstream [bubblewrap
    v0.9.0 release
    notes](https://github.com/containers/bubblewrap/releases/tag/v0.9.0)
    explicitly call out `Add --argv0`. Flipping `use_legacy_landlock`
    globally works around that compatibility bug, but it also weakens the
    default Linux sandbox and breaks proxy-routed and split-policy cases
    called out in review.
    
    The follow-up Linux CI failure was in the new launcher test rather than
    the launcher logic: the fake `bwrap` helper stayed open for writing, so
    Linux would not exec it. This update also closes the user-visibility gap
    from review by surfacing the same startup warning when `/usr/bin/bwrap`
    is present but too old for `--argv0`, not only when it is missing.
    
    ## What Changed
    
    - keep `use_legacy_landlock` default-disabled
    - teach `codex-rs/linux-sandbox/src/launcher.rs` to fall back to the
    vendored bubblewrap build when `/usr/bin/bwrap` does not advertise
    `--argv0` support
    - add launcher tests for supported, unsupported, and missing system
    `bwrap`
    - write the fake `bwrap` test helper to a closed temp path so the
    supported-path launcher test works on Linux too
    - extend the startup warning path so Codex warns when `/usr/bin/bwrap`
    is missing or too old to support `--argv0`
    - mirror the warning/fallback wording across
    `codex-rs/linux-sandbox/README.md` and `codex-rs/core/README.md`,
    including that the fallback is the vendored bubblewrap compiled into the
    binary
    - cite the upstream `bubblewrap` release that introduced `--argv0`
    
    ## Verification
    
    - `bazel test --config=remote --platforms=//:rbe
    //codex-rs/linux-sandbox:linux-sandbox-unit-tests
    --test_filter=launcher::tests::prefers_system_bwrap_when_help_lists_argv0
    --test_output=errors`
    - `cargo test -p codex-core system_bwrap_warning`
    - `cargo check -p codex-exec -p codex-tui -p codex-tui-app-server -p
    codex-app-server`
    - `just argument-comment-lint`
  • Use Shift+Left to edit queued messages in tmux (#15480)
    ## Summary
    - use Shift+Left to edit the most recent queued message when running
    under tmux
    - mirror the same binding change in the app-server TUI
    - add tmux-specific tests and snapshot coverage for the rendered
    queued-message hint
    
    ## Testing
    - just fmt
    - cargo test -p codex-tui
    - cargo test -p codex-tui-app-server
    - just argument-comment-lint -p codex-tui -p codex-tui-app-server
    
    Co-authored-by: Codex <noreply@openai.com>
  • Remove legacy auth and notification handling from tui_app_server (#15414)
    ## Summary
    - remove `tui_app_server` handling for legacy app-server notifications
    - drop the local ChatGPT auth refresh request path from `tui_app_server`
    - remove the now-unused refresh response helper from local auth loading
    
    Split out of #15106 so the `tui_app_server` cleanup can land separately
    from the larger `codex-exec` app-server migration.
  • Remove legacy app-server notification handling from tui_app_server (#15390)
    As part of moving the TUI onto the app server, we added some temporary
    handling of some legacy events. We've confirmed that these do not need
    to be supported, so this PR removes this support from the
    tui_app_server, allowing for additional simplifications in follow-on
    PRs. These events are needed only for very old rollouts. None of the
    other app server clients (IDE extension or app) support these either.
    
    ## Summary
    - stop translating legacy `codex/event/*` notifications inside
    `tui_app_server`
    - remove the TUI-side legacy warning and rollback buffering/replay paths
    that were only fed by those notifications
    - keep the lower-level app-server and app-server-client legacy event
    plumbing intact so PR #15106 can rebase on top and handle the remaining
    exec/lower-layer migration separately
  • Gate tui /plugins menu behind flag (#15285)
    Gate /plugins menu behind `--enable plugins` flag
  • Add realtime transcript notification in v2 (#15344)
    - emit a typed `thread/realtime/transcriptUpdated` notification from
    live realtime transcript deltas
    - expose that notification as flat `threadId`, `role`, and `text` fields
    instead of a nested transcript array
    - continue forwarding raw `handoff_request` items on
    `thread/realtime/itemAdded`, including the accumulated
    `active_transcript`
    - update app-server docs, tests, and generated protocol schema artifacts
    to match the delta-based payloads
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Feat/restore image generation history (#15223)
    Restore image generation items in resumed thread history
  • Initial plugins TUI menu - list and read only. tui + tui_app_server (#15215)
    ### Preliminary /plugins TUI menu
    - Adds a preliminary /plugins menu flow in both tui and tui_app_server.
    - Fetches plugin list data asynchronously and shows loading/error/cached
    states.
      - Limits this first pass to the curated ChatGPT marketplace.
      - Shows available plugins with installed/status metadata.
    - Supports in-menu search over plugin display name, plugin id, plugin
    name, and marketplace label.
    - Opens a plugin detail view on selection, including summaries for
    Skills, Apps, and MCP Servers, with back navigation.
    
    ### Testing
      - Launch codex-cli with plugins enabled (`--enable plugins`).
      - Run /plugins and verify:
          - loading state appears first
          - plugin list is shown
          - search filters results
    - selecting a plugin opens detail view, with a list of
    skills/connectors/MCP servers for the plugin
          - back action returns to the list.
    - Verify disabled behavior by running /plugins without plugins enabled
    (shows “Plugins are disabled” message).
    - Launch with `--enable tui_app_server` (and plugins enabled) and repeat
    the same /plugins flow; behavior should match.
  • Use released DotSlash package for argument-comment lint (#15199)
    ## Why
    The argument-comment lint now has a packaged DotSlash artifact from
    [#15198](https://github.com/openai/codex/pull/15198), so the normal repo
    lint path should use that released payload instead of rebuilding the
    lint from source every time.
    
    That keeps `just clippy` and CI aligned with the shipped artifact while
    preserving a separate source-build path for people actively hacking on
    the lint crate.
    
    The current alpha package also exposed two integration wrinkles that the
    repo-side prebuilt wrapper needs to smooth over:
    - the bundled Dylint library filename includes the host triple, for
    example `@nightly-2025-09-18-aarch64-apple-darwin`, and Dylint derives
    `RUSTUP_TOOLCHAIN` from that filename
    - on Windows, Dylint's driver path also expects `RUSTUP_HOME` to be
    present in the environment
    
    Without those adjustments, the prebuilt CI jobs fail during `cargo
    metadata` or driver setup. This change makes the checked-in prebuilt
    wrapper normalize the packaged library name to the plain
    `nightly-2025-09-18` channel before invoking `cargo-dylint`, and it
    teaches both the wrapper and the packaged runner source to infer
    `RUSTUP_HOME` from `rustup show home` when the environment does not
    already provide it.
    
    After the prebuilt Windows lint job started running successfully, it
    also surfaced a handful of existing anonymous literal callsites in
    `windows-sandbox-rs`. This PR now annotates those callsites so the new
    cross-platform lint job is green on the current tree.
    
    ## What Changed
    - checked in the current
    `tools/argument-comment-lint/argument-comment-lint` DotSlash manifest
    - kept `tools/argument-comment-lint/run.sh` as the source-build wrapper
    for lint development
    - added `tools/argument-comment-lint/run-prebuilt-linter.sh` as the
    normal enforcement path, using the checked-in DotSlash package and
    bundled `cargo-dylint`
    - updated `just clippy` and `just argument-comment-lint` to use the
    prebuilt wrapper
    - split `.github/workflows/rust-ci.yml` so source-package checks live in
    a dedicated `argument_comment_lint_package` job, while the released lint
    runs in an `argument_comment_lint_prebuilt` matrix on Linux, macOS, and
    Windows
    - kept the pinned `nightly-2025-09-18` toolchain install in the prebuilt
    CI matrix, since the prebuilt package still relies on rustup-provided
    toolchain components
    - updated `tools/argument-comment-lint/run-prebuilt-linter.sh` to
    normalize host-qualified nightly library filenames, keep the `rustup`
    shim directory ahead of direct toolchain `cargo` binaries, and export
    `RUSTUP_HOME` when needed for Windows Dylint driver setup
    - updated `tools/argument-comment-lint/src/bin/argument-comment-lint.rs`
    so future published DotSlash artifacts apply the same nightly-filename
    normalization and `RUSTUP_HOME` inference internally
    - fixed the remaining Windows lint violations in
    `codex-rs/windows-sandbox-rs` by adding the required `/*param*/`
    comments at the reported callsites
    - documented the checked-in DotSlash file, wrapper split, archive
    layout, nightly prerequisite, and Windows `RUSTUP_HOME` requirement in
    `tools/argument-comment-lint/README.md`
  • Split features into codex-features crate (#15253)
    - Split the feature system into a new `codex-features` crate.
    - Cut `codex-core` and workspace consumers over to the new config and
    warning APIs.
    
    Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
    Co-authored-by: Codex <noreply@openai.com>
  • Move auth code into login crate (#15150)
    - Move the auth implementation and token data into codex-login.
    - Keep codex-core re-exporting that surface from codex-login for
    existing callers.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat(app-server): add mcpServer/startupStatus/updated notification (#15220)
    Exposes the legacy `codex/event/mcp_startup_update` event as an API v2
    notification.
    
    The legacy event has this shape:
    ```
    #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
    pub struct McpStartupUpdateEvent {
        /// Server name being started.
        pub server: String,
        /// Current startup status.
        pub status: McpStartupStatus,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
    #[serde(rename_all = "snake_case", tag = "state")]
    #[ts(rename_all = "snake_case", tag = "state")]
    pub enum McpStartupStatus {
        Starting,
        Ready,
        Failed { error: String },
        Cancelled,
    }
    ```
  • adding full imagepath to tui (#15154)
    adding full path to TUI so image is open-able in the TUI after being
    generated. LImited to VSCode Terminal for now.
  • Move terminal module to terminal-detection crate (#15216)
    - Move core/src/terminal.rs and its tests into a standalone
    terminal-detection workspace crate.
    - Update direct consumers to depend on codex-terminal-detection and
    import terminal APIs directly.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [hooks] use a user message > developer message for prompt continuation (#14867)
    ## Summary
    
    Persist Stop-hook continuation prompts as `user` messages instead of
    hidden `developer` messages + some requested integration tests
    
    This is a followup to @pakrym 's comment in
    https://github.com/openai/codex/pull/14532 to make sure stop-block
    continuation prompts match training for turn loops
    
    - Stop continuation now writes `<hook_prompt hook_run_id="...">stop
    hook's user prompt<hook_prompt>`
    - Introduces quick-xml dependency, though we already indirectly depended
    on it anyway via syntect
    - This PR only has about 500 lines of actual logic changes, the rest is
    tests/schema
    
    ## Testing
    
    Example run (with a sessionstart hook and 3 stop hooks) - this shows
    context added by session start, then two stop hooks sending their own
    additional prompts in a new turn. The model responds with a single
    message addressing both. Then when that turn ends, the hooks detect that
    they just ran using `stop_hook_active` and decide not to infinite loop
    
    test files for this (unzip, move codex -> .codex):
    [codex.zip](https://github.com/user-attachments/files/26075806/codex.zip)
    
    ```
    › cats
    
    
    • Running SessionStart hook: lighting the observatory
    
    SessionStart hook (completed)
      warning: Hi, I'm a session start hook for wizard-tower (startup).
      hook context: A wimboltine stonpet is an exotic cuisine from hyperspace
    
    • Cats are tiny zen wizards, my friend: equal parts nap, mystery, and chaos. If you want, we can talk
      cat facts, cat breeds, cat names, or build something cat-themed in this repo.
    
    • Running Stop hook: checking the tower wards
    
    • Running Stop hook: sacking the guards
    
    • Running Stop hook: hiring the guards
    
    Stop hook (completed)
      warning: Wizard Tower Stop hook reviewed the completed reply (177 chars).
    
    Stop hook (blocked)
      warning: Wizard Tower Stop hook continuing conversation
      feedback: cook the stonpet
    
    Stop hook (blocked)
      warning: Wizard Tower Stop hook continuing conversation
      feedback: eat the cooked stonpet
    
    • Stonpet’s cooked, aloha style: flash-seared over a blue quasiflame, glazed with nebula salt, and
      rested until the hyperspace juices settle.
    
      Now we eat with gratitude, my friend. One mindful bite in, and the flavor is pure cosmic surf:
      smoky, bright, and totally out of this dimension.
    
    • Running Stop hook: checking the tower wards
    
    • Running Stop hook: sacking the guards
    
    • Running Stop hook: hiring the guards
    
    Stop hook (completed)
      warning: Wizard Tower Stop hook reviewed the completed reply (285 chars).
    
    Stop hook (completed)
      warning: Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop.
    
    Stop hook (completed)
      warning: Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop.
    ```
  • feat: support product-scoped plugins. (#15041)
    1. Added SessionSource::Custom(String) and --session-source.
      2. Enforced plugin and skill products by session_source.
      3. Applied the same filtering to curated background refresh.
  • Add thread/shellCommand to app server API surface (#14988)
    This PR adds a new `thread/shellCommand` app server API so clients can
    implement `!` shell commands. These commands are executed within the
    sandbox, and the command text and output are visible to the model.
    
    The internal implementation mirrors the current TUI `!` behavior.
    - persist shell command execution as `CommandExecution` thread items,
    including source and formatted output metadata
    - bridge live and replayed app-server command execution events back into
    the existing `tui_app_server` exec rendering path
    
    This PR also wires `tui_app_server` to submit `!` commands through the
    new API.
  • Simple directory mentions (#14970)
    - Adds simple support for directory mentions in the TUI.
    - Codex App/VS Code will require minor change to recognize a directory
    mention as such and change the link behavior.
    - Directory mentions have a trailing slash to differentiate from
    extensionless files
    
    
    <img width="972" height="382" alt="image"
    src="https://github.com/user-attachments/assets/8035b1eb-0978-465b-8d7a-4db2e5feca39"
    />
    <img width="978" height="228" alt="image"
    src="https://github.com/user-attachments/assets/af22cf0b-dd10-4440-9bee-a09915f6ba52"
    />
  • feat(tui): restore composer history in app-server tui (#14945)
    ## Problem
    
    The app-server TUI (`tui_app_server`) lacked composer history support.
    Pressing Up/Down to recall previous prompts hit a stub that logged a
    warning and displayed "Not available in app-server TUI yet." New
    submissions were silently dropped from the shared history file, so
    nothing persisted for future sessions.
    
    ## Mental model
    
    Codex maintains a single, append-only history file
    (`$CODEX_HOME/history.jsonl`) shared across all TUI processes on the
    same machine. The legacy (in-process) TUI already reads/writes this file
    through `codex_core::message_history`. The app-server TUI delegates most
    operations to a separate process over RPC, but history is intentionally
    *not* an RPC concern — it's a client-local file.
    
    This PR makes the app-server TUI access the same history file directly,
    bypassing the app-server process entirely. The composer's Up/Down
    navigation and submit-time persistence now follow the same code paths as
    the legacy TUI, with the only difference being *where* the call is
    dispatched (locally in `App`, rather than inside `CodexThread`).
    
    The branch is rebuilt directly on top of `upstream/main`, so it keeps
    the
    existing app-server restore architecture intact.
    `AppServerStartedThread`
    still restores transcript history from the server `Thread` snapshot via
    `thread_snapshot_events`; this PR only adds composer-history support.
    
    ## Non-goals
    
    - Adding history support to the app-server protocol. History remains
    client-local.
    - Changing the on-disk format or location of `history.jsonl`.
    - Surfacing history I/O errors to the user (failures are logged and
    silently swallowed, matching the legacy TUI).
    
    ## Tradeoffs
    
    | Decision | Why | Risk |
    |----------|-----|------|
    | Widen `message_history` from `pub(crate)` to `pub` | Avoids
    duplicating file I/O logic; the module already has a clean, minimal API
    surface. | Other workspace crates can now call these functions — the
    contract is no longer crate-private. However, this is consistent with
    recent precedent: `590cfa617` exposed `mention_syntax` for TUI
    consumption, `752402c4f` exposed plugin APIs (`PluginsManager`), and
    `14fcb6645`/`edacbf7b6` widened internal core APIs for other crates.
    These were all narrow, intentional exposures of specific APIs — not
    broad "make internals public" moves. `1af2a37ad` even went the other
    direction, reducing broad re-exports to tighten boundaries. This change
    follows the same pattern: a small, deliberate API surface (3 functions)
    rather than a wholesale visibility change. |
    | Intercept `AddToHistory` / `GetHistoryEntryRequest` in `App` before
    RPC fallback | Keeps history ops out of the "unsupported op" error path
    without changing app-server protocol. | This now routes through a single
    `submit_thread_op` entry point, which is safer than the original
    duplicated dispatch. The remaining risk is organizational: future
    thread-op submission paths need to keep using that shared entry point. |
    | `session_configured_from_thread_response` is now `async` | Needs
    `await` on `history_metadata()` to populate real `history_log_id` /
    `history_entry_count`. | Adds an async file-stat + full-file newline
    scan to the session bootstrap path. The scan is bounded by
    `history.max_bytes` and matches the legacy TUI's cost profile, but
    startup latency still scales with file size. |
    
    ## Architecture
    
    ```
    User presses Up                     User submits a prompt
           │                                    │
           ▼                                    ▼
    ChatComposerHistory                 ChatWidget::do_submit_turn
      navigate_up()                       encode_history_mentions()
           │                                    │
           ▼                                    ▼
      AppEvent::CodexOp                  Op::AddToHistory { text }
      (GetHistoryEntryRequest)                  │
           │                                    ▼
           ▼                            App::try_handle_local_history_op
      App::try_handle_local_history_op    message_history::append_entry()
        spawn_blocking {                        │
          message_history::lookup()             ▼
        }                                $CODEX_HOME/history.jsonl
           │
           ▼
      AppEvent::ThreadEvent
      (GetHistoryEntryResponse)
           │
           ▼
      ChatComposerHistory::on_entry_response()
    ```
    
    ## Observability
    
    - `tracing::warn` on `append_entry` failure (includes thread ID).
    - `tracing::warn` on `spawn_blocking` lookup join error.
    - `tracing::warn` from `message_history` internals on file-open, lock,
    or parse failures.
    
    ## Tests
    
    - `chat_composer_history::tests::navigation_with_async_fetch` — verifies
    that Up emits `Op::GetHistoryEntryRequest` (was: checked for stub error
    cell).
    - `app::tests::history_lookup_response_is_routed_to_requesting_thread` —
    verifies multi-thread composer recall routes the lookup result back to
    the originating thread.
    -
    `app_server_session::tests::resume_response_relies_on_snapshot_replay_not_initial_messages`
    — verifies app-server session restore still uses the upstream
    thread-snapshot path.
    -
    `app_server_session::tests::session_configured_populates_history_metadata`
    — verifies bootstrap sets nonzero `history_log_id` /
    `history_entry_count` from the shared local history file.
  • [hooks] userpromptsubmit - hook before user's prompt is executed (#14626)
    - this allows blocking the user's prompts from executing, and also
    prevents them from entering history
    - handles the edge case where you can both prevent the user's prompt AND
    add n amount of additionalContexts
    - refactors some old code into common.rs where hooks overlap
    functionality
    - refactors additionalContext being previously added to user messages,
    instead we use developer messages for them
    - handles queued messages correctly
    
    Sample hook for testing - if you write "[block-user-submit]" this hook
    will stop the thread:
    
    example run
    ```
    › sup
    
    
    • Running UserPromptSubmit hook: reading the observatory notes
    
    UserPromptSubmit hook (completed)
      warning: wizard-tower UserPromptSubmit demo inspected: sup
      hook context: Wizard Tower UserPromptSubmit demo fired. For this reply only, include the exact
    phrase 'observatory lanterns lit' exactly once near the end.
    
    • Just riding the cosmic wave and ready to help, my friend. What are we building today? observatory
      lanterns lit
    
    
    › and [block-user-submit]
    
    
    • Running UserPromptSubmit hook: reading the observatory notes
    
    UserPromptSubmit hook (stopped)
      warning: wizard-tower UserPromptSubmit demo blocked the prompt on purpose.
      stop: Wizard Tower demo block: remove [block-user-submit] to continue.
    ```
    
    .codex/config.toml
    ```
    [features]
    codex_hooks = true
    ```
    
    .codex/hooks.json
    ```
    {
      "hooks": {
        "UserPromptSubmit": [
          {
            "hooks": [
              {
                "type": "command",
                "command": "/usr/bin/python3 .codex/hooks/user_prompt_submit_demo.py",
                "timeoutSec": 10,
                "statusMessage": "reading the observatory notes"
              }
            ]
          }
        ]
      }
    }
    ```
    
    .codex/hooks/user_prompt_submit_demo.py
    ```
    #!/usr/bin/env python3
    
    import json
    import sys
    from pathlib import Path
    
    
    def prompt_from_payload(payload: dict) -> str:
        prompt = payload.get("prompt")
        if isinstance(prompt, str) and prompt.strip():
            return prompt.strip()
    
        event = payload.get("event")
        if isinstance(event, dict):
            user_prompt = event.get("user_prompt")
            if isinstance(user_prompt, str):
                return user_prompt.strip()
    
        return ""
    
    
    def main() -> int:
        payload = json.load(sys.stdin)
        prompt = prompt_from_payload(payload)
        cwd = Path(payload.get("cwd", ".")).name or "wizard-tower"
    
        if "[block-user-submit]" in prompt:
            print(
                json.dumps(
                    {
                        "systemMessage": (
                            f"{cwd} UserPromptSubmit demo blocked the prompt on purpose."
                        ),
                        "decision": "block",
                        "reason": (
                            "Wizard Tower demo block: remove [block-user-submit] to continue."
                        ),
                    }
                )
            )
            return 0
    
        prompt_preview = prompt or "(empty prompt)"
        if len(prompt_preview) > 80:
            prompt_preview = f"{prompt_preview[:77]}..."
    
        print(
            json.dumps(
                {
                    "systemMessage": (
                        f"{cwd} UserPromptSubmit demo inspected: {prompt_preview}"
                    ),
                    "hookSpecificOutput": {
                        "hookEventName": "UserPromptSubmit",
                        "additionalContext": (
                            "Wizard Tower UserPromptSubmit demo fired. "
                            "For this reply only, include the exact phrase "
                            "'observatory lanterns lit' exactly once near the end."
                        ),
                    },
                }
            )
        )
        return 0
    
    
    if __name__ == "__main__":
        raise SystemExit(main())
    ```
  • Use workspace requirements for guardian prompt override (#14727)
    ## Summary
    - move `guardian_developer_instructions` from managed config into
    workspace-managed `requirements.toml`
    - have guardian continue using the override when present and otherwise
    fall back to the bundled local guardian prompt
    - keep the generalized prompt-quality improvements in the shared
    guardian default prompt
    - update requirements parsing, layering, schema, and tests for the new
    source of truth
    
    ## Context
    This replaces the earlier managed-config / MDM rollout plan.
    
    The intended rollout path is workspace-managed requirements, including
    cloud enterprise policies, rather than backend model metadata, Statsig,
    or Jamf-managed config. That keeps the default/fallback behavior local
    to `codex-rs` while allowing faster policy updates through the
    enterprise requirements plane.
    
    This is intentionally an admin-managed policy input, not a user
    preference: the guardian prompt should come either from the bundled
    `codex-rs` default or from enterprise-managed `requirements.toml`, and
    normal user/project/session config should not override it.
    
    ## Updating The OpenAI Prompt
    After this lands, the OpenAI-specific guardian prompt should be updated
    through the workspace Policies UI at `/codex/settings/policies` rather
    than through Jamf or codex-backend model metadata.
    
    Operationally:
    - open the workspace Policies editor as a Codex admin
    - edit the default `requirements.toml` policy, or a higher-precedence
    group-scoped override if we ever want different behavior for a subset of
    users
    - set `guardian_developer_instructions = """..."""` to the full
    OpenAI-specific guardian prompt text
    - save the policy; codex-backend stores the raw TOML and `codex-rs`
    fetches the effective requirements file from `/wham/config/requirements`
    
    When updating the OpenAI-specific prompt, keep it aligned with the
    shared default guardian policy in `codex-rs` except for intentional
    OpenAI-only additions.
    
    ## Testing
    - `cargo check --tests -p codex-core -p codex-config -p
    codex-cloud-requirements --message-format short`
    - `cargo run -p codex-core --bin codex-write-config-schema`
    - `cargo fmt`
    - `git diff --check`
    
    Co-authored-by: Codex <noreply@openai.com>
  • Handle realtime conversation end in the TUI (#14903)
    - close live realtime sessions on errors, ctrl-c, and active meter
    removal
    - centralize TUI realtime cleanup and avoid duplicate follow-up close
    info
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
    Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
  • fix(linux-sandbox): prefer system /usr/bin/bwrap when available (#14963)
    ## Problem
    Ubuntu/AppArmor hosts started failing in the default Linux sandbox path
    after the switch to vendored/default bubblewrap in `0.115.0`.
    
    The clearest report is in
    [#14919](https://github.com/openai/codex/issues/14919), especially [this
    investigation
    comment](https://github.com/openai/codex/issues/14919#issuecomment-4076504751):
    on affected Ubuntu systems, `/usr/bin/bwrap` works, but a copied or
    vendored `bwrap` binary fails with errors like `bwrap: setting up uid
    map: Permission denied` or `bwrap: loopback: Failed RTM_NEWADDR:
    Operation not permitted`.
    
    The root cause is Ubuntu's `/etc/apparmor.d/bwrap-userns-restrict`
    profile, which grants `userns` access specifically to `/usr/bin/bwrap`.
    Once Codex started using a vendored/internal bubblewrap path, that path
    was no longer covered by the distro AppArmor exception, so sandbox
    namespace setup could fail even when user namespaces were otherwise
    enabled and `uidmap` was installed.
    
    ## What this PR changes
    - prefer system `/usr/bin/bwrap` whenever it is available
    - keep vendored bubblewrap as the fallback when `/usr/bin/bwrap` is
    missing
    - when `/usr/bin/bwrap` is missing, surface a Codex startup warning
    through the app-server/TUI warning path instead of printing directly
    from the sandbox helper with `eprintln!`
    - use the same launcher decision for both the main sandbox execution
    path and the `/proc` preflight path
    - document the updated Linux bubblewrap behavior in the Linux sandbox
    and core READMEs
    
    ## Why this fix
    This still fixes the Ubuntu/AppArmor regression from
    [#14919](https://github.com/openai/codex/issues/14919), but it keeps the
    runtime rule simple and platform-agnostic: if the standard system
    bubblewrap is installed, use it; otherwise fall back to the vendored
    helper.
    
    The warning now follows that same simple rule. If Codex cannot find
    `/usr/bin/bwrap`, it tells the user that it is falling back to the
    vendored helper, and it does so through the existing startup warning
    plumbing that reaches the TUI and app-server instead of low-level
    sandbox stderr.
    
    ## Testing
    - `cargo test -p codex-linux-sandbox`
    - `cargo test -p codex-app-server --lib`
    - `cargo test -p codex-tui-app-server
    tests::embedded_app_server_start_failure_is_returned`
    - `cargo clippy -p codex-linux-sandbox --all-targets`
    - `cargo clippy -p codex-app-server --all-targets`
    - `cargo clippy -p codex-tui-app-server --all-targets`
  • Gate realtime audio interruption logic to v2 (#14984)
    - thread the realtime version into conversation start and app-server
    notifications
    - keep playback-aware mic gating and playback interruption behavior on
    v2 only, leaving v1 on the legacy path
  • Cleanup skills/remote/xxx endpoints. (#14977)
    Remote skills/remote/xxx as they are not in used for now.
  • fix(tui): implement /mcp inventory for tui_app_server (#14931)
    ## Problem
    
    The `/mcp` command did not work in the app-server TUI (remote mode). On
    `main`, `add_mcp_output()` called `McpManager::effective_servers()`
    in-process, which only sees locally configured servers, and then emitted
    a generic stub message for the app-server to handle. In remote usage,
    that left `/mcp` without a real inventory view.
    
    ## Solution
    
    Implement `/mcp` for the app-server TUI by fetching MCP server inventory
    directly from the app-server via the paginated `mcpServerStatus/list`
    RPC and rendering the results into chat history.
    
    The command now follows a three-phase lifecycle:
    
    1. Loading: `ChatWidget::add_mcp_output()` inserts a transient
    `McpInventoryLoadingCell` and emits `AppEvent::FetchMcpInventory`. This
    gives immediate feedback that the command registered.
    2. Fetch: `App::fetch_mcp_inventory()` spawns a background task that
    calls `fetch_all_mcp_server_statuses()` over an app-server request
    handle. When the RPC completes, it sends `AppEvent::McpInventoryLoaded {
    result }`.
    3. Resolve: `App::handle_mcp_inventory_result()` clears the loading cell
    and renders either `new_mcp_tools_output_from_statuses(...)` or an error
    message.
    
    This keeps the main app event loop responsive, so the TUI can repaint
    before the remote RPC finishes.
    
    ## Notes
    
    - No `app-server` changes were required.
    - The rendered inventory includes auth, tools, resources, and resource
    templates, plus transport details when they are available from local
    config for display enrichment.
    - The app-server RPC does not expose authoritative `enabled` or
    `disabled_reason` state for MCP servers, so the remote `/mcp` view no
    longer renders a `Status:` row rather than guessing from local config.
    - RPC failures surface in history as `Failed to load MCP inventory:
    ...`.
    
    ## Tests
    
    - `slash_mcp_requests_inventory_via_app_server`
    - `mcp_inventory_maps_prefix_tool_names_by_server`
    - `handle_mcp_inventory_result_clears_committed_loading_cell`
    - `mcp_tools_output_from_statuses_renders_status_only_servers`
    - `mcp_inventory_loading_snapshot`