Compare commits

..

44 Commits

Author SHA1 Message Date
Michael Bolin
decaf182ca fix: tighten up shell arg quoting in GitHub workflows 2026-03-16 16:48:40 -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
iceweasel-oai
d0a693e541 windows-sandbox: add runner IPC foundation for future unified_exec (#14139)
# Summary

This PR introduces the Windows sandbox runner IPC foundation that later
unified_exec work will build on.

The key point is that this is intentionally infrastructure-only. The new
IPC transport, runner plumbing, and ConPTY helpers are added here, but
the active elevated Windows sandbox path still uses the existing
request-file bootstrap. In other words, this change prepares the
transport and module layout we need for unified_exec without switching
production behavior over yet.

Part of this PR is also a source-layout cleanup: some Windows sandbox
files are moved into more explicit `elevated/`, `conpty/`, and shared
locations so it is clearer which code is for the elevated sandbox flow,
which code is legacy/direct-spawn behavior, and which helpers are shared
between them. That reorganization is intentional in this first PR so
later behavioral changes do not also have to carry a large amount of
file-move churn.

# Why This Is Needed For unified_exec

Windows elevated sandboxed unified_exec needs a long-lived,
bidirectional control channel between the CLI and a helper process
running under the sandbox user. That channel has to support:

- starting a process and reporting structured spawn success/failure
- streaming stdout/stderr back incrementally
- forwarding stdin over time
- terminating or polling a long-lived process
- supporting both pipe-backed and PTY-backed sessions

The existing elevated one-shot path is built around a request-file
bootstrap and does not provide those primitives cleanly. Before we can
turn on Windows sandbox unified_exec, we need the underlying runner
protocol and transport layer that can carry those lifecycle events and
streams.

# Why Windows Needs More Machinery Than Linux Or macOS

Linux and macOS can generally build unified_exec on top of the existing
sandbox/process model: the parent can spawn the child directly, retain
normal ownership of stdio or PTY handles, and manage the lifetime of the
sandboxed process without introducing a second control process.

Windows elevated sandboxing is different. To run inside the sandbox
boundary, we cross into a different user/security context and then need
to manage a long-lived process from outside that boundary. That means we
need an explicit helper process plus an IPC transport to carry spawn,
stdin, output, and exit events back and forth. The extra code here is
mostly that missing Windows sandbox infrastructure, not a conceptual
difference in unified_exec itself.

# What This PR Adds

- the framed IPC message types and transport helpers for parent <->
runner communication
- the renamed Windows command runner with both the existing request-file
bootstrap and the dormant IPC bootstrap
- named-pipe helpers for the elevated runner path
- ConPTY helpers and process-thread attribute plumbing needed for
PTY-backed sessions
- shared sandbox/process helpers that later PRs will reuse when
switching live execution paths over
- early file/module moves so later PRs can focus on behavior rather than
layout churn

# What This PR Does Not Yet Do

- it does not switch the active elevated one-shot path over to IPC yet
- it does not enable Windows sandbox unified_exec yet
- it does not remove the existing request-file bootstrap yet

So while this code compiles and the new path has basic validation, it is
not yet the exercised production path. That is intentional for this
first PR: the goal here is to land the transport and runner foundation
cleanly before later PRs start routing real command execution through
it.

# Follow-Ups

Planned follow-up PRs will:

1. switch elevated one-shot Windows sandbox execution to the new runner
IPC path
2. layer Windows sandbox unified_exec sessions on top of the same
transport
3. remove the legacy request-file path once the IPC-based path is live

# Validation

- `cargo build -p codex-windows-sandbox`
2026-03-16 19:45:06 +00:00
Andi Liu
4c9dbc1f88 memories: exclude AGENTS and skills from stage1 input (#14268)
###### Why/Context/Summary
- Exclude injected AGENTS.md instructions and standalone skill payloads
from memory stage 1 inputs so memory generation focuses on conversation
content instead of prompt scaffolding.
- Strip only the AGENTS fragment from mixed contextual user messages
during stage-1 serialization, which preserves environment context in the
same message.
- Keep subagent notifications in the memory input, and add focused unit
coverage for the fragment classifier, rollout policy, and stage-1
serialization path.

###### Test plan
- `just fmt`
- `cargo test -p codex-core --lib contextual_user_message`
- `cargo test -p codex-core --lib rollout::policy`
- `cargo test -p codex-core --lib memories::phase1`
2026-03-16 19:30:38 +00:00
Anton Panasenko
663dd3f935 fix(core): fix sanitize name to use '_' everywhere (#14833) 2026-03-16 12:22:10 -07:00
Eric Traut
a0e41f4ff9 Fixed build failures related to PR 14717 (#14826) 2026-03-16 12:41:25 -06:00
Jack Mousseau
7a6e30b55b Use request permission profile in app server (#14665) 2026-03-16 10:12:23 -07:00
Eric Traut
db89b73a9c Move TUI on top of app server (parallel code) (#14717)
This PR replicates the `tui` code directory and creates a temporary
parallel `tui_app_server` directory. It also implements a new feature
flag `tui_app_server` to select between the two tui implementations.

Once the new app-server-based TUI is stabilized, we'll delete the old
`tui` directory and feature flag.
2026-03-16 10:49:19 -06:00
jif-oai
c04a0a7454 fix: tui freeze when sub-agents are present (#14816)
The issue was due to a circular `Drop` schema where the embedded
app-server wait for some listeners that wait for this app-server
them-selves.

The fix is an explicit cleaning

**Repro:**
* Start codex
* Ask it to spawn a sub-agent
* Close Codex
* It takes 5s to exit
2026-03-16 16:42:43 +00:00
jif-oai
3f266bcd68 feat: make interrupt state not final for multi-agents (#13850)
Make `interrupted` an agent state and make it not final. As a result, a
`wait` won't return on an interrupted agent and no notification will be
send to the parent agent.

The rationals are:
* If a user interrupt a sub-agent for any reason, you don't want the
parent agent to instantaneously ask the sub-agent to restart
* If a parent agent interrupt a sub-agent, no need to add a noisy
notification in the parent agen
2026-03-16 16:39:40 +00:00
jif-oai
18ad67549c feat: improve skills cache key to take into account config layering (#14806)
Fix https://github.com/openai/codex/issues/14161

This fixes sub-agent [[skills.config]] overrides being ignored when
parent and child share the same cwd. The root cause was that turn skill
loading rebuilt from cwd-only state and reused a cwd-scoped cache, so
role-local skill enable/disable overrides did not reliably affect the
spawned agent's effective skill set.

This change switches turn construction to use the effective per-turn
config and adds a config-aware skills cache keyed by skill roots plus
final disabled paths.
2026-03-16 16:12:44 +00:00
jif-oai
33acc1e65f fix: sub-agent role when using profiles (#14807)
Fix the layering conflict when a project profile is used with agents.
This PR clean the config layering and make sure the agent config >
project profile

Fix https://github.com/openai/codex/issues/13849,
https://github.com/openai/codex/issues/14671
2026-03-16 16:08:16 +00:00
Matthew Zeng
029aab5563 fix(core): preserve tool_params for elicitations (#14769)
- [x] Preserve tool_params keys.
2026-03-15 23:15:52 -07:00
Charley Cunningham
6fdeb1d602 Reuse guardian session across approvals (#14668)
## Summary
- reuse a guardian subagent session across approvals so reviews keep a
stable prompt cache key and avoid one-shot startup overhead
- clear the guardian child history before each review so prior guardian
decisions do not leak into later approvals
- include the `smart_approvals` -> `guardian_approval` feature flag
rename in the same PR to minimize release latency on a very tight
timeline
- add regression coverage for prompt-cache-key reuse without
prior-review prompt bleed

## Request
- Bug/enhancement request: internal guardian prompt-cache and latency
improvement request

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-15 22:56:18 -07:00
friel-openai
ba463a9dc7 Preserve background terminals on interrupt and rename cleanup command to /stop (#14602)
### Motivation
- Interrupting a running turn (Ctrl+C / Esc) currently also terminates
long‑running background shells, which is surprising for workflows like
local dev servers or file watchers.
- The existing cleanup command name was confusing; callers expect an
explicit command to stop background terminals rather than a UI clear
action.
- Make background‑shell termination explicit and surface a clearer
command name while preserving backward compatibility.

### Description
- Renamed the background‑terminal cleanup slash command from `Clean`
(`/clean`) to `Stop` (`/stop`) and kept `clean` as an alias in the
command parsing/visibility layer, updated the user descriptions and
command popup wiring accordingly.
- Updated the unified‑exec footer text and snapshots to point to `/stop`
(and trimmed corresponding snapshot output to match the new label).
- Changed interrupt behavior so `Op::Interrupt` (Ctrl+C / Esc interrupt)
no longer closes or clears tracked unified exec / background terminal
processes in the TUI or core cleanup path; background shells are now
preserved after an interrupt.
- Updated protocol/docs to clarify that `turn/interrupt` (or
`Op::Interrupt`) interrupts the active turn but does not terminate
background terminals, and that `thread/backgroundTerminals/clean` is the
explicit API to stop those shells.
- Updated unit/integration tests and insta snapshots in the TUI and core
unified‑exec suites to reflect the new semantics and command name.

### Testing
- Ran formatting with `just fmt` in `codex-rs` (succeeded). 
- Ran `cargo test -p codex-protocol` (succeeded). 
- Attempted `cargo test -p codex-tui` but the build could not complete
in this environment due to a native build dependency that requires
`libcap` development headers (the `codex-linux-sandbox` vendored build
step); install `libcap-dev` / make `libcap.pc` available in
`PKG_CONFIG_PATH` to run the TUI test suite locally.
- Updated and accepted the affected `insta` snapshots for the TUI
changes so visual diffs reflect the new `/stop` wording and preserved
interrupt behavior.

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_69b39c44b6dc8323bd133ae206310fae)
2026-03-15 22:17:25 -07:00
Matthew Zeng
d4af6053e2 [apps] Improve search tool fallback. (#14732)
- [x] Bypass tool search and stuff tool specs directly into model
context when either a. Tool search is not available for the model or b.
There are not that many tools to search for.
2026-03-15 21:41:55 -07:00
Matthew Zeng
49edf311ac [apps] Add tool call meta. (#14647)
- [x] Add resource_uri and other things to _meta to shortcut resource
lookup and speed things up.
2026-03-14 22:24:13 -07:00
Colin Young
d692b74007 Add auth 401 observability to client bug reports (#14611)
CXC-392

  [With
  401](https://openai.sentry.io/issues/7333870443/?project=4510195390611458&query=019ce8f8-560c-7f10-a00a-c59553740674&referrer=issue-stream)
  <img width="1909" height="555" alt="401 auth tags in Sentry"
  src="https://github.com/user-attachments/assets/412ea950-61c4-4780-9697-15c270971ee3"
  />


  - auth_401_*: preserved facts from the latest unauthorized response snapshot
  - auth_*: latest auth-related facts from the latest request attempt
  - auth_recovery_*: unauthorized recovery state and follow-up result


  Without 401
  <img width="1917" height="522" alt="happy-path auth tags in Sentry"
  src="https://github.com/user-attachments/assets/3381ed28-8022-43b0-b6c0-623a630e679f"
  />

  ###### Summary
  - Add client-visible 401 diagnostics for auth attachment, upstream auth classification, and 401 request id / cf-ray correlation.
  - Record unauthorized recovery mode, phase, outcome, and retry/follow-up status without changing auth behavior.
  - Surface the highest-signal auth and recovery fields on uploaded client bug reports so they are usable in Sentry.
  - Preserve original unauthorized evidence under `auth_401_*` while keeping follow-up result tags separate.

  ###### Rationale (from spec findings)
  - The dominant bucket needed proof of whether the client attached auth before send or upstream still classified the request as missing auth.
  - Client uploads needed to show whether unauthorized recovery ran and what the client tried next.
  - Request id and cf-ray needed to be preserved on the unauthorized response so server-side correlation is immediate.
  - The bug-report path needed the same auth evidence as the request telemetry path, otherwise the observability would not be operationally useful.

  ###### Scope
  - Add auth 401 and unauthorized-recovery observability in `codex-rs/core`, `codex-rs/codex-api`, and `codex-rs/otel`, including feedback-tag surfacing.
  - Keep auth semantics, refresh behavior, retry behavior, endpoint classification, and geo-denial follow-up work out of this PR.

  ###### Trade-offs
  - This exports only safe auth evidence: header presence/name, upstream auth classification, request ids, and recovery state. It does not export token values or raw upstream bodies.
  - This keeps websocket connection reuse as a transport clue because it can help distinguish stale reused sessions from fresh reconnects.
  - Misroute/base-url classification and geo-denial are intentionally deferred to a separate follow-up PR so this review stays focused on the dominant auth 401 bucket.

  ###### Client follow-up
  - PR 2 will add misroute/provider and geo-denial observability plus the matching feedback-tag surfacing.
  - A separate host/app-server PR should log auth-decision inputs so pre-send host auth state can be correlated with client request evidence.
  - `device_id` remains intentionally separate until there is a safe existing source on the feedback upload path.

  ###### Testing
  - `cargo test -p codex-core refresh_available_models_sorts_by_priority`
  - `cargo test -p codex-core emit_feedback_request_tags_`
  - `cargo test -p codex-core emit_feedback_auth_recovery_tags_`
  - `cargo test -p codex-core auth_request_telemetry_context_tracks_attached_auth_and_retry_phase`
  - `cargo test -p codex-core extract_response_debug_context_decodes_identity_headers`
  - `cargo test -p codex-core identity_auth_details`
  - `cargo test -p codex-core telemetry_error_messages_preserve_non_http_details`
  - `cargo test -p codex-core --all-features --no-run`
  - `cargo test -p codex-otel otel_export_routing_policy_routes_api_request_auth_observability`
  - `cargo test -p codex-otel otel_export_routing_policy_routes_websocket_connect_auth_observability`
  - `cargo test -p codex-otel otel_export_routing_policy_routes_websocket_request_transport_observability`
2026-03-14 15:38:51 -07:00
viyatb-oai
9060dc7557 fix: fix symlinked writable roots in sandbox policies (#14674)
## Summary
- normalize effective readable, writable, and unreadable sandbox roots
after resolving special paths so symlinked roots use canonical runtime
paths
- add a protocol regression test for a symlinked writable root with a
denied child and update protocol expectations to canonicalized effective
paths
- update macOS seatbelt tests to assert against effective normalized
roots produced by the shared policy helpers

## Testing
- just fmt
- cargo test -p codex-protocol
- cargo test -p codex-core explicit_unreadable_paths_are_excluded_
- cargo clippy -p codex-protocol -p codex-core --tests -- -D warnings

## Notes
- This is intended to fix the symlinked TMPDIR bind failure in
bubblewrap described in #14672.
Fixes #14672
2026-03-14 13:24:43 -07:00
Michael Bolin
4b31848f5b Add argument-comment Dylint runner (#14651) 2026-03-14 08:18:04 -07:00
Channing Conger
70eddad6b0 dynamic tool calls: add param exposeToContext to optionally hide tool (#14501)
This extends dynamic_tool_calls to allow us to hide a tool from the
model context but still use it as part of the general tool calling
runtime (for ex from js_repl/code_mode)
2026-03-14 01:58:43 -07:00
sayan-oai
e389091042 make defaultPrompt an array, keep backcompat (#14649)
make plugins' `defaultPrompt` an array, but keep backcompat for strings.

the array is limited by app-server to 3 entries of up to 128 chars
(drops extra entries, `None`s-out ones that are too long) without
erroring if those invariants are violating.

added tests, tested locally.
2026-03-14 06:13:51 +00:00
sayan-oai
8ca358a13c Refresh Python SDK generated types (#14646)
## Summary
- regenerate `sdk/python` protocol-derived artifacts on latest
`origin/main`
- update `notification_registry.py` to match the regenerated
notification set
- fix the stale SDK test expectation for `GranularAskForApproval`

## Validation
- `cd sdk/python && python scripts/update_sdk_artifacts.py
generate-types`
- `cd sdk/python && python -m pytest`
2026-03-14 05:50:33 +00:00
Eric Traut
ae0a6510e1 Enforce errors on overriding built-in model providers (#12024)
We receive bug reports from users who attempt to override one of the
three built-in model providers (openai, ollama, or lmstuio). Currently,
these overrides are silently ignored. This PR makes it an error to
override them.

## Summary
- add validation for `model_providers` so `openai`, `ollama`, and
`lmstudio` keys now produce clear configuration errors instead of being
silently ignored
2026-03-13 22:10:13 -06:00
sayan-oai
d272f45058 move plugin/skill instructions into dev msg and reorder (#14609)
Move the general `Apps`, `Skills` and `Plugins` instructions blocks out
of `user_instructions` and into the developer message, with new `Apps ->
Skills -> Plugins` order for better clarity.

Also wrap those sections in stable XML-style instruction tags (like
other sections) and update prompt-layout tests/snapshots. This makes the
tests less brittle in snapshot output (we can parse the sections), and
it consolidates the capability instructions in one place.

#### Tests
Updated snapshots, added tests.

`<AGENTS_MD>` disappearing in snapshots is expected: before this change,
the wrapped user-instructions message was kept alive by `Skills`
content. Now that `Skills` and `Plugins` are in the developer message,
that wrapper only appears when there is real
project-doc/user-instructions content.

---------

Co-authored-by: Charley Cunningham <ccunningham@openai.com>
2026-03-13 20:51:01 -07:00
viyatb-oai
7f571396c8 fix: sync split sandbox policies for spawned subagents (#14650)
## Summary
- reapply the live split filesystem and network sandbox policies when
building spawned subagent configs
- keep spawned child sessions aligned with the parent turn after
role-layer config reloads
- add regression coverage for both config construction and spawned
child-turn inheritance
2026-03-14 03:03:49 +00:00
viyatb-oai
6dc04df5e6 fix: persist future network host approvals across sessions (#14619)
## Summary
- apply persisted execpolicy network rules when booting the managed
network proxy
- pass the current execpolicy into managed proxy startup so host
approvals selected with "allow this host in the future" survive new
sessions
2026-03-14 02:46:10 +00:00
Charley Cunningham
bbd329a812 Fix turn context reconstruction after backtracking (#14616)
## Summary
- reuse rollout reconstruction when applying a backtrack rollback so
`reference_context_item` is restored from persisted rollout state
- build rollback replay from the flushed rollout items plus the rollback
marker, avoiding the extra reread/fallback path
- add regression coverage for rollback after compaction so turn-context
diffing stays aligned after backtracking

Co-authored-by: Codex <noreply@openai.com>
2026-03-13 19:28:31 -07:00
Ahmed Ibrahim
69c8a1ef9e Fix Windows CI assertions for guardian and Smart Approvals (#14645)
- Normalize guardian assessment path serialization to use forward
slashes for cross-platform stability.
- Seed workspace-write defaults in the Smart Approvals
override-turn-context test so Windows and non-Windows selection flows
are consistent.

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Charles Cunningham <ccunningham@openai.com>
2026-03-14 02:15:58 +00:00
Eric Traut
4b9d5c8c1b Add openai_base_url config override for built-in provider (#12031)
We regularly get bug reports from users who mistakenly have the
`OPENAI_BASE_URL` environment variable set. This PR deprecates this
environment variable in favor of a top-level config key
`openai_base_url` that is used for the same purpose. By making it a
config key, it will be more visible to users. It will also participate
in all of the infrastructure we've added for layered and managed
configs.

Summary
- introduce the `openai_base_url` top-level config key, update
schema/tests, and route the built-in openai provider through it while
- fall back to deprecated `OPENAI_BASE_URL` env var but warn user of
deprecation when no `openai_base_url` config key is present
- update CLI, SDK, and TUI code to prefer the new config path (with a
deprecated env-var fallback) and document the SDK behavior change
2026-03-13 20:12:25 -06:00
Michael Bolin
b859a98e0f refactor: make unified-exec zsh-fork state explicit (#14633)
## Why

The unified-exec path was carrying zsh-fork state in a partially
flattened way.

First, the decision about whether zsh-fork was active came from feature
selection in `ToolsConfig`, while the real prerequisites lived in
session state. That left the handler and runtime defending against
partially configured cases later.

Second, once zsh-fork was active, its two runtime-only paths were
threaded through the runtime as separate arguments even though they form
one coherent piece of configuration.

This change keeps unified-exec on a single session-derived source of
truth and bundles the zsh-fork-specific paths into a named config type
so the runtime can pass them around as one unit.

In particular, this PR introduces this enum so the `ZshFork` variant can
carry the appropriate state with it:

```rust
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum UnifiedExecShellMode {
    Direct,
    ZshFork(ZshForkConfig),
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ZshForkConfig {
    pub(crate) shell_zsh_path: AbsolutePathBuf,
    pub(crate) main_execve_wrapper_exe: AbsolutePathBuf,
}
```

This cleanup was done in preparation for
https://github.com/openai/codex/pull/13432.

## What Changed

- Replaced the feature-only `UnifiedExecBackendConfig` split with
`UnifiedExecShellMode` in `codex-rs/core/src/tools/spec.rs`.
- Derived the unified-exec mode from session-backed inputs when building
turn `ToolsConfig`, and preserved that mode across model switches and
review turns.
- Introduced `ZshForkConfig`, which stores the resolved zsh-fork
`AbsolutePathBuf` values for the configured `zsh` binary and `execve`
wrapper.
- Threaded `ZshForkConfig` through unified-exec command construction and
the zsh-fork preparation path so zsh-fork-specific runtime code consumes
a single config object instead of separate path arguments.
- Added focused tests for constructing zsh-fork mode only when session
prerequisites are available, and updated the zsh-fork expectations to be
target-platform aware.

## Testing

- `cargo test -p codex-core zsh_fork --lib`


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/14633).
* #13432
* __->__ #14633
2026-03-13 17:20:01 -07:00
Ahmed Ibrahim
7fa5201365 Use parser-specific realtime voice enum (#14636)
Model realtime session output voices with an enum and map by parser so
v1 uses fathom and v2 uses alloy.

Co-authored-by: Codex <noreply@openai.com>
2026-03-13 16:17:13 -07:00
Ahmed Ibrahim
e9050e3e64 Fix realtime transcription session.update tools payload (#14635)
Only attach session tools for Realtime v2 conversational sessions, and
omit tools in transcription mode so realtime startup no longer fails
with unknown parameter errors.

Co-authored-by: Codex <noreply@openai.com>
2026-03-13 16:08:58 -07:00
Andrei Eternal
9a44a7e499 [hooks] stop continuation & stop_hook_active mechanics (#14532)
Stop hooks now receive `stop_hook_active` and enable stop hooks to loop
forever if they'd like to. In the initial hooks PR, we implemented a
simpler mechanic that the stop-blocking could only happen once in a row

- support stop hook adding a continuation prompt to add a further task
- if multiple stop-blocks happen that have continuation prompts, they
are concatenated

example run:
```
› hey :)


• 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

• Aloha :) Happy to jam with you. What are we building today?

• Running Stop hook: updating the guards

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

• Aloha, here’s the hyperspace move for cooking a wimboltine stonpet:

  1. Sear the stonpet in a hot pan with moon-oil until the edges shimmer.
  2. Add star-lime, black salt, and a little fermented nebula paste.
  3. Lower the heat and let it braise for 8 cosmic minutes with a splash of comet broth.
  4. Finish with sky herbs and serve over warm asteroid rice.

  The vibe: crispy outside, tender center, deep interdimensional savor.

• Running Stop hook: updating the guards

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

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

.codex/hooks.json
```
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup|resume",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 .codex/hooks/session_start_demo.py",
            "timeoutSec": 10,
            "statusMessage": "lighting the observatory"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 .codex/hooks/stop_demo_block.py",
            "timeoutSec": 10,
            "statusMessage": "updating the guards"
          }
        ]
      }
    ]
  }
}
```

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

import json
import sys
from pathlib import Path


def main() -> int:
    payload = json.load(sys.stdin)
    cwd = Path(payload.get("cwd", ".")).name or "wizard-tower"
    source = payload.get("source", "startup")
    source_label = "resume" if source == "resume" else "startup"
    source_prefix = (
        "Resume context:"
        if source == "resume"
        else "Startup context:"
    )

    output = {
        "systemMessage": (
            f"Hi, I'm a session start hook for {cwd} ({source_label})."
        ),
        "hookSpecificOutput": {
            "hookEventName": "SessionStart",
            "additionalContext": (
                f"{source_prefix} A wimboltine stonpet is an exotic cuisine from hyperspace"
            ),
        },
    }
    print(json.dumps(output))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
```

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

import json
import sys


def main() -> int:
    payload = json.load(sys.stdin)
    stop_hook_active = payload.get("stop_hook_active", False)
    last_assistant_message = payload.get("last_assistant_message") or ""
    char_count = len(last_assistant_message.strip())

    if stop_hook_active:
        system_message = (
            "Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop."
        )
        print(json.dumps({"systemMessage": system_message}))
    else:
        system_message = (
            f"Wizard Tower Stop hook continuing conversation"
        )
        print(json.dumps({"systemMessage": system_message, "decision": "block", "reason": "cook the stonpet"}))

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
```
2026-03-13 15:51:19 -07:00
Charley Cunningham
467e6216bb Fix stale create_wait_tool reference (#14639)
## Summary
- replace the stale `create_wait_tool()` reference in `spec_tests.rs`
- use `create_wait_agent_tool()` to match the actual multi-agent tool
rename from `#14631`
- fix the resulting `codex-core` spec-test compile failure on current
`main`

## Context
`#14631` renamed the model-facing multi-agent tool from `wait` to
`wait_agent` and renamed the corresponding spec helper to
`create_wait_agent_tool()`.

One `spec_tests.rs` call site was left behind, so current `main` fails
to compile `codex-core` tests with:
- `cannot find function create_wait_tool`

Using `create_wait_agent_tool()` is the correct fix here;
`create_exec_wait_tool()` would point at the separate exec wait tool and
would not match the renamed multi-agent toolset.

## Testing
- not rerun locally after the rebase

Co-authored-by: Codex <noreply@openai.com>
2026-03-13 15:35:25 -07:00
Charley Cunningham
bc24017d64 Add Smart Approvals guardian review across core, app-server, and TUI (#13860)
## Summary
- add `approvals_reviewer = "user" | "guardian_subagent"` as the runtime
control for who reviews approval requests
- route Smart Approvals guardian review through core for command
execution, file changes, managed-network approvals, MCP approvals, and
delegated/subagent approval flows
- expose guardian review in app-server with temporary unstable
`item/autoApprovalReview/{started,completed}` notifications carrying
`targetItemId`, `review`, and `action`
- update the TUI so Smart Approvals can be enabled from `/experimental`,
aligned with the matching `/approvals` mode, and surfaced clearly while
reviews are pending or resolved

## Runtime model
This PR does not introduce a new `approval_policy`.

Instead:
- `approval_policy` still controls when approval is needed
- `approvals_reviewer` controls who reviewable approval requests are
routed to:
  - `user`
  - `guardian_subagent`

`guardian_subagent` is a carefully prompted reviewer subagent that
gathers relevant context and applies a risk-based decision framework
before approving or denying the request.

The `smart_approvals` feature flag is a rollout/UI gate. Core runtime
behavior keys off `approvals_reviewer`.

When Smart Approvals is enabled from the TUI, it also switches the
current `/approvals` settings to the matching Smart Approvals mode so
users immediately see guardian review in the active thread:
- `approval_policy = on-request`
- `approvals_reviewer = guardian_subagent`
- `sandbox_mode = workspace-write`

Users can still change `/approvals` afterward.

Config-load behavior stays intentionally narrow:
- plain `smart_approvals = true` in `config.toml` remains just the
rollout/UI gate and does not auto-set `approvals_reviewer`
- the deprecated `guardian_approval = true` alias migration does
backfill `approvals_reviewer = "guardian_subagent"` in the same scope
when that reviewer is not already configured there, so old configs
preserve their original guardian-enabled behavior

ARC remains a separate safety check. For MCP tool approvals, ARC
escalations now flow into the configured reviewer instead of always
bypassing guardian and forcing manual review.

## Config stability
The runtime reviewer override is stable, but the config-backed
app-server protocol shape is still settling.

- `thread/start`, `thread/resume`, and `turn/start` keep stable
`approvalsReviewer` overrides
- the config-backed `approvals_reviewer` exposure returned via
`config/read` (including profile-level config) is now marked
`[UNSTABLE]` / experimental in the app-server protocol until we are more
confident in that config surface

## App-server surface
This PR intentionally keeps the guardian app-server shape narrow and
temporary.

It adds generic unstable lifecycle notifications:
- `item/autoApprovalReview/started`
- `item/autoApprovalReview/completed`

with payloads of the form:
- `{ threadId, turnId, targetItemId, review, action? }`

`review` is currently:
- `{ status, riskScore?, riskLevel?, rationale? }`
- where `status` is one of `inProgress`, `approved`, `denied`, or
`aborted`

`action` carries the guardian action summary payload from core when
available. This lets clients render temporary standalone pending-review
UI, including parallel reviews, even when the underlying tool item has
not been emitted yet.

These notifications are explicitly documented as `[UNSTABLE]` and
expected to change soon.

This PR does **not** persist guardian review state onto `thread/read`
tool items. The intended follow-up is to attach guardian review state to
the reviewed tool item lifecycle instead, which would improve
consistency with manual approvals and allow thread history / reconnect
flows to replay guardian review state directly.

## TUI behavior
- `/experimental` exposes the rollout gate as `Smart Approvals`
- enabling it in the TUI enables the feature and switches the current
session to the matching Smart Approvals `/approvals` mode
- disabling it in the TUI clears the persisted `approvals_reviewer`
override when appropriate and returns the session to default manual
review when the effective reviewer changes
- `/approvals` still exposes the reviewer choice directly
- the TUI renders:
- pending guardian review state in the live status footer, including
parallel review aggregation
  - resolved approval/denial state in history

## Scope notes
This PR includes the supporting core/runtime work needed to make Smart
Approvals usable end-to-end:
- shell / unified-exec / apply_patch / managed-network / MCP guardian
review
- delegated/subagent approval routing into guardian review
- guardian review risk metadata and action summaries for app-server/TUI
- config/profile/TUI handling for `smart_approvals`, `guardian_approval`
alias migration, and `approvals_reviewer`
- a small internal cleanup of delegated approval forwarding to dedupe
fallback paths and simplify guardian-vs-parent approval waiting (no
intended behavior change)

Out of scope for this PR:
- redesigning the existing manual approval protocol shapes
- persisting guardian review state onto app-server `ThreadItem`s
- delegated MCP elicitation auto-review (the current delegated MCP
guardian shim only covers the legacy `RequestUserInput` path)

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-13 15:27:00 -07:00
Charley Cunningham
e3cbf913e8 Fix wait_agent expectations in core tests (#14637)
## Summary
- update stale core tool-spec expectations from `wait` to `wait_agent`
- update the prompt-caching tool-name assertion to match the renamed
tool
- fix the Bazel regressions introduced after #14631 renamed the
multi-agent wait tool

## Testing
- cargo test -p codex-core tools::spec::tests
- cargo test -p codex-core
suite::prompt_caching::prompt_tools_are_consistent_across_requests

Co-authored-by: Codex <noreply@openai.com>
2026-03-13 15:15:59 -07:00
pakrym-oai
cb7d8f45a1 Normalize MCP tool names to code-mode safe form (#14605)
Code mode doesn't allow `-` in names and it's better if function names
and code-mode names are the same.
2026-03-13 14:50:16 -07:00
Ruslan Nigmatullin
f8f82bfc2b app-server: add v2 filesystem APIs (#14245)
Add a protocol-level filesystem surface to the v2 app-server so Codex
clients can read and write files, inspect directories, and subscribe to
path changes without relying on host-specific helpers.

High-level changes:
- define the new v2 fs/readFile, fs/writeFile, fs/createDirectory,
fs/getMetadata, fs/readDirectory, fs/remove, fs/copy RPCs
- implement the app-server handlers, including absolute-path validation,
base64 file payloads, recursive copy/remove semantics
- document the API, regenerate protocol schemas/types, and add
end-to-end tests for filesystem operations, copy edge cases

Testing plan:
- validate protocol serialization and generated schema output for the
new fs request, response, and notification types
- run app-server integration coverage for file and directory CRUD paths,
metadata/readDirectory responses, copy failure modes, and absolute-path
validation
2026-03-13 14:42:20 -07:00
Ahmed Ibrahim
36dfb84427 Stabilize multi-agent feature flag (#14622)
- make multi_agent stable and enabled by default
- update feature and tool-spec coverage to match the new default

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-13 14:38:15 -07:00
Ahmed Ibrahim
cfd97b36da Rename multi-agent wait tool to wait_agent (#14631)
- rename the multi-agent tool name the model sees to wait_agent
- update the model-facing prompts and tool descriptions to match

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-13 14:38:05 -07:00
Won Park
6720caf778 Slash copy osc52 wsl support (#13201)
This PR is a followup to the /copy feature to support WSL and SSH!
2026-03-13 14:00:58 -07:00
1633 changed files with 160410 additions and 9033 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

@@ -6,3 +6,4 @@ MODULE.bazel.lock
codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json
codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json
codex-rs/tui/tests/fixtures/oss-story.jsonl
codex-rs/tui_app_server/tests/fixtures/oss-story.jsonl

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

@@ -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)' || '' }}
@@ -657,13 +703,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 +719,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

@@ -490,9 +490,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
@@ -561,10 +562,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"

View File

@@ -11,6 +11,11 @@ In the codex-rs folder where the rust code lives:
- Always collapse if statements per https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
- Always inline format! args when possible per https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args
- Use method references over closures when possible per https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
- Avoid bool or ambiguous `Option` parameters that force callers to write hard-to-read code such as `foo(false)` or `bar(None)`. Prefer enums, named methods, newtypes, or other idiomatic Rust API shapes when they keep the callsite self-documenting.
- When you cannot make that API change and still need a small positional-literal callsite in Rust, follow the `argument_comment_lint` convention:
- Use an exact `/*param_name*/` comment before opaque literal arguments such as `None`, booleans, and numeric literals when passing them by position.
- Do not add these comments for string or char literals unless the comment adds real clarity; those literals are intentionally exempt from the lint.
- If you add one of these comments, the parameter name must exactly match the callee signature.
- When possible, make `match` statements exhaustive and avoid wildcard arms.
- When writing tests, prefer comparing the equality of entire objects over fields one by one.
- When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable.
@@ -49,6 +54,8 @@ See `codex-rs/tui/styles.md`.
## TUI code conventions
- When a change lands in `codex-rs/tui` and `codex-rs/tui_app_server` has a parallel implementation of the same behavior, reflect the change in `codex-rs/tui_app_server` too unless there is a documented reason not to.
- Use concise styling helpers from ratatuis Stylize trait.
- Basic spans: use "text".into()
- Styled spans: use "text".red(), "text".green(), "text".magenta(), "text".dim(), etc.

98
codex-rs/Cargo.lock generated
View File

@@ -1462,6 +1462,7 @@ dependencies = [
"tracing-opentelemetry",
"tracing-subscriber",
"uuid",
"walkdir",
"wiremock",
]
@@ -1475,12 +1476,15 @@ dependencies = [
"codex-core",
"codex-feedback",
"codex-protocol",
"futures",
"pretty_assertions",
"serde",
"serde_json",
"tokio",
"tokio-tungstenite",
"toml 0.9.11+spec-1.1.0",
"tracing",
"url",
]
[[package]]
@@ -1659,6 +1663,7 @@ dependencies = [
"codex-state",
"codex-stdio-to-uds",
"codex-tui",
"codex-tui-app-server",
"codex-utils-cargo-bin",
"codex-utils-cli",
"codex-windows-sandbox",
@@ -2511,6 +2516,97 @@ dependencies = [
"codex-protocol",
"codex-shell-command",
"codex-state",
"codex-tui-app-server",
"codex-utils-absolute-path",
"codex-utils-approval-presets",
"codex-utils-cargo-bin",
"codex-utils-cli",
"codex-utils-elapsed",
"codex-utils-fuzzy-match",
"codex-utils-oss",
"codex-utils-pty",
"codex-utils-sandbox-summary",
"codex-utils-sleep-inhibitor",
"codex-utils-string",
"codex-windows-sandbox",
"color-eyre",
"cpal",
"crossterm",
"derive_more 2.1.1",
"diffy",
"dirs",
"dunce",
"hound",
"image",
"insta",
"itertools 0.14.0",
"lazy_static",
"libc",
"pathdiff",
"pretty_assertions",
"pulldown-cmark",
"rand 0.9.2",
"ratatui",
"ratatui-macros",
"regex-lite",
"reqwest",
"rmcp",
"serde",
"serde_json",
"serial_test",
"shlex",
"strum 0.27.2",
"strum_macros 0.28.0",
"supports-color 3.0.2",
"syntect",
"tempfile",
"textwrap 0.16.2",
"thiserror 2.0.18",
"tokio",
"tokio-stream",
"tokio-util",
"toml 0.9.11+spec-1.1.0",
"tracing",
"tracing-appender",
"tracing-subscriber",
"two-face",
"unicode-segmentation",
"unicode-width 0.2.1",
"url",
"uuid",
"vt100",
"webbrowser",
"which",
"windows-sys 0.52.0",
"winsplit",
]
[[package]]
name = "codex-tui-app-server"
version = "0.0.0"
dependencies = [
"anyhow",
"arboard",
"assert_matches",
"base64 0.22.1",
"chrono",
"clap",
"codex-ansi-escape",
"codex-app-server-client",
"codex-app-server-protocol",
"codex-arg0",
"codex-chatgpt",
"codex-cli",
"codex-client",
"codex-cloud-requirements",
"codex-core",
"codex-feedback",
"codex-file-search",
"codex-login",
"codex-otel",
"codex-protocol",
"codex-shell-command",
"codex-state",
"codex-utils-absolute-path",
"codex-utils-approval-presets",
"codex-utils-cargo-bin",
@@ -2750,6 +2846,7 @@ dependencies = [
"chrono",
"codex-protocol",
"codex-utils-absolute-path",
"codex-utils-pty",
"codex-utils-string",
"dirs-next",
"dunce",
@@ -2758,6 +2855,7 @@ dependencies = [
"serde",
"serde_json",
"tempfile",
"tokio",
"windows 0.58.0",
"windows-sys 0.52.0",
"winres",

View File

@@ -42,6 +42,7 @@ members = [
"stdio-to-uds",
"otel",
"tui",
"tui_app_server",
"utils/absolute-path",
"utils/cargo-bin",
"utils/git",
@@ -129,6 +130,7 @@ codex-state = { path = "state" }
codex-stdio-to-uds = { path = "stdio-to-uds" }
codex-test-macros = { path = "test-macros" }
codex-tui = { path = "tui" }
codex-tui-app-server = { path = "tui_app_server" }
codex-utils-absolute-path = { path = "utils/absolute-path" }
codex-utils-approval-presets = { path = "utils/approval-presets" }
codex-utils-cache = { path = "utils/cache" }

View File

@@ -18,11 +18,14 @@ codex-arg0 = { workspace = true }
codex-core = { workspace = true }
codex-feedback = { workspace = true }
codex-protocol = { workspace = true }
futures = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["sync", "time", "rt"] }
tokio-tungstenite = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }
url = { workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }

View File

@@ -15,6 +15,8 @@
//! bridging async `mpsc` channels on both sides. Queues are bounded so overload
//! surfaces as channel-full errors rather than unbounded memory growth.
mod remote;
use std::error::Error;
use std::fmt;
use std::io::Error as IoError;
@@ -33,8 +35,11 @@ use codex_app_server_protocol::ConfigWarningNotification;
use codex_app_server_protocol::InitializeCapabilities;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::Result as JsonRpcResult;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
use codex_arg0::Arg0DispatchPaths;
use codex_core::AuthManager;
use codex_core::ThreadManager;
@@ -51,6 +56,9 @@ use tokio::time::timeout;
use toml::Value as TomlValue;
use tracing::warn;
pub use crate::remote::RemoteAppServerClient;
pub use crate::remote::RemoteAppServerConnectArgs;
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
/// Raw app-server request result for typed in-process requests.
@@ -60,6 +68,30 @@ const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
/// `MessageProcessor` continues to produce that shape internally.
pub type RequestResult = std::result::Result<JsonRpcResult, JSONRPCErrorError>;
#[derive(Debug, Clone)]
pub enum AppServerEvent {
Lagged { skipped: usize },
ServerNotification(ServerNotification),
LegacyNotification(JSONRPCNotification),
ServerRequest(ServerRequest),
Disconnected { message: String },
}
impl From<InProcessServerEvent> for AppServerEvent {
fn from(value: InProcessServerEvent) -> Self {
match value {
InProcessServerEvent::Lagged { skipped } => Self::Lagged { skipped },
InProcessServerEvent::ServerNotification(notification) => {
Self::ServerNotification(notification)
}
InProcessServerEvent::LegacyNotification(notification) => {
Self::LegacyNotification(notification)
}
InProcessServerEvent::ServerRequest(request) => Self::ServerRequest(request),
}
}
}
fn event_requires_delivery(event: &InProcessServerEvent) -> bool {
// These terminal events drive surface shutdown/completion state. Dropping
// them under backpressure can leave exec/TUI waiting forever even though
@@ -281,6 +313,22 @@ pub struct InProcessAppServerClient {
thread_manager: Arc<ThreadManager>,
}
#[derive(Clone)]
pub struct InProcessAppServerRequestHandle {
command_tx: mpsc::Sender<ClientCommand>,
}
#[derive(Clone)]
pub enum AppServerRequestHandle {
InProcess(InProcessAppServerRequestHandle),
Remote(crate::remote::RemoteAppServerRequestHandle),
}
pub enum AppServerClient {
InProcess(InProcessAppServerClient),
Remote(RemoteAppServerClient),
}
impl InProcessAppServerClient {
/// Starts the in-process runtime and facade worker task.
///
@@ -457,6 +505,12 @@ impl InProcessAppServerClient {
self.thread_manager.clone()
}
pub fn request_handle(&self) -> InProcessAppServerRequestHandle {
InProcessAppServerRequestHandle {
command_tx: self.command_tx.clone(),
}
}
/// Sends a typed client request and returns raw JSON-RPC result.
///
/// Callers that expect a concrete response type should usually prefer
@@ -641,9 +695,141 @@ impl InProcessAppServerClient {
}
}
impl InProcessAppServerRequestHandle {
pub async fn request(&self, request: ClientRequest) -> IoResult<RequestResult> {
let (response_tx, response_rx) = oneshot::channel();
self.command_tx
.send(ClientCommand::Request {
request: Box::new(request),
response_tx,
})
.await
.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"in-process app-server worker channel is closed",
)
})?;
response_rx.await.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"in-process app-server request channel is closed",
)
})?
}
pub async fn request_typed<T>(&self, request: ClientRequest) -> Result<T, TypedRequestError>
where
T: DeserializeOwned,
{
let method = request_method_name(&request);
let response =
self.request(request)
.await
.map_err(|source| TypedRequestError::Transport {
method: method.clone(),
source,
})?;
let result = response.map_err(|source| TypedRequestError::Server {
method: method.clone(),
source,
})?;
serde_json::from_value(result)
.map_err(|source| TypedRequestError::Deserialize { method, source })
}
}
impl AppServerRequestHandle {
pub async fn request(&self, request: ClientRequest) -> IoResult<RequestResult> {
match self {
Self::InProcess(handle) => handle.request(request).await,
Self::Remote(handle) => handle.request(request).await,
}
}
pub async fn request_typed<T>(&self, request: ClientRequest) -> Result<T, TypedRequestError>
where
T: DeserializeOwned,
{
match self {
Self::InProcess(handle) => handle.request_typed(request).await,
Self::Remote(handle) => handle.request_typed(request).await,
}
}
}
impl AppServerClient {
pub async fn request(&self, request: ClientRequest) -> IoResult<RequestResult> {
match self {
Self::InProcess(client) => client.request(request).await,
Self::Remote(client) => client.request(request).await,
}
}
pub async fn request_typed<T>(&self, request: ClientRequest) -> Result<T, TypedRequestError>
where
T: DeserializeOwned,
{
match self {
Self::InProcess(client) => client.request_typed(request).await,
Self::Remote(client) => client.request_typed(request).await,
}
}
pub async fn notify(&self, notification: ClientNotification) -> IoResult<()> {
match self {
Self::InProcess(client) => client.notify(notification).await,
Self::Remote(client) => client.notify(notification).await,
}
}
pub async fn resolve_server_request(
&self,
request_id: RequestId,
result: JsonRpcResult,
) -> IoResult<()> {
match self {
Self::InProcess(client) => client.resolve_server_request(request_id, result).await,
Self::Remote(client) => client.resolve_server_request(request_id, result).await,
}
}
pub async fn reject_server_request(
&self,
request_id: RequestId,
error: JSONRPCErrorError,
) -> IoResult<()> {
match self {
Self::InProcess(client) => client.reject_server_request(request_id, error).await,
Self::Remote(client) => client.reject_server_request(request_id, error).await,
}
}
pub async fn next_event(&mut self) -> Option<AppServerEvent> {
match self {
Self::InProcess(client) => client.next_event().await.map(Into::into),
Self::Remote(client) => client.next_event().await,
}
}
pub async fn shutdown(self) -> IoResult<()> {
match self {
Self::InProcess(client) => client.shutdown().await,
Self::Remote(client) => client.shutdown().await,
}
}
pub fn request_handle(&self) -> AppServerRequestHandle {
match self {
Self::InProcess(client) => AppServerRequestHandle::InProcess(client.request_handle()),
Self::Remote(client) => AppServerRequestHandle::Remote(client.request_handle()),
}
}
}
/// Extracts the JSON-RPC method name for diagnostics without extending the
/// protocol crate with in-process-only helpers.
fn request_method_name(request: &ClientRequest) -> String {
pub(crate) fn request_method_name(request: &ClientRequest) -> String {
serde_json::to_value(request)
.ok()
.and_then(|value| {
@@ -658,16 +844,29 @@ fn request_method_name(request: &ClientRequest) -> String {
#[cfg(test)]
mod tests {
use super::*;
use codex_app_server_protocol::AccountUpdatedNotification;
use codex_app_server_protocol::ConfigRequirementsReadResponse;
use codex_app_server_protocol::GetAccountResponse;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::SessionSource as ApiSessionSource;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::ToolRequestUserInputParams;
use codex_app_server_protocol::ToolRequestUserInputQuestion;
use codex_core::AuthManager;
use codex_core::ThreadManager;
use codex_core::config::ConfigBuilder;
use futures::SinkExt;
use futures::StreamExt;
use pretty_assertions::assert_eq;
use tokio::net::TcpListener;
use tokio::time::Duration;
use tokio::time::timeout;
use tokio_tungstenite::accept_async;
use tokio_tungstenite::tungstenite::Message;
async fn build_test_config() -> Config {
match ConfigBuilder::default().build().await {
@@ -705,6 +904,97 @@ mod tests {
start_test_client_with_capacity(session_source, DEFAULT_IN_PROCESS_CHANNEL_CAPACITY).await
}
async fn start_test_remote_server<F, Fut>(handler: F) -> String
where
F: FnOnce(tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>) -> Fut
+ Send
+ 'static,
Fut: std::future::Future<Output = ()> + Send + 'static,
{
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("listener should bind");
let addr = listener.local_addr().expect("listener address");
tokio::spawn(async move {
let (stream, _) = listener.accept().await.expect("accept should succeed");
let websocket = accept_async(stream)
.await
.expect("websocket upgrade should succeed");
handler(websocket).await;
});
format!("ws://{addr}")
}
async fn expect_remote_initialize(
websocket: &mut tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>,
) {
let JSONRPCMessage::Request(request) = read_websocket_message(websocket).await else {
panic!("expected initialize request");
};
assert_eq!(request.method, "initialize");
write_websocket_message(
websocket,
JSONRPCMessage::Response(JSONRPCResponse {
id: request.id,
result: serde_json::json!({}),
}),
)
.await;
let JSONRPCMessage::Notification(notification) = read_websocket_message(websocket).await
else {
panic!("expected initialized notification");
};
assert_eq!(notification.method, "initialized");
}
async fn read_websocket_message(
websocket: &mut tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>,
) -> JSONRPCMessage {
loop {
let frame = websocket
.next()
.await
.expect("frame should be available")
.expect("frame should decode");
match frame {
Message::Text(text) => {
return serde_json::from_str::<JSONRPCMessage>(&text)
.expect("text frame should be valid JSON-RPC");
}
Message::Binary(_) | Message::Ping(_) | Message::Pong(_) | Message::Frame(_) => {
continue;
}
Message::Close(_) => panic!("unexpected close frame"),
}
}
}
async fn write_websocket_message(
websocket: &mut tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>,
message: JSONRPCMessage,
) {
websocket
.send(Message::Text(
serde_json::to_string(&message)
.expect("message should serialize")
.into(),
))
.await
.expect("message should send");
}
fn test_remote_connect_args(websocket_url: String) -> RemoteAppServerConnectArgs {
RemoteAppServerConnectArgs {
websocket_url,
client_name: "codex-app-server-client-test".to_string(),
client_version: "0.0.0-test".to_string(),
experimental_api: true,
opt_out_notification_methods: Vec::new(),
channel_capacity: 8,
}
}
#[tokio::test]
async fn typed_request_roundtrip_works() {
let client = start_test_client(SessionSource::Exec).await;
@@ -802,6 +1092,354 @@ mod tests {
client.shutdown().await.expect("shutdown should complete");
}
#[tokio::test]
async fn remote_typed_request_roundtrip_works() {
let websocket_url = start_test_remote_server(|mut websocket| async move {
expect_remote_initialize(&mut websocket).await;
let JSONRPCMessage::Request(request) = read_websocket_message(&mut websocket).await
else {
panic!("expected account/read request");
};
assert_eq!(request.method, "account/read");
write_websocket_message(
&mut websocket,
JSONRPCMessage::Response(JSONRPCResponse {
id: request.id,
result: serde_json::to_value(GetAccountResponse {
account: None,
requires_openai_auth: false,
})
.expect("response should serialize"),
}),
)
.await;
})
.await;
let client = RemoteAppServerClient::connect(test_remote_connect_args(websocket_url))
.await
.expect("remote client should connect");
let response: GetAccountResponse = client
.request_typed(ClientRequest::GetAccount {
request_id: RequestId::Integer(1),
params: codex_app_server_protocol::GetAccountParams {
refresh_token: false,
},
})
.await
.expect("typed request should succeed");
assert_eq!(response.account, None);
client.shutdown().await.expect("shutdown should complete");
}
#[tokio::test]
async fn remote_duplicate_request_id_keeps_original_waiter() {
let (first_request_seen_tx, first_request_seen_rx) = tokio::sync::oneshot::channel();
let websocket_url = start_test_remote_server(|mut websocket| async move {
expect_remote_initialize(&mut websocket).await;
let JSONRPCMessage::Request(request) = read_websocket_message(&mut websocket).await
else {
panic!("expected account/read request");
};
assert_eq!(request.method, "account/read");
first_request_seen_tx
.send(request.id.clone())
.expect("request id should send");
assert!(
timeout(
Duration::from_millis(100),
read_websocket_message(&mut websocket)
)
.await
.is_err(),
"duplicate request should not be forwarded to the server"
);
write_websocket_message(
&mut websocket,
JSONRPCMessage::Response(JSONRPCResponse {
id: request.id,
result: serde_json::to_value(GetAccountResponse {
account: None,
requires_openai_auth: false,
})
.expect("response should serialize"),
}),
)
.await;
let _ = websocket.next().await;
})
.await;
let client = RemoteAppServerClient::connect(test_remote_connect_args(websocket_url))
.await
.expect("remote client should connect");
let first_request_handle = client.request_handle();
let second_request_handle = first_request_handle.clone();
let first_request = tokio::spawn(async move {
first_request_handle
.request_typed::<GetAccountResponse>(ClientRequest::GetAccount {
request_id: RequestId::Integer(1),
params: codex_app_server_protocol::GetAccountParams {
refresh_token: false,
},
})
.await
});
let first_request_id = first_request_seen_rx
.await
.expect("server should observe the first request");
assert_eq!(first_request_id, RequestId::Integer(1));
let second_err = second_request_handle
.request_typed::<GetAccountResponse>(ClientRequest::GetAccount {
request_id: RequestId::Integer(1),
params: codex_app_server_protocol::GetAccountParams {
refresh_token: false,
},
})
.await
.expect_err("duplicate request id should be rejected");
assert_eq!(
second_err.to_string(),
"account/read transport error: duplicate remote app-server request id `1`"
);
let first_response = first_request
.await
.expect("first request task should join")
.expect("first request should succeed");
assert_eq!(
first_response,
GetAccountResponse {
account: None,
requires_openai_auth: false,
}
);
client.shutdown().await.expect("shutdown should complete");
}
#[tokio::test]
async fn remote_notifications_arrive_over_websocket() {
let websocket_url = start_test_remote_server(|mut websocket| async move {
expect_remote_initialize(&mut websocket).await;
write_websocket_message(
&mut websocket,
JSONRPCMessage::Notification(
serde_json::from_value(
serde_json::to_value(ServerNotification::AccountUpdated(
AccountUpdatedNotification {
auth_mode: None,
plan_type: None,
},
))
.expect("notification should serialize"),
)
.expect("notification should convert to JSON-RPC"),
),
)
.await;
})
.await;
let mut client = RemoteAppServerClient::connect(test_remote_connect_args(websocket_url))
.await
.expect("remote client should connect");
let event = client.next_event().await.expect("event should arrive");
assert!(matches!(
event,
AppServerEvent::ServerNotification(ServerNotification::AccountUpdated(_))
));
client.shutdown().await.expect("shutdown should complete");
}
#[tokio::test]
async fn remote_server_request_resolution_roundtrip_works() {
let websocket_url = start_test_remote_server(|mut websocket| async move {
expect_remote_initialize(&mut websocket).await;
let request_id = RequestId::String("srv-1".to_string());
let server_request = JSONRPCRequest {
id: request_id.clone(),
method: "item/tool/requestUserInput".to_string(),
params: Some(
serde_json::to_value(ToolRequestUserInputParams {
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
item_id: "call-1".to_string(),
questions: vec![ToolRequestUserInputQuestion {
id: "question-1".to_string(),
header: "Mode".to_string(),
question: "Pick one".to_string(),
is_other: false,
is_secret: false,
options: Some(vec![]),
}],
})
.expect("params should serialize"),
),
trace: None,
};
write_websocket_message(&mut websocket, JSONRPCMessage::Request(server_request)).await;
let JSONRPCMessage::Response(response) = read_websocket_message(&mut websocket).await
else {
panic!("expected server request response");
};
assert_eq!(response.id, request_id);
})
.await;
let mut client = RemoteAppServerClient::connect(test_remote_connect_args(websocket_url))
.await
.expect("remote client should connect");
let AppServerEvent::ServerRequest(request) = client
.next_event()
.await
.expect("request event should arrive")
else {
panic!("expected server request event");
};
client
.resolve_server_request(request.id().clone(), serde_json::json!({}))
.await
.expect("server request should resolve");
client.shutdown().await.expect("shutdown should complete");
}
#[tokio::test]
async fn remote_server_request_received_during_initialize_is_delivered() {
let websocket_url = start_test_remote_server(|mut websocket| async move {
let JSONRPCMessage::Request(request) = read_websocket_message(&mut websocket).await
else {
panic!("expected initialize request");
};
assert_eq!(request.method, "initialize");
let request_id = RequestId::String("srv-init".to_string());
write_websocket_message(
&mut websocket,
JSONRPCMessage::Request(JSONRPCRequest {
id: request_id.clone(),
method: "item/tool/requestUserInput".to_string(),
params: Some(
serde_json::to_value(ToolRequestUserInputParams {
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
item_id: "call-1".to_string(),
questions: vec![ToolRequestUserInputQuestion {
id: "question-1".to_string(),
header: "Mode".to_string(),
question: "Pick one".to_string(),
is_other: false,
is_secret: false,
options: Some(vec![]),
}],
})
.expect("params should serialize"),
),
trace: None,
}),
)
.await;
write_websocket_message(
&mut websocket,
JSONRPCMessage::Response(JSONRPCResponse {
id: request.id,
result: serde_json::json!({}),
}),
)
.await;
let JSONRPCMessage::Notification(notification) =
read_websocket_message(&mut websocket).await
else {
panic!("expected initialized notification");
};
assert_eq!(notification.method, "initialized");
let JSONRPCMessage::Response(response) = read_websocket_message(&mut websocket).await
else {
panic!("expected server request response");
};
assert_eq!(response.id, request_id);
})
.await;
let mut client = RemoteAppServerClient::connect(test_remote_connect_args(websocket_url))
.await
.expect("remote client should connect");
let AppServerEvent::ServerRequest(request) = client
.next_event()
.await
.expect("request event should arrive")
else {
panic!("expected server request event");
};
client
.resolve_server_request(request.id().clone(), serde_json::json!({}))
.await
.expect("server request should resolve");
client.shutdown().await.expect("shutdown should complete");
}
#[tokio::test]
async fn remote_unknown_server_request_is_rejected() {
let websocket_url = start_test_remote_server(|mut websocket| async move {
expect_remote_initialize(&mut websocket).await;
let request_id = RequestId::String("srv-unknown".to_string());
write_websocket_message(
&mut websocket,
JSONRPCMessage::Request(JSONRPCRequest {
id: request_id.clone(),
method: "thread/unknown".to_string(),
params: None,
trace: None,
}),
)
.await;
let JSONRPCMessage::Error(response) = read_websocket_message(&mut websocket).await
else {
panic!("expected JSON-RPC error response");
};
assert_eq!(response.id, request_id);
assert_eq!(response.error.code, -32601);
assert_eq!(
response.error.message,
"unsupported remote app-server request `thread/unknown`"
);
})
.await;
let client = RemoteAppServerClient::connect(test_remote_connect_args(websocket_url))
.await
.expect("remote client should connect");
client.shutdown().await.expect("shutdown should complete");
}
#[tokio::test]
async fn remote_disconnect_surfaces_as_event() {
let websocket_url = start_test_remote_server(|mut websocket| async move {
expect_remote_initialize(&mut websocket).await;
websocket.close(None).await.expect("close should succeed");
})
.await;
let mut client = RemoteAppServerClient::connect(test_remote_connect_args(websocket_url))
.await
.expect("remote client should connect");
let event = client
.next_event()
.await
.expect("disconnect event should arrive");
assert!(matches!(event, AppServerEvent::Disconnected { .. }));
}
#[test]
fn typed_request_error_exposes_sources() {
let transport = TypedRequestError::Transport {

View File

@@ -0,0 +1,911 @@
/*
This module implements the websocket-backed app-server client transport.
It owns the remote connection lifecycle, including the initialize/initialized
handshake, JSON-RPC request/response routing, server-request resolution, and
notification streaming. The rest of the crate uses the same `AppServerEvent`
surface for both in-process and remote transports, so callers such as
`tui_app_server` can switch between them without changing their higher-level
session logic.
*/
use std::collections::HashMap;
use std::collections::VecDeque;
use std::io::Error as IoError;
use std::io::ErrorKind;
use std::io::Result as IoResult;
use std::time::Duration;
use crate::AppServerEvent;
use crate::RequestResult;
use crate::SHUTDOWN_TIMEOUT;
use crate::TypedRequestError;
use crate::request_method_name;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ClientNotification;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::InitializeCapabilities;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::Result as JsonRpcResult;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
use futures::SinkExt;
use futures::StreamExt;
use serde::de::DeserializeOwned;
use tokio::net::TcpStream;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
use tokio::time::timeout;
use tokio_tungstenite::MaybeTlsStream;
use tokio_tungstenite::WebSocketStream;
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::Message;
use tracing::warn;
use url::Url;
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
const INITIALIZE_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Debug, Clone)]
pub struct RemoteAppServerConnectArgs {
pub websocket_url: String,
pub client_name: String,
pub client_version: String,
pub experimental_api: bool,
pub opt_out_notification_methods: Vec<String>,
pub channel_capacity: usize,
}
impl RemoteAppServerConnectArgs {
fn initialize_params(&self) -> InitializeParams {
let capabilities = InitializeCapabilities {
experimental_api: self.experimental_api,
opt_out_notification_methods: if self.opt_out_notification_methods.is_empty() {
None
} else {
Some(self.opt_out_notification_methods.clone())
},
};
InitializeParams {
client_info: ClientInfo {
name: self.client_name.clone(),
title: None,
version: self.client_version.clone(),
},
capabilities: Some(capabilities),
}
}
}
enum RemoteClientCommand {
Request {
request: Box<ClientRequest>,
response_tx: oneshot::Sender<IoResult<RequestResult>>,
},
Notify {
notification: ClientNotification,
response_tx: oneshot::Sender<IoResult<()>>,
},
ResolveServerRequest {
request_id: RequestId,
result: JsonRpcResult,
response_tx: oneshot::Sender<IoResult<()>>,
},
RejectServerRequest {
request_id: RequestId,
error: JSONRPCErrorError,
response_tx: oneshot::Sender<IoResult<()>>,
},
Shutdown {
response_tx: oneshot::Sender<IoResult<()>>,
},
}
pub struct RemoteAppServerClient {
command_tx: mpsc::Sender<RemoteClientCommand>,
event_rx: mpsc::Receiver<AppServerEvent>,
pending_events: VecDeque<AppServerEvent>,
worker_handle: tokio::task::JoinHandle<()>,
}
#[derive(Clone)]
pub struct RemoteAppServerRequestHandle {
command_tx: mpsc::Sender<RemoteClientCommand>,
}
impl RemoteAppServerClient {
pub async fn connect(args: RemoteAppServerConnectArgs) -> IoResult<Self> {
let channel_capacity = args.channel_capacity.max(1);
let websocket_url = args.websocket_url.clone();
let url = Url::parse(&websocket_url).map_err(|err| {
IoError::new(
ErrorKind::InvalidInput,
format!("invalid websocket URL `{websocket_url}`: {err}"),
)
})?;
let stream = timeout(CONNECT_TIMEOUT, connect_async(url.as_str()))
.await
.map_err(|_| {
IoError::new(
ErrorKind::TimedOut,
format!("timed out connecting to remote app server at `{websocket_url}`"),
)
})?
.map(|(stream, _response)| stream)
.map_err(|err| {
IoError::other(format!(
"failed to connect to remote app server at `{websocket_url}`: {err}"
))
})?;
let mut stream = stream;
let pending_events = initialize_remote_connection(
&mut stream,
&websocket_url,
args.initialize_params(),
INITIALIZE_TIMEOUT,
)
.await?;
let (command_tx, mut command_rx) = mpsc::channel::<RemoteClientCommand>(channel_capacity);
let (event_tx, event_rx) = mpsc::channel::<AppServerEvent>(channel_capacity);
let worker_handle = tokio::spawn(async move {
let mut pending_requests =
HashMap::<RequestId, oneshot::Sender<IoResult<RequestResult>>>::new();
let mut skipped_events = 0usize;
loop {
tokio::select! {
command = command_rx.recv() => {
let Some(command) = command else {
let _ = stream.close(None).await;
break;
};
match command {
RemoteClientCommand::Request { request, response_tx } => {
let request_id = request_id_from_client_request(&request);
if pending_requests.contains_key(&request_id) {
let _ = response_tx.send(Err(IoError::new(
ErrorKind::InvalidInput,
format!("duplicate remote app-server request id `{request_id}`"),
)));
continue;
}
pending_requests.insert(request_id.clone(), response_tx);
if let Err(err) = write_jsonrpc_message(
&mut stream,
JSONRPCMessage::Request(jsonrpc_request_from_client_request(*request)),
&websocket_url,
)
.await
{
let err_message = err.to_string();
if let Some(response_tx) = pending_requests.remove(&request_id) {
let _ = response_tx.send(Err(err));
}
let _ = deliver_event(
&event_tx,
&mut skipped_events,
AppServerEvent::Disconnected {
message: format!(
"remote app server at `{websocket_url}` write failed: {err_message}"
),
},
&mut stream,
)
.await;
break;
}
}
RemoteClientCommand::Notify { notification, response_tx } => {
let result = write_jsonrpc_message(
&mut stream,
JSONRPCMessage::Notification(
jsonrpc_notification_from_client_notification(notification),
),
&websocket_url,
)
.await;
let _ = response_tx.send(result);
}
RemoteClientCommand::ResolveServerRequest {
request_id,
result,
response_tx,
} => {
let result = write_jsonrpc_message(
&mut stream,
JSONRPCMessage::Response(JSONRPCResponse {
id: request_id,
result,
}),
&websocket_url,
)
.await;
let _ = response_tx.send(result);
}
RemoteClientCommand::RejectServerRequest {
request_id,
error,
response_tx,
} => {
let result = write_jsonrpc_message(
&mut stream,
JSONRPCMessage::Error(JSONRPCError {
error,
id: request_id,
}),
&websocket_url,
)
.await;
let _ = response_tx.send(result);
}
RemoteClientCommand::Shutdown { response_tx } => {
let close_result = stream.close(None).await.map_err(|err| {
IoError::other(format!(
"failed to close websocket app server `{websocket_url}`: {err}"
))
});
let _ = response_tx.send(close_result);
break;
}
}
}
message = stream.next() => {
match message {
Some(Ok(Message::Text(text))) => {
match serde_json::from_str::<JSONRPCMessage>(&text) {
Ok(JSONRPCMessage::Response(response)) => {
if let Some(response_tx) = pending_requests.remove(&response.id) {
let _ = response_tx.send(Ok(Ok(response.result)));
}
}
Ok(JSONRPCMessage::Error(error)) => {
if let Some(response_tx) = pending_requests.remove(&error.id) {
let _ = response_tx.send(Ok(Err(error.error)));
}
}
Ok(JSONRPCMessage::Notification(notification)) => {
let event = app_server_event_from_notification(notification);
if let Err(err) = deliver_event(
&event_tx,
&mut skipped_events,
event,
&mut stream,
)
.await
{
warn!(%err, "failed to deliver remote app-server event");
break;
}
}
Ok(JSONRPCMessage::Request(request)) => {
let request_id = request.id.clone();
let method = request.method.clone();
match ServerRequest::try_from(request) {
Ok(request) => {
if let Err(err) = deliver_event(
&event_tx,
&mut skipped_events,
AppServerEvent::ServerRequest(request),
&mut stream,
)
.await
{
warn!(%err, "failed to deliver remote app-server server request");
break;
}
}
Err(err) => {
warn!(%err, method, "rejecting unknown remote app-server request");
if let Err(reject_err) = write_jsonrpc_message(
&mut stream,
JSONRPCMessage::Error(JSONRPCError {
error: JSONRPCErrorError {
code: -32601,
message: format!(
"unsupported remote app-server request `{method}`"
),
data: None,
},
id: request_id,
}),
&websocket_url,
)
.await
{
let err_message = reject_err.to_string();
let _ = deliver_event(
&event_tx,
&mut skipped_events,
AppServerEvent::Disconnected {
message: format!(
"remote app server at `{websocket_url}` write failed: {err_message}"
),
},
&mut stream,
)
.await;
break;
}
}
}
}
Err(err) => {
let _ = deliver_event(
&event_tx,
&mut skipped_events,
AppServerEvent::Disconnected {
message: format!(
"remote app server at `{websocket_url}` sent invalid JSON-RPC: {err}"
),
},
&mut stream,
)
.await;
break;
}
}
}
Some(Ok(Message::Close(frame))) => {
let reason = frame
.as_ref()
.map(|frame| frame.reason.to_string())
.filter(|reason| !reason.is_empty())
.unwrap_or_else(|| "connection closed".to_string());
let _ = deliver_event(
&event_tx,
&mut skipped_events,
AppServerEvent::Disconnected {
message: format!(
"remote app server at `{websocket_url}` disconnected: {reason}"
),
},
&mut stream,
)
.await;
break;
}
Some(Ok(Message::Binary(_)))
| Some(Ok(Message::Ping(_)))
| Some(Ok(Message::Pong(_)))
| Some(Ok(Message::Frame(_))) => {}
Some(Err(err)) => {
let _ = deliver_event(
&event_tx,
&mut skipped_events,
AppServerEvent::Disconnected {
message: format!(
"remote app server at `{websocket_url}` transport failed: {err}"
),
},
&mut stream,
)
.await;
break;
}
None => {
let _ = deliver_event(
&event_tx,
&mut skipped_events,
AppServerEvent::Disconnected {
message: format!(
"remote app server at `{websocket_url}` closed the connection"
),
},
&mut stream,
)
.await;
break;
}
}
}
}
}
let err = IoError::new(
ErrorKind::BrokenPipe,
"remote app-server worker channel is closed",
);
for (_, response_tx) in pending_requests {
let _ = response_tx.send(Err(IoError::new(err.kind(), err.to_string())));
}
});
Ok(Self {
command_tx,
event_rx,
pending_events: pending_events.into(),
worker_handle,
})
}
pub fn request_handle(&self) -> RemoteAppServerRequestHandle {
RemoteAppServerRequestHandle {
command_tx: self.command_tx.clone(),
}
}
pub async fn request(&self, request: ClientRequest) -> IoResult<RequestResult> {
let (response_tx, response_rx) = oneshot::channel();
self.command_tx
.send(RemoteClientCommand::Request {
request: Box::new(request),
response_tx,
})
.await
.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server worker channel is closed",
)
})?;
response_rx.await.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server request channel is closed",
)
})?
}
pub async fn request_typed<T>(&self, request: ClientRequest) -> Result<T, TypedRequestError>
where
T: DeserializeOwned,
{
let method = request_method_name(&request);
let response =
self.request(request)
.await
.map_err(|source| TypedRequestError::Transport {
method: method.clone(),
source,
})?;
let result = response.map_err(|source| TypedRequestError::Server {
method: method.clone(),
source,
})?;
serde_json::from_value(result)
.map_err(|source| TypedRequestError::Deserialize { method, source })
}
pub async fn notify(&self, notification: ClientNotification) -> IoResult<()> {
let (response_tx, response_rx) = oneshot::channel();
self.command_tx
.send(RemoteClientCommand::Notify {
notification,
response_tx,
})
.await
.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server worker channel is closed",
)
})?;
response_rx.await.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server notify channel is closed",
)
})?
}
pub async fn resolve_server_request(
&self,
request_id: RequestId,
result: JsonRpcResult,
) -> IoResult<()> {
let (response_tx, response_rx) = oneshot::channel();
self.command_tx
.send(RemoteClientCommand::ResolveServerRequest {
request_id,
result,
response_tx,
})
.await
.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server worker channel is closed",
)
})?;
response_rx.await.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server resolve channel is closed",
)
})?
}
pub async fn reject_server_request(
&self,
request_id: RequestId,
error: JSONRPCErrorError,
) -> IoResult<()> {
let (response_tx, response_rx) = oneshot::channel();
self.command_tx
.send(RemoteClientCommand::RejectServerRequest {
request_id,
error,
response_tx,
})
.await
.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server worker channel is closed",
)
})?;
response_rx.await.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server reject channel is closed",
)
})?
}
pub async fn next_event(&mut self) -> Option<AppServerEvent> {
if let Some(event) = self.pending_events.pop_front() {
return Some(event);
}
self.event_rx.recv().await
}
pub async fn shutdown(self) -> IoResult<()> {
let Self {
command_tx,
event_rx,
pending_events: _pending_events,
worker_handle,
} = self;
let mut worker_handle = worker_handle;
drop(event_rx);
let (response_tx, response_rx) = oneshot::channel();
if command_tx
.send(RemoteClientCommand::Shutdown { response_tx })
.await
.is_ok()
&& let Ok(command_result) = timeout(SHUTDOWN_TIMEOUT, response_rx).await
{
command_result.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server shutdown channel is closed",
)
})??;
}
if let Err(_elapsed) = timeout(SHUTDOWN_TIMEOUT, &mut worker_handle).await {
worker_handle.abort();
let _ = worker_handle.await;
}
Ok(())
}
}
impl RemoteAppServerRequestHandle {
pub async fn request(&self, request: ClientRequest) -> IoResult<RequestResult> {
let (response_tx, response_rx) = oneshot::channel();
self.command_tx
.send(RemoteClientCommand::Request {
request: Box::new(request),
response_tx,
})
.await
.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server worker channel is closed",
)
})?;
response_rx.await.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server request channel is closed",
)
})?
}
pub async fn request_typed<T>(&self, request: ClientRequest) -> Result<T, TypedRequestError>
where
T: DeserializeOwned,
{
let method = request_method_name(&request);
let response =
self.request(request)
.await
.map_err(|source| TypedRequestError::Transport {
method: method.clone(),
source,
})?;
let result = response.map_err(|source| TypedRequestError::Server {
method: method.clone(),
source,
})?;
serde_json::from_value(result)
.map_err(|source| TypedRequestError::Deserialize { method, source })
}
}
async fn initialize_remote_connection(
stream: &mut WebSocketStream<MaybeTlsStream<TcpStream>>,
websocket_url: &str,
params: InitializeParams,
initialize_timeout: Duration,
) -> IoResult<Vec<AppServerEvent>> {
let initialize_request_id = RequestId::String("initialize".to_string());
let mut pending_events = Vec::new();
write_jsonrpc_message(
stream,
JSONRPCMessage::Request(jsonrpc_request_from_client_request(
ClientRequest::Initialize {
request_id: initialize_request_id.clone(),
params,
},
)),
websocket_url,
)
.await?;
timeout(initialize_timeout, async {
loop {
match stream.next().await {
Some(Ok(Message::Text(text))) => {
let message = serde_json::from_str::<JSONRPCMessage>(&text).map_err(|err| {
IoError::other(format!(
"remote app server at `{websocket_url}` sent invalid initialize response: {err}"
))
})?;
match message {
JSONRPCMessage::Response(response) if response.id == initialize_request_id => {
break Ok(());
}
JSONRPCMessage::Error(error) if error.id == initialize_request_id => {
break Err(IoError::other(format!(
"remote app server at `{websocket_url}` rejected initialize: {}",
error.error.message
)));
}
JSONRPCMessage::Notification(notification) => {
pending_events.push(app_server_event_from_notification(notification));
}
JSONRPCMessage::Request(request) => {
let request_id = request.id.clone();
let method = request.method.clone();
match ServerRequest::try_from(request) {
Ok(request) => {
pending_events.push(AppServerEvent::ServerRequest(request));
}
Err(err) => {
warn!(%err, method, "rejecting unknown remote app-server request during initialize");
write_jsonrpc_message(
stream,
JSONRPCMessage::Error(JSONRPCError {
error: JSONRPCErrorError {
code: -32601,
message: format!(
"unsupported remote app-server request `{method}`"
),
data: None,
},
id: request_id,
}),
websocket_url,
)
.await?;
}
}
}
JSONRPCMessage::Response(_) | JSONRPCMessage::Error(_) => {}
}
}
Some(Ok(Message::Binary(_)))
| Some(Ok(Message::Ping(_)))
| Some(Ok(Message::Pong(_)))
| Some(Ok(Message::Frame(_))) => {}
Some(Ok(Message::Close(frame))) => {
let reason = frame
.as_ref()
.map(|frame| frame.reason.to_string())
.filter(|reason| !reason.is_empty())
.unwrap_or_else(|| "connection closed during initialize".to_string());
break Err(IoError::new(
ErrorKind::ConnectionAborted,
format!(
"remote app server at `{websocket_url}` closed during initialize: {reason}"
),
));
}
Some(Err(err)) => {
break Err(IoError::other(format!(
"remote app server at `{websocket_url}` transport failed during initialize: {err}"
)));
}
None => {
break Err(IoError::new(
ErrorKind::UnexpectedEof,
format!("remote app server at `{websocket_url}` closed during initialize"),
));
}
}
}
})
.await
.map_err(|_| {
IoError::new(
ErrorKind::TimedOut,
format!("timed out waiting for initialize response from `{websocket_url}`"),
)
})??;
write_jsonrpc_message(
stream,
JSONRPCMessage::Notification(jsonrpc_notification_from_client_notification(
ClientNotification::Initialized,
)),
websocket_url,
)
.await?;
Ok(pending_events)
}
fn app_server_event_from_notification(notification: JSONRPCNotification) -> AppServerEvent {
match ServerNotification::try_from(notification.clone()) {
Ok(notification) => AppServerEvent::ServerNotification(notification),
Err(_) => AppServerEvent::LegacyNotification(notification),
}
}
async fn deliver_event(
event_tx: &mpsc::Sender<AppServerEvent>,
skipped_events: &mut usize,
event: AppServerEvent,
stream: &mut WebSocketStream<MaybeTlsStream<TcpStream>>,
) -> IoResult<()> {
if *skipped_events > 0 {
if event_requires_delivery(&event) {
if event_tx
.send(AppServerEvent::Lagged {
skipped: *skipped_events,
})
.await
.is_err()
{
return Err(IoError::new(
ErrorKind::BrokenPipe,
"remote app-server event consumer channel is closed",
));
}
*skipped_events = 0;
} else {
match event_tx.try_send(AppServerEvent::Lagged {
skipped: *skipped_events,
}) {
Ok(()) => *skipped_events = 0,
Err(mpsc::error::TrySendError::Full(_)) => {
*skipped_events = (*skipped_events).saturating_add(1);
reject_if_server_request_dropped(stream, &event).await?;
return Ok(());
}
Err(mpsc::error::TrySendError::Closed(_)) => {
return Err(IoError::new(
ErrorKind::BrokenPipe,
"remote app-server event consumer channel is closed",
));
}
}
}
}
if event_requires_delivery(&event) {
event_tx.send(event).await.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server event consumer channel is closed",
)
})?;
return Ok(());
}
match event_tx.try_send(event) {
Ok(()) => Ok(()),
Err(mpsc::error::TrySendError::Full(event)) => {
*skipped_events = (*skipped_events).saturating_add(1);
reject_if_server_request_dropped(stream, &event).await
}
Err(mpsc::error::TrySendError::Closed(_)) => Err(IoError::new(
ErrorKind::BrokenPipe,
"remote app-server event consumer channel is closed",
)),
}
}
async fn reject_if_server_request_dropped(
stream: &mut WebSocketStream<MaybeTlsStream<TcpStream>>,
event: &AppServerEvent,
) -> IoResult<()> {
let AppServerEvent::ServerRequest(request) = event else {
return Ok(());
};
write_jsonrpc_message(
stream,
JSONRPCMessage::Error(JSONRPCError {
error: JSONRPCErrorError {
code: -32001,
message: "remote app-server event queue is full".to_string(),
data: None,
},
id: request.id().clone(),
}),
"<remote-app-server>",
)
.await
}
fn event_requires_delivery(event: &AppServerEvent) -> bool {
match event {
AppServerEvent::ServerNotification(ServerNotification::TurnCompleted(_)) => true,
AppServerEvent::LegacyNotification(notification) => matches!(
notification
.method
.strip_prefix("codex/event/")
.unwrap_or(&notification.method),
"task_complete" | "turn_aborted" | "shutdown_complete"
),
AppServerEvent::Disconnected { .. } => true,
AppServerEvent::Lagged { .. }
| AppServerEvent::ServerNotification(_)
| AppServerEvent::ServerRequest(_) => false,
}
}
fn request_id_from_client_request(request: &ClientRequest) -> RequestId {
jsonrpc_request_from_client_request(request.clone()).id
}
fn jsonrpc_request_from_client_request(request: ClientRequest) -> JSONRPCRequest {
let value = match serde_json::to_value(request) {
Ok(value) => value,
Err(err) => panic!("client request should serialize: {err}"),
};
match serde_json::from_value(value) {
Ok(request) => request,
Err(err) => panic!("client request should encode as JSON-RPC request: {err}"),
}
}
fn jsonrpc_notification_from_client_notification(
notification: ClientNotification,
) -> JSONRPCNotification {
let value = match serde_json::to_value(notification) {
Ok(value) => value,
Err(err) => panic!("client notification should serialize: {err}"),
};
match serde_json::from_value(value) {
Ok(notification) => notification,
Err(err) => panic!("client notification should encode as JSON-RPC notification: {err}"),
}
}
async fn write_jsonrpc_message(
stream: &mut WebSocketStream<MaybeTlsStream<TcpStream>>,
message: JSONRPCMessage,
websocket_url: &str,
) -> IoResult<()> {
let payload = serde_json::to_string(&message).map_err(IoError::other)?;
stream
.send(Message::Text(payload.into()))
.await
.map_err(|err| {
IoError::other(format!(
"failed to write websocket message to `{websocket_url}`: {err}"
))
})
}

View File

@@ -5,6 +5,14 @@
"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"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AppsListParams": {
"description": "EXPERIMENTAL - list available apps/connectors.",
"properties": {
@@ -498,6 +506,9 @@
},
"DynamicToolSpec": {
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -634,6 +645,164 @@
],
"type": "object"
},
"FsCopyParams": {
"description": "Copy a file or directory tree on the host filesystem.",
"properties": {
"destinationPath": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute destination path."
},
"recursive": {
"description": "Required for directory copies; ignored for file copies.",
"type": "boolean"
},
"sourcePath": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute source path."
}
},
"required": [
"destinationPath",
"sourcePath"
],
"type": "object"
},
"FsCreateDirectoryParams": {
"description": "Create a directory on the host filesystem.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute directory path to create."
},
"recursive": {
"description": "Whether parent directories should also be created. Defaults to `true`.",
"type": [
"boolean",
"null"
]
}
},
"required": [
"path"
],
"type": "object"
},
"FsGetMetadataParams": {
"description": "Request metadata for an absolute path.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to inspect."
}
},
"required": [
"path"
],
"type": "object"
},
"FsReadDirectoryParams": {
"description": "List direct child names for a directory.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute directory path to read."
}
},
"required": [
"path"
],
"type": "object"
},
"FsReadFileParams": {
"description": "Read a file from the host filesystem.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to read."
}
},
"required": [
"path"
],
"type": "object"
},
"FsRemoveParams": {
"description": "Remove a file or directory tree from the host filesystem.",
"properties": {
"force": {
"description": "Whether missing paths should be ignored. Defaults to `true`.",
"type": [
"boolean",
"null"
]
},
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to remove."
},
"recursive": {
"description": "Whether directory removal should recurse. Defaults to `true`.",
"type": [
"boolean",
"null"
]
}
},
"required": [
"path"
],
"type": "object"
},
"FsWriteFileParams": {
"description": "Write a file on the host filesystem.",
"properties": {
"dataBase64": {
"description": "File contents encoded as base64.",
"type": "string"
},
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to write."
}
},
"required": [
"dataBase64",
"path"
],
"type": "object"
},
"FunctionCallOutputBody": {
"anyOf": [
{
@@ -2350,6 +2519,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",
@@ -2630,6 +2810,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",
@@ -2781,6 +2972,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",
@@ -2920,6 +3122,17 @@
],
"description": "Override the approval policy for this turn and subsequent turns."
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this turn and subsequent turns."
},
"cwd": {
"description": "Override the working directory for this turn and subsequent turns.",
"type": [
@@ -3670,6 +3883,174 @@
"title": "App/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/readFile"
],
"title": "Fs/readFileRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsReadFileParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/readFileRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/writeFile"
],
"title": "Fs/writeFileRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsWriteFileParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/writeFileRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/createDirectory"
],
"title": "Fs/createDirectoryRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsCreateDirectoryParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/createDirectoryRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/getMetadata"
],
"title": "Fs/getMetadataRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsGetMetadataParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/getMetadataRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/readDirectory"
],
"title": "Fs/readDirectoryRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsReadDirectoryParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/readDirectoryRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/remove"
],
"title": "Fs/removeRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsRemoveParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/removeRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/copy"
],
"title": "Fs/copyRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsCopyParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/copyRequest",
"type": "object"
},
{
"properties": {
"id": {

View File

@@ -28,41 +28,6 @@
},
"type": "object"
},
"AdditionalMacOsPermissions": {
"properties": {
"accessibility": {
"type": "boolean"
},
"automations": {
"$ref": "#/definitions/MacOsAutomationPermission"
},
"calendar": {
"type": "boolean"
},
"contacts": {
"$ref": "#/definitions/MacOsContactsPermission"
},
"launchServices": {
"type": "boolean"
},
"preferences": {
"$ref": "#/definitions/MacOsPreferencesPermission"
},
"reminders": {
"type": "boolean"
}
},
"required": [
"accessibility",
"automations",
"calendar",
"contacts",
"launchServices",
"preferences",
"reminders"
],
"type": "object"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
@@ -74,7 +39,8 @@
},
"type": "object"
},
"AdditionalPermissionProfile": {
"RequestPermissionProfile": {
"additionalProperties": false,
"properties": {
"fileSystem": {
"anyOf": [
@@ -86,16 +52,6 @@
}
]
},
"macos": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalMacOsPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
@@ -108,49 +64,6 @@
}
},
"type": "object"
},
"MacOsAutomationPermission": {
"oneOf": [
{
"enum": [
"none",
"all"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"bundle_ids": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"bundle_ids"
],
"title": "BundleIdsMacOsAutomationPermission",
"type": "object"
}
]
},
"MacOsContactsPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
},
"MacOsPreferencesPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
}
},
"properties": {
@@ -158,7 +71,7 @@
"type": "string"
},
"permissions": {
"$ref": "#/definitions/AdditionalPermissionProfile"
"$ref": "#/definitions/RequestPermissionProfile"
},
"reason": {
"type": [

View File

@@ -39,65 +39,6 @@
},
"type": "object"
},
"GrantedMacOsPermissions": {
"properties": {
"accessibility": {
"type": [
"boolean",
"null"
]
},
"automations": {
"anyOf": [
{
"$ref": "#/definitions/MacOsAutomationPermission"
},
{
"type": "null"
}
]
},
"calendar": {
"type": [
"boolean",
"null"
]
},
"contacts": {
"anyOf": [
{
"$ref": "#/definitions/MacOsContactsPermission"
},
{
"type": "null"
}
]
},
"launchServices": {
"type": [
"boolean",
"null"
]
},
"preferences": {
"anyOf": [
{
"$ref": "#/definitions/MacOsPreferencesPermission"
},
{
"type": "null"
}
]
},
"reminders": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"GrantedPermissionProfile": {
"properties": {
"fileSystem": {
@@ -110,16 +51,6 @@
}
]
},
"macos": {
"anyOf": [
{
"$ref": "#/definitions/GrantedMacOsPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
@@ -133,49 +64,6 @@
},
"type": "object"
},
"MacOsAutomationPermission": {
"oneOf": [
{
"enum": [
"none",
"all"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"bundle_ids": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"bundle_ids"
],
"title": "BundleIdsMacOsAutomationPermission",
"type": "object"
}
]
},
"MacOsContactsPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
},
"MacOsPreferencesPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
},
"PermissionGrantScope": {
"enum": [
"turn",

View File

@@ -535,6 +535,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",
@@ -1056,6 +1057,61 @@
},
"type": "object"
},
"GuardianApprovalReview": {
"description": "[UNSTABLE] Temporary guardian approval review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
"properties": {
"rationale": {
"type": [
"string",
"null"
]
},
"riskLevel": {
"anyOf": [
{
"$ref": "#/definitions/GuardianRiskLevel"
},
{
"type": "null"
}
]
},
"riskScore": {
"format": "uint8",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/GuardianApprovalReviewStatus"
}
},
"required": [
"status"
],
"type": "object"
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
"inProgress",
"approved",
"denied",
"aborted"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
"low",
"medium",
"high"
],
"type": "string"
},
"HookCompletedNotification": {
"properties": {
"run": {
@@ -1253,6 +1309,56 @@
],
"type": "object"
},
"ItemGuardianApprovalReviewCompletedNotification": {
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
"targetItemId": {
"type": "string"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": "string"
}
},
"required": [
"review",
"targetItemId",
"threadId",
"turnId"
],
"type": "object"
},
"ItemGuardianApprovalReviewStartedNotification": {
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
"targetItemId": {
"type": "string"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": "string"
}
},
"required": [
"review",
"targetItemId",
"threadId",
"turnId"
],
"type": "object"
},
"ItemStartedNotification": {
"properties": {
"item": {
@@ -3706,6 +3812,46 @@
"title": "Item/startedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"item/autoApprovalReview/started"
],
"title": "Item/autoApprovalReview/startedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ItemGuardianApprovalReviewStartedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Item/autoApprovalReview/startedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"item/autoApprovalReview/completed"
],
"title": "Item/autoApprovalReview/completedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ItemGuardianApprovalReviewCompletedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Item/autoApprovalReview/completedNotification",
"type": "object"
},
{
"properties": {
"method": {

View File

@@ -1449,7 +1449,7 @@
"type": "string"
},
"permissions": {
"$ref": "#/definitions/AdditionalPermissionProfile"
"$ref": "#/definitions/RequestPermissionProfile"
},
"reason": {
"type": [
@@ -1483,6 +1483,32 @@
}
]
},
"RequestPermissionProfile": {
"additionalProperties": false,
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"ThreadId": {
"type": "string"
},

View File

@@ -739,6 +739,174 @@
"title": "App/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"fs/readFile"
],
"title": "Fs/readFileRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/FsReadFileParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/readFileRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"fs/writeFile"
],
"title": "Fs/writeFileRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/FsWriteFileParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/writeFileRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"fs/createDirectory"
],
"title": "Fs/createDirectoryRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/FsCreateDirectoryParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/createDirectoryRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"fs/getMetadata"
],
"title": "Fs/getMetadataRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/FsGetMetadataParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/getMetadataRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"fs/readDirectory"
],
"title": "Fs/readDirectoryRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/FsReadDirectoryParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/readDirectoryRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"fs/remove"
],
"title": "Fs/removeRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/FsRemoveParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/removeRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"fs/copy"
],
"title": "Fs/copyRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/FsCopyParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/copyRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -2030,65 +2198,6 @@
"title": "FuzzyFileSearchSessionUpdatedNotification",
"type": "object"
},
"GrantedMacOsPermissions": {
"properties": {
"accessibility": {
"type": [
"boolean",
"null"
]
},
"automations": {
"anyOf": [
{
"$ref": "#/definitions/MacOsAutomationPermission"
},
{
"type": "null"
}
]
},
"calendar": {
"type": [
"boolean",
"null"
]
},
"contacts": {
"anyOf": [
{
"$ref": "#/definitions/MacOsContactsPermission"
},
{
"type": "null"
}
]
},
"launchServices": {
"type": [
"boolean",
"null"
]
},
"preferences": {
"anyOf": [
{
"$ref": "#/definitions/MacOsPreferencesPermission"
},
{
"type": "null"
}
]
},
"reminders": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"GrantedPermissionProfile": {
"properties": {
"fileSystem": {
@@ -2101,16 +2210,6 @@
}
]
},
"macos": {
"anyOf": [
{
"$ref": "#/definitions/GrantedMacOsPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
@@ -3156,7 +3255,7 @@
"type": "string"
},
"permissions": {
"$ref": "#/definitions/AdditionalPermissionProfile"
"$ref": "#/definitions/RequestPermissionProfile"
},
"reason": {
"type": [
@@ -3214,6 +3313,32 @@
],
"title": "RequestId"
},
"RequestPermissionProfile": {
"additionalProperties": false,
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"ReviewDecision": {
"description": "User's decision in response to an ExecApprovalRequest.",
"oneOf": [
@@ -3619,6 +3744,46 @@
"title": "Item/startedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"item/autoApprovalReview/started"
],
"title": "Item/autoApprovalReview/startedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/ItemGuardianApprovalReviewStartedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Item/autoApprovalReview/startedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"item/autoApprovalReview/completed"
],
"title": "Item/autoApprovalReview/completedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/ItemGuardianApprovalReviewCompletedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Item/autoApprovalReview/completedNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -5120,6 +5285,14 @@
"AppToolsConfig": {
"type": "object"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AppsConfig": {
"properties": {
"_default": {
@@ -5475,6 +5648,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",
@@ -6016,6 +6190,17 @@
}
]
},
"approvals_reviewer": {
"anyOf": [
{
"$ref": "#/definitions/v2/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "[UNSTABLE] Optional default for where approval requests are routed for review."
},
"compact_prompt": {
"type": [
"string",
@@ -6823,6 +7008,9 @@
},
"DynamicToolSpec": {
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -7191,6 +7379,290 @@
],
"type": "string"
},
"FsCopyParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Copy a file or directory tree on the host filesystem.",
"properties": {
"destinationPath": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Absolute destination path."
},
"recursive": {
"description": "Required for directory copies; ignored for file copies.",
"type": "boolean"
},
"sourcePath": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Absolute source path."
}
},
"required": [
"destinationPath",
"sourcePath"
],
"title": "FsCopyParams",
"type": "object"
},
"FsCopyResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/copy`.",
"title": "FsCopyResponse",
"type": "object"
},
"FsCreateDirectoryParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Create a directory on the host filesystem.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Absolute directory path to create."
},
"recursive": {
"description": "Whether parent directories should also be created. Defaults to `true`.",
"type": [
"boolean",
"null"
]
}
},
"required": [
"path"
],
"title": "FsCreateDirectoryParams",
"type": "object"
},
"FsCreateDirectoryResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/createDirectory`.",
"title": "FsCreateDirectoryResponse",
"type": "object"
},
"FsGetMetadataParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Request metadata for an absolute path.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Absolute path to inspect."
}
},
"required": [
"path"
],
"title": "FsGetMetadataParams",
"type": "object"
},
"FsGetMetadataResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Metadata returned by `fs/getMetadata`.",
"properties": {
"createdAtMs": {
"description": "File creation time in Unix milliseconds when available, otherwise `0`.",
"format": "int64",
"type": "integer"
},
"isDirectory": {
"description": "Whether the path currently resolves to a directory.",
"type": "boolean"
},
"isFile": {
"description": "Whether the path currently resolves to a regular file.",
"type": "boolean"
},
"modifiedAtMs": {
"description": "File modification time in Unix milliseconds when available, otherwise `0`.",
"format": "int64",
"type": "integer"
}
},
"required": [
"createdAtMs",
"isDirectory",
"isFile",
"modifiedAtMs"
],
"title": "FsGetMetadataResponse",
"type": "object"
},
"FsReadDirectoryEntry": {
"description": "A directory entry returned by `fs/readDirectory`.",
"properties": {
"fileName": {
"description": "Direct child entry name only, not an absolute or relative path.",
"type": "string"
},
"isDirectory": {
"description": "Whether this entry resolves to a directory.",
"type": "boolean"
},
"isFile": {
"description": "Whether this entry resolves to a regular file.",
"type": "boolean"
}
},
"required": [
"fileName",
"isDirectory",
"isFile"
],
"type": "object"
},
"FsReadDirectoryParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "List direct child names for a directory.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Absolute directory path to read."
}
},
"required": [
"path"
],
"title": "FsReadDirectoryParams",
"type": "object"
},
"FsReadDirectoryResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Directory entries returned by `fs/readDirectory`.",
"properties": {
"entries": {
"description": "Direct child entries in the requested directory.",
"items": {
"$ref": "#/definitions/v2/FsReadDirectoryEntry"
},
"type": "array"
}
},
"required": [
"entries"
],
"title": "FsReadDirectoryResponse",
"type": "object"
},
"FsReadFileParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Read a file from the host filesystem.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Absolute path to read."
}
},
"required": [
"path"
],
"title": "FsReadFileParams",
"type": "object"
},
"FsReadFileResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Base64-encoded file contents returned by `fs/readFile`.",
"properties": {
"dataBase64": {
"description": "File contents encoded as base64.",
"type": "string"
}
},
"required": [
"dataBase64"
],
"title": "FsReadFileResponse",
"type": "object"
},
"FsRemoveParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Remove a file or directory tree from the host filesystem.",
"properties": {
"force": {
"description": "Whether missing paths should be ignored. Defaults to `true`.",
"type": [
"boolean",
"null"
]
},
"path": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Absolute path to remove."
},
"recursive": {
"description": "Whether directory removal should recurse. Defaults to `true`.",
"type": [
"boolean",
"null"
]
}
},
"required": [
"path"
],
"title": "FsRemoveParams",
"type": "object"
},
"FsRemoveResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/remove`.",
"title": "FsRemoveResponse",
"type": "object"
},
"FsWriteFileParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Write a file on the host filesystem.",
"properties": {
"dataBase64": {
"description": "File contents encoded as base64.",
"type": "string"
},
"path": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Absolute path to write."
}
},
"required": [
"dataBase64",
"path"
],
"title": "FsWriteFileParams",
"type": "object"
},
"FsWriteFileResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/writeFile`.",
"title": "FsWriteFileResponse",
"type": "object"
},
"FunctionCallOutputBody": {
"anyOf": [
{
@@ -7395,6 +7867,61 @@
},
"type": "object"
},
"GuardianApprovalReview": {
"description": "[UNSTABLE] Temporary guardian approval review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
"properties": {
"rationale": {
"type": [
"string",
"null"
]
},
"riskLevel": {
"anyOf": [
{
"$ref": "#/definitions/v2/GuardianRiskLevel"
},
{
"type": "null"
}
]
},
"riskScore": {
"format": "uint8",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/v2/GuardianApprovalReviewStatus"
}
},
"required": [
"status"
],
"type": "object"
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
"inProgress",
"approved",
"denied",
"aborted"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
"low",
"medium",
"high"
],
"type": "string"
},
"HazelnutScope": {
"enum": [
"example",
@@ -7635,6 +8162,60 @@
"title": "ItemCompletedNotification",
"type": "object"
},
"ItemGuardianApprovalReviewCompletedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": true,
"review": {
"$ref": "#/definitions/v2/GuardianApprovalReview"
},
"targetItemId": {
"type": "string"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": "string"
}
},
"required": [
"review",
"targetItemId",
"threadId",
"turnId"
],
"title": "ItemGuardianApprovalReviewCompletedNotification",
"type": "object"
},
"ItemGuardianApprovalReviewStartedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": true,
"review": {
"$ref": "#/definitions/v2/GuardianApprovalReview"
},
"targetItemId": {
"type": "string"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": "string"
}
},
"required": [
"review",
"targetItemId",
"threadId",
"turnId"
],
"title": "ItemGuardianApprovalReviewStartedNotification",
"type": "object"
},
"ItemStartedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -8672,8 +9253,12 @@
]
},
"defaultPrompt": {
"description": "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.",
"items": {
"type": "string"
},
"type": [
"string",
"array",
"null"
]
},
@@ -8946,6 +9531,17 @@
}
]
},
"approvals_reviewer": {
"anyOf": [
{
"$ref": "#/definitions/v2/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "[UNSTABLE] Optional profile-level override for where approval requests are routed for review. If omitted, the enclosing config default is used."
},
"chatgpt_base_url": {
"type": [
"string",
@@ -11201,6 +11797,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/v2/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",
@@ -11285,6 +11892,14 @@
"approvalPolicy": {
"$ref": "#/definitions/v2/AskForApproval"
},
"approvalsReviewer": {
"allOf": [
{
"$ref": "#/definitions/v2/ApprovalsReviewer"
}
],
"description": "Reviewer currently used for approval requests on this thread."
},
"cwd": {
"type": "string"
},
@@ -11323,6 +11938,7 @@
},
"required": [
"approvalPolicy",
"approvalsReviewer",
"cwd",
"model",
"modelProvider",
@@ -12319,6 +12935,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/v2/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",
@@ -12410,6 +13037,14 @@
"approvalPolicy": {
"$ref": "#/definitions/v2/AskForApproval"
},
"approvalsReviewer": {
"allOf": [
{
"$ref": "#/definitions/v2/ApprovalsReviewer"
}
],
"description": "Reviewer currently used for approval requests on this thread."
},
"cwd": {
"type": "string"
},
@@ -12448,6 +13083,7 @@
},
"required": [
"approvalPolicy",
"approvalsReviewer",
"cwd",
"model",
"modelProvider",
@@ -12552,6 +13188,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/v2/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",
@@ -12648,6 +13295,14 @@
"approvalPolicy": {
"$ref": "#/definitions/v2/AskForApproval"
},
"approvalsReviewer": {
"allOf": [
{
"$ref": "#/definitions/v2/ApprovalsReviewer"
}
],
"description": "Reviewer currently used for approval requests on this thread."
},
"cwd": {
"type": "string"
},
@@ -12686,6 +13341,7 @@
},
"required": [
"approvalPolicy",
"approvalsReviewer",
"cwd",
"model",
"modelProvider",
@@ -13195,6 +13851,17 @@
],
"description": "Override the approval policy for this turn and subsequent turns."
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/v2/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this turn and subsequent turns."
},
"cwd": {
"description": "Override the working directory for this turn and subsequent turns.",
"type": [

View File

@@ -532,6 +532,14 @@
"AppToolsConfig": {
"type": "object"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AppsConfig": {
"properties": {
"_default": {
@@ -1258,6 +1266,174 @@
"title": "App/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/readFile"
],
"title": "Fs/readFileRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsReadFileParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/readFileRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/writeFile"
],
"title": "Fs/writeFileRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsWriteFileParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/writeFileRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/createDirectory"
],
"title": "Fs/createDirectoryRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsCreateDirectoryParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/createDirectoryRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/getMetadata"
],
"title": "Fs/getMetadataRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsGetMetadataParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/getMetadataRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/readDirectory"
],
"title": "Fs/readDirectoryRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsReadDirectoryParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/readDirectoryRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/remove"
],
"title": "Fs/removeRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsRemoveParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/removeRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"fs/copy"
],
"title": "Fs/copyRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FsCopyParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Fs/copyRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -2116,6 +2292,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",
@@ -2657,6 +2834,17 @@
}
]
},
"approvals_reviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "[UNSTABLE] Optional default for where approval requests are routed for review."
},
"compact_prompt": {
"type": [
"string",
@@ -3464,6 +3652,9 @@
},
"DynamicToolSpec": {
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -3832,6 +4023,290 @@
],
"type": "string"
},
"FsCopyParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Copy a file or directory tree on the host filesystem.",
"properties": {
"destinationPath": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute destination path."
},
"recursive": {
"description": "Required for directory copies; ignored for file copies.",
"type": "boolean"
},
"sourcePath": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute source path."
}
},
"required": [
"destinationPath",
"sourcePath"
],
"title": "FsCopyParams",
"type": "object"
},
"FsCopyResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/copy`.",
"title": "FsCopyResponse",
"type": "object"
},
"FsCreateDirectoryParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Create a directory on the host filesystem.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute directory path to create."
},
"recursive": {
"description": "Whether parent directories should also be created. Defaults to `true`.",
"type": [
"boolean",
"null"
]
}
},
"required": [
"path"
],
"title": "FsCreateDirectoryParams",
"type": "object"
},
"FsCreateDirectoryResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/createDirectory`.",
"title": "FsCreateDirectoryResponse",
"type": "object"
},
"FsGetMetadataParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Request metadata for an absolute path.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to inspect."
}
},
"required": [
"path"
],
"title": "FsGetMetadataParams",
"type": "object"
},
"FsGetMetadataResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Metadata returned by `fs/getMetadata`.",
"properties": {
"createdAtMs": {
"description": "File creation time in Unix milliseconds when available, otherwise `0`.",
"format": "int64",
"type": "integer"
},
"isDirectory": {
"description": "Whether the path currently resolves to a directory.",
"type": "boolean"
},
"isFile": {
"description": "Whether the path currently resolves to a regular file.",
"type": "boolean"
},
"modifiedAtMs": {
"description": "File modification time in Unix milliseconds when available, otherwise `0`.",
"format": "int64",
"type": "integer"
}
},
"required": [
"createdAtMs",
"isDirectory",
"isFile",
"modifiedAtMs"
],
"title": "FsGetMetadataResponse",
"type": "object"
},
"FsReadDirectoryEntry": {
"description": "A directory entry returned by `fs/readDirectory`.",
"properties": {
"fileName": {
"description": "Direct child entry name only, not an absolute or relative path.",
"type": "string"
},
"isDirectory": {
"description": "Whether this entry resolves to a directory.",
"type": "boolean"
},
"isFile": {
"description": "Whether this entry resolves to a regular file.",
"type": "boolean"
}
},
"required": [
"fileName",
"isDirectory",
"isFile"
],
"type": "object"
},
"FsReadDirectoryParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "List direct child names for a directory.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute directory path to read."
}
},
"required": [
"path"
],
"title": "FsReadDirectoryParams",
"type": "object"
},
"FsReadDirectoryResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Directory entries returned by `fs/readDirectory`.",
"properties": {
"entries": {
"description": "Direct child entries in the requested directory.",
"items": {
"$ref": "#/definitions/FsReadDirectoryEntry"
},
"type": "array"
}
},
"required": [
"entries"
],
"title": "FsReadDirectoryResponse",
"type": "object"
},
"FsReadFileParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Read a file from the host filesystem.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to read."
}
},
"required": [
"path"
],
"title": "FsReadFileParams",
"type": "object"
},
"FsReadFileResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Base64-encoded file contents returned by `fs/readFile`.",
"properties": {
"dataBase64": {
"description": "File contents encoded as base64.",
"type": "string"
}
},
"required": [
"dataBase64"
],
"title": "FsReadFileResponse",
"type": "object"
},
"FsRemoveParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Remove a file or directory tree from the host filesystem.",
"properties": {
"force": {
"description": "Whether missing paths should be ignored. Defaults to `true`.",
"type": [
"boolean",
"null"
]
},
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to remove."
},
"recursive": {
"description": "Whether directory removal should recurse. Defaults to `true`.",
"type": [
"boolean",
"null"
]
}
},
"required": [
"path"
],
"title": "FsRemoveParams",
"type": "object"
},
"FsRemoveResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/remove`.",
"title": "FsRemoveResponse",
"type": "object"
},
"FsWriteFileParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Write a file on the host filesystem.",
"properties": {
"dataBase64": {
"description": "File contents encoded as base64.",
"type": "string"
},
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to write."
}
},
"required": [
"dataBase64",
"path"
],
"title": "FsWriteFileParams",
"type": "object"
},
"FsWriteFileResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/writeFile`.",
"title": "FsWriteFileResponse",
"type": "object"
},
"FunctionCallOutputBody": {
"anyOf": [
{
@@ -4136,6 +4611,61 @@
},
"type": "object"
},
"GuardianApprovalReview": {
"description": "[UNSTABLE] Temporary guardian approval review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
"properties": {
"rationale": {
"type": [
"string",
"null"
]
},
"riskLevel": {
"anyOf": [
{
"$ref": "#/definitions/GuardianRiskLevel"
},
{
"type": "null"
}
]
},
"riskScore": {
"format": "uint8",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/GuardianApprovalReviewStatus"
}
},
"required": [
"status"
],
"type": "object"
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
"inProgress",
"approved",
"denied",
"aborted"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
"low",
"medium",
"high"
],
"type": "string"
},
"HazelnutScope": {
"enum": [
"example",
@@ -4420,6 +4950,60 @@
"title": "ItemCompletedNotification",
"type": "object"
},
"ItemGuardianApprovalReviewCompletedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
"targetItemId": {
"type": "string"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": "string"
}
},
"required": [
"review",
"targetItemId",
"threadId",
"turnId"
],
"title": "ItemGuardianApprovalReviewCompletedNotification",
"type": "object"
},
"ItemGuardianApprovalReviewStartedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
"targetItemId": {
"type": "string"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": "string"
}
},
"required": [
"review",
"targetItemId",
"threadId",
"turnId"
],
"title": "ItemGuardianApprovalReviewStartedNotification",
"type": "object"
},
"ItemStartedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -5457,8 +6041,12 @@
]
},
"defaultPrompt": {
"description": "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.",
"items": {
"type": "string"
},
"type": [
"string",
"array",
"null"
]
},
@@ -5731,6 +6319,17 @@
}
]
},
"approvals_reviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "[UNSTABLE] Optional profile-level override for where approval requests are routed for review. If omitted, the enclosing config default is used."
},
"chatgpt_base_url": {
"type": [
"string",
@@ -7479,6 +8078,46 @@
"title": "Item/startedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"item/autoApprovalReview/started"
],
"title": "Item/autoApprovalReview/startedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ItemGuardianApprovalReviewStartedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Item/autoApprovalReview/startedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"item/autoApprovalReview/completed"
],
"title": "Item/autoApprovalReview/completedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ItemGuardianApprovalReviewCompletedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Item/autoApprovalReview/completedNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -8918,6 +9557,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",
@@ -9002,6 +9652,14 @@
"approvalPolicy": {
"$ref": "#/definitions/AskForApproval"
},
"approvalsReviewer": {
"allOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
}
],
"description": "Reviewer currently used for approval requests on this thread."
},
"cwd": {
"type": "string"
},
@@ -9040,6 +9698,7 @@
},
"required": [
"approvalPolicy",
"approvalsReviewer",
"cwd",
"model",
"modelProvider",
@@ -10036,6 +10695,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",
@@ -10127,6 +10797,14 @@
"approvalPolicy": {
"$ref": "#/definitions/AskForApproval"
},
"approvalsReviewer": {
"allOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
}
],
"description": "Reviewer currently used for approval requests on this thread."
},
"cwd": {
"type": "string"
},
@@ -10165,6 +10843,7 @@
},
"required": [
"approvalPolicy",
"approvalsReviewer",
"cwd",
"model",
"modelProvider",
@@ -10269,6 +10948,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",
@@ -10365,6 +11055,14 @@
"approvalPolicy": {
"$ref": "#/definitions/AskForApproval"
},
"approvalsReviewer": {
"allOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
}
],
"description": "Reviewer currently used for approval requests on this thread."
},
"cwd": {
"type": "string"
},
@@ -10403,6 +11101,7 @@
},
"required": [
"approvalPolicy",
"approvalsReviewer",
"cwd",
"model",
"modelProvider",
@@ -10912,6 +11611,17 @@
],
"description": "Override the approval policy for this turn and subsequent turns."
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this turn and subsequent turns."
},
"cwd": {
"description": "Override the working directory for this turn and subsequent turns.",
"type": [

View File

@@ -96,6 +96,14 @@
"AppToolsConfig": {
"type": "object"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AppsConfig": {
"properties": {
"_default": {
@@ -202,6 +210,17 @@
}
]
},
"approvals_reviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "[UNSTABLE] Optional default for where approval requests are routed for review."
},
"compact_prompt": {
"type": [
"string",
@@ -578,6 +597,17 @@
}
]
},
"approvals_reviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "[UNSTABLE] Optional profile-level override for where approval requests are routed for review. If omitted, the enclosing config default is used."
},
"chatgpt_base_url": {
"type": [
"string",

View File

@@ -0,0 +1,38 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
}
},
"description": "Copy a file or directory tree on the host filesystem.",
"properties": {
"destinationPath": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute destination path."
},
"recursive": {
"description": "Required for directory copies; ignored for file copies.",
"type": "boolean"
},
"sourcePath": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute source path."
}
},
"required": [
"destinationPath",
"sourcePath"
],
"title": "FsCopyParams",
"type": "object"
}

View File

@@ -0,0 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/copy`.",
"title": "FsCopyResponse",
"type": "object"
}

View File

@@ -0,0 +1,32 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
}
},
"description": "Create a directory on the host filesystem.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute directory path to create."
},
"recursive": {
"description": "Whether parent directories should also be created. Defaults to `true`.",
"type": [
"boolean",
"null"
]
}
},
"required": [
"path"
],
"title": "FsCreateDirectoryParams",
"type": "object"
}

View File

@@ -0,0 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/createDirectory`.",
"title": "FsCreateDirectoryResponse",
"type": "object"
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
}
},
"description": "Request metadata for an absolute path.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to inspect."
}
},
"required": [
"path"
],
"title": "FsGetMetadataParams",
"type": "object"
}

View File

@@ -0,0 +1,32 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Metadata returned by `fs/getMetadata`.",
"properties": {
"createdAtMs": {
"description": "File creation time in Unix milliseconds when available, otherwise `0`.",
"format": "int64",
"type": "integer"
},
"isDirectory": {
"description": "Whether the path currently resolves to a directory.",
"type": "boolean"
},
"isFile": {
"description": "Whether the path currently resolves to a regular file.",
"type": "boolean"
},
"modifiedAtMs": {
"description": "File modification time in Unix milliseconds when available, otherwise `0`.",
"format": "int64",
"type": "integer"
}
},
"required": [
"createdAtMs",
"isDirectory",
"isFile",
"modifiedAtMs"
],
"title": "FsGetMetadataResponse",
"type": "object"
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
}
},
"description": "List direct child names for a directory.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute directory path to read."
}
},
"required": [
"path"
],
"title": "FsReadDirectoryParams",
"type": "object"
}

View File

@@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"FsReadDirectoryEntry": {
"description": "A directory entry returned by `fs/readDirectory`.",
"properties": {
"fileName": {
"description": "Direct child entry name only, not an absolute or relative path.",
"type": "string"
},
"isDirectory": {
"description": "Whether this entry resolves to a directory.",
"type": "boolean"
},
"isFile": {
"description": "Whether this entry resolves to a regular file.",
"type": "boolean"
}
},
"required": [
"fileName",
"isDirectory",
"isFile"
],
"type": "object"
}
},
"description": "Directory entries returned by `fs/readDirectory`.",
"properties": {
"entries": {
"description": "Direct child entries in the requested directory.",
"items": {
"$ref": "#/definitions/FsReadDirectoryEntry"
},
"type": "array"
}
},
"required": [
"entries"
],
"title": "FsReadDirectoryResponse",
"type": "object"
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
}
},
"description": "Read a file from the host filesystem.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to read."
}
},
"required": [
"path"
],
"title": "FsReadFileParams",
"type": "object"
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Base64-encoded file contents returned by `fs/readFile`.",
"properties": {
"dataBase64": {
"description": "File contents encoded as base64.",
"type": "string"
}
},
"required": [
"dataBase64"
],
"title": "FsReadFileResponse",
"type": "object"
}

View File

@@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
}
},
"description": "Remove a file or directory tree from the host filesystem.",
"properties": {
"force": {
"description": "Whether missing paths should be ignored. Defaults to `true`.",
"type": [
"boolean",
"null"
]
},
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to remove."
},
"recursive": {
"description": "Whether directory removal should recurse. Defaults to `true`.",
"type": [
"boolean",
"null"
]
}
},
"required": [
"path"
],
"title": "FsRemoveParams",
"type": "object"
}

View File

@@ -0,0 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/remove`.",
"title": "FsRemoveResponse",
"type": "object"
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
}
},
"description": "Write a file on the host filesystem.",
"properties": {
"dataBase64": {
"description": "File contents encoded as base64.",
"type": "string"
},
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Absolute path to write."
}
},
"required": [
"dataBase64",
"path"
],
"title": "FsWriteFileParams",
"type": "object"
}

View File

@@ -0,0 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/writeFile`.",
"title": "FsWriteFileResponse",
"type": "object"
}

View File

@@ -41,6 +41,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -0,0 +1,84 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"GuardianApprovalReview": {
"description": "[UNSTABLE] Temporary guardian approval review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
"properties": {
"rationale": {
"type": [
"string",
"null"
]
},
"riskLevel": {
"anyOf": [
{
"$ref": "#/definitions/GuardianRiskLevel"
},
{
"type": "null"
}
]
},
"riskScore": {
"format": "uint8",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/GuardianApprovalReviewStatus"
}
},
"required": [
"status"
],
"type": "object"
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
"inProgress",
"approved",
"denied",
"aborted"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
"low",
"medium",
"high"
],
"type": "string"
}
},
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
"targetItemId": {
"type": "string"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": "string"
}
},
"required": [
"review",
"targetItemId",
"threadId",
"turnId"
],
"title": "ItemGuardianApprovalReviewCompletedNotification",
"type": "object"
}

View File

@@ -0,0 +1,84 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"GuardianApprovalReview": {
"description": "[UNSTABLE] Temporary guardian approval review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
"properties": {
"rationale": {
"type": [
"string",
"null"
]
},
"riskLevel": {
"anyOf": [
{
"$ref": "#/definitions/GuardianRiskLevel"
},
{
"type": "null"
}
]
},
"riskScore": {
"format": "uint8",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/GuardianApprovalReviewStatus"
}
},
"required": [
"status"
],
"type": "object"
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
"inProgress",
"approved",
"denied",
"aborted"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
"low",
"medium",
"high"
],
"type": "string"
}
},
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
"targetItemId": {
"type": "string"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": "string"
}
},
"required": [
"review",
"targetItemId",
"threadId",
"turnId"
],
"title": "ItemGuardianApprovalReviewStartedNotification",
"type": "object"
}

View File

@@ -41,6 +41,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -51,8 +51,12 @@
]
},
"defaultPrompt": {
"description": "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.",
"items": {
"type": "string"
},
"type": [
"string",
"array",
"null"
]
},

View File

@@ -125,8 +125,12 @@
]
},
"defaultPrompt": {
"description": "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.",
"items": {
"type": "string"
},
"type": [
"string",
"array",
"null"
]
},

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -1,6 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AskForApproval": {
"oneOf": [
{
@@ -79,6 +87,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",

View File

@@ -5,6 +5,14 @@
"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"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AskForApproval": {
"oneOf": [
{
@@ -209,6 +217,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",
@@ -1955,6 +1964,14 @@
"approvalPolicy": {
"$ref": "#/definitions/AskForApproval"
},
"approvalsReviewer": {
"allOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
}
],
"description": "Reviewer currently used for approval requests on this thread."
},
"cwd": {
"type": "string"
},
@@ -1993,6 +2010,7 @@
},
"required": [
"approvalPolicy",
"approvalsReviewer",
"cwd",
"model",
"modelProvider",

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -1,6 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AskForApproval": {
"oneOf": [
{
@@ -1002,6 +1010,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",

View File

@@ -5,6 +5,14 @@
"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"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AskForApproval": {
"oneOf": [
{
@@ -209,6 +217,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",
@@ -1955,6 +1964,14 @@
"approvalPolicy": {
"$ref": "#/definitions/AskForApproval"
},
"approvalsReviewer": {
"allOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
}
],
"description": "Reviewer currently used for approval requests on this thread."
},
"cwd": {
"type": "string"
},
@@ -1993,6 +2010,7 @@
},
"required": [
"approvalPolicy",
"approvalsReviewer",
"cwd",
"model",
"modelProvider",

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -1,6 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AskForApproval": {
"oneOf": [
{
@@ -53,6 +61,9 @@
},
"DynamicToolSpec": {
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -103,6 +114,17 @@
}
]
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this thread and subsequent turns."
},
"baseInstructions": {
"type": [
"string",

View File

@@ -5,6 +5,14 @@
"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"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AskForApproval": {
"oneOf": [
{
@@ -209,6 +217,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",
@@ -1955,6 +1964,14 @@
"approvalPolicy": {
"$ref": "#/definitions/AskForApproval"
},
"approvalsReviewer": {
"allOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
}
],
"description": "Reviewer currently used for approval requests on this thread."
},
"cwd": {
"type": "string"
},
@@ -1993,6 +2010,7 @@
},
"required": [
"approvalPolicy",
"approvalsReviewer",
"cwd",
"model",
"modelProvider",

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -5,6 +5,14 @@
"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"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AskForApproval": {
"oneOf": [
{
@@ -502,6 +510,17 @@
],
"description": "Override the approval policy for this turn and subsequent turns."
},
"approvalsReviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
],
"description": "Override where approval requests are routed for review on this turn and subsequent turns."
},
"cwd": {
"description": "Override the working directory for this turn and subsequent turns.",
"type": [

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -155,6 +155,7 @@
"enum": [
"pendingInit",
"running",
"interrupted",
"completed",
"errored",
"shutdown",

View File

@@ -20,6 +20,13 @@ import type { ExperimentalFeatureListParams } from "./v2/ExperimentalFeatureList
import type { ExternalAgentConfigDetectParams } from "./v2/ExternalAgentConfigDetectParams";
import type { ExternalAgentConfigImportParams } from "./v2/ExternalAgentConfigImportParams";
import type { FeedbackUploadParams } from "./v2/FeedbackUploadParams";
import type { FsCopyParams } from "./v2/FsCopyParams";
import type { FsCreateDirectoryParams } from "./v2/FsCreateDirectoryParams";
import type { FsGetMetadataParams } from "./v2/FsGetMetadataParams";
import type { FsReadDirectoryParams } from "./v2/FsReadDirectoryParams";
import type { FsReadFileParams } from "./v2/FsReadFileParams";
import type { FsRemoveParams } from "./v2/FsRemoveParams";
import type { FsWriteFileParams } from "./v2/FsWriteFileParams";
import type { GetAccountParams } from "./v2/GetAccountParams";
import type { ListMcpServerStatusParams } from "./v2/ListMcpServerStatusParams";
import type { LoginAccountParams } from "./v2/LoginAccountParams";
@@ -55,4 +62,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": "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/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, };

View File

@@ -18,6 +18,8 @@ import type { FileChangeOutputDeltaNotification } from "./v2/FileChangeOutputDel
import type { HookCompletedNotification } from "./v2/HookCompletedNotification";
import type { HookStartedNotification } from "./v2/HookStartedNotification";
import type { ItemCompletedNotification } from "./v2/ItemCompletedNotification";
import type { ItemGuardianApprovalReviewCompletedNotification } from "./v2/ItemGuardianApprovalReviewCompletedNotification";
import type { ItemGuardianApprovalReviewStartedNotification } from "./v2/ItemGuardianApprovalReviewStartedNotification";
import type { ItemStartedNotification } from "./v2/ItemStartedNotification";
import type { McpServerOauthLoginCompletedNotification } from "./v2/McpServerOauthLoginCompletedNotification";
import type { McpToolCallProgressNotification } from "./v2/McpToolCallProgressNotification";
@@ -52,4 +54,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW
/**
* Notification sent from the server to the client.
*/
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification };
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification };

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.
/**
* Configures who approval requests are routed to for review. Examples
* include sandbox escapes, blocked network access, MCP approval prompts, and
* ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully
* prompted subagent to gather relevant context and apply a risk-based
* decision framework before approving or denying the request.
*/
export type ApprovalsReviewer = "user" | "guardian_subagent";

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 CollabAgentStatus = "pendingInit" | "running" | "completed" | "errored" | "shutdown" | "notFound";
export type CollabAgentStatus = "pendingInit" | "running" | "interrupted" | "completed" | "errored" | "shutdown" | "notFound";

View File

@@ -9,10 +9,15 @@ import type { Verbosity } from "../Verbosity";
import type { WebSearchMode } from "../WebSearchMode";
import type { JsonValue } from "../serde_json/JsonValue";
import type { AnalyticsConfig } from "./AnalyticsConfig";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { ProfileV2 } from "./ProfileV2";
import type { SandboxMode } from "./SandboxMode";
import type { SandboxWorkspaceWrite } from "./SandboxWorkspaceWrite";
import type { ToolsV2 } from "./ToolsV2";
export type Config = {model: string | null, review_model: string | null, model_context_window: bigint | null, model_auto_compact_token_limit: bigint | null, model_provider: string | null, approval_policy: AskForApproval | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, service_tier: ServiceTier | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
export type Config = {model: string | null, review_model: string | null, model_context_window: bigint | null, model_auto_compact_token_limit: bigint | null, model_provider: string | null, approval_policy: AskForApproval | null, /**
* [UNSTABLE] Optional default for where approval requests are routed for
* review.
*/
approvals_reviewer: ApprovalsReviewer | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, service_tier: ServiceTier | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });

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 { JsonValue } from "../serde_json/JsonValue";
export type DynamicToolSpec = { name: string, description: string, inputSchema: JsonValue, };
export type DynamicToolSpec = { name: string, description: string, inputSchema: JsonValue, deferLoading?: boolean, };

View File

@@ -0,0 +1,21 @@
// 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 { AbsolutePathBuf } from "../AbsolutePathBuf";
/**
* Copy a file or directory tree on the host filesystem.
*/
export type FsCopyParams = {
/**
* Absolute source path.
*/
sourcePath: AbsolutePathBuf,
/**
* Absolute destination path.
*/
destinationPath: AbsolutePathBuf,
/**
* Required for directory copies; ignored for file copies.
*/
recursive?: boolean, };

View File

@@ -0,0 +1,8 @@
// 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.
/**
* Successful response for `fs/copy`.
*/
export type FsCopyResponse = Record<string, never>;

View File

@@ -0,0 +1,17 @@
// 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 { AbsolutePathBuf } from "../AbsolutePathBuf";
/**
* Create a directory on the host filesystem.
*/
export type FsCreateDirectoryParams = {
/**
* Absolute directory path to create.
*/
path: AbsolutePathBuf,
/**
* Whether parent directories should also be created. Defaults to `true`.
*/
recursive?: boolean | null, };

View File

@@ -0,0 +1,8 @@
// 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.
/**
* Successful response for `fs/createDirectory`.
*/
export type FsCreateDirectoryResponse = Record<string, never>;

View File

@@ -0,0 +1,13 @@
// 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 { AbsolutePathBuf } from "../AbsolutePathBuf";
/**
* Request metadata for an absolute path.
*/
export type FsGetMetadataParams = {
/**
* Absolute path to inspect.
*/
path: AbsolutePathBuf, };

View File

@@ -0,0 +1,24 @@
// 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.
/**
* Metadata returned by `fs/getMetadata`.
*/
export type FsGetMetadataResponse = {
/**
* Whether the path currently resolves to a directory.
*/
isDirectory: boolean,
/**
* Whether the path currently resolves to a regular file.
*/
isFile: boolean,
/**
* File creation time in Unix milliseconds when available, otherwise `0`.
*/
createdAtMs: number,
/**
* File modification time in Unix milliseconds when available, otherwise `0`.
*/
modifiedAtMs: number, };

View File

@@ -0,0 +1,20 @@
// 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.
/**
* A directory entry returned by `fs/readDirectory`.
*/
export type FsReadDirectoryEntry = {
/**
* Direct child entry name only, not an absolute or relative path.
*/
fileName: string,
/**
* Whether this entry resolves to a directory.
*/
isDirectory: boolean,
/**
* Whether this entry resolves to a regular file.
*/
isFile: boolean, };

View File

@@ -0,0 +1,13 @@
// 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 { AbsolutePathBuf } from "../AbsolutePathBuf";
/**
* List direct child names for a directory.
*/
export type FsReadDirectoryParams = {
/**
* Absolute directory path to read.
*/
path: AbsolutePathBuf, };

View File

@@ -0,0 +1,13 @@
// 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 { FsReadDirectoryEntry } from "./FsReadDirectoryEntry";
/**
* Directory entries returned by `fs/readDirectory`.
*/
export type FsReadDirectoryResponse = {
/**
* Direct child entries in the requested directory.
*/
entries: Array<FsReadDirectoryEntry>, };

View File

@@ -0,0 +1,13 @@
// 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 { AbsolutePathBuf } from "../AbsolutePathBuf";
/**
* Read a file from the host filesystem.
*/
export type FsReadFileParams = {
/**
* Absolute path to read.
*/
path: AbsolutePathBuf, };

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.
/**
* Base64-encoded file contents returned by `fs/readFile`.
*/
export type FsReadFileResponse = {
/**
* File contents encoded as base64.
*/
dataBase64: string, };

View File

@@ -0,0 +1,21 @@
// 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 { AbsolutePathBuf } from "../AbsolutePathBuf";
/**
* Remove a file or directory tree from the host filesystem.
*/
export type FsRemoveParams = {
/**
* Absolute path to remove.
*/
path: AbsolutePathBuf,
/**
* Whether directory removal should recurse. Defaults to `true`.
*/
recursive?: boolean | null,
/**
* Whether missing paths should be ignored. Defaults to `true`.
*/
force?: boolean | null, };

View File

@@ -0,0 +1,8 @@
// 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.
/**
* Successful response for `fs/remove`.
*/
export type FsRemoveResponse = Record<string, never>;

View File

@@ -0,0 +1,17 @@
// 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 { AbsolutePathBuf } from "../AbsolutePathBuf";
/**
* Write a file on the host filesystem.
*/
export type FsWriteFileParams = {
/**
* Absolute path to write.
*/
path: AbsolutePathBuf,
/**
* File contents encoded as base64.
*/
dataBase64: string, };

View File

@@ -0,0 +1,8 @@
// 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.
/**
* Successful response for `fs/writeFile`.
*/
export type FsWriteFileResponse = Record<string, never>;

View File

@@ -1,8 +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 { MacOsAutomationPermission } from "../MacOsAutomationPermission";
import type { MacOsContactsPermission } from "../MacOsContactsPermission";
import type { MacOsPreferencesPermission } from "../MacOsPreferencesPermission";
export type GrantedMacOsPermissions = { preferences?: MacOsPreferencesPermission, automations?: MacOsAutomationPermission, launchServices?: boolean, accessibility?: boolean, calendar?: boolean, reminders?: boolean, contacts?: MacOsContactsPermission, };

View File

@@ -3,6 +3,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions";
import type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions";
import type { GrantedMacOsPermissions } from "./GrantedMacOsPermissions";
export type GrantedPermissionProfile = { network?: AdditionalNetworkPermissions, fileSystem?: AdditionalFileSystemPermissions, macos?: GrantedMacOsPermissions, };
export type GrantedPermissionProfile = { network?: AdditionalNetworkPermissions, fileSystem?: AdditionalFileSystemPermissions, };

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.
import type { GuardianApprovalReviewStatus } from "./GuardianApprovalReviewStatus";
import type { GuardianRiskLevel } from "./GuardianRiskLevel";
/**
* [UNSTABLE] Temporary guardian approval review payload used by
* `item/autoApprovalReview/*` notifications. This shape is expected to change
* soon.
*/
export type GuardianApprovalReview = { status: GuardianApprovalReviewStatus, riskScore: number | null, riskLevel: GuardianRiskLevel | null, rationale: string | null, };

View File

@@ -0,0 +1,8 @@
// 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.
/**
* [UNSTABLE] Lifecycle state for a guardian approval review.
*/
export type GuardianApprovalReviewStatus = "inProgress" | "approved" | "denied" | "aborted";

View File

@@ -0,0 +1,8 @@
// 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.
/**
* [UNSTABLE] Risk level assigned by guardian approval review.
*/
export type GuardianRiskLevel = "low" | "medium" | "high";

View File

@@ -0,0 +1,15 @@
// 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 { JsonValue } from "../serde_json/JsonValue";
import type { GuardianApprovalReview } from "./GuardianApprovalReview";
/**
* [UNSTABLE] Temporary notification payload for guardian automatic approval
* review. This shape is expected to change soon.
*
* TODO(ccunningham): Attach guardian review state to the reviewed tool item's
* lifecycle instead of sending separate standalone review notifications so the
* app-server API can persist and replay review state via `thread/read`.
*/
export type ItemGuardianApprovalReviewCompletedNotification = { threadId: string, turnId: string, targetItemId: string, review: GuardianApprovalReview, action: JsonValue | null, };

View File

@@ -0,0 +1,15 @@
// 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 { JsonValue } from "../serde_json/JsonValue";
import type { GuardianApprovalReview } from "./GuardianApprovalReview";
/**
* [UNSTABLE] Temporary notification payload for guardian automatic approval
* review. This shape is expected to change soon.
*
* TODO(ccunningham): Attach guardian review state to the reviewed tool item's
* lifecycle instead of sending separate standalone review notifications so the
* app-server API can persist and replay review state via `thread/read`.
*/
export type ItemGuardianApprovalReviewStartedNotification = { threadId: string, turnId: string, targetItemId: string, review: GuardianApprovalReview, action: JsonValue | null, };

View File

@@ -1,6 +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 { AdditionalPermissionProfile } from "./AdditionalPermissionProfile";
import type { RequestPermissionProfile } from "./RequestPermissionProfile";
export type PermissionsRequestApprovalParams = { threadId: string, turnId: string, itemId: string, reason: string | null, permissions: AdditionalPermissionProfile, };
export type PermissionsRequestApprovalParams = { threadId: string, turnId: string, itemId: string, reason: string | null, permissions: RequestPermissionProfile, };

View File

@@ -3,4 +3,9 @@
// 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 PluginInterface = { displayName: string | null, shortDescription: string | null, longDescription: string | null, developerName: string | null, category: string | null, capabilities: Array<string>, websiteUrl: string | null, privacyPolicyUrl: string | null, termsOfServiceUrl: string | null, defaultPrompt: string | null, brandColor: string | null, composerIcon: AbsolutePathBuf | null, logo: AbsolutePathBuf | null, screenshots: Array<AbsolutePathBuf>, };
export type PluginInterface = { displayName: string | null, shortDescription: string | null, longDescription: string | null, developerName: string | null, category: string | null, capabilities: Array<string>, websiteUrl: string | null, privacyPolicyUrl: string | null, termsOfServiceUrl: string | null,
/**
* Starter prompts for the plugin. Capped at 3 entries with a maximum of
* 128 characters per entry.
*/
defaultPrompt: Array<string> | null, brandColor: string | null, composerIcon: AbsolutePathBuf | null, logo: AbsolutePathBuf | null, screenshots: Array<AbsolutePathBuf>, };

View File

@@ -7,7 +7,13 @@ import type { ServiceTier } from "../ServiceTier";
import type { Verbosity } from "../Verbosity";
import type { WebSearchMode } from "../WebSearchMode";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { ToolsV2 } from "./ToolsV2";
export type ProfileV2 = { model: string | null, model_provider: string | null, approval_policy: AskForApproval | null, service_tier: ServiceTier | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, chatgpt_base_url: string | null, } & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
export type ProfileV2 = {model: string | null, model_provider: string | null, approval_policy: AskForApproval | null, /**
* [UNSTABLE] Optional profile-level override for where approval requests
* are routed for review. If omitted, the enclosing config default is
* used.
*/
approvals_reviewer: ApprovalsReviewer | null, service_tier: ServiceTier | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, chatgpt_base_url: string | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });

View File

@@ -0,0 +1,7 @@
// 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 { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions";
import type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions";
export type RequestPermissionProfile = { network: AdditionalNetworkPermissions | null, fileSystem: AdditionalFileSystemPermissions | null, };

View File

@@ -3,6 +3,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxMode } from "./SandboxMode";
@@ -22,7 +23,11 @@ export type ThreadForkParams = {threadId: string, /**
path?: string | null, /**
* Configuration overrides for the forked thread, if any.
*/
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /**
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/

View File

@@ -3,8 +3,13 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadForkResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };
export type ThreadForkResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: string, approvalPolicy: AskForApproval,
/**
* Reviewer currently used for approval requests on this thread.
*/
approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };

View File

@@ -5,6 +5,7 @@ import type { Personality } from "../Personality";
import type { ResponseItem } from "../ResponseItem";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxMode } from "./SandboxMode";
@@ -31,7 +32,11 @@ history?: Array<ResponseItem> | null, /**
path?: string | null, /**
* Configuration overrides for the resumed thread, if any.
*/
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/

View File

@@ -3,8 +3,13 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadResumeResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };
export type ThreadResumeResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: string, approvalPolicy: AskForApproval,
/**
* Reviewer currently used for approval requests on this thread.
*/
approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };

View File

@@ -4,10 +4,15 @@
import type { Personality } from "../Personality";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxMode } from "./SandboxMode";
export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, /**
export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, /**
* If true, opt into emitting raw Responses API items on the event stream.
* This is for internal use only (e.g. Codex Cloud).
*/

View File

@@ -3,8 +3,13 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadStartResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };
export type ThreadStartResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: string, approvalPolicy: AskForApproval,
/**
* Reviewer currently used for approval requests on this thread.
*/
approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };

View File

@@ -7,6 +7,7 @@ import type { ReasoningEffort } from "../ReasoningEffort";
import type { ReasoningSummary } from "../ReasoningSummary";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { UserInput } from "./UserInput";
@@ -18,6 +19,10 @@ cwd?: string | null, /**
* Override the approval policy for this turn and subsequent turns.
*/
approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this turn and
* subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, /**
* Override the sandbox policy for this turn and subsequent turns.
*/
sandboxPolicy?: SandboxPolicy | null, /**

View File

@@ -19,6 +19,7 @@ export type { AppScreenshot } from "./AppScreenshot";
export type { AppSummary } from "./AppSummary";
export type { AppToolApproval } from "./AppToolApproval";
export type { AppToolsConfig } from "./AppToolsConfig";
export type { ApprovalsReviewer } from "./ApprovalsReviewer";
export type { AppsConfig } from "./AppsConfig";
export type { AppsDefaultConfig } from "./AppsDefaultConfig";
export type { AppsListParams } from "./AppsListParams";
@@ -95,12 +96,29 @@ export type { FileChangeOutputDeltaNotification } from "./FileChangeOutputDeltaN
export type { FileChangeRequestApprovalParams } from "./FileChangeRequestApprovalParams";
export type { FileChangeRequestApprovalResponse } from "./FileChangeRequestApprovalResponse";
export type { FileUpdateChange } from "./FileUpdateChange";
export type { FsCopyParams } from "./FsCopyParams";
export type { FsCopyResponse } from "./FsCopyResponse";
export type { FsCreateDirectoryParams } from "./FsCreateDirectoryParams";
export type { FsCreateDirectoryResponse } from "./FsCreateDirectoryResponse";
export type { FsGetMetadataParams } from "./FsGetMetadataParams";
export type { FsGetMetadataResponse } from "./FsGetMetadataResponse";
export type { FsReadDirectoryEntry } from "./FsReadDirectoryEntry";
export type { FsReadDirectoryParams } from "./FsReadDirectoryParams";
export type { FsReadDirectoryResponse } from "./FsReadDirectoryResponse";
export type { FsReadFileParams } from "./FsReadFileParams";
export type { FsReadFileResponse } from "./FsReadFileResponse";
export type { FsRemoveParams } from "./FsRemoveParams";
export type { FsRemoveResponse } from "./FsRemoveResponse";
export type { FsWriteFileParams } from "./FsWriteFileParams";
export type { FsWriteFileResponse } from "./FsWriteFileResponse";
export type { GetAccountParams } from "./GetAccountParams";
export type { GetAccountRateLimitsResponse } from "./GetAccountRateLimitsResponse";
export type { GetAccountResponse } from "./GetAccountResponse";
export type { GitInfo } from "./GitInfo";
export type { GrantedMacOsPermissions } from "./GrantedMacOsPermissions";
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";
@@ -113,6 +131,8 @@ export type { HookRunSummary } from "./HookRunSummary";
export type { HookScope } from "./HookScope";
export type { HookStartedNotification } from "./HookStartedNotification";
export type { ItemCompletedNotification } from "./ItemCompletedNotification";
export type { ItemGuardianApprovalReviewCompletedNotification } from "./ItemGuardianApprovalReviewCompletedNotification";
export type { ItemGuardianApprovalReviewStartedNotification } from "./ItemGuardianApprovalReviewStartedNotification";
export type { ItemStartedNotification } from "./ItemStartedNotification";
export type { ListMcpServerStatusParams } from "./ListMcpServerStatusParams";
export type { ListMcpServerStatusResponse } from "./ListMcpServerStatusResponse";
@@ -201,6 +221,7 @@ export type { ReasoningSummaryPartAddedNotification } from "./ReasoningSummaryPa
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";
export type { ReviewStartParams } from "./ReviewStartParams";

View File

@@ -182,7 +182,7 @@ 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_json_with_experimental(out_dir: &Path, experimental_api: bool) -> Result<()> {
@@ -1984,7 +1984,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),
);
}
}

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