Commit Graph

141 Commits

  • [codex] Use input items for Responses Lite tools (#27946)
    When using Responses Lite, we should all use `additional_tools` and a
    developer item instead of the top level tools array & instructions
    field. This keeps things 1-to-1.
    
    Forced namespacing for _all_ tools will land in a following PR after
    some coordination & fixes in Responses API (around collisions & return
    items).
    
    The goal is to eventually expand the scope of this to _all_ requests
    from codex, but that will require larger coordination across providers &
    slower rollout.
  • mcp: accept foreign absolute cwd for remote stdio (#29493)
    ## Why
    
    Remote stdio MCP servers can run in an environment whose path convention
    differs from the Codex host. A Windows cwd such as
    `C:\Users\openai\share` is absolute for the executor but was rejected by
    a POSIX orchestrator.
    
    Built on #29501, now merged, which only clarifies the host-native
    `PathUri` constructor name.
    
    ## What changed
    
    - Deserialize MCP cwd values as `LegacyAppPathString` so config does not
    apply host path rules.
    - Interpret that spelling as host-native for local launches and convert
    it to `PathUri` at executor launch.
    - Skip host filesystem and command resolution checks for remote stdio in
    `codex doctor`.
    - Add host-independent config and executor-boundary coverage using the
    foreign path convention for each test platform.
    
    ## Validation
    
    - `just test -p codex-utils-path-uri -p codex-config -p codex-mcp -p
    codex-rmcp-client` (408 passed)
    - `just test -p codex-cli -p codex-rmcp-client` (372 passed)
    - `cargo check --workspace --tests`
    - `just test` (11,311 passed; 43 unrelated environment/timing failures)
    - `just fix -p codex-cli -p codex-config -p codex-core -p codex-mcp -p
    codex-mcp-extension -p codex-rmcp-client -p codex-tui`
  • core: rename metadata -> internal_chat_message_metadata_passthrough (#28968)
    ## Description
    This PR cuts Codex over from generic `ResponseItem.metadata` (introduced
    here: https://github.com/openai/codex/pull/28355) to
    `ResponseItem.internal_chat_message_metadata_passthrough`, which is the
    blessed path and has strongly-typed keys.
    
    For now we have to drop this MAv2 usage of `metadata`:
    https://github.com/openai/codex/pull/28561 until we figure out where
    that should live.
  • [codex] Preserve skill descriptions outside model context (#29006)
    ## Why
    
    Skill descriptions are used in model-visible lists: the default
    available-skills catalog that supports implicit selection, and the
    on-demand `skills.list` tool response used to discover orchestrator
    skills. A single overlong description should not consume a
    disproportionate share of either list.
    
    Enforcing the 1024-character limit while loading or migrating skills is
    the wrong boundary: it rejects otherwise-valid skills and discards
    metadata that non-model consumers and full skill reads may need. Skill
    metadata and `SKILL.md` content should remain intact; the cap belongs at
    model-visible list rendering boundaries.
    
    ## What changed
    
    - Preserve full `description` and `metadata.short-description` values
    when loading skills.
    - Preserve full external-agent command descriptions during
    `source-command-*` migration instead of skipping commands solely because
    their descriptions exceed 1024 characters.
    - Preserve full normalized orchestrator descriptions in the underlying
    skills catalog.
    - Cap each description at 1024 Unicode characters when rendering the
    default available-skills context in `codex-core-skills` and
    `codex-skills-extension`.
    - Apply the same cap when serializing descriptions in the model-visible
    `skills.list` response.
    - Render truncated descriptions as 1021 original characters plus `...`.
    - Leave explicit `$skill` injection, `skills.read`, underlying metadata,
    and on-disk `SKILL.md` files unchanged and full-fidelity.
    
    ## Implicit skill selection
    
    Codex injects a bounded catalog containing each implicitly allowed
    skill's name, description, and source locator, together with
    instructions to use a skill when the task clearly matches its
    description. The model makes that semantic choice; after selecting a
    skill, it reads the full `SKILL.md` from its filesystem or provider
    resource. Explicit `$skill` mentions remain a separate path that injects
    the full skill instructions. For orchestrator skills, `skills.list`
    provides bounded discovery metadata before `skills.read` returns the
    full selected resource.
    
    ## Test plan
    
    - `just test -p codex-core-skills`
    - `just test -p codex-skills-extension`
    - `just test -p codex-external-agent-migration`
    
    The focused regressions verify that overlong metadata is preserved at
    load and migration boundaries while default available-skills rendering
    and `skills.list` output produce the 1021-character prefix plus `...`.
  • Add config toggles for orchestrator skills and MCP (#28942)
    ## Why
    
    Orchestrator-provided skills and Codex Apps MCP tools add model-visible
    instructions, resources, and tools beyond the local workspace. Hosts
    need config-level switches to disable those orchestrator-owned surfaces
    independently, without disabling regular skills or regular MCP servers.
    
    ## What changed
    
    - Adds `[orchestrator.skills].enabled` and `[orchestrator.mcp].enabled`
    config entries, both defaulting to `true`.
    - Includes the new settings in `config.schema.json` and in the config
    lock so resolved thread configuration preserves the same orchestrator
    exposure decisions.
    - Threads `orchestrator.skills.enabled` through the app-server skills
    extension so disabled orchestrator skills do not expose the `skills`
    namespace or inject orchestrator skill context.
    - Gates Codex Apps MCP exposure, app instructions, and app auth
    eligibility on `orchestrator.mcp.enabled` while leaving non-Codex-Apps
    MCP tools available.
    - Updates the thread-manager sample config to disable both
    orchestrator-owned surfaces.
    
    ## Verification
    
    - Added config parsing, loading, defaulting, and schema coverage for the
    new settings.
    - Added MCP exposure coverage that `orchestrator.mcp.enabled = false`
    removes Codex Apps tools while preserving regular MCP tools.
    - Added app-server coverage that `orchestrator.skills.enabled = false`
    prevents orchestrator skill tools, prompts, and resource reads from
    reaching the model turn.
  • Add indexed web search mode (#28489)
    ## Summary
    
    - Add `web_search = "indexed"` alongside `disabled`, `cached`, and
    `live`.
    - Use that same resolved mode for both hosted and standalone web search.
    - For hosted search, send `index_gated_web_access: true` with external
    web access enabled only when `indexed` is selected.
    - For standalone search, preserve the existing boolean wire values for
    existing modes (`cached` maps to `false` and `live` to `true`) and send
    `"indexed"` only for `indexed`; `disabled` keeps the tool unavailable.
    - Carry the mode through managed configuration requirements and
    generated schemas.
    
    ## Why
    
    Indexed search provides a middle ground between cached-only search and
    unrestricted live page fetching. Search queries can remain live while
    direct page fetches are limited to URLs admitted by the server.
    
    The existing `web_search` setting remains the single source of truth, so
    hosted and standalone executors cannot drift into different access
    modes. Without an explicit `indexed` selection, the existing
    model-visible tool and request shapes are unchanged.
    
    ```toml
    web_search = "indexed"
    
    [features]
    standalone_web_search = true
    ```
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-api` (`126 passed`)
    - `just test -p codex-web-search-extension` (`7 passed`)
    - `just test -p codex-core
    code_mode_can_call_indexed_standalone_web_search` (`1 passed`)
    - Focused configuration, hosted request, standalone request, and
    managed-requirement coverage is included in the PR; remaining suites run
    in CI.
    
    The full workspace test suite was not run locally.
  • [codex] Assign response item IDs when recording history (#28814)
    ## Why
    
    Client-created response items enter history without IDs, so their
    identity is lost across rollout persistence and resume. IDs should be
    assigned once at the history-recording boundary, while IDs returned by
    the server must remain unchanged.
    
    The Responses API validates item IDs using type-specific prefixes.
    Locally generated IDs therefore use the matching prefix plus a
    hyphenated UUIDv7, keeping them valid while distinguishable from
    server-generated IDs. Because this changes persisted history and
    provider request shapes, the behavior is opt-in behind the
    under-development `item_ids` feature. Compaction triggers remain request
    controls whose API shape does not accept an ID.
    
    ## What changed
    
    - Register the disabled-by-default `item_ids` feature and expose it in
    `config.schema.json`.
    - Make supported optional `ResponseItem` IDs serializable and expose
    them in the generated app-server schemas.
    - When `item_ids` is enabled, assign an ID during conversation-history
    preparation if an item has no ID.
    - Generate type-prefixed, hyphenated UUIDv7 IDs using the Responses API
    item conventions.
    - Preserve existing server IDs without rewriting them.
    - Persist assigned IDs in rollouts and include them in subsequent
    Responses requests.
    - Remove the unsupported ID field from `CompactionTrigger` and document
    why it has no ID.
    - Add integration coverage for enabled ID persistence, preservation of
    server IDs, and omission of generated IDs while the feature is disabled.
    
    `prepare_conversation_items_for_history` is the single response-item ID
    allocation boundary.
    
    ## Test plan
    
    - `just test -p codex-features`
    - `just test -p codex-core
    response_item_ids_persist_across_resume_and_preserve_server_ids`
    - `just test -p codex-core
    non_openai_responses_requests_omit_item_turn_metadata`
    - `just test -p codex-core
    resize_all_images_prepares_failures_before_history_insertion`
    - `just test -p codex-protocol`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-api azure_default_store_attaches_ids_and_headers`
  • [codex] Reuse parsed plugin skills during session startup (#28844)
    ## Summary
    
    - Preserve raw plugin skill-root snapshots in the matching loaded-plugin
    cache entry, keyed by the effective plugin root identity including
    namespace.
    - Pass those snapshots through `SkillsLoadInput` as an optional preload,
    so session startup reuses plugin parsing while ordinary skill loads pass
    `None`.
    - Keep plugin skill loading cohesive: the existing loaders accept the
    optional snapshots directly, and uncached or marketplace-detail paths do
    not create a cache.
    
    ## Why
    
    Plugin discovery already parses plugin skills to determine available
    capabilities. Cold session startup then scanned and parsed the same
    roots again while building the skills snapshot.
    
    This solves the same duplicate-work problem as #28623 while keeping
    ownership narrow: `PluginsManager` creates and owns
    `PluginSkillSnapshots` only for its loaded-plugin cache entry;
    `SkillsService` consumes an optional clone. Entry replacement or
    clearing naturally drops the snapshots, with no separate generation,
    capacity policy, or watcher coupling.
    
    ## Validation
    
    - `cargo clippy -p codex-core-skills --all-targets -- -D warnings`
    - `just test -p codex-core-plugins
    skills_service_reuses_skills_parsed_during_plugin_load`
    - `just test -p codex-core-skills
    namespaces_plugin_skills_using_provided_namespace`
    - `just fmt`
  • Fix goal-first live threads missing from thread/list (#28808)
    Fixes #28263.
    
    ## Why
    
    When a thread starts with `/goal`, the goal extension can update SQLite
    goal state before the thread has any user-turn rollout items.
    `thread/list` and `thread/search` rely on persisted listing metadata, so
    a goal-first live thread could be absent from app-server listings after
    restart even though the goal itself existed.
    
    This regressed when goal handling moved out of core: the core path wrote
    the goal update through the live thread rollout path, while the
    extension-backed app-server path only updated goal state and emitted the
    live notification.
    
    ## What
    
    - Add `GoalSetOutcome::thread_goal_updated_item()` so the goal extension
    owns the canonical `ThreadGoalUpdated` rollout item shape.
    - Expose a narrow `CodexThread::append_rollout_items()` helper that
    appends through the live thread and keeps derived SQLite metadata in
    sync.
    - When app-server sets a goal on an active live thread, persist the goal
    update through that live-thread path.
    - Add an app-server regression test that starts a live thread with
    `thread/goal/set` and verifies it appears in state-DB-only
    `thread/list`.
    
    ## Verification
    
    - `env -u CODEX_SQLITE_HOME just test -p codex-app-server
    goal_first_live_thread_appears_in_state_db_thread_list`
  • Add turn-scoped context contributions (#28911)
    ## Summary
    - keep context injection on a single ContextContributor trait
    - split context injection into thread-scoped and turn-scoped
    contribution methods
    - wire turn-scoped fragments into initial context assembly so extensions
    can contribute context from turn-local state
  • [codex] Pass plugin namespace into skill loading (#28608)
    ## What changed
    
    - retain the parsed plugin manifest namespace on loaded plugins
    - carry that namespace through `PluginSkillRoot` and `SkillRoot`
    - use the provided namespace when qualifying plugin skill names
    - include the namespace in the skills cache key
    
    ## Why
    
    Plugin loading has already parsed `plugin.json`, but skill parsing
    currently walks every `SKILL.md` ancestor and probes/reads the manifest
    again to reconstruct the same namespace. Passing the parsed namespace
    removes those repeated filesystem calls, which are particularly costly
    on remote filesystems.
    
    Context:
    https://openai.slack.com/archives/C0ARA9GF5D4/p1781639496496439?thread_ts=1781202444.891669&cid=C0ARA9GF5D4
    
    ## Impact
    
    Plugin skill names remain unchanged. A regression test uses a
    deliberately different on-disk manifest name to verify that plugin roots
    use the provided parsed namespace.
    
    ## Validation
    
    - `just test -p codex-core-skills -p codex-core-plugins -p codex-plugin
    -p codex-utils-plugins` (352 passed)
    - `just fix -p codex-core-skills -p codex-core-plugins -p codex-plugin
    -p codex-utils-plugins`
    - `just fmt`
  • [codex] Support plugin manifest path lists (#28790)
    ## Summary
    
    Allow plugin manifests to declare `skills` as either a single path
    string or an array of path strings in the core plugin loader.
    
    ## Why
    
    Some plugin packages need to expose skills from more than one directory.
    Before this change, `plugin.json` only accepted a single string for
    `skills`, so manifests like this were ignored as an invalid `skills`
    shape:
    
    ```json
    {
      "skills": ["./skills/abc", "./skills/edk"]
    }
    ```
    
    This keeps the existing single-string form working while adding support
    for the list form. The final scope is intentionally limited to the core
    plugin manifest/load path for `skills`; `apps`, file-backed
    `mcpServers`, and the bundled plugin-creator assets are unchanged in
    this PR.
    
    ## What changed
    
    - Parse `skills` as either a string or an array of strings in
    `plugin.json`.
    - Store resolved skill paths as a list in `PluginManifestPaths`.
    - Load manifest-declared skill roots in addition to the default
    `./skills` root.
    - Deduplicate exact duplicate skill roots before loading.
    - Rely on existing skill-loader dedupe by canonical `SKILL.md` path for
    overlapping roots such as `./skills` plus `./skills/abc`.
    - Update plugin manifest tests to cover:
      - single string `skills`
      - list of string `skills`
      - duplicate skill roots
      - `./skills` as a manifest path
      - explicit child roots like `./skills/abc` and `./skills/edk`
      - overlapping-root dedupe
    
    ## Validation
    
    - `just test -p codex-plugin`
    - `just test -p codex-core-plugins`
    - `just test -p codex-mcp-extension`
    - `git diff --check`
  • [codex] Add optional IDs to response items (#28812)
    ## Why
    
    `ResponseItem` variants do not have a consistent internal ID shape: some
    variants carry required IDs, some carry optional IDs, and some cannot
    represent an ID at all. The existing fields also use inconsistent serde,
    TypeScript, and JSON-schema annotations. A single enum-level access path
    is needed before history recording can assign and retain IDs.
    
    This PR establishes that internal model only. It intentionally does not
    generate or serialize IDs; allocation and wire persistence are isolated
    in the stacked follow-up.
    
    ## What changed
    
    - Give every concrete `ResponseItem` variant an `Option<String>` ID
    field.
    - Apply the same internal-only annotations to every ID field:
    `#[serde(default, skip_serializing)]`, `#[ts(skip)]`, and
    `#[schemars(skip)]`.
    - Add `ResponseItem::id()` and `ResponseItem::set_id()` as the shared
    accessors.
    - Preserve IDs when history items are rewritten for truncation.
    - Adapt consumers that previously assumed reasoning and image-generation
    IDs were required.
    - Regenerate app-server schemas so the hidden fields are represented
    consistently.
    
    The serde catch-all `ResponseItem::Other` remains ID-less because it
    must remain a unit variant.
    
    ## Test plan
    
    - `cargo check --tests -p codex-core -p codex-api -p codex-rollout-trace
    -p codex-image-generation-extension`
    - `just test -p codex-protocol`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-api -p codex-rollout-trace -p
    codex-image-generation-extension`
    - `just test -p codex-core event_mapping`
  • Replace SkillsManager with SkillsService (#28705)
    ## Why
    
    Host skill discovery was still exposed as a manager even though it is a
    process-owned service shared by sessions, the app-server catalog, and
    file-watcher invalidation. The skills extension also consumed an ad hoc
    loaded-skills wrapper instead of a named immutable snapshot.
    
    ## What changed
    
    - replace `SkillsManager` with concrete `SkillsService`
    - make the service cache and return immutable `HostSkillsSnapshot`
    values
    - migrate the skills extension host provider to the snapshot boundary
    - migrate app-server catalog, watcher, and invalidation paths to the
    service
    
    This keeps the service limited to host discovery, caching, roots, and
    invalidation. Catalog rendering and invocation remain extension
    responsibilities for the next stacked change.
  • [codex] Support object-valued plugin MCP manifests (#28580)
    ## Summary
    This fixes plugin manifest parsing for MCP servers declared as an object
    directly in `plugin.json`.
    
    Before this change, Codex modeled `mcpServers` as only a string path,
    for example:
    
    ```json
    {
      "name": "counter-sample",
      "version": "1.1.1",
      "mcpServers": "./.mcp.json"
    }
    ```
    
    Some migrated plugins instead provide the server map directly in the
    manifest:
    
    ```json
    {
      "name": "counter-sample",
      "version": "1.1.1",
      "description": "Plugin that declares MCP servers in the manifest",
      "mcpServers": {
        "counter": {
          "type": "http",
          "url": "https://sample.example/counter/mcp"
        }
      }
    }
    ```
    
    That object form previously failed during install/load with an error
    like:
    
    ```text
    failed to parse plugin manifest: invalid type: map, expected a string
    ```
    
    ## What changed
    - Add a manifest representation for `mcpServers` as either
    `Path(Resource)` or `Object(map)`.
    - Parse `plugin.json` `mcpServers` as either a string path or an object.
    - Route object-valued MCP server maps through the existing plugin MCP
    config parser instead of adding a second parser.
    - Apply existing per-plugin MCP server policy to object-valued MCP
    servers the same way as file-backed MCP servers.
    - Include object-valued MCP server names in plugin telemetry/capability
    metadata.
    - Support object-valued MCP config for executor plugins without
    requiring a `.mcp.json` filesystem read.
    - Update the bundled plugin-creator validator and `plugin-json-spec.md`
    so generated-plugin validation accepts the same object-valued shape.
    
    ## Compatibility
    Existing plugin manifests that use `"mcpServers": "./.mcp.json"`
    continue to work. Plugins can now also use the object shape shown above.
    
    ## Tests
    Added coverage for the new manifest attribute shape at the install,
    normal load, telemetry, and executor-provider layers:
    
    - `install_accepts_manifest_mcp_server_objects`
    - `load_plugins_loads_manifest_mcp_server_objects`
    - `plugin_telemetry_metadata_uses_manifest_mcp_server_objects`
    - `reads_manifest_object_config_without_executor_file_system_access`
    
    Also smoke-tested the plugin-creator validator against both supported
    forms:
    
    - `mcpServers` as a direct object in `plugin.json`
    - `mcpServers` as `"./.mcp.json"` with a companion `.mcp.json`
    
    ## Validation
    - `just test -p codex-plugin`
    - `just test -p codex-core-plugins`
    - `just test -p codex-mcp-extension`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just fmt`
    - `git diff --check`
    - Focused rename/object-form rerun: `just test -p codex-core-plugins
    manager::tests::load_plugins_loads_manifest_mcp_server_objects
    manager::tests::plugin_telemetry_metadata_uses_manifest_mcp_server_objects
    store::tests::install_accepts_manifest_mcp_server_objects`
    - Focused executor rerun: `just test -p codex-mcp-extension
    executor_plugin::provider::tests::reads_manifest_object_config_without_executor_file_system_access`
    - `python3
    codex-rs/skills/src/assets/samples/plugin-creator/scripts/validate_plugin.py
    /private/tmp/codex-validator-object`
    - `python3
    codex-rs/skills/src/assets/samples/plugin-creator/scripts/validate_plugin.py
    /private/tmp/codex-validator-path`
  • [codex] exec-server: stream files in chunks (#28354)
    ## Why
    
    `fs/readFile` buffers the entire file in one response, which makes large
    remote reads expensive and prevents callers from applying backpressure.
    We need an opt-in streaming path with bounded block sizes while
    preserving the existing single-call API for small and sandboxed reads.
    
    ## What changed
    
    - Add `ExecServerClient::stream`, returning a named `FileReadStream`
    that implements `futures::Stream` and yields immutable 1 MiB byte
    blocks.
    - Add internal `fs/open`, `fs/readBlock`, and `fs/close` RPCs.
    `fs/readBlock` accepts an explicit offset and length.
    - Keep unsandboxed files open between block reads, cap open handles per
    connection, and clean them up on EOF, error, stream drop, explicit
    close, or connection shutdown.
    - Reject platform-sandboxed streaming opens instead of turning the
    one-shot sandbox helper into a persistent server. Existing `fs/readFile`
    behavior is unchanged.
    
    ## Testing
    
    - `just test -p codex-exec-server`
    - Integration coverage for 1 MiB chunking, exact block-boundary EOF,
    sandbox rejection, and continued reads from the opened file after path
    replacement.
    - Handle-manager coverage for non-sequential offsets, variable block
    lengths, the 128-handle limit, and capacity release after close.
  • feat: render typed envelopes for multi-agent v2 messages (#28368)
    ## Why
    
    Multi-agent v2 messages need a consistent, model-visible envelope that
    identifies what kind of interaction occurred, who sent it, and which
    agent it targets. Previously, encrypted deliveries exposed only
    `encrypted_content`, while child completion used the legacy
    `<subagent_notification>` shape. That meant the client could not
    consistently present `NEW_TASK`, `MESSAGE`, and `FINAL_ANSWER` using the
    same format.
    
    This change adds the routing envelope as plaintext while keeping task
    and message payloads encrypted. No new Responses API field is required:
    an encrypted delivery is represented as an `input_text` header
    immediately followed by its existing `encrypted_content` item.
    
    Every envelope now follows this shape:
    
    ```text
    Message Type: <NEW_TASK | MESSAGE | FINAL_ANSWER>
    Task name: <recipient agent path>
    Sender: <author agent path>
    Payload:
    <message payload>
    ```
    
    ## Message types
    
    ### `NEW_TASK`
    
    `NEW_TASK` is used when the recipient should begin a new turn, including
    an initial `spawn_agent` task and a later `followup_task`.
    
    For a root agent spawning `/root/worker`, the request contains a
    plaintext envelope followed by the encrypted task:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root",
      "recipient": "/root/worker",
      "content": [
        {
          "type": "input_text",
          "text": "Message Type: NEW_TASK\nTask name: /root/worker\nSender: /root\nPayload:\n"
        },
        {
          "type": "encrypted_content",
          "encrypted_content": "<encrypted task payload>"
        }
      ]
    }
    ```
    
    Conceptually, the model receives:
    
    ```text
    Message Type: NEW_TASK
    Task name: /root/worker
    Sender: /root
    Payload:
    Review the authentication changes and report any regressions.
    ```
    
    ### `MESSAGE`
    
    `MESSAGE` is used for a queued `send_message` delivery. It communicates
    with an existing agent without starting a new turn.
    
    For `/root/worker` reporting progress to the root agent, the request
    contains:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root/worker",
      "recipient": "/root",
      "content": [
        {
          "type": "input_text",
          "text": "Message Type: MESSAGE\nTask name: /root\nSender: /root/worker\nPayload:\n"
        },
        {
          "type": "encrypted_content",
          "encrypted_content": "<encrypted message payload>"
        }
      ]
    }
    ```
    
    Conceptually, the model receives:
    
    ```text
    Message Type: MESSAGE
    Task name: /root
    Sender: /root/worker
    Payload:
    The protocol tests pass; I am checking the resume path now.
    ```
    
    ### `FINAL_ANSWER`
    
    `FINAL_ANSWER` is emitted when a child agent reaches a terminal state
    and reports its result to its parent. Completion payloads are already
    available locally, so the complete envelope is represented as plaintext
    rather than as a plaintext header plus encrypted content.
    
    For `/root/worker` completing work for the root agent, the request
    contains:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root/worker",
      "recipient": "/root",
      "content": [
        {
          "type": "input_text",
          "text": "Message Type: FINAL_ANSWER\nTask name: /root\nSender: /root/worker\nPayload:\nNo regressions found."
        }
      ]
    }
    ```
    
    The model-visible form is:
    
    ```text
    Message Type: FINAL_ANSWER
    Task name: /root
    Sender: /root/worker
    Payload:
    No regressions found.
    ```
    
    Errored, shut down, and missing agents also use `FINAL_ANSWER`, with a
    terminal-status description in the payload.
    
    ## What changed
    
    - Render `NEW_TASK` or `MESSAGE` in
    `InterAgentCommunication::to_model_input_item`, based on whether the
    encrypted delivery starts a turn.
    - Replace the multi-agent v2 `<subagent_notification>` completion
    payload with a model-visible `FINAL_ANSWER` envelope.
    - Document `Task name`, `Sender`, and `Payload` consistently in the
    multi-agent developer instructions.
    - Prevent local-only history projections from treating an encrypted
    message's plaintext header as the complete assistant message.
    - Preserve rollout-trace interaction edges when an agent message
    contains both plaintext and encrypted content.
    
    Legacy multi-agent behavior remains unchanged.
    
    ## Verification
    
    - `just test -p codex-protocol`
    - `just test -p codex-rollout-trace`
    - `just test -p codex-web-search-extension`
    - `just test -p codex-core
    encrypted_multi_agent_v2_spawn_sends_agent_message_to_child`
    - `just test -p codex-core
    plaintext_multi_agent_v2_completion_sends_agent_message`
    - `just test -p codex-core
    multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn`
    - `just test -p codex-core
    multi_agent_v2_completion_queues_message_for_direct_parent`
  • [codex] Use expect in integration tests (#28441)
    The workspace denies `clippy::expect_used` in production. Although
    `clippy.toml` allows `expect` in tests, Bazel Clippy compiles
    integration-test helper code in a way that does not receive that
    exemption, which encouraged verbose `unwrap_or_else(... panic!(...))`
    and equivalent `match`/`let else` forms.
    
    This allows `clippy::expect_used` once at each integration-test crate
    root (including aggregated suites and test-support libraries), then
    replaces manual panic-based Result and Option unwraps with
    `expect`/`expect_err`. Standalone `tests/*.rs` files remain their own
    crate roots. Intentional assertion and unexpected-variant panics remain
    unchanged, and the production `expect_used = "deny"` lint remains in
    place.
    
    The cleanup is mechanical and net-negative in line count.
  • Use PathUri in filesystem permission paths for exec-server (#28165)
    ## Why
    
    Progress towards letting app-server and exec-server run on different
    platforms, specifically for sandbox configuration.
    
    ## What
    
    - Make the filesystem path containment hierarchy generic, defaulting to
    `AbsolutePathBuf` for now.
    - Have clients specify `AbsolutePathBuf` or `PathUri` directly where
    needed.
    - Use `PathUri` throughout exec-server filesystem protocol and trait
    boundaries.
    - Implement `From` for conversion to path URIs and `TryFrom` for
    fallible conversion to absolute paths through the generic type
    hierarchy.
  • 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.
  • skills: cache orchestrator resources per thread (#28336)
    ## Why
    
    Hosted orchestrator skills are read through the remote MCP resource
    server. Within one thread, the same catalog or skill resource can be
    requested multiple times by prompt injection and the `skills.list` /
    `skills.read` tools. Re-fetching adds latency and can make those
    surfaces observe different remote contents during the same thread.
    
    This is a follow-up to #28333: orchestrator skills remain limited to
    threads without a local executor, and those threads now get a stable
    per-thread view of the remote skill data they use.
    
    ## What changed
    
    - Reuse the existing per-thread orchestrator catalog snapshot for
    `skills.list` and `skills.read` availability checks.
    - Cache successful orchestrator resource reads by authority, package,
    and resource so prompt injection and tool calls share the same contents.
    - Keep the cache memory-only and bounded to 100 resources and 8 MiB per
    thread.
    - Leave host and executor skill reads unchanged, and do not cache failed
    remote reads.
    
    ## Verification
    
    - Extended the app-server MCP resource integration test to read the same
    hosted skill resource twice and verify that the remote server receives
    one read.
    - The same test verifies that catalog discovery and the selected skill's
    main prompt are each fetched only once per thread.
  • skills: hide orchestrator skills with a local executor (#28333)
    ## Why
    
    App-server threads without a local executor need orchestrator-owned
    skills from the hosted `codex_apps` MCP server. Threads with the local
    executor already discover installed skills from the local filesystem.
    
    After the orchestrator skill provider was enabled for every app-server
    thread, local-executor threads also received the hosted skill catalog
    and the `skills.list` and `skills.read` tools. This changed the existing
    local behavior and could expose a second hosted copy of a skill that was
    already installed locally.
    
    ## What changed
    
    - Expose the thread's selected execution environments to extensions at
    thread startup.
    - Enable orchestrator skills only when the reserved local environment is
    not selected.
    - Apply that decision consistently to hosted skill catalog discovery,
    explicit skill injection, and the `skills.list` and `skills.read` tools.
    
    ## Verification
    
    - The existing no-executor app-server test continues to verify hosted
    skill discovery, invocation, and child-resource reads.
    - A new app-server test verifies that local-executor threads do not
    receive hosted skill context or `skills.*` tools.
  • Discover stdio MCP servers from selected executor plugins (#27870)
    ## Why
    
    **In short:** this PR discovers MCP registrations by reading a selected
    plugin's `.mcp.json` on its executor. #27884 then resolves those
    registrations in the shared catalog.
    
    `thread/start.selectedCapabilityRoots` can select a plugin root owned by
    an executor, and Codex can resolve that package through the executor
    filesystem. MCP declarations inside the selected plugin are still
    ignored.
    
    This PR adds the source-specific discovery layer on top of the
    selected-plugin catalog boundary in #27884:
    
    ```text
    selected capability root
            |
            v
    resolve the plugin through its executor filesystem
            |
            v
    read and normalize its MCP config through the same filesystem
            |
            v
    contribute stdio registrations bound to that environment ID
    ```
    
    The existing MCP launcher and connection manager remain unchanged. MCP
    config parsing is shared with local plugins through #27863.
    
    ## What changed
    
    - Added an executor plugin MCP provider in the MCP extension.
    - Retained only the exact filesystem capability used for package
    resolution and reused it for the selected plugin's MCP config, with no
    host-filesystem fallback or unrelated process/HTTP authority.
    - Read either the manifest-declared MCP config or the default
    `.mcp.json`; a missing default file means the plugin has no MCP servers.
    - Accepted stdio servers only for this first vertical. Executor-owned
    HTTP declarations are skipped with a warning until their placement
    semantics are defined.
    - Normalized stdio registrations with the owning environment's stable
    logical ID and plugin-root working directory.
    - Resolved environment-variable names on the owning executor and
    rejected explicit local forwarding for non-local plugins.
    - Froze discovered declarations once per active thread runtime, then
    applied current managed plugin and MCP requirements when contributing
    them.
    - Carried the selected root ID, display name, and selection order into
    the catalog contribution defined by #27884.
    
    ## Behavior and scope
    
    There is intentionally no production behavior change yet. This PR
    provides the executor provider and contribution boundary, but app-server
    does not install it in this change. Existing local plugin MCP loading is
    unchanged, and no MCP process is launched by this PR alone.
    
    ## Assumptions
    
    - The selected root ID is the plugin policy identity; the manifest
    display name is presentation metadata.
    - An environment ID is a stable logical authority. Reconnection or
    replacement under the same ID does not change ownership.
    - Selected plugin packages and their manifests are trusted inputs.
    - The selected package and MCP discovery snapshot remain frozen for the
    active thread runtime.
    
    ## Follow-up
    
    The next PR installs this contributor in app-server and adds an
    end-to-end test proving that a selected plugin MCP tool launches on its
    owning executor, can be called by the model, survives an explicit MCP
    refresh, and is invisible when its root was not selected.
    
    Resume, fork, environment removal or ID changes, dynamic catalog reload,
    and executor-owned HTTP MCP placement remain separate lifecycle
    decisions.
    
    ## Verification
    
    Focused tests cover executor-only filesystem reads, missing and
    malformed config, stdio filtering and normalization, managed
    requirements, package attribution, and selection order. CI owns
    execution of the test suite.
  • Add selected-plugin precedence and attribution to the MCP catalog (#27884)
    ## Why
    
    **In short:** this PR resolves already-discovered MCP registrations. It
    does not read selected plugins or discover their MCP servers.
    
    The resolved MCP catalog currently builds config and auto-discovered
    plugin registrations before runtime contributors are applied. A
    thread-selected plugin needs a distinct precedence tier in that same
    initial resolution pass: otherwise a disabled lower-precedence winner
    can leave stale name-level state behind, and the winning MCP tools
    cannot be attributed to the selected package reliably.
    
    This PR adds that catalog boundary before executor discovery is
    connected.
    
    ## What changed
    
    - Added an explicit selected-plugin registration tier between
    auto-discovered plugins and explicit config.
    - Collected selected-plugin contributions before the initial catalog
    build, while leaving compatibility and generic extension overlays in
    their existing runtime phase.
    - Retained the winning plugin ID and display name directly on
    plugin-owned catalog registrations.
    - Derived MCP tool provenance from the winning catalog entry instead of
    joining against local-only plugin summaries.
    - Retained the winning selected server's tool approval policy in the
    running connection manager, so a selected registration cannot inherit
    approval behavior from a losing local plugin.
    - Kept remembered approval session-scoped for selected plugins until
    there is an authority-aware persistence contract; Codex will not write
    approval back to an unrelated local plugin.
    - Preserved existing name-level disabled vetoes for discovered plugins
    and config, while keeping a selected package's own disabled registration
    scoped to that registration.
    - Preserved deterministic selection order and existing config,
    compatibility, and extension precedence.
    
    The resulting order is:
    
    ```text
    auto-discovered plugin
      < selected plugin
      < explicit config
      < compatibility registration
      < extension overlay
    ```
    
    ## Behavior and scope
    
    This is a catalog and provenance change only. No production host
    contributes selected-plugin MCP registrations yet, so existing local MCP
    behavior remains unchanged.
    
    The stacked follow-up, #27870, installs the executor plugin provider
    that produces these registrations. App-server activation remains a
    separate final step.
    
    ## Verification
    
    Focused tests cover precedence, deterministic selected-plugin conflicts,
    disabled-veto behavior across catalog phases, managed requirements
    before selected-plugin resolution, winning-server approval policy, and
    attribution when local and selected packages share an ID or server name.
    CI owns execution of the test suite.
  • build: run buildifier from just fmt (#28125)
    ## Intent
    
    Keep Bazel and Starlark files consistently formatted without requiring
    contributors to install or version buildifier themselves.
    
    ## Implementation
    
    - Add a SHA-256-pinned, cross-platform DotSlash manifest for buildifier
    v8.5.1.
    - Run buildifier from the shared `just fmt` and `just fmt-check` driver,
    with Windows-safe explicit DotSlash invocation.
    - Provision DotSlash in formatting CI and contributor devcontainers, and
    document the source-build prerequisite.
    - Apply the initial mechanical buildifier formatting baseline.
  • [codex] make PathUri::from_abs_path infallible (#27976)
    ## Why
    
    `PathUri::from_abs_path` can fail for absolute paths that do not have a
    normal `file:` URI representation, forcing filesystem call sites to
    handle a conversion error even though the original path can be preserved
    losslessly.
    
    ## What
    
    Make `from_abs_path` infallible and migrate its callers. Unrepresentable
    paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or
    Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The
    leading encoded null reserves a namespace that cannot collide with a
    real Unix or Windows path, and fallback URIs remain opaque to lexical
    path operations.
    
    ## Validation
    
    Added path-URI coverage for Unix null and non-UTF-8 paths, Windows
    device/verbatim and non-Unicode paths, serialization, malformed
    fallbacks, opaque lexical operations, invalid native payloads, and
    literal `/bad/path` collision resistance.
  • Support plaintext agent messages (#27830)
    ## Why
    
    Multi-agent v2 `send_message` deliveries already reach the receiving
    model as typed `agent_message` items with encrypted content.
    Child-completion notifications are generated by Codex itself, so their
    content is plaintext and previously fell back to a serialized JSON
    envelope inside an assistant message.
    
    With plaintext `input_text` supported for `agent_message`, both delivery
    paths can use the same model-visible type while preserving explicit
    author and recipient metadata.
    
    ## What changed
    
    - add plaintext `input_text` support to `AgentMessageInputContent` and
    regenerate the affected app-server schemas
    - preserve `InterAgentCommunication` as structured mailbox input instead
    of converting it to assistant text
    - record delivered communications as typed `agent_message` history items
    - persist a dedicated rollout item so local delivery metadata such as
    `trigger_turn` remains available without leaking into the Responses
    request
    - reconstruct typed agent messages on resume and preserve fork-turn
    truncation behavior
    - remove request-time assistant-content parsing
    - preserve plaintext and encrypted inter-agent deliveries in stage-one
    memory inputs
    - normalize and link plaintext and encrypted agent messages in rollout
    traces without treating inbound messages as child results
    - cover the real MultiAgent V2 child-completion path end to end with
    deterministic mailbox synchronization
    
    ## Verification
    
    - `just test -p codex-core
    plaintext_multi_agent_v2_completion_sends_agent_message`
    - `just test -p codex-core input_queue_drains_mailbox_in_delivery_order
    record_initial_history_reconstructs_typed_inter_agent_message
    fork_turn_positions_use_inter_agent_delivery_metadata`
    - `just test -p codex-memories-write
    serializes_inter_agent_communications_for_memory`
    - `just test -p codex-rollout-trace
    agent_messages_preserve_routing_and_content
    sub_agent_started_activity_creates_spawn_edge`
    - `just test -p codex-rollout-trace
    agent_result_edge_falls_back_to_child_thread_without_result_message`
    - `just test -p codex-protocol -p codex-rollout -p
    codex-app-server-protocol`
  • [codex] Add size to internal filesystem metadata (#27927)
    ## Why
    
    `ExecutorFileSystem::get_metadata` reports file kind and timestamps but
    not size. Internal callers that need to enforce a size limit therefore
    have to read the complete file first, which is especially wasteful for
    remote filesystems.
    
    This adds the missing internal metadata so consumers can reject
    oversized files before transferring or buffering them. The field is
    named `size`, matching VS Code's `FileStat.size` filesystem convention.
    
    ## What changed
    
    - add `size: u64` to internal `FileMetadata`
    - populate it from the underlying filesystem metadata
    - carry it through sandbox-helper and remote exec-server responses
    - cover files, directories, symlink targets, and sandboxed reads across
    local and remote filesystem implementations
    
    The new field is intentionally not exposed through the app-server API.
    
    ## Testing
    
    - `just test -p codex-exec-server get_metadata`
    - `just test -p codex-exec-server
    file_system_sandboxed_metadata_and_read_allow_readable_root`
    - `just test -p codex-core-plugins`
    - `just test -p codex-skills-extension`
  • Handle standalone image generation failures as terminal items (#27920)
    ## Why
    
    Standalone image generation emitted a started item but no terminal item
    when the backend failed. Clients could leave the operation unresolved or
    render it as successful.
    
    ## What changed
    
    - Emit a terminal image-generation item with `status: "failed"` when
    generation or editing fails.
    - Skip image persistence for failed terminal items.
    - Render failed image generation distinctly in TUI history.
    - Preserve the status when handling live and replayed terminal items.
    
    ## Looks for TUI, App-Side change needed 
    
    <img width="867" height="89" alt="image"
    src="https://github.com/user-attachments/assets/9e32342f-a982-411e-8498-456639fc468a"
    />
    
    ## Validation
    
    - `just test -p codex-image-generation-extension`
    - App-server image-generation tests
    - Core stream-event tests
    - TUI image-generation lifecycle and snapshot tests
    - Scoped Clippy and formatting
  • Make MCP server contributions thread-scoped (#27670)
    ## Why
    
    `selectedCapabilityRoots` belongs to one thread, but MCP contributors
    previously received only the global Codex config. That left no clean way
    for a selected executor capability to contribute MCP servers to its own
    thread.
    
    ## What this PR does
    
    - Gives MCP contributors a small context containing the config and, for
    a running thread, its frozen host-seeded inputs.
    - Uses the same thread inputs during startup, status queries, refreshes,
    and skill dependency checks.
    - Keeps threadless MCP operations and the existing hosted Apps behavior
    unchanged.
    - Adds coverage showing that two threads resolve independent
    registrations and that later lifecycle mutations do not change the
    frozen MCP inputs.
    
    This PR does not discover plugin manifests, add MCP servers, or launch
    anything new. It only establishes the thread-scoped registration
    boundary.
    
    ## Follow-ups
    
    - Resolve selected executor plugin roots through their owning
    environment filesystem.
    - Convert their stdio MCP declarations into environment-bound
    registrations and add an executor MCP end-to-end test.
    
    ## Verification
    
    - `just fmt`
    - `cargo check --tests -p codex-protocol -p codex-extension-api -p
    codex-mcp-extension -p codex-core -p codex-app-server`
    
    Tests and Clippy were not run.
  • [codex] Remove async_trait from first-party code (#27475)
    ## Why
    
    First-party async traits should expose their `Send` contracts explicitly
    without requiring `async_trait`. This completes the migration pattern
    established in #27303 and #27304.
    
    ## What changed
    
    - Replaced the remaining first-party `async_trait` traits with native
    return-position `impl Future + Send` where statically dispatched and
    explicit boxed `Send` futures where object safety is required.
    - Kept implementations behavior-preserving, outlining existing async
    bodies into inherent methods where that keeps the diff reviewable.
    - Removed all direct first-party `async-trait` dependencies and the
    workspace dependency declaration.
    - Added a cargo-deny policy that permits `async-trait` only through the
    remaining transitive wrapper crates.
    - Updated `rand` from 0.8.5 to 0.8.6 to resolve RUSTSEC-2026-0097 and
    keep the full cargo-deny check passing.
    
    ## Validation
    
    - `just test -p codex-exec-server`: 216 passed, 2 skipped.
    - `just test -p codex-model-provider`: 39 passed.
    - `just test -p codex-core` and `just test`: changed tests passed;
    remaining failures are environment-sensitive suites unrelated to this
    migration.
    - `cargo deny check`
    - `just fix`
    - `just fmt`
    - `cargo shear`
    - `just bazel-lock-check`
  • Fix image extension PathUri conversion (#27711)
    ## Why
    
    `main` stopped compiling when #27498 passed an `AbsolutePathBuf` to the
    `ExecutorFileSystem` API migrated to `PathUri` by #27653.
    
    ## What
    
    Convert referenced image paths to `PathUri` before filesystem reads,
    declare the internal path-URI dependency, and refresh `Cargo.lock`.
  • Route image extension reads through turn environments v2 (#27498)
    ## Why
    
    Image generation used `std::fs::read` for referenced image paths, which
    did not support environment-backed filesystems or their sandbox context.
    
    ## What changed
    
    - Expose optional turn environments to extension tool calls.
    - Include each environment’s ID, working directory, filesystem, and
    sandbox context.
    - Read referenced images through the selected environment filesystem.
    - Keep sandbox usage at the extension call site so extensions can choose
    the appropriate access mode.
    - Consolidate image request construction into one async function.
    - Add coverage for successful environment reads and read failures.
    
    ## Validation
    
    - `cargo check -p codex-image-generation-extension --tests`
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    
    `just test -p codex-image-generation-extension` could not complete
    because the build exhausted available disk space.
  • Resolve MCP server registrations through a catalog (#27634)
    ## Why
    
    MCP servers currently come from user config, local plugins,
    compatibility Apps synthesis, and host extensions. Those sources were
    composed by mutating a shared map, leaving registration identity,
    precedence, removal, and provenance implicit in assembly order.
    
    Before adding executor-owned MCPs, Codex needs one durable resolution
    boundary above `McpConnectionManager`. This PR introduces that boundary
    while preserving current server configuration, policy, and runtime
    behavior. Executor-scoped registrations and explicit policy layers
    remain follow-ups.
    
    ## What changed
    
    - Add typed `McpServerRegistration` inputs and an immutable
    `ResolvedMcpCatalog` in `codex-mcp`.
    - Retain each registration's complete `McpServerConfig`, including its
    environment binding, while recording its source and provenance.
    - Preserve the existing structural precedence between plugin, config,
    compatibility, and ordered extension sources.
    - Resolve equal-precedence actions by contribution order; provenance IDs
    are used only for diagnostics and cannot affect the winner.
    - Preserve extension removals and the existing name-scoped `enabled =
    false` veto.
    - Report same-tier conflicts with every contender and the final catalog
    outcome, including whether the winning action registers or removes the
    server.
    - Require MCP contributors to provide a stable diagnostic identity.
    - Derive materialized server maps and plugin ownership from the resolved
    catalog.
    
    `McpConnectionManager`, transport startup, tool calls, and resource
    routing continue to consume the same effective `McpServerConfig` values.
    
    ## Scope
    
    This PR does not add new MCP capabilities or change user-visible
    behavior. It does not add executor plugin discovery, thread-scoped
    registrations, dynamic refresh generations, or new user/managed policy
    semantics.
    
    ## Verification
    
    - Added focused catalog coverage for source precedence, complete
    configuration preservation, disabled vetoes, plugin ownership,
    contribution-order tie breaking, removal outcomes, and conflict
    diagnostics.
    - Extended hosted Apps coverage for ordered extension removal and
    Apps-disabled hosts with and without the hosted extension installed.
    - `cargo check -p codex-mcp --tests -p codex-extension-api -p
    codex-core`
  • [codex] Load user instructions through an injected provider (#27101)
    ## Why
    
    We want to remove implicit use of `$CODEX_HOME` from `codex-core` and
    make embedders responsible for supplying user-level instructions. This
    also ensures user instructions load when no primary environment is
    selected.
    
    ## What changed
    
    Stacked on #27415, which makes `codex exec` surface thread-scoped
    runtime warnings.
    
    - Added `UserInstructionsProvider` to `codex-extension-api`, with
    absolute source attribution and recoverable loading warnings.
    - Added `codex-home` with the filesystem-backed provider for
    `AGENTS.override.md` and `AGENTS.md`, preserving precedence, fallback,
    trimming, lossy UTF-8 handling, and the existing uncapped global
    instruction size.
    - Removed global instruction loading from `Config` and require
    `ThreadManager` callers to inject a provider.
    - Load provider instructions once for each fresh root runtime, including
    runtimes without a primary environment. Running sessions retain their
    snapshot, while child agents inherit the parent snapshot without
    invoking the provider.
    - Keep provider instructions separate while loading project `AGENTS.md`,
    then assemble the model-visible instructions with the existing ordering,
    source attribution, warning, and turn-context behavior.
    - Wired the Codex home provider through the CLI, app server, MCP server,
    core facade, and thread-manager sample.
    
    ## Validation
    
    - `just test -p codex-home -p codex-extension-api`
    - `just test -p codex-core agents_md`
    - `just test -p codex-core guardian`
    - `just test -p codex-app-server
    thread_start_without_selected_environment_includes_only_global_instruction_source`
    - `just test -p codex-exec warning`
    - `just bazel-lock-check`
  • [codex] migrate ExecutorFileSystem paths to PathUri (#27424)
    ## Why
    
    We're moving exec-server to use PathUri for its internal path
    representations.
    
    ## What
    
    Move `ExecutorFileSystem` APIs to use `PathUri` instead of
    `AbsolutePathBuf`. Future changes will convert higher-level parts of
    exec-server.
  • [codex] remove EnvironmentPathRef (#27433)
    We're switching to using a static encoding of the host path in
    `PathUri`. We may need a type like this again but we can add it when
    it's more compelling.
    
    Stacked on #27454.
  • skills: decouple the skills extension from core (#27413)
    ## Why
    
    `ext/skills` currently depends on `codex-core` for two host concerns:
    reading the concrete `Config` type and borrowing core-owned
    model-context fragment types. That coupling prevents the extension from
    being assembled independently above core and leaves context that belongs
    to the skills feature owned by core.
    
    This stacked PR introduces the host boundary needed for the broader
    extension migration while intentionally preserving existing skills
    behavior. It is stacked on #27404.
    
    ## What changed
    
    - Adds a small public `SkillsExtensionConfig` view and makes skills
    installation generic over the host config type.
    - Requires the host to map its config into that view; app-server
    supplies the current `Config` values.
    - Moves the available-skills and selected-skill context fragment
    implementations into `ext/skills`, preserving their roles, markers, and
    rendered bytes.
    - Removes the direct `codex-core` dependency from
    `codex-skills-extension`.
    - Keeps local discovery, invocation, side effects, and the
    `codex-core-skills` compatibility types unchanged for later staged PRs.
    
    ## Behavior
    
    This adds no capability and is intended to have no user-visible or
    model-visible behavior change. The install API and ownership boundary
    change internally; emitted skills context remains byte-for-byte
    compatible.
    
    ## Validation
    
    - Updates the skills extension integration coverage to use a host-owned
    test config.
    - Asserts the complete rendered catalog and selected-skill fragments,
    including their roles and markers.
    - `just bazel-lock-check`
    - Rust tests and Clippy were not run locally per request; CI will run
    them.
  • skills: render catalog locators by authority (#27591)
    ## Why
    
    Hosted skills introduced by #27388 use opaque `skill://` resource
    identifiers, but the skills catalog rendered every locator as a `file`
    and told the model that every skill body lived on disk. That can send
    the model toward filesystem tools for a resource that must instead be
    read through its owning authority.
    
    The catalog should describe how each source is accessed without changing
    the underlying discovery or invocation behavior.
    
    ## What changed
    
    - Render host skills as `file`, executor-owned skills as `environment
    resource`, orchestrator-owned skills as `orchestrator resource`, and
    custom-provider skills as `custom resource`.
    - Update the shared no-alias guidance to describe source locators rather
    than assuming every skill is stored on the host filesystem.
    - Direct orchestrator resources through `skills.list` and `skills.read`,
    and explicitly tell the model not to treat `skill://` identifiers as
    filesystem paths.
    - Preserve the existing filesystem and alias behavior for local skills.
    
    ## Scope
    
    This PR changes only model-visible catalog rendering and guidance. It
    does not change skill discovery, selection, prompt injection, provider
    routing, catalog caching or refresh behavior, resource validation, or
    the `skills.*` tool contract.
    
    ## Verification
    
    - Extended skills-extension coverage for host-file and executor-resource
    labels.
    - Extended the no-executor app-server flow to assert
    orchestrator-resource wording and non-filesystem guidance.
  • nit: cap error (#27585)
    Just cap an error that could end up in the model context
  • skills: expose remote skill resource tools (#27388)
    ## Why
    
    PR #27387 makes backend plugin skills discoverable and invocable without
    an executor, but resources referenced by those skills still sit behind
    the generic MCP resource surface. The model needs a skills-owned API
    that preserves the provider authority and package boundary instead of
    treating remote resources like local files.
    
    This is stacked on #27387.
    
    ## What
    
    - Adds one `skills` namespace with bounded `list` and `read` tools for
    remote skill providers.
    - Revalidates `authority + package` against the live remote catalog on
    every read, then routes the opaque resource ID back through that
    provider.
    - Allows the backend provider to read canonical child `skill://`
    resources while rejecting cross-package, non-canonical, query, fragment,
    and traversal-shaped URIs.
    - Caps each serialized tool result at 8 KB. Lists are paginated; reads
    return an opaque continuation cursor.
    - Marks the JSON output as external context so memory generation can
    apply its normal suppression policy.
    - Deliberately does not add `skills.search`; that waits for a bounded
    plugin-service search contract.
    
    ## Tool contract
    
    Pseudo-Python matching the wire shape:
    
    ```python
    from typing import Literal, NotRequired, TypedDict
    
    
    class RemoteSkillAuthority(TypedDict):
        kind: Literal["remote"]
        id: str  # e.g. "codex_apps"
    
    
    class RemoteSkill(TypedDict):
        authority: RemoteSkillAuthority
        package: str  # opaque provider-owned package ID
        name: str
        description: str
        main_resource: str  # opaque provider-owned SKILL.md ID
    
    
    class SkillsListParams(TypedDict):
        cursor: NotRequired[str]
    
    
    class SkillsListResult(TypedDict):
        skills: list[RemoteSkill]
        next_cursor: str | None
        warnings: list[str]
        truncated: bool
    
    
    class SkillsReadParams(TypedDict):
        authority: RemoteSkillAuthority  # copied from skills.list
        package: str  # copied from skills.list
        resource: str  # provider-owned child resource ID
        cursor: NotRequired[str]  # copy next_cursor to continue
    
    
    class SkillsReadResult(TypedDict):
        resource: str
        contents: str
        next_cursor: str | None
        truncated: bool
    
    
    class Skills:
        def list(self, params: SkillsListParams) -> SkillsListResult: ...
        def read(self, params: SkillsReadParams) -> SkillsReadResult: ...
    ```
    
    There is one namespace for all remote skills, not one tool or MCP server
    per skill. No resource ID is converted into a filesystem path.
    
    ## Backend dependency
    
    `/ps/mcp` must support direct reads of child resources such as
    `skill://plugin_demo/deploy/references/deploy.md`. This PR implements
    and tests the Codex side of that contract; production child reads remain
    dependent on the corresponding plugin-service support. Search remains
    out of scope until that service exposes a bounded search/resource API.
    
    ## Validation
    
    - Added an app-server integration test covering `skills.list` followed
    by `skills.read` with no executor.
    - Ran `just fmt`.
    - Ran `just bazel-lock-update` and `just bazel-lock-check`.
    - Did not run Rust tests or Clippy locally, per request; CI will run
    them.
  • skills: cache remote catalog failures per thread (#27403)
    ## Summary
    
    - cache the first remote skill catalog outcome per thread, including
    failures
    - preserve discovery errors as catalog warnings
    - update the existing cache regression test to verify failed discovery
    is attempted once
    
    ## Why
    
    A failed or hanging `codex_apps` `resources/list` call could run once
    while building initial context and immediately again while contributing
    first-turn input. With the discovery timeout, an ordinary Apps turn
    could wait up to 20 seconds before inference and retry again on later
    turns even when no remote skill was mentioned.
    
    Caching a warning-only empty catalog preserves graceful degradation
    while preventing repeated synchronous discovery attempts.
    
    ## Testing
    
    - `just fmt`
    - Tests and Clippy not run per request; CI will validate the change.
  • skills: make backend plugin skills invocable without an executor (#27387)
    ## Why
    
    #27198 made the extension-owned `codex_apps` MCP connection the hosted
    plugin runtime, but its `mcp/skill` resources still bypassed the skills
    extension. App-server could list and read those resources through
    generic MCP APIs, but a thread with no selected environment did not
    expose them in the model's skills catalog or load their `SKILL.md`
    through `$skill`.
    
    Hosted skills should stay remote while using the same typed catalog,
    source authority, deduplication, bounded contextual catalog, and
    selected-skill prompt injection as host and executor skills. They should
    not be downloaded or exposed as ambient filesystem paths.
    
    ## What changed
    
    - Add a session-scoped `McpResourceClient` over the replaceable MCP
    connection manager so resource list/read calls follow startup and
    refresh replacements.
    - Add a `BackendSkillProvider` that pages `codex_apps` resources,
    accepts bounded and validated `mcp/skill` entries, and reads a selected
    skill's `SKILL.md` through the same MCP connection.
    - Register the remote provider in app-server and include it in the
    skills catalog even when a thread has no selected capability roots or
    executor.
    - Contribute hosted skill metadata through the bounded
    `AvailableSkillsInstructions` developer-context path, exclude remote
    entries from per-turn catalog injection, and classify `<skills>`
    messages as contextual developer content so rollback can trim and
    rebuild them correctly.
    
    ## Testing
    
    - Extend the app-server MCP resource integration test with
    `environments: []` to exercise two-page discovery, filter a
    non-`mcp/skill` resource, verify the escaped developer catalog entry and
    user-role `<skill>` fragment containing the fetched `SKILL.md`, and
    preserve generic MCP resource reads.
    - Add core event-mapping coverage that classifies `<skills>` developer
    messages as contextual history.
  • [codex] Expand hosted web search citation guidance (#27501)
    ## Summary
    
    - Expand the hosted web search prompt with explicit Markdown-link
    citation guidance.
    - Keep internal `turnX` reference IDs out of final responses and place
    citations next to supported claims.
    
    ## Context
    
    
    https://openai.slack.com/archives/C0AU83S0ZQU/p1781133381448499?thread_ts=1780352049.512299&cid=C0AU83S0ZQU
    
    ## Test plan
    
    - Confirmed `codex-rs/ext/web-search/web_run_description.md` exactly
    matches the supplied target prompt.
    - `UV_CACHE_DIR=/tmp/codex-uv-cache
    PATH=/tmp/codex-just/bin:/home/dev-user/.rustup/toolchains/1.95.0-x86_64-unknown-linux-gnu/bin:$PATH
    python3 scripts/format.py --check`
    - `git diff --check`
  • [codex] Preserve disabled MCP servers across runtime overlays (#27414)
    ## Why
    
    Recent MCP runtime overlay changes replace same-name configured server
    entries with compatibility or extension-provided configs. Those
    replacement configs default to enabled, so an MCP server explicitly
    configured with `enabled = false` could be initialized anyway.
    
    The connection manager still filters disabled servers correctly, but the
    configured disabled state was lost before initialization reached that
    filter.
    
    ## What changed
    
    - Remember MCP servers that are disabled in the configured view before
    applying runtime fallbacks and extension overlays.
    - Restore `enabled = false` for those servers after overlays, while
    leaving all other overlay fields and `Remove` precedence unchanged.
    - Add focused extension-backed regression coverage for a disabled
    `codex_apps` server.
    
    ## Testing
    
    - `just fmt`
    - `just test -p codex-mcp-extension`
    - `just fix -p codex-core`
    - `just fix -p codex-mcp-extension`
    
    The full workspace `just test` suite was not run.
  • [codex] Remove async_trait from ToolExecutor (#27304)
    ## Why
    
    We're now [discouraging use of
    `async_trait`](https://github.com/openai/codex/pull/20242).
    
    Removing use of `async_trait` from `ToolExecutor` yields a `codex_core`
    debug test build speedup of ~78% (from 227.5s to 50.3s) on my machine.
    
    Stacked on #27299, this PR applies the trait change after the handler
    bodies have been outlined.
    
    ## What
    
    Changed `ToolExecutor::handle` to return an explicit boxed
    `ToolExecutorFuture` instead of using `async_trait`.
    
    Updated ToolExecutor implementors to return `Box::pin(...)`, reexported
    the future alias through `codex-tools` and `codex-extension-api`, and
    removed `codex-tools` direct `async-trait` dependency.
  • [codex] Outline ToolExecutor handler bodies (#27299)
    ## Why
    
    We're now [discouraging use of
    `async_trait`](https://github.com/openai/codex/pull/20242).
    
    Removing use of `async_trait` from `ToolExecutor` yields a `codex_core`
    debug test build speedup of ~78% (from 227.5s to 50.3s) on my machine.
    
    For ease of reviewing, this is a prefactor to extract trait method
    implementations to inherent methods. This will prevent changing
    indentation from creating a huge diff.
    
    ## What
    
    Outlined existing `ToolExecutor::handle` bodies into inherent async
    `handle_call` methods across core and extension tool handlers.
    
    The trait methods still use `async_trait` and now delegate to
    `self.handle_call(...).await`; handler behavior is unchanged.
  • Remove async-trait from extension contributors (#27383)
    ## Why
    
    Extension contributors are registered behind `dyn Trait` objects, so
    native `async fn`/RPITIT methods would make these traits
    non-object-safe. Spell out the boxed, `Send` future contract directly so
    `extension-api` no longer needs `async-trait` while retaining the
    existing runtime model.
    
    ## What changed
    
    - add a shared `ExtensionFuture` alias and use it for asynchronous
    contributor methods
    - migrate production and test implementations to return `Box::pin(async
    move { ... })`
    - remove `async-trait` dependencies where they are no longer used,
    keeping it dev-only where unrelated test executors still require it
    
    ## Behavior
    
    No behavior change is intended. Contributor futures remain boxed,
    `Send`, dynamically dispatched, and lazily executed; cancellation and
    callback ordering stay unchanged.
    
    ## Testing
    
    - `just test -p codex-extension-api` (11 passed)
    - affected extension crates (64 passed)
    - targeted `codex-core` contributor tests (14 passed)
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    
    A broad local `codex-core` run compiled successfully but encountered
    unrelated sandbox and missing test-binary fixture failures; CI will run
    the full checks.
  • Use plugin-service MCP as the hosted plugin runtime (#27198)
    ## Stack
    
    - Base: #27191
    - This PR is the third vertical and should be reviewed against
    `jif/external-plugins-2`, not `main`.
    
    ## Why
    
    #27191 moves the host-owned Apps MCP registration behind an extension
    contributor, but deliberately preserves the existing endpoint-selection
    feature while that contribution contract lands. App-server can therefore
    resolve the server through extensions, yet the hosted plugin endpoint is
    still selected through temporary `apps_mcp_path_override` plumbing.
    
    That is not the long-term plugin model. A plugin can bundle skills,
    connectors, MCP servers, and hooks, and those components do not all need
    the same source or execution environment. In particular, an
    authenticated HTTP MCP server can expose plugin capabilities directly
    from a backend without an executor or an orchestrator filesystem.
    
    This PR completes that hosted vertical. App-server's MCP extension now
    owns the aggregate hosted plugin runtime at `/ps/mcp`. Connector actions
    continue to arrive as MCP tools, while backend-provided skills arrive as
    MCP resources and use Codex's existing resource list/read paths. No
    second backend client, skill filesystem, or generic plugin activation
    framework is introduced.
    
    The backend route remains the hosted implementation. This change
    replaces Codex's temporary endpoint-selection mechanism, not the service
    behind the endpoint.
    
    ## What changed
    
    ### Hosted plugin runtime
    
    The MCP extension now contributes `codex_apps` as the hosted plugin
    runtime rather than as a configurable Apps endpoint:
    
    - `https://chatgpt.com` resolves to
    `https://chatgpt.com/backend-api/ps/mcp`;
    - a bare custom ChatGPT base resolves to `/api/codex/ps/mcp`;
    - the existing product-SKU header and ChatGPT authentication behavior
    are preserved;
    - executor availability is never consulted for this streamable HTTP
    transport.
    
    The same MCP connection carries both component shapes supported by the
    hosted endpoint:
    
    - connector actions are discovered and invoked as MCP tools;
    - hosted skills are enumerated and read as MCP resources through the
    existing `list_mcp_resources` and `read_mcp_resource` paths.
    
    This keeps component access in the subsystem that already owns the
    protocol instead of downloading backend skills into an orchestrator
    filesystem or inventing a parallel hosted-skill client.
    
    ### Explicit runtime ordering
    
    `McpManager` now resolves the reserved `codex_apps` entry in three
    ordered phases:
    
    1. install the legacy Apps fallback for compatibility;
    2. apply ordered extension `Set` or `Remove` overlays;
    3. apply the final ChatGPT-auth gate without synthesizing the server
    again.
    
    This ordering is important:
    
    - an ordinary configured or plugin MCP server cannot claim the
    auth-bearing `codex_apps` name;
    - an extension-contributed hosted runtime wins over the fallback;
    - an extension `Remove` remains authoritative;
    - a host without the MCP extension retains the legacy Apps endpoint and
    current local-only behavior.
    
    The temporary `legacy_apps_mcp_loader_enabled` coordination flag is no
    longer needed.
    
    ### Remove the path override
    
    The `apps_mcp_path_override` feature and its runtime plumbing are
    removed, including:
    
    - the feature registry entry and structured feature config;
    - `Config` and `McpConfig` fields;
    - config schema output;
    - config-lock materialization;
    - URL override handling in `codex-mcp`.
    
    Existing boolean and structured forms still deserialize as ignored
    compatibility input. They are omitted from new serialized config, and
    config-lock comparison normalizes the removed input so older locks
    remain replayable.
    
    ### App-server coverage
    
    App-server MCP fixtures now serve the hosted route at
    `/api/codex/ps/mcp`. Existing resource-read and tool/elicitation flows
    therefore exercise the extension-owned endpoint rather than succeeding
    through the legacy fallback.
    
    The stack also adds the missing `codex_chatgpt::connectors` re-export
    for the manager-backed connector helper introduced in #27191.
    
    ## Compatibility
    
    - App-server installs the extension and uses `/ps/mcp` for the hosted
    runtime.
    - CLI and other hosts that do not install the extension retain the
    legacy Apps endpoint.
    - Apps disabled or non-ChatGPT authentication removes `codex_apps` from
    the effective runtime view.
    - Existing local plugins, local skills, executor-selected skills,
    configured MCP servers, and MCP OAuth behavior are otherwise unchanged.
    - Backend plugin enablement remains account/workspace state owned by the
    hosted endpoint; this PR does not add thread-local backend plugin
    selection.
    
    ## Architectural fit
    
    The stack now proves two independent runtime shapes:
    
    1. #27184 resolves filesystem-backed skills through the executor that
    owns a selected root.
    2. #27191 and this PR resolve a backend-hosted HTTP MCP through an
    extension with no executor.
    
    Together they preserve the intended separation:
    
    - selection identifies a plugin/root when explicit selection is needed;
    - each component's owning extension resolves its concrete access
    mechanism;
    - execution stays with the runtime required by that component;
    - existing skills, MCP, connector, and hook subsystems remain the
    downstream consumers.
    
    ## Planned follow-ups
    
    1. **Executor stdio MCP:** selecting an executor plugin registers a
    manifest-declared stdio MCP server and executes it in the environment
    that owns the plugin.
    2. **Optional backend selection:** only if CCA needs thread-local
    selection distinct from backend account/workspace enablement, add a
    concrete backend-owned capability location and surface those selected
    skills through the skills catalog.
    3. **Connector metadata and hooks:** activate those plugin components
    through their existing owning subsystems, with executor hooks remaining
    environment-bound.
    4. **Propagation and persistence:** define explicit resume, fork,
    subagent, refresh, and environment-removal semantics once selected roots
    have multiple real consumers.
    5. **Local convergence:** migrate legacy local skill, MCP, connector,
    and hook paths behind their owning extensions one vertical at a time,
    then remove duplicate core managers and compatibility plumbing after
    parity.
    
    ## Verification
    
    Coverage in this change exercises:
    
    - extension-owned `/backend-api/ps/mcp` registration without an
    executor;
    - preservation of the legacy endpoint in hosts without the extension;
    - extension `Set` and `Remove` precedence over the legacy fallback;
    - ChatGPT-auth gating for the reserved server;
    - hosted MCP resource reads with and without an active thread;
    - connector tool invocation and MCP elicitation through the hosted
    route;
    - ignored boolean and structured forms of the removed path override;
    - config-lock replay compatibility for the removed feature.
    
    `cargo check -p codex-features -p codex-mcp-extension -p
    codex-app-server` passes. Tests and Clippy were not run locally under
    the current development instruction; CI provides the full validation
    pass.