Compare commits

...

96 Commits

Author SHA1 Message Date
David Wiesen
8679dff2ee Fix Windows sandbox junction cwd root 2026-05-07 16:05:57 -07:00
gabec-openai
fe287ac467 Log automated reviewer approval sources distinctly (#15201)
## Summary

- log guardian-reviewed tool approvals as `source=automated_reviewer` in
`codex.tool_decision`
- keep direct user approvals as `source=user` and config-driven
approvals as `source=config`

## Testing

-
`/Users/gabec/.codex/skills/codex-oss-fastdev/scripts/codex-rs-fmt-quiet.sh`
-
`/Users/gabec/.codex/skills/codex-oss-fastdev/scripts/codex-rs-test-quiet.sh
-p codex-otel` (fails in sandboxed loopback bind tests under
`otel/tests/suite/otlp_http_loopback.rs`)
- `cargo test -p codex-core guardian -- --nocapture` (original-tree run
reached Guardian tests and only hit sandbox-related listener/proxy
failures)

Co-authored-by: Codex <noreply@openai.com>
2026-03-19 12:10:41 -07:00
starr-openai
1d210f639e Add exec-server exec RPC implementation (#15090)
Stacked PR 2/3, based on the stub PR.

Adds the exec RPC implementation and process/event flow in exec-server
only.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-19 19:00:36 +00:00
Michael Bolin
b87ba0a3cc Publish runnable DotSlash package for argument-comment lint (#15198)
## Why

To date, the argument-comment linter introduced in
https://github.com/openai/codex/pull/14651 had to be built from source
to run, which can be a bit slow (both for local dev and when it is run
in CI). Because of the potential slowness, I did not wire it up to run
as part of `just clippy` or anything like that. As a result, I have seen
a number of occasions where folks put up PRs that violate the lint, see
it fail in CI, and then have to put up their PR again.

The goal of this PR is to pre-build a runnable version of the linter and
then make it available via a DotSlash file. Once it is available, I will
update `just clippy` and other touchpoints to make it a natural part of
the dev cycle so lint violations should get flagged _before_ putting up
a PR for review.

To get things started, we will build the DotSlash file as part of an
alpha release. Though I don't expect the linter to change often, so I'll
probably change this to only build as part of mainline releases once we
have a working DotSlash file. (Ultimately, we should probably move the
linter into its own repo so it can have its own release cycle.)

## What Changed
- add a reusable `rust-release-argument-comment-lint.yml` workflow that
builds host-specific archives for macOS arm64, Linux arm64/x64, and
Windows x64
- wire `rust-release.yml` to publish the `argument-comment-lint`
DotSlash manifest on all releases for now, including alpha tags
- package a runnable layout instead of a bare library

The Unix archive layout is:

```text
argument-comment-lint/
  bin/
    argument-comment-lint
    cargo-dylint
  lib/
    libargument_comment_lint@nightly-2025-09-18-<target>.dylib|so
```

On Windows the same layout is published as a `.zip`, with `.exe` and
`.dll` filenames instead.

DotSlash resolves the package entrypoint to
`argument-comment-lint/bin/argument-comment-lint`. That runner finds the
sibling bundled `cargo-dylint` binary plus the single packaged Dylint
library under `lib/`, then invokes `cargo-dylint dylint --lib-path
<that-library>` with the repo's default lint settings.
2026-03-19 18:59:02 +00:00
pakrym-oai
1837038f4e Add experimental exec server URL handling (#15196)
Add a config and attempt to start the server.
2026-03-19 18:25:11 +00:00
Andrei Eternal
267499bed8 [hooks] use a user message > developer message for prompt continuation (#14867)
## Summary

Persist Stop-hook continuation prompts as `user` messages instead of
hidden `developer` messages + some requested integration tests

This is a followup to @pakrym 's comment in
https://github.com/openai/codex/pull/14532 to make sure stop-block
continuation prompts match training for turn loops

- Stop continuation now writes `<hook_prompt hook_run_id="...">stop
hook's user prompt<hook_prompt>`
- Introduces quick-xml dependency, though we already indirectly depended
on it anyway via syntect
- This PR only has about 500 lines of actual logic changes, the rest is
tests/schema

## Testing

Example run (with a sessionstart hook and 3 stop hooks) - this shows
context added by session start, then two stop hooks sending their own
additional prompts in a new turn. The model responds with a single
message addressing both. Then when that turn ends, the hooks detect that
they just ran using `stop_hook_active` and decide not to infinite loop

test files for this (unzip, move codex -> .codex):
[codex.zip](https://github.com/user-attachments/files/26075806/codex.zip)

```
› cats


• Running SessionStart hook: lighting the observatory

SessionStart hook (completed)
  warning: Hi, I'm a session start hook for wizard-tower (startup).
  hook context: A wimboltine stonpet is an exotic cuisine from hyperspace

• Cats are tiny zen wizards, my friend: equal parts nap, mystery, and chaos. If you want, we can talk
  cat facts, cat breeds, cat names, or build something cat-themed in this repo.

• Running Stop hook: checking the tower wards

• Running Stop hook: sacking the guards

• Running Stop hook: hiring the guards

Stop hook (completed)
  warning: Wizard Tower Stop hook reviewed the completed reply (177 chars).

Stop hook (blocked)
  warning: Wizard Tower Stop hook continuing conversation
  feedback: cook the stonpet

Stop hook (blocked)
  warning: Wizard Tower Stop hook continuing conversation
  feedback: eat the cooked stonpet

• Stonpet’s cooked, aloha style: flash-seared over a blue quasiflame, glazed with nebula salt, and
  rested until the hyperspace juices settle.

  Now we eat with gratitude, my friend. One mindful bite in, and the flavor is pure cosmic surf:
  smoky, bright, and totally out of this dimension.

• Running Stop hook: checking the tower wards

• Running Stop hook: sacking the guards

• Running Stop hook: hiring the guards

Stop hook (completed)
  warning: Wizard Tower Stop hook reviewed the completed reply (285 chars).

Stop hook (completed)
  warning: Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop.

Stop hook (completed)
  warning: Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop.
```
2026-03-19 10:53:08 -07:00
nicholasclark-openai
5ec121ba12 Revert "Forward session and turn headers to MCP HTTP requests" (#15185)
Reverts openai/codex#15011

Codex merged by mistake before feedback applied
2026-03-19 10:38:53 -07:00
jif-oai
859c58f07d chore: morpheus does not generate memories (#15175)
For obvious reasons
2026-03-19 15:48:28 +00:00
jif-oai
2cf4d5ef35 chore: add metrics for profile (#15180) 2026-03-19 15:48:02 +00:00
pakrym-oai
dee03da508 Move environment abstraction into exec server (#15125)
The idea is that codex-exec exposes an Environment struct with services
on it. Each of those is a trait.

Depending on construction parameters passed to Environment they are
either backed by local or remote server but core doesn't see these
differences.
2026-03-19 08:31:14 -07:00
jif-oai
32d2df5c1e fix: case where agent is already closed (#15163) 2026-03-19 12:12:50 +00:00
jif-oai
70cdb17703 feat: add graph representation of agent network (#15056)
Add a representation of the agent graph. This is now used for:
* Cascade close agents (when I close a parent, it close the kids)
* Cascade resume (oposite)

Later, this will also be used for post-compaction stuffing of the
context

Direct fix for: https://github.com/openai/codex/issues/14458
2026-03-19 10:21:25 +00:00
xl-openai
db5781a088 feat: support product-scoped plugins. (#15041)
1. Added SessionSource::Custom(String) and --session-source.
  2. Enforced plugin and skill products by session_source.
  3. Applied the same filtering to curated background refresh.
2026-03-19 00:46:15 -07:00
Eric Traut
01df50cf42 Add thread/shellCommand to app server API surface (#14988)
This PR adds a new `thread/shellCommand` app server API so clients can
implement `!` shell commands. These commands are executed within the
sandbox, and the command text and output are visible to the model.

The internal implementation mirrors the current TUI `!` behavior.
- persist shell command execution as `CommandExecution` thread items,
including source and formatted output metadata
- bridge live and replayed app-server command execution events back into
the existing `tui_app_server` exec rendering path

This PR also wires `tui_app_server` to submit `!` commands through the
new API.
2026-03-18 23:42:40 -06:00
canvrno-oai
10eb3ec7fc Simple directory mentions (#14970)
- Adds simple support for directory mentions in the TUI.
- Codex App/VS Code will require minor change to recognize a directory
mention as such and change the link behavior.
- Directory mentions have a trailing slash to differentiate from
extensionless files


<img width="972" height="382" alt="image"
src="https://github.com/user-attachments/assets/8035b1eb-0978-465b-8d7a-4db2e5feca39"
/>
<img width="978" height="228" alt="image"
src="https://github.com/user-attachments/assets/af22cf0b-dd10-4440-9bee-a09915f6ba52"
/>
2026-03-19 05:24:09 +00:00
Andrei Eternal
42e932d7bf [hooks] turn_id extension for Stop & UserPromptSubmit (#15118)
## Description

Adding an extension to the spec that exposes the turn_id to hook
scripts. This is a codex-specific mechanic that allows connecting the
hook runs with particular turns

## Testing

hooks config / sample hooks to use. Extract this, rename codex ->
.codex, and place this into a repo or your home folder. It includes:
config.toml that enables hooks, hooks.json, and sample python hooks:


[codex.zip](https://github.com/user-attachments/files/26102671/codex.zip)

example run (note the turn_ids change between turns):

```
› hello


• Running SessionStart hook: lighting the observatory

SessionStart hook (completed)
  warning: Hi, I'm a session start hook for wizard-tower (startup).
  hook context: Startup context: A wimboltine stonpet is an exotic cuisine from hyperspace

• Running UserPromptSubmit hook: lighting the observatory lanterns

UserPromptSubmit hook (completed)
  warning: wizard-tower UserPromptSubmit demo inspected: hello for turn: 019d036d-c7fa-72d2-b6fd-
78878bfe34e4
  hook context: Wizard Tower UserPromptSubmit demo fired. For this reply only, include the exact
phrase 'observatory lanterns lit' near the end.

• Aloha! Grateful to be here and ready to build with you. Show me what you want to tackle in wizard-
  tower, and we’ll surf the next wave together. observatory lanterns lit

• Running Stop hook: back to shore

Stop hook (completed)
  warning: Wizard Tower Stop hook reviewed the completed reply (170 chars) for turn: 019d036d-c7fa-
72d2-b6fd-78878bfe34e4


› what's a stonpet?


• Running UserPromptSubmit hook: lighting the observatory lanterns

UserPromptSubmit hook (completed)
  warning: wizard-tower UserPromptSubmit demo inspected: what's a stonpet? for turn: 019d036e-3164-
72c3-a170-98925564c4fc
  hook context: Wizard Tower UserPromptSubmit demo fired. For this reply only, include the exact
phrase 'observatory lanterns lit' near the end.

• A stonpet isn’t a standard real-world word, brah. In our shared context here, a wimboltine stonpet
  is an exotic cuisine from hyperspace, so “stonpet” sounds like the dish or food itself.

  If you want, we can totally invent the lore for it next. observatory lanterns lit

• Running Stop hook: back to shore

Stop hook (completed)
  warning: Wizard Tower Stop hook reviewed the completed reply (271 chars) for turn: 019d036e-3164-
72c3-a170-98925564c4fc
```
2026-03-18 21:48:31 -07:00
nicholasclark-openai
b14689df3b Forward session and turn headers to MCP HTTP requests (#15011)
## Summary
- forward request-scoped task headers through MCP tool metadata lookups
and tool calls
- apply those headers to streamable HTTP initialize, tools/list, and
tools/call requests
- update affected rmcp/core tests for the new request_headers plumbing

## Testing
- cargo test -p codex-rmcp-client
- cargo test -p codex-core (fails on pre-existing unrelated error in
core/src/auth_env_telemetry.rs: missing websocket_connect_timeout_ms in
ModelProviderInfo initializer)
- just fix -p codex-rmcp-client
- just fix -p codex-core (hits the same unrelated auth_env_telemetry.rs
error)
- just fmt

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-18 21:29:37 -07:00
Owen Lin
20f2a216df feat(core, tracing): create turn spans over websockets (#14632)
## Description

Dependent on:
- [responsesapi] https://github.com/openai/openai/pull/760991 
- [codex-backend] https://github.com/openai/openai/pull/760985

`codex app-server -> codex-backend -> responsesapi` now reuses a
persistent websocket connection across many turns. This PR updates
tracing when using websockets so that each `response.create` websocket
request propagates the current tracing context, so we can get a holistic
end-to-end trace for each turn.

Tracing is propagated via special keys (`ws_request_header_traceparent`,
`ws_request_header_tracestate`) set in the `client_metadata` param in
Responses API.

Currently tracing on websockets is a bit broken because we only set
tracing context on ws connection time, so it's detached from a
`turn/start` request.
2026-03-19 03:41:06 +00:00
pakrym-oai
903660edba Remove stdio transport from exec server (#15119)
Summary
- delete the deprecated stdio transport plumbing from the exec server
stack
- add a basic `exec_server()` harness plus test utilities to start a
server, send requests, and await events
- refresh exec-server dependencies, configs, and documentation to
reflect the new flow

Testing
- Not run (not requested)

---------

Co-authored-by: starr-openai <starr@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-03-19 01:00:35 +00:00
Shaqayeq
4fd2774614 Add Python SDK thread.run convenience methods (#15088)
## TL;DR
Add `thread.run(...)` / `async thread.run(...)` convenience methods to
the Python SDK for the common case.

- add `RunInput = Input | str` and `RunResult` with `final_response`,
collected `items`, and optional `usage`
- keep `thread.turn(...)` strict and lower-level for streaming,
steering, interrupting, and raw generated `Turn` access
- update Python SDK docs, quickstart examples, and tests for the sync
and async convenience flows

## Validation
- `python3 -m pytest sdk/python/tests/test_public_api_signatures.py
sdk/python/tests/test_public_api_runtime_behavior.py`
- `python3 -m pytest
sdk/python/tests/test_real_app_server_integration.py -k
'thread_run_convenience or async_thread_run_convenience'` (skipped in
this environment)

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-19 00:57:48 +00:00
alexsong-oai
825d09373d Support featured plugins (#15042) 2026-03-18 17:45:30 -07:00
starr-openai
81996fcde6 Add exec-server stub server and protocol docs (#15089)
Stacked PR 1/3.

This is the initialize-only exec-server stub slice: binary/client
scaffolding and protocol docs, without exec/filesystem implementation.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-19 00:30:05 +00:00
xl-openai
dcd5e08269 fix: harden plugin feature gating (#15104)
Resubmit https://github.com/openai/codex/pull/15020 with correct
content.

1. Use requirement-resolved config.features as the plugin gate.
2. Guard plugin/list, plugin/read, and related flows behind that gate.
3. Skip bad marketplace.json files instead of failing the whole list.
4. Simplify plugin state and caching.
2026-03-19 00:03:37 +00:00
pakrym-oai
56d0c6bf67 Add apply_patch code mode result (#15100)
It's empty !
2026-03-18 16:11:10 -07:00
pakrym-oai
3590e181fa Add update_plan code mode result (#15103)
It's empty!
2026-03-18 16:10:51 -07:00
Ahmed Ibrahim
b306885bd8 don't add transcript for v2 realtime (#15111)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2026-03-18 15:54:13 -07:00
Shijie Rao
bb30432421 Feat: reuse persisted model and reasoning effort on thread resume (#14888)
## Summary

This PR makes `thread/resume` reuse persisted thread model metadata when
the caller does not explicitly override it.

Changes:
- read persisted thread metadata from SQLite during `thread/resume`
- reuse persisted `model` and `model_reasoning_effort` as resume-time
defaults
- fetch persisted metadata once and reuse it later in the resume
response path
- keep thread summary loading on the existing rollout path, while
reusing persisted metadata when available
- document the resume fallback behavior in the app-server README

## Why

Before this change, resuming a thread without explicit overrides derived
`model` and `model_reasoning_effort` from current config, which could
drift from the thread’s last persisted values. That meant a resumed
thread could report and run with different model settings than the ones
it previously used.

## Behavior

Precedence on `thread/resume` is now:
1. explicit resume overrides
2. persisted SQLite metadata for the thread
3. normal config resolution for the resumed cwd
2026-03-18 15:45:17 -07:00
Charley Cunningham
ebbbc52ce4 Align SQLite feedback logs with feedback formatter (#13494)
## Summary
- store a pre-rendered `feedback_log_body` in SQLite so `/feedback`
exports keep span prefixes and structured event fields
- render SQLite feedback exports with timestamps and level prefixes to
match the old in-memory feedback formatter, while preserving existing
trailing newlines
- count `feedback_log_body` in the SQLite retention budget so structured
or span-prefixed rows still prune correctly
- bound `/feedback` row loading in SQL with the retention estimate, then
apply exact whole-line truncation in Rust so uploads stay capped without
splitting lines

## Details
- add a `feedback_log_body` column to `logs` and backfill it from
`message` for existing rows
- capture span names plus formatted span and event fields at write time,
since SQLite does not retain enough structure to reconstruct the old
formatter later
- keep SQLite feedback queries scoped to the requested thread plus
same-process threadless rows
- restore a SQL-side cumulative `estimated_bytes` cap for feedback
export queries so over-retained partitions do not load every matching
row before truncation
- add focused formatting coverage for exported feedback lines and parity
coverage against `tracing_subscriber`

## Testing
- cargo test -p codex-state
- just fix -p codex-state
- just fmt

codex author: `codex resume 019ca1b0-0ecc-78b1-85eb-6befdd7e4f1f`

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-18 22:44:31 +00:00
Ahmed Ibrahim
7b37a0350f Add final message prefix to realtime handoff output (#15077)
- prefix realtime handoff output with the agent final message label for
both realtime v1 and v2
- update realtime websocket and core expectations to match
2026-03-18 15:19:49 -07:00
xl-openai
86982ca1f9 Revert "fix: harden plugin feature gating" (#15102)
Reverts openai/codex#15020

I messed up the commit in my PR and accidentally merged changes that
were still under review.
2026-03-18 15:19:29 -07:00
Eric Traut
e5de13644d Add a startup deprecation warning for custom prompts (#15076)
## Summary
- detect custom prompts in `$CODEX_HOME/prompts` during TUI startup
- show a deprecation notice only when prompts are present, with guidance
to use `$skill-creator`
- add TUI tests and snapshot coverage for present, missing, and empty
prompts directories

## Testing
- Manually tested
2026-03-18 15:21:30 -06:00
pakrym-oai
5cada46ddf Return image URL from view_image tool (#15072)
Cleanup image semantics in code mode.

`view_image` now returns `{image_url:string, details?: string}` 

`image()` now allows both string parameter and `{image_url:string,
details?: string}`
2026-03-18 13:58:20 -07:00
pakrym-oai
88e5382fc4 Propagate tool errors to code mode (#15075)
Clean up error flow to push the FunctionCallError all the way up to
dispatcher and allow code mode to surface as exception.
2026-03-18 13:57:55 -07:00
Michael Bolin
392347d436 fix: try to fix "Stage npm package" step in ci.yml (#15092)
Fix the CI job by updating it to use artifacts from a more recent
release (`0.115.0`) instead of the existing one (`0.74.0`).

This step in our CI job on PRs started failing today:


334164a6f7/.github/workflows/ci.yml (L33-L47)

I believe it's because this test verifies that the "package npm" script
works, but we want it to be fast and not wait for binaries to be built,
so it uses a GitHub workflow that's already done. Because it was using a
GitHub workflow associated with `0.74.0`, it seems likely that
workflow's history has been reaped, so we need to use a newer one.
2026-03-18 13:52:33 -07:00
Felipe Coury
334164a6f7 feat(tui): restore composer history in app-server tui (#14945)
## Problem

The app-server TUI (`tui_app_server`) lacked composer history support.
Pressing Up/Down to recall previous prompts hit a stub that logged a
warning and displayed "Not available in app-server TUI yet." New
submissions were silently dropped from the shared history file, so
nothing persisted for future sessions.

## Mental model

Codex maintains a single, append-only history file
(`$CODEX_HOME/history.jsonl`) shared across all TUI processes on the
same machine. The legacy (in-process) TUI already reads/writes this file
through `codex_core::message_history`. The app-server TUI delegates most
operations to a separate process over RPC, but history is intentionally
*not* an RPC concern — it's a client-local file.

This PR makes the app-server TUI access the same history file directly,
bypassing the app-server process entirely. The composer's Up/Down
navigation and submit-time persistence now follow the same code paths as
the legacy TUI, with the only difference being *where* the call is
dispatched (locally in `App`, rather than inside `CodexThread`).

The branch is rebuilt directly on top of `upstream/main`, so it keeps
the
existing app-server restore architecture intact.
`AppServerStartedThread`
still restores transcript history from the server `Thread` snapshot via
`thread_snapshot_events`; this PR only adds composer-history support.

## Non-goals

- Adding history support to the app-server protocol. History remains
client-local.
- Changing the on-disk format or location of `history.jsonl`.
- Surfacing history I/O errors to the user (failures are logged and
silently swallowed, matching the legacy TUI).

## Tradeoffs

| Decision | Why | Risk |
|----------|-----|------|
| Widen `message_history` from `pub(crate)` to `pub` | Avoids
duplicating file I/O logic; the module already has a clean, minimal API
surface. | Other workspace crates can now call these functions — the
contract is no longer crate-private. However, this is consistent with
recent precedent: `590cfa617` exposed `mention_syntax` for TUI
consumption, `752402c4f` exposed plugin APIs (`PluginsManager`), and
`14fcb6645`/`edacbf7b6` widened internal core APIs for other crates.
These were all narrow, intentional exposures of specific APIs — not
broad "make internals public" moves. `1af2a37ad` even went the other
direction, reducing broad re-exports to tighten boundaries. This change
follows the same pattern: a small, deliberate API surface (3 functions)
rather than a wholesale visibility change. |
| Intercept `AddToHistory` / `GetHistoryEntryRequest` in `App` before
RPC fallback | Keeps history ops out of the "unsupported op" error path
without changing app-server protocol. | This now routes through a single
`submit_thread_op` entry point, which is safer than the original
duplicated dispatch. The remaining risk is organizational: future
thread-op submission paths need to keep using that shared entry point. |
| `session_configured_from_thread_response` is now `async` | Needs
`await` on `history_metadata()` to populate real `history_log_id` /
`history_entry_count`. | Adds an async file-stat + full-file newline
scan to the session bootstrap path. The scan is bounded by
`history.max_bytes` and matches the legacy TUI's cost profile, but
startup latency still scales with file size. |

## Architecture

```
User presses Up                     User submits a prompt
       │                                    │
       ▼                                    ▼
ChatComposerHistory                 ChatWidget::do_submit_turn
  navigate_up()                       encode_history_mentions()
       │                                    │
       ▼                                    ▼
  AppEvent::CodexOp                  Op::AddToHistory { text }
  (GetHistoryEntryRequest)                  │
       │                                    ▼
       ▼                            App::try_handle_local_history_op
  App::try_handle_local_history_op    message_history::append_entry()
    spawn_blocking {                        │
      message_history::lookup()             ▼
    }                                $CODEX_HOME/history.jsonl
       │
       ▼
  AppEvent::ThreadEvent
  (GetHistoryEntryResponse)
       │
       ▼
  ChatComposerHistory::on_entry_response()
```

## Observability

- `tracing::warn` on `append_entry` failure (includes thread ID).
- `tracing::warn` on `spawn_blocking` lookup join error.
- `tracing::warn` from `message_history` internals on file-open, lock,
or parse failures.

## Tests

- `chat_composer_history::tests::navigation_with_async_fetch` — verifies
that Up emits `Op::GetHistoryEntryRequest` (was: checked for stub error
cell).
- `app::tests::history_lookup_response_is_routed_to_requesting_thread` —
verifies multi-thread composer recall routes the lookup result back to
the originating thread.
-
`app_server_session::tests::resume_response_relies_on_snapshot_replay_not_initial_messages`
— verifies app-server session restore still uses the upstream
thread-snapshot path.
-
`app_server_session::tests::session_configured_populates_history_metadata`
— verifies bootstrap sets nonzero `history_log_id` /
`history_entry_count` from the shared local history file.
2026-03-18 11:54:11 -06:00
xl-openai
580f32ad2a fix: harden plugin feature gating (#15020)
1. Use requirement-resolved config.features as the plugin gate.
2. Guard plugin/list, plugin/read, and related flows behind that gate.
3. Skip bad marketplace.json files instead of failing the whole list.
4. Simplify plugin state and caching.
2026-03-18 10:11:43 -07:00
pakrym-oai
606d85055f Add notify to code-mode (#14842)
Allows model to send an out-of-band notification.

The notification is injected as another tool call output for the same
call_id.
2026-03-18 09:37:13 -07:00
jif-oai
7ae99576a6 chore: disable memory read path for morpheus (#15059)
Because we don't want prompts collisions
2026-03-18 15:42:56 +00:00
Eric Traut
347c6b12ec Removed remaining core events from tui_app_server (#14942) 2026-03-18 09:35:05 -06:00
jif-oai
58ac2a8773 nit: disable live memory edition (#15058) 2026-03-18 14:49:57 +00:00
jif-oai
a265d6043e feat: add memory citation to agent message (#14821)
Client side to come
2026-03-18 10:03:38 +00:00
jif-oai
0f9484dc8a feat: adapt artifacts to new packaging and 2.5.6 (#14947) 2026-03-18 09:17:44 +00:00
Matthew Zeng
40a7d1d15b [plugins] Support configuration tool suggest allowlist. (#15022)
- [x] Support configuration tool suggest allowlist.

Supports both plugins and connectors.
2026-03-17 23:58:27 -07:00
Dylan Hurd
84f4e7b39d fix(subagents) share execpolicy by default (#13702)
## Summary
If a subagent requests approval, and the user persists that approval to
the execpolicy, it should (by default) propagate. We'll need to rethink
this a bit in light of coming Permissions changes, though I think this
is closer to the end state that we'd want, which is that execpolicy
changes to one permissions profile should be synced across threads.

## Testing
- [x] Added integration test

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-18 06:42:26 +00:00
viyatb-oai
a3613035f3 Pin setup-zig GitHub Action to immutable SHA (#14858)
### Motivation
- Pinning the action to an immutable commit SHA reduces the risk of
arbitrary code execution in runners with repository access and secrets.

### Description
- Replaced `uses: mlugg/setup-zig@v2` with `uses:
mlugg/setup-zig@d1434d0886 # v2` in three
workflow files.
- Updated the following files: ` .github/workflows/rust-ci.yml`, `
.github/workflows/rust-release.yml`, and `
.github/workflows/shell-tool-mcp.yml` to reference the immutable SHA
while preserving the original `v2` intent in a trailing comment.

### Testing
- No automated tests were run because this is a workflow-only change and
does not affect repository source code, so CI validation will occur on
the next workflow execution.

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_69763f570234832d9c67b1b66a27c78d)
2026-03-17 22:40:14 -07:00
Andrei Eternal
6fef421654 [hooks] userpromptsubmit - hook before user's prompt is executed (#14626)
- this allows blocking the user's prompts from executing, and also
prevents them from entering history
- handles the edge case where you can both prevent the user's prompt AND
add n amount of additionalContexts
- refactors some old code into common.rs where hooks overlap
functionality
- refactors additionalContext being previously added to user messages,
instead we use developer messages for them
- handles queued messages correctly

Sample hook for testing - if you write "[block-user-submit]" this hook
will stop the thread:

example run
```
› sup


• Running UserPromptSubmit hook: reading the observatory notes

UserPromptSubmit hook (completed)
  warning: wizard-tower UserPromptSubmit demo inspected: sup
  hook context: Wizard Tower UserPromptSubmit demo fired. For this reply only, include the exact
phrase 'observatory lanterns lit' exactly once near the end.

• Just riding the cosmic wave and ready to help, my friend. What are we building today? observatory
  lanterns lit


› and [block-user-submit]


• Running UserPromptSubmit hook: reading the observatory notes

UserPromptSubmit hook (stopped)
  warning: wizard-tower UserPromptSubmit demo blocked the prompt on purpose.
  stop: Wizard Tower demo block: remove [block-user-submit] to continue.
```

.codex/config.toml
```
[features]
codex_hooks = true
```

.codex/hooks.json
```
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 .codex/hooks/user_prompt_submit_demo.py",
            "timeoutSec": 10,
            "statusMessage": "reading the observatory notes"
          }
        ]
      }
    ]
  }
}
```

.codex/hooks/user_prompt_submit_demo.py
```
#!/usr/bin/env python3

import json
import sys
from pathlib import Path


def prompt_from_payload(payload: dict) -> str:
    prompt = payload.get("prompt")
    if isinstance(prompt, str) and prompt.strip():
        return prompt.strip()

    event = payload.get("event")
    if isinstance(event, dict):
        user_prompt = event.get("user_prompt")
        if isinstance(user_prompt, str):
            return user_prompt.strip()

    return ""


def main() -> int:
    payload = json.load(sys.stdin)
    prompt = prompt_from_payload(payload)
    cwd = Path(payload.get("cwd", ".")).name or "wizard-tower"

    if "[block-user-submit]" in prompt:
        print(
            json.dumps(
                {
                    "systemMessage": (
                        f"{cwd} UserPromptSubmit demo blocked the prompt on purpose."
                    ),
                    "decision": "block",
                    "reason": (
                        "Wizard Tower demo block: remove [block-user-submit] to continue."
                    ),
                }
            )
        )
        return 0

    prompt_preview = prompt or "(empty prompt)"
    if len(prompt_preview) > 80:
        prompt_preview = f"{prompt_preview[:77]}..."

    print(
        json.dumps(
            {
                "systemMessage": (
                    f"{cwd} UserPromptSubmit demo inspected: {prompt_preview}"
                ),
                "hookSpecificOutput": {
                    "hookEventName": "UserPromptSubmit",
                    "additionalContext": (
                        "Wizard Tower UserPromptSubmit demo fired. "
                        "For this reply only, include the exact phrase "
                        "'observatory lanterns lit' exactly once near the end."
                    ),
                },
            }
        )
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
```
2026-03-17 22:09:22 -07:00
Charley Cunningham
226241f035 Use workspace requirements for guardian prompt override (#14727)
## Summary
- move `guardian_developer_instructions` from managed config into
workspace-managed `requirements.toml`
- have guardian continue using the override when present and otherwise
fall back to the bundled local guardian prompt
- keep the generalized prompt-quality improvements in the shared
guardian default prompt
- update requirements parsing, layering, schema, and tests for the new
source of truth

## Context
This replaces the earlier managed-config / MDM rollout plan.

The intended rollout path is workspace-managed requirements, including
cloud enterprise policies, rather than backend model metadata, Statsig,
or Jamf-managed config. That keeps the default/fallback behavior local
to `codex-rs` while allowing faster policy updates through the
enterprise requirements plane.

This is intentionally an admin-managed policy input, not a user
preference: the guardian prompt should come either from the bundled
`codex-rs` default or from enterprise-managed `requirements.toml`, and
normal user/project/session config should not override it.

## Updating The OpenAI Prompt
After this lands, the OpenAI-specific guardian prompt should be updated
through the workspace Policies UI at `/codex/settings/policies` rather
than through Jamf or codex-backend model metadata.

Operationally:
- open the workspace Policies editor as a Codex admin
- edit the default `requirements.toml` policy, or a higher-precedence
group-scoped override if we ever want different behavior for a subset of
users
- set `guardian_developer_instructions = """..."""` to the full
OpenAI-specific guardian prompt text
- save the policy; codex-backend stores the raw TOML and `codex-rs`
fetches the effective requirements file from `/wham/config/requirements`

When updating the OpenAI-specific prompt, keep it aligned with the
shared default guardian policy in `codex-rs` except for intentional
OpenAI-only additions.

## Testing
- `cargo check --tests -p codex-core -p codex-config -p
codex-cloud-requirements --message-format short`
- `cargo run -p codex-core --bin codex-write-config-schema`
- `cargo fmt`
- `git diff --check`

Co-authored-by: Codex <noreply@openai.com>
2026-03-17 22:05:41 -07:00
Ahmed Ibrahim
3ce879c646 Handle realtime conversation end in the TUI (#14903)
- close live realtime sessions on errors, ctrl-c, and active meter
removal
- centralize TUI realtime cleanup and avoid duplicate follow-up close
info

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
2026-03-17 21:04:58 -07:00
pakrym-oai
770616414a Prefer websockets when providers support them (#13592)
Remove all flags and model settings.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-17 19:46:44 -07:00
viyatb-oai
d950543e65 feat: support restricted ReadOnlyAccess in elevated Windows sandbox (#14610)
## Summary
- support legacy `ReadOnlyAccess::Restricted` on Windows in the elevated
setup/runner backend
- keep the unelevated restricted-token backend on the legacy full-read
model only, and fail closed for restricted read-only policies there
- keep the legacy full-read Windows path unchanged while deriving
narrower read roots only for elevated restricted-read policies
- honor `include_platform_defaults` by adding backend-managed Windows
system roots only when requested, while always keeping helper roots and
the command `cwd` readable
- preserve `workspace-write` semantics by keeping writable roots
readable when restricted read access is in use in the elevated backend
- document the current Windows boundary: legacy `SandboxPolicy` is
supported on both backends, while richer split-only carveouts still fail
closed instead of running with weaker enforcement

## Testing
- `cargo test -p codex-windows-sandbox`
- `cargo check -p codex-windows-sandbox --tests --target
x86_64-pc-windows-msvc`
- `cargo clippy -p codex-windows-sandbox --tests --target
x86_64-pc-windows-msvc -- -D warnings`
- `cargo test -p codex-core windows_restricted_token_`

## Notes
- local `cargo test -p codex-windows-sandbox` on macOS only exercises
the non-Windows stubs; the Windows-targeted compile and clippy runs
provide the local signal, and GitHub Windows CI exercises the runtime
path
2026-03-17 19:08:50 -07:00
viyatb-oai
6fe8a05dcb fix: honor active permission profiles in sandbox debug (#14293)
## Summary
- stop `codex sandbox` from forcing legacy `sandbox_mode` when active
`[permissions]` profiles are configured
- keep the legacy `read-only` / `workspace-write` fallback for legacy
configs and reject `--full-auto` for profile-based configs
- use split filesystem and network policies in the macOS/Linux debug
sandbox helpers and add regressions for the config-loading behavior


assuming "codex/docs/private/secret.txt" = "none"
```
codex -c 'default_permissions="limited-read-test"' sandbox macos -- <command> ...

codex sandbox macos -- cat codex/docs/private/secret.txt >/dev/null; echo EXIT:$?
cat: codex/docs/private/secret.txt: Operation not permitted
EXIT:1
```

---------

Co-authored-by: celia-oai <celia@openai.com>
2026-03-18 01:52:02 +00:00
pakrym-oai
83a60fdb94 Add FS abstraction and use in view_image (#14960)
Adds an environment crate and environment + file system abstraction.

Environment is a combination of attributes and services specific to
environment the agent is connected to:
File system, process management, OS, default shell.

The goal is to move most of agent logic that assumes environment to work
through the environment abstraction.
2026-03-17 17:36:23 -07:00
Max Johnson
19b887128e app-server: reject websocket requests with Origin headers (#14995)
Reject websocket requests that carry an `Origin` header
2026-03-18 00:24:53 +00:00
xl-openai
a5d3114e97 feat: Add product-aware plugin policies and clean up manifest naming (#14993)
- Add shared Product support to marketplace plugin policy and skill
policy (no enforced yet).
- Move marketplace installation/authentication under policy and model it
as MarketplacePluginPolicy.
- Rename plugin/marketplace local manifest types to separate raw serde
shapes from resolved in-memory models.
2026-03-17 17:01:34 -07:00
Shaqayeq
fc75d07504 Add Python SDK public API and examples (#14446)
## TL;DR
WIP esp the examples

Thin the Python SDK public surface so the wrapper layer returns
canonical app-server generated models directly.

- keeps `Codex` / `AsyncCodex` / `Thread` / `Turn` and input helpers,
but removes alias-only type layers and custom result models
- `metadata` now returns `InitializeResponse` and `run()` returns the
generated app-server `Turn`
- updates docs, examples, notebook, and tests to use canonical generated
types and regenerates `v2_all.py` against current schema
- keeps the pinned runtime-package integration flow and real integration
coverage

  ## Validation
  - `PYTHONPATH=sdk/python/src python3 -m pytest sdk/python/tests`
- `GH_TOKEN="$(gh auth token)" RUN_REAL_CODEX_TESTS=1
PYTHONPATH=sdk/python/src python3 -m pytest sdk/python/tests -rs`

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-17 16:05:56 -07:00
viyatb-oai
0d1539e74c fix(linux-sandbox): prefer system /usr/bin/bwrap when available (#14963)
## Problem
Ubuntu/AppArmor hosts started failing in the default Linux sandbox path
after the switch to vendored/default bubblewrap in `0.115.0`.

The clearest report is in
[#14919](https://github.com/openai/codex/issues/14919), especially [this
investigation
comment](https://github.com/openai/codex/issues/14919#issuecomment-4076504751):
on affected Ubuntu systems, `/usr/bin/bwrap` works, but a copied or
vendored `bwrap` binary fails with errors like `bwrap: setting up uid
map: Permission denied` or `bwrap: loopback: Failed RTM_NEWADDR:
Operation not permitted`.

The root cause is Ubuntu's `/etc/apparmor.d/bwrap-userns-restrict`
profile, which grants `userns` access specifically to `/usr/bin/bwrap`.
Once Codex started using a vendored/internal bubblewrap path, that path
was no longer covered by the distro AppArmor exception, so sandbox
namespace setup could fail even when user namespaces were otherwise
enabled and `uidmap` was installed.

## What this PR changes
- prefer system `/usr/bin/bwrap` whenever it is available
- keep vendored bubblewrap as the fallback when `/usr/bin/bwrap` is
missing
- when `/usr/bin/bwrap` is missing, surface a Codex startup warning
through the app-server/TUI warning path instead of printing directly
from the sandbox helper with `eprintln!`
- use the same launcher decision for both the main sandbox execution
path and the `/proc` preflight path
- document the updated Linux bubblewrap behavior in the Linux sandbox
and core READMEs

## Why this fix
This still fixes the Ubuntu/AppArmor regression from
[#14919](https://github.com/openai/codex/issues/14919), but it keeps the
runtime rule simple and platform-agnostic: if the standard system
bubblewrap is installed, use it; otherwise fall back to the vendored
helper.

The warning now follows that same simple rule. If Codex cannot find
`/usr/bin/bwrap`, it tells the user that it is falling back to the
vendored helper, and it does so through the existing startup warning
plumbing that reaches the TUI and app-server instead of low-level
sandbox stderr.

## Testing
- `cargo test -p codex-linux-sandbox`
- `cargo test -p codex-app-server --lib`
- `cargo test -p codex-tui-app-server
tests::embedded_app_server_start_failure_is_returned`
- `cargo clippy -p codex-linux-sandbox --all-targets`
- `cargo clippy -p codex-app-server --all-targets`
- `cargo clippy -p codex-tui-app-server --all-targets`
2026-03-17 23:05:34 +00:00
Ahmed Ibrahim
98be562fd3 Unify realtime shutdown in core (#14902)
- route realtime startup, input, and transport failures through a single
shutdown path
- emit one realtime error/closed lifecycle while clearing session state
once

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
2026-03-17 15:58:52 -07:00
Ahmed Ibrahim
c6ab4ee537 Gate realtime audio interruption logic to v2 (#14984)
- thread the realtime version into conversation start and app-server
notifications
- keep playback-aware mic gating and playback interruption behavior on
v2 only, leaving v1 on the legacy path
2026-03-17 15:24:37 -07:00
xl-openai
1a9555eda9 Cleanup skills/remote/xxx endpoints. (#14977)
Remote skills/remote/xxx as they are not in used for now.
2026-03-17 15:22:36 -07:00
Felipe Coury
43ee72a9b9 fix(tui): implement /mcp inventory for tui_app_server (#14931)
## Problem

The `/mcp` command did not work in the app-server TUI (remote mode). On
`main`, `add_mcp_output()` called `McpManager::effective_servers()`
in-process, which only sees locally configured servers, and then emitted
a generic stub message for the app-server to handle. In remote usage,
that left `/mcp` without a real inventory view.

## Solution

Implement `/mcp` for the app-server TUI by fetching MCP server inventory
directly from the app-server via the paginated `mcpServerStatus/list`
RPC and rendering the results into chat history.

The command now follows a three-phase lifecycle:

1. Loading: `ChatWidget::add_mcp_output()` inserts a transient
`McpInventoryLoadingCell` and emits `AppEvent::FetchMcpInventory`. This
gives immediate feedback that the command registered.
2. Fetch: `App::fetch_mcp_inventory()` spawns a background task that
calls `fetch_all_mcp_server_statuses()` over an app-server request
handle. When the RPC completes, it sends `AppEvent::McpInventoryLoaded {
result }`.
3. Resolve: `App::handle_mcp_inventory_result()` clears the loading cell
and renders either `new_mcp_tools_output_from_statuses(...)` or an error
message.

This keeps the main app event loop responsive, so the TUI can repaint
before the remote RPC finishes.

## Notes

- No `app-server` changes were required.
- The rendered inventory includes auth, tools, resources, and resource
templates, plus transport details when they are available from local
config for display enrichment.
- The app-server RPC does not expose authoritative `enabled` or
`disabled_reason` state for MCP servers, so the remote `/mcp` view no
longer renders a `Status:` row rather than guessing from local config.
- RPC failures surface in history as `Failed to load MCP inventory:
...`.

## Tests

- `slash_mcp_requests_inventory_via_app_server`
- `mcp_inventory_maps_prefix_tool_names_by_server`
- `handle_mcp_inventory_result_clears_committed_loading_cell`
- `mcp_tools_output_from_statuses_renders_status_only_servers`
- `mcp_inventory_loading_snapshot`
2026-03-17 16:11:27 -06:00
Colin Young
0d2ff40a58 Add auth env observability (#14905)
CXC-410 Emit Env Var Status with `/feedback` report

Add more observability on top of #14611 

[Unset](https://openai.sentry.io/issues/7340419168/?project=4510195390611458&query=019cfa8d-c1ba-7002-96fa-e35fc340551d&referrer=issue-stream)

[Set](https://openai.sentry.io/issues/7340426331/?project=4510195390611458&query=019cfa91-aba1-7823-ab7e-762edfbc0ed4&referrer=issue-stream)
<img width="1063" height="610" alt="image"
src="https://github.com/user-attachments/assets/937ab026-1c2d-4757-81d5-5f31b853113e"
/>


###### Summary
- Adds auth-env telemetry that records whether key auth-related env
overrides were present on session start and request paths.
- Threads those auth-env fields through `/responses`, websocket, and
`/models` telemetry and feedback metadata.
- Buckets custom provider `env_key` configuration to a safe
`"configured"` value instead of emitting raw config text.
- Keeps the slice observability-only: no raw token values or raw URLs
are emitted.

###### Rationale (from spec findings)
- 401 and auth-path debugging needs a way to distinguish env-driven auth
paths from sessions with no auth env override.
- Startup and model-refresh failures need the same auth-env diagnostics
as normal request failures.
- Feedback and Sentry tags need the same auth-env signal as OTel events
so reports can be triaged consistently.
- Custom provider config is user-controlled text, so the telemetry
contract must stay presence-only / bucketed.

###### Scope
- Adds a small `AuthEnvTelemetry` bundle for env presence collection and
threads it through the main request/session telemetry paths.
- Does not add endpoint/base-url/provider-header/geo routing attribution
or broader telemetry API redesign.

###### Trade-offs
- `provider_env_key_name` is bucketed to `"configured"` instead of
preserving the literal configured env var name.
- `/models` is included because startup/model-refresh auth failures need
the same diagnostics, but broader parity work remains out of scope.
- This slice keeps the existing telemetry APIs and layers auth-env
fields onto them rather than redesigning the metadata model.

###### Client follow-up
- Add the separate endpoint/base-url attribution slice if routing-source
diagnosis is still needed.
- Add provider-header or residency attribution only if auth-env presence
proves insufficient in real reports.
- Revisit whether any additional auth-related env inputs need safe
bucketing after more 401 triage data.

###### Testing
- `cargo test -p codex-core emit_feedback_request_tags -- --nocapture`
- `cargo test -p codex-core
collect_auth_env_telemetry_buckets_provider_env_key_name -- --nocapture`
- `cargo test -p codex-core
models_request_telemetry_emits_auth_env_feedback_tags_on_failure --
--nocapture`
- `cargo test -p codex-otel
otel_export_routing_policy_routes_api_request_auth_observability --
--nocapture`
- `cargo test -p codex-otel
otel_export_routing_policy_routes_websocket_connect_auth_observability
-- --nocapture`
- `cargo test -p codex-otel
otel_export_routing_policy_routes_websocket_request_transport_observability
-- --nocapture`
- `cargo test -p codex-core --no-run --message-format short`
- `cargo test -p codex-otel --no-run --message-format short`

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-17 14:26:27 -07:00
pakrym-oai
ee756eb80f Rename exec_wait tool to wait (#14983)
Summary
- document that code mode only exposes `exec` and the renamed `wait`
tool
- update code mode tool spec and descriptions to match the new tool name
- rename tests and helper references from `exec_wait` to `wait`

Testing
- Not run (not requested)
2026-03-17 14:22:26 -07:00
iceweasel-oai
2cc4ee413f temporarily disable private desktop until it works with elevated IPC path (#14986) 2026-03-17 21:09:57 +00:00
Ahmed Ibrahim
4d9d4b7b0f Stabilize approval matrix write-file command (#14968)
## What is flaky
The approval-matrix `WriteFile` scenario is flaky. It sometimes fails in
CI even though the approval logic is unchanged, because the test
delegates the file write and readback to shell parsing instead of
deterministic file I/O.

## Why it was flaky
The test generated a command shaped like `printf ... > file && cat
file`. That means the scenario depended on shell quoting, redirection,
newline handling, and encoding behavior in addition to the approval
system it was actually trying to validate. If the shell interpreted the
payload differently, the test would report an approval failure even
though the product logic was fine.

That also made failures hard to diagnose, because the test did not log
the exact generated command or the parsed result payload.

## How this PR fixes it
This PR replaces the shell-redirection path with a deterministic
`python3 -c` script that writes the file with `Path.write_text(...,
encoding='utf-8')` and then reads it back with the same UTF-8 path. It
also logs the generated command and the resulting exit code/stdout for
the approval scenario so any future failure is directly attributable.

## Why this fix fixes the flakiness
The scenario no longer depends on shell parsing and redirection
semantics. The file contents are produced and read through explicit
UTF-8 file I/O, so the approval test is measuring approval behavior
instead of shell behavior. The added diagnostics mean a future failure
will show the exact command/result pair instead of looking like a
generic intermittent mismatch.

Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
2026-03-17 13:52:36 -07:00
Ahmed Ibrahim
23a44ddbe8 Stabilize permissions popup selection tests (#14966)
## What is flaky
The permissions popup tests in the TUI are flaky, especially on Windows.
They assume the popup opens on a specific row and that a fixed number of
`Up` or `Down` keypresses will land on a specific preset. They also
match popup text too loosely, so a non-selected row can satisfy the
assertion.

## Why it was flaky
These tests were asserting incidental rendering details rather than the
actual selected permission preset. On Windows, the initial selection can
differ from non-Windows runs. Some tests also searched the entire popup
for text like `Guardian Approvals` or `(current)`, which can match a row
that is visible but not selected. Once the popup order or current preset
shifted slightly, a test could fail even though the UI behavior was
still correct.

## How this PR fixes it
This PR adds helpers that identify the selected popup row and selected
preset name directly. The tests now assert the current selection by
name, navigate to concrete target presets instead of assuming a fixed
number of keypresses, and explicitly set the reviewer state in the cases
that require `Guardian Approvals` to be current.

## Why this fix fixes the flakiness
The assertions now track semantic state, not fragile text placement.
Navigation is target-based instead of order-based, so
Windows/non-Windows row differences and harmless popup layout changes no
longer break the tests. That removes the scheduler- and
platform-sensitive assumptions that made the popup suite intermittent.

Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
2026-03-17 20:45:44 +00:00
Ahmed Ibrahim
b02388672f Stabilize Windows cmd-based shell test harnesses (#14958)
## What is flaky
The Windows shell-driven integration tests in `codex-rs/core` were
intermittently unstable, especially:

- `apply_patch_cli_can_use_shell_command_output_as_patch_input`
- `websocket_test_codex_shell_chain`
- `websocket_v2_test_codex_shell_chain`

## Why it was flaky
These tests were exercising real shell-tool flows through whichever
shell Codex selected on Windows, and the `apply_patch` test also nested
a PowerShell read inside `cmd /c`.

There were multiple independent sources of nondeterminism in that setup:

- The test harness depended on the model-selected Windows shell instead
of pinning the shell it actually meant to exercise.
- `cmd.exe /c powershell.exe -Command "..."` is quoting-sensitive; on CI
that could leave the read command wrapped as a literal string instead of
executing it.
- Even after getting the quoting right, PowerShell could emit CLIXML
progress records like module-initialization output onto stdout.
- The `apply_patch` test was building a patch directly from shell
stdout, so any quoting artifact or progress noise corrupted the patch
input.

So the failures were driven by shell startup and output-shape variance,
not by the `apply_patch` or websocket logic themselves.

## How this PR fixes it
- Add a test-only `user_shell_override` path so Windows integration
tests can pin `cmd.exe` explicitly.
- Use that override in the websocket shell-chain tests and in the
`apply_patch` harness.
- Change the nested Windows file read in
`apply_patch_cli_can_use_shell_command_output_as_patch_input` to a UTF-8
PowerShell `-EncodedCommand` script.
- Run that nested PowerShell process with `-NonInteractive`, set
`$ProgressPreference = 'SilentlyContinue'`, and read the file with
`[System.IO.File]::ReadAllText(...)`.

## Why this fix fixes the flakiness
The outer harness now runs under a deterministic shell, and the inner
PowerShell read no longer depends on fragile `cmd` quoting or on
progress output staying quiet by accident. The shell tool returns only
the file contents, so patch construction and websocket assertions depend
on stable test inputs instead of on runner-specific shell behavior.

---------

Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
2026-03-17 20:21:46 +00:00
Matthew Zeng
683c37ce75 [plugins] Support plugin installation elicitation. (#14896)
It now supports:

- Connectors that are from installed and enabled plugins that are not
installed yet
- Plugins that are on the allowlist that are not installed yet.
2026-03-17 13:19:28 -07:00
Eric Traut
49e7dda2df Add device-code onboarding and ChatGPT token refresh to app-server TUI (#14952)
## Summary
- add device-code ChatGPT sign-in to `tui_app_server` onboarding and
reuse the existing `chatgptAuthTokens` login path
- fall back to browser login when device-code auth is unavailable on the
server
- treat `ChatgptAuthTokens` as an existing signed-in ChatGPT state
during onboarding
- add a local ChatGPT auth loader for handing local tokens to the app
server and serving refresh requests
- handle `account/chatgptAuthTokens/refresh` instead of marking it
unsupported, including workspace/account mismatch checks
- add focused coverage for onboarding success, existing auth handling,
local auth loading, and refresh request behavior

## Testing
- `cargo test -p codex-tui-app-server`
- `just fix -p codex-tui-app-server`
2026-03-17 14:12:12 -06:00
iceweasel-oai
95bdea93d2 use framed IPC for elevated command runner (#14846)
## Summary
This is PR 2 of the Windows sandbox runner split.

PR 1 introduced the framed IPC runner foundation and related Windows
sandbox infrastructure without changing the active elevated one-shot
execution path. This PR switches that elevated one-shot path over to the
new runner IPC transport and removes the old request-file bootstrap that
PR 1 intentionally left in place.

After this change, ordinary elevated Windows sandbox commands still
behave as one-shot executions, but they now run as the simple case of
the same helper/IPC transport that later unified_exec work will build
on.

## Why this is needed for unified_exec
Windows elevated sandboxed execution crosses a user boundary: the CLI
launches a helper as the sandbox user and has to manage command
execution from outside that security context. For one-shot commands, the
old request-file/bootstrap flow was sufficient. For unified_exec, it is
not.

Unified_exec needs a long-lived bidirectional channel so the parent can:
- send a spawn request
- receive structured spawn success/failure
- stream stdout and stderr incrementally
- eventually support stdin writes, termination, and other session
lifecycle events

This PR does not add long-lived sessions yet. It converts the existing
elevated one-shot path to use the same framed IPC transport so that PR 3
can add unified_exec session semantics on top of a transport that is
already exercised by normal elevated command execution.

## Scope
This PR:
- updates `windows-sandbox-rs/src/elevated_impl.rs` to launch the runner
with named pipes, send a framed `SpawnRequest`, wait for `SpawnReady`,
and collect framed `Output`/`Exit` messages
- removes the old `--request-file=...` execution path from
`windows-sandbox-rs/src/elevated/command_runner_win.rs`
- keeps the public behavior one-shot: no session reuse or interactive
unified_exec behavior is introduced here

This PR does not:
- add Windows unified_exec session support
- add background terminal reuse
- add PTY session lifecycle management

## Why Windows needs this and Linux/macOS do not
On Linux and macOS, the existing sandbox/process model composes much
more directly with long-lived process control. The parent can generally
spawn and own the child process (or PTY) directly inside the sandbox
model we already use.

Windows elevated sandboxing is different. The parent is not directly
managing the sandboxed process in the same way; it launches across a
different user/security context. That means long-lived control requires
an explicit helper process plus IPC for spawn, output, exit, and later
stdin/session control.

So the extra machinery here is not because unified_exec is conceptually
different on Windows. It is because the elevated Windows sandbox
boundary requires a helper-mediated transport to support it cleanly.

## Validation
- `cargo test -p codex-windows-sandbox`
2026-03-17 11:38:44 -07:00
Keyan Zhang
904dbd414f generate an internal json schema for RolloutLine (#14434)
### Why
i'm working on something that parses and analyzes codex rollout logs,
and i'd like to have a schema for generating a parser/validator.

`codex app-server generate-internal-json-schema` writes an
`RolloutLine.json` file

while doing this, i noticed we have a writer <> reader mismatch issue on
`FunctionCallOutputPayload` and reasoning item ID -- added some schemars
annotations to fix those

### Test

```
$ just codex app-server generate-internal-json-schema --out ./foo
```

generates an `RolloutLine.json` file, which i validated against jsonl
files on disk

`just codex app-server --help` doesn't expose the
`generate-internal-json-schema` option by default, but you can do `just
codex app-server generate-internal-json-schema --help` if you know the
command

everything else still works

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-17 11:19:42 -07:00
Ahmed Ibrahim
0d531c05f2 Fix code mode yield startup race (#14959) 2026-03-17 11:09:12 -07:00
jif-oai
d484bb57d9 feat: add suffix to shell snapshot name (#14938)
https://github.com/openai/codex/issues/14906
2026-03-17 17:59:27 +00:00
Ahmed Ibrahim
f26ad3c92c Fix fuzzy search notification buffering in app-server tests (#14955)
## What is flaky
`codex-rs/app-server/tests/suite/fuzzy_file_search.rs` intermittently
loses the expected `fuzzyFileSearch/sessionUpdated` and
`fuzzyFileSearch/sessionCompleted` notifications when multiple
fuzzy-search sessions are active and CI delivers notifications out of
order.

## Why it was flaky
The wait helpers were keyed only by JSON-RPC method name.

- `wait_for_session_updated` consumed the next
`fuzzyFileSearch/sessionUpdated` notification even when it belonged to a
different search session.
- `wait_for_session_completed` did the same for
`fuzzyFileSearch/sessionCompleted`.
- Once an unmatched notification was read, it was dropped permanently
instead of buffered.
- That meant a valid completion for the target search could arrive
slightly early, be consumed by the wrong waiter, and disappear before
the test started waiting for it.

The result depended on notification ordering and runner scheduling
instead of on the actual product behavior.

## How this PR fixes it
- Add a buffered notification reader in
`codex-rs/app-server/tests/common/mcp_process.rs`.
- Match fuzzy-search notifications on the identifying payload fields
instead of matching only on method name.
- Preserve unmatched notifications in the in-process queue so later
waiters can still consume them.
- Include pending notification methods in timeout failures to make
future diagnosis concrete.

## Why this fix fixes the flakiness
The test now behaves like a real consumer of an out-of-order event
stream: notifications for other sessions stay buffered until the correct
waiter asks for them. Reordering no longer loses the target event, so
the test result is determined by whether the server emitted the right
notifications, not by which one happened to be read first.

Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
2026-03-17 10:52:16 -07:00
Felipe Coury
78e8ee4591 fix(tui): restore remote resume and fork history (#14930)
## Problem

When the TUI connects to a **remote** app-server (via WebSocket), resume
and fork operations lost all conversation history.
`AppServerStartedThread` carried only the `SessionConfigured` event, not
the full `Thread` snapshot. After resume or fork, the chat transcript
was empty — prior turns were silently discarded.

A secondary issue: `primary_session_configured` was not cleared on
reset, causing stale session state after reconnection.

## Approach: TUI-side only, zero app-server changes

The app-server **already returns** the full `Thread` object (with
populated `turns: Vec<Turn>`) in its `ThreadStartResponse`,
`ThreadResumeResponse`, and `ThreadForkResponse`. The data was always
there — the TUI was simply throwing it away. The old
`AppServerStartedThread` struct only kept the `SessionConfiguredEvent`,
discarding the rich turn history that the server had already provided.

This PR fixes the problem entirely within `tui_app_server` (3 files
changed, 0 changes to `app-server`, `app-server-protocol`, or any other
crate). Rather than modifying the server to send history in a different
format or adding a new endpoint, the fix preserves the existing `Thread`
snapshot and replays it through the TUI's standard event pipeline —
making restored sessions indistinguishable from live ones.

## Solution

Add a **thread snapshot replay** path. When the server hands back a
`Thread` object (on start, resume, or fork),
`restore_started_app_server_thread` converts its historical turns into
the same core `Event` sequence the TUI already processes for live
interactions, then replays them into the event store so the chat widget
renders them.

Key changes:
- **`AppServerStartedThread` now carries the full `Thread`** —
`started_thread_from_{start,resume,fork}_response` clone the thread into
the struct alongside the existing `SessionConfiguredEvent`.
- **`thread_snapshot_events()`** walks the thread's turns and items,
producing `TurnStarted` → `ItemCompleted`* →
`TurnComplete`/`TurnAborted` event sequences that the TUI already knows
how to render.
- **`restore_started_app_server_thread()`** pushes the session event +
history events into the thread channel's store, activates the channel,
and replays the snapshot — used for initial startup, resume, and fork.
- **`primary_session_configured` cleared on reset** to prevent stale
session state after reconnection.

## Tradeoffs

- **`Thread` is cloned into `AppServerStartedThread`**: The full thread
snapshot (including all historical turns) is cloned at startup. For
long-lived threads this could be large, but it's a one-time cost and
avoids lifetime gymnastics with the response.

## Tests

- `restore_started_app_server_thread_replays_remote_history` —
end-to-end: constructs a `Thread` with one completed turn, restores it,
and asserts user/agent messages appear in the transcript.
- `bridges_thread_snapshot_turns_for_resume_restore` — unit: verifies
`thread_snapshot_events` produces the correct event sequence for
completed and interrupted turns.

## Test plan

- [ ] Verify `cargo check -p codex-tui-app-server` passes
- [ ] Verify `cargo test -p codex-tui-app-server` passes
- [ ] Manual: connect to a remote app-server, resume an existing thread,
confirm history renders in the chat widget
- [ ] Manual: fork a thread via remote, confirm prior turns appear
2026-03-17 11:16:08 -06:00
Shijie Rao
8e258eb3f5 Feat: CXA-1831 Persist latest model and reasoning effort in sqlite (#14859)
### Summary
The goal is for us to get the latest turn model and reasoning effort on
thread/resume is no override is provided on the thread/resume func call.
This is the part 1 which we write the model and reasoning effort for a
thread to the sqlite db and there will be a followup PR to consume the
two new fields on thread/resume.

[part 2 PR is currently WIP](https://github.com/openai/codex/pull/14888)
and this one can be merged independently.
2026-03-17 10:14:34 -07:00
Owen Lin
6ea041032b fix(core): prevent hanging turn/start due to websocket warming issues (#14838)
## Description

This PR fixes a bad first-turn failure mode in app-server when the
startup websocket prewarm hangs. Before this change, `initialize ->
thread/start -> turn/start` could sit behind the prewarm for up to five
minutes, so the client would not see `turn/started`, and even
`turn/interrupt` would block because the turn had not actually started
yet.

Now, we:
- set a (configurable) timeout of 15s for websocket startup time,
exposed as `websocket_startup_timeout_ms` in config.toml
- `turn/started` is sent immediately on `turn/start` even if the
websocket is still connecting
- `turn/interrupt` can be used to cancel a turn that is still waiting on
the websocket warmup
- the turn task will wait for the full 15s websocket warming timeout
before falling back

## Why

The old behavior made app-server feel stuck at exactly the moment the
client expects turn lifecycle events to start flowing. That was
especially painful for external clients, because from their point of
view the server had accepted the request but then went silent for
minutes.

## Configuring the websocket startup timeout
Can set it in config.toml like this:
```
[model_providers.openai]
supports_websockets = true
websocket_connect_timeout_ms = 15000
```
2026-03-17 10:07:46 -07:00
jif-oai
e8add54e5d feat: show effective model in spawn agent event (#14944)
Show effective model after the full config layering for the sub agent
2026-03-17 16:58:58 +00:00
daveaitel-openai
ef36d39199 Fix agent jobs finalization race and reduce status polling churn (#14843)
## Summary
- make `report_agent_job_result` atomically transition an item from
running to completed while storing `result_json`
- remove brittle finalization grace-sleep logic and make finished-item
cleanup idempotent
- replace blind fixed-interval waiting with status-subscription-based
waiting for active worker threads
- add state runtime tests for atomic completion and late-report
rejection

## Why
This addresses the race and polling concerns in #13948 by removing
timing-based correctness assumptions and reducing unnecessary status
polling churn.

## Validation
- `cd codex-rs && just fmt`
- `cd codex-rs && cargo test -p codex-state`
- `cd codex-rs && cargo test -p codex-core --test all suite::agent_jobs`
- `cd codex-rs && cargo test`
- fails in an unrelated app-server tracing test:
`message_processor::tracing_tests::thread_start_jsonrpc_span_exports_server_span_and_parents_children`
timed out waiting for response

## Notes
- This PR supersedes #14129 with the same agent-jobs fix on a clean
branch from `main`.
- The earlier PR branch was stacked on unrelated history, which made the
review diff include unrelated commits.

Fixes #13948
2026-03-17 10:40:14 -04:00
jif-oai
4ed19b0766 feat: rename to get more explicit close agent (#14935)
https://github.com/openai/codex/issues/14907
2026-03-17 14:37:20 +00:00
jif-oai
31648563c8 feat: centralize package manager version (#14920) 2026-03-17 12:03:07 +00:00
viyatb-oai
603b6493a9 fix(linux-sandbox): ignore missing writable roots (#14890)
## Summary
- skip nonexistent `workspace-write` writable roots in the Linux
bubblewrap mount builder instead of aborting sandbox startup
- keep existing writable roots mounted normally so mixed Windows/WSL
configs continue to work
- add unit and Linux integration regression coverage for the
missing-root case

## Context
This addresses regression A from #14875. Regression B will be handled in
a separate PR.

The old bubblewrap integration added `ensure_mount_targets_exist` as a
preflight guard because bubblewrap bind targets must exist, and failing
early let Codex return a clearer error than a lower-level mount failure.

That policy turned out to be too strict once bubblewrap became the
default Linux sandbox: shared Windows/WSL or mixed-platform configs can
legitimately contain a well-formed writable root that does not exist on
the current machine. This PR keeps bubblewrap's existing-target
requirement, but changes Codex to skip missing writable roots instead of
treating them as fatal configuration errors.
2026-03-17 00:21:00 -07:00
Eric Traut
d37dcca7e0 Revert tui code so it does not rely on in-process app server (#14899)
PR https://github.com/openai/codex/pull/14512 added an in-process app
server and started to wire up the tui to use it. We were originally
planning to modify the `tui` code in place, converting it to use the app
server a bit at a time using a hybrid adapter. We've since decided to
create an entirely new parallel `tui_app_server` implementation and do
the conversion all at once but retain the existing `tui` while we work
the bugs out of the new implementation.

This PR undoes the changes to the `tui` made in the PR #14512 and
restores the old initialization to its previous state. This allows us to
modify the `tui_app_server` without the risk of regressing the old `tui`
code. For example, we can start to remove support for all legacy core
events, like the ones that PR https://github.com/openai/codex/pull/14892
needed to ignore.

Testing:
* I manually verified that the old `tui` starts and shuts down without a
problem.
2026-03-17 00:56:32 -06:00
Eric Traut
57f865c069 Fix tui_app_server: ignore duplicate legacy stream events (#14892)
The in-process app-server currently emits both typed
`ServerNotification`s and legacy `codex/event/*` notifications for the
same live turn updates. `tui_app_server` was consuming both paths, so
message deltas and completed items could be enqueued twice and rendered
as duplicated output in the transcript.

Ignore legacy notifications for event types that already have typed (app
server) notification handling, while keeping legacy fallback behavior
for events that still only arrive on the old path. This preserves
compatibility without duplicating streamed commentary or final agent
output.

We will remove all of the legacy event handlers over time; they're here
only during the short window where we're moving the tui to use the app
server.
2026-03-17 00:50:25 -06:00
viyatb-oai
db7e02c739 fix: canonicalize symlinked Linux sandbox cwd (#14849)
## Problem
On Linux, Codex can be launched from a workspace path that is a symlink
(for example, a symlinked checkout or a symlinked parent directory).

Our sandbox policy intentionally canonicalizes writable/readable roots
to the real filesystem path before building the bubblewrap mounts. That
part is correct and needed for safety.

The remaining bug was that bubblewrap could still inherit the helper
process's logical cwd, which might be the symlinked alias instead of the
mounted canonical path. In that case, the sandbox starts in a cwd that
does not exist inside the sandbox namespace even though the real
workspace is mounted. This can cause sandboxed commands to fail in
symlinked workspaces.

## Fix
This PR keeps the sandbox policy behavior the same, but separates two
concepts that were previously conflated:

- the canonical cwd used to define sandbox mounts and permissions
- the caller's logical cwd used when launching the command

On the Linux bubblewrap path, we now thread the logical command cwd
through the helper explicitly and only add `--chdir <canonical path>`
when the logical cwd differs from the mounted canonical path.

That means:
- permissions are still computed from canonical paths
- bubblewrap starts the command from a cwd that definitely exists inside
the sandbox
- we do not widen filesystem access or undo the earlier symlink
hardening

## Why This Is Safe
This is a narrow Linux-only launch fix, not a policy change.

- Writable/readable root canonicalization stays intact.
- Protected metadata carveouts still operate on canonical roots.
- We only override bubblewrap's inherited cwd when the logical path
would otherwise point at a symlink alias that is not mounted in the
sandbox.

## Tests
- kept the existing protocol/core regression coverage for symlink
canonicalization
- added regression coverage for symlinked cwd handling in the Linux
bubblewrap builder/helper path

Local validation:
- `just fmt`
- `cargo test -p codex-protocol`
- `cargo test -p codex-core
normalize_additional_permissions_canonicalizes_symlinked_write_paths`
- `cargo clippy -p codex-linux-sandbox -p codex-protocol -p codex-core
--tests -- -D warnings`
- `cargo build --bin codex`

## Context
This is related to #14694. The earlier writable-root symlink fix
addressed the mount/permission side; this PR fixes the remaining
symlinked-cwd launch mismatch in the Linux sandbox path.
2026-03-16 22:39:18 -07:00
Ahmed Ibrahim
32e4a5d5d9 [stack 4/4] Reduce realtime self-interruptions during playback (#14827)
## Stack Position
4/4. Top-of-stack sibling built on #14830.

## Base
- #14830

## Sibling
- #14829

## Scope
- Gate low-level mic chunks while speaker playback is active, while
still allowing spoken barge-in.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-17 05:19:51 +00:00
Ahmed Ibrahim
79f476e47d [stack 3/4] Add current thread context to realtime startup (#14829)
## Stack Position
3/4. Top-of-stack sibling built on #14830.

## Base
- #14830

## Sibling
- #14827

## Scope
- Extend the realtime startup context with a bounded summary of the
latest thread turns for continuity.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-17 05:11:05 +00:00
Michael Bolin
15ede607a0 fix: tighten up shell arg quoting in GitHub workflows (#14864)
Inspired by the work done over in
https://github.com/openai/codex-action/pull/74, this tightens up our use
of GitHub expressions as shell/environment variables.
2026-03-16 22:01:16 -07:00
Thibault Sottiaux
8e34caffcc [codex] add Jason as a predefined subagent name (#14881)
This change adds Jason to codex-core's built-in subagent nickname pool
so spawned agents can pick it without any custom role configuration. The
default list was simply missing that predefined name (a grave mistake).
2026-03-16 22:01:14 -07:00
xl-openai
e5a28ba0c2 fix: align marketplace display name with existing interface conventions (#14886)
1. camelCase for displayName;
2. move displayName under interface.
2026-03-16 21:52:19 -07:00
Ahmed Ibrahim
fbd7f9b986 [stack 2/4] Align main realtime v2 wire and runtime flow (#14830)
## Stack Position
2/4. Built on top of #14828.

## Base
- #14828

## Unblocks
- #14829
- #14827

## Scope
- Port the realtime v2 wire parsing, session, app-server, and
conversation runtime behavior onto the split websocket-method base.
- Branch runtime behavior directly on the current realtime session kind
instead of parser-derived flow flags.
- Keep regression coverage in the existing e2e suites.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-16 21:38:07 -07:00
xl-openai
1d85fe79ed feat: support remote_sync for plugin install/uninstall. (#14878)
- Added forceRemoteSync to plugin/install and plugin/uninstall.
- With forceRemoteSync=true, we update the remote plugin status first,
then apply the local change only if the backend call succeeds.
- Kept plugin/list(forceRemoteSync=true) as the main recon path, and for
now it treats remote enabled=false as uninstall. We
will eventually migrate to plugin/installed for more precise state
handling.
2026-03-16 21:37:27 -07:00
xl-openai
49c2b66ece Add marketplace display names to plugin/list (#14861)
Add display_name support to marketplace.json.
2026-03-16 19:04:40 -07:00
xl-openai
59533a2c26 skill-creator: default new skills to ~/.codex/skills (#14837)
### Motivation
- Prevent newly-created skills from being placed in unexpected locations
by prompting for an install path and defaulting to a discoverable
location so skills are usable immediately.
- Make the `skill-creator` instructions explicit about the recommended
default (`~/.codex/skills` / `$CODEX_HOME/skills`) so the agent and
users follow a consistent, discoverable convention.

### Description
- Updated `codex-rs/skills/src/assets/samples/skill-creator/SKILL.md` to
add a user prompt: "Where should I create this skill? If you do not have
a preference, I will place it in ~/.codex/skills so Codex can discover
it automatically.".
- Added guidance before running `init_skill.py` that if the user does
not specify a location, the agent should default to `~/.codex/skills`
(equivalently `$CODEX_HOME/skills`) for auto-discovery.
- Updated the `init_skill.py` examples in the same `SKILL.md` to use
`~/.codex/skills` as the recommended default while keeping one custom
path example.

### Testing
- Ran `cargo test -p codex-skills` and the crate's unit test suite
passed (`1 passed; 0 failed`).
- Verified relevant discovery behavior in code by checking
`codex-rs/utils/home-dir/src/lib.rs` (`find_codex_home` defaults to
`~/.codex`) and `codex-rs/core/src/skills/loader.rs` (user skill roots
include `$CODEX_HOME/skills`).

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_69b75a50bb008322a278e55eb0ddccd6)
2026-03-16 18:36:11 -07:00
Michael Bolin
b77fe8fefe Apply argument comment lint across codex-rs (#14652)
## Why

Once the repo-local lint exists, `codex-rs` needs to follow the
checked-in convention and CI needs to keep it from drifting. This commit
applies the fallback `/*param*/` style consistently across existing
positional literal call sites without changing those APIs.

The longer-term preference is still to avoid APIs that require comments
by choosing clearer parameter types and call shapes. This PR is
intentionally the mechanical follow-through for the places where the
existing signatures stay in place.

After rebasing onto newer `main`, the rollout also had to cover newly
introduced `tui_app_server` call sites. That made it clear the first cut
of the CI job was too expensive for the common path: it was spending
almost as much time installing `cargo-dylint` and re-testing the lint
crate as a representative test job spends running product tests. The CI
update keeps the full workspace enforcement but trims that extra
overhead from ordinary `codex-rs` PRs.

## What changed

- keep a dedicated `argument_comment_lint` job in `rust-ci`
- mechanically annotate remaining opaque positional literals across
`codex-rs` with exact `/*param*/` comments, including the rebased
`tui_app_server` call sites that now fall under the lint
- keep the checked-in style aligned with the lint policy by using
`/*param*/` and leaving string and char literals uncommented
- cache `cargo-dylint`, `dylint-link`, and the relevant Cargo
registry/git metadata in the lint job
- split changed-path detection so the lint crate's own `cargo test` step
runs only when `tools/argument-comment-lint/*` or `rust-ci.yml` changes
- continue to run the repo wrapper over the `codex-rs` workspace, so
product-code enforcement is unchanged

Most of the code changes in this commit are intentionally mechanical
comment rewrites or insertions driven by the lint itself.

## Verification

- `./tools/argument-comment-lint/run.sh --workspace`
- `cargo test -p codex-tui-app-server -p codex-tui`
- parsed `.github/workflows/rust-ci.yml` locally with PyYAML

---

* -> #14652
* #14651
2026-03-16 16:48:15 -07:00
Ahmed Ibrahim
6f05d8d735 [stack 1/4] Split realtime websocket methods by version (#14828)
## Stack Position
1/4. Base PR in the realtime stack.

## Base
- `main`

## Unblocks
- #14830

## Scope
- Split the realtime websocket request builders into `common`, `v1`, and
`v2` modules.
- Keep runtime behavior unchanged in this PR.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-16 16:00:59 -07:00
pakrym-oai
a3ba10b44b Add exit helper to code mode scripts (#14851)
- **Summary**
- expose `exit` through the code mode bridge and module so scripts can
stop mid-flight
  - surface the helper in the description documentation
  - add a regression test ensuring `exit()` terminates execution cleanly
- **Testing**
  - Not run (not requested)
2026-03-16 22:07:58 +00:00
671 changed files with 41956 additions and 9523 deletions

View File

@@ -17,6 +17,7 @@ runs:
- name: Cosign Linux artifacts
shell: bash
env:
ARTIFACTS_DIR: ${{ inputs.artifacts-dir }}
COSIGN_EXPERIMENTAL: "1"
COSIGN_YES: "true"
COSIGN_OIDC_CLIENT_ID: "sigstore"
@@ -24,7 +25,7 @@ runs:
run: |
set -euo pipefail
dest="${{ inputs.artifacts-dir }}"
dest="$ARTIFACTS_DIR"
if [[ ! -d "$dest" ]]; then
echo "Destination $dest does not exist"
exit 1

View File

@@ -117,6 +117,8 @@ runs:
- name: Sign macOS binaries
if: ${{ inputs.sign-binaries == 'true' }}
shell: bash
env:
TARGET: ${{ inputs.target }}
run: |
set -euo pipefail
@@ -131,7 +133,7 @@ runs:
fi
for binary in codex codex-responses-api-proxy; do
path="codex-rs/target/${{ inputs.target }}/release/${binary}"
path="codex-rs/target/${TARGET}/release/${binary}"
codesign --force --options runtime --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path"
done
@@ -139,6 +141,7 @@ runs:
if: ${{ inputs.sign-binaries == 'true' }}
shell: bash
env:
TARGET: ${{ inputs.target }}
APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }}
APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }}
@@ -163,7 +166,7 @@ runs:
notarize_binary() {
local binary="$1"
local source_path="codex-rs/target/${{ inputs.target }}/release/${binary}"
local source_path="codex-rs/target/${TARGET}/release/${binary}"
local archive_path="${RUNNER_TEMP}/${binary}.zip"
if [[ ! -f "$source_path" ]]; then
@@ -184,6 +187,7 @@ runs:
if: ${{ inputs.sign-dmg == 'true' }}
shell: bash
env:
TARGET: ${{ inputs.target }}
APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }}
APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }}
@@ -206,7 +210,8 @@ runs:
source "$GITHUB_ACTION_PATH/notary_helpers.sh"
dmg_path="codex-rs/target/${{ inputs.target }}/release/codex-${{ inputs.target }}.dmg"
dmg_name="codex-${TARGET}.dmg"
dmg_path="codex-rs/target/${TARGET}/release/${dmg_name}"
if [[ ! -f "$dmg_path" ]]; then
echo "dmg $dmg_path not found"
@@ -219,7 +224,7 @@ runs:
fi
codesign --force --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$dmg_path"
notarize_submission "codex-${{ inputs.target }}.dmg" "$dmg_path" "$notary_key_path"
notarize_submission "$dmg_name" "$dmg_path" "$notary_key_path"
xcrun stapler staple "$dmg_path"
- name: Remove signing keychain

View File

@@ -0,0 +1,24 @@
{
"outputs": {
"argument-comment-lint": {
"platforms": {
"macos-aarch64": {
"regex": "^argument-comment-lint-aarch64-apple-darwin\\.tar\\.gz$",
"path": "argument-comment-lint/bin/argument-comment-lint"
},
"linux-x86_64": {
"regex": "^argument-comment-lint-x86_64-unknown-linux-gnu\\.tar\\.gz$",
"path": "argument-comment-lint/bin/argument-comment-lint"
},
"linux-aarch64": {
"regex": "^argument-comment-lint-aarch64-unknown-linux-gnu\\.tar\\.gz$",
"path": "argument-comment-lint/bin/argument-comment-lint"
},
"windows-x86_64": {
"regex": "^argument-comment-lint-x86_64-pc-windows-msvc\\.zip$",
"path": "argument-comment-lint/bin/argument-comment-lint.exe"
}
}
}
}
}

View File

@@ -21,9 +21,12 @@ jobs:
echo "head=$(git rev-parse HEAD^2)" >> "$GITHUB_OUTPUT"
- name: Check changed blob sizes
env:
BASE_SHA: ${{ steps.range.outputs.base }}
HEAD_SHA: ${{ steps.range.outputs.head }}
run: |
python3 scripts/check_blob_size.py \
--base "${{ steps.range.outputs.base }}" \
--head "${{ steps.range.outputs.head }}" \
--base "$BASE_SHA" \
--head "$HEAD_SHA" \
--max-bytes 512000 \
--allowlist .github/blob-size-allowlist.txt

View File

@@ -37,7 +37,7 @@ jobs:
run: |
set -euo pipefail
# Use a rust-release version that includes all native binaries.
CODEX_VERSION=0.74.0
CODEX_VERSION=0.115.0
OUTPUT_DIR="${RUNNER_TEMP}"
python3 ./scripts/stage_npm_packages.py \
--release-version "$CODEX_VERSION" \

View File

@@ -396,6 +396,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
gh issue edit "${{ github.event.issue.number }}" --remove-label codex-deduplicate || true
gh issue edit "$ISSUE_NUMBER" --remove-label codex-deduplicate || true
echo "Attempted to remove label: codex-deduplicate"

View File

@@ -14,6 +14,8 @@ jobs:
name: Detect changed areas
runs-on: ubuntu-24.04
outputs:
argument_comment_lint: ${{ steps.detect.outputs.argument_comment_lint }}
argument_comment_lint_package: ${{ steps.detect.outputs.argument_comment_lint_package }}
codex: ${{ steps.detect.outputs.codex }}
workflows: ${{ steps.detect.outputs.workflows }}
steps:
@@ -39,12 +41,18 @@ jobs:
fi
codex=false
argument_comment_lint=false
argument_comment_lint_package=false
workflows=false
for f in "${files[@]}"; do
[[ $f == codex-rs/* ]] && codex=true
[[ $f == codex-rs/* || $f == tools/argument-comment-lint/* || $f == justfile ]] && argument_comment_lint=true
[[ $f == tools/argument-comment-lint/* || $f == .github/workflows/rust-ci.yml ]] && argument_comment_lint_package=true
[[ $f == .github/* ]] && workflows=true
done
echo "argument_comment_lint=$argument_comment_lint" >> "$GITHUB_OUTPUT"
echo "argument_comment_lint_package=$argument_comment_lint_package" >> "$GITHUB_OUTPUT"
echo "codex=$codex" >> "$GITHUB_OUTPUT"
echo "workflows=$workflows" >> "$GITHUB_OUTPUT"
@@ -83,6 +91,44 @@ jobs:
- name: cargo shear
run: cargo shear
argument_comment_lint:
name: Argument comment lint
runs-on: ubuntu-24.04
needs: changed
if: ${{ needs.changed.outputs.argument_comment_lint == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
steps:
- uses: actions/checkout@v6
- name: Install Linux sandbox build dependencies
run: |
sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
- uses: dtolnay/rust-toolchain@1.93.0
with:
toolchain: nightly-2025-09-18
components: llvm-tools-preview, rustc-dev, rust-src
- name: Cache cargo-dylint tooling
id: cargo_dylint_cache
uses: actions/cache@v5
with:
path: |
~/.cargo/bin/cargo-dylint
~/.cargo/bin/dylint-link
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
key: argument-comment-lint-${{ runner.os }}-${{ hashFiles('tools/argument-comment-lint/Cargo.lock', 'tools/argument-comment-lint/rust-toolchain', '.github/workflows/rust-ci.yml') }}
- name: Install cargo-dylint tooling
if: ${{ steps.cargo_dylint_cache.outputs.cache-hit != 'true' }}
run: cargo install --locked cargo-dylint dylint-link
- name: Test argument comment lint package
if: ${{ needs.changed.outputs.argument_comment_lint_package == 'true' || github.event_name == 'push' }}
working-directory: tools/argument-comment-lint
run: cargo test
- name: Run argument comment lint on codex-rs
run: |
bash -n tools/argument-comment-lint/run.sh
./tools/argument-comment-lint/run.sh
# --- CI to validate on different os/targets --------------------------------
lint_build:
name: Lint/Build — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.profile == 'release' && ' (release)' || '' }}
@@ -95,8 +141,10 @@ jobs:
run:
working-directory: codex-rs
env:
# Speed up repeated builds across CI runs by caching compiled objects (non-Windows).
USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }}
# Speed up repeated builds across CI runs by caching compiled objects, except on
# arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce
# mixed-architecture archives under sccache.
USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }}
CARGO_INCREMENTAL: "0"
SCCACHE_CACHE_SIZE: 10G
# In rust-ci, representative release-profile checks use thin LTO for faster feedback.
@@ -305,7 +353,7 @@ jobs:
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Install Zig
uses: mlugg/setup-zig@v2
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
with:
version: 0.14.0
@@ -460,8 +508,10 @@ jobs:
run:
working-directory: codex-rs
env:
# Speed up repeated builds across CI runs by caching compiled objects (non-Windows).
USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }}
# Speed up repeated builds across CI runs by caching compiled objects, except on
# arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce
# mixed-architecture archives under sccache.
USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }}
CARGO_INCREMENTAL: "0"
SCCACHE_CACHE_SIZE: 10G
@@ -657,13 +707,15 @@ jobs:
# --- Gatherer job that you mark as the ONLY required status -----------------
results:
name: CI results (required)
needs: [changed, general, cargo_shear, lint_build, tests]
needs:
[changed, general, cargo_shear, argument_comment_lint, lint_build, tests]
if: always()
runs-on: ubuntu-24.04
steps:
- name: Summarize
shell: bash
run: |
echo "arglint: ${{ needs.argument_comment_lint.result }}"
echo "general: ${{ needs.general.result }}"
echo "shear : ${{ needs.cargo_shear.result }}"
echo "lint : ${{ needs.lint_build.result }}"
@@ -671,16 +723,21 @@ jobs:
# If nothing relevant changed (PR touching only root README, etc.),
# declare success regardless of other jobs.
if [[ '${{ needs.changed.outputs.codex }}' != 'true' && '${{ needs.changed.outputs.workflows }}' != 'true' && '${{ github.event_name }}' != 'push' ]]; then
if [[ '${{ needs.changed.outputs.argument_comment_lint }}' != 'true' && '${{ needs.changed.outputs.codex }}' != 'true' && '${{ needs.changed.outputs.workflows }}' != 'true' && '${{ github.event_name }}' != 'push' ]]; then
echo 'No relevant changes -> CI not required.'
exit 0
fi
# Otherwise require the jobs to have succeeded
[[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; }
[[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; }
[[ '${{ needs.lint_build.result }}' == 'success' ]] || { echo 'lint_build failed'; exit 1; }
[[ '${{ needs.tests.result }}' == 'success' ]] || { echo 'tests failed'; exit 1; }
if [[ '${{ needs.changed.outputs.argument_comment_lint }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' || '${{ github.event_name }}' == 'push' ]]; then
[[ '${{ needs.argument_comment_lint.result }}' == 'success' ]] || { echo 'argument_comment_lint failed'; exit 1; }
fi
if [[ '${{ needs.changed.outputs.codex }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' || '${{ github.event_name }}' == 'push' ]]; then
[[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; }
[[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; }
[[ '${{ needs.lint_build.result }}' == 'success' ]] || { echo 'lint_build failed'; exit 1; }
[[ '${{ needs.tests.result }}' == 'success' ]] || { echo 'tests failed'; exit 1; }
fi
- name: sccache summary note
if: always()

View File

@@ -0,0 +1,103 @@
name: rust-release-argument-comment-lint
on:
workflow_call:
inputs:
publish:
required: true
type: boolean
jobs:
skip:
if: ${{ !inputs.publish }}
runs-on: ubuntu-latest
steps:
- run: echo "Skipping argument-comment-lint release assets for prerelease tag"
build:
if: ${{ inputs.publish }}
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runs_on || matrix.runner }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15-xlarge
target: aarch64-apple-darwin
archive_name: argument-comment-lint-aarch64-apple-darwin.tar.gz
lib_name: libargument_comment_lint@nightly-2025-09-18-aarch64-apple-darwin.dylib
runner_binary: argument-comment-lint
cargo_dylint_binary: cargo-dylint
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
archive_name: argument-comment-lint-x86_64-unknown-linux-gnu.tar.gz
lib_name: libargument_comment_lint@nightly-2025-09-18-x86_64-unknown-linux-gnu.so
runner_binary: argument-comment-lint
cargo_dylint_binary: cargo-dylint
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
archive_name: argument-comment-lint-aarch64-unknown-linux-gnu.tar.gz
lib_name: libargument_comment_lint@nightly-2025-09-18-aarch64-unknown-linux-gnu.so
runner_binary: argument-comment-lint
cargo_dylint_binary: cargo-dylint
- runner: windows-x64
target: x86_64-pc-windows-msvc
archive_name: argument-comment-lint-x86_64-pc-windows-msvc.zip
lib_name: argument_comment_lint@nightly-2025-09-18-x86_64-pc-windows-msvc.dll
runner_binary: argument-comment-lint.exe
cargo_dylint_binary: cargo-dylint.exe
runs_on:
group: codex-runners
labels: codex-windows-x64
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.93.0
with:
toolchain: nightly-2025-09-18
targets: ${{ matrix.target }}
components: llvm-tools-preview, rustc-dev, rust-src
- name: Install tooling
shell: bash
run: |
install_root="${RUNNER_TEMP}/argument-comment-lint-tools"
cargo install --locked cargo-dylint --root "$install_root"
cargo install --locked dylint-link
echo "INSTALL_ROOT=$install_root" >> "$GITHUB_ENV"
- name: Cargo build
working-directory: tools/argument-comment-lint
shell: bash
run: cargo build --release --target ${{ matrix.target }}
- name: Stage artifact
shell: bash
run: |
dest="dist/argument-comment-lint/${{ matrix.target }}"
mkdir -p "$dest"
package_root="${RUNNER_TEMP}/argument-comment-lint"
rm -rf "$package_root"
mkdir -p "$package_root/bin" "$package_root/lib"
cp "tools/argument-comment-lint/target/${{ matrix.target }}/release/${{ matrix.runner_binary }}" \
"$package_root/bin/${{ matrix.runner_binary }}"
cp "${INSTALL_ROOT}/bin/${{ matrix.cargo_dylint_binary }}" \
"$package_root/bin/${{ matrix.cargo_dylint_binary }}"
cp "tools/argument-comment-lint/target/${{ matrix.target }}/release/${{ matrix.lib_name }}" \
"$package_root/lib/${{ matrix.lib_name }}"
archive_path="$dest/${{ matrix.archive_name }}"
if [[ "${{ runner.os }}" == "Windows" ]]; then
(cd "${RUNNER_TEMP}" && 7z a "$GITHUB_WORKSPACE/$archive_path" argument-comment-lint >/dev/null)
else
(cd "${RUNNER_TEMP}" && tar -czf "$GITHUB_WORKSPACE/$archive_path" argument-comment-lint)
fi
- uses: actions/upload-artifact@v7
with:
name: argument-comment-lint-${{ matrix.target }}
path: dist/argument-comment-lint/${{ matrix.target }}/*

View File

@@ -142,7 +142,7 @@ jobs:
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Install Zig
uses: mlugg/setup-zig@v2
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
with:
version: 0.14.0
@@ -380,11 +380,19 @@ jobs:
publish: true
secrets: inherit
argument-comment-lint-release-assets:
name: argument-comment-lint release assets
needs: tag-check
uses: ./.github/workflows/rust-release-argument-comment-lint.yml
with:
publish: true
release:
needs:
- build
- build-windows
- shell-tool-mcp
- argument-comment-lint-release-assets
name: release
runs-on: ubuntu-latest
permissions:
@@ -490,9 +498,10 @@ jobs:
- name: Stage npm packages
env:
GH_TOKEN: ${{ github.token }}
RELEASE_VERSION: ${{ steps.release_name.outputs.name }}
run: |
./scripts/stage_npm_packages.py \
--release-version "${{ steps.release_name.outputs.name }}" \
--release-version "$RELEASE_VERSION" \
--package codex \
--package codex-responses-api-proxy \
--package codex-sdk
@@ -520,6 +529,13 @@ jobs:
tag: ${{ github.ref_name }}
config: .github/dotslash-config.json
- uses: facebook/dotslash-publish-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag: ${{ github.ref_name }}
config: .github/dotslash-argument-comment-lint-config.json
- name: Trigger developers.openai.com deploy
# Only trigger the deploy if the release is not a pre-release.
# The deploy is used to update the developers.openai.com website with the new config schema json file.
@@ -561,10 +577,12 @@ jobs:
- name: Download npm tarballs from release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ needs.release.outputs.tag }}
RELEASE_VERSION: ${{ needs.release.outputs.version }}
run: |
set -euo pipefail
version="${{ needs.release.outputs.version }}"
tag="${{ needs.release.outputs.tag }}"
version="$RELEASE_VERSION"
tag="$RELEASE_TAG"
mkdir -p dist/npm
patterns=(
"codex-npm-${version}.tgz"

View File

@@ -31,11 +31,14 @@ jobs:
steps:
- name: Compute version and tags
id: compute
env:
RELEASE_TAG_INPUT: ${{ inputs.release-tag }}
RELEASE_VERSION_INPUT: ${{ inputs.release-version }}
run: |
set -euo pipefail
version="${{ inputs.release-version }}"
release_tag="${{ inputs.release-tag }}"
version="$RELEASE_VERSION_INPUT"
release_tag="$RELEASE_TAG_INPUT"
if [[ -z "$version" ]]; then
if [[ -n "$release_tag" && "$release_tag" =~ ^rust-v.+ ]]; then
@@ -483,20 +486,22 @@ jobs:
STAGING_DIR: ${{ runner.temp }}/shell-tool-mcp
- name: Ensure binaries are executable
env:
STAGING_DIR: ${{ steps.staging.outputs.dir }}
run: |
set -euo pipefail
staging="${{ steps.staging.outputs.dir }}"
chmod +x \
"$staging"/vendor/*/bash/*/bash \
"$staging"/vendor/*/zsh/*/zsh
"$STAGING_DIR"/vendor/*/bash/*/bash \
"$STAGING_DIR"/vendor/*/zsh/*/zsh
- name: Create npm tarball
shell: bash
env:
STAGING_DIR: ${{ steps.staging.outputs.dir }}
run: |
set -euo pipefail
mkdir -p dist/npm
staging="${{ steps.staging.outputs.dir }}"
pack_info=$(cd "$staging" && npm pack --ignore-scripts --json --pack-destination "${GITHUB_WORKSPACE}/dist/npm")
pack_info=$(cd "$STAGING_DIR" && npm pack --ignore-scripts --json --pack-destination "${GITHUB_WORKSPACE}/dist/npm")
filename=$(PACK_INFO="$pack_info" node -e 'const data = JSON.parse(process.env.PACK_INFO); console.log(data[0].filename);')
mv "dist/npm/${filename}" "dist/npm/codex-shell-tool-mcp-npm-${PACKAGE_VERSION}.tgz"

1
MODULE.bazel.lock generated
View File

@@ -1295,6 +1295,7 @@
"strum_0.26.3": "{\"dependencies\":[{\"features\":[\"macros\"],\"name\":\"phf\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"strum_macros\",\"optional\":true,\"req\":\"^0.26.3\"},{\"kind\":\"dev\",\"name\":\"strum_macros\",\"req\":\"^0.26\"}],\"features\":{\"default\":[\"std\"],\"derive\":[\"strum_macros\"],\"std\":[]}}",
"strum_0.27.2": "{\"dependencies\":[{\"features\":[\"macros\"],\"name\":\"phf\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"strum_macros\",\"optional\":true,\"req\":\"^0.27\"}],\"features\":{\"default\":[\"std\"],\"derive\":[\"strum_macros\"],\"std\":[]}}",
"strum_macros_0.26.4": "{\"dependencies\":[{\"name\":\"heck\",\"req\":\"^0.5.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.26\"},{\"features\":[\"parsing\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}",
"strum_macros_0.27.2": "{\"dependencies\":[{\"name\":\"heck\",\"req\":\"^0.5.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"parsing\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}",
"strum_macros_0.28.0": "{\"dependencies\":[{\"name\":\"heck\",\"req\":\"^0.5.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"parsing\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}",
"subtle_2.6.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{\"const-generics\":[],\"core_hint_black_box\":[],\"default\":[\"std\",\"i128\"],\"i128\":[],\"nightly\":[],\"std\":[]}}",
"supports-color_2.1.0": "{\"dependencies\":[{\"name\":\"is-terminal\",\"req\":\"^0.4.0\"},{\"name\":\"is_ci\",\"req\":\"^1.1.1\"}],\"features\":{}}",

55
codex-rs/Cargo.lock generated
View File

@@ -1427,6 +1427,7 @@ dependencies = [
"codex-chatgpt",
"codex-cloud-requirements",
"codex-core",
"codex-exec-server",
"codex-feedback",
"codex-file-search",
"codex-login",
@@ -1462,7 +1463,6 @@ dependencies = [
"tracing-opentelemetry",
"tracing-subscriber",
"uuid",
"walkdir",
"wiremock",
]
@@ -1570,11 +1570,13 @@ name = "codex-artifacts"
version = "0.0.0"
dependencies = [
"codex-package-manager",
"flate2",
"pretty_assertions",
"reqwest",
"serde",
"serde_json",
"sha2",
"tar",
"tempfile",
"thiserror 2.0.18",
"tokio",
@@ -1841,6 +1843,7 @@ dependencies = [
"codex-client",
"codex-config",
"codex-connectors",
"codex-exec-server",
"codex-execpolicy",
"codex-file-search",
"codex-git",
@@ -1989,6 +1992,29 @@ dependencies = [
"wiremock",
]
[[package]]
name = "codex-exec-server"
version = "0.0.0"
dependencies = [
"anyhow",
"async-trait",
"base64 0.22.1",
"clap",
"codex-app-server-protocol",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
"codex-utils-pty",
"futures",
"pretty_assertions",
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"tokio",
"tokio-tungstenite",
"tracing",
]
[[package]]
name = "codex-execpolicy"
version = "0.0.0"
@@ -2313,8 +2339,8 @@ dependencies = [
"icu_decimal",
"icu_locale_core",
"icu_provider",
"mime_guess",
"pretty_assertions",
"quick-xml",
"schemars 0.8.22",
"serde",
"serde_json",
@@ -2463,6 +2489,7 @@ dependencies = [
"serde",
"serde_json",
"sqlx",
"strum 0.27.2",
"tokio",
"tracing",
"tracing-subscriber",
@@ -2500,7 +2527,6 @@ dependencies = [
"chrono",
"clap",
"codex-ansi-escape",
"codex-app-server-client",
"codex-app-server-protocol",
"codex-arg0",
"codex-backend-client",
@@ -2745,7 +2771,7 @@ dependencies = [
"base64 0.22.1",
"codex-utils-cache",
"image",
"tempfile",
"mime_guess",
"thiserror 2.0.18",
"tokio",
]
@@ -3056,6 +3082,8 @@ dependencies = [
"ctor 0.6.3",
"futures",
"notify",
"opentelemetry",
"opentelemetry_sdk",
"pretty_assertions",
"regex-lite",
"reqwest",
@@ -3064,6 +3092,9 @@ dependencies = [
"tempfile",
"tokio",
"tokio-tungstenite",
"tracing",
"tracing-opentelemetry",
"tracing-subscriber",
"walkdir",
"wiremock",
"zstd",
@@ -7236,6 +7267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
dependencies = [
"memchr",
"serde",
]
[[package]]
@@ -9355,6 +9387,9 @@ name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
"strum_macros 0.27.2",
]
[[package]]
name = "strum_macros"
@@ -9369,6 +9404,18 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "strum_macros"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "strum_macros"
version = "0.28.0"

View File

@@ -25,6 +25,7 @@ members = [
"hooks",
"secrets",
"exec",
"exec-server",
"execpolicy",
"execpolicy-legacy",
"keyring-store",
@@ -104,6 +105,7 @@ codex-connectors = { path = "connectors" }
codex-config = { path = "config" }
codex-core = { path = "core" }
codex-exec = { path = "exec" }
codex-exec-server = { path = "exec-server" }
codex-execpolicy = { path = "execpolicy" }
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
codex-feedback = { path = "feedback" }
@@ -230,6 +232,7 @@ portable-pty = "0.9.0"
predicates = "3"
pretty_assertions = "1.4.1"
pulldown-cmark = "0.10"
quick-xml = "0.38.4"
rand = "0.9"
ratatui = "0.29.0"
ratatui-macros = "0.6.0"

View File

@@ -871,24 +871,6 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"FuzzyFileSearchParams": {
"properties": {
"cancellationToken": {
@@ -955,15 +937,6 @@
],
"type": "object"
},
"HazelnutScope": {
"enum": [
"example",
"workspace-shared",
"all-shared",
"personal"
],
"type": "string"
},
"ImageDetail": {
"enum": [
"auto",
@@ -1280,6 +1253,10 @@
},
"PluginInstallParams": {
"properties": {
"forceRemoteSync": {
"description": "When true, apply the remote plugin change before the local install flow.",
"type": "boolean"
},
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
@@ -1329,6 +1306,10 @@
},
"PluginUninstallParams": {
"properties": {
"forceRemoteSync": {
"description": "When true, apply the remote plugin change before the local uninstall flow.",
"type": "boolean"
},
"pluginId": {
"type": "string"
}
@@ -1338,15 +1319,6 @@
],
"type": "object"
},
"ProductSurface": {
"enum": [
"chatgpt",
"codex",
"api",
"atlas"
],
"type": "string"
},
"ReadOnlyAccess": {
"oneOf": [
{
@@ -1575,10 +1547,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -1594,7 +1562,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -1728,7 +1695,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -1792,8 +1759,14 @@
"call_id": {
"type": "string"
},
"name": {
"type": [
"string",
"null"
]
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -2425,42 +2398,6 @@
},
"type": "object"
},
"SkillsRemoteReadParams": {
"properties": {
"enabled": {
"default": false,
"type": "boolean"
},
"hazelnutScope": {
"allOf": [
{
"$ref": "#/definitions/HazelnutScope"
}
],
"default": "example"
},
"productSurface": {
"allOf": [
{
"$ref": "#/definitions/ProductSurface"
}
],
"default": "codex"
}
},
"type": "object"
},
"SkillsRemoteWriteParams": {
"properties": {
"hazelnutId": {
"type": "string"
}
},
"required": [
"hazelnutId"
],
"type": "object"
},
"TextElement": {
"properties": {
"byteRange": {
@@ -2771,6 +2708,12 @@
"data": {
"type": "string"
},
"itemId": {
"type": [
"string",
"null"
]
},
"numChannels": {
"format": "uint16",
"minimum": 0.0,
@@ -2938,6 +2881,22 @@
],
"type": "object"
},
"ThreadShellCommandParams": {
"properties": {
"command": {
"description": "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.",
"type": "string"
},
"threadId": {
"type": "string"
}
},
"required": [
"command",
"threadId"
],
"type": "object"
},
"ThreadSortKey": {
"enum": [
"created_at",
@@ -3643,6 +3602,30 @@
"title": "Thread/compact/startRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/shellCommand"
],
"title": "Thread/shellCommandRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadShellCommandParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/shellCommandRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -3811,54 +3794,6 @@
"title": "Plugin/readRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"skills/remote/list"
],
"title": "Skills/remote/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/SkillsRemoteReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Skills/remote/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"skills/remote/export"
],
"title": "Skills/remote/exportRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/SkillsRemoteWriteParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Skills/remote/exportRequest",
"type": "object"
},
{
"properties": {
"id": {

View File

@@ -1,6 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"FuzzyFileSearchMatchType": {
"enum": [
"file",
"directory"
],
"type": "string"
},
"FuzzyFileSearchResult": {
"description": "Superset of [`codex_file_search::FileMatch`]",
"properties": {
@@ -18,6 +25,9 @@
"null"
]
},
"match_type": {
"$ref": "#/definitions/FuzzyFileSearchMatchType"
},
"path": {
"type": "string"
},
@@ -32,6 +42,7 @@
},
"required": [
"file_name",
"match_type",
"path",
"root",
"score"

View File

@@ -1,6 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"FuzzyFileSearchMatchType": {
"enum": [
"file",
"directory"
],
"type": "string"
},
"FuzzyFileSearchResult": {
"description": "Superset of [`codex_file_search::FileMatch`]",
"properties": {
@@ -18,6 +25,9 @@
"null"
]
},
"match_type": {
"$ref": "#/definitions/FuzzyFileSearchMatchType"
},
"path": {
"type": "string"
},
@@ -32,6 +42,7 @@
},
"required": [
"file_name",
"match_type",
"path",
"root",
"score"

View File

@@ -745,6 +745,15 @@
],
"type": "object"
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -964,6 +973,13 @@
],
"type": "object"
},
"FuzzyFileSearchMatchType": {
"enum": [
"file",
"directory"
],
"type": "string"
},
"FuzzyFileSearchResult": {
"description": "Superset of [`codex_file_search::FileMatch`]",
"properties": {
@@ -981,6 +997,9 @@
"null"
]
},
"match_type": {
"$ref": "#/definitions/FuzzyFileSearchMatchType"
},
"path": {
"type": "string"
},
@@ -995,6 +1014,7 @@
},
"required": [
"file_name",
"match_type",
"path",
"root",
"score"
@@ -1136,6 +1156,7 @@
"HookEventName": {
"enum": [
"sessionStart",
"userPromptSubmit",
"stop"
],
"type": "string"
@@ -1180,6 +1201,21 @@
],
"type": "string"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"HookRunStatus": {
"enum": [
"running",
@@ -1454,6 +1490,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -1694,6 +1778,13 @@
],
"type": "object"
},
"RealtimeConversationVersion": {
"enum": [
"v1",
"v2"
],
"type": "string"
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -1824,6 +1915,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -2170,9 +2274,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -2312,6 +2454,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},
@@ -2750,6 +2900,12 @@
"data": {
"type": "string"
},
"itemId": {
"type": [
"string",
"null"
]
},
"numChannels": {
"format": "uint16",
"minimum": 0.0,
@@ -2851,10 +3007,14 @@
},
"threadId": {
"type": "string"
},
"version": {
"$ref": "#/definitions/RealtimeConversationVersion"
}
},
"required": [
"threadId"
"threadId",
"version"
],
"type": "object"
},

View File

@@ -499,6 +499,30 @@
"title": "Thread/compact/startRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"thread/shellCommand"
],
"title": "Thread/shellCommandRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/ThreadShellCommandParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/shellCommandRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -667,54 +691,6 @@
"title": "Plugin/readRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"skills/remote/list"
],
"title": "Skills/remote/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/SkillsRemoteReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Skills/remote/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"skills/remote/export"
],
"title": "Skills/remote/exportRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/SkillsRemoteWriteParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Skills/remote/exportRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -2082,6 +2058,13 @@
"title": "FileChangeRequestApprovalResponse",
"type": "object"
},
"FuzzyFileSearchMatchType": {
"enum": [
"file",
"directory"
],
"type": "string"
},
"FuzzyFileSearchParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -2141,6 +2124,9 @@
"null"
]
},
"match_type": {
"$ref": "#/definitions/FuzzyFileSearchMatchType"
},
"path": {
"type": "string"
},
@@ -2155,6 +2141,7 @@
},
"required": [
"file_name",
"match_type",
"path",
"root",
"score"
@@ -6158,6 +6145,15 @@
"title": "CommandExecutionOutputDeltaNotification",
"type": "object"
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -7731,24 +7727,6 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/v2/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"GetAccountParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -7922,15 +7900,6 @@
],
"type": "string"
},
"HazelnutScope": {
"enum": [
"example",
"workspace-shared",
"all-shared",
"personal"
],
"type": "string"
},
"HookCompletedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -7957,6 +7926,7 @@
"HookEventName": {
"enum": [
"sessionStart",
"userPromptSubmit",
"stop"
],
"type": "string"
@@ -8001,6 +7971,21 @@
],
"type": "string"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"HookRunStatus": {
"enum": [
"running",
@@ -8491,6 +8476,17 @@
"title": "LogoutAccountResponse",
"type": "object"
},
"MarketplaceInterface": {
"properties": {
"displayName": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"McpAuthStatus": {
"enum": [
"unsupported",
@@ -8663,6 +8659,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/v2/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MergeStrategy": {
"enum": [
"replace",
@@ -9180,6 +9224,10 @@
"PluginInstallParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"forceRemoteSync": {
"description": "When true, apply the remote plugin change before the local install flow.",
"type": "boolean"
},
"marketplacePath": {
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
@@ -9351,6 +9399,13 @@
"PluginListResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"featuredPluginIds": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"marketplaces": {
"items": {
"$ref": "#/definitions/v2/PluginMarketplaceEntry"
@@ -9372,6 +9427,16 @@
},
"PluginMarketplaceEntry": {
"properties": {
"interface": {
"anyOf": [
{
"$ref": "#/definitions/v2/MarketplaceInterface"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
@@ -9494,6 +9559,10 @@
"PluginUninstallParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"forceRemoteSync": {
"description": "When true, apply the remote plugin change before the local uninstall flow.",
"type": "boolean"
},
"pluginId": {
"type": "string"
}
@@ -9509,15 +9578,6 @@
"title": "PluginUninstallResponse",
"type": "object"
},
"ProductSurface": {
"enum": [
"chatgpt",
"codex",
"api",
"atlas"
],
"type": "string"
},
"ProfileV2": {
"additionalProperties": true,
"properties": {
@@ -9774,6 +9834,13 @@
}
]
},
"RealtimeConversationVersion": {
"enum": [
"v1",
"v2"
],
"type": "string"
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -9975,25 +10042,6 @@
"title": "ReasoningTextDeltaNotification",
"type": "object"
},
"RemoteSkillSummary": {
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"description",
"id",
"name"
],
"type": "object"
},
"RequestId": {
"anyOf": [
{
@@ -10166,10 +10214,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/v2/ReasoningItemReasoningSummary"
@@ -10185,7 +10229,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -10319,7 +10362,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/v2/FunctionCallOutputPayload"
"$ref": "#/definitions/v2/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -10383,8 +10426,14 @@
"call_id": {
"type": "string"
},
"name": {
"type": [
"string",
"null"
]
},
"output": {
"$ref": "#/definitions/v2/FunctionCallOutputPayload"
"$ref": "#/definitions/v2/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -11001,6 +11050,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -11375,79 +11437,6 @@
"title": "SkillsListResponse",
"type": "object"
},
"SkillsRemoteReadParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"enabled": {
"default": false,
"type": "boolean"
},
"hazelnutScope": {
"allOf": [
{
"$ref": "#/definitions/v2/HazelnutScope"
}
],
"default": "example"
},
"productSurface": {
"allOf": [
{
"$ref": "#/definitions/v2/ProductSurface"
}
],
"default": "codex"
}
},
"title": "SkillsRemoteReadParams",
"type": "object"
},
"SkillsRemoteReadResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"data": {
"items": {
"$ref": "#/definitions/v2/RemoteSkillSummary"
},
"type": "array"
}
},
"required": [
"data"
],
"title": "SkillsRemoteReadResponse",
"type": "object"
},
"SkillsRemoteWriteParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"hazelnutId": {
"type": "string"
}
},
"required": [
"hazelnutId"
],
"title": "SkillsRemoteWriteParams",
"type": "object"
},
"SkillsRemoteWriteResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"id": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"id",
"path"
],
"title": "SkillsRemoteWriteResponse",
"type": "object"
},
"SubAgentSource": {
"oneOf": [
{
@@ -11982,9 +11971,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/v2/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/v2/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -12124,6 +12151,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/v2/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/v2/CommandExecutionStatus"
},
@@ -12803,6 +12838,12 @@
"data": {
"type": "string"
},
"itemId": {
"type": [
"string",
"null"
]
},
"numChannels": {
"format": "uint16",
"minimum": 0.0,
@@ -12913,10 +12954,14 @@
},
"threadId": {
"type": "string"
},
"version": {
"$ref": "#/definitions/v2/RealtimeConversationVersion"
}
},
"required": [
"threadId"
"threadId",
"version"
],
"title": "ThreadRealtimeStartedNotification",
"type": "object"
@@ -13153,6 +13198,29 @@
"title": "ThreadSetNameResponse",
"type": "object"
},
"ThreadShellCommandParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"command": {
"description": "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.",
"type": "string"
},
"threadId": {
"type": "string"
}
},
"required": [
"command",
"threadId"
],
"title": "ThreadShellCommandParams",
"type": "object"
},
"ThreadShellCommandResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ThreadShellCommandResponse",
"type": "object"
},
"ThreadSortKey": {
"enum": [
"created_at",

View File

@@ -1026,6 +1026,30 @@
"title": "Thread/compact/startRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/shellCommand"
],
"title": "Thread/shellCommandRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadShellCommandParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/shellCommandRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -1194,54 +1218,6 @@
"title": "Plugin/readRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"skills/remote/list"
],
"title": "Skills/remote/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/SkillsRemoteReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Skills/remote/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"skills/remote/export"
],
"title": "Skills/remote/exportRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/SkillsRemoteWriteParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Skills/remote/exportRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -2802,6 +2778,15 @@
"title": "CommandExecutionOutputDeltaNotification",
"type": "object"
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -4375,23 +4360,12 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
"FuzzyFileSearchMatchType": {
"enum": [
"file",
"directory"
],
"type": "object"
"type": "string"
},
"FuzzyFileSearchParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
@@ -4436,6 +4410,9 @@
"null"
]
},
"match_type": {
"$ref": "#/definitions/FuzzyFileSearchMatchType"
},
"path": {
"type": "string"
},
@@ -4450,6 +4427,7 @@
},
"required": [
"file_name",
"match_type",
"path",
"root",
"score"
@@ -4666,15 +4644,6 @@
],
"type": "string"
},
"HazelnutScope": {
"enum": [
"example",
"workspace-shared",
"all-shared",
"personal"
],
"type": "string"
},
"HookCompletedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -4701,6 +4670,7 @@
"HookEventName": {
"enum": [
"sessionStart",
"userPromptSubmit",
"stop"
],
"type": "string"
@@ -4745,6 +4715,21 @@
],
"type": "string"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"HookRunStatus": {
"enum": [
"running",
@@ -5279,6 +5264,17 @@
"title": "LogoutAccountResponse",
"type": "object"
},
"MarketplaceInterface": {
"properties": {
"displayName": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"McpAuthStatus": {
"enum": [
"unsupported",
@@ -5451,6 +5447,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MergeStrategy": {
"enum": [
"replace",
@@ -5968,6 +6012,10 @@
"PluginInstallParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"forceRemoteSync": {
"description": "When true, apply the remote plugin change before the local install flow.",
"type": "boolean"
},
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
@@ -6139,6 +6187,13 @@
"PluginListResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"featuredPluginIds": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"marketplaces": {
"items": {
"$ref": "#/definitions/PluginMarketplaceEntry"
@@ -6160,6 +6215,16 @@
},
"PluginMarketplaceEntry": {
"properties": {
"interface": {
"anyOf": [
{
"$ref": "#/definitions/MarketplaceInterface"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
@@ -6282,6 +6347,10 @@
"PluginUninstallParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"forceRemoteSync": {
"description": "When true, apply the remote plugin change before the local uninstall flow.",
"type": "boolean"
},
"pluginId": {
"type": "string"
}
@@ -6297,15 +6366,6 @@
"title": "PluginUninstallResponse",
"type": "object"
},
"ProductSurface": {
"enum": [
"chatgpt",
"codex",
"api",
"atlas"
],
"type": "string"
},
"ProfileV2": {
"additionalProperties": true,
"properties": {
@@ -6562,6 +6622,13 @@
}
]
},
"RealtimeConversationVersion": {
"enum": [
"v1",
"v2"
],
"type": "string"
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -6763,25 +6830,6 @@
"title": "ReasoningTextDeltaNotification",
"type": "object"
},
"RemoteSkillSummary": {
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"description",
"id",
"name"
],
"type": "object"
},
"RequestId": {
"anyOf": [
{
@@ -6954,10 +7002,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -6973,7 +7017,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -7107,7 +7150,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -7171,8 +7214,14 @@
"call_id": {
"type": "string"
},
"name": {
"type": [
"string",
"null"
]
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -8761,6 +8810,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -9135,79 +9197,6 @@
"title": "SkillsListResponse",
"type": "object"
},
"SkillsRemoteReadParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"enabled": {
"default": false,
"type": "boolean"
},
"hazelnutScope": {
"allOf": [
{
"$ref": "#/definitions/HazelnutScope"
}
],
"default": "example"
},
"productSurface": {
"allOf": [
{
"$ref": "#/definitions/ProductSurface"
}
],
"default": "codex"
}
},
"title": "SkillsRemoteReadParams",
"type": "object"
},
"SkillsRemoteReadResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"data": {
"items": {
"$ref": "#/definitions/RemoteSkillSummary"
},
"type": "array"
}
},
"required": [
"data"
],
"title": "SkillsRemoteReadResponse",
"type": "object"
},
"SkillsRemoteWriteParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"hazelnutId": {
"type": "string"
}
},
"required": [
"hazelnutId"
],
"title": "SkillsRemoteWriteParams",
"type": "object"
},
"SkillsRemoteWriteResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"id": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"id",
"path"
],
"title": "SkillsRemoteWriteResponse",
"type": "object"
},
"SubAgentSource": {
"oneOf": [
{
@@ -9742,9 +9731,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -9884,6 +9911,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},
@@ -10563,6 +10598,12 @@
"data": {
"type": "string"
},
"itemId": {
"type": [
"string",
"null"
]
},
"numChannels": {
"format": "uint16",
"minimum": 0.0,
@@ -10673,10 +10714,14 @@
},
"threadId": {
"type": "string"
},
"version": {
"$ref": "#/definitions/RealtimeConversationVersion"
}
},
"required": [
"threadId"
"threadId",
"version"
],
"title": "ThreadRealtimeStartedNotification",
"type": "object"
@@ -10913,6 +10958,29 @@
"title": "ThreadSetNameResponse",
"type": "object"
},
"ThreadShellCommandParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"command": {
"description": "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.",
"type": "string"
},
"threadId": {
"type": "string"
}
},
"required": [
"command",
"threadId"
],
"title": "ThreadShellCommandParams",
"type": "object"
},
"ThreadShellCommandResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ThreadShellCommandResponse",
"type": "object"
},
"ThreadSortKey": {
"enum": [
"created_at",

View File

@@ -4,6 +4,7 @@
"HookEventName": {
"enum": [
"sessionStart",
"userPromptSubmit",
"stop"
],
"type": "string"

View File

@@ -4,6 +4,7 @@
"HookEventName": {
"enum": [
"sessionStart",
"userPromptSubmit",
"stop"
],
"type": "string"

View File

@@ -177,6 +177,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -257,6 +266,21 @@
],
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -289,6 +313,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -441,9 +513,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -583,6 +693,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -177,6 +177,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -257,6 +266,21 @@
],
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -289,6 +313,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -441,9 +513,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -583,6 +693,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -7,6 +7,10 @@
}
},
"properties": {
"forceRemoteSync": {
"description": "When true, apply the remote plugin change before the local install flow.",
"type": "boolean"
},
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
},

View File

@@ -5,6 +5,17 @@
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"MarketplaceInterface": {
"properties": {
"displayName": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"PluginAuthPolicy": {
"enum": [
"ON_INSTALL",
@@ -127,6 +138,16 @@
},
"PluginMarketplaceEntry": {
"properties": {
"interface": {
"anyOf": [
{
"$ref": "#/definitions/MarketplaceInterface"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
@@ -218,6 +239,13 @@
}
},
"properties": {
"featuredPluginIds": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"marketplaces": {
"items": {
"$ref": "#/definitions/PluginMarketplaceEntry"

View File

@@ -1,6 +1,10 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"forceRemoteSync": {
"description": "When true, apply the remote plugin change before the local uninstall flow.",
"type": "boolean"
},
"pluginId": {
"type": "string"
}

View File

@@ -133,24 +133,6 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"GhostCommit": {
"description": "Details of a ghost commit created from a repository state.",
"properties": {
@@ -413,10 +395,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -432,7 +410,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -566,7 +543,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -630,8 +607,14 @@
"call_id": {
"type": "string"
},
"name": {
"type": [
"string",
"null"
]
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -371,6 +380,21 @@
],
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -403,6 +427,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -555,9 +627,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -697,6 +807,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -1,47 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"HazelnutScope": {
"enum": [
"example",
"workspace-shared",
"all-shared",
"personal"
],
"type": "string"
},
"ProductSurface": {
"enum": [
"chatgpt",
"codex",
"api",
"atlas"
],
"type": "string"
}
},
"properties": {
"enabled": {
"default": false,
"type": "boolean"
},
"hazelnutScope": {
"allOf": [
{
"$ref": "#/definitions/HazelnutScope"
}
],
"default": "example"
},
"productSurface": {
"allOf": [
{
"$ref": "#/definitions/ProductSurface"
}
],
"default": "codex"
}
},
"title": "SkillsRemoteReadParams",
"type": "object"
}

View File

@@ -1,37 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"RemoteSkillSummary": {
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"description",
"id",
"name"
],
"type": "object"
}
},
"properties": {
"data": {
"items": {
"$ref": "#/definitions/RemoteSkillSummary"
},
"type": "array"
}
},
"required": [
"data"
],
"title": "SkillsRemoteReadResponse",
"type": "object"
}

View File

@@ -1,13 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"hazelnutId": {
"type": "string"
}
},
"required": [
"hazelnutId"
],
"title": "SkillsRemoteWriteParams",
"type": "object"
}

View File

@@ -1,17 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"id": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"id",
"path"
],
"title": "SkillsRemoteWriteResponse",
"type": "object"
}

View File

@@ -353,6 +353,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -456,6 +465,21 @@
},
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -488,6 +512,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -778,6 +850,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -1035,9 +1120,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -1177,6 +1300,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -394,6 +403,21 @@
},
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -426,6 +450,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -536,6 +608,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -793,9 +878,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -935,6 +1058,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -394,6 +403,21 @@
},
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -426,6 +450,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -536,6 +608,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -793,9 +878,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -935,6 +1058,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -394,6 +403,21 @@
},
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -426,6 +450,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -536,6 +608,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -793,9 +878,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -935,6 +1058,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -7,6 +7,12 @@
"data": {
"type": "string"
},
"itemId": {
"type": [
"string",
"null"
]
},
"numChannels": {
"format": "uint16",
"minimum": 0.0,

View File

@@ -1,5 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"RealtimeConversationVersion": {
"enum": [
"v1",
"v2"
],
"type": "string"
}
},
"description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.",
"properties": {
"sessionId": {
@@ -10,10 +19,14 @@
},
"threadId": {
"type": "string"
},
"version": {
"$ref": "#/definitions/RealtimeConversationVersion"
}
},
"required": [
"threadId"
"threadId",
"version"
],
"title": "ThreadRealtimeStartedNotification",
"type": "object"

View File

@@ -191,24 +191,6 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"GhostCommit": {
"description": "Details of a ghost commit created from a repository state.",
"properties": {
@@ -479,10 +461,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -498,7 +476,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -632,7 +609,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -696,8 +673,14 @@
"call_id": {
"type": "string"
},
"name": {
"type": [
"string",
"null"
]
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [

View File

@@ -353,6 +353,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -456,6 +465,21 @@
},
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -488,6 +512,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -778,6 +850,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -1035,9 +1120,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -1177,6 +1300,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -394,6 +403,21 @@
},
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -426,6 +450,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -536,6 +608,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -793,9 +878,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -935,6 +1058,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"command": {
"description": "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.",
"type": "string"
},
"threadId": {
"type": "string"
}
},
"required": [
"command",
"threadId"
],
"title": "ThreadShellCommandParams",
"type": "object"
}

View File

@@ -0,0 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ThreadShellCommandResponse",
"type": "object"
}

View File

@@ -353,6 +353,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -456,6 +465,21 @@
},
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -488,6 +512,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -778,6 +850,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -1035,9 +1120,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -1177,6 +1300,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -394,6 +403,21 @@
},
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -426,6 +450,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -536,6 +608,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -793,9 +878,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -935,6 +1058,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -394,6 +403,21 @@
},
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -426,6 +450,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -536,6 +608,19 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomSessionSource",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -793,9 +878,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -935,6 +1058,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -371,6 +380,21 @@
],
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -403,6 +427,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -555,9 +627,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -697,6 +807,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -371,6 +380,21 @@
],
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -403,6 +427,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -555,9 +627,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -697,6 +807,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -291,6 +291,15 @@
}
]
},
"CommandExecutionSource": {
"enum": [
"agent",
"userShell",
"unifiedExecStartup",
"unifiedExecInteraction"
],
"type": "string"
},
"CommandExecutionStatus": {
"enum": [
"inProgress",
@@ -371,6 +380,21 @@
],
"type": "object"
},
"HookPromptFragment": {
"properties": {
"hookRunId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"hookRunId",
"text"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -403,6 +427,54 @@
],
"type": "string"
},
"MemoryCitation": {
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/MemoryCitationEntry"
},
"type": "array"
},
"threadIds": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"entries",
"threadIds"
],
"type": "object"
},
"MemoryCitationEntry": {
"properties": {
"lineEnd": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"lineStart": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"note": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"lineEnd",
"lineStart",
"note",
"path"
],
"type": "object"
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
@@ -555,9 +627,47 @@
},
{
"properties": {
"fragments": {
"items": {
"$ref": "#/definitions/HookPromptFragment"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"hookPrompt"
],
"title": "HookPromptThreadItemType",
"type": "string"
}
},
"required": [
"fragments",
"id",
"type"
],
"title": "HookPromptThreadItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"memoryCitation": {
"anyOf": [
{
"$ref": "#/definitions/MemoryCitation"
},
{
"type": "null"
}
],
"default": null
},
"phase": {
"anyOf": [
{
@@ -697,6 +807,14 @@
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/CommandExecutionSource"
}
],
"default": "agent"
},
"status": {
"$ref": "#/definitions/CommandExecutionStatus"
},

View File

@@ -39,8 +39,6 @@ import type { PluginUninstallParams } from "./v2/PluginUninstallParams";
import type { ReviewStartParams } from "./v2/ReviewStartParams";
import type { SkillsConfigWriteParams } from "./v2/SkillsConfigWriteParams";
import type { SkillsListParams } from "./v2/SkillsListParams";
import type { SkillsRemoteReadParams } from "./v2/SkillsRemoteReadParams";
import type { SkillsRemoteWriteParams } from "./v2/SkillsRemoteWriteParams";
import type { ThreadArchiveParams } from "./v2/ThreadArchiveParams";
import type { ThreadCompactStartParams } from "./v2/ThreadCompactStartParams";
import type { ThreadForkParams } from "./v2/ThreadForkParams";
@@ -51,6 +49,7 @@ import type { ThreadReadParams } from "./v2/ThreadReadParams";
import type { ThreadResumeParams } from "./v2/ThreadResumeParams";
import type { ThreadRollbackParams } from "./v2/ThreadRollbackParams";
import type { ThreadSetNameParams } from "./v2/ThreadSetNameParams";
import type { ThreadShellCommandParams } from "./v2/ThreadShellCommandParams";
import type { ThreadStartParams } from "./v2/ThreadStartParams";
import type { ThreadUnarchiveParams } from "./v2/ThreadUnarchiveParams";
import type { ThreadUnsubscribeParams } from "./v2/ThreadUnsubscribeParams";
@@ -62,4 +61,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta
/**
* Request from the client to the server.
*/
export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "skills/remote/list", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/export", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, };
export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, };

View File

@@ -1,12 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { FunctionCallOutputBody } from "./FunctionCallOutputBody";
/**
* The payload we send back to OpenAI when reporting a tool call result.
*
* `body` serializes directly as the wire value for `function_call_output.output`.
* `success` remains internal metadata for downstream handling.
*/
export type FunctionCallOutputPayload = { body: FunctionCallOutputBody, success: boolean | null, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SkillsRemoteWriteParams = { hazelnutId: string, };
export type FuzzyFileSearchMatchType = "file" | "directory";

View File

@@ -1,8 +1,9 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { FuzzyFileSearchMatchType } from "./FuzzyFileSearchMatchType";
/**
* Superset of [`codex_file_search::FileMatch`]
*/
export type FuzzyFileSearchResult = { root: string, path: string, file_name: string, score: number, indices: Array<number> | null, };
export type FuzzyFileSearchResult = { root: string, path: string, match_type: FuzzyFileSearchMatchType, file_name: string, score: number, indices: Array<number> | null, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ProductSurface = "chatgpt" | "codex" | "api" | "atlas";
export type RealtimeConversationVersion = "v1" | "v2";

View File

@@ -2,7 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ContentItem } from "./ContentItem";
import type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload";
import type { FunctionCallOutputBody } from "./FunctionCallOutputBody";
import type { GhostCommit } from "./GhostCommit";
import type { LocalShellAction } from "./LocalShellAction";
import type { LocalShellStatus } from "./LocalShellStatus";
@@ -15,4 +15,4 @@ export type ResponseItem = { "type": "message", role: string, content: Array<Con
/**
* Set when using the Responses API.
*/
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, } | { "type": "ghost_snapshot", ghost_commit: GhostCommit, } | { "type": "compaction", encrypted_content: string, } | { "type": "other" };
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputBody, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, name?: string, output: FunctionCallOutputBody, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, } | { "type": "ghost_snapshot", ghost_commit: GhostCommit, } | { "type": "compaction", encrypted_content: string, } | { "type": "other" };

View File

@@ -3,4 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { SubAgentSource } from "./SubAgentSource";
export type SessionSource = "cli" | "vscode" | "exec" | "mcp" | { "subagent": SubAgentSource } | "unknown";
export type SessionSource = "cli" | "vscode" | "exec" | "mcp" | { "custom": string } | { "subagent": SubAgentSource } | "unknown";

View File

@@ -18,7 +18,7 @@ export type { FileChange } from "./FileChange";
export type { ForcedLoginMethod } from "./ForcedLoginMethod";
export type { FunctionCallOutputBody } from "./FunctionCallOutputBody";
export type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem";
export type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload";
export type { FuzzyFileSearchMatchType } from "./FuzzyFileSearchMatchType";
export type { FuzzyFileSearchParams } from "./FuzzyFileSearchParams";
export type { FuzzyFileSearchResponse } from "./FuzzyFileSearchResponse";
export type { FuzzyFileSearchResult } from "./FuzzyFileSearchResult";
@@ -50,6 +50,7 @@ export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction";
export type { ParsedCommand } from "./ParsedCommand";
export type { Personality } from "./Personality";
export type { PlanType } from "./PlanType";
export type { RealtimeConversationVersion } from "./RealtimeConversationVersion";
export type { ReasoningEffort } from "./ReasoningEffort";
export type { ReasoningItemContent } from "./ReasoningItemContent";
export type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSummary";

View File

@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CommandExecutionSource = "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction";

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type HookEventName = "sessionStart" | "stop";
export type HookEventName = "sessionStart" | "userPromptSubmit" | "stop";

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SkillsRemoteWriteResponse = { id: string, path: string, };
export type HookPromptFragment = { text: string, hookRunId: string, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type RemoteSkillSummary = { id: string, name: string, description: string, };
export type MarketplaceInterface = { displayName: string | null, };

View File

@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { MemoryCitationEntry } from "./MemoryCitationEntry";
export type MemoryCitation = { entries: Array<MemoryCitationEntry>, threadIds: Array<string>, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type HazelnutScope = "example" | "workspace-shared" | "all-shared" | "personal";
export type MemoryCitationEntry = { path: string, lineStart: number, lineEnd: number, note: string, };

View File

@@ -3,4 +3,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type PluginInstallParams = { marketplacePath: AbsolutePathBuf, pluginName: string, };
export type PluginInstallParams = { marketplacePath: AbsolutePathBuf, pluginName: string,
/**
* When true, apply the remote plugin change before the local install flow.
*/
forceRemoteSync?: boolean, };

View File

@@ -3,4 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PluginMarketplaceEntry } from "./PluginMarketplaceEntry";
export type PluginListResponse = { marketplaces: Array<PluginMarketplaceEntry>, remoteSyncError: string | null, };
export type PluginListResponse = { marketplaces: Array<PluginMarketplaceEntry>, remoteSyncError: string | null, featuredPluginIds: Array<string>, };

View File

@@ -2,6 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { MarketplaceInterface } from "./MarketplaceInterface";
import type { PluginSummary } from "./PluginSummary";
export type PluginMarketplaceEntry = { name: string, path: AbsolutePathBuf, plugins: Array<PluginSummary>, };
export type PluginMarketplaceEntry = { name: string, path: AbsolutePathBuf, interface: MarketplaceInterface | null, plugins: Array<PluginSummary>, };

View File

@@ -2,4 +2,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PluginUninstallParams = { pluginId: string, };
export type PluginUninstallParams = { pluginId: string,
/**
* When true, apply the remote plugin change before the local uninstall flow.
*/
forceRemoteSync?: boolean, };

View File

@@ -3,4 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { SubAgentSource } from "../SubAgentSource";
export type SessionSource = "cli" | "vscode" | "exec" | "appServer" | { "subAgent": SubAgentSource } | "unknown";
export type SessionSource = "cli" | "vscode" | "exec" | "appServer" | { "custom": string } | { "subAgent": SubAgentSource } | "unknown";

View File

@@ -1,7 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { HazelnutScope } from "./HazelnutScope";
import type { ProductSurface } from "./ProductSurface";
export type SkillsRemoteReadParams = { hazelnutScope: HazelnutScope, productSurface: ProductSurface, enabled: boolean, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RemoteSkillSummary } from "./RemoteSkillSummary";
export type SkillsRemoteReadResponse = { data: Array<RemoteSkillSummary>, };

View File

@@ -8,18 +8,21 @@ import type { CollabAgentState } from "./CollabAgentState";
import type { CollabAgentTool } from "./CollabAgentTool";
import type { CollabAgentToolCallStatus } from "./CollabAgentToolCallStatus";
import type { CommandAction } from "./CommandAction";
import type { CommandExecutionSource } from "./CommandExecutionSource";
import type { CommandExecutionStatus } from "./CommandExecutionStatus";
import type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem";
import type { DynamicToolCallStatus } from "./DynamicToolCallStatus";
import type { FileUpdateChange } from "./FileUpdateChange";
import type { HookPromptFragment } from "./HookPromptFragment";
import type { McpToolCallError } from "./McpToolCallError";
import type { McpToolCallResult } from "./McpToolCallResult";
import type { McpToolCallStatus } from "./McpToolCallStatus";
import type { MemoryCitation } from "./MemoryCitation";
import type { PatchApplyStatus } from "./PatchApplyStatus";
import type { UserInput } from "./UserInput";
import type { WebSearchAction } from "./WebSearchAction";
export type ThreadItem = { "type": "userMessage", id: string, content: Array<UserInput>, } | { "type": "agentMessage", id: string, text: string, phase: MessagePhase | null, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array<string>, content: Array<string>, } | { "type": "commandExecution", id: string,
export type ThreadItem = { "type": "userMessage", id: string, content: Array<UserInput>, } | { "type": "hookPrompt", id: string, fragments: Array<HookPromptFragment>, } | { "type": "agentMessage", id: string, text: string, phase: MessagePhase | null, memoryCitation: MemoryCitation | null, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array<string>, content: Array<string>, } | { "type": "commandExecution", id: string,
/**
* The command to be executed.
*/
@@ -31,7 +34,7 @@ cwd: string,
/**
* Identifier for the underlying PTY process (when available).
*/
processId: string | null, status: CommandExecutionStatus,
processId: string | null, source: CommandExecutionSource, status: CommandExecutionStatus,
/**
* A best-effort parsing of the command to understand the action(s) it will perform.
* This returns a list of CommandAction objects because a single shell command may

View File

@@ -5,4 +5,4 @@
/**
* EXPERIMENTAL - thread realtime audio chunk.
*/
export type ThreadRealtimeAudioChunk = { data: string, sampleRate: number, numChannels: number, samplesPerChannel: number | null, };
export type ThreadRealtimeAudioChunk = { data: string, sampleRate: number, numChannels: number, samplesPerChannel: number | null, itemId: string | null, };

View File

@@ -1,8 +1,9 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RealtimeConversationVersion } from "../RealtimeConversationVersion";
/**
* EXPERIMENTAL - emitted when thread realtime startup is accepted.
*/
export type ThreadRealtimeStartedNotification = { threadId: string, sessionId: string | null, };
export type ThreadRealtimeStartedNotification = { threadId: string, sessionId: string | null, version: RealtimeConversationVersion, };

View File

@@ -0,0 +1,12 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ThreadShellCommandParams = { threadId: string,
/**
* Shell command string evaluated by the thread's configured shell.
* Unlike `command/exec`, this intentionally preserves shell syntax
* such as pipes, redirects, and quoting. This runs unsandboxed with full
* access rather than inheriting the thread sandbox policy.
*/
command: string, };

View File

@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ThreadShellCommandResponse = Record<string, never>;

View File

@@ -55,6 +55,7 @@ export type { CommandExecutionOutputDeltaNotification } from "./CommandExecution
export type { CommandExecutionRequestApprovalParams } from "./CommandExecutionRequestApprovalParams";
export type { CommandExecutionRequestApprovalResponse } from "./CommandExecutionRequestApprovalResponse";
export type { CommandExecutionRequestApprovalSkillMetadata } from "./CommandExecutionRequestApprovalSkillMetadata";
export type { CommandExecutionSource } from "./CommandExecutionSource";
export type { CommandExecutionStatus } from "./CommandExecutionStatus";
export type { Config } from "./Config";
export type { ConfigBatchWriteParams } from "./ConfigBatchWriteParams";
@@ -119,13 +120,13 @@ export type { GrantedPermissionProfile } from "./GrantedPermissionProfile";
export type { GuardianApprovalReview } from "./GuardianApprovalReview";
export type { GuardianApprovalReviewStatus } from "./GuardianApprovalReviewStatus";
export type { GuardianRiskLevel } from "./GuardianRiskLevel";
export type { HazelnutScope } from "./HazelnutScope";
export type { HookCompletedNotification } from "./HookCompletedNotification";
export type { HookEventName } from "./HookEventName";
export type { HookExecutionMode } from "./HookExecutionMode";
export type { HookHandlerType } from "./HookHandlerType";
export type { HookOutputEntry } from "./HookOutputEntry";
export type { HookOutputEntryKind } from "./HookOutputEntryKind";
export type { HookPromptFragment } from "./HookPromptFragment";
export type { HookRunStatus } from "./HookRunStatus";
export type { HookRunSummary } from "./HookRunSummary";
export type { HookScope } from "./HookScope";
@@ -139,6 +140,7 @@ export type { ListMcpServerStatusResponse } from "./ListMcpServerStatusResponse"
export type { LoginAccountParams } from "./LoginAccountParams";
export type { LoginAccountResponse } from "./LoginAccountResponse";
export type { LogoutAccountResponse } from "./LogoutAccountResponse";
export type { MarketplaceInterface } from "./MarketplaceInterface";
export type { McpAuthStatus } from "./McpAuthStatus";
export type { McpElicitationArrayType } from "./McpElicitationArrayType";
export type { McpElicitationBooleanSchema } from "./McpElicitationBooleanSchema";
@@ -174,6 +176,8 @@ export type { McpToolCallError } from "./McpToolCallError";
export type { McpToolCallProgressNotification } from "./McpToolCallProgressNotification";
export type { McpToolCallResult } from "./McpToolCallResult";
export type { McpToolCallStatus } from "./McpToolCallStatus";
export type { MemoryCitation } from "./MemoryCitation";
export type { MemoryCitationEntry } from "./MemoryCitationEntry";
export type { MergeStrategy } from "./MergeStrategy";
export type { Model } from "./Model";
export type { ModelAvailabilityNux } from "./ModelAvailabilityNux";
@@ -210,7 +214,6 @@ export type { PluginSource } from "./PluginSource";
export type { PluginSummary } from "./PluginSummary";
export type { PluginUninstallParams } from "./PluginUninstallParams";
export type { PluginUninstallResponse } from "./PluginUninstallResponse";
export type { ProductSurface } from "./ProductSurface";
export type { ProfileV2 } from "./ProfileV2";
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
export type { RateLimitWindow } from "./RateLimitWindow";
@@ -220,7 +223,6 @@ export type { ReasoningEffortOption } from "./ReasoningEffortOption";
export type { ReasoningSummaryPartAddedNotification } from "./ReasoningSummaryPartAddedNotification";
export type { ReasoningSummaryTextDeltaNotification } from "./ReasoningSummaryTextDeltaNotification";
export type { ReasoningTextDeltaNotification } from "./ReasoningTextDeltaNotification";
export type { RemoteSkillSummary } from "./RemoteSkillSummary";
export type { RequestPermissionProfile } from "./RequestPermissionProfile";
export type { ResidencyRequirement } from "./ResidencyRequirement";
export type { ReviewDelivery } from "./ReviewDelivery";
@@ -246,10 +248,6 @@ export type { SkillsListEntry } from "./SkillsListEntry";
export type { SkillsListExtraRootsForCwd } from "./SkillsListExtraRootsForCwd";
export type { SkillsListParams } from "./SkillsListParams";
export type { SkillsListResponse } from "./SkillsListResponse";
export type { SkillsRemoteReadParams } from "./SkillsRemoteReadParams";
export type { SkillsRemoteReadResponse } from "./SkillsRemoteReadResponse";
export type { SkillsRemoteWriteParams } from "./SkillsRemoteWriteParams";
export type { SkillsRemoteWriteResponse } from "./SkillsRemoteWriteResponse";
export type { TerminalInteractionNotification } from "./TerminalInteractionNotification";
export type { TextElement } from "./TextElement";
export type { TextPosition } from "./TextPosition";
@@ -287,6 +285,8 @@ export type { ThreadRollbackParams } from "./ThreadRollbackParams";
export type { ThreadRollbackResponse } from "./ThreadRollbackResponse";
export type { ThreadSetNameParams } from "./ThreadSetNameParams";
export type { ThreadSetNameResponse } from "./ThreadSetNameResponse";
export type { ThreadShellCommandParams } from "./ThreadShellCommandParams";
export type { ThreadShellCommandResponse } from "./ThreadShellCommandResponse";
export type { ThreadSortKey } from "./ThreadSortKey";
export type { ThreadSourceKind } from "./ThreadSourceKind";
export type { ThreadStartParams } from "./ThreadStartParams";

View File

@@ -17,6 +17,7 @@ use crate::protocol::common::EXPERIMENTAL_CLIENT_METHODS;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use codex_protocol::protocol::RolloutLine;
use schemars::JsonSchema;
use schemars::schema_for;
use serde::Serialize;
@@ -182,7 +183,13 @@ pub fn generate_ts_with_options(
}
pub fn generate_json(out_dir: &Path) -> Result<()> {
generate_json_with_experimental(out_dir, false)
generate_json_with_experimental(out_dir, /*experimental_api*/ false)
}
pub fn generate_internal_json_schema(out_dir: &Path) -> Result<()> {
ensure_dir(out_dir)?;
write_json_schema::<RolloutLine>(out_dir, "RolloutLine")?;
Ok(())
}
pub fn generate_json_with_experimental(out_dir: &Path, experimental_api: bool) -> Result<()> {
@@ -1984,7 +1991,7 @@ pub(crate) fn generate_index_ts_tree(tree: &mut BTreeMap<PathBuf, String>) {
if !v2_entries.is_empty() {
tree.insert(
PathBuf::from("v2").join("index.ts"),
index_ts_entries(&v2_entries, false),
index_ts_entries(&v2_entries, /*has_v2_ts*/ false),
);
}
}

View File

@@ -6,6 +6,7 @@ mod schema_fixtures;
pub use experimental_api::*;
pub use export::GenerateTsOptions;
pub use export::generate_internal_json_schema;
pub use export::generate_json;
pub use export::generate_json_with_experimental;
pub use export::generate_ts;

View File

@@ -267,6 +267,10 @@ client_request_definitions! {
params: v2::ThreadCompactStartParams,
response: v2::ThreadCompactStartResponse,
},
ThreadShellCommand => "thread/shellCommand" {
params: v2::ThreadShellCommandParams,
response: v2::ThreadShellCommandResponse,
},
#[experimental("thread/backgroundTerminals/clean")]
ThreadBackgroundTerminalsClean => "thread/backgroundTerminals/clean" {
params: v2::ThreadBackgroundTerminalsCleanParams,
@@ -300,14 +304,6 @@ client_request_definitions! {
params: v2::PluginReadParams,
response: v2::PluginReadResponse,
},
SkillsRemoteList => "skills/remote/list" {
params: v2::SkillsRemoteReadParams,
response: v2::SkillsRemoteReadResponse,
},
SkillsRemoteExport => "skills/remote/export" {
params: v2::SkillsRemoteWriteParams,
response: v2::SkillsRemoteWriteResponse,
},
AppsList => "app/list" {
params: v2::AppsListParams,
response: v2::AppsListResponse,
@@ -808,11 +804,20 @@ pub struct FuzzyFileSearchParams {
pub struct FuzzyFileSearchResult {
pub root: String,
pub path: String,
pub match_type: FuzzyFileSearchMatchType,
pub file_name: String,
pub score: u32,
pub indices: Option<Vec<u32>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
pub enum FuzzyFileSearchMatchType {
File,
Directory,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct FuzzyFileSearchResponse {
pub files: Vec<FuzzyFileSearchResult>,
@@ -946,6 +951,7 @@ mod tests {
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
use codex_protocol::parse_command::ParsedCommand;
use codex_protocol::protocol::RealtimeConversationVersion;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use serde_json::json;
@@ -1577,6 +1583,7 @@ mod tests {
sample_rate: 24_000,
num_channels: 1,
samples_per_channel: Some(512),
item_id: None,
},
},
);
@@ -1589,7 +1596,8 @@ mod tests {
"data": "AQID",
"sampleRate": 24000,
"numChannels": 1,
"samplesPerChannel": 512
"samplesPerChannel": 512,
"itemId": null
}
}
}),
@@ -1626,6 +1634,7 @@ mod tests {
ServerNotification::ThreadRealtimeStarted(v2::ThreadRealtimeStartedNotification {
thread_id: "thr_123".to_string(),
session_id: Some("sess_456".to_string()),
version: RealtimeConversationVersion::V1,
});
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&notification);
assert_eq!(reason, Some("thread/realtime/started"));
@@ -1641,6 +1650,7 @@ mod tests {
sample_rate: 24_000,
num_channels: 1,
samples_per_channel: Some(512),
item_id: None,
},
},
);

View File

@@ -18,6 +18,7 @@ use crate::protocol::v2::TurnError;
use crate::protocol::v2::TurnStatus;
use crate::protocol::v2::UserInput;
use crate::protocol::v2::WebSearchAction;
use codex_protocol::items::parse_hook_prompt_message;
use codex_protocol::models::MessagePhase;
use codex_protocol::protocol::AgentReasoningEvent;
use codex_protocol::protocol::AgentReasoningRawContentEvent;
@@ -118,9 +119,11 @@ impl ThreadHistoryBuilder {
pub fn handle_event(&mut self, event: &EventMsg) {
match event {
EventMsg::UserMessage(payload) => self.handle_user_message(payload),
EventMsg::AgentMessage(payload) => {
self.handle_agent_message(payload.message.clone(), payload.phase.clone())
}
EventMsg::AgentMessage(payload) => self.handle_agent_message(
payload.message.clone(),
payload.phase.clone(),
payload.memory_citation.clone().map(Into::into),
),
EventMsg::AgentReasoning(payload) => self.handle_agent_reasoning(payload),
EventMsg::AgentReasoningRawContent(payload) => {
self.handle_agent_reasoning_raw_content(payload)
@@ -182,12 +185,37 @@ impl ThreadHistoryBuilder {
match item {
RolloutItem::EventMsg(event) => self.handle_event(event),
RolloutItem::Compacted(payload) => self.handle_compacted(payload),
RolloutItem::TurnContext(_)
| RolloutItem::SessionMeta(_)
| RolloutItem::ResponseItem(_) => {}
RolloutItem::ResponseItem(item) => self.handle_response_item(item),
RolloutItem::TurnContext(_) | RolloutItem::SessionMeta(_) => {}
}
}
fn handle_response_item(&mut self, item: &codex_protocol::models::ResponseItem) {
let codex_protocol::models::ResponseItem::Message {
role, content, id, ..
} = item
else {
return;
};
if role != "user" {
return;
}
let Some(hook_prompt) = parse_hook_prompt_message(id.as_ref(), content) else {
return;
};
self.ensure_turn().items.push(ThreadItem::HookPrompt {
id: hook_prompt.id,
fragments: hook_prompt
.fragments
.into_iter()
.map(crate::protocol::v2::HookPromptFragment::from)
.collect(),
});
}
fn handle_user_message(&mut self, payload: &UserMessageEvent) {
// User messages should stay in explicitly opened turns. For backward
// compatibility with older streams that did not open turns explicitly,
@@ -201,22 +229,30 @@ impl ThreadHistoryBuilder {
let mut turn = self
.current_turn
.take()
.unwrap_or_else(|| self.new_turn(None));
.unwrap_or_else(|| self.new_turn(/*id*/ None));
let id = self.next_item_id();
let content = self.build_user_inputs(payload);
turn.items.push(ThreadItem::UserMessage { id, content });
self.current_turn = Some(turn);
}
fn handle_agent_message(&mut self, text: String, phase: Option<MessagePhase>) {
fn handle_agent_message(
&mut self,
text: String,
phase: Option<MessagePhase>,
memory_citation: Option<crate::protocol::v2::MemoryCitation>,
) {
if text.is_empty() {
return;
}
let id = self.next_item_id();
self.ensure_turn()
.items
.push(ThreadItem::AgentMessage { id, text, phase });
self.ensure_turn().items.push(ThreadItem::AgentMessage {
id,
text,
phase,
memory_citation,
});
}
fn handle_agent_reasoning(&mut self, payload: &AgentReasoningEvent) {
@@ -271,6 +307,7 @@ impl ThreadHistoryBuilder {
);
}
codex_protocol::items::TurnItem::UserMessage(_)
| codex_protocol::items::TurnItem::HookPrompt(_)
| codex_protocol::items::TurnItem::AgentMessage(_)
| codex_protocol::items::TurnItem::Reasoning(_)
| codex_protocol::items::TurnItem::WebSearch(_)
@@ -291,6 +328,7 @@ impl ThreadHistoryBuilder {
);
}
codex_protocol::items::TurnItem::UserMessage(_)
| codex_protocol::items::TurnItem::HookPrompt(_)
| codex_protocol::items::TurnItem::AgentMessage(_)
| codex_protocol::items::TurnItem::Reasoning(_)
| codex_protocol::items::TurnItem::WebSearch(_)
@@ -331,6 +369,7 @@ impl ThreadHistoryBuilder {
command,
cwd: payload.cwd.clone(),
process_id: payload.process_id.clone(),
source: payload.source.into(),
status: CommandExecutionStatus::InProgress,
command_actions,
aggregated_output: None,
@@ -361,6 +400,7 @@ impl ThreadHistoryBuilder {
command,
cwd: payload.cwd.clone(),
process_id: payload.process_id.clone(),
source: payload.source.into(),
status,
command_actions,
aggregated_output,
@@ -937,7 +977,7 @@ impl ThreadHistoryBuilder {
fn ensure_turn(&mut self) -> &mut PendingTurn {
if self.current_turn.is_none() {
let turn = self.new_turn(None);
let turn = self.new_turn(/*id*/ None);
return self.current_turn.insert(turn);
}
@@ -1134,10 +1174,13 @@ impl From<&PendingTurn> for Turn {
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::v2::CommandExecutionSource;
use codex_protocol::ThreadId;
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
use codex_protocol::items::HookPromptFragment as CoreHookPromptFragment;
use codex_protocol::items::TurnItem as CoreTurnItem;
use codex_protocol::items::UserMessageItem as CoreUserMessageItem;
use codex_protocol::items::build_hook_prompt_message;
use codex_protocol::models::MessagePhase as CoreMessagePhase;
use codex_protocol::models::WebSearchAction as CoreWebSearchAction;
use codex_protocol::parse_command::ParsedCommand;
@@ -1178,6 +1221,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "Hi there".into(),
phase: None,
memory_citation: None,
}),
EventMsg::AgentReasoning(AgentReasoningEvent {
text: "thinking".into(),
@@ -1194,6 +1238,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "Reply two".into(),
phase: None,
memory_citation: None,
}),
];
@@ -1229,6 +1274,7 @@ mod tests {
id: "item-2".into(),
text: "Hi there".into(),
phase: None,
memory_citation: None,
}
);
assert_eq!(
@@ -1260,6 +1306,7 @@ mod tests {
id: "item-5".into(),
text: "Reply two".into(),
phase: None,
memory_citation: None,
}
);
}
@@ -1318,6 +1365,7 @@ mod tests {
let events = vec![EventMsg::AgentMessage(AgentMessageEvent {
message: "Final reply".into(),
phase: Some(CoreMessagePhase::FinalAnswer),
memory_citation: None,
})];
let items = events
@@ -1332,6 +1380,7 @@ mod tests {
id: "item-1".into(),
text: "Final reply".into(),
phase: Some(MessagePhase::FinalAnswer),
memory_citation: None,
}
);
}
@@ -1354,6 +1403,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "interlude".into(),
phase: None,
memory_citation: None,
}),
EventMsg::AgentReasoning(AgentReasoningEvent {
text: "second summary".into(),
@@ -1399,6 +1449,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "Working...".into(),
phase: None,
memory_citation: None,
}),
EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: Some("turn-1".into()),
@@ -1413,6 +1464,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "Second attempt complete.".into(),
phase: None,
memory_citation: None,
}),
];
@@ -1442,6 +1494,7 @@ mod tests {
id: "item-2".into(),
text: "Working...".into(),
phase: None,
memory_citation: None,
}
);
@@ -1464,6 +1517,7 @@ mod tests {
id: "item-4".into(),
text: "Second attempt complete.".into(),
phase: None,
memory_citation: None,
}
);
}
@@ -1480,6 +1534,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "A1".into(),
phase: None,
memory_citation: None,
}),
EventMsg::UserMessage(UserMessageEvent {
message: "Second".into(),
@@ -1490,6 +1545,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "A2".into(),
phase: None,
memory_citation: None,
}),
EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 1 }),
EventMsg::UserMessage(UserMessageEvent {
@@ -1501,6 +1557,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "A3".into(),
phase: None,
memory_citation: None,
}),
];
@@ -1529,6 +1586,7 @@ mod tests {
id: "item-2".into(),
text: "A1".into(),
phase: None,
memory_citation: None,
},
]
);
@@ -1546,6 +1604,7 @@ mod tests {
id: "item-4".into(),
text: "A3".into(),
phase: None,
memory_citation: None,
},
]
);
@@ -1563,6 +1622,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "A1".into(),
phase: None,
memory_citation: None,
}),
EventMsg::UserMessage(UserMessageEvent {
message: "Two".into(),
@@ -1573,6 +1633,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "A2".into(),
phase: None,
memory_citation: None,
}),
EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 99 }),
];
@@ -1717,6 +1778,7 @@ mod tests {
command: "echo 'hello world'".into(),
cwd: PathBuf::from("/tmp"),
process_id: Some("pid-1".into()),
source: CommandExecutionSource::Agent,
status: CommandExecutionStatus::Completed,
command_actions: vec![CommandAction::Unknown {
command: "echo hello world".into(),
@@ -1865,6 +1927,7 @@ mod tests {
command: "ls".into(),
cwd: PathBuf::from("/tmp"),
process_id: Some("pid-2".into()),
source: CommandExecutionSource::Agent,
status: CommandExecutionStatus::Declined,
command_actions: vec![CommandAction::Unknown {
command: "ls".into(),
@@ -1959,6 +2022,7 @@ mod tests {
command: "echo done".into(),
cwd: PathBuf::from("/tmp"),
process_id: Some("pid-42".into()),
source: CommandExecutionSource::Agent,
status: CommandExecutionStatus::Completed,
command_actions: vec![CommandAction::Unknown {
command: "echo done".into(),
@@ -2209,6 +2273,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "still in b".into(),
phase: None,
memory_citation: None,
}),
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-b".into(),
@@ -2263,6 +2328,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "still in b".into(),
phase: None,
memory_citation: None,
}),
];
@@ -2497,6 +2563,7 @@ mod tests {
EventMsg::AgentMessage(AgentMessageEvent {
message: "done".into(),
phase: None,
memory_citation: None,
}),
EventMsg::Error(ErrorEvent {
message: "rollback failed".into(),
@@ -2608,4 +2675,80 @@ mod tests {
})
);
}
#[test]
fn rebuilds_hook_prompt_items_from_rollout_response_items() {
let hook_prompt = build_hook_prompt_message(&[
CoreHookPromptFragment::from_single_hook("Retry with tests.", "hook-run-1"),
CoreHookPromptFragment::from_single_hook("Then summarize cleanly.", "hook-run-2"),
])
.expect("hook prompt message");
let items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
model_context_window: None,
collaboration_mode_kind: Default::default(),
})),
RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent {
message: "hello".into(),
images: None,
text_elements: Vec::new(),
local_images: Vec::new(),
})),
RolloutItem::ResponseItem(hook_prompt),
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
})),
];
let turns = build_turns_from_rollout_items(&items);
assert_eq!(turns.len(), 1);
assert_eq!(turns[0].items.len(), 2);
assert_eq!(
turns[0].items[1],
ThreadItem::HookPrompt {
id: turns[0].items[1].id().to_string(),
fragments: vec![
crate::protocol::v2::HookPromptFragment {
text: "Retry with tests.".into(),
hook_run_id: "hook-run-1".into(),
},
crate::protocol::v2::HookPromptFragment {
text: "Then summarize cleanly.".into(),
hook_run_id: "hook-run-2".into(),
},
],
}
);
}
#[test]
fn ignores_plain_user_response_items_in_rollout_replay() {
let items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
model_context_window: None,
collaboration_mode_kind: Default::default(),
})),
RolloutItem::ResponseItem(codex_protocol::models::ResponseItem::Message {
id: Some("msg-1".into()),
role: "user".into(),
content: vec![codex_protocol::models::ContentItem::InputText {
text: "plain text".into(),
}],
end_turn: None,
phase: None,
}),
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
})),
];
let turns = build_turns_from_rollout_items(&items);
assert_eq!(turns.len(), 1);
assert!(turns[0].items.is_empty());
}
}

View File

@@ -30,6 +30,8 @@ use codex_protocol::items::TurnItem as CoreTurnItem;
use codex_protocol::mcp::Resource as McpResource;
use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;
use codex_protocol::mcp::Tool as McpTool;
use codex_protocol::memory_citation::MemoryCitation as CoreMemoryCitation;
use codex_protocol::memory_citation::MemoryCitationEntry as CoreMemoryCitationEntry;
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
use codex_protocol::models::MacOsAutomationPermission as CoreMacOsAutomationPermission;
use codex_protocol::models::MacOsContactsPermission as CoreMacOsContactsPermission;
@@ -50,6 +52,7 @@ use codex_protocol::protocol::AgentStatus as CoreAgentStatus;
use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
use codex_protocol::protocol::ExecCommandSource as CoreExecCommandSource;
use codex_protocol::protocol::ExecCommandStatus as CoreExecCommandStatus;
use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig;
use codex_protocol::protocol::GuardianRiskLevel as CoreGuardianRiskLevel;
@@ -68,6 +71,7 @@ use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess;
use codex_protocol::protocol::RealtimeAudioFrame as CoreRealtimeAudioFrame;
use codex_protocol::protocol::RealtimeConversationVersion;
use codex_protocol::protocol::ReviewDecision as CoreReviewDecision;
use codex_protocol::protocol::SessionSource as CoreSessionSource;
use codex_protocol::protocol::SkillDependencies as CoreSkillDependencies;
@@ -89,6 +93,7 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
use serde_with::serde_as;
use thiserror::Error;
use ts_rs::TS;
@@ -342,7 +347,7 @@ v2_enum_from_core!(
v2_enum_from_core!(
pub enum HookEventName from CoreHookEventName {
SessionStart, Stop
SessionStart, UserPromptSubmit, Stop
}
);
@@ -1464,6 +1469,7 @@ pub enum SessionSource {
VsCode,
Exec,
AppServer,
Custom(String),
SubAgent(CoreSubAgentSource),
#[serde(other)]
Unknown,
@@ -1476,6 +1482,7 @@ impl From<CoreSessionSource> for SessionSource {
CoreSessionSource::VSCode => SessionSource::VsCode,
CoreSessionSource::Exec => SessionSource::Exec,
CoreSessionSource::Mcp => SessionSource::AppServer,
CoreSessionSource::Custom(source) => SessionSource::Custom(source),
CoreSessionSource::SubAgent(sub) => SessionSource::SubAgent(sub),
CoreSessionSource::Unknown => SessionSource::Unknown,
}
@@ -1489,6 +1496,7 @@ impl From<SessionSource> for CoreSessionSource {
SessionSource::VsCode => CoreSessionSource::VSCode,
SessionSource::Exec => CoreSessionSource::Exec,
SessionSource::AppServer => CoreSessionSource::Mcp,
SessionSource::Custom(source) => CoreSessionSource::Custom(source),
SessionSource::SubAgent(sub) => CoreSessionSource::SubAgent(sub),
SessionSource::Unknown => CoreSessionSource::Unknown,
}
@@ -2868,6 +2876,23 @@ pub struct ThreadCompactStartParams {
#[ts(export_to = "v2/")]
pub struct ThreadCompactStartResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadShellCommandParams {
pub thread_id: String,
/// Shell command string evaluated by the thread's configured shell.
/// Unlike `command/exec`, this intentionally preserves shell syntax
/// such as pipes, redirects, and quoting. This runs unsandboxed with full
/// access rather than inheriting the thread sandbox policy.
pub command: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadShellCommandResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -3090,6 +3115,8 @@ pub struct PluginListParams {
pub struct PluginListResponse {
pub marketplaces: Vec<PluginMarketplaceEntry>,
pub remote_sync_error: Option<String>,
#[serde(default)]
pub featured_plugin_ids: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -3107,73 +3134,6 @@ pub struct PluginReadResponse {
pub plugin: PluginDetail,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsRemoteReadParams {
#[serde(default)]
pub hazelnut_scope: HazelnutScope,
#[serde(default)]
pub product_surface: ProductSurface,
#[serde(default)]
pub enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, Default)]
#[serde(rename_all = "kebab-case")]
#[ts(rename_all = "kebab-case")]
#[ts(export_to = "v2/")]
pub enum HazelnutScope {
#[default]
Example,
WorkspaceShared,
AllShared,
Personal,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, Default)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum ProductSurface {
Chatgpt,
#[default]
Codex,
Api,
Atlas,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct RemoteSkillSummary {
pub id: String,
pub name: String,
pub description: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsRemoteReadResponse {
pub data: Vec<RemoteSkillSummary>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsRemoteWriteParams {
pub hazelnut_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsRemoteWriteResponse {
pub id: String,
pub path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
@@ -3276,9 +3236,17 @@ pub struct SkillsListEntry {
pub struct PluginMarketplaceEntry {
pub name: String,
pub path: AbsolutePathBuf,
pub interface: Option<MarketplaceInterface>,
pub plugins: Vec<PluginSummary>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceInterface {
pub display_name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginInstallPolicy {
@@ -3395,6 +3363,9 @@ pub struct SkillsConfigWriteResponse {
pub struct PluginInstallParams {
pub marketplace_path: AbsolutePathBuf,
pub plugin_name: String,
/// When true, apply the remote plugin change before the local install flow.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub force_remote_sync: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -3410,6 +3381,9 @@ pub struct PluginInstallResponse {
#[ts(export_to = "v2/")]
pub struct PluginUninstallParams {
pub plugin_id: String,
/// When true, apply the remote plugin change before the local uninstall flow.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub force_remote_sync: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -3620,6 +3594,44 @@ pub struct Turn {
pub error: Option<TurnError>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MemoryCitation {
pub entries: Vec<MemoryCitationEntry>,
pub thread_ids: Vec<String>,
}
impl From<CoreMemoryCitation> for MemoryCitation {
fn from(value: CoreMemoryCitation) -> Self {
Self {
entries: value.entries.into_iter().map(Into::into).collect(),
thread_ids: value.rollout_ids,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MemoryCitationEntry {
pub path: String,
pub line_start: u32,
pub line_end: u32,
pub note: String,
}
impl From<CoreMemoryCitationEntry> for MemoryCitationEntry {
fn from(value: CoreMemoryCitationEntry) -> Self {
Self {
path: value.path,
line_start: value.line_start,
line_end: value.line_end,
note: value.note,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Error)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -3652,6 +3664,7 @@ pub struct ThreadRealtimeAudioChunk {
pub sample_rate: u32,
pub num_channels: u16,
pub samples_per_channel: Option<u32>,
pub item_id: Option<String>,
}
impl From<CoreRealtimeAudioFrame> for ThreadRealtimeAudioChunk {
@@ -3661,12 +3674,14 @@ impl From<CoreRealtimeAudioFrame> for ThreadRealtimeAudioChunk {
sample_rate,
num_channels,
samples_per_channel,
item_id,
} = value;
Self {
data,
sample_rate,
num_channels,
samples_per_channel,
item_id,
}
}
}
@@ -3678,12 +3693,14 @@ impl From<ThreadRealtimeAudioChunk> for CoreRealtimeAudioFrame {
sample_rate,
num_channels,
samples_per_channel,
item_id,
} = value;
Self {
data,
sample_rate,
num_channels,
samples_per_channel,
item_id,
}
}
}
@@ -3756,6 +3773,7 @@ pub struct ThreadRealtimeStopResponse {}
pub struct ThreadRealtimeStartedNotification {
pub thread_id: String,
pub session_id: Option<String>,
pub version: RealtimeConversationVersion,
}
/// EXPERIMENTAL - raw non-audio thread realtime item emitted by the backend.
@@ -4109,11 +4127,19 @@ pub enum ThreadItem {
UserMessage { id: String, content: Vec<UserInput> },
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
HookPrompt {
id: String,
fragments: Vec<HookPromptFragment>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
AgentMessage {
id: String,
text: String,
#[serde(default)]
phase: Option<MessagePhase>,
#[serde(default)]
memory_citation: Option<MemoryCitation>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
@@ -4139,6 +4165,8 @@ pub enum ThreadItem {
cwd: PathBuf,
/// Identifier for the underlying PTY process (when available).
process_id: Option<String>,
#[serde(default)]
source: CommandExecutionSource,
status: CommandExecutionStatus,
/// A best-effort parsing of the command to understand the action(s) it will perform.
/// This returns a list of CommandAction objects because a single shell command may
@@ -4238,10 +4266,19 @@ pub enum ThreadItem {
ContextCompaction { id: String },
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
pub struct HookPromptFragment {
pub text: String,
pub hook_run_id: String,
}
impl ThreadItem {
pub fn id(&self) -> &str {
match self {
ThreadItem::UserMessage { id, .. }
| ThreadItem::HookPrompt { id, .. }
| ThreadItem::AgentMessage { id, .. }
| ThreadItem::Plan { id, .. }
| ThreadItem::Reasoning { id, .. }
@@ -4351,6 +4388,14 @@ impl From<CoreTurnItem> for ThreadItem {
id: user.id,
content: user.content.into_iter().map(UserInput::from).collect(),
},
CoreTurnItem::HookPrompt(hook_prompt) => ThreadItem::HookPrompt {
id: hook_prompt.id,
fragments: hook_prompt
.fragments
.into_iter()
.map(HookPromptFragment::from)
.collect(),
},
CoreTurnItem::AgentMessage(agent) => {
let text = agent
.content
@@ -4363,6 +4408,7 @@ impl From<CoreTurnItem> for ThreadItem {
id: agent.id,
text,
phase: agent.phase,
memory_citation: agent.memory_citation.map(Into::into),
}
}
CoreTurnItem::Plan(plan) => ThreadItem::Plan {
@@ -4392,6 +4438,15 @@ impl From<CoreTurnItem> for ThreadItem {
}
}
impl From<codex_protocol::items::HookPromptFragment> for HookPromptFragment {
fn from(value: codex_protocol::items::HookPromptFragment) -> Self {
Self {
text: value.text,
hook_run_id: value.hook_run_id,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -4418,6 +4473,17 @@ impl From<&CoreExecCommandStatus> for CommandExecutionStatus {
}
}
v2_enum_from_core! {
#[derive(Default)]
pub enum CommandExecutionSource from CoreExecCommandSource {
#[default]
Agent,
UserShell,
UnifiedExecStartup,
UnifiedExecInteraction,
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -4864,6 +4930,7 @@ pub struct TerminalInteractionNotification {
pub stdin: String,
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -6314,6 +6381,40 @@ mod tests {
assert_eq!(decoded, params);
}
#[test]
fn thread_shell_command_params_round_trip() {
let params = ThreadShellCommandParams {
thread_id: "thr_123".to_string(),
command: "printf 'hello world\\n'".to_string(),
};
let value = serde_json::to_value(&params).expect("serialize thread/shellCommand params");
assert_eq!(
value,
json!({
"threadId": "thr_123",
"command": "printf 'hello world\\n'",
})
);
let decoded = serde_json::from_value::<ThreadShellCommandParams>(value)
.expect("deserialize thread/shellCommand params");
assert_eq!(decoded, params);
}
#[test]
fn thread_shell_command_response_round_trip() {
let response = ThreadShellCommandResponse {};
let value =
serde_json::to_value(&response).expect("serialize thread/shellCommand response");
assert_eq!(value, json!({}));
let decoded = serde_json::from_value::<ThreadShellCommandResponse>(value)
.expect("deserialize thread/shellCommand response");
assert_eq!(decoded, response);
}
#[test]
fn command_exec_params_default_optional_streaming_flags() {
let params = serde_json::from_value::<CommandExecParams>(json!({
@@ -6608,6 +6709,32 @@ mod tests {
assert_eq!(decoded, notification);
}
#[test]
fn command_execution_output_delta_round_trips() {
let notification = CommandExecutionOutputDeltaNotification {
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
item_id: "item-1".to_string(),
delta: "\u{fffd}a\n".to_string(),
};
let value = serde_json::to_value(&notification)
.expect("serialize item/commandExecution/outputDelta notification");
assert_eq!(
value,
json!({
"threadId": "thread-1",
"turnId": "turn-1",
"itemId": "item-1",
"delta": "\u{fffd}a\n",
})
);
let decoded = serde_json::from_value::<CommandExecutionOutputDeltaNotification>(value)
.expect("deserialize round-trip");
assert_eq!(decoded, notification);
}
#[test]
fn sandbox_policy_round_trips_external_sandbox_network_access() {
let v2_policy = SandboxPolicy::ExternalSandbox {
@@ -7439,6 +7566,7 @@ mod tests {
},
],
phase: None,
memory_citation: None,
});
assert_eq!(
@@ -7447,6 +7575,7 @@ mod tests {
id: "agent-1".to_string(),
text: "Hello world".to_string(),
phase: None,
memory_citation: None,
}
);
@@ -7456,6 +7585,15 @@ mod tests {
text: "final".to_string(),
}],
phase: Some(MessagePhase::FinalAnswer),
memory_citation: Some(CoreMemoryCitation {
entries: vec![CoreMemoryCitationEntry {
path: "MEMORY.md".to_string(),
line_start: 1,
line_end: 2,
note: "summary".to_string(),
}],
rollout_ids: vec!["rollout-1".to_string()],
}),
});
assert_eq!(
@@ -7464,6 +7602,15 @@ mod tests {
id: "agent-2".to_string(),
text: "final".to_string(),
phase: Some(MessagePhase::FinalAnswer),
memory_citation: Some(MemoryCitation {
entries: vec![MemoryCitationEntry {
path: "MEMORY.md".to_string(),
line_start: 1,
line_end: 2,
note: "summary".to_string(),
}],
thread_ids: vec!["rollout-1".to_string()],
}),
}
);
@@ -7570,6 +7717,69 @@ mod tests {
);
}
#[test]
fn plugin_install_params_serialization_uses_force_remote_sync() {
let marketplace_path = if cfg!(windows) {
r"C:\plugins\marketplace.json"
} else {
"/plugins/marketplace.json"
};
let marketplace_path = AbsolutePathBuf::try_from(PathBuf::from(marketplace_path)).unwrap();
let marketplace_path_json = marketplace_path.as_path().display().to_string();
assert_eq!(
serde_json::to_value(PluginInstallParams {
marketplace_path: marketplace_path.clone(),
plugin_name: "gmail".to_string(),
force_remote_sync: false,
})
.unwrap(),
json!({
"marketplacePath": marketplace_path_json,
"pluginName": "gmail",
}),
);
assert_eq!(
serde_json::to_value(PluginInstallParams {
marketplace_path,
plugin_name: "gmail".to_string(),
force_remote_sync: true,
})
.unwrap(),
json!({
"marketplacePath": marketplace_path_json,
"pluginName": "gmail",
"forceRemoteSync": true,
}),
);
}
#[test]
fn plugin_uninstall_params_serialization_uses_force_remote_sync() {
assert_eq!(
serde_json::to_value(PluginUninstallParams {
plugin_id: "gmail@openai-curated".to_string(),
force_remote_sync: false,
})
.unwrap(),
json!({
"pluginId": "gmail@openai-curated",
}),
);
assert_eq!(
serde_json::to_value(PluginUninstallParams {
plugin_id: "gmail@openai-curated".to_string(),
force_remote_sync: true,
})
.unwrap(),
json!({
"pluginId": "gmail@openai-curated",
"forceRemoteSync": true,
}),
);
}
#[test]
fn codex_error_info_serializes_http_status_code_in_camel_case() {
let value = CodexErrorInfo::ResponseTooManyFailedAttempts {

View File

@@ -657,7 +657,7 @@ pub async fn send_message_v2(
&endpoint,
config_overrides,
user_message,
true,
/*experimental_api*/ true,
dynamic_tools,
)
.await
@@ -1510,7 +1510,7 @@ impl CodexClient {
}
fn initialize(&mut self) -> Result<InitializeResponse> {
self.initialize_with_experimental_api(true)
self.initialize_with_experimental_api(/*experimental_api*/ true)
}
fn initialize_with_experimental_api(

View File

@@ -32,6 +32,7 @@ axum = { workspace = true, default-features = false, features = [
codex-arg0 = { workspace = true }
codex-cloud-requirements = { workspace = true }
codex-core = { workspace = true }
codex-exec-server = { workspace = true }
codex-otel = { workspace = true }
codex-shell-command = { workspace = true }
codex-utils-cli = { workspace = true }
@@ -68,7 +69,6 @@ tokio-tungstenite = { workspace = true }
tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "json"] }
uuid = { workspace = true, features = ["serde", "v7"] }
walkdir = { workspace = true }
[dev-dependencies]
app_test_support = { workspace = true }

View File

@@ -29,7 +29,8 @@ Supported transports:
When running with `--listen ws://IP:PORT`, the same listener also serves basic HTTP health probes:
- `GET /readyz` returns `200 OK` once the listener is accepting new connections.
- `GET /healthz` currently always returns `200 OK`.
- `GET /healthz` returns `200 OK` when no `Origin` header is present.
- Any request carrying an `Origin` header is rejected with `403 Forbidden`.
Websocket transport is currently experimental and unsupported. Do not rely on it for production workloads.
@@ -114,10 +115,7 @@ Example with notification opt-out:
},
"capabilities": {
"experimentalApi": true,
"optOutNotificationMethods": [
"thread/started",
"item/agentMessage/delta"
]
"optOutNotificationMethods": ["thread/started", "item/agentMessage/delta"]
}
}
}
@@ -138,6 +136,7 @@ Example with notification opt-out:
- `thread/name/set` — set or update a threads user-facing name for either a loaded thread or a persisted rollout; returns `{}` on success and emits `thread/name/updated` to initialized, opted-in clients. Thread names are not required to be unique; name lookups resolve to the most recently updated thread.
- `thread/unarchive` — move an archived rollout file back into the sessions directory; returns the restored `thread` on success and emits `thread/unarchived`.
- `thread/compact/start` — trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications.
- `thread/shellCommand` — run a user-initiated `!` shell command against a thread; this runs unsandboxed with full access rather than inheriting the thread sandbox policy. Returns `{}` immediately while progress streams through standard turn/item notifications and any active turn receives the formatted output in its message stream.
- `thread/backgroundTerminals/clean` — terminate all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`); returns `{}` when the cleanup request is accepted.
- `thread/rollback` — drop the last N turns from the agents in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success.
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode".
@@ -164,11 +163,9 @@ Example with notification opt-out:
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.
- `skills/list` — list skills for one or more `cwd` values (optional `forceReload`).
- `plugin/list` — list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata. `interface.category` uses the marketplace category when present; otherwise it falls back to the plugin manifest category. Pass `forceRemoteSync: true` to refresh curated plugin state before listing (**under development; do not call from production clients yet**).
- `plugin/list` — list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata and best-effort `featuredPluginIds` for the official curated marketplace. `interface.category` uses the marketplace category when present; otherwise it falls back to the plugin manifest category. Pass `forceRemoteSync: true` to refresh curated plugin state before listing (**under development; do not call from production clients yet**).
- `plugin/read` — read one plugin by `marketplacePath` plus `pluginName`, returning marketplace info, a list-style `summary`, manifest descriptions/interface metadata, and bundled skills/apps/MCP server names (**under development; do not call from production clients yet**).
- `skills/changed` — notification emitted when watched local skill files change.
- `skills/remote/list` — list public remote skills (**under development; do not call from production clients yet**).
- `skills/remote/export` — download a remote skill by `hazelnutId` into `skills` under `codex_home` (**under development; do not call from production clients yet**).
- `app/list` — list available apps.
- `skills/config/write` — write user-level skill config by path.
- `plugin/install` — install a plugin from a discovered marketplace entry, rejecting marketplace entries marked unavailable for install, and return the effective plugin auth policy plus any apps that still need auth (**under development; do not call from production clients yet**).
@@ -229,7 +226,11 @@ Start a fresh thread when you need a new Codex conversation.
Valid `personality` values are `"friendly"`, `"pragmatic"`, and `"none"`. When `"none"` is selected, the personality placeholder is replaced with an empty string.
To continue a stored session, call `thread/resume` with the `thread.id` you previously recorded. The response shape matches `thread/start`, and no additional notifications are emitted. You can also pass the same configuration overrides supported by `thread/start`, including `approvalsReviewer`:
To continue a stored session, call `thread/resume` with the `thread.id` you previously recorded. The response shape matches `thread/start`, and no additional notifications are emitted. You can also pass the same configuration overrides supported by `thread/start`, including `approvalsReviewer`.
By default, resume uses the latest persisted `model` and `reasoningEffort` values associated with the thread. Supplying any of `model`, `modelProvider`, `config.model`, or `config.model_reasoning_effort` disables that persisted fallback and uses the explicit overrides plus normal config resolution instead.
Example:
```json
{ "method": "thread/resume", "id": 11, "params": {
@@ -302,10 +303,13 @@ When `nextCursor` is `null`, youve reached the final page.
- `thread/start`, `thread/fork`, and detached review threads do not emit a separate initial `thread/status/changed`; their `thread/started` notification already carries the current `thread.status`.
```json
{ "method": "thread/status/changed", "params": {
{
"method": "thread/status/changed",
"params": {
"threadId": "thr_123",
"status": { "type": "active", "activeFlags": [] }
} }
}
}
```
### Example: Unsubscribe from a loaded thread
@@ -412,6 +416,31 @@ While compaction is running, the thread is effectively in a turn so clients shou
{ "id": 25, "result": {} }
```
### Example: Run a thread shell command
Use `thread/shellCommand` for the TUI `!` workflow. The request returns immediately with `{}`.
This API runs unsandboxed with full access; it does not inherit the thread
sandbox policy.
If the thread already has an active turn, the command runs as an auxiliary action on that turn. In that case, progress is emitted as standard `item/*` notifications on the existing turn and the formatted output is injected into the turns message stream:
- `item/started` with `item: { "type": "commandExecution", "source": "userShell", ... }`
- zero or more `item/commandExecution/outputDelta`
- `item/completed` with the same `commandExecution` item id
If the thread does not already have an active turn, the server starts a standalone turn for the shell command. In that case clients should expect:
- `turn/started`
- `item/started` with `item: { "type": "commandExecution", "source": "userShell", ... }`
- zero or more `item/commandExecution/outputDelta`
- `item/completed` with the same `commandExecution` item id
- `turn/completed`
```json
{ "method": "thread/shellCommand", "id": 26, "params": { "threadId": "thr_b", "command": "git status --short" } }
{ "id": 26, "result": {} }
```
### Example: Start a turn (send user input)
Turns attach user input (text or images) to a thread and trigger Codex generation. The `input` field is a list of discriminated unions:
@@ -954,10 +983,7 @@ The built-in `request_permissions` tool sends an `item/permissions/requestApprov
"reason": "Select a workspace root",
"permissions": {
"fileSystem": {
"write": [
"/Users/me/project",
"/Users/me/shared"
]
"write": ["/Users/me/project", "/Users/me/shared"]
}
}
}
@@ -973,9 +999,7 @@ The client responds with `result.permissions`, which should be the granted subse
"scope": "session",
"permissions": {
"fileSystem": {
"write": [
"/Users/me/project"
]
"write": ["/Users/me/project"]
}
}
}

View File

@@ -78,7 +78,7 @@ pub(crate) fn typed_request_span(
.or(session.client_version.as_deref()),
);
attach_parent_context(&span, &method, request.id(), None);
attach_parent_context(&span, &method, request.id(), /*parent_trace*/ None);
span
}

View File

@@ -27,6 +27,7 @@ use codex_app_server_protocol::CommandExecutionOutputDeltaNotification;
use codex_app_server_protocol::CommandExecutionRequestApprovalParams;
use codex_app_server_protocol::CommandExecutionRequestApprovalResponse;
use codex_app_server_protocol::CommandExecutionRequestApprovalSkillMetadata;
use codex_app_server_protocol::CommandExecutionSource;
use codex_app_server_protocol::CommandExecutionStatus;
use codex_app_server_protocol::ContextCompactedNotification;
use codex_app_server_protocol::DeprecationNoticeNotification;
@@ -110,6 +111,7 @@ use codex_core::sandboxing::intersect_permission_profiles;
use codex_protocol::ThreadId;
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse;
use codex_protocol::items::parse_hook_prompt_message;
use codex_protocol::plan_tool::UpdatePlanArgs;
use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
@@ -338,6 +340,7 @@ pub(crate) async fn apply_bespoke_event_handling(
let notification = ThreadRealtimeStartedNotification {
thread_id: conversation_id.to_string(),
session_id: event.session_id,
version: event.version,
};
outgoing
.send_server_notification(ServerNotification::ThreadRealtimeStarted(
@@ -350,6 +353,20 @@ pub(crate) async fn apply_bespoke_event_handling(
if let ApiVersion::V2 = api_version {
match event.payload {
RealtimeEvent::SessionUpdated { .. } => {}
RealtimeEvent::InputAudioSpeechStarted(event) => {
let notification = ThreadRealtimeItemAddedNotification {
thread_id: conversation_id.to_string(),
item: serde_json::json!({
"type": "input_audio_buffer.speech_started",
"item_id": event.item_id,
}),
};
outgoing
.send_server_notification(ServerNotification::ThreadRealtimeItemAdded(
notification,
))
.await;
}
RealtimeEvent::InputTranscriptDelta(_) => {}
RealtimeEvent::OutputTranscriptDelta(_) => {}
RealtimeEvent::AudioOut(audio) => {
@@ -363,6 +380,20 @@ pub(crate) async fn apply_bespoke_event_handling(
)
.await;
}
RealtimeEvent::ResponseCancelled(event) => {
let notification = ThreadRealtimeItemAddedNotification {
thread_id: conversation_id.to_string(),
item: serde_json::json!({
"type": "response.cancelled",
"response_id": event.response_id,
}),
};
outgoing
.send_server_notification(ServerNotification::ThreadRealtimeItemAdded(
notification,
))
.await;
}
RealtimeEvent::ConversationItemAdded(item) => {
let notification = ThreadRealtimeItemAddedNotification {
thread_id: conversation_id.to_string(),
@@ -1454,6 +1485,14 @@ pub(crate) async fn apply_bespoke_event_handling(
.await;
}
EventMsg::RawResponseItem(raw_response_item_event) => {
maybe_emit_hook_prompt_item_completed(
api_version,
conversation_id,
&event_turn_id,
&raw_response_item_event.item,
&outgoing,
)
.await;
maybe_emit_raw_response_item_completed(
api_version,
conversation_id,
@@ -1534,6 +1573,7 @@ pub(crate) async fn apply_bespoke_event_handling(
command,
cwd,
process_id,
source: exec_command_begin_event.source.into(),
status: CommandExecutionStatus::InProgress,
command_actions,
aggregated_output: None,
@@ -1551,7 +1591,6 @@ pub(crate) async fn apply_bespoke_event_handling(
}
EventMsg::ExecCommandOutputDelta(exec_command_output_delta_event) => {
let item_id = exec_command_output_delta_event.call_id.clone();
let delta = String::from_utf8_lossy(&exec_command_output_delta_event.chunk).to_string();
// The underlying EventMsg::ExecCommandOutputDelta is used for shell, unified_exec,
// and apply_patch tool calls. We represent apply_patch with the FileChange item, and
// everything else with the CommandExecution item.
@@ -1563,6 +1602,8 @@ pub(crate) async fn apply_bespoke_event_handling(
state.turn_summary.file_change_started.contains(&item_id)
};
if is_file_change {
let delta =
String::from_utf8_lossy(&exec_command_output_delta_event.chunk).to_string();
let notification = FileChangeOutputDeltaNotification {
thread_id: conversation_id.to_string(),
turn_id: event_turn_id.clone(),
@@ -1579,7 +1620,8 @@ pub(crate) async fn apply_bespoke_event_handling(
thread_id: conversation_id.to_string(),
turn_id: event_turn_id.clone(),
item_id,
delta,
delta: String::from_utf8_lossy(&exec_command_output_delta_event.chunk)
.to_string(),
};
outgoing
.send_server_notification(ServerNotification::CommandExecutionOutputDelta(
@@ -1612,6 +1654,7 @@ pub(crate) async fn apply_bespoke_event_handling(
aggregated_output,
exit_code,
duration,
source,
status,
..
} = exec_command_end_event;
@@ -1643,6 +1686,7 @@ pub(crate) async fn apply_bespoke_event_handling(
command: shlex_join(&command),
cwd,
process_id,
source: source.into(),
status,
command_actions,
aggregated_output,
@@ -1906,6 +1950,7 @@ async fn complete_command_execution_item(
command: String,
cwd: PathBuf,
process_id: Option<String>,
source: CommandExecutionSource,
command_actions: Vec<V2ParsedCommand>,
status: CommandExecutionStatus,
outgoing: &ThreadScopedOutgoingMessageSender,
@@ -1915,6 +1960,7 @@ async fn complete_command_execution_item(
command,
cwd,
process_id,
source,
status,
command_actions,
aggregated_output: None,
@@ -1952,6 +1998,49 @@ async fn maybe_emit_raw_response_item_completed(
.await;
}
async fn maybe_emit_hook_prompt_item_completed(
api_version: ApiVersion,
conversation_id: ThreadId,
turn_id: &str,
item: &codex_protocol::models::ResponseItem,
outgoing: &ThreadScopedOutgoingMessageSender,
) {
let ApiVersion::V2 = api_version else {
return;
};
let codex_protocol::models::ResponseItem::Message {
role, content, id, ..
} = item
else {
return;
};
if role != "user" {
return;
}
let Some(hook_prompt) = parse_hook_prompt_message(id.as_ref(), content) else {
return;
};
let notification = ItemCompletedNotification {
thread_id: conversation_id.to_string(),
turn_id: turn_id.to_string(),
item: ThreadItem::HookPrompt {
id: hook_prompt.id,
fragments: hook_prompt
.fragments
.into_iter()
.map(codex_app_server_protocol::HookPromptFragment::from)
.collect(),
},
};
outgoing
.send_server_notification(ServerNotification::ItemCompleted(notification))
.await;
}
async fn find_and_remove_turn_summary(
_conversation_id: ThreadId,
thread_state: &Arc<Mutex<ThreadState>>,
@@ -1988,7 +2077,7 @@ async fn handle_turn_interrupted(
conversation_id,
event_turn_id,
TurnStatus::Interrupted,
None,
/*error*/ None,
outgoing,
)
.await;
@@ -2379,7 +2468,7 @@ fn render_review_output_text(output: &ReviewOutputEvent) -> String {
sections.push(explanation.to_string());
}
if !output.findings.is_empty() {
let findings = format_review_findings_block(&output.findings, None);
let findings = format_review_findings_block(&output.findings, /*selection*/ None);
let trimmed = findings.trim();
if !trimmed.is_empty() {
sections.push(trimmed.to_string());
@@ -2577,7 +2666,8 @@ async fn on_command_execution_request_approval_response(
item_id.clone(),
completion_item.command,
completion_item.cwd,
None,
/*process_id*/ None,
CommandExecutionSource::Agent,
completion_item.command_actions,
status,
&outgoing,
@@ -2722,6 +2812,8 @@ mod tests {
use codex_app_server_protocol::GuardianApprovalReviewStatus;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::TurnPlanStepStatus;
use codex_protocol::items::HookPromptFragment;
use codex_protocol::items::build_hook_prompt_message;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
@@ -3756,4 +3848,59 @@ mod tests {
assert!(rx.try_recv().is_err(), "no messages expected");
Ok(())
}
#[tokio::test]
async fn test_hook_prompt_raw_response_emits_item_completed() -> Result<()> {
let (tx, mut rx) = mpsc::channel(CHANNEL_CAPACITY);
let outgoing = Arc::new(OutgoingMessageSender::new(tx));
let conversation_id = ThreadId::new();
let outgoing = ThreadScopedOutgoingMessageSender::new(
outgoing,
vec![ConnectionId(1)],
conversation_id,
);
let item = build_hook_prompt_message(&[
HookPromptFragment::from_single_hook("Retry with tests.", "hook-run-1"),
HookPromptFragment::from_single_hook("Then summarize cleanly.", "hook-run-2"),
])
.expect("hook prompt message");
maybe_emit_hook_prompt_item_completed(
ApiVersion::V2,
conversation_id,
"turn-1",
&item,
&outgoing,
)
.await;
let msg = recv_broadcast_message(&mut rx).await?;
match msg {
OutgoingMessage::AppServerNotification(ServerNotification::ItemCompleted(
notification,
)) => {
assert_eq!(notification.thread_id, conversation_id.to_string());
assert_eq!(notification.turn_id, "turn-1");
assert_eq!(
notification.item,
ThreadItem::HookPrompt {
id: notification.item.id().to_string(),
fragments: vec![
codex_app_server_protocol::HookPromptFragment {
text: "Retry with tests.".into(),
hook_run_id: "hook-run-1".into(),
},
codex_app_server_protocol::HookPromptFragment {
text: "Then summarize cleanly.".into(),
hook_run_id: "hook-run-2".into(),
},
],
}
);
}
other => bail!("unexpected message: {other:?}"),
}
assert!(rx.try_recv().is_err(), "no extra messages expected");
Ok(())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -15,15 +15,16 @@ pub(super) async fn load_plugin_app_summaries(
return Vec::new();
}
let connectors = match connectors::list_all_connectors_with_options(config, false).await {
Ok(connectors) => connectors,
Err(err) => {
warn!("failed to load app metadata for plugin/read: {err:#}");
connectors::list_cached_all_connectors(config)
.await
.unwrap_or_default()
}
};
let connectors =
match connectors::list_all_connectors_with_options(config, /*force_refetch*/ false).await {
Ok(connectors) => connectors,
Err(err) => {
warn!("failed to load app metadata for plugin/read: {err:#}");
connectors::list_cached_all_connectors(config)
.await
.unwrap_or_default()
}
};
connectors::connectors_for_plugin_apps(connectors, plugin_apps)
.into_iter()

View File

@@ -201,7 +201,9 @@ impl CommandExecManager {
let sessions = Arc::clone(&self.sessions);
tokio::spawn(async move {
let _started_network_proxy = started_network_proxy;
match codex_core::sandboxing::execute_env(exec_request, None).await {
match codex_core::sandboxing::execute_env(exec_request, /*stdout_stream*/ None)
.await
{
Ok(output) => {
outgoing
.send_response(

View File

@@ -298,6 +298,7 @@ mod tests {
allowed_web_search_modes: Some(vec![
codex_core::config_loader::WebSearchModeRequirement::Cached,
]),
guardian_developer_instructions: None,
feature_requirements: Some(codex_core::config_loader::FeatureRequirementsToml {
entries: std::collections::BTreeMap::from([
("apps".to_string(), false),
@@ -374,6 +375,7 @@ mod tests {
allowed_approval_policies: None,
allowed_sandbox_modes: None,
allowed_web_search_modes: Some(Vec::new()),
guardian_developer_instructions: None,
feature_requirements: None,
mcp_servers: None,
apps: None,

View File

@@ -18,23 +18,37 @@ use codex_app_server_protocol::FsRemoveResponse;
use codex_app_server_protocol::FsWriteFileParams;
use codex_app_server_protocol::FsWriteFileResponse;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_exec_server::CopyOptions;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::Environment;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::RemoveOptions;
use std::io;
use std::path::Component;
use std::path::Path;
use std::path::PathBuf;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use walkdir::WalkDir;
use std::sync::Arc;
#[derive(Clone, Default)]
pub(crate) struct FsApi;
#[derive(Clone)]
pub(crate) struct FsApi {
file_system: Arc<dyn ExecutorFileSystem>,
}
impl Default for FsApi {
fn default() -> Self {
Self {
file_system: Arc::new(Environment::default().get_filesystem()),
}
}
}
impl FsApi {
pub(crate) async fn read_file(
&self,
params: FsReadFileParams,
) -> Result<FsReadFileResponse, JSONRPCErrorError> {
let bytes = tokio::fs::read(params.path).await.map_err(map_io_error)?;
let bytes = self
.file_system
.read_file(&params.path)
.await
.map_err(map_fs_error)?;
Ok(FsReadFileResponse {
data_base64: STANDARD.encode(bytes),
})
@@ -49,9 +63,10 @@ impl FsApi {
"fs/writeFile requires valid base64 dataBase64: {err}"
))
})?;
tokio::fs::write(params.path, bytes)
self.file_system
.write_file(&params.path, bytes)
.await
.map_err(map_io_error)?;
.map_err(map_fs_error)?;
Ok(FsWriteFileResponse {})
}
@@ -59,15 +74,15 @@ impl FsApi {
&self,
params: FsCreateDirectoryParams,
) -> Result<FsCreateDirectoryResponse, JSONRPCErrorError> {
if params.recursive.unwrap_or(true) {
tokio::fs::create_dir_all(params.path)
.await
.map_err(map_io_error)?;
} else {
tokio::fs::create_dir(params.path)
.await
.map_err(map_io_error)?;
}
self.file_system
.create_directory(
&params.path,
CreateDirectoryOptions {
recursive: params.recursive.unwrap_or(true),
},
)
.await
.map_err(map_fs_error)?;
Ok(FsCreateDirectoryResponse {})
}
@@ -75,14 +90,16 @@ impl FsApi {
&self,
params: FsGetMetadataParams,
) -> Result<FsGetMetadataResponse, JSONRPCErrorError> {
let metadata = tokio::fs::metadata(params.path)
let metadata = self
.file_system
.get_metadata(&params.path)
.await
.map_err(map_io_error)?;
.map_err(map_fs_error)?;
Ok(FsGetMetadataResponse {
is_directory: metadata.is_dir(),
is_file: metadata.is_file(),
created_at_ms: metadata.created().ok().map_or(0, system_time_to_unix_ms),
modified_at_ms: metadata.modified().ok().map_or(0, system_time_to_unix_ms),
is_directory: metadata.is_directory,
is_file: metadata.is_file,
created_at_ms: metadata.created_at_ms,
modified_at_ms: metadata.modified_at_ms,
})
}
@@ -90,232 +107,59 @@ impl FsApi {
&self,
params: FsReadDirectoryParams,
) -> Result<FsReadDirectoryResponse, JSONRPCErrorError> {
let mut entries = Vec::new();
let mut read_dir = tokio::fs::read_dir(params.path)
let entries = self
.file_system
.read_directory(&params.path)
.await
.map_err(map_io_error)?;
while let Some(entry) = read_dir.next_entry().await.map_err(map_io_error)? {
let metadata = tokio::fs::metadata(entry.path())
.await
.map_err(map_io_error)?;
entries.push(FsReadDirectoryEntry {
file_name: entry.file_name().to_string_lossy().into_owned(),
is_directory: metadata.is_dir(),
is_file: metadata.is_file(),
});
}
Ok(FsReadDirectoryResponse { entries })
.map_err(map_fs_error)?;
Ok(FsReadDirectoryResponse {
entries: entries
.into_iter()
.map(|entry| FsReadDirectoryEntry {
file_name: entry.file_name,
is_directory: entry.is_directory,
is_file: entry.is_file,
})
.collect(),
})
}
pub(crate) async fn remove(
&self,
params: FsRemoveParams,
) -> Result<FsRemoveResponse, JSONRPCErrorError> {
let path = params.path.as_path();
let recursive = params.recursive.unwrap_or(true);
let force = params.force.unwrap_or(true);
match tokio::fs::symlink_metadata(path).await {
Ok(metadata) => {
let file_type = metadata.file_type();
if file_type.is_dir() {
if recursive {
tokio::fs::remove_dir_all(path)
.await
.map_err(map_io_error)?;
} else {
tokio::fs::remove_dir(path).await.map_err(map_io_error)?;
}
} else {
tokio::fs::remove_file(path).await.map_err(map_io_error)?;
}
Ok(FsRemoveResponse {})
}
Err(err) if err.kind() == io::ErrorKind::NotFound && force => Ok(FsRemoveResponse {}),
Err(err) => Err(map_io_error(err)),
}
self.file_system
.remove(
&params.path,
RemoveOptions {
recursive: params.recursive.unwrap_or(true),
force: params.force.unwrap_or(true),
},
)
.await
.map_err(map_fs_error)?;
Ok(FsRemoveResponse {})
}
pub(crate) async fn copy(
&self,
params: FsCopyParams,
) -> Result<FsCopyResponse, JSONRPCErrorError> {
let FsCopyParams {
source_path,
destination_path,
recursive,
} = params;
tokio::task::spawn_blocking(move || -> Result<(), JSONRPCErrorError> {
let metadata =
std::fs::symlink_metadata(source_path.as_path()).map_err(map_io_error)?;
let file_type = metadata.file_type();
if file_type.is_dir() {
if !recursive {
return Err(invalid_request(
"fs/copy requires recursive: true when sourcePath is a directory",
));
}
if destination_is_same_or_descendant_of_source(
source_path.as_path(),
destination_path.as_path(),
)
.map_err(map_io_error)?
{
return Err(invalid_request(
"fs/copy cannot copy a directory to itself or one of its descendants",
));
}
copy_dir_recursive(source_path.as_path(), destination_path.as_path())
.map_err(map_io_error)?;
return Ok(());
}
if file_type.is_symlink() {
copy_symlink(source_path.as_path(), destination_path.as_path())
.map_err(map_io_error)?;
return Ok(());
}
if file_type.is_file() {
std::fs::copy(source_path.as_path(), destination_path.as_path())
.map_err(map_io_error)?;
return Ok(());
}
Err(invalid_request(
"fs/copy only supports regular files, directories, and symlinks",
))
})
.await
.map_err(map_join_error)??;
self.file_system
.copy(
&params.source_path,
&params.destination_path,
CopyOptions {
recursive: params.recursive,
},
)
.await
.map_err(map_fs_error)?;
Ok(FsCopyResponse {})
}
}
fn copy_dir_recursive(source: &Path, target: &Path) -> io::Result<()> {
for entry in WalkDir::new(source) {
let entry = entry.map_err(|err| {
if let Some(io_err) = err.io_error() {
io::Error::new(io_err.kind(), io_err.to_string())
} else {
io::Error::other(err.to_string())
}
})?;
let relative_path = entry.path().strip_prefix(source).map_err(|err| {
io::Error::other(format!(
"failed to compute relative path for {} under {}: {err}",
entry.path().display(),
source.display()
))
})?;
let target_path = target.join(relative_path);
let file_type = entry.file_type();
if file_type.is_dir() {
std::fs::create_dir_all(&target_path)?;
continue;
}
if file_type.is_file() {
std::fs::copy(entry.path(), &target_path)?;
continue;
}
if file_type.is_symlink() {
copy_symlink(entry.path(), &target_path)?;
continue;
}
// For now ignore special files such as FIFOs, sockets, and device nodes during recursive copies.
}
Ok(())
}
fn destination_is_same_or_descendant_of_source(
source: &Path,
destination: &Path,
) -> io::Result<bool> {
let source = std::fs::canonicalize(source)?;
let destination = resolve_copy_destination_path(destination)?;
Ok(destination.starts_with(&source))
}
fn resolve_copy_destination_path(path: &Path) -> io::Result<PathBuf> {
let mut normalized = PathBuf::new();
for component in path.components() {
match component {
Component::Prefix(prefix) => normalized.push(prefix.as_os_str()),
Component::RootDir => normalized.push(component.as_os_str()),
Component::CurDir => {}
Component::ParentDir => {
normalized.pop();
}
Component::Normal(part) => normalized.push(part),
}
}
let mut unresolved_suffix = Vec::new();
let mut existing_path = normalized.as_path();
while !existing_path.exists() {
let Some(file_name) = existing_path.file_name() else {
break;
};
unresolved_suffix.push(file_name.to_os_string());
let Some(parent) = existing_path.parent() else {
break;
};
existing_path = parent;
}
let mut resolved = std::fs::canonicalize(existing_path)?;
for file_name in unresolved_suffix.iter().rev() {
resolved.push(file_name);
}
Ok(resolved)
}
fn copy_symlink(source: &Path, target: &Path) -> io::Result<()> {
let link_target = std::fs::read_link(source)?;
#[cfg(unix)]
{
std::os::unix::fs::symlink(&link_target, target)
}
#[cfg(windows)]
{
if symlink_points_to_directory(source)? {
std::os::windows::fs::symlink_dir(&link_target, target)
} else {
std::os::windows::fs::symlink_file(&link_target, target)
}
}
#[cfg(not(any(unix, windows)))]
{
let _ = link_target;
let _ = target;
Err(io::Error::new(
io::ErrorKind::Unsupported,
"copying symlinks is unsupported on this platform",
))
}
}
#[cfg(windows)]
fn symlink_points_to_directory(source: &Path) -> io::Result<bool> {
use std::os::windows::fs::FileTypeExt;
Ok(std::fs::symlink_metadata(source)?
.file_type()
.is_symlink_dir())
}
fn system_time_to_unix_ms(time: SystemTime) -> i64 {
time.duration_since(UNIX_EPOCH)
.ok()
.and_then(|duration| i64::try_from(duration.as_millis()).ok())
.unwrap_or(0)
}
pub(crate) fn invalid_request(message: impl Into<String>) -> JSONRPCErrorError {
fn invalid_request(message: impl Into<String>) -> JSONRPCErrorError {
JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: message.into(),
@@ -323,43 +167,14 @@ pub(crate) fn invalid_request(message: impl Into<String>) -> JSONRPCErrorError {
}
}
fn map_join_error(err: tokio::task::JoinError) -> JSONRPCErrorError {
JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: format!("filesystem task failed: {err}"),
data: None,
}
}
pub(crate) fn map_io_error(err: io::Error) -> JSONRPCErrorError {
JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: err.to_string(),
data: None,
}
}
#[cfg(all(test, windows))]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn symlink_points_to_directory_handles_dangling_directory_symlinks() -> io::Result<()> {
use std::os::windows::fs::symlink_dir;
let temp_dir = tempfile::TempDir::new()?;
let source_dir = temp_dir.path().join("source");
let link_path = temp_dir.path().join("source-link");
std::fs::create_dir(&source_dir)?;
if symlink_dir(&source_dir, &link_path).is_err() {
return Ok(());
fn map_fs_error(err: io::Error) -> JSONRPCErrorError {
if err.kind() == io::ErrorKind::InvalidInput {
invalid_request(err.to_string())
} else {
JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: err.to_string(),
data: None,
}
std::fs::remove_dir(&source_dir)?;
assert_eq!(symlink_points_to_directory(&link_path)?, true);
Ok(())
}
}

View File

@@ -5,6 +5,7 @@ use std::sync::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use codex_app_server_protocol::FuzzyFileSearchMatchType;
use codex_app_server_protocol::FuzzyFileSearchResult;
use codex_app_server_protocol::FuzzyFileSearchSessionCompletedNotification;
use codex_app_server_protocol::FuzzyFileSearchSessionUpdatedNotification;
@@ -60,6 +61,10 @@ pub(crate) async fn run_fuzzy_file_search(
FuzzyFileSearchResult {
root: m.root.to_string_lossy().to_string(),
path: m.path.to_string_lossy().to_string(),
match_type: match m.match_type {
file_search::MatchType::File => FuzzyFileSearchMatchType::File,
file_search::MatchType::Directory => FuzzyFileSearchMatchType::Directory,
},
file_name: file_name.to_string_lossy().to_string(),
score: m.score,
indices: m.indices,
@@ -231,6 +236,10 @@ fn collect_files(snapshot: &file_search::FileSearchSnapshot) -> Vec<FuzzyFileSea
FuzzyFileSearchResult {
root: m.root.to_string_lossy().to_string(),
path: m.path.to_string_lossy().to_string(),
match_type: match m.match_type {
file_search::MatchType::File => FuzzyFileSearchMatchType::File,
file_search::MatchType::Directory => FuzzyFileSearchMatchType::Directory,
},
file_name: file_name.to_string_lossy().to_string(),
score: m.score,
indices: m.indices.clone(),

View File

@@ -390,8 +390,8 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
Arc::clone(&outbound_initialized),
Arc::clone(&outbound_experimental_api_enabled),
Arc::clone(&outbound_opted_out_notification_methods),
true,
None,
/*allow_legacy_notifications*/ true,
/*disconnect_sender*/ None,
),
);
let mut outbound_handle = tokio::spawn(async move {

View File

@@ -266,10 +266,10 @@ fn app_text_range(range: &CoreTextRange) -> AppTextRange {
fn project_config_warning(config: &Config) -> Option<ConfigWarningNotification> {
let mut disabled_folders = Vec::new();
for layer in config
.config_layer_stack
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
{
for layer in config.config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
) {
if !matches!(layer.name, ConfigLayerSource::Project { .. })
|| layer.disabled_reason.is_none()
{
@@ -336,6 +336,7 @@ pub async fn run_main(
loader_overrides,
default_analytics_enabled,
AppServerTransport::Stdio,
SessionSource::VSCode,
)
.await
}
@@ -346,6 +347,7 @@ pub async fn run_main_with_transport(
loader_overrides: LoaderOverrides,
default_analytics_enabled: bool,
transport: AppServerTransport,
session_source: SessionSource,
) -> IoResult<()> {
let (transport_event_tx, mut transport_event_rx) =
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
@@ -418,7 +420,7 @@ pub async fn run_main_with_transport(
let auth_manager = AuthManager::shared(
config.codex_home.clone(),
false,
/*enable_codex_api_key_env*/ false,
config.cli_auth_credentials_store_mode,
);
cloud_requirements_loader(
@@ -477,6 +479,14 @@ pub async fn run_main_with_transport(
range: None,
});
}
if let Some(warning) = codex_core::config::missing_system_bwrap_warning() {
config_warnings.push(ConfigWarningNotification {
summary: warning,
details: None,
path: None,
range: None,
});
}
let feedback = CodexFeedback::new();
@@ -613,7 +623,7 @@ pub async fn run_main_with_transport(
feedback: feedback.clone(),
log_db,
config_warnings,
session_source: SessionSource::VSCode,
session_source,
enable_codex_api_key_env: false,
});
let mut thread_created_rx = processor.thread_created_receiver();

View File

@@ -4,6 +4,7 @@ use codex_app_server::run_main_with_transport;
use codex_arg0::Arg0DispatchPaths;
use codex_arg0::arg0_dispatch_or_else;
use codex_core::config_loader::LoaderOverrides;
use codex_protocol::protocol::SessionSource;
use codex_utils_cli::CliConfigOverrides;
use std::path::PathBuf;
@@ -21,6 +22,15 @@ struct AppServerArgs {
default_value = AppServerTransport::DEFAULT_LISTEN_URL
)]
listen: AppServerTransport,
/// Session source used to derive product restrictions and metadata.
#[arg(
long = "session-source",
value_name = "SOURCE",
default_value = "vscode",
value_parser = SessionSource::from_startup_arg
)]
session_source: SessionSource,
}
fn main() -> anyhow::Result<()> {
@@ -32,13 +42,15 @@ fn main() -> anyhow::Result<()> {
..Default::default()
};
let transport = args.listen;
let session_source = args.session_source;
run_main_with_transport(
arg0_paths,
CliConfigOverrides::default(),
loader_overrides,
false,
/*default_analytics_enabled*/ false,
transport,
session_source,
)
.await?;
Ok(())

View File

@@ -228,10 +228,7 @@ impl MessageProcessor {
thread_manager
.plugins_manager()
.set_analytics_events_client(analytics_events_client.clone());
// TODO(xl): Move into PluginManager once this no longer depends on config feature gating.
thread_manager
.plugins_manager()
.maybe_start_curated_repo_sync_for_config(&config);
let cloud_requirements = Arc::new(RwLock::new(cloud_requirements));
let codex_message_processor = CodexMessageProcessor::new(CodexMessageProcessorArgs {
auth_manager: auth_manager.clone(),
@@ -244,6 +241,11 @@ impl MessageProcessor {
feedback,
log_db,
});
// Keep plugin startup warmups aligned at app-server startup.
// TODO(xl): Move into PluginManager once this no longer depends on config feature gating.
thread_manager
.plugins_manager()
.maybe_start_curated_repo_sync_for_config(&config, auth_manager.clone());
let config_api = ConfigApi::new(
config.codex_home.clone(),
cli_overrides,
@@ -253,7 +255,7 @@ impl MessageProcessor {
analytics_events_client,
);
let external_agent_config_api = ExternalAgentConfigApi::new(config.codex_home.clone());
let fs_api = FsApi;
let fs_api = FsApi::default();
Self {
outgoing,
@@ -332,7 +334,7 @@ impl MessageProcessor {
request_id.clone(),
codex_request,
session,
None,
/*outbound_initialized*/ None,
request_context.clone(),
)
.await;
@@ -358,7 +360,8 @@ impl MessageProcessor {
};
let request_span =
crate::app_server_tracing::typed_request_span(&request, connection_id, session);
let request_context = RequestContext::new(request_id.clone(), request_span, None);
let request_context =
RequestContext::new(request_id.clone(), request_span, /*parent_trace*/ None);
tracing::trace!(
?connection_id,
request_id = ?request_id.request_id,

View File

@@ -234,7 +234,10 @@ impl OutgoingMessageSender {
&self,
request: ServerRequestPayload,
) -> (RequestId, oneshot::Receiver<ClientRequestResult>) {
self.send_request_to_connections(None, request, None).await
self.send_request_to_connections(
/*connection_ids*/ None, request, /*thread_id*/ None,
)
.await
}
fn next_request_id(&self) -> RequestId {

View File

@@ -284,7 +284,7 @@ impl ThreadStateManager {
{
let mut thread_state_guard = thread_state.lock().await;
if experimental_raw_events {
thread_state_guard.set_experimental_raw_events(true);
thread_state_guard.set_experimental_raw_events(/*enabled*/ true);
}
}
Some(thread_state)

View File

@@ -91,13 +91,17 @@ impl ThreadWatchManager {
}
pub(crate) async fn upsert_thread(&self, thread: Thread) {
self.mutate_and_publish(move |state| state.upsert_thread(thread.id, true))
.await;
self.mutate_and_publish(move |state| {
state.upsert_thread(thread.id, /*emit_notification*/ true)
})
.await;
}
pub(crate) async fn upsert_thread_silently(&self, thread: Thread) {
self.mutate_and_publish(move |state| state.upsert_thread(thread.id, false))
.await;
self.mutate_and_publish(move |state| {
state.upsert_thread(thread.id, /*emit_notification*/ false)
})
.await;
}
pub(crate) async fn remove_thread(&self, thread_id: &str) {

View File

@@ -5,13 +5,19 @@ use crate::outgoing_message::OutgoingEnvelope;
use crate::outgoing_message::OutgoingError;
use crate::outgoing_message::OutgoingMessage;
use axum::Router;
use axum::body::Body;
use axum::extract::ConnectInfo;
use axum::extract::State;
use axum::extract::ws::Message as WebSocketMessage;
use axum::extract::ws::WebSocket;
use axum::extract::ws::WebSocketUpgrade;
use axum::http::Request;
use axum::http::StatusCode;
use axum::http::header::ORIGIN;
use axum::middleware;
use axum::middleware::Next;
use axum::response::IntoResponse;
use axum::response::Response;
use axum::routing::any;
use axum::routing::get;
use codex_app_server_protocol::JSONRPCErrorError;
@@ -91,6 +97,22 @@ async fn health_check_handler() -> StatusCode {
StatusCode::OK
}
async fn reject_requests_with_origin_header(
request: Request<Body>,
next: Next,
) -> Result<Response, StatusCode> {
if request.headers().contains_key(ORIGIN) {
warn!(
method = %request.method(),
uri = %request.uri(),
"rejecting websocket listener request with Origin header"
);
Err(StatusCode::FORBIDDEN)
} else {
Ok(next.run(request).await)
}
}
async fn websocket_upgrade_handler(
websocket: WebSocketUpgrade,
ConnectInfo(peer_addr): ConnectInfo<SocketAddr>,
@@ -322,6 +344,7 @@ pub(crate) async fn start_websocket_acceptor(
.route("/readyz", get(health_check_handler))
.route("/healthz", get(health_check_handler))
.fallback(any(websocket_upgrade_handler))
.layer(middleware::from_fn(reject_requests_with_origin_header))
.with_state(WebSocketListenerState {
transport_event_tx,
connection_counter: Arc::new(AtomicU64::new(1)),

Some files were not shown because too many files have changed in this diff Show More