Commit Graph

214 Commits

  • [codex] Add interruptible sleep tool (#28429)
    ## Why
    
    Models sometimes need to pause briefly while waiting for external work,
    but using a shell command for that delay ties the wait to a process and
    does not naturally resume when new turn input arrives.
    
    ## What changed
    
    - add a built-in `sleep` tool behind the under-development `sleep_tool`
    feature
    - accept a bounded `duration_ms` argument, matching the millisecond
    convention used by unified exec
    - end the sleep early when either steered user input or mailbox input
    arrives
    - include elapsed wall-clock time in completed and interrupted outputs
    - emit a dedicated core `SleepItem` through `item/started` and
    `item/completed`
    - expose the sleep item as app-server v2 `ThreadItem::Sleep` and retain
    it in reconstructed thread history
    - regenerate the configuration schema for the new feature flag
    - regenerate app-server JSON and TypeScript schema fixtures
    
    ## Test plan
    
    - `just test -p codex-core sleep_tool_follows_feature_gate`
    - `just test -p codex-core any_new_input_interrupts_sleep`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    sleep_emits_started_and_completed_items`
  • Use ApiPathString in app-server filesystem permission paths (#28367)
    ## Why
    
    Clients running an app-server on one OS and an exec-server on another OS
    need to be able to pass sandbox config to app-server that refers to
    resources on the executor's foreign OS.
    
    ## What
    
    `AbsolutePathBuf` can't represent these paths and we don't want users to
    be exposed to `PathUri` yet, so this moves the public app-server API to
    be expressed in terms of `ApiPathString`.
    
    Stacked on #28165.
    
    - change app-server v2 filesystem permission paths, including legacy
    read/write roots, to `ApiPathString`
    - localize API paths through `PathUri` when converting into the current
    native core permission types
    - make path-bearing permission conversions fallible and surface
    localization failures instead of silently treating malformed grants as
    ordinary denials
    - propagate conversion failures through app-server and TUI approval
    handling
    - regenerate the app-server JSON and TypeScript schemas
    - leave migration TODOs on native-path conversions so they can be
    removed once core permission paths use `PathUri`
  • feat(core): add metadata field to ResponseItem (#28355)
    ## Description
    
    This PR adds an optional `metadata` field to `ResponseItem` for
    Responses API calls. Only mechanical plumbing, no actual values
    populated and sent yet. Turns out just adding a new field to
    `ResponseItem` has quite a large blast radius already.
    
    This change is backwards compatible because `metadata` is optional and
    omitted when absent, so existing response items and rollout history
    without it still deserialize and requests that do not set it keep the
    same wire shape. For provider compatibility, we strip out `metadata`
    before non-OpenAI Responses requests so Azure and AWS Bedrock never see
    this field.
    
    My followup PR here will actually make use of it to start storing and
    passing along `turn_id`: https://github.com/openai/codex/pull/28360
    
    ## What changed
    
    - Added `ResponseItemMetadata` with optional `turn_id`, plus optional
    `metadata` on Responses API item variants and inter-agent communication.
    - Preserved item metadata through response-item rewrites such as
    truncation, missing tool-output synthesis, compaction history
    rebuilding, visible-history conversion, rollout/resume, and generated
    app-server schemas/types.
    - Strip item metadata from non-OpenAI Responses requests while
    preserving it for OpenAI-shaped requests.
    - Updated the mechanical fixture/test construction churn required by the
    new optional field.
  • TUI Plugin Sharing 2 - add remote plugin section plumbing (#26702)
    This adds the background plumbing for remote-backed plugin catalog
    sections while leaving the fuller directory presentation to the next PR.
    The TUI can fetch section-specific remote marketplace results, keep
    local plugin data available, and carry section errors forward for later
    rendering.
    
    - Fetches explicit remote marketplace kinds for curated, workspace, and
    shared-with-me sections.
    - Gates shared-with-me loading on the plugin sharing feature flag.
    - Adds section-level error state and user-actionable error copy.
    - Merges remote marketplace results into the cached plugin list without
    discarding local results.
  • Remove terminal resize reflow flag gates (#27794)
    ## Why
    
    `terminal_resize_reflow` is now stable and should behave as always on.
    Keeping the disabled runtime paths around made the feature look
    configurable even though the rollout is complete, and old config could
    still suggest there was a supported off mode.
    
    ## What Changed
    
    - Marked `terminal_resize_reflow` as `Stage::Removed` while keeping it
    default-enabled for compatibility.
    - Ignored `[features].terminal_resize_reflow` config entries so stale
    `false` settings no longer affect the effective feature set.
    - Removed TUI branches that depended on the flag being disabled, so
    draw, replay buffering, stream finalization, and resize scheduling all
    assume resize reflow is active.
    - Simplified resize smoke coverage to exercise the always-on behavior
    only.
    
    ## Verification
    
    - `just test -p codex-features`
    - `just test -p codex-tui resize_reflow`
    - `just test -p codex-tui initial_replay_buffer
    thread_switch_replay_buffer`
  • feat(tui): reland token activity command (#27925)
    ## Why
    
    [#25345](https://github.com/openai/codex/pull/25345) was approved,
    green, and squash-merged into its stacked base branch,
    `fcoury/tokenmaxxing-api`. Four minutes later, that base branch was
    force-pushed back to an API-only rebased head while preparing
    [#25344](https://github.com/openai/codex/pull/25344) for `main`. As a
    result, the squash commit from #25345 was orphaned and the TUI command
    never reached `main` or a release.
    
    This PR relands the orphaned TUI change from
    [`411410b8`](https://github.com/openai/codex/commit/411410b85c2d8eb050d441f17396c5c4048d866f)
    on current `main`.
    
    ## What changed
    
    - Add `/usage`, `/usage daily`, `/usage weekly`, and `/usage cumulative`
    for account token activity.
    - Fetch account usage asynchronously through the existing
    `account/usage/read` app-server RPC.
    - Render daily, weekly, and cumulative activity with theme-aware
    terminal palettes and bounded transient cards.
    - Preserve transcript ordering while assistant streams, history
    consolidations, active cells, and hooks complete.
    - Hide `/usage` from completion when backend auth is unavailable while
    keeping typed-command guidance.
    - Carry current-main behavior forward for cwd-aware Markdown parsing,
    Windows Terminal color detection, and personal access token auth.
    - Clear pending usage cards on thread rollback and delay completed cards
    until live hook output is committed.
    - Add focused regression and snapshot coverage for loading, auth errors,
    invalid views, rollback, hook ordering, layout, and charts.
    
    ## Prior review
    
    The original implementation was approved by Eric Traut in #25345 after
    testing multiple themes and light/dark terminals. This PR preserves that
    reviewed implementation while adapting it to current `main` and adding
    regression coverage for newer rollback and hook lifecycle behavior.
    
    ## Validation
    
    - `just test -p codex-tui token_activity palette renderable
    usage_command` — 37 passed.
    - Focused rollback, hook-ordering, and error snapshot tests — 4 passed.
    - `just fix -p codex-tui` — passed.
    - `UV_CACHE_DIR=/private/tmp/codex-uv-cache just fmt` — passed.
    - `cargo insta pending-snapshots` — no pending snapshots.
    - `just test -p codex-tui` — 2,870 passed; two unrelated guardian
    feature-flag tests failed because their expected `OverrideTurnContext`
    event was absent:
    -
    `update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
    -
    `update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    - `just argument-comment-lint` could not complete because the local
    Bazel LLVM `compiler-rt` repository is missing `include/sanitizer/*.h`.
    The touched Rust diff was manually inspected and no missing
    opaque-literal argument comments were found.
  • [3 of 3] Support images in TUI goals (#27510)
    ## Stack
    
    1. [1 of 3] Support long raw TUI goal objectives - #27508
    2. [2 of 3] Support long pasted text in TUI goals - #27509
    3. **[3 of 3] Support images in TUI goals** - this PR
    
    ## Why
    
    The first two PRs make goal definitions resilient to long text, but
    `/goal` still dropped image inputs from the composer. That meant a user
    could attach images while defining a goal and the resulting goal
    continuation would not have any useful reference to those images.
    
    Goal state still persists only objective text, so image inputs need to
    become paths or URLs that the agent can read later.
    
    ## What Changed
    
    - Extends TUI `GoalDraft` with local image attachments and remote image
    URLs.
    - Copies local goal images through the app-server filesystem layer into
    the managed goal attachment directory, then rewrites active image
    placeholders to file references.
    - Appends unplaced local images and remote image URLs to the objective
    as referenced image files or URLs.
    - Preserves goal image metadata through live `/goal` submission and
    queued `/goal` dispatch.
    
    ## Verification
    
    - Added goal materialization coverage for local image files and remote
    image URLs.
    - Added/updated TUI slash-command coverage showing `/goal` drafts
    include attached images instead of dropping them.
    
    ## Manual Testing
    
    - Attached an image by bracketed-pasting its local path into a live
    `/goal` composer. The `[Image #1]` placeholder became a server-host
    `image-1.png` reference, copied bytes matched exactly, and no attachment
    was written under the TUI's local home.
    - Deleted an image placeholder before submitting a small goal and
    verified no image was copied.
    - Attached PNG and JPEG files to the same goal. Placeholder order was
    preserved as `image-1.png` and `image-2.jpg`, and both remote copies
    matched their source bytes.
    - Tried extensionless, malformed-extension, and
    extension/content-mismatched paths; the composer rejected them as image
    attachments before goal dispatch rather than creating misleading managed
    image files.
    - Combined a local image, a large pasted block, and enough raw text to
    exceed 4,000 characters. The remote attachment directory contained the
    image, paste sidecar, and `goal-objective.md`; all embedded references
    used server-host paths and both payloads matched their sources.
    - Submitted an image replacement while a goal was active, verified no
    image was copied before confirmation, then canceled and confirmed the
    attachment count was unchanged.
  • [2 of 3] Support long pasted text in TUI goals (#27509)
    ## Stack
    
    1. [1 of 3] Support long raw TUI goal objectives - #27508
    2. **[2 of 3] Support long pasted text in TUI goals** - this PR
    3. [3 of 3] Support images in TUI goals - #27510
    
    ## Why
    
    Large text pasted into the TUI composer is represented as a paste
    placeholder plus pending paste metadata. For `/goal`, preserving only
    the visible placeholder is not enough: the agent would see a short
    placeholder string instead of the actual pasted text, and the long-text
    support from the first PR would never see the payload.
    
    The TUI also needs to avoid writing stale sidecar files when a user
    pastes a large block and then deletes its placeholder before submitting
    the goal.
    
    ## What Changed
    
    - Introduces a TUI `GoalDraft` for goal submissions so `/goal`, `/goal
    edit`, and queued goal commands can carry objective text plus text
    elements and pending paste payloads.
    - Materializes active pasted-text placeholders to `pasted-text-N.txt`
    files through the app-server filesystem path introduced in #27508.
    - Rewrites active paste placeholders in the persisted objective to file
    references, while leaving literal placeholder-looking text alone.
    - Filters out deleted paste placeholders so otherwise-small goals do not
    require `$CODEX_HOME` or remote filesystem writes.
    - Preserves pending paste metadata when a `/goal` command is queued
    before a thread exists.
    
    ## Verification
    
    - Added goal materialization tests for active paste placeholders,
    deleted paste placeholders, and whitespace-only paste payloads.
    - Added/updated TUI slash-command tests for large pasted text, queued
    `/goal` commands before thread start, and queued oversized goal
    behavior.
    
    ## Manual Testing
    
    - Used real terminal bracketed-paste sequences through a remote TUI
    session. A 1,228-byte multiline paste became `pasted-text-1.txt`; its
    first/last lines and byte count matched exactly, and the persisted
    objective referenced the server-host path.
    - Pasted a large block, deleted its placeholder, and submitted a small
    replacement objective. No new directory or sidecar file was created.
    - Added two same-length large pastes to one goal. The composer
    disambiguated their visible placeholders, and materialization preserved
    order and contents in `pasted-text-1.txt` and `pasted-text-2.txt`.
    - Submitted a whitespace-only large paste and verified the goal was
    rejected as empty without writing a file.
    - Submitted a pasted-text replacement while another goal was active,
    verified no file was written before confirmation, then canceled and
    confirmed the original goal remained unchanged.
    - Combined a large paste with enough raw text to exceed 4,000 characters
    after placeholder rewriting. The paste sidecar and `goal-objective.md`
    were created in the same remote attachment directory, and `/goal edit`
    restored the rewritten objective with its sidecar reference.
  • Remove TUI realtime voice support (#27801)
    ## Why
    
    Removes the realtime audio support from TUI.
    
    ## What Changed
    
    - Removed the TUI `/realtime` and realtime `/settings` command paths.
    - Deleted TUI voice capture/playback, WebRTC session handling,
    audio-device selection UI, and recording-meter code.
    - Removed TUI realtime tests and snapshots that covered the deleted
    surfaces.
    - Dropped the TUI-only `cpal` and `codex-realtime-webrtc` dependencies
    and refreshed the Rust/Bazel locks.
  • Reject transcript backtrack in side conversations (#27791)
    ## Why
    
    Fixes #27735.
    
    Side conversations are ephemeral forks, and thread rollback currently
    requires persisted thread history. The normal backtrack path already
    rejected editing previous prompts in side conversations, but
    transcript-mode backtrack could still call the rollback path and surface
    the core `thread/rollback` failure as a TUI error.
    
    ## What changed
    
    - Moved the existing side-conversation edit rejection message into
    `app_backtrack.rs` so backtrack rollback code can reuse it.
    - Added a side-conversation guard in `apply_backtrack_rollback` so
    transcript-mode confirmation is rejected before submitting
    `thread/rollback`.
    
    ## Verification
    
    - `just test -p codex-tui
    app::tests::side_backtrack_rejection_reports_unavailable_message_snapshot`
  • Add request_user_input auto-resolution window contract (#27256)
    ## Why
    
    `request_user_input` is moving beyond its original plan-mode-only
    workflow, and future default/goal-mode usage needs a way for the model
    to ask helpful but non-blocking questions without forcing the turn to
    wait forever. This PR adds an explicit `autoResolutionMs` contract so a
    later client/runtime change can auto-resolve unanswered prompts after a
    bounded window while leaving truly blocking questions unchanged.
    
    This is contract plumbing only; it does not implement the client-side
    timer or auto-selection behavior, and the model-facing description
    treats the field as reserved unless the current runtime explicitly
    supports auto-resolution.
    
    ## What Changed
    
    - Added optional `autoResolutionMs` to the model-facing
    `request_user_input` args and core `RequestUserInputEvent`.
    - Added model-facing schema text for `autoResolutionMs` while marking it
    reserved for runtimes that explicitly support auto-resolution.
    - Bounds `autoResolutionMs` to `60_000..=240_000` ms during argument
    normalization by clamping out-of-range model-provided values.
    - Propagated the field through app-server v2
    `ToolRequestUserInputParams`, app-server request forwarding, generated
    TypeScript, and JSON schema fixtures.
    - Updated app-server, core, protocol, and TUI call sites/tests so
    omitted values preserve existing `None`/`null` behavior and coverage
    verifies a `Some(60_000)` round trip.
    
    ## Verification
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-core request_user_input`
    - `just test -p codex-app-server request_user_input_round_trip`
    - `just test -p codex-tui request_user_input`
    - `just test -p codex-protocol`
  • [1 of 3] Support long raw TUI goal objectives (#27508)
    ## Stack
    
    1. **[1 of 3] Support long raw TUI goal objectives** - this PR
    2. [2 of 3] Support long pasted text in TUI goals - #27509
    3. [3 of 3] Support images in TUI goals - #27510
    
    ## Why
    
    `thread/goal/set` limits persisted objective text to 4000 characters.
    The TUI used to reject raw `/goal` objectives above that limit, even
    though the client can make them usable by writing the long text to a
    file and storing a short objective that points at that file.
    
    This also needs to work for remote app-server sessions: filesystem API
    calls must create files on the app-server host, and the stored path must
    be meaningful to the agent on that host.
    
    ## What Changed
    
    - Adds an app-server-host path helper so TUI code can build paths that
    are resolved on the app-server host rather than the TUI host.
    - Adds TUI app-server session helpers for `fs/createDirectory`,
    `fs/writeFile`, `fs/readFile`, and `fs/remove` that work for embedded
    and remote app-server sessions without changing the app-server protocol.
    - Materializes oversized raw `/goal` objectives into
    `$CODEX_HOME/attachments/<uuid>/goal-objective.md` through the
    app-server filesystem APIs, then stores a short, readable objective that
    directs the agent to that file.
    - Reads managed objective files back for `/goal edit`. Other goal UI
    renders the readable stored objective normally, without
    managed-file-specific presentation logic.
    - Recognizes managed references only when they name the expected
    generated file under the app server's reported `$CODEX_HOME`, and cleans
    up newly materialized files when goal replacement or setting does not
    complete.
    
    ## Verification
    
    - Added/updated TUI tests for raw oversized `/goal` submission, large
    inline-paste expansion, queued oversized goals, app-facing
    materialization before `thread/goal/set`, managed-path validation,
    editing, and cleanup.
    - Added/updated app-server-client remote coverage for initialized remote
    Codex home handling.
    
    ## Manual Testing
    
    - Ran the real TUI against a Unix-socket app server with different local
    and server `$CODEX_HOME` directories. Oversized goals wrote only under
    the server home, and persisted references used the server-canonical path
    rather than the TUI path.
    - Exercised 3,999-, 4,000-, and 4,001-character raw objectives. The
    first two stayed inline without new files; the 4,001-character objective
    became a managed objective file.
    - Submitted a larger 8,275-character objective, verified its full
    contents on the app-server host, and observed the goal continuation open
    the referenced server-side file.
    - Opened `/goal edit` for a managed objective and verified the full text
    was restored through remote `fs/readFile`.
    - Submitted an oversized replacement while a goal was active, verified
    no file was written before confirmation, then canceled and confirmed
    that the existing goal and attachment count were unchanged.
  • Print TUI session info on fatal exits (#27417)
    ## Summary
    
    TUI exits printed the resume/session summary only after checking the
    exit reason. On fatal exits, both CLI wrappers wrote the error and
    called `process::exit(1)` immediately, so an active session that ended
    on a fatal error could skip the session information entirely.
    
    This change prints the normal exit summary before returning the fatal
    nonzero exit code. If a fatal exit has a known thread id but no
    resumable rollout hint, it prints `Session ID: <id>` instead of staying
    silent. It also flushes stdout before `process::exit(1)` so the summary
    line is not lost during process teardown.
    
    ## Implementation
    
    - Apply the fatal-exit ordering fix in both `codex` and standalone
    `codex-tui`.
    - Keep normal user-requested exit behavior unchanged.
    - Preserve the existing resume hint when a rollout is resumable, and use
    the raw thread id only as a fatal-exit fallback.
  • Remove TUI legacy Windows sandbox dependency (#27490)
    ## Why
    
    This is part of an ongoing attempt to eliminate the TUI's direct
    dependency on core features. When we moved the TUI to the app server, we
    left a `legacy_core` shim that re-exported some remaining core symbols
    for the TUI. The intent was to eventually remove all of these.
    
    In this PR, we remove the symbols related to the Windows sandbox.
    
    The change should be behavior-neutral and low risk because it's just
    refactoring and removal of code that is now effectively dead.
    
    When working on this PR, I noticed a big existing problem that affects
    mixed-platform remoting. For example, if you run the TUI on a Linux box
    and remote into a Windows box, the TUI logic doesn't properly handle
    Windows sandbox setup properly. Fixing this is beyond the scope of this
    PR, but I've left a TODO comment in place so we don't forget.
    
    ## What changed
    
    - Move the remaining TUI-specific sandbox level, setup, telemetry, and
    read-root helpers into `codex-tui`, calling `codex-windows-sandbox`
    directly.
    - Remove the Windows sandbox namespace and read-root grant re-exports
    from the client-side `legacy_core` facade.
    - Remove the dormant pre-elevation prompt fallback guarded by the
    permanently enabled `ELEVATED_SANDBOX_NUX_ENABLED` switch. The reachable
    elevated and non-elevated setup flows remain unchanged.
  • Add session delete commands in CLI and TUI (#27476)
    ## Summary
    
    The app server exposes `thread/delete`, but users cannot invoke it from
    the CLI or TUI. Because deletion is irreversible, the user-facing
    commands need deliberate confirmation and safer handling of name-based
    targets.
    
    - Add `codex delete <SESSION>` with interactive confirmation,
    restricting `--force` to UUID targets.
    - Resolve exact names across active and archived sessions, including
    renamed sessions, and validate prompted UUID targets before
    confirmation.
    - Add a `/delete` command with a confirmation popup that warns the
    current session and its subagent threads will be permanently deleted.
    
    ## Manual testing
    
    - Deleted by UUID with `--force` and verified the rollout, session-index
    entry, and database row were removed.
    - Exercised name-based confirmation for both cancellation and
    affirmative deletion; cancellation preserved the session and
    confirmation removed it.
    - Verified deletion refuses to proceed without `--force`, while
    `--force` rejects names, including duplicate names.
    - Verified duplicate-name confirmation displays the concrete UUID
    selected.
    - Deleted an archived session by name.
    - Verified an already-missing UUID fails before displaying a
    confirmation prompt.
    - Exercised `/delete` in the TUI: the popup defaults to No, cancellation
    preserves the session, and confirmation deletes the session and exits.
    - Verified that `codex delete` works for both archived and non-archived
    sessions.
  • Remove TUI legacy core test_support dependencies (#27484)
    ## Why
    
    The TUI now sits on the app-server layer, but
    `app-server-client::legacy_core` still exposed core test helpers solely
    for TUI tests. We've been whittling away the remaining dependencies.
    This is the next step on that journey.
    
    There is no functional change — just a refactor, and this affects only
    test code, so it should be low risk.
    
    ## What changed
    
    - remove the `legacy_core::test_support` re-export and call
    model-manager test helpers directly
    - keep the bundled model-preset cache local to TUI test support
    - import constraint types directly from `codex-config`
  • [codex] add /import for external agents (#27071)
    ## Why
    
    External-agent import should be discoverable and deliberate without
    blocking startup or claiming the public `codex [PROMPT]` CLI namespace.
    The slash command keeps the flow local to the interactive TUI and reuses
    the existing app-server import API.
    
    ## What changed
    
    - add the user-facing `/import` slash command
    - detect external-agent importable items only when the command is
    invoked
    - run imports through the embedded local app-server
    - show start and completion messages, refresh configuration, and block
    duplicate imports while one is pending
    - reject the flow for unsupported remote and local-daemon sessions
    
    ## Validation
    
    - `just test -p codex-tui external_agent_config_migration` (10 passed)
    - manually exercised an isolated TUI fixture with existing
    external-agent setup and session data using a fresh `CODEX_HOME`
    - verified picker customization, plugin and session detection, import
    completion, repeated invocation, and imported-session resume context
    - the broader `just test -p codex-tui` run passed 2,805 tests, with 2
    unrelated guardian feature-flag failures and 4 skipped tests
    
    ## Draft follow-ups
    
    - review whether completion messaging should remain attached to the
    initiating chat if the user switches chats during an import
    - review shutdown semantics for an in-progress background import
    
    ## Stack
    
    1. [#27064](https://github.com/openai/codex/pull/27064): remove the
    startup migration flow
    2. [#27065](https://github.com/openai/codex/pull/27065): extract the
    picker renderer
    3. [#27070](https://github.com/openai/codex/pull/27070): add the
    external-agent import picker UX
    4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow
    through `/import`
    
    **This PR is stack item 4.** Draft while the lower stack dependencies
    are reviewed.
  • Add app-server thread/delete API (#25018)
    ## Why
    
    Clients can archive and unarchive threads today, but there is no
    app-server API for permanently removing a thread. Deletion also needs to
    cover the full session tree: deleting a main thread should remove
    spawned subagent threads and the related local metadata instead of
    leaving orphaned rollout files, goals, or subagent state behind.
    
    ## What
    
    - Adds the v2 `thread/delete` request and `thread/deleted` notification,
    with the response shape kept consistent with `thread/archive`.
    - Implements local hard delete for active and archived rollout files.
    - Deletes the requested thread's state DB row as the commit point, then
    best-effort cleans associated state including spawned descendants,
    goals, spawn edges, logs, dynamic tools, and agent job assignments.
    - Updates app-server API docs and generated protocol schema/TypeScript
    fixtures.
  • fix: Prevent /review crash when entering Esc on steer message (#22879)
    This changes the `/review` escape path so `Esc` no longer behaves like
    the normal queued-follow-up interrupt flow while a review is running.
    Steering is not currently supported in `/review` mode, without this
    change users are able to attempt a steer but it leads to a crash (see
    #22815). If the user has already tried to send additional guidance
    during `/review`, the TUI now keeps the review running and shows a
    warning that steer messages are not supported in that mode, while still
    pointing users to `Ctrl+C` if they actually want to cancel. It also adds
    regression coverage for the review-specific warning behavior. When users
    do cancel with Ctrl+C during /review, the TUI now tolerates the
    active-turn race that can happen during review handoff, and any queued
    steer messages are restored to the composer instead of being discarded.
    
    - Special-case `Esc` during an active `/review` when follow-up steer
    input is pending or has already been deferred.
    - Show a clear warning instead of interrupting the running review.
    - Make the Ctrl+C cancel path during /review resilient to active-turn
    races, while preserving any queued steer text by restoring it to the
    composer.
    - Add review-mode test coverage for the warning path.
    
    ## Testing
    
    1. Start a `/review` with a diff large enough that the review stays
    active for more than a few seconds.
    
    2. While the review is still running, type a follow-up / steer message,
    submit it, and then press `Esc`.
       Before: `Esc` causes the TUI to close abruptly.  
    After: the review keeps running and the transcript shows a warning that
    steer messages are not supported during `/review`, with guidance to use
    `Ctrl+C` if you want to cancel.
    
    3. Press `Ctrl+C` if you actually want to stop the review.  
    Before: (after restarting the test since Pt. 2 crashed) this is the
    intentional cancellation path.
    After: this remains the intentional cancellation path, and any queued
    follow-up steer text is restored to the composer instead of being lost.
       
    ## Note:
    `/review` mode explicitly does not support steering at this time (as
    noted in `turn_processer.rs`, if we want to explore that in the future
    this code will need to be modified). This change keeps unsupported steer
    attempts from crashing the TUI and preserves queued follow-up text if
    the user cancels with Ctrl+C.
  • multi-agent: add path-based v2 activity tracking (#27007)
    ## Why
    
    Multi-agent v2 identifies agents by canonical paths, but its tool
    handlers still emitted the larger legacy collaboration begin/end events
    built around nickname and role metadata. App-server, rollout-trace,
    analytics, and TUI consumers therefore lacked one compact path-based
    completion signal that behaved consistently across live events and
    replay.
    
    The TUI also needs a bounded `/agent` status surface for v2 agents. It
    should use recent local activity for previews, refresh liveness without
    loading full histories, and keep the legacy picker available when no
    path-backed v2 agent is known.
    
    ## What changed
    
    - Replace the v2 `spawn_agent`, `send_message`, `followup_task`, and
    `interrupt_agent` legacy lifecycle emissions with a success-only
    `SubAgentActivity` event. The event records the tool call ID, occurrence
    time, affected thread, canonical agent path, and `started`,
    `interacted`, or `interrupted` kind.
    - Expose the activity as a completion-only app-server v2
    `subAgentActivity` thread item in live notifications and reconstructed
    history, regenerate the protocol schemas, and count it in sub-agent tool
    analytics.
    - Track canonical paths from live activity and loaded-thread metadata in
    the TUI, and render the activity in live and replayed transcripts.
    - Make `/agent` list running path-backed agents with summaries from
    bounded local event buffers. Each summary is capped at 240 graphemes,
    the scan is capped at six recent items, only the last three wrapped
    lines are shown, and command output is omitted. Liveness falls back to
    metadata-only `thread/read` when local turn state is unavailable.
    - Persist the activity as a terminal rollout-trace runtime payload and
    reduce it to the corresponding spawn, send, follow-up, or close
    interaction edge. `interrupt_agent` is classified as a close-edge
    operation.
    - Preserve the legacy picker when no path-backed v2 agent is known.
    
    ## Compatibility
    
    App-server v2 clients that consumed `collabAgentToolCall` begin/end
    pairs for these tools must handle the new completion-only
    `subAgentActivity` item. Legacy v1 collaboration behavior is unchanged.
    
    ## Screenshot
    
    <img width="684" height="288" alt="Screenshot 2026-06-08 at 15 40 47"
    src="https://github.com/user-attachments/assets/194b3cd0-619d-45fb-b587-cf3e2b1b8a1d"
    />
    
    ## Testing
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-rollout-trace`
    - Added focused coverage for activity analytics, terminal trace
    serialization, spawn-edge reduction, `interrupt_agent` classification,
    TUI status rendering without aggregated command output, and clearing
    stale running state after a completed turn.
  • Preserve cloud requirements across TUI thread resets (#25177)
    Fixes a TUI regression where thread transitions such as `/new` and
    `/clear` could rebuild config without the cloud requirements loader,
    allowing users to fall back to non-cloud-managed settings. The config
    refresh path now preserves cloud requirements during thread
    reinitialization, and config loading is moved off the deep TUI event
    stack to avoid stack-overflow crashes during those reloads.
    
    - Passes the cloud requirements loader through TUI config rebuild paths.
    - Keeps cloud requirements applied for `/new`, `/clear`, `/fork`, side
    conversations, and session picker transitions.
    - Runs config building on a Tokio task so reloads do not occur on the
    deep TUI caller stack.
    - Adds regression coverage that cloud requirements survive
    thread-transition config refreshes.
    
    ## Test/Repro:
      - Start Codex with a cloud requirement applied.
      - Use `/new` or `/clear`.
    - The refreshed/fresh-session config should still include the cloud
    requirements
      
    This can be tested with any config item, at this moment for oai staff
    the easiest item to test is the `mentions_v2` feature. This is currently
    enabled in cloud requirements, but is not enabled by default. As a
    result, prior to these changes that feature is disabled after `/new` or
    `/clear`. Testing the same steps with a binary from this branch should
    not drop the feature enablement.
  • fix: preserve auto review across config and delegation (#26230)
    ## Why
    
    Auto Review should remain the effective approval reviewer when settings
    cross runtime boundaries. A config or app-server round trip must not
    change the reviewer identity, and delegated work must not silently fall
    back to user review.
    
    This requires both a stable canonical serialized value and propagation
    of the effective setting. `auto_review` is the canonical value across
    protocol and app-server output, while `guardian_subagent` remains
    accepted as backward-compatible input.
    
    ## What changed
    
    - serialize `ApprovalsReviewer::AutoReview` consistently as
    `auto_review` across core protocol and app-server v2
    - continue accepting `guardian_subagent` when reading existing config or
    client requests
    - carry the active turn's approval reviewer into spawned agents
    - update config/debug expectations and add delegated-task regression
    coverage
    
    ## Scope
    
    This does not change Guardian policy or remove compatibility with
    existing `guardian_subagent` inputs. It preserves the selected reviewer
    across serialization, config reloads, app-server settings, and delegated
    task setup.
    
    Related Guardian changes are split independently:
    
    - #26231 adds denials and soft denials
    - #26334 retries transient reviewer failures
    - #26333 reuses narrowly scoped low-risk approvals
    - #26232 adds TUI denial recovery
    
    ## Validation
    
    - `just test -p codex-app-server-protocol` (224 passed)
    - regression coverage for delegated task reviewer propagation
    - serialization coverage for canonical `auto_review` output and legacy
    `guardian_subagent` input
    
    ---------
    
    Co-authored-by: saud-oai <saud@openai.com>
  • fix(tui): scope MCP startup status by thread (#26639)
    ## Why
    
    MCP startup failures from spawned subagents were rendered as global
    notifications, so a child thread's failure could pollute the visible
    parent transcript. Routing the notification to the child exposed two
    related replay problems: session refresh could discard the buffered
    event, and a newly created child `ChatWidget` did not know the expected
    MCP server set, which could leave its startup spinner running after
    every server had settled.
    
    MCP startup diagnostics should remain visible in the thread that owns
    the startup without affecting other transcripts. The protocol also needs
    to support a future app-scoped MCP lifecycle where startup is not owned
    by any thread.
    
    ## Reported Behavior
    
    The [originating Slack
    report](https://openai.slack.com/archives/C08JZTV654K/p1780604538859939)
    called out that using subagents could turn MCP startup failures into a
    wall of yellow CLI warnings because repeated failures were not
    deduplicated. The intended behavior is for those diagnostics to remain
    visible once in the thread that owns the startup, without polluting the
    parent transcript.
    
    ## What Changed
    
    - add nullable `threadId` ownership to `mcpServer/startupStatus/updated`
    - populate it from the app-server conversation ID for the current
    thread-scoped lifecycle and regenerate the protocol schema and
    TypeScript artifacts
    - treat a missing or null `threadId` as app-scoped without injecting it
    into the active chat transcript
    - route and buffer thread-owned MCP startup notifications by thread in
    the TUI
    - preserve buffered MCP startup events across child session refresh
    - seed expected MCP servers before replaying a thread snapshot so
    startup reaches its terminal state
    - suppress an identical repeated failure warning for the same server
    within one startup round
    
    The owning thread still renders the detailed failure and final `MCP
    startup incomplete (...)` summary.
    
    ## How to Test
    
    1. Configure an optional MCP server named `smoke` that exits during
    initialization.
    2. Launch the TUI with multi-agent support enabled.
    3. Confirm the main thread's own startup failure renders one detailed
    `smoke` warning and one incomplete-startup summary.
    4. Spawn exactly one subagent.
    5. Confirm the parent transcript does not receive the subagent's MCP
    startup failure.
    6. Switch to the subagent thread and confirm it contains exactly one
    detailed `smoke` failure and one incomplete-startup summary.
    7. Confirm the subagent's MCP startup spinner disappears and the thread
    remains usable.
    8. Switch between the parent and subagent and confirm the warnings
    neither move nor duplicate.
    
    Targeted tests:
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    thread_start_emits_mcp_server_status_updated_notifications`
    - `just test -p codex-tui mcp_startup`
    
    The parent/child behavior and spinner completion were also exercised
    manually in tmux. `just argument-comment-lint` was attempted but blocked
    by an unrelated local Bazel LLVM empty-glob failure; touched Rust
    callsites were inspected manually.
  • [codex] Deduplicate skill load warnings (#26698)
    Skill reloads can get noisy when the watcher keeps triggering
    `skills/list` and the same invalid `SKILL.md` error comes back each
    time.
    
    This keeps the first warning visible, then suppresses repeats while the
    same `(path, message)` is still active. If the error clears and later
    comes back, or if the message changes, it will show again.
    
    Validation:
    - `just fmt`
    - `just test -p codex-tui skill_load_warning_state`
  • [codex-rs] support v2 personal access tokens (#25731)
    ## Summary
    
    - add v2 personal access token support for `codex login
    --with-access-token` and `CODEX_ACCESS_TOKEN`
    - classify opaque `at-` tokens separately from legacy Agent Identity
    JWTs
    - hydrate required ChatGPT account metadata through AuthAPI
    `/v1/user-auth-credential/whoami`
    - use PATs directly as bearer tokens while preserving existing ChatGPT
    account surfaces
    - expose PAT-backed auth as the explicit `personalAccessToken`
    app-server auth mode
    
    ## Implementation
    
    PAT auth is intentionally small and stateless. Loading a PAT performs
    one AuthAPI metadata request, stores the hydrated metadata in the
    in-memory auth object, and redacts the secret from debug output. Legacy
    Agent Identity JWT handling remains unchanged. The shared access-token
    classifier lives in a private neutral module because it dispatches
    between both credential types.
    
    PAT hydration fails closed when AuthAPI omits any required metadata,
    including email. Hydrated metadata is intentionally not persisted:
    startup performs a live `whoami` preflight so revoked tokens or changed
    account metadata are not accepted from a stale cache.
    
    ## Workspace restriction scope
    
    This change intentionally does **not** apply
    `forced_chatgpt_workspace_id` to PAT authentication. The setting is a
    client-side config guardrail, not an authorization boundary, and PAT
    does not currently require workspace-ID parity. The PAT login and
    `CODEX_ACCESS_TOKEN` paths therefore validate through AuthAPI without
    threading workspace-restriction state through access-token loading.
    Existing workspace checks for non-PAT auth remain on their established
    paths.
    
    ## App-server compatibility
    
    The public app-server `AuthMode` is shared across v1 and v2, and
    PAT-backed auth reports `personalAccessToken` through both APIs.
    Following human review, this intentionally removes the temporary v1
    compatibility mapping that reported PATs as `chatgpt`; the deprecated v1
    API is kept in parity with v2 rather than maintaining a separate closed
    enum. Clients with exhaustive auth-mode handling in either API version
    must add the new case and should generally treat it as ChatGPT-backed
    unless they need PAT-specific behavior.
    
    The v1 auth-status response still omits the raw PAT when `includeToken`
    is requested because that response cannot carry the account metadata
    needed to reuse the credential safely. Persisted PAT auth also omits the
    new enum value so older Codex builds can deserialize `auth.json` and
    infer PAT auth from the credential field after a rollback.
    
    ## Validation
    
    Latest review-fix validation:
    
    - `CARGO_INCREMENTAL=0 just test -p codex-login` (126 passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-cli` (263 passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-cli
    stored_auth_validation_handles_personal_access_token`
    - `CARGO_INCREMENTAL=0 just test -p codex-app-server-protocol` (226
    passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-models-manager
    refresh_available_models_uses_remote_only_catalog_for_chatgpt_auth`
    - `CARGO_INCREMENTAL=0 just test -p codex-tui
    existing_non_oauth_chatgpt_login_counts_as_signed_in`
    - `CARGO_INCREMENTAL=0 just fix -p codex-login -p
    codex-app-server-protocol -p codex-models-manager -p codex-tui -p
    codex-cli`
    - `just fmt`
    - `git diff --check`
    
    The broader `codex-tui` suite previously compiled and ran 2,834 tests.
    Three unrelated environment-sensitive guardian/IDE-socket tests failed
    after retries; the PAT-relevant TUI coverage passed.
  • Fix /goal usage text for control commands (#26551)
    ## Why
    
    The TUI's `/goal` usage text only advertised the objective form even
    though `/goal clear`, `/goal edit`, `/goal pause`, and `/goal resume`
    are implemented. This made the lifecycle controls difficult to discover
    and allowed the duplicated help text to drift from actual behavior.
    
    Fixes #25530.
    
    ## What changed
    
    - Show the complete `/goal [<objective>|clear|edit|pause|resume]` syntax
    in usage messages.
    - Share one usage string across slash-command dispatch and goal-related
    app messages.
    - Add inline snapshot coverage for the control-command usage path.
  • Surface TUI config write error causes (#26537)
    ## Summary
    
    TUI config writes currently wrap app-server failures with local context
    like `config/batchWrite failed in TUI`, but several user-visible paths
    only render the outer error. That hides the actionable app-server
    message, such as validation constraints or read-only `CODEX_HOME`
    failures, leaving users with a dead-end diagnostic.
    
    This change adds a small formatter next to the TUI config write helpers
    that renders the error source chain, then uses it for model persistence,
    feature persistence, project trust, status line writes, hook trust, and
    hook enablement.
    
    Fixes #26077
  • [codex] Forward turn moderation metadata through app-server (#25710)
    ## Why
    First-party backends can supply turn-scoped moderation metadata that
    app-server clients need for client-side presentation. Exposing this as
    an experimental typed notification lets opted-in clients consume it
    without interpreting raw Responses API events.
    
    ## What changed
    - forward `response.metadata.openai_chatgpt_moderation_metadata` from
    Responses API SSE and WebSocket streams as turn-scoped moderation
    metadata
    - emit the experimental app-server v2 `turn/moderationMetadata`
    notification with `{ threadId, turnId, metadata }`
    - add app-server integration coverage for the typed moderation metadata
    notification
    
    ## Testing
    - `just test -p codex-core
    build_ws_client_metadata_includes_window_lineage_and_turn_metadata`
    - `just test -p codex-core` (fails locally: 46 failures and 1 timeout,
    primarily missing `test_stdio_server` and shell snapshot timeouts)
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    turn_moderation_metadata_emits_typed_notification_v2`
    - `just test -p codex-app-server` (fails locally: 792 passed, 10 failed,
    and 5 timed out; failures are in existing environment-sensitive tests,
    primarily because nested macOS `sandbox-exec` is not permitted)
    - `just write-app-server-schema --experimental --schema-root
    /tmp/codex-app-server-schema-experimental`
  • [codex] Use model-advertised reasoning effort order (#26446)
    ## Summary
    - preserve the model catalog order for app-server
    `supportedReasoningEfforts` and document that client contract
    - render TUI reasoning choices in the advertised order
    - step reasoning shortcuts by adjacent list position instead of deriving
    order from known effort names
    - anchor unsupported configured values to the advertised default, or the
    first option when needed
    - remove canonical effort ordering helpers and the unused upgrade effort
    mapping
    
    ## Validation
    - `just fmt`
    - Local tests and compilation were not run per request; relying on CI.
    
    Stacked on #26444.
  • [codex] Support model-defined reasoning efforts (#26444)
    ## Summary
    - accept non-empty model-defined reasoning effort values while
    preserving built-in effort behavior
    - propagate the non-Copy effort type through core, app-server, TUI,
    telemetry, and persistence call sites
    - preserve string wire encoding and expose an open-string schema for
    clients
    - update model selection and shortcut behavior for model-advertised
    effort values
    
    ## Root cause
    `ReasoningEffort` gained a string-backed custom variant, so it could no
    longer implement `Copy` or rely on derived closed-enum serialization.
    Existing consumers still moved effort values from shared references and
    assumed a fixed built-in value set.
    
    ## Validation
    - `just fmt`
    - Local tests and compilation were not run per request; relying on CI.
  • Propagate permission approval environment id (#25862)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. #25858 - Add `environmentId` to `request_permissions`: lets the model
    target a selected environment and resolves relative permission paths
    against it.
    3. This PR (#25862) - Propagate permission approval environment id:
    carries the selected environment id through approval events, app-server
    requests, TUI prompts, and delegate forwarding.
    4. #25867 - Add remote request permissions integration coverage:
    verifies the selected remote environment across request, approval, grant
    reuse, and exec.
    
    This PR is stacked on #25858, and #25867 is stacked on this PR.
    
    ## Why
    
    PR2 lets the model bind a `request_permissions` call to a selected
    environment, but the approval event and client-facing request still
    needed to carry that binding. For CCA, the user-facing prompt and
    delegated approval path should know which environment the grant applies
    to instead of relying on cwd alone.
    
    ## What Changed
    
    - Added optional `environmentId` to `RequestPermissionsEvent`.
    - Emit the selected environment id from core permission approval events.
    - Preserve the environment id through delegate forwarding, including
    cwd-based delegated requests.
    - Added `environmentId` to app-server permission approval params,
    generated schema/TypeScript artifacts, and README examples.
    - Preserve and display the environment id in TUI permission approval
    prompts.
    - Updated focused core, app-server protocol, and TUI conversion
    coverage.
    
    ## Testing
    
    Not run locally per instruction. Performed read-only `git diff --check`.
  • Reduce stack pressure in session startup and config rebuilds (#25844)
    ## Why
    
    `/clear` starts a fresh thread with `InitialHistory::Cleared`, which
    re-enters the thread/session startup path. That path now builds large
    async futures through `ThreadManagerState::spawn_thread_with_source`,
    `Codex::spawn`, and `Session::new`. Separately, TUI config rebuilds for
    cwd and permission-profile changes build a similarly heavy
    `ConfigBuilder::build()` future inside the app task. In debug and Bazel
    runs, those call chains can put enough state on the caller stack to
    abort before startup or config refresh completes.
    
    This change keeps the behavior the same while moving the heaviest future
    frames off the caller stack.
    
    ## What changed
    
    - Box `Codex::spawn(...)` in `codex-rs/core/src/thread_manager.rs`
    before awaiting it from `spawn_thread_with_source`.
    - Box `Session::new(...)` in `codex-rs/core/src/session/mod.rs` before
    awaiting it from `Codex::spawn_internal`.
    - Route `ConfigBuilder::build()` through a small `tokio::spawn` helper
    in `codex-rs/tui/src/app/config_persistence.rs` so cwd and
    permission-profile config rebuilds run on a runtime worker stack while
    preserving error context.
    
    ## Verification
    
    CI is running on the PR.
    
    No new targeted tests were added. This is a mechanical stack-pressure
    reduction that keeps the existing behavior and error propagation intact.
  • feat: show enterprise monthly credit limits in status (#24812)
    ## Summary
    
    Enterprise users can have an effective monthly credit limit, but Codex
    `/status` currently drops that metadata from the account-usage response.
    
    This change adds the optional `spend_control.individual_limit`
    projection to the existing rate-limit snapshot flow. The backend client
    reads the monthly limit, app-server exposes it as `individualLimit`, and
    the TUI renders a `Monthly credit limit` row through the existing
    progress-bar renderer.
    
    When the backend does not return an effective monthly limit, existing
    rate-limit behavior is unchanged.
    
    ## Existing backend state
    
    The account-usage backend already returns the effective monthly limit
    and current usage together:
    
    ```json
    {
      "spend_control": {
        "reached": false,
        "individual_limit": {
          "limit": "25000",
          "used": "8000",
          "remaining": "17000",
          "used_percent": 32,
          "remaining_percent": 68,
          "reset_after_seconds": 86400,
          "reset_at": 1778137680
        }
      }
    }
    ```
    
    Before this change, Codex projected rolling `primary` and `secondary`
    windows plus `credits`. It ignored `spend_control.individual_limit`, so
    app-server clients and `/status` could not render the monthly cap.
    
    The updated flow is:
    
    ```text
    account usage backend
      -> backend-client reads spend_control.individual_limit
      -> existing rate-limit snapshot carries optional individual_limit
      -> app-server exposes optional individualLimit
      -> TUI renders Monthly credit limit
    ```
    
    ## App-server contract
    
    `account/rateLimits/read` and sparse `account/rateLimits/updated`
    notifications now include an additive nullable
    `rateLimits.individualLimit` field:
    
    ```json
    {
      "individualLimit": {
        "limit": "25000",
        "used": "8000",
        "remainingPercent": 68,
        "resetsAt": 1778137680
      }
    }
    ```
    
    In an `account/rateLimits/read` response, `null` means no monthly limit
    is available. `account/rateLimits/updated` remains a sparse rolling
    notification: clients merge available values into their most recent
    `account/rateLimits/read` snapshot or refetch. Nullable account metadata
    in a rolling notification does not clear a previously observed value.
    
    ## Design decisions
    
    - Extend the existing rate-limit snapshot instead of introducing a
    separate request or wire-level update protocol.
    - Keep the Codex projection narrow: `/status` needs the effective limit,
    current usage, remaining percentage, and reset timestamp.
    - Render the monthly row through the existing progress-bar renderer,
    with one optional detail line for `8,000 of 25,000 credits used`.
    - Keep the backend response optional so existing accounts and older
    usage states preserve their current behavior.
    - Preserve cached monthly metadata when sparse rolling notifications
    omit it. Live account-usage reads remain authoritative and can clear a
    removed limit.
    
    ## Visual evidence
    
    ```text
     Monthly credit limit:   [██████████████░░░░░░] 68% left (resets 07:08 on 7 May)
                             8,000 of 25,000 credits used
    ```
    
    Snapshot:
    `codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_enterprise_monthly_credit_limit.snap`
    
    ## Testing
    
    Tests: generated app-server schema verification, protocol tests,
    backend-client tests, app-server integration coverage, TUI snapshot
    coverage, formatting, and workspace lint cleanup.
  • feat(tui): restore output-free cancelled prompts (#25316)
    ## TL;DR
    
    When you press Esc or Ctrl+C after sending a prompt but before any
    output was rendering, it restores the last composer and the message.
    
    ## Summary
    
    Cancelling a prompt immediately after submission should behave like
    returning to edit that prompt, not like discarding the user's draft.
    Today, pressing `Esc` or `Ctrl+C` before Codex responds leaves the
    submitted prompt in the transcript and returns an empty composer,
    forcing the user to recall or retype it.
    
    When an interrupted turn has not produced substantive visible output,
    restore its submitted prompt directly into the composer and roll back
    that latest turn. This also covers the first prompt in a fresh thread,
    before the TUI has retained a local user-history cell. The restored
    draft keeps its text, image attachments, and active collaboration mode
    so it can be edited and resubmitted in place.
    
    Restoration is intentionally suppressed once the turn has produced
    user-visible activity such as assistant output, tool work, hooks, or
    patches. A transient thinking status does not make the prompt
    ineligible. Rollback also rebuilds terminal scrollback from the retained
    transcript cells so repeated cancellations and terminal resizes do not
    duplicate history.
    
    ## How to Test
    
    1. Start the TUI with `cargo run -p codex-cli --bin codex`.
    2. In a fresh thread, submit the first prompt and press `Esc` before
    Codex emits substantive output. Confirm that the prompt returns to the
    composer for editing and its submitted transcript row is removed.
    3. Repeat with `Ctrl+C`, then repeat after at least one completed turn.
    Confirm the same behavior.
    4. Submit a prompt, wait for assistant output or tool activity, then
    cancel. Confirm that the transcript remains intact and the prompt is not
    restored into the composer.
    5. Cancel several output-free prompts and resize the terminal between
    attempts. Confirm that the startup banner, tip, and transcript history
    do not duplicate in scrollback.
    
    Targeted tests:
    - `just test -p codex-tui cancelled_turn_edit_restores_prompt`
    - `just test -p codex-tui
    output_free_interrupted_turn_requests_prompt_restore`
    - `just test -p codex-tui
    visible_output_prevents_cancelled_turn_prompt_restore`
    - `just test -p codex-tui
    thinking_status_keeps_cancelled_turn_prompt_restore_eligible`
    - `just test -p codex-tui
    patch_activity_prevents_cancelled_turn_prompt_restore`
    
    The full `just test -p codex-tui` run completed with `2746` passing
    tests and two unrelated existing guardian feature-flag failures. `just
    argument-comment-lint` remains blocked locally by the existing Bazel
    LLVM `compiler-rt` sanitizer-header glob failure; the touched Rust diff
    was manually audited for positional literal comments.
  • store and expose parent_thread_id on Threads (#25113)
    ## Why
    
    This PR
    https://github.com/openai/codex/pull/24161#discussion_r3325692763
    revealed a subagent data modeling issue, where we overloaded
    `forked_from_id` to also mean `parent_thread_id`. That's incorrect since
    guardian and review subagents can be a subagent and NOT fork the main
    thread's history.
    
    The solution here is to explicitly store a new `parent_thread_id` on
    `SessionMeta`, alongside `forked_from_id` which already exists. While
    we're at it, also expose it in the app-server protocol on the `Thread`
    object.
    
    A thread->subagent relationship and a fork of thread history are
    orthogonal concepts.
    
    ## What Changed
    
    - Added top-level `parent_thread_id` persistence on `SessionMeta` and
    runtime/session plumbing through `SessionConfiguredEvent`,
    `CodexSpawnArgs`, `SessionConfiguration`, `ThreadConfigSnapshot`,
    `TurnContext`, and `ModelClient`.
    - Made turn metadata, request headers, analytics, and subagent-start
    events read the separate runtime/top-level parent field instead of
    deriving general parent lineage from `SessionSource` or
    `forked_from_thread_id`.
    - Passed parent lineage separately at delegated subagent, review,
    guardian, agent-job, and multi-agent spawn construction sites;
    copied-history fork lineage remains derived only from `InitialHistory`.
    - Persisted and exposed parent lineage through rollout/thread-store
    projections and app-server v2 `Thread.parentThreadId`.
    - Updated app-server README text and regenerated app-server schema
    fixtures for the additive `parentThreadId` response field.
  • Add thread archive CLI commands (#25021)
    ## Problem
    
    Saved threads can already be archived through app-server RPCs, but the
    command line did not expose direct archive or unarchive commands.
    
    ## Solution
    
    Add `codex archive <thread>` and `codex unarchive <thread>`, resolving
    UUIDs or exact thread names before calling the existing `thread/archive`
    and `thread/unarchive` RPCs. The commands support scoped remote flags so
    callers can target remote app-server endpoints when archiving or
    unarchiving threads.
    
    This also fixes a long-standing bug in `codex resume <thread id>` and
    `codex fork <thread id>` that I found when testing the new commands.
    These operations shouldn't be allowed on archived sessions. They now
    fail with an error that tells the user to run `codex unarchive <thread
    id>` first.
    
    ## Verification
    
    Added app-server coverage for rejecting archived thread resume by id and
    checking that the error includes the matching `codex unarchive <thread
    id>` command.
  • Constrain Windows sandbox requirements (#23766)
    # Why
    
    Managed requirements can already constrain sandbox policy choices, but
    Windows sandbox implementation selection was still resolved
    independently from those requirements. That left the TUI able to
    continue through the unelevated fallback even when an organization wants
    to require the elevated Windows sandbox implementation.
    
    # What
    
    - Add `[windows].allowed_sandbox_implementations` requirements support
    for the Windows `elevated` and `unelevated` implementations.
    - Apply that allowlist during core config resolution so disallowed
    configured or feature-selected Windows sandbox implementations fall back
    to an allowed implementation with the existing requirements warning
    path.
    - Reuse the existing TUI Windows setup prompts to block disallowed
    unelevated continuation, keep required elevated setup in front of the
    user, and refuse to persist a TUI-selected Windows sandbox mode that
    requirements disallow.
    
    # Semantics
    
    | Allowed | Selected | Effective |
    | --- | --- | --- |
    | `["elevated"]` | `unelevated` / unset | `elevated` |
    | `["unelevated"]` | `elevated` / unset | `unelevated` |
    | `["elevated", "unelevated"]` | `elevated` | `elevated` |
    | `["elevated", "unelevated"]` | `unelevated` | `unelevated` |
    | `["elevated", "unelevated"]` | unset | `elevated` |
    
    Availability is handled by interactive setup surfaces after allowlist
    resolution. If the effective elevated implementation is not ready,
    elevated-only requirements block on setup. When unelevated is also
    allowed, the UI may offer the existing unelevated fallback.
    
    ## TUI Screens
    
    If elevated setup is not already complete:
    ```
      Your organization requires the default Codex agent sandbox to continue. Set it up to protect your files and control
      network access.
      Learn more <https://developers.openai.com/codex/windows>
    
    › 1. Set up default sandbox (requires Administrator permissions)
      2. Quit
    ```
    
    If admin setup fails under `["elevated"]`:
    ```
      Couldn't set up your sandbox with Administrator permissions
    
      Your organization requires the default sandbox before Codex can continue.
      Learn more <https://developers.openai.com/codex/windows>
    
    › 1. Try setting up admin sandbox again
      2. Quit
    ```
    
    # Next Steps
    
    
    - extend the requirements/readout surface, such as
    `configRequirements/read`, so clients can inspect the loaded
    `[windows].allowed_sandbox_implementations` requirement instead of
    inferring it from Windows setup state
    - consider extending `windowsSandbox/readiness` as well
    - update the App startup guide, setup flow, and banner surfaces so an
    elevated-only requirement omits any continue-unelevated escape hatch and
    blocks startup until a permitted implementation is ready;
    - preserve the existing unelevated fallback path when requirements allow
    it, including the `["unelevated"]` case where elevated is disallowed
  • Add /archive slash command (#25027)
    ## Why
    
    TUI users can archive saved sessions from other surfaces, but there is
    no in-session command for archiving the active session. Since archiving
    the active session also exits the TUI, the command should ask for
    explicit confirmation instead of firing immediately.
    
    I'm also working on [a companion
    PR](https://github.com/openai/codex/pull/25021) that adds `codex
    archive` and `codex unarchive` top-level CLI commands.
    
    ## What changed
    
    - Adds a new `/archive` slash command described as `archive this session
    and exit`.
    - Shows a confirmation dialog with `No, don't archive` selected first
    and `Yes, archive and exit` as the explicit action.
    - On confirmation, calls the existing `thread/archive` app-server RPC
    for the active main session and exits after success.
    - Keeps `/archive` disabled while a task is running and unavailable in
    side conversations.
    
    ## Verification
    
    Added focused TUI coverage for the `/archive` confirmation flow,
    disabled-while-task-running behavior, and the `/ar` slash-command popup
    snapshot.
  • Align TUI permissions labels with app (#25017)
    ## Summary
    
    The desktop app now presents the on-request permissions mode as `Ask for
    approval` and the manual-review-backed mode as `Approve for me`. The TUI
    still exposed older/internal labels like `Default` and `Auto-review`,
    which made the same underlying settings look different across clients.
    
    This updates the TUI UX copy to match the app without changing the
    underlying default behavior. Fresh threads continue to use the existing
    on-request approval mode, now displayed as `Ask for approval`.
    
    The label changes cover `/permissions`, explicit profile permissions
    menus, status surfaces, config persistence history/error text, and the
    corresponding TUI snapshots.
    
    ### Before
    <img width="1181" height="119" alt="Screenshot 2026-05-28 at 10 19
    47 PM"
    src="https://github.com/user-attachments/assets/0664846b-b6dd-4931-b4dd-d0af0d42058e"
    />
    <img width="523" height="19" alt="Screenshot 2026-05-28 at 10 21 29 PM"
    src="https://github.com/user-attachments/assets/7899c33e-b35d-4684-8389-97e357803423"
    />
    
    ### After
    <img width="1216" height="117" alt="Screenshot 2026-05-28 at 10 19
    32 PM"
    src="https://github.com/user-attachments/assets/015aab43-ac97-411f-8031-75cdd887251b"
    />
    <img width="567" height="18" alt="Screenshot 2026-05-28 at 10 20 24 PM"
    src="https://github.com/user-attachments/assets/28b6422c-b823-4298-b221-c83d46d09d66"
    />
  • windows-sandbox: pass workspace roots to runner (#24108)
    ## Why
    
    #23813 switches the Windows sandbox runner path to `PermissionProfile`,
    but it still left one runtime anchor for resolving symbolic
    `:workspace_roots` entries. That is not enough once a turn has multiple
    effective workspace roots: exact entries and deny globs under
    `:workspace_roots` need to be materialized for every runtime root before
    the command runner chooses token mode or builds ACL plans.
    
    ## What Changed
    
    - Replaces the Windows runner/setup `permission_profile_cwd` plumbing
    with `workspace_roots: Vec<AbsolutePathBuf>`.
    - Resolves Windows-local `PermissionProfile` data with
    `materialize_project_roots_with_workspace_roots(...)` instead of the
    single-cwd helper.
    - Threads `Config::effective_workspace_roots()` through core execution,
    unified exec, TUI setup/read-grant flows, app-server setup, app-server
    `command/exec`, and `debug sandbox` on Windows.
    - Preserves those workspace roots through the zsh-fork escalation
    executor instead of rebuilding them from `sandbox_policy_cwd`.
    - Makes `ExecRequest::new(...)` and the remaining
    `build_exec_request(...)` helper path take
    `windows_sandbox_workspace_roots` explicitly so new call sites cannot
    silently fall back to `vec![cwd]`.
    - Clarifies the `debug sandbox` non-Windows comment: remaining
    cwd-dependent resolution still uses `sandbox_policy_cwd`, while
    `:workspace_roots` entries are already materialized from config roots.
    - Updates elevated runner IPC `SpawnRequest` to send `workspace_roots`
    and bumps the framed IPC protocol version to `3` for the payload shape
    change.
    - Adds Windows-local resolver coverage for expanding exact and glob
    `:workspace_roots` entries across multiple roots, plus core helper
    coverage proving explicit roots are preserved.
    
    ## Verification
    
    - `cargo check -p codex-windows-sandbox -p codex-core -p codex-tui -p
    codex-cli -p codex-app-server`
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-core unix_escalation`
    - `cargo test -p codex-app-server windows_sandbox`
    - `cargo test -p codex-tui windows_sandbox`
    - `cargo test -p codex-cli debug_sandbox`
    - `just test -p codex-core unified_exec`
    - `just test -p codex-core
    build_exec_request_preserves_windows_workspace_roots`
    - `env -u CODEX_NETWORK_PROXY_ACTIVE -u
    CODEX_NETWORK_ALLOW_LOCAL_BINDING just test -p codex-app-server --lib
    command_exec`
    - `just test -p codex-windows-sandbox`
    - `just test -p codex-exec sandbox`
    - `just fix -p codex-core -p codex-app-server -p codex-windows-sandbox`
    
    A local macOS cross-check with `cargo check --target
    x86_64-pc-windows-msvc ...` did not reach crate Rust code because native
    dependencies require Windows SDK headers (`windows.h` / `assert.h`) in
    this environment; Windows CI remains the real target validation.
    
    Two local targeted filters compile but do not run assertions on macOS:
    `env -u CODEX_NETWORK_PROXY_ACTIVE -u CODEX_NETWORK_ALLOW_LOCAL_BINDING
    just test -p codex-app-server --lib command_exec_processor` matched zero
    tests, and `just test -p codex-linux-sandbox landlock` matched zero
    tests because the landlock suite is Linux-only.
  • [codex] Add user input client ids (#24653)
    ## Summary
    
    Adds an optional `clientId` field to app-server v2 `UserInput` and
    carries it through the core `UserInput` model so clients can correlate
    echoed user input items without relying on payload equality.
    
    ## Details
    
    - Adds `client_id: Option<String>` to core `UserInput` variants.
    - Exposes the v2 app-server field as `clientId` on the wire and in
    generated TypeScript.
    - Preserves the id when converting between app-server v2 and core
    protocol types.
    - Regenerates app-server schema fixtures.
    
    ## Validation
    
    - `just fmt`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-protocol`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-protocol`
    - `git diff --check`
  • Expose MCP server info as part of server status (#24698)
    # Summary
    
    Expose MCP server info via App Server (when available) so apps can
    render a richer MCP experience
  • feat(tui): add OSC 8 web links to rich content (#24472)
    ## Why
    
    Wrapped URLs in rich TUI output, especially URLs rendered inside
    Markdown tables, are split across terminal rows. In terminals that
    support OSC 8 hyperlinks, treating each visible fragment as part of the
    complete destination enables reliable open-link and copy-link actions
    even after table layout wraps the URL.
    
    This addresses the semantic-link portion of #12200 and the behavior
    described in
    https://github.com/openai/codex/issues/12200#issuecomment-4535452980. It
    does not change ordinary drag-selection across bordered table rows.
    
    ## What Changed
    
    - Added shared TUI OSC 8 support that validates `http://` and `https://`
    destinations, sanitizes terminal payloads, and applies metadata
    separately from visible line width/layout.
    - Added semantic web-link annotations to assistant and proposed-plan
    Markdown, including explicit web links and bare web URLs in prose and
    table cells while excluding code and non-web Markdown destinations.
    - Preserved complete URL targets through table wrapping, narrow pipe
    fallback, streaming, transcript overlay rendering, history insertion,
    and resize replay.
    - Routed intentional Codex-owned links in notices,
    status/setup/app-link, feedback, onboarding, MCP/plugin help, memories,
    and update surfaces through the shared hyperlink handling.
    
    ## How to Test
    
    1. Run Codex in a terminal with OSC 8 link support, such as Ghostty, and
    request an assistant response containing a Markdown table whose last
    column contains a long `https://` URL.
    2. Make the terminal narrow enough for the URL to wrap across multiple
    bordered table rows.
    3. Use the terminal's open-link or copy-link action on more than one
    wrapped URL fragment and confirm each fragment resolves to the complete
    original URL.
    4. Resize the terminal after the table is rendered and repeat the link
    action to confirm the destination survives scrollback replay.
    5. Open the transcript overlay while rich output is present and confirm
    web links remain interactive there.
    6. As a regression check, render inline/fenced code containing URL text
    and a Markdown link such as
    `[https://example.com](mailto:support@example.com)`; confirm these do
    not acquire a web OSC 8 destination.
    
    Targeted automated coverage exercised Markdown links and exclusions,
    wrapped and pipe-fallback tables, streaming/transcript overlay
    propagation, status-link truncation, and rendered word-wrapping cell
    alignment. `just test -p codex-tui` was also run; it passed the
    hyperlink coverage and reproduced two unrelated existing guardian
    feature-flag test failures.
  • TUI config cleanup: plugin marketplace (#24257)
    ## Why
    
    Plugin and marketplace mutations are applied by the app server, but
    several TUI follow-up paths still refreshed state from the TUI host
    config. In remote workspace mode, that can leave plugin UI state tied to
    stale client-local `config.toml` after the server has already applied
    the mutation.
    
    ## What
    
    - Stop reloading the TUI host config after app-server-owned plugin,
    marketplace, skill, and app mutations.
    - Use the same app-server-owned refresh path for local and remote
    sessions: ask the app server to reload user config where the running
    session needs it, then refetch plugin list/detail state from the app
    server.
    - Build plugin mention candidates from existing app-server `plugin/list`
    and `plugin/read` data in both local and remote sessions instead of
    TUI-host plugin config.
    - Avoid the duplicate local config reload after `ReloadUserConfig` asks
    the app server to reload config.
    
    ## Verification
    
    Manually launched a local WebSocket app-server with a temp server
    `CODEX_HOME`, launched the TUI with a separate temp host `CODEX_HOME`
    and `--remote`, installed a sample plugin from a temp local marketplace
    through `/plugins`, and confirmed the TUI refreshed to installed state
    while only the server config gained `[plugins."sample@debug"]`. Trace
    logs showed the TUI using app-server `plugin/list` and `plugin/read` for
    the refresh path.
  • Uprev Rust toolchain pins to 1.95.0 (#24684)
    ## Summary
    - Bump the workspace Rust toolchain from `1.93.0` to `1.95.0` across
    Cargo, Bazel, CI, release workflows, devcontainers, and the Codex
    environment config.
    - Refresh `MODULE.bazel.lock` so the Bazel Rust toolchain artifacts
    match the new version.
    - Leave purpose-specific toolchains unchanged, including the
    `argument-comment-lint` nightly and the upstream `rusty_v8` `1.91.0`
    build pin.
    - Includes fixes for new lints from `just fix` and a few codex-authored
    fixes for lints without a suggestion.
  • windows-sandbox: remove SandboxPolicy runner plumbing (#23813)
    ## Why
    
    The Windows sandbox runner still carried the old `SandboxPolicy`
    compatibility path even though core now computes `PermissionProfile`.
    That meant Windows command-runner execution could only see the legacy
    projection, so profile-only filesystem rules such as deny globs were not
    part of the runner input.
    
    ## What Changed
    
    - Removed the Windows-local `SandboxPolicy` parser/export and deleted
    `windows-sandbox-rs/src/policy.rs`.
    - Changed restricted-token capture/session setup, elevated setup,
    world-writable audit, read-root grant, and command-runner session APIs
    to accept `PermissionProfile` plus the profile cwd.
    - Bumped the elevated command-runner IPC protocol to version 2 because
    `SpawnRequest` now carries `permission_profile` /
    `permission_profile_cwd` instead of the legacy `policy_json_or_preset` /
    `sandbox_policy_cwd` fields.
    - Updated core exec, unified exec, debug-sandbox, TUI setup/grant flows,
    and app-server setup to pass the actual effective `PermissionProfile`.
    - Left regression coverage asserting the old IPC policy fields are
    absent and the runner serializes tagged `PermissionProfile` JSON.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-app-server
    request_processors::windows_sandbox_processor`
    - `just fix -p codex-windows-sandbox -p codex-core -p codex-app-server
    -p codex-cli -p codex-tui`
    - `just fix -p codex-cli -p codex-tui`
    - `just fix -p codex-windows-sandbox -p codex-tui`
    - `rg "\\bSandboxPolicy\\b" codex-rs/windows-sandbox-rs` returned no
    matches.
    
    Note: `cargo test -p codex-cli` was attempted but did not reach crate
    tests because local disk filled while compiling dependencies (`No space
    left on device`). The targeted clippy pass compiled the affected CLI/TUI
    surfaces afterward.
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23813).
    * #24108
    * __->__ #23813
  • TUI config cleanup: plugin mentions (#24266)
    ## Summary
    
    TUI plugin mention refresh still joined app-server plugin inventory with
    client-local plugin config, which can diverge once plugin state is owned
    by the app server.
    
    This changes the TUI to mirror the GUI client: `plugin/list` is the
    autocomplete source, and mention candidates are plugin-level entries
    filtered to installed, enabled, and not disabled by admin. The TUI no
    longer reads local plugin config or calls `plugin/read` while refreshing
    plugin mention candidates.
    
    ## API shape and limitations
    
    The current app-server API does not expose effective per-session plugin
    capability summaries for mention autocomplete. As in the GUI,
    autocomplete now trusts `plugin/list` metadata rather than proving which
    plugin capabilities are loaded in the active session.
    
    That avoids stale client-local reads and the cwd/remote detail gaps in
    `plugin/read`, but intentionally accepts the same list-level tradeoff as
    the app: if `plugin/list` reports a remote plugin before its local
    bundle is materialized, the plugin can still appear as a mention
    candidate.
  • Use thread config for TUI MCP inventory (#24532)
    ## Summary
    `/mcp` in the TUI should reflect the current loaded thread, including
    project-local MCP servers from that thread config. Before this change,
    `mcpServerStatus/list` only read the latest global MCP config, so the
    active chat could miss project-local servers.
    
    This adds optional `threadId` to `mcpServerStatus/list`. When present,
    app-server resolves the loaded thread and lists MCP status from the
    refreshed effective config for that thread; when omitted, existing
    global config behavior stays unchanged.
    
    The TUI now sends the active chat thread id for `/mcp` and `/mcp
    verbose`, carries that origin through the async inventory result, and
    ignores stale completions if the user has switched threads before the
    fetch returns. The app-server schemas were regenerated.
    
    ## Follow-up
    Once this app-server API change lands, the desktop app should make the
    same `threadId` plumbing so its MCP inventory also uses the current
    thread config.
    
    Fixes #23874