Commit Graph

3154 Commits

  • [2 of 2] Finish moving goal runtime to extension (#26548)
    ## Stack
    
    1. [#26547](https://github.com/openai/codex/pull/26547) - [1 of 2] Align
    goal extension with core behavior
    2. [#26548](https://github.com/openai/codex/pull/26548) - [2 of 2] Move
    goal runtime to extension
    
    ## Why
    
    This PR completes the switch of the goal behavior to the
    extension-backed runtime and removes the old core goal implementation.
    
    ## What Changed
    
    - Installs the goal extension for app-server `ThreadManager` sessions.
    - Routes app-server thread goal `get`, `set`, and `clear` through
    `GoalService`.
    - Uses thread-idle lifecycle emission after goal resume and snapshot
    ordering so the extension can decide whether to continue the goal.
    - Forwards extension goal updates through a FIFO async app-server
    notification path so backpressure does not drop them or reorder updates.
    - Keeps review turns from enabling goal runtime behavior.
    - Plans extension tools before dynamic tools so built-in goal tool names
    keep their old precedence when goals are enabled.
    - Removes the old core goal runtime, core goal tool handlers, and core
    goal tool specs.
    - Updates tests that were coupled to the core-owned goal runtime while
    leaving the legacy `<goal_context>` compatibility path in core for old
    threads.
    - Removes the stale cargo-shear ignore now that `codex-goal-extension`
    is used by the workspace.
    - Keeps realtime event matching exhaustive after removing the old
    goal-specific realtime text path.
    
    
    ## Validation
    
    - Ran manual `/goal` runs in TUI. Validated time accounting matched
    wall-clock time and goal lifecycle state transitions.
  • Make runtime workspace roots absolute in app-server API (#26552)
    Stacked on #26532.
    
    ## Why
    
    #26532 moves cwd normalization to the app-server/core boundary.
    `runtimeWorkspaceRoots` still accepted raw paths in v2 requests and in
    `ConfigOverrides`, which left core responsible for interpreting those
    roots later. This makes runtime workspace roots follow the same
    absolute-path boundary as cwd.
    
    ## What
    
    - Change v2 `runtimeWorkspaceRoots` request fields for `thread/start`,
    `thread/resume`, `thread/fork`, and `turn/start` to `AbsolutePathBuf`.
    - Deduplicate already-absolute runtime roots in app-server handlers and
    pass them through `ConfigOverrides.workspace_roots` as
    `AbsolutePathBuf`.
    - Update TUI and exec client request builders to pass absolute runtime
    roots directly.
    - Update app-server docs, schema fixtures, and focused tests for
    absolute runtime roots.
    
    ## Testing
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server runtime_workspace_roots`
    - `just test -p codex-core
    session_permission_profile_rebinds_runtime_workspace_roots`
    - `just test -p codex-tui app_server_session`
    - `just test -p codex-exec`
  • [codex] Add turn profiling analytics (#26484)
    ## Summary
    
    Add flat profiling fields to `codex_turn_event` so analytics can explain
    where turn wall-clock time is spent without changing tool execution
    behavior.
    
    The profile reports:
    - time before the first sampling request
    - sampling time across all attempts and follow-ups
    - overhead between sampling requests
    - time blocked in the post-sampling tool drain
    - time after the final sampling request
    - sampling request and retry counts
    
    ## Implementation
    
    - Extend the existing turn timing state with constant-memory phase
    accounting and one RAII phase guard.
    - Observe sampling and the existing post-sampling drain only at turn
    orchestration boundaries.
    - Keep tool runtime, tool futures, response item handling, and turn
    lifecycle values unchanged.
    - Add the profiling fields directly to the existing analytics turn event
    without changing app-server protocol or rollout persistence.
    - Use the existing turn `status` to distinguish completed, failed, and
    interrupted profiles.
    
    Exact sampling/tool overlap is intentionally omitted because measuring
    tool completion accurately would require hooks in the tool execution
    path.
    
    ## Validation
    
    - Add app-server end-to-end coverage for a single-sampling turn with no
    blocking tool work.
    - Add app-server end-to-end coverage for `request_user_input` blocking
    followed by a second sampling request.
    - CI is running on the PR; tests were not executed locally per
    repository guidance.
  • [codex] Respect Windows sandbox backend in exec policy (#26307)
    ## Why
    
    Windows managed filesystem permissions can now be backed by a real
    Windows sandbox. `exec-policy` was still treating the managed read-only
    policy shape as if there were never a sandbox backend, so benign
    unmatched commands such as PowerShell directory listings could be
    rejected with `blocked by policy` even when `windows.sandbox` was
    enabled.
    
    The inverse case still needs to stay conservative: when the Windows
    sandbox backend is disabled, managed filesystem restrictions are only
    configuration intent, not an enforced filesystem boundary. That applies
    to writable-root restricted profiles too, not just read-only profiles.
    
    ## What Changed
    
    - Thread the effective `WindowsSandboxLevel` into exec-policy approval
    decisions for shell, unified exec, and intercepted shell exec paths.
    - Treat managed restricted filesystem profiles as lacking sandbox
    protection only on Windows when `WindowsSandboxLevel::Disabled`.
    - Exclude full-disk-write profiles from that no-backend path because
    they do not rely on filesystem sandbox enforcement.
    - Remove the cwd-sensitive read-only heuristic and the now-stale cwd
    plumbing from exec-policy approval contexts.
    - Add Windows coverage for both enabled-sandbox and disabled-backend
    behavior, including a writable-root managed profile.
    
    ## Validation
    
    - Added/updated `exec_policy` coverage for managed filesystem
    restrictions, full-disk-write exclusion, enabled Windows sandbox
    behavior, and disabled-backend read-only/writable-root behavior.
    - `just test -p codex-core exec_policy` — 100 passed, 10 leaky
    - Empirical local `codex exec` probe with `--sandbox read-only -c
    'windows.sandbox="unelevated"'`: PowerShell directory listing completed
    successfully.
    - Disabled-backend control with Windows sandbox cleared: the same
    command was rejected with `blocked by policy`.
  • Make turn diff tracker multi-env aware (#26433)
    ## Why
    
    Turn diffs were tracked as one flat set of absolute paths. In
    multi-environment turns, local and remote environments can report the
    same path while representing different filesystems, so a single path key
    can collapse distinct changes or attribute them to the wrong
    environment.
    
    The environment name is **NOT** included in the generated unified diff.
    This can come later.
  • Require absolute cwd in thread settings (#26532)
    ## Why
    
    Thread settings cwd overrides are expected to be resolved before they
    enter core. Keeping this boundary as a plain `PathBuf` made it easy for
    core/session code to keep fallback normalization and relative-path
    resolution logic in places that should only receive an already-resolved
    cwd.
    
    This is intentionally the absolute-cwd-only slice: it does not change
    environment selection stickiness or cwd-to-default-environment fallback
    behavior.
    
    ## What changed
    
    - Changes `ThreadSettingsOverrides.cwd`,
    `CodexThreadSettingsOverrides.cwd`, and `SessionSettingsUpdate.cwd` to
    use `AbsolutePathBuf`.
    - Removes core-side cwd normalization/resolution from session settings
    updates.
    - Updates affected core/app-server test helpers and callsites to pass
    existing absolute cwd values or use `abs()` helpers.
    
    ## Validation
    
    Opening as draft so CI can start while local validation continues.
  • feat: reload v2 agents on delivery (#26623)
    ## Summary
    
    This is the first small step toward making multi-agent v2 agents durable
    logical agents whose `ThreadManager` residency is only an implementation
    detail.
    
    This PR adds a narrow v2 reload-on-delivery hook:
    
    - If a known v2 agent target is already loaded, delivery is unchanged.
    - If the target is still registered but missing from `ThreadManager`,
    delivery reloads that exact v2 thread from durable rollout history
    before submitting the message.
    - If the target is unknown, closed, missing from storage, or not a v2
    thread, delivery still fails as not found.
    
    The reload is wired only into existing-agent delivery paths: v2
    `send_message` / `followup_task`, and legacy `send_input` when its
    target is a known v2 agent.
    
    ## Stack
    
    1. **Reload on delivery**: load known unloaded v2 agents before
    `followup_task`, `send_message`, or `send_input` delivery. This PR.
    2. **Residency LRU**: unload idle resident v2 agents from
    `ThreadManager` without making them closed or unreachable.
    3. **Execution concurrency**: count active non-root turns, not logical
    agents or resident idle threads.
    4. **Close semantics**: make v2 close interrupt-only and leave durable
    agent identity intact.
    5. **Resume cleanup**: remove user-facing v2 resume semantics;
    addressing an unloaded durable agent reloads it implicitly.
    
    ## Validation
    
    - Ran `just fmt`.
    - Left broader tests and clippy to CI.
  • refactor: split agent control modules (#26610)
    ## Summary
    
    Mechanically splits `AgentControl` into focused modules so later agent
    runtime changes are easier to review. The shared lookup, messaging, and
    completion logic remains in `control.rs`, while spawn-specific code and
    V1 legacy close/resume behavior move into dedicated files.
    
    ## Changes
    
    - Extract spawn-agent code into `agent/control/spawn.rs`.
    - Extract V1-only legacy close/resume behavior into
    `agent/control/legacy.rs`.
    - Keep shared control-plane behavior in `agent/control.rs`.
    - Preserve existing behavior; this PR is intended to be mechanical.
    
    ## Stack
    
    1. This PR - Mechanical `AgentControl` split: extracts spawn and V1
    legacy code without behavior changes.
    2. #26614 - Execution slot accounting: separates logical agents from
    active execution slots.
    3. #26611 - Residency and reload runtime: adds resident-agent LRU,
    eviction/reload, durable lookup, and V2 delivery through reload.
    4. #26612 - V2 tool semantics: narrows `close_agent` to interrupt-only
    and updates V2 tool coverage.
  • [codex] Keep v1 spawn metadata visible (#26599)
    ## Summary
    - keep the legacy v1 `spawn_agent` role and model selectors visible
    - add regression coverage for the default v1 tool plan
    
    ## Why
    `hide_spawn_agent_metadata` is a multi-agent v2 setting, but the v1
    planning branch also consumed it. After the default changed to `true`,
    v1 stopped advertising `agent_type`, `model`, `reasoning_effort`, and
    `service_tier`, preventing configured agents from being selected.
    
    This keeps the hidden-metadata default for v2 while opting v1 out of
    that behavior.
    
    Fixes #26363.
    
    ## Validation
    Not run locally, per request; CI will validate the change.
  • [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`
  • nit: doc (#26566)
    Matching CBv9
  • Encrypt multi-agent v2 message payloads (#26210)
    ## Why
    
    Multi-agent v2 currently routes agent instructions through normal tool
    arguments and inter-agent context. That means the parent model can emit
    plaintext task text, Codex can persist it in history/rollouts, and the
    recipient can receive it as ordinary assistant-message JSON.
    
    This changes the v2 path so agent instructions stay encrypted between
    model calls: Responses encrypts the `message` argument returned by the
    model, Codex forwards only that ciphertext, and Responses decrypts it
    internally for the recipient model.
    
    ## What changed
    
    - Mark the v2 `message` parameter as encrypted for `spawn_agent`,
    `send_message`, and `followup_task`.
    - Treat multi-agent v2 tool `message` values as ciphertext
    unconditionally.
    - Store v2 inter-agent task text in
    `InterAgentCommunication.encrypted_content` with empty plaintext
    `content`.
    - Convert encrypted inter-agent communications into the Responses
    `agent_message` input item before sending the child request.
    - Preserve `agent_message` items across history, rollout, compaction,
    telemetry, and app-server schema paths.
    - Leave multi-agent v1 unchanged.
    
    ## Message shape
    
    The model still calls the v2 tools with a `message` argument, but that
    value is now ciphertext:
    
    ```json
    {
      "name": "spawn_agent",
      "arguments": {
        "task_name": "worker",
        "message": "<ciphertext>"
      }
    }
    ```
    
    Codex stores the task as encrypted inter-agent communication:
    
    ```json
    {
      "author": "/root",
      "recipient": "/root/worker",
      "content": "",
      "encrypted_content": "<ciphertext>",
      "trigger_turn": true
    }
    ```
    
    When Codex builds the recipient request, it forwards the ciphertext
    using the new Responses input item:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root",
      "recipient": "/root/worker",
      "content": [
        {
          "type": "encrypted_content",
          "encrypted_content": "<ciphertext>"
        }
      ]
    }
    ```
    
    Responses decrypts that item internally for the recipient model.
    
    ## Context impact
    
    - Parent context no longer carries plaintext v2 agent task instructions
    from these tool arguments.
    - Codex rollout/history stores ciphertext for v2 agent instructions.
    - Recipient requests receive an `agent_message` item instead of
    assistant commentary JSON for encrypted task delivery.
    - Plaintext completion/status notifications are still plaintext because
    they are Codex-generated status messages, not encrypted model tool
    arguments.
    
    ## Validation
    
    - `just test -p codex-tools`
    - `just test -p codex-protocol`
    - `just test -p codex-rollout`
    - `just test -p codex-rollout-trace`
    - `just test -p codex-otel`
    - `just write-app-server-schema`
  • [codex] Add environment shell info (#26480)
    ## Why
    
    Shell detection needs to be available through the `Environment`
    abstraction so callers can ask the selected local or remote environment
    for shell metadata without adding a separate HTTP endpoint or parallel
    info-source path. This keeps shell metadata shaped like the existing
    environment-owned filesystem capability and lets remote environments
    answer through exec-server JSON-RPC.
    
    ## What changed
    
    - Added `environment/info` to the exec-server protocol/client/server and
    exposed `Environment::info()`.
    - Added local and remote environment info providers on `Environment`,
    following the existing capability-provider pattern used for filesystem
    access.
    - Moved the shared shell detection logic into `codex-shell-command` and
    kept core shell APIs as wrappers around that implementation.
    - Returned shell metadata as `EnvironmentInfo { shell: ShellInfo }`
    using the existing shell detection path.
    - Added a remote environment test that calls `Environment::info()`
    through an exec-server-backed environment.
    
    ## Validation
    
    - `git diff --check`
    - `just test -p codex-shell-command`
    - `just test -p codex-core -E 'test(/shell::tests::/)'`\n- `just test -p
    codex-exec-server environment`
  • core: derive exec policy filesystem policy from profile (#26499)
    ## Why
    
    `PermissionProfile` already owns the runtime filesystem sandbox policy
    through `file_system_sandbox_policy()`. Keeping a separate
    `FileSystemSandboxPolicy` on exec-policy fallback contexts made it
    possible for callers and tests to construct split states that the
    production permission model should not rely on.
    
    ## What changed
    
    - Removed `file_system_sandbox_policy` from `UnmatchedCommandContext`,
    `ExecApprovalRequest`, and the intercepted Unix exec-policy context.
    - Derived filesystem sandbox policy inside unmatched-command decision
    logic from `PermissionProfile::file_system_sandbox_policy()`.
    - Simplified shell/unified-exec callers and tests that were only
    plumbing the duplicate policy through.
    
    ## Testing
    
    Local tests not run per request; relying on remote CI.
  • [codex] Add use_responses_lite 'override' logic (#26487)
    ## Summary
    
    - add a defaulted `ModelInfo.use_responses_lite` catalog field
    - support serializing `reasoning.context` while preserving the existing
    effort and summary path
    - has not been turned on for any models yet
    
    I've added an override to parallel tools if responses_lite is on. I've
    also forced persistent reasoning when using responses_lite. It would be
    ideal if we could centralize all the responses_lite plumbing, but I
    think this is best for now to keep the plumbing & diffs small.
    
    ## Testing
    
    - `cargo test -p codex-protocol
    model_info_defaults_availability_nux_to_none_when_omitted`
    - `RUST_MIN_STACK=8388608 cargo test -p codex-core
    responses_lite_sets_all_turns_context_and_disables_parallel_tool_calls`
    - `RUST_MIN_STACK=8388608 cargo test -p codex-core
    configured_reasoning_summary_is_sent`
    - `cargo check -p codex-core --tests`
    - `RUST_MIN_STACK=8388608 cargo clippy -p codex-core --tests` (passes
    with pre-existing warnings in `codex-code-mode` and
    `codex-core-plugins`)
  • [codex] Emit sandbox outcome telemetry event (#25955)
    ## Summary
    
    Adds a dedicated `codex.sandbox_outcome` telemetry event so we can query
    sandbox edge outcomes without threading sandbox metadata through
    tool-result output types.
    
    This is meant to make sandbox failures and approved escalation retries
    visible in OTEL while keeping the existing `codex.tool_result` event
    shape focused on tool completion data.
    
    ## What changed
    
    - Adds `SessionTelemetry::sandbox_outcome(...)`, which emits
    `codex.sandbox_outcome` as both a log and trace event.
    - Records the tool name, call id, sandbox outcome, initial attempt
    duration, and escalated attempt duration when a retry runs.
    - Emits `denied` when the sandbox blocks execution and no retry is run.
    - Emits `timed_out` and `signal` when those sandbox errors surface from
    tool execution.
    - Emits `escalated` when the initial sandboxed attempt fails and the
    approved unsandboxed retry succeeds.
    - Adds OTEL coverage for the new event payload, including timing fields.
    
    ## Validation
    
    - `RUST_MIN_STACK=8388608 just test -p codex-core
    sandbox_outcome_event_records_outcome
    handle_sandbox_error_user_approves_retry_records_tool_decision`
    - `just test -p codex-otel
    otel_export_routing_policy_routes_tool_result_log_and_trace_events
    runtime_metrics_summary_collects_tool_api_and_streaming_metrics`
    - `just fix -p codex-core`
    - `just fix -p codex-otel`
  • [codex] Preserve logical paths during AGENTS.md discovery (#26465)
    ## Intent
    
    Follow up on #26205 by avoiding unnecessary filesystem canonicalization
    during `AGENTS.md` discovery. The configured working directory is
    already absolute, and canonicalization incorrectly switches symlinked
    workspaces from their logical parent hierarchy to the target's
    hierarchy.
    
    ## User-facing behavior
    
    For a symlinked working directory such as:
    
    ```text
    test-root/
    |-- logical-repo/
    |   |-- AGENTS.md              ("logical parent doc")
    |   `-- workspace ------------> physical-repo/workspace/
    `-- physical-repo/
        |-- AGENTS.md              ("physical parent doc")
        `-- workspace/
            `-- AGENTS.md          ("workspace doc")
    ```
    
    Before this change, Codex canonicalized `logical-repo/workspace` to
    `physical-repo/workspace` before discovery. It therefore loaded
    `physical-repo/AGENTS.md` and `physical-repo/workspace/AGENTS.md`,
    ignoring the instructions from the repository through which the user
    entered the workspace.
    
    After this change, ancestor discovery walks the configured logical path,
    so Codex loads `logical-repo/AGENTS.md`. Opening
    `logical-repo/workspace/AGENTS.md` still follows the symlink through the
    host filesystem, so the workspace document is also loaded.
    `physical-repo/AGENTS.md` is not loaded.
    
    ## Implementation
    
    Use the logical absolute working directory when discovering project
    instructions and reporting instruction sources. Filesystem reads still
    follow the working-directory symlink, so an `AGENTS.md` in the target
    workspace continues to load while ancestor discovery uses the symlink's
    parents.
    
    ## Validation
    
    Added integration coverage proving that discovery loads the logical
    parent's instructions and the target workspace's instructions, but not
    the target parent's instructions.
  • [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.
  • Remove response.processed websocket request (#26447)
    ## Why
    
    The Responses websocket client no longer needs to send a follow-up
    `response.processed` request after a turn response has already been
    recorded. Keeping that extra acknowledgement path adds feature-gated
    control flow and a second websocket request shape that no longer carries
    useful behavior.
    
    ## What Changed
    
    - Removed the `response.processed` websocket request type and sender.
    - Removed the `responses_websocket_response_processed` feature flag and
    schema entry.
    - Removed turn and remote-compaction plumbing that only tracked response
    IDs to send the acknowledgement.
    - Removed tests that existed solely to cover the deleted feature path.
    
    ## Validation
    
    - `just fix -p codex-core -p codex-api -p codex-features`
  • Route AGENTS.md loading through environment filesystems (#26205)
    ## Why
    
    Workspace-specific `AGENTS.md` loading needs to use the selected
    environment filesystem so remote workspaces and child agents read
    instructions from their actual environment instead of the host
    filesystem. The app-server should report the same instruction sources
    the initialized thread actually loaded, rather than independently
    rescanning configuration and filesystem state.
    
    ## What changed
    
    - Introduce `LoadedAgentsMd` to retain ordered user, project, and
    internal instructions with their provenance.
    - Load and canonicalize workspace `AGENTS.md` paths through the primary
    `EnvironmentManager` environment, then render the loaded instructions
    when constructing turn context.
    - Expose cached loaded instruction sources from initialized threads and
    use them for app-server start, resume, and fork responses.
    - Preserve global `CODEX_HOME` loading and separator behavior while
    excluding empty project files that did not supply model-visible
    instructions.
    - Add integration coverage for CLI injection, selected-environment
    provenance and rendering, empty environment selection, and cached
    sources on loaded-thread resume.
    
    ## Validation
    
    - `just test -p codex-core agents_md`
    - `just test -p codex-core
    selected_environment_sources_match_model_visible_instructions`
    - `just test -p codex-exec agents_md`
    - `just test -p codex-app-server instruction_sources`
    - `just test -p codex-app-server --status-level fail`
  • core: allow excluding tool namespaces from code mode (#26320)
    ## Why
    
    Research and training setups need to control which tool namespaces
    appear inside code mode's nested `tools` surface without disabling those
    tools entirely. This makes it possible to train against a deliberately
    reduced nested-tool setup while preserving the normal direct and
    deferred tool paths.
    
    ## What
    
    - Extend `features.code_mode` to accept structured configuration while
    preserving the existing boolean syntax.
    - Add an exact `excluded_tool_namespaces` list under
    `[features.code_mode]`:
    
      ```toml
      [features.code_mode]
      enabled = true
      excluded_tool_namespaces = ["mcp__codex_apps", "multi_agent_v1"]
      ```
    
    - Filter matching canonical `ToolName` namespaces when constructing code
    mode's nested router and code-mode-specific direct tool descriptions.
    - Keep excluded tools registered, directly exposed in mixed code mode,
    and discoverable through top-level `tool_search` when otherwise
    eligible.
    - Derive deferred nested-tool guidance after namespace filtering so the
    `exec` description does not advertise excluded-only deferred tools.
    - Preserve the boolean/table representation when materializing config
    locks and update the generated config schema.
    
    ## Testing
    
    - `just test -p codex-features`
    - `just test -p codex-config`
    - `just test -p codex-core load_config_resolves_code_mode_config`
    - `just test -p codex-core
    lock_contains_prompts_and_materializes_features`
    - `just test -p codex-core
    excluded_deferred_namespaces_do_not_enable_nested_tool_guidance`
    - `just test -p codex-core
    code_mode_excludes_configured_nested_tool_namespaces`
    - `cargo check -p codex-thread-manager-sample`
  • [codex-analytics] emit forked thread id on initialization (#26248)
    ## Why
    - Thread initialization analytics do not identify the source thread for
    forked threads.
    - The session viewer needs this lineage to construct thread trees.
    - Depends on openai/openai#987854. Do not release this change before
    that backend schema change is deployed.
    
    ## What Changed
    - Adds optional `forked_from_thread_id` to `codex_thread_initialized`.
    - Populates it from the existing thread fork lineage for app-server and
    in-process subagent initialization paths.
    - Keeps it null for non-forked threads.
    
    ## Verification
    - `just fmt`
    - `just test -p codex-analytics`
    - `just test -p codex-app-server
    thread_fork_tracks_thread_initialized_analytics`
  • Add saved image path hint to standalone image generation (#25947)
    ## Why
    
    Standalone image generation returns image bytes to the model, but the
    model also needs the host artifact path to reference the generated file
    in follow-up work.
    
    ## What changed
    
    - Append the default saved-image path hint alongside the generated image
    tool output.
    - Reuse the existing core image-generation hint text.
    - Pass the thread ID and Codex home directory needed to compute the
    artifact path.
    - Add app-server and extension coverage for the model-visible hint.
    
    ## Validation
    
    - `just fmt`
    - `just bazel-lock-check`
    - `just test -p codex-app-server
    standalone_image_generation_returns_saved_path_hint_to_model`
  • Bridge host-loaded skills into the skills extension (#26172)
    ## Why
    
    The skills extension needs to become the path that exposes local host
    skills without losing the behavior already owned by core skill loading.
    Host skill discovery is not just `$CODEX_HOME/skills`: it also includes
    config layers, bundled-skill settings, plugin roots, runtime extra
    roots, and the filesystem for the selected primary environment.
    
    Rather than making the extension reload host skills and risk drifting
    from that authoritative load, this PR bridges the already-loaded
    per-turn skills outcome into the extension. That lets the extension
    advertise host skills and inject explicit `$skill` prompts while
    preserving the same roots, disabled/hidden state, rendered paths, and
    environment-backed file reads that the legacy path uses.
    
    ## What Changed
    
    - Adds `HostLoadedSkills` in `core-skills` to wrap the turn's
    `SkillLoadOutcome` and read `SKILL.md` through the filesystem that
    loaded that skill.
    - Stores `HostLoadedSkills` in turn extension data for normal turns and
    review turns, so the skills extension can consume the loaded host
    catalog without reloading it.
    - Adds `HostSkillProvider` under `ext/skills/src/provider/host.rs`,
    mapping host-loaded skill metadata into the skills-extension
    catalog/read contract.
    - Registers the host provider by default from
    `codex_skills_extension::install()`.
    - Preserves host skill metadata such as dependencies, disabled state,
    hidden-from-prompt policy, and slash-normalized display paths.
    - Passes host-loaded skills through `SkillListQuery` and
    `SkillReadRequest` so explicit skill invocation reads only resources
    from the loaded host catalog.
    - Adds integration coverage for a real legacy
    `$CODEX_HOME/skills/.../SKILL.md` skill being listed and injected
    through the installed extension.
    
    ## Testing
    
    - Added `installed_extension_loads_host_skills_from_legacy_roots` in
    `ext/skills/tests/skills_extension.rs`.
    - `just test -p codex-skills-extension`
  • Gate automatic idle turns in Plan mode (#26147)
    ## Why
    
    Goal idle continuation is extension-triggered model-visible work, so it
    should follow one core-owned rule for when automatic work may start. In
    particular, it should not jump ahead of queued user/client work, start
    while another task is active, or inject a continuation turn while the
    thread is in Plan mode.
    
    Keeping this policy in `try_start_turn_if_idle` avoids passing
    `collaboration_mode` or review-specific state through
    `ThreadLifecycleContributor::on_thread_idle`. Active `/review` is
    covered by the same active-task gate because Review turns are not
    steerable.
    
    ## What Changed
    
    - Teach `Session::try_start_turn_if_idle` to reject automatic idle turns
    in Plan mode, both before reserving an idle turn and after building the
    turn context.
    - Document `CodexThread::try_start_turn_if_idle` as the extension-facing
    gate for automatic idle work, including Plan-mode and active Review-task
    behavior.
    - Add focused coverage for Plan-mode rejection and active Review-task
    rejection without queuing synthetic input.
    
    ## Testing
    
    - `just test -p codex-core try_start_turn_if_idle`
  • chore: calm down (#26367)
    Prompt update to address feedback
  • [codex-analytics] report compaction request token counts (#25946)
    ## Why
    
    Compaction analytics need token counts that better represent the request
    being compacted. The existing session snapshot can diverge from the
    actual remote compaction request after output rewriting, and remote v2
    can use server-side Responses usage when available.
    
    ## What changed
    
    - Add an optional `active_context_tokens_before` override to
    `CompactionAnalyticsAttempt::track(...)` for remote compaction when it
    has a better before-token value than the begin-time session snapshot.
    The local `/compact` path passes no override.
    - For remote v1 `responses_compact`, subtract the estimated token delta
    from pre-compaction output rewriting from the session snapshot, capped
    by locally-added tokens since the last successful API response.
    - For remote v2 `responses_compaction_v2`, use the same bounded
    output-rewrite fallback as remote v1, then overwrite
    `active_context_tokens_before` with server `token_usage.input_tokens`
    from the `response.completed` event when present.
    - Keep the existing v2 compaction-output validation while carrying the
    completed response token usage through `collect_compaction_output`.
    
    ## Verification
    
    - `just fmt`
    - `just test -p codex-core
    collect_compaction_output_accepts_additional_output_items`
    - `git diff --check`
  • cli: add package path from install context (#26189)
    ## Why
    
    Codex package installs include helper binaries in `codex-path`, such as
    the bundled `rg`. Package-layout launches should add that directory
    before user commands run, but standalone launches were missing it while
    npm launches only worked because `codex.js` had its own legacy `PATH`
    rewrite. That made npm and standalone package behavior diverge.
    
    Shell snapshot restoration can also reset `PATH` after runtime setup.
    Any package-owned `PATH` prepend has to be recorded as an explicit
    runtime override so shells, unified exec, and user-shell commands keep
    access to `codex-path` after a snapshot is sourced.
    
    ## Repro
    
    Before this change, a curl-installed package could contain `rg` under
    `codex-path` but still fail to put it on `PATH`:
    
    ```shell
    mkdir /tmp/test-codex-curl
    curl -fsSL https://chatgpt.com/codex/install.sh \
      | CODEX_HOME=/tmp/test-codex-curl CODEX_NON_INTERACTIVE=1 sh
    /tmp/test-codex-curl/packages/standalone/current/bin/codex exec \
      --skip-git-repo-check 'print `which -a rg`'
    find /tmp/test-codex-curl -name rg
    ```
    
    The `which -a rg` output omitted the packaged helper even though `find`
    showed it under
    `/tmp/test-codex-curl/packages/standalone/releases/.../codex-path/rg`.
    
    The npm install path behaved differently only because
    `codex-cli/bin/codex.js` had legacy `PATH` rewriting:
    
    ```shell
    mkdir /tmp/test-codex-npm
    cd /tmp/test-codex-npm
    npm install @openai/codex
    ./node_modules/.bin/codex exec --skip-git-repo-check 'print `which -a rg`'
    ```
    
    That printed the npm package's `vendor/<target>/codex-path/rg` first.
    This PR moves that behavior into Rust-side package launch setup so
    curl/standalone and npm/bun launches agree without JS rewriting `PATH`.
    
    ## What Changed
    
    - `codex-rs/arg0` now uses
    `InstallContext::current().package_layout.path_dir` to prepend the
    package helper directory before any threads are created.
    - Package helper `PATH` setup is independent from the temporary arg0
    alias setup, so `codex-path` is still added even if CODEX_HOME tempdir,
    lock, or symlink setup fails.
    - `codex-rs/install-context` detects the canonical package layout we
    ship: `bin/`, `codex-resources/`, and `codex-path/` next to
    `codex-package.json`.
    - Shell, local unified exec, and user-shell runtimes now record package
    `codex-path` prepends in `explicit_env_overrides`, matching the existing
    zsh-fork behavior so shell snapshots cannot restore over the package
    helper path.
    - Remote unified exec requests do not receive the local app-server
    package path overlay.
    - `codex-cli/bin/codex.js` no longer computes or overrides `PATH`; it
    only locates the native binary in the canonical package layout and
    passes npm/bun management metadata.
    - Added regression tests for `PATH` ordering, package layout detection,
    and shell snapshot preservation of package path prepends.
    
    ## Verification
    
    - `node --check codex-cli/bin/codex.js`
    - `just test -p codex-install-context -p codex-arg0`
    - `just test -p codex-core
    user_shell_snapshot_preserves_package_path_prepend`
    - `just test -p codex-core tools::runtimes::tests`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just fix -p codex-install-context -p codex-arg0 -p codex-core`
  • Rewrite oversized tool outputs during remote compaction (#26251)
    ## Why
    
    When trying to fit history under compaction limit rewrite output items
    instead of removing them entirely. Otherwise we're breaking
    incrementality in relation to the previous response.
  • feat: catalog multi-agent v2 config (#26254)
    ## Why
    
    Model metadata can now select multi-agent v2 even when a user has not
    enabled `features.multi_agent_v2` in their config. Some existing configs
    still set the legacy `agents.max_threads` knob for v1 multi-agent
    behavior, so treating every v2 runtime as incompatible with
    `agents.max_threads` would break users whose only v2 signal came from
    the model catalog.
    
    The incompatible configuration is specifically enabling
    `features.multi_agent_v2` while also setting `agents.max_threads`.
    Catalog-forced v2 should use the v2 concurrency setting and ignore the
    legacy v1 cap instead of rejecting the config.
    
    ## What changed
    
    - Split config validation from runtime concurrency calculation:
    `effective_agent_max_threads` now just returns the effective cap for the
    resolved multi-agent runtime.
    - Added explicit validation for `features.multi_agent_v2` +
    `agents.max_threads` at session startup.
    - Preserved catalog-selected v2 behavior when `features.multi_agent_v2`
    is disabled, so existing configs with `agents.max_threads` keep
    starting.
    - Updated model-runtime selector coverage so a catalog v2 model still
    exposes v2 tools even when `agents.max_threads` is set and the config
    flag is disabled.
    
    ## Validation
    
    - `cargo check -p codex-core --lib`
    - `just test -p codex-core --lib -E
    "test(multi_agent_v2_feature_rejects_agents_max_threads) |
    test(catalog_v2_allows_agents_max_threads_when_feature_disabled)"`
  • Expose local image paths to models (#25944)
    ## Why
    
    Local image attachments include image bytes, but the adjacent
    model-visible label omits the source path. Exposing the path lets
    model-selected workflows refer back to the intended local image
    explicitly.
    
    ## What changed
    
    - Include an escaped `path` attribute in model-visible local image
    opening tags.
    - Reuse the path-aware marker generator in rollout coverage.
    - Update protocol, replay, and rollout coverage for the new request
    shape.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-protocol`
    - `just test -p codex-core skips_local_image_label_text`
    - `just test -p codex-core
    copy_paste_local_image_persists_rollout_request_shape`
    - `git diff --check`
  • core: stop threading SandboxPolicy through exec (#25700)
    ## Why
    
    #25450 attempts a broad `SandboxPolicy` removal across several unrelated
    surfaces, which makes it hard to review and still leaves new helper code
    moving legacy policies around. This PR is a narrower alternative:
    migrate only the exec-side Windows sandbox plumbing so the review can
    focus on one production path and one compatibility boundary.
    
    The goal is to stop threading `SandboxPolicy` through exec code without
    expanding the migration into app-server, protocol, telemetry, config, or
    session behavior.
    
    ## What changed
    
    - Removed `ExecRequest::compatibility_sandbox_policy()`.
    - Changed the Windows restricted-token and elevated filesystem override
    helpers to accept `PermissionProfile` plus the split filesystem/network
    policies instead of a `SandboxPolicy`.
    - Kept the remaining legacy projection local to the writable-root
    comparison that still needs to compare split policy behavior against the
    legacy Windows backend model.
    - Rejected restricted split filesystem policies that still grant
    full-disk writes before using the Windows restricted-token backend,
    preserving the previous clear-failure behavior for profiles that project
    to `ExternalSandbox`.
    - Updated the Windows sandbox override tests to exercise the new call
    shape and cover the full-write split-profile regression.
    
    ## Verification
    
    - `just test -p codex-core windows_restricted_token`
    - `just test -p codex-core windows_elevated`
  • feat: guard git enrichment (#26175)
    Skip turn git metadata enrichment when a turn has remote or multiple
    executors, so we do not report the orchestrator checkout as executor
    workspace metadata.
    
    Test: `just test -p codex-core` (blocked by existing
    `Session::conversation_id` compile error in `close_agent.rs`).
  • nit: small prompt update for MAv2 (#26179)
    Simple prompt change for MAv2 because of OOD compared to CBv9
  • chore: mechanical rename (#26156)
    Rename `Session::conversation_id` to `Session::thread_id` with an auto
    refactor in RustRover
  • skills: resolve per-turn catalogs from turn input context (#26106)
    ## Why
    
    The skills extension needs the resolved turn environments to build a
    real per-turn `SkillListQuery`. The previous `TurnLifecycleContributor`
    hook only had a turn id, so it could only seed a placeholder query and
    never carry the executor authorities that executor-scoped skill routing
    will need.
    
    Moving catalog resolution onto `TurnInputContributor` puts the skills
    extension on the same turn-preparation path that already has the
    environment ids and working directories for the submitted turn, while
    keeping the actual prompt injection work for follow-up changes.
    
    ## What changed
    
    - switch `ext/skills` from `TurnLifecycleContributor` to
    `TurnInputContributor`
    - build `executor_authorities` from `TurnInputContext.environments` and
    pass them through `SkillListQuery`
    - keep storing the resolved catalog in `SkillsTurnState`, but drop the
    placeholder query helper that no longer matches the real data flow
    - update the extension TODOs to reflect that per-turn catalog resolution
    now happens in the turn-input contributor, and that prompt/context
    injection still needs to move later
    
    ## Testing
    
    - Not run locally.
  • Reject MAv2 close_agent self-targets (#26144)
    ## Why
    
    `close_agent` is a parent-owned coordination tool: a worker should
    return its result, then let its parent decide when to close it. Before
    this change, if an MAv2 worker targeted itself, the resolved target
    could flow through the normal close path and ask the agent control layer
    to close the current conversation.
    
    ## What changed
    
    - Reject `close_agent` when the resolved target is the current session's
    `conversation_id`, returning a model-visible error that tells the worker
    to return its result instead.
    - Keep the guard after target resolution so it covers both thread-id
    targets and task-path targets.
    - Add coverage for self-targeting by thread id and by task name in
    `multi_agents_tests.rs`.
    
    Relevant code:
    
    -
    [`handle_close_agent`](https://github.com/openai/codex/blob/7c24e6641b693a3eed933dd376ce8f424ab6ea5f/codex-rs/core/src/tools/handlers/multi_agents_v2/close_agent.rs#L39-L57)
    - [`multi_agent_v2_close_agent_rejects_self_target_by_id` /
    `multi_agent_v2_close_agent_rejects_self_target_by_task_name`](https://github.com/openai/codex/blob/7c24e6641b693a3eed933dd376ce8f424ab6ea5f/codex-rs/core/src/tools/handlers/multi_agents_tests.rs#L3936-L4070)
    
    ## Testing
    
    Not run locally.
  • chore: extract context fragments into dedicated crate (#26122)
    ## Why
    
    `codex-core` currently owns the generic contextual-fragment trait and
    several reusable fragment implementations. That makes it harder for
    other crates to share the same host-owned model-input abstraction
    without depending on all of `codex-core`.
    
    This change extracts the reusable fragment machinery into a small
    `codex-context-fragments` crate so future extension and skills work can
    depend on the fragment abstraction directly.
    
    ## What Changed
    
    - Added the `codex-context-fragments` crate with:
      - `ContextualUserFragment`
      - `FragmentRegistration` / `FragmentRegistrationProxy`
      - additional-context fragment types
    - Moved `SkillInstructions` into `codex-core-skills`, since
    skill-specific rendering belongs with skills rather than generic core
    context machinery.
    - Kept `codex-core` re-exporting the fragment types it still uses
    internally, so existing call sites keep the same shape.
    - Updated Cargo and Bazel workspace metadata for the new crate.
    
    ## Verification
    
    - `cargo metadata --locked --format-version 1 --no-deps`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
  • feat: add extension turn-input contributors (#25959)
    ## Disclaimer
    Do not use for now
    
    ## Why
    
    Extensions can already contribute prompt fragments and request same-turn
    item injection, but there was no host-owned hook for contributing
    structured `ResponseItem`s while Codex is assembling a new turn's
    initial model input. This change adds that seam so extensions can attach
    turn-local input that depends on the submitted user input and resolved
    turn environments without routing through prompt text or late injection.
    
    ## What changed
    
    - add `TurnInputContributor` to `codex_extension_api` and export the new
    `TurnInputContext` / `TurnInputEnvironment` types it receives
    - teach `ExtensionRegistry` to register and expose turn-input
    contributors alongside the existing extension hooks
    - call registered turn-input contributors from
    `core/src/session/turn.rs` while building the initial injected input for
    a turn, then append their returned `ResponseItem`s after the skill and
    plugin injections
  • config: express implicit sandbox defaults as permission profiles (#25926)
    ## Why
    
    `PermissionProfile` is becoming the default way to represent Codex
    permissions, but the implicit default behavior should stay the same for
    now:
    
    - trusted projects use `:workspace`
    - untrusted projects also use `:workspace`
    - roots without a trust decision use `:read-only`
    - unsandboxed Windows falls back to `:read-only`
    
    This keeps the existing sandbox semantics while making silent config
    defaults observable as built-in permission profiles instead of treating
    the legacy `SandboxPolicy` projection as the primary shape.
    
    ## What Changed
    
    - Refactored legacy sandbox derivation to resolve the configured sandbox
    mode once, then apply the implicit project fallback only when no sandbox
    mode was configured.
    - Preserved the existing trust-decision fallback: trusted and untrusted
    projects default to workspace-write where supported.
    - Added empty-config coverage asserting that an untrusted project
    resolves to the built-in active permission profile (`:workspace` outside
    unsandboxed Windows).
    
    ## Verification
    
    - `just fmt`
    - `just test -p codex-core 'config::'`
    - `just test -p codex-config`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/25926).
    * __->__ #25926
  • config: remove dead profile sandbox fallback (#25943)
    ## Why
    
    `profile_sandbox_mode` was left over from the old selected legacy
    profile path. Production now always derives permissions without that
    value, and legacy profile contents are ignored, so keeping a parameter
    that is always `None` makes `derive_permission_profile` look like it
    still supports a fallback that no longer exists.
    
    ## What Changed
    
    - Removed the `profile_sandbox_mode` argument from
    `ConfigToml::derive_permission_profile`.
    - Updated the production caller and legacy sandbox-policy test helper to
    match.
    - Dropped the stale unselected legacy-profile sandbox test that only
    protected the removed fallback shape.
    
    ## Verification
    
    - `just test -p codex-config`
    - `just test -p codex-core 'config::'`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/25943).
    * #25926
    * __->__ #25943
  • [codex] Keep hosted tools visible in code-only mode (#25890)
    ## Why
    
    `code_mode_only` moved ordinary runtime tools behind `exec`, but it also
    hid hosted Responses tools. Hosted `web_search` and `image_generation`
    do not have a nested `exec` runtime path, so code-only sessions lost
    those capabilities entirely even when their existing provider, auth,
    model, and configuration gates passed.
    
    ## What changed
    
    - Keep hosted Responses tools top-level in `code_mode_only` sessions
    after their existing gates pass.
    - Preserve the existing nested-tool behavior for ordinary runtimes and
    the direct-only behavior for multi-agent v2 tools.
    - Add planner coverage for `code_mode_only` with default multi-agent v2
    settings, hosted live web search, and hosted image generation.
    
    ## Verification
    
    - Added focused regression coverage in
    `codex-rs/core/src/tools/spec_plan_tests.rs`.
    - Left execution to CI per repository workflow.
  • core: stop passing legacy SandboxPolicy to guardian reviews (#25911)
    ## Why
    
    Guardian review turns already submit a read-only `PermissionProfile`,
    which is the permissions model the runtime should honor. Passing the
    equivalent legacy `SandboxPolicy` through `ThreadSettingsOverrides`
    keeps two representations of the same read-only constraint alive on this
    path and makes the guardian flow depend on compatibility plumbing that
    is being phased out.
    
    ## What Changed
    
    - Set `sandbox_policy` to `None` when the guardian review session
    submits its child `Op::UserInput`.
    - Keep `permission_profile: Some(PermissionProfile::read_only())` and
    `approval_policy: Some(AskForApproval::Never)`, so the guardian review
    remains read-only and cannot request approvals.
    - Remove the now-unused `SandboxPolicy` import and redundant comment
    from `codex-rs/core/src/guardian/review_session.rs`.
    
    ## Verification
    
    Not run locally; this is a narrow cleanup of redundant thread-settings
    override state.
  • Switch runtime to cloud config bundle (#24622)
    ## Summary
    
    - Adapts the moved `codex-cloud-config` crate from the legacy cloud
    requirements endpoint to the new config bundle endpoint.
    - Switches runtime consumers from `CloudRequirementsLoader` to
    `CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered
    config and requirements.
    - Removes the legacy cloud requirements domain loader path.
    
    ## Details
    
    This intentionally keeps `codex-cloud-config` monolithic for review
    lineage: the previous PR establishes the crate move, and this PR shows
    the behavior change against that moved implementation. A follow-up PR
    splits the module back into focused files.
    
    The new bundle path preserves the important cloud requirements loader
    semantics where intended: account-scoped signed cache, 30 minute TTL, 5
    minute refresh cadence, retry/backoff, auth recovery, and fail-closed
    startup loading. The cached payload changes from a single requirements
    TOML string to the backend-delivered bundle, and validation rejects
    malformed config or requirements fragments before cache write/use.
  • Populate workspace kind on Codex turn events (#25135)
    ## Summary
    - carry `workspace_kind` from Responses API client metadata into the
    turn resolved analytics fact
    - serialize the optional value on `codex_turn_event`
    - cover both the turn metadata source and turn event serialization
    
    The `workspace_kind` tells us whether a thread had a project attached vs
    projectless. this is an indicator for who is adopting Codex for
    knowledge work outside of coding
    
    ## Testing
    - `env UV_CACHE_DIR=/private/tmp/uv-cache
    /private/tmp/cargo-tools/bin/just fmt`
    - `env PATH=/private/tmp/cargo-tools/bin:$PATH
    CARGO_HOME=/private/tmp/cargo-home UV_CACHE_DIR=/private/tmp/uv-cache
    /private/tmp/cargo-tools/bin/just test -p codex-analytics`
    - `env PATH=/private/tmp/cargo-tools/bin:$PATH
    CARGO_HOME=/private/tmp/cargo-home UV_CACHE_DIR=/private/tmp/uv-cache
    /private/tmp/cargo-tools/bin/just test -p codex-core turn_metadata`
    
    Paired with openai/openai#970661, which keeps forwarding the same
    metadata key through Responses API headers.
  • 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`.
  • Route standalone image generation through host finalization md (#25176)
    ## Why
    
    Standalone image-generation extensions emitted turn items through the
    low-level event path, bypassing host-owned finalization such as image
    persistence and contributor processing. At the same time, the
    generated-image save-path hint must remain visible to the model through
    the extension tool's `FunctionCallOutput`, rather than the legacy
    built-in developer-message path.
    
    ## What changed
    
    - Extended `ExtensionTurnItem` to support image-generation items while
    keeping the extension-facing emitter API limited to `emit_started` and
    `emit_completed`.
    - Routed extension completion through core `finalize_turn_item`, so
    standalone image-generation items receive host-owned processing and
    persisted `saved_path` values before publication.
    - Kept legacy built-in image generation on its existing
    developer-message hint path, while standalone image generation returns
    its deterministic saved-path hint in `FunctionCallOutput`.
    - Shared the image artifact path and output-hint formatting used by core
    and the image-generation extension.
    - Passed thread identity through extension tool calls so standalone
    image generation can construct the same intended artifact path as core.
    - Added an app-server integration test covering real standalone image
    generation, saved artifact publication, model-visible output hint
    wiring, and absence of the legacy developer-message hint.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-image-generation-extension`
    - `just test -p codex-web-search-extension`
    - `just test -p codex-goal-extension`
    - `just test -p codex-memories-extension`
    - Targeted `codex-core` tests for image save history, extension
    completion finalization, and contributor execution
    - `just test -p codex-app-server
    standalone_image_generation_returns_saved_path_hint_to_model`
    - `just fix -p codex-core`
    - `just fix -p codex-image-generation-extension`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
  • Add environmentId to request_permissions (#25858)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. This PR (#25858) - Add `environmentId` to `request_permissions`: lets
    the model target a selected environment and resolves relative permission
    paths against it.
    3. #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 #25850; #25862 and #25867 are stacked on this PR.
    
    ## Why
    
    PR1 made request-permission grants internally environment-keyed, but the
    model-facing `request_permissions` tool could still only target the
    primary environment. For CCA and multi-environment turns, the tool needs
    an explicit way to bind a permission request to a selected attached
    environment before resolving relative paths.
    
    ## What Changed
    
    - Added optional `environmentId` to `RequestPermissionsArgs`, with
    `environment_id` accepted as an alias.
    - Exposed `environmentId` in the `request_permissions` tool schema and
    description.
    - Resolve the selected environment before parsing filesystem permission
    paths, so relative paths bind to the selected environment cwd.
    - Route validated tool calls through
    `request_permissions_for_environment` directly instead of duplicating
    environment lookup in `Session::request_permissions`.
    - Reject unknown environment ids with a model-facing error.
    - Updated focused request-permissions and Guardian call sites for the
    new optional field.
    
    ## Testing
    
    Not run locally per instruction.