# ide

> Unix shell + filesystem worker — exec with denylist/timeout/output caps and background jobs; fs::ls|stat|mkdir|rm|chmod|mv|grep|sed|read|write with host jail, denylist, size caps, and sandbox-target forwarding

| field | value |
|-------|-------|
| version | 0.12.9 |
| type | binary |
| license | Apache-2.0 |
| repo | https://github.com/iii-hq/workers |
| supported_targets | aarch64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii trigger compose::add worker=ide@0.12.9
```

## configuration

```yaml
- code:
    default_exclude_globs:
      - **/.git/**
      - **/node_modules/**
      - **/target/**
      - **/dist/**
      - **/.venv/**
      - **/__pycache__/**
    non_accessible_globs:
      - **/.env
      - **/.env.*
      - **/*.pem
      - **/*.key
      - **/secrets/**
  default_timeout_ms: 30000
  denylist_patterns:
    - rm\s+-rf\s+/
    - :\(\)\s*\{\s*:\|
    - (?i)^(?:(?:\S*/)?env(?:\s+\S+=\S*)*\s+|(?:\S*/)?(?:sudo|doas|nohup)\s+|(?:\S*/)?timeout(?:\s+\S+)?\s+)*(\S*/)?mkfs
    - (?i)^(?:(?:\S*/)?env(?:\s+\S+=\S*)*\s+|(?:\S*/)?(?:sudo|doas|nohup)\s+|(?:\S*/)?timeout(?:\s+\S+)?\s+)*(\S*/)?dd\s+if=
    - (?i)^(?:(?:\S*/)?env(?:\s+\S+=\S*)*\s+|(?:\S*/)?(?:sudo|doas|nohup)\s+|(?:\S*/)?timeout(?:\s+\S+)?\s+)*(\S*/)?shutdown\b
    - (?i)^(?:(?:\S*/)?env(?:\s+\S+=\S*)*\s+|(?:\S*/)?(?:sudo|doas|nohup)\s+|(?:\S*/)?timeout(?:\s+\S+)?\s+)*(\S*/)?reboot\b
    - /etc/shadow
  env:
    allow:
      - PATH
      - HOME
      - LANG
      - LC_ALL
      - TERM
    inherit: true
  fs:
    allow_unjailed: true
    denylist_paths:
      - /etc/passwd
      - /etc/shadow
    host_roots:

    max_read_bytes: 16777216
    max_write_bytes: 16777216
  job_retention_secs: 3600
  max_bg_timeout_ms: 0
  max_concurrent_jobs: 16
  max_output_bytes: 1048576
  max_timeout_ms: 120000
  sandbox:
    enabled: true
  turns:
    data_dir: data/shell/turns
    max_blob_bytes: 268435456
  working_dir: null
```

## readme

# ide

Run Unix commands, background jobs, and structured filesystem operations from the iii engine, on the host or forwarded into a sandbox microVM.

## Notice: renamed from `shell`

This worker used to be called `shell`. Only its identity changed, so nothing that talks to it needs to be rewritten:

- worker name, binary, Cargo package and registry entry: `shell` → `ide` (`iii trigger compose::add worker=ide`, `npx skills add iii-hq/workers --skill ide`)
- source directory: `shell/` → `ide/`; dependents pin `ide` in their `iii.worker.yaml`
- unchanged on purpose: the `shell::*` function ids (`shell::exec`, `shell::fs::*`, `shell::pty::*`, …), `coder::*`, the `shell::changed` trigger type, the `shell` configuration entry, the `data/shell/` data directory and the `#/ext/shell` console page

## Install

```sh
iii trigger compose::add worker=ide
```

Sandbox-targeted execution and `shell::fs::*` forwarding need the `iii-sandbox` worker; `iii trigger compose::add worker=ide` does not pull it in. To surface `shell::*` to LLM agents, pair with `iii-directory`:

```sh
iii trigger compose::add worker=iii-sandbox worker=iii-directory
```

## Skills

Install the `shell` agent skill for Claude Code, Cursor, and 30+ other agents:

```bash
npx skills add iii-hq/workers --skill ide
```

Browse or install every worker skill at once:

```bash
npx skills add iii-hq/workers --list
npx skills add iii-hq/workers --all
```

## Running

The binary needs no required environment variables — it boots against a local
engine with pure defaults. The full operator surface:

| Knob | Default | What it does |
|---|---|---|
| `--url <URL>` / `III_URL` env var | `ws://127.0.0.1:49134` | WebSocket URL of the iii engine. The CLI flag wins over the env var. |
| `--config <path>` | `./config.yaml` | Seed config sent as `initial_value` on FIRST registration only; the stored value wins afterwards (see [Configure](#configure)). |
| `--version` | — | Print the worker version (also registered with the engine as worker metadata). |
| `RUST_LOG` env var | `info` | Log filter (tracing `EnvFilter` syntax, e.g. `RUST_LOG=ide=debug,info`). |

If the engine is unreachable at boot, a pre-connect probe (run detached, so it
never delays startup) logs one ERROR ("engine unreachable at <host>:<port> —
is the iii engine running? Set --url or the III_URL env var...", logging the
resolved host/port rather than the raw URL) and the worker keeps retrying in
the background every 2s — it never exits, so supervised deployments recover
as soon as the engine is up.

A `--config` seed file that exists but fails to parse (for example, one still
carrying 0.6.x keys) aborts boot rather than silently falling back to the
permissive built-in default — see [Upgrading to 0.7.0](#upgrading-to-070). A
genuinely missing file still falls through gracefully to the stored value or
the built-in zero-config default.

## Configure

Settings are managed through the central `configuration` worker. On boot, the shell worker registers its schema (id `shell`) and fetches the live value over RPC — that live value is the authoritative config, not a local file. The optional `--config <path>` flag (default `./config.yaml`) provides the `initial_value` sent on first registration only; once registered, subsequent boots pull the stored value from the `configuration` worker. When the config changes, the worker hot-reloads the security policy and fs backend automatically (see [Hot-reload](#hot-reload)).

The worker refuses to start unless `fs.host_roots` is set, or `fs.allow_unjailed: true` is explicitly opted in, because an unset root exposes the whole host filesystem behind only the advisory denylist.

By default, `mkdir`/`chmod`/`write` reject modes carrying setuid/setgid/sticky bits (the top octal digit, e.g. `4755`) with `S210`, since they are a privilege-escalation primitive when the worker runs as root. Set `fs.allow_special_bits: true` only if your workload genuinely needs them.

```yaml
max_timeout_ms: 120000       # foreground exec hard cap; per-call timeout_ms is clamped to this
max_bg_timeout_ms: 0         # host bg job hard cap in ms; 0 = unbounded (foreground uses max_timeout_ms)
default_timeout_ms: 30000    # applied when the caller omits timeout_ms (code default 10000)
max_output_bytes: 1048576    # 1 MiB; stdout/stderr past this set *_truncated
env:
  inherit: true              # forward the worker's env to children; per-call dangerous keys still blocked
  allow: [PATH, HOME, LANG, LC_ALL, TERM]  # forwarded when inherit is false; has no effect on per-call `env` (deny-only — dangerous keys never settable)

# Command policy is deny-only: allow/ask policy lives in the approval-gate.
# denylist_patterns are advisory regex over argv.join(" "), a tripwire for
# catastrophic mistakes only, NOT a security boundary. Command-shaped
# patterns are anchored to argv[0] (tolerating a sudo/doas/nohup/env/timeout
# wrapper) so `grep -rn shutdown src/` is not rejected but `sudo shutdown -h
# now` still is; argument-shaped ones (rm -rf /) stay unanchored. See the
# shipped config.yaml for the full patterns.
denylist_patterns:
  - "rm\\s+-rf\\s+/"
  - "^(\\S*/)?mkfs"
  - "^(\\S*/)?dd\\s+if="

max_concurrent_jobs: 16      # exec_bg past this is rejected
job_retention_secs: 3600     # finished jobs pruned after this

fs:
  host_roots: []             # jail roots for shell::fs::*/cwd; empty = unjailed (the shipped default); first entry = primary
  allow_unjailed: true       # explicit opt-in to running with no jail root (required whenever host_roots is empty)
  max_read_bytes: 16777216   # 0 = unlimited (reads stream; cap bounds caller cost)
  max_write_bytes: 16777216  # 0 = unlimited
  denylist_paths: [/etc/passwd, /etc/shadow]  # primary protection while unjailed; defense in depth once you set host_roots
  allow_special_bits: false  # permit setuid/setgid/sticky bits in mode (default false)

sandbox:
  enabled: true              # false -> every target: sandbox call returns S210
```

### Zero-config default

With no `--config` file and no value stored in the `configuration` worker, the worker seeds a built-in default on first registration — so it boots with nothing configured (database-style). That built-in default is the shipped [`config.yaml`](config.yaml): unjailed (`fs.allow_unjailed: true`, empty `host_roots`), env forwarded, deny-only exec with a catastrophic-only denylist (kept in sync by a unit test). Unjailed means `shell::fs::*` and `shell::exec`'s per-call `cwd` operate against the real filesystem, confined only by `fs.denylist_paths` — matching `shell::exec` itself, which has never been confinement-based. `coder::*` is the one exception: it falls back to its own default roots (engine workspace cwd + `/tmp`) whenever `fs.host_roots` is empty, so it stays reasonably scoped even under the zero-config default. Set `fs.host_roots` if you want `shell::fs::*`/`cwd` jailed too. If the stored value is later nulled, the worker does not silently fall back to this seed: boot fails closed and a hot-reload keeps the last-good config. A config that is *present* but leaves `fs.host_roots` unset (without `fs.allow_unjailed: true`) also fails closed — the failure mode only disappears once you explicitly opt in, one way or another.

Host `shell::exec` is not a security boundary: any interpreter (`sh`, `node`, `python3`) can construct a denylisted token at runtime and bypass the regex. Run untrusted input with `target: { kind: "sandbox", sandbox_id }`, which forwards through the `iii-sandbox` microVM. The denylist still applies on top of either backend.

### Per-call `cwd`, `env`, and `stdin` (host target)

`shell::exec` and `shell::exec_bg` each accept optional fields so an agent can scope a single command to a directory, set specific env values, and feed it standard input without wrapping everything in `sh -lc` (which would blur what the argv actually was):

- **`cwd`** (string): the working directory for this one call. It is confined to the fs jail **exactly** like `shell::fs::*` paths — jail-relative when `fs.host_roots` is set (else absolute), canonicalized, and required to resolve inside a jail root and miss `denylist_paths`. A `cwd` that escapes the jail returns `S215`; one that doesn't exist or isn't a directory returns `S211`/`S210`. Omit it to use the configured `working_dir` (unchanged default).
- **`env`** (object of string→string): per-call environment values. Deny-only, like the exec/fs command policy: a key may be set to **any** value **except** an exec-hijacking key — `PATH`, `IFS`, `HOME`, every `LD_*`/`DYLD_*` variant, and other loader/lookup-path and interpreter startup-file keys (`GCONV_PATH`, `BASH_ENV`, `ENV`, `PYTHONSTARTUP`, `PERL5OPT`, `RUBYOPT`, `NODE_OPTIONS`, …), which are rejected unconditionally. `env.allow` plays **no role** in this gate at all — that list's only job is which vars get *forwarded* from the worker's own environment when `env.inherit` is false (see [Configure](#configure)); it has nothing to do with what a per-call override may set. Supplying a dangerous key rejects the **whole call** with `S210` (the offending key is named); the env is never silently dropped. A per-call value overrides the value that would otherwise be forwarded for that key. So an agent can do `NODE_ENV=test` freely, but can never inject `PATH`, `HOME`, or `LD_PRELOAD`.
- **`stdin`** (string): written to the program's standard input, which is then closed (EOF). Use it to feed `tee`, `patch`, `cat`, or any stdin filter instead of a shell heredoc. Omit it and stdin is `/dev/null`.

All three fields are **host-only**. The `sandbox::exec` protocol does not forward `cwd`/`env`/`stdin`, so a sandbox-targeted call that supplies any of them is rejected with `S210` rather than silently ignoring it. Omit them and behaviour is identical to prior versions.

## Quick start

```sh
npm install iii-sdk
```

```ts
import { registerWorker } from 'iii-sdk'

const iii = registerWorker(process.env.III_URL ?? 'ws://127.0.0.1:49134')

const result = await iii.trigger({
  function_id: 'shell::exec',
  payload: { command: 'echo', args: ['hello'] },
})

console.log(result)
```

The example runs on the host. The same payload retargets at a microVM with `target: { kind: 'sandbox', sandbox_id: '<uuid>' }`. The other entry points are `shell::exec_bg`, `shell::status`, `shell::kill`, `shell::list`, `shell::config-status`, plus the `shell::fs::*` family (`ls`, `stat`, `read`, `write`, `grep`, `sed`, `mkdir`, `rm`, `chmod`, `mv`).

## Functions

| Function | Purpose |
|---|---|
| `shell::exec` | Run a command in the foreground; returns stdout, stderr, exit code, and timing. Blocks until exit or timeout. Accepts optional host-only `cwd` (confined to `fs.host_roots` when set, else absolute), `env` (deny-only, gated by a dangerous-key denylist), and `stdin` (string piped to the program's stdin, then EOF) — see [Per-call `cwd`, `env`, and `stdin`](#per-call-cwd-env-and-stdin-host-target). |
| `shell::exec_bg` | Spawn a command as a background job; returns `{ job_id, argv }` immediately. Host-targeted jobs run until they exit or `shell::kill` terminates them — unbounded by default, and capped only when the operator sets a positive `max_bg_timeout_ms` (default `0` = unbounded), after which a runaway job is killed and its status becomes `killed`. Sandbox jobs honor `timeout_ms`. Same optional host-only `cwd`/`env`/`stdin` as `shell::exec`. |
| `shell::status` | Fetch one job's full record: state, exit code, and captured stdout/stderr. A missing id — one that never existed or aged out past `job_retention_secs` — returns an `S211` ("no such job") error. |
| `shell::list` | Enumerate current jobs as lightweight summaries; argv, stdout, and stderr are redacted. |
| `shell::kill` | Terminate a running background job by `job_id`. Sandbox jobs cannot be hard-killed: the record flips to `killed` but the in-VM process runs until its `timeout_ms` (or `sandbox::stop`). |
| `shell::config-status` | *(operator/automation only — not agent-callable)* Report the last hot-reload outcome: `last_outcome` (`applied`/`rejected`), `last_error`, and `rejected_reloads` (count since boot). A rejected outcome or non-zero count means a stored config was refused and shell is enforcing an older policy than the central store. Takes no arguments. |
| `shell::fs::ls` | List a directory's entries with structured metadata. |
| `shell::fs::stat` | Read one path's metadata (size, mode, symlink flag). |
| `shell::fs::mkdir` | Create a directory, optionally with missing parents. Returns `{ created, path, already_existed }`. |
| `shell::fs::rm` | Remove a file or directory, optionally recursive. Returns `{ removed, path, was_present }`. |
| `shell::fs::chmod` | Change a path's mode, and optionally its uid/gid. Returns `{ entries_changed, path, recursive }`. (**Breaking**: field renamed from `updated` to `entries_changed`.) |
| `shell::fs::mv` | Rename or move one path within the jail. Returns `{ moved, src, dst, overwrote }`. |
| `shell::fs::grep` | Recursive regex search across a tree; returns structured matches. Keys are singular `include_glob`/`exclude_glob`; the case flag is `ignore_case`. |
| `shell::fs::sed` | Regex find-and-replace across one file or many. |
| `shell::fs::write` | Write a file. Simplest form passes inline string `content` (host target only): `{ path, content: "file text" }`, with `mode` (octal, default `"0644"`) and `parents: true` to create parents. A `ContentRef` object in `content` instead streams large/staged payloads through an SDK channel (temp file + atomic rename) and is **required** for sandbox targets — an inline string on a sandbox target returns `S210`. Batch form: pass `files: [{ path, content, mode?, parents? }, ...]` (host, inline per file) to write several files in one call; the response then carries per-file `files: [{ path, bytes_written }]`. A single-file write leaves `files` empty and returns `{ bytes_written, path }`. Supplying both single `path`/`content` and `files` returns `S210`. |
| `shell::fs::read` | Stream a file's bytes out through an SDK channel. For an inline read on the web surface, use the `harness::fs::read_inline` wrapper instead. |

Every `shell::fs::*` call accepts the same optional `target` as `exec`, so host and sandbox share one wire shape.

## Code surface (`coder::*`)

The shell worker also serves the **`coder::*`** code-file functions (the former
standalone `coder` worker, folded in). They are agent-ergonomic, structured
file operations that share `shell::fs::*`'s jail **when `fs.host_roots` is
set**. When it's empty (the shipped default — see
[Zero-config default](#zero-config-default)), the two surfaces diverge:
`shell::fs::*` becomes fully unjailed, but `coder::*` falls back to its own
default roots (engine workspace cwd + `/tmp`) instead — it never runs
fully unjailed, regardless of `fs.allow_unjailed`.

| Function | Purpose |
|---|---|
| `coder::info` | Discover the jail: roots, caps, response budgets, exclude/non-accessible globs. Call first. |
| `coder::read-file` | Windowed reads (`line_from`/`line_to`), `stat` probe, byte-budgeted full reads, and multi-file batch reads. |
| `coder::search` | Literal/regex content + path search with context lines, bounded by match/byte budgets. `respect_gitignore: true` skips what `.gitignore` hides; `fuzzy_paths: true` ranks path matches quick-open style, best first. |
| `coder::list-folder` | Paginated single-folder listing. |
| `coder::tree` | Recursive depth- and per-folder-bounded directory snapshot. |
| `coder::create-file` / `coder::update-file` / `coder::delete-file` / `coder::move` | Batched create, line/regex edits, delete, and atomic rename/move. |

Roots come from `fs.host_roots` (with the cwd+`/tmp` fallback noted above);
protection globs come from `code.non_accessible_globs` in the shipped
`config.yaml`'s `code:` block — the **same** list `shell::fs::*` enforces.
`coder::*` returns its own `C2xx` codes. Since 0.10.0, shared `C2xx` and
`S2xx` numbers mean the same failure class; coder-only failures are called
out explicitly below:

| Code | Meaning | fs twin |
|---|---|---|
| `C210` | Malformed input: bad payload, illegal line numbers, overlapping ops. | `S210` |
| `C211` | Path not found, permission denied, matched `non_accessible_globs`, or under `fs.denylist_paths` — deliberately ONE code and wording for all four, so a caller can't probe for a denied path's existence. | `S211` |
| `C213` | `create-file`/`move` saw an existing target and `overwrite=false`. | `S213` |
| `C215` | Path escapes every allowed root, lexically or through a symlink (jailed mode only). | `S215` |
| `C216` | Underlying I/O error. | `S216` |
| `C218` | File exceeds `max_read_bytes`/`max_write_bytes`. | `S218` |
| `C220` | Path resolves inside a configured root but outside the per-call `scope_root` the session is scoped to. | `S220` |
| `C221` | Optimistic whole-file save conflict: the file no longer matches `expected_revision`; no bytes were written. | n/a |

No separate install: `iii trigger compose::add worker=ide` brings the whole surface.

## Terminal sessions (`shell::pty::*`)

The console's terminal runs on this worker: `shell::pty::open` starts a
persistent host PTY, `write` sends base64 keystrokes, `resize` follows the
pane, `attach`/`detach` survive a reload (buffered output replays from a
sequence number, out of a 2 MiB ring buffer), and `close` terminates the
session. These are console plumbing — flagged `internal` and `trace_hidden`,
so their spans stay out of the default trace view; one span per keystroke
buries the work worth reading.

A session runs the user's login shell by default, or the program a caller
names:

| Field on `open` | Meaning |
|---|---|
| `program` | Program to run instead of the login shell, resolved on PATH. A caller that can open a login shell can already run any program by typing it, so this is reach, not privilege — it is what lets a session BE one program, with no shell around it. |
| `args` | argv for `program`; ignored without it. |
| `env` | Extra environment for the session. Deny-only exactly like `shell::exec`'s per-call `env`: an exec-hijacking key (`PATH`, `LD_*`, `DYLD_*`, `BASH_ENV`, ...) refuses the whole call, and so does a key that is not a variable name (`[A-Za-z_][A-Za-z0-9_]*`) — a key like `PATH=/tmp/evil` would otherwise carry its own assignment past the `PATH` rule. |

`cwd` resolves through the same jail as `shell::fs::*`, so what it can reach is
whatever that config says:

- `fs.host_roots` set: a session can only start inside a configured root.
- `fs.allow_unjailed: true` with `fs.host_roots` empty (**the shipped
  default**): there is no root to be inside, and `cwd` reaches the real
  filesystem, held back only by `fs.denylist_paths`.

Either way this bounds where a session STARTS. A login shell can then `cd`
anywhere its user can, which is the point of a terminal.

Output goes to a browser-registered console handler and nowhere else:
`iii::<worker>-ui::pty-output::console-<browser-id>`. The `<worker>-ui`
segment is not pinned to this worker's own page — a worker that runs its own
program in a session serves its own console page, and therefore its own
handler — but the shape is enforced, so a session can never be pointed at an
arbitrary function on the bus.

Each pane carries a font-size stepper (8–40 px, 14 by default; Ctrl or ⌘ +
scroll does the same). The size is a browser preference rather than a worker
setting — the same person reading the same engine from a laptop and from a
wall display wants two different answers — so it is stored per browser and
read by every terminal the console renders, this worker's panes and the
agent-CLI pages alike. A change refits the pane, which resizes the PTY
through the ordinary `shell::pty::resize` path.

### Taking a session back (`shell::pty::adopt`)

The reconnect token is what proves a caller opened a session, and a browser
that loses its storage loses the token while the program keeps running — an
agent still working in a workspace nobody can reach. `shell::pty::adopt` is
the way back, under two rules that keep it from being a way in:

- **The session must be unattached.** A terminal someone is watching is never
  taken from them; only one nobody holds.
- **The new output handler must name the same console page as the old one.**
  The browser id may differ — that is the point — but `iii::claude-ui::…`
  cannot adopt a session whose output went to `iii::pi-ui::…`.

Credentials rotate on adoption, so whatever the previous owner still held
stops working, and an in-flight retry of the old attach cannot resurrect it.
`shell::pty::sessions` is how a page finds its orphan: every session reports
`ui`, the console page it belongs to (a page, never a browser), whether or not
anyone is attached.

`shell::pty::sessions` lists live sessions with their program, cwd, status,
last sequence number, replayable frames and bytes, and current output target.
No credentials: it exists to separate a terminal that shows nothing because
no output arrived from one whose frames the browser dropped.

## Two surfaces, one contract

`shell::fs::*` and `coder::*` are two views of the same filesystem, served by
this one worker under ONE policy: the same jail (`fs.host_roots`), the same
unjailed opt-in (`fs.allow_unjailed`), the same protected globs
(`code.non_accessible_globs`), the same operator denylist
(`fs.denylist_paths`), and — since 0.10.0 — the same error-code semantics
(equal digits, equal meaning) and the same existence redaction (a denied path
reads exactly like a missing one on both surfaces).

They differ in ergonomics, and each operation has a twin:

| Task | `coder::*` (agent-ergonomic) | `shell::fs::*` (byte-level) |
|---|---|---|
| read | `coder::read-file` — inline text, windowed, batched | `shell::fs::read` — streams bytes via channel |
| create | `coder::create-file` — batched inline text | `shell::fs::write` — inline or streamed, modes |
| edit | `coder::update-file` — line ops + regex, post-apply echoes | `shell::fs::sed` — regex replace across files |
| delete | `coder::delete-file` — batched, per-entry errors | `shell::fs::rm` — single path |
| list | `coder::list-folder` / `coder::tree` — paginated, noise-filtered | `shell::fs::ls` — single directory |
| move | `coder::move` — batched, cross-root copy+delete | `shell::fs::mv` — single path |
| search | `coder::search` — budgeted, context lines | `shell::fs::grep` — raw matches |
| introspect | `coder::info` — mode, roots, caps, globs | `shell::fs::stat` — one path's metadata |

Conventions (hold these when adding functions to either surface):

- **Batching**: `coder::*` operations are batched with per-entry error
  isolation. `shell::fs::*` point operations are single-path (`write` and
  `sed` are the historical exceptions).
- **Naming**: `coder::*` uses verb-noun kebab (`read-file`); `shell::fs::*`
  uses the unix tool name (`read`, `ls`, `mv`). Follow the surface you are
  extending.
- **Errors**: one `{ code, message }` envelope; C/S codes with equal digits
  mean the same failure class (see [Errors](#errors)). Redaction: never let
  a denied path read differently from a missing one.
- **Discovery**: `coder::info` is the agent-facing contract report.
  `shell::config-status` (reload health), `shell::workspace::*` (console
  working-directory picker and the explorer's chunked byte reads) and
  `shell::turns::*` (per-session change history and its `revert`) are
  operator/console control plane, not agent tools.
- **Sandbox**: `shell::fs::*`/`shell::exec` accept `target: sandbox`;
  `coder::*` is host-only.

## Live change feed (`shell::changed`)

The worker registers a custom **trigger type** backed by a system-level
directory watch (FSEvents on macOS, inotify on Linux, via the `notify`
crate). A subscriber names the directory in its binding config:

```json
{ "type": "shell::changed", "config": { "path": "/some/dir" } }
```

Paths the root ignores — git-ignored inside a repository; `data/`,
`config/`, `.iii/`, `node_modules/` and the root `.gitignore` outside one —
are **not delivered** unless the binding also sets `include_ignored: true`.
A watch on a project root would otherwise fire on the engine's own
`data/observability` and `data/session-manager` writes, i.e. on the
subscriber's own transcript. The workspace UI opts in to count them.

Every fire carries the binding's registered `metadata` and `namespace`
back to the bound function, the same way the engine's own trigger types
do — the harness relies on it to match a wake to its `__binding`.

Each registration starts one recursive watcher; unregistering (or the
console GC'ing a closed tab's binding) tears it down. `config.path` goes
through the same path policy as every `coder::*` call — jail containment
(`fs.host_roots`), the operator denylist, canonicalization — and must be
a directory: watching a tree is a read of every filename under it, so a
path you can't read is a path you can't watch.

The payload is lean — `{ path, kind, root, dir }` with `kind` ∈
`created | modified | deleted`, `path` relative to the watched `root`,
and `dir` true when the path is a directory (a subscriber that opens
files must skip those; deleted paths can't be probed and report false).
A subscriber that wants content asks `coder::read-file`; one that wants
the diff asks git.

Semantics:

- **Catches every writer** — an agent calling `coder::*`, a
  `shell::exec` command's side effects, or an editor outside the engine
  entirely. Nothing is coupled to the harness and no other worker is
  involved.
- **Coalesced**: raw OS events storm, so each watcher batches per path
  in a 200 ms window; kinds merge toward the visible outcome (a create
  plus the write that fills it is `created`, a deletion supersedes what
  came before, a create-after-delete is `created`). `.git` internals
  are filtered — the visible outcome arrives as worktree events of its
  own.
- **Best-effort fan-out**: events fire with no reply expected; a slow
  or absent subscriber never delays anything.

The console explorer page binds this on its browsed root: the tree and
git panels refresh live, a clean active buffer follows the disk (a dirty
one keeps the user's edits), and the last written file follows the
writer into view as a diff — git baseline in a repo, empty baseline for
created files, the page's last-seen content otherwise. Hidden segments,
noise directories (`target`, `node_modules`, `dist`, `build`, …) at any
depth, and build/temp artifact extensions never steal the view.

## Hot-reload

When the `configuration` worker pushes an updated config, the shell worker swaps in the new security policy and fs backend atomically. A few things to know:

- Each call executes against one consistent runtime snapshot; there is no mid-call config change.
- Already-running background jobs are **not** retroactively re-checked when the policy tightens — they continue under the policy that was active when they were spawned.
- A reload that widens the jail (for example, clearing `host_roots`) succeeds but is logged as a privilege change.
- If the incoming config is invalid or unsafe, the worker keeps the last-good runtime and logs an error. The rejection is also surfaced through `shell::config-status` (a `rejected` outcome with a non-zero `rejected_reloads` count), so the divergence between the central store and the policy shell is actually enforcing is detectable instead of silent. Rejections are kept last-good and not retried (re-fetching returns the same bad value), so they will not retry-storm.
- At boot the reconcile against the configuration worker is **fail-closed**: the worker refuses to start (and exposes no functions) if it cannot confirm the authoritative config, so it never serves a possibly stale security policy.

## Errors

Returned error bodies carry a stable `code` field. Denylist rejections come back as a plain message (`command matches denylist: <pattern>`) rather than an S-code.

| Code | Meaning |
|---|---|
| `S200` | In-VM execution failure on a sandbox target. |
| `S210` | Invalid request: non-absolute path, empty command or pattern, bad octal mode, malformed payload, `sandbox.enabled: false` on a sandbox-targeted call, a `cwd` that is not a directory, an `env` key in the dangerous-key denylist, `cwd`/`env`/`stdin` supplied on a sandbox target (host-only), an inline string `content` on a sandbox-targeted `shell::fs::write`, or both single `path`/`content` and `files` on `shell::fs::write`. |
| `S211` | Path not found, permission denied, matched the protected globs, or under `fs.denylist_paths` — ONE code and wording for all four (redaction: a denied path reads exactly like a missing one). Includes a `cwd` that does not exist. |
| `S212` | Wrong file type for the operation (for example, a file where a directory was expected). |
| `S213` | Path already exists. |
| `S214` | Directory not empty (non-recursive `rm`). |
| `S215` | Path (or a per-call `cwd`) escapes the `fs.host_roots` jail — exclusively a confinement escape; denylist and permission-denied fold into `S211`. |
| `S216` | Generic shell-internal failure: host spawn error, channel error, or a bad engine response. |
| `S217` | Invalid regex passed to `grep`/`sed`. |
| `S218` | `fs.max_read_bytes` / `fs.max_write_bytes` cap exceeded. |
| `S300` | Sandbox VM boot failed (needs a virtualization host: Apple Silicon or `/dev/kvm`). |

Sandbox-forwarded `fs::*`/`exec` errors can also surface engine codes verbatim instead of collapsing to `S216`: `S001`–`S004` (sandbox lifecycle), `S100`–`S102` (image/VM/resource), `S300`, and `S400`. Branch on the specific code where relevant; only an unrecognized engine code falls back to `S216`.

## Upgrading to 0.10.0

- **BREAKING: three `coder::*` error codes renumbered** to align with the
  `S2xx` scheme (equal digits now mean the same failure class on both
  surfaces): already-exists `C217` → `C213`; too-large `C213` → `C218`;
  outside-session `C218` → `C220`. Consumers branching on the old numbers
  must update; the approval-gate's jail-scope allowlist ships the matching
  change in the same release wave.
- **BREAKING: `shell::fs::*` existence redaction.** Permission-denied,
  protected-glob (`code.non_accessible_globs`), and `fs.denylist_paths`
  rejections now return `S211` with the same "not found or not accessible"
  wording as a missing path, instead of `S215`. `S215` is now exclusively a
  jail-confinement escape. Callers must not distinguish "missing" from
  "denied" — that distinction was an existence-probing side channel.
- **`coder::*` is permissive when unjailed.** With the explicit opt-in
  (`fs.allow_unjailed: true`, empty `fs.host_roots`) the coder surface now
  follows the same deny-only policy as `shell::fs::*`: absolute paths
  anywhere on the host, the cwd + `/tmp` fallback roots demoted to
  relative-path anchors, and the harness-selected working directory trusted
  as the anchor. `fs.denylist_paths` now applies to `coder::*` in every
  mode. Jailed deployments (explicit `fs.host_roots`) are unchanged.
  `coder::info` reports the effective `mode`.

## Upgrading to 0.8.0

- **BREAKING: `allowlist` removed.** The command allowlist is gone — the
  shell is deny-only now (see [Configure](#configure)); allow/ask policy
  lives entirely in the approval-gate. Any stored config still carrying the
  key, including the inert `allowlist: []` written by older seeds, fails
  closed at boot with a migration hint. Rewrite the stored value to drop it:

  ```ts
  import { registerWorker } from 'iii-sdk'
  const iii = registerWorker(process.env.III_URL ?? 'ws://127.0.0.1:49134')
  const { value } = await iii.trigger({ function_id: 'configuration::get', payload: { id: 'shell' } })
  delete value.allowlist
  await iii.trigger({ function_id: 'configuration::set', payload: { id: 'shell', value } })
  ```

  If you seed from a `--config` file instead, just delete the `allowlist: []` line.

- **BREAKING (fresh installs only): the fs jail is unjailed by default.** The
  shipped `config.yaml` now sets `fs.allow_unjailed: true` with empty
  `fs.host_roots` — `shell::fs::*` and `shell::exec`'s per-call `cwd` operate
  against the real filesystem by default (see
  [Zero-config default](#zero-config-default)). This only affects a
  genuinely fresh install or a config that gets nulled — an existing
  deployment with an explicit `fs.host_roots` already in its stored config is
  unaffected; the stored value always wins over the seed. To keep the old
  jailed-to-`/tmp` behavior, set it explicitly:

  ```yaml
  fs:
    host_roots: [/tmp]
  ```

- **The per-call `env` override is deny-only.** `env.allow` no longer gates
  which keys `shell::exec`/`exec_bg`'s per-call `env` may set — only the
  hardcoded dangerous-key list does (see [Per-call `cwd`, `env`, and
  `stdin`](#per-call-cwd-env-and-stdin-host-target)). This is a pure
  widening: nothing that worked before now fails, and no stored config needs
  a rewrite. `env.allow` keeps its other job (forwarding when `env.inherit`
  is false) unchanged.

## Upgrading to 0.7.0

- **BREAKING: env config keys renamed and nested.** The top-level `inherit_env`
  and `allowed_env` keys are replaced by one `env` block — no legacy aliases:

  ```yaml
  # 0.6.x                                # 0.7.0
  inherit_env: true                      env:
  allowed_env: [PATH, HOME, LANG]          inherit: true
                                           allow: [PATH, HOME, LANG]
  ```

  The old keys are **rejected at parse** with a migration hint ("config keys
  removed in 0.7.0: `inherit_env` -> `env.inherit` ..."), because serde would
  otherwise ignore them silently and boot with env forwarding OFF. A stored
  configuration value still carrying the old keys makes the worker fail closed
  at boot; rewrite it via `configuration::set` (id `shell`) with the nested
  shape. **Sequencing matters**: update the binary FIRST, then the stored
  value — writing the new shape while 0.6.x is still running makes the old
  worker hot-reload it, ignore the unknown `env` block, and silently stop
  forwarding env until restart. A **half-migration** (nesting the OLD key
  names under the new block, e.g. `env: { inherit_env: true }`) is also
  rejected — `env` denies unknown fields — rather than silently falling back
  to the wider default `allow` list.
  A `--config` seed file carrying any of these removed keys now **aborts
  boot** rather than warning and falling back to the permissive built-in
  seed; only a genuinely missing seed file falls through gracefully.
- **BREAKING: `fs.host_root` (single-root alias) removed.** The 0.6.x
  one-entry alias for the jail root is **rejected at parse** with a migration
  hint ("config key removed in 0.7.0: `fs.host_root` -> `fs.host_roots`
  (one-entry list)"). Replace it with the list form:

  ```yaml
  # 0.6.x                                # 0.7.0
  fs:                                    fs:
    host_root: /srv/app                    host_roots: [/srv/app]
  ```

  Same fail-closed rationale as the env keys: serde would otherwise ignore
  the stale key and the worker would see no jail configured at all.
- **BREAKING: `code.base_path`/`code.base_paths` removed from the schema and
  REJECTED at parse.** They were inert even before removal — the code
  resolver has taken its roots from `fs.host_roots` since the coder merge —
  but this is a hard migration: "never had an effect" is not an exception. A
  stored value still carrying either fails closed with a hint naming both
  keys, same as every other removed key. Set the jail once via
  `fs.host_roots`.
- **The one-shot coder→shell config migration is removed.** 0.7.0 no longer
  folds a legacy standalone-`coder` configuration entry into the `shell`
  value at boot, and the hidden `migrated_from_coder` marker field is
  REJECTED at parse rather than silently tolerated. Boot also no longer
  probes `configuration::get` for the `coder` entry, so the
  "configuration 'coder' not found" WARN retries at startup are gone.
  **Two distinct upgrade scenarios**:
  - An install that ALREADY has a `shell` entry (it went through the fold
    under 0.6.x, so the entry carries `migrated_from_coder: true`) now fails
    closed at 0.7.0 boot with a migration hint, instead of silently parsing
    past the marker.
  - An install with ONLY a standalone `coder` entry and NO `shell` entry at
    all has nothing to reject — it still boots 0.7.0 with the generic
    permissive `/tmp` dev seed for `shell`, silently, because there is no
    stored `shell` value to fail closed on. The old `coder` roots and
    protected globs are NOT carried over. Boot 0.6.x once first (it performs
    the migration and writes the `shell` entry) before upgrading to 0.7.0 to
    avoid this.
- **`--version` added**, and `--url`/`III_URL` and `RUST_LOG` are now
  documented (see [Running](#running)).
- **Unreachable-engine boot is loud**: one ERROR naming the host/port and the
  fix hint, instead of only the SDK's silent retry WARNs. The probe runs
  detached so it never delays boot.
- **Every config field now carries a schema description**, so the console
  configuration UI documents each knob inline.
- **Command-shaped denylist patterns tolerate a wrapper prefix**
  (`sudo`/`doas`/`nohup`/`env`/`timeout [duration]`, optionally
  path-qualified): `sudo shutdown -h now` trips the tripwire again.

## Upgrading to 0.4.0

0.4.0 is a breaking release. Migrating from 0.3.x:

- **`shell::fs::chmod` response field renamed** `updated` → `entries_changed`; read `entries_changed`. Inbound legacy `{ updated }` from an older engine still deserializes (serde alias), but new consumers should read `entries_changed`.
- **`mkdir`/`rm`/`mv` gained structured fields** (`path`/`already_existed`, `path`/`was_present`, `src`/`dst`/`overwrote`) alongside the original boolean. Additive: existing readers of `created`/`removed`/`moved` keep working.
- **Configuration moved to the central `configuration` worker** (database-style) with hot-reload. `--config` is now a first-boot seed only; the live value from the configuration worker wins once an entry exists. `--manifest` was removed (the interface is collected live).
- **New read-only `shell::config-status`** reports the last hot-reload outcome. Operator/automation only; it is denied to agents.
- **Error envelope: the S-code is now the top-level wire `code`.** Every S-coded failure returns `{ "code": "S211", "message": "no such job: ..." }` with the S-code as the top-level `code`. Previously the code was buried — handlers returned the `{code,message}` JSON stringified into `message` under the generic `invocation_failed` code. Consumers that branched on `code == "invocation_failed"` or parsed the embedded JSON out of the message must update to read `error.code` directly.
- **Host background jobs are unbounded by default.** A host-targeted `shell::exec_bg` job runs until it exits or `shell::kill` terminates it — long installs, builds, dev-servers, and tails are not killed. To force-kill a runaway host bg job, set a positive `max_bg_timeout_ms` (new operator config field, default `0` = unbounded; separate from `max_timeout_ms`, which bounds foreground `shell::exec`); a job that exceeds it is killed and its status becomes `killed`. Sandbox jobs still honor `timeout_ms`.
- **Per-call `env` denylist broadened.** The always-rejected key set now includes `HOME` and loader/lookup-path keys (`LD_*`, `DYLD_*`, `GCONV_PATH`, …) **and** interpreter/shell startup-file keys (`BASH_ENV`, `ENV`, `PYTHONSTARTUP`, `PERL5OPT`, `RUBYOPT`, `NODE_OPTIONS`). These are rejected even if listed in `allowed_env`. `LD_*`/`DYLD_*` are matched by family prefix, so a loader variable not in the explicit list is still rejected. Note that `HOME` is in the default `allowed_env` for the worker's own forwarded env but is **not** settable per-call.
- **Sandbox-forwarded fs/exec errors can now surface engine S-codes** that 0.3.7 collapsed to `S216` — e.g. `S001`–`S004` (lifecycle), `S100`–`S102` (image/VM), `S300`, and `S400`. Branch on the specific code where relevant.

## Troubleshooting

- **`fs.host_roots is empty ... refusing to start unjailed`**: set `fs.host_roots` to at least one directory, or set `fs.allow_unjailed: true`.
- **`S215 path escapes the fs jail roots` on a path inside the jail**: a symlink in the path resolves outside the jail. Resolve it yourself, or move the target inside a jail root.
- **`S300` on a sandbox target**: the host cannot boot microVMs. Sandbox execution requires Apple Silicon or `/dev/kvm`.
- **Worker never connects**: the engine is not running or not bound on the configured `--url`. Start the engine first; the default WebSocket port is 49134.
- **`config keys removed in 0.7.0: ...` at boot or on reload**: the seed file or the stored configuration value still uses the 0.6.x `inherit_env`/`allowed_env` keys (nest them under `env:` as `inherit`/`allow`) or the single-root `fs.host_root` alias (use `fs.host_roots: [<path>]`) — see [Upgrading to 0.7.0](#upgrading-to-070).
- **`` `allowlist` (removed in 0.8.0, no replacement) `` at boot or on reload**: the seed file or the stored configuration value still carries the removed `allowlist` key (even an empty `allowlist: []` trips this) — see [Upgrading to 0.8.0](#upgrading-to-080) for the fix.
- **A relative path (including `~`) passed to `shell::fs::*`/`cwd` behaves unexpectedly**: paths are never shell-expanded. `~` is not resolved to your home directory — when jailed, a relative path (including one starting with `~`) resolves against the primary `fs.host_roots` entry, so `~/foo` is interpreted as a path literally named `~` under that root, not your home directory; when unjailed, only absolute paths are accepted at all, so a relative path (including `~/foo`) is rejected outright. Pass an absolute, already-expanded path instead.

For the threat model, streaming wire shapes, and contributor build steps, see [ARCHITECTURE.md](ARCHITECTURE.md).

## License

Apache 2.0 — see [LICENSE](https://github.com/iii-hq/workers/blob/main/LICENSE).

## api reference

```json
{
  "functions": [
    {
      "description": "Internal console UI: retrieve an exact before/after snapshot by change id.",
      "metadata": {
        "internal": true
      },
      "name": "coder::change-diff",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "change_id": {
            "type": "string"
          }
        },
        "required": [
          "change_id"
        ],
        "title": "ChangeDiffInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "is_binary": {
            "type": "boolean"
          },
          "new_contents": {
            "description": "Exact post-mutation body. Missing when either side is not UTF-8.",
            "type": [
              "string",
              "null"
            ]
          },
          "old_contents": {
            "description": "Exact pre-mutation body. Missing when either side is not UTF-8.",
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "is_binary",
          "path"
        ],
        "title": "ChangeDiffOutput",
        "type": "object"
      }
    },
    {
      "description": "Create one or more files atomically; per-file overwrite and parents flags. For a conflict-safe overwrite pass the revision from coder::read-file as expected_revision (stale revision: C221, nothing written). Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {
        "display": true
      },
      "name": "coder::create-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "CreateFileSpec": {
            "properties": {
              "content": {
                "type": "string"
              },
              "expected_revision": {
                "default": null,
                "description": "Optimistic-concurrency guard for an overwrite: the `revision` from coder::read-file; if the content changed the entry fails C221 unwritten.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "mode": {
                "default": "0644",
                "description": "Octal permission bits as a string, e.g. \"0644\". Defaults to \"0644\".",
                "type": "string"
              },
              "overwrite": {
                "default": false,
                "description": "When false (the default), refuse to write if `path` already exists.",
                "type": "boolean"
              },
              "parents": {
                "default": true,
                "description": "Create missing parent directories. Defaults to true so a single `coder::create-file` call can scaffold a fresh subtree.",
                "type": "boolean"
              },
              "path": {
                "description": "File to create.",
                "type": "string"
              }
            },
            "required": [
              "content",
              "path"
            ],
            "type": "object"
          }
        },
        "examples": [
          {
            "files": [
              {
                "content": "pub mod utils;\n",
                "overwrite": false,
                "path": "src/lib.rs"
              },
              {
                "content": "# scratch notes\n",
                "overwrite": true,
                "path": "/tmp/scratch/notes.md"
              }
            ]
          }
        ],
        "properties": {
          "files": {
            "items": {
              "$ref": "#/definitions/CreateFileSpec"
            },
            "type": "array"
          }
        },
        "required": [
          "files"
        ],
        "title": "CreateFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "CreateFileResult": {
            "properties": {
              "bytes_written": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error for this entry. `code` is stable for programmatic branching (e.g. `\"C213\"` means already-exists; pass `overwrite=true` to replace). `message` carries the corrective action an LLM agent needs to make a successful second call."
              },
              "path": {
                "description": "Canonical absolute path (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              },
              "revision": {
                "description": "Opaque revision for the exact bytes written. Supply this as `expected_revision` on a later overwrite to avoid lost updates.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "bytes_written",
              "path",
              "success"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/CreateFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "CreateFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Remove one or more paths. Directories need recursive: true; missing paths succeed; recursion refuses to descend through non-accessible entries. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {
        "display": true
      },
      "name": "coder::delete-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "paths": [
              "src/old_module.rs",
              "build/artifacts"
            ],
            "recursive": true
          }
        ],
        "properties": {
          "paths": {
            "description": "Paths to remove.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "recursive": {
            "default": false,
            "description": "Required for non-empty directories. Files and empty dirs ignore it.",
            "type": "boolean"
          }
        },
        "required": [
          "paths"
        ],
        "title": "DeleteFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "DeleteFileResult": {
            "properties": {
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error for this entry. `code` is stable for programmatic branching (e.g. `\"C211\"` for not-found-or-denied; `\"C210\"` for refusing to delete an allowed root). `message` carries the corrective action an LLM agent needs to make a successful second call."
              },
              "path": {
                "description": "Canonical absolute path (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              },
              "removed": {
                "type": "boolean"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "path",
              "removed",
              "success"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/DeleteFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "DeleteFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Report the coder access contract: mode (jailed | unjailed), allowed roots (primary first), the session_root relative paths anchor against, size caps, response budgets, listing/search limits, non-accessible globs and default_exclude_globs. Call it first when a path is rejected.",
      "metadata": {},
      "name": "coder::info",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "No arguments — `coder::info` is a pure discovery call.",
        "examples": [
          {}
        ],
        "title": "InfoInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "AccessMode": {
            "description": "Effective access mode of the coder surface — the same deny-only policy switch `shell::fs::*` runs under.",
            "oneOf": [
              {
                "description": "Paths are confined to `base_paths`; anything outside is rejected.",
                "enum": [
                  "jailed"
                ],
                "type": "string"
              },
              {
                "description": "Operator opt-in (`fs.allow_unjailed: true`, empty `fs.host_roots`): absolute paths anywhere on the host are accepted, confined only by `fs.denylist_paths` and `non_accessible_globs`. `base_paths` only anchor relative wire paths.",
                "enum": [
                  "unjailed"
                ],
                "type": "string"
              }
            ]
          }
        },
        "properties": {
          "base_paths": {
            "description": "Canonical absolute paths of the allowed roots, in configuration order. The primary root (index 0) is where relative wire paths resolve; an absolute path is accepted when it canonicalises inside ANY of these (in `unjailed` mode: anywhere on the host). In `jailed` mode paths outside every root are rejected — use `shell::fs::*` instead.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "batch_read_budget_bytes": {
            "description": "Aggregate budget across a single `paths[]` batch call to `coder::read-file`, measured in bytes of returned content (after UTF-8 sanitization — invalid bytes expand to U+FFFD before being counted, so the cap bounds what the caller actually receives). Entries are collected in request order; each entry may consume up to `min(remaining_budget, max_read_bytes)`. An entry reached with zero remaining budget receives a per-entry C218 naming this key, its value, and the bytes already consumed, with recovery guidance. Budget topology: batch reads are governed by this key; single-path full reads by `max_output_bytes`; windowed reads by `max_read_bytes` applied per returned window — `max_read_bytes` is also the per-file IO ceiling for all of them.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "default_exclude_globs": {
            "description": "Noise-exclusion globs (root-relative, same matching as `non_accessible_globs`): matching paths (node_modules, .git, …) are omitted from `coder::search` results and pruned from `coder::tree` descent — the directory surfaces as a childless `truncated` stub. Hide-only — no access protection. Pass `use_default_excludes: false` on those calls to look inside.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "list_default_page_size": {
            "description": "Default `page_size` used by `coder::list-folder` when the caller omits it.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "list_max_page_size": {
            "description": "Hard cap on `page_size` accepted by `coder::list-folder`.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "max_output_bytes": {
            "description": "Context budget for single-path FULL reads in `coder::read-file`, in bytes of returned content. Full reads larger than this return C218 with the file's size/line count and window/stat recovery guidance; a per-call `max_output_bytes` override is available on `coder::read-file` (clamped to `max_read_bytes`).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "max_read_bytes": {
            "description": "Per-file IO ceiling for `coder::read-file`. Full reads of files larger than this are rejected with C218; windowed reads cap the returned window bytes instead, so larger files stay readable window by window. Also the ceiling for `coder::search` content scanning — larger files are silently skipped during search.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "max_write_bytes": {
            "description": "Maximum bytes that `coder::create-file` or `coder::update-file` will accept for a single file write. Larger writes are rejected with C218.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "mode": {
            "allOf": [
              {
                "$ref": "#/definitions/AccessMode"
              }
            ],
            "description": "`jailed`: paths must stay inside `base_paths`. `unjailed`: absolute paths anywhere on the host are accepted (deny-only, like `shell::fs::*`) and `base_paths` only anchor relative paths."
          },
          "non_accessible_globs": {
            "description": "Glob patterns matched per root (root-relative). Files whose root-relative path matches are listable but not readable/writable/deletable/creatable; they return C211.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "primary_root": {
            "description": "Convenience duplicate of `base_paths[0]` — the primary allowed root. Relative paths resolve against this directory UNLESS `session_root` is set, which takes precedence.",
            "type": "string"
          },
          "search_default_max_line_bytes": {
            "description": "Per-line byte cap in `coder::search`: matching considers at most this many bytes of each line, and matched/context lines are truncated to it.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "search_default_max_matches": {
            "description": "Default `max_matches` used by `coder::search` when the caller omits it.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "search_response_budget_bytes": {
            "description": "Aggregate byte budget for one `coder::search` response, measured in payload bytes (paths + matched text + context lines). When the budget is hit the response sets `truncated: true` — refine the query or add `include_globs`.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "session_root": {
            "description": "The session's working directory when the caller runs under a harness-stamped filesystem scope; `null` otherwise. When set, THIS is what relative wire paths (and `coder::search` glob matching) anchor against — not `primary_root`. In `unjailed` mode it may sit outside every `base_paths` entry, so a relative path that looks wrong against the allowed roots can still be correct here. Check it first when a relative path was rejected.",
            "type": [
              "string",
              "null"
            ]
          },
          "tree_default_depth": {
            "description": "Default `max_depth` used by `coder::tree` when the caller omits it.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "tree_per_folder_limit": {
            "description": "Maximum entries returned per folder node by `coder::tree`; folders that exceed this are flagged `truncated`.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "version": {
            "description": "Coder worker version (`CARGO_PKG_VERSION`).",
            "type": "string"
          }
        },
        "required": [
          "base_paths",
          "batch_read_budget_bytes",
          "default_exclude_globs",
          "list_default_page_size",
          "list_max_page_size",
          "max_output_bytes",
          "max_read_bytes",
          "max_write_bytes",
          "mode",
          "non_accessible_globs",
          "primary_root",
          "search_default_max_line_bytes",
          "search_default_max_matches",
          "search_response_budget_bytes",
          "tree_default_depth",
          "tree_per_folder_limit",
          "version"
        ],
        "title": "InfoOutput",
        "type": "object"
      }
    },
    {
      "description": "List one folder, paginated and sorted by name. Entries carry only name; entry path = response path + '/' + name. Non-accessible entries are listed with non_accessible: true. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::list-folder",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "page": 1,
            "page_size": 50,
            "path": "src"
          }
        ],
        "properties": {
          "page": {
            "default": 1,
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "page_size": {
            "default": null,
            "description": "Capped at list_max_page_size; defaults to list_default_page_size (see coder::info).",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "default": ".",
            "description": "Folder to list (default `.`).",
            "type": "string"
          }
        },
        "title": "ListFolderInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "DirEntry": {
            "properties": {
              "kind": {
                "$ref": "#/definitions/EntryKind"
              },
              "mtime": {
                "format": "int64",
                "type": "integer"
              },
              "name": {
                "description": "Entry basename. The absolute path is derivable from the response's `path`: entry path = folder path + \"/\" + name.",
                "type": "string"
              },
              "non_accessible": {
                "description": "True if this entry matches `non_accessible_globs` — caller cannot read/write/delete it via `coder::*` even though it shows up here.",
                "type": "boolean"
              },
              "size": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "kind",
              "mtime",
              "name",
              "non_accessible",
              "size"
            ],
            "type": "object"
          },
          "EntryKind": {
            "enum": [
              "file",
              "dir",
              "symlink",
              "other"
            ],
            "type": "string"
          }
        },
        "properties": {
          "entries": {
            "items": {
              "$ref": "#/definitions/DirEntry"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "page": {
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "page_size": {
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Canonical absolute path of the listed folder (resolved through the jail). Entries carry only `name`; derive an entry's absolute path by joining: entry path = this path + \"/\" + name. Operations on derived paths re-validate through the jail.",
            "type": "string"
          },
          "total": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "entries",
          "has_more",
          "page",
          "page_size",
          "path",
          "total"
        ],
        "title": "ListFolderOutput",
        "type": "object"
      }
    },
    {
      "description": "Move or rename one or more paths; per-entry overwrite and parents flags. Same-root moves rename atomically; cross-root moves copy+delete files only (move directory contents individually) and remove the copy if the delete fails. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::move",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "MoveFileSpec": {
            "properties": {
              "from": {
                "description": "Source path.",
                "type": "string"
              },
              "overwrite": {
                "default": false,
                "description": "When false (the default), refuse to overwrite an existing destination. Pass `overwrite: true` to replace an existing file at `to`.",
                "type": "boolean"
              },
              "parents": {
                "default": true,
                "description": "Create missing parent directories of the destination. Defaults to true.",
                "type": "boolean"
              },
              "to": {
                "description": "Destination path.",
                "type": "string"
              }
            },
            "required": [
              "from",
              "to"
            ],
            "type": "object"
          }
        },
        "examples": [
          {
            "files": [
              {
                "from": "src/old_name.rs",
                "to": "src/new_name.rs"
              },
              {
                "from": "build/output.bin",
                "overwrite": true,
                "to": "/tmp/coder-cache/output.bin"
              }
            ]
          }
        ],
        "properties": {
          "files": {
            "description": "Entries to move. Each entry is processed independently so a single failure never aborts the rest.",
            "items": {
              "$ref": "#/definitions/MoveFileSpec"
            },
            "type": "array"
          }
        },
        "required": [
          "files"
        ],
        "title": "MoveFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "MoveFileResult": {
            "properties": {
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error for this entry. `code` is stable for programmatic branching (e.g. `\"C213\"` means destination exists; pass `overwrite=true` to replace; `\"C210\"` for disallowed operations such as cross-root directory moves, moving a root itself, or a destination that is a directory — the message then names the corrected target path). `message` carries the corrective action an LLM agent needs to make a successful second call."
              },
              "from": {
                "description": "Canonical absolute path of the source (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              },
              "moved": {
                "description": "True only when the move fully completed; false for a no-op self-move (`from` and `to` resolve to the same file).",
                "type": "boolean"
              },
              "success": {
                "type": "boolean"
              },
              "to": {
                "description": "Canonical absolute path of the destination (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              }
            },
            "required": [
              "from",
              "moved",
              "success",
              "to"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/MoveFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "MoveFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Read a file. stat: true probes size/mtime/total_lines without content; line_from/line_to (1-based, inclusive) read a window of a file of any size; numbered: true prefixes absolute line numbers; paths[] (XOR path) batch-reads. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::read-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ReadEncoding": {
            "description": "Wire encoding for returned `content`.",
            "oneOf": [
              {
                "description": "UTF-8 text; invalid bytes replaced by U+FFFD.",
                "enum": [
                  "text"
                ],
                "type": "string"
              },
              {
                "description": "Exact file bytes, base64-encoded (standard alphabet, padded).",
                "enum": [
                  "base64"
                ],
                "type": "string"
              }
            ]
          },
          "ReadTarget": {
            "anyOf": [
              {
                "description": "Bare path string: read the whole file.",
                "type": "string"
              },
              {
                "description": "Object form: path plus optional window and flags.",
                "properties": {
                  "line_from": {
                    "default": null,
                    "description": "First line of the window, 1-based inclusive (>= 1); defaults to 1 when only `line_to` is set.",
                    "format": "uint64",
                    "minimum": 1,
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "line_to": {
                    "default": null,
                    "description": "Last line of the window, 1-based inclusive, >= `line_from`; omit to read to EOF.",
                    "format": "uint64",
                    "minimum": 1,
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "numbered": {
                    "default": false,
                    "description": "Prefix this entry's lines with their absolute 1-based line numbers (`N→`); prefix bytes count toward the batch budget.",
                    "type": "boolean"
                  },
                  "path": {
                    "description": "File to read.",
                    "type": "string"
                  },
                  "stat": {
                    "default": false,
                    "description": "Metadata probe for this entry: size/mode/mtime plus total_lines, content null, no budget consumed; C210 with a window or `numbered`.",
                    "type": "boolean"
                  }
                },
                "required": [
                  "path"
                ],
                "type": "object"
              }
            ],
            "description": "One batch entry: a bare path string (whole-file read) or an object with a per-entry window, stat or numbered flag."
          }
        },
        "examples": [
          {
            "line_from": 10,
            "line_to": 50,
            "path": "src/main.rs"
          },
          {
            "paths": [
              "src/lib.rs",
              {
                "line_from": 1,
                "line_to": 30,
                "path": "src/config.rs"
              }
            ]
          }
        ],
        "properties": {
          "encoding": {
            "allOf": [
              {
                "$ref": "#/definitions/ReadEncoding"
              }
            ],
            "description": "Content encoding for full reads; `base64` returns the exact file bytes (encoded length is budgeted). C210 with stat/window/numbered."
          },
          "line_from": {
            "default": null,
            "description": "First line (1-based, >= 1) of a windowed read; windows keep files over max_read_bytes readable slice by slice. `path` mode only.",
            "format": "uint64",
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ]
          },
          "line_to": {
            "default": null,
            "description": "Last line of the window, 1-based inclusive, >= `line_from`; omit to read from `line_from` to EOF. `path` mode only.",
            "format": "uint64",
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ]
          },
          "max_output_bytes": {
            "default": null,
            "description": "Full-read byte budget (returned bytes, clamped to max_read_bytes); over budget fails C218 naming size/total_lines. C210 with a window.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "numbered": {
            "default": false,
            "description": "Prefix each line with its absolute 1-based file line number (`N→`), matching coder::update-file line ops; C210 with `stat`. `path` mode only.",
            "type": "boolean"
          },
          "path": {
            "default": null,
            "description": "Single file to read; XOR with `paths` (C210 if both or neither is set).",
            "type": [
              "string",
              "null"
            ]
          },
          "paths": {
            "description": "Batch of files to read in one call (bare path strings or window objects), processed in order against batch_read_budget_bytes; XOR with `path`.",
            "items": {
              "$ref": "#/definitions/ReadTarget"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "stat": {
            "default": false,
            "description": "Metadata probe: size/mode/mtime plus total_lines/is_utf8, no content; C210 with line_from/line_to/numbered/max_output_bytes.",
            "type": "boolean"
          }
        },
        "title": "ReadFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ReadEntryResult": {
            "description": "Per-entry result in a batch `paths[]` response.",
            "properties": {
              "content": {
                "description": "File content as a UTF-8 string — the whole file or the requested window. Binary bytes are replaced by U+FFFD (`is_utf8: false`). `null` on failure.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error — present only when `success: false`."
              },
              "is_utf8": {
                "description": "Whether `content` survived UTF-8 conversion without losing bytes. `null` on failure.",
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "lines_returned": {
                "description": "Number of lines returned in `content`. `null` on failure.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "mode": {
                "description": "Unix permission bits (lower 9 bits of `st_mode`), e.g. 0o644. `null` on failure.",
                "format": "uint32",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "more_lines": {
                "description": "`true` when the file has content beyond what `content` includes (window ended before EOF, or byte budget cut the window short). `null` on failure.",
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "mtime": {
                "description": "Last-modified time as a Unix epoch in seconds. `null` on failure.",
                "format": "int64",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "path": {
                "description": "Canonical absolute path of the file (resolved through the jail). If resolution failed, this echoes the caller's input verbatim.",
                "type": "string"
              },
              "size": {
                "description": "Size of the FILE in bytes (from metadata). `null` on failure or when the entry budget was exhausted before the file was opened.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "success": {
                "description": "`true` when the read succeeded (content/metadata fields are populated); `false` when an error occurred (only `error` is set).",
                "type": "boolean"
              },
              "total_lines": {
                "description": "Total lines in the file; present when the stream reached EOF during this entry's read. `null` when not traversed or on failure.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "path",
              "success"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "content": {
            "description": "File content as a UTF-8 string — the whole file, or just the requested window when `line_from`/`line_to` was given (window lines keep their newline terminators). Binary content is returned with invalid bytes replaced by U+FFFD; request `encoding: \"base64\"` when exact bytes matter (content is then the file's bytes base64-encoded). **Single-path mode only; null when the request used `paths[]`.**",
            "type": [
              "string",
              "null"
            ]
          },
          "is_utf8": {
            "description": "Whether `content` survived UTF-8 conversion without losing bytes. Reflects the RETURNED content only: a clean window inside an otherwise-binary file is still `true`. **Single-path mode only; null when the request used `paths[]`.**",
            "type": [
              "boolean",
              "null"
            ]
          },
          "lines_returned": {
            "description": "Number of lines in `content`. For full reads this equals the file's total line count. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "mode": {
            "description": "Unix permission bits (lower 9 bits of `st_mode`), e.g. 0o644. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "more_lines": {
            "description": "True when the file has content beyond what `content` includes: the window ended before EOF, or the byte budget cut the window short. Always false for full reads. **Single-path mode only; null when the request used `paths[]`.**",
            "type": [
              "boolean",
              "null"
            ]
          },
          "mtime": {
            "description": "Last-modified time as a Unix epoch in seconds. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "description": "Canonical absolute path of the file read (resolved through the jail). **Single-path mode only; null when the request used `paths[]`.**",
            "type": [
              "string",
              "null"
            ]
          },
          "results": {
            "description": "Per-entry results for a batch `paths[]` request. **Present only when the request used `paths[]`; null in single-path mode.**",
            "items": {
              "$ref": "#/definitions/ReadEntryResult"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "revision": {
            "description": "Opaque identity of the file's exact bytes. Present for complete single-path reads only; pass it as `expected_revision` to `coder::create-file` for a conflict-safe whole-file overwrite.",
            "type": [
              "string",
              "null"
            ]
          },
          "size": {
            "description": "Size of the FILE in bytes (from metadata) — not the size of `content`; in windowed mode the two differ. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "total_lines": {
            "description": "Total number of lines in the file. Present only when the read traversed the whole file: always for full reads; for windowed reads only when the stream naturally reached EOF within the byte cap. Never computed by forcing an extra full scan — absent means the file was not fully traversed. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "title": "ReadFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Search file contents (literal or regex) and/or paths (files and dirs); first match per line only. default_exclude_globs noise is skipped unless use_default_excludes: false. truncated: true means refine the query, not paginate. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::search",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "context_lines_after": 2,
            "context_lines_before": 2,
            "include_globs": [
              "**/*.rs"
            ],
            "path": "src",
            "query": "fn handle",
            "search_content": true,
            "search_paths": false
          }
        ],
        "properties": {
          "context_lines_after": {
            "default": null,
            "description": "Lines of context after each content match; same rules as `context_lines_before`.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "context_lines_before": {
            "default": null,
            "description": "Lines of context before each content match (max 10, C210 above); truncated to max_line_bytes and counted in the budget. Default 0.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "exclude_globs": {
            "default": [],
            "description": "Glob patterns (same relative-to-root matching) that exclude paths.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "fuzzy_paths": {
            "default": false,
            "description": "Rank path matches by fuzzy subsequence score (quick-open style) instead of substring/regex matching; `path_matches` then comes back best first. Content matching is unaffected.",
            "type": "boolean"
          },
          "ignore_case": {
            "default": false,
            "type": "boolean"
          },
          "include_globs": {
            "default": [],
            "description": "Root-relative glob patterns paths must match; empty = everything.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "include_hidden": {
            "default": true,
            "description": "Walk dot-files and dot-folders (`.github`, `.env`, …); default true. `false` leaves them out, the way an editor's quick open does.",
            "type": "boolean"
          },
          "max_line_bytes": {
            "default": null,
            "description": "Bytes per line to consider when scanning content; longer lines are truncated for the match snippet.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "max_matches": {
            "default": null,
            "description": "Optional explicit cap. Falls back to config when unset.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "default": ".",
            "description": "Folder to search (default `.`); globs match relative to its root, result paths are absolute.",
            "type": "string"
          },
          "query": {
            "description": "Pattern to search for. Treated as a regex when `regex: true`, otherwise as a literal substring. May be empty only when `search_content` is false: a path-only search with no query lists every path (with `fuzzy_paths`, shallow and short paths first).",
            "type": "string"
          },
          "regex": {
            "default": false,
            "type": "boolean"
          },
          "respect_gitignore": {
            "default": false,
            "description": "Honour `.gitignore`/`.ignore` rules (inside a Git repository) while walking, the way an editor's search does; default false keeps every non-excluded file searchable.",
            "type": "boolean"
          },
          "search_content": {
            "default": true,
            "description": "Search file contents (default true).",
            "type": "boolean"
          },
          "search_paths": {
            "default": true,
            "description": "Search file paths (default true).",
            "type": "boolean"
          },
          "use_default_excludes": {
            "default": true,
            "description": "Skip paths matching default_exclude_globs (.git, node_modules, …; see coder::info) in content and path results; false searches inside them.",
            "type": "boolean"
          }
        },
        "required": [
          "query"
        ],
        "title": "SearchInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ContentMatch": {
            "properties": {
              "after": {
                "description": "Context lines immediately after the matched line — same file only, in file order, each truncated to `max_line_bytes`. Omitted when empty (no `context_lines_after` requested, or the match is at the end of the file).",
                "items": {
                  "type": "string"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "before": {
                "description": "Context lines immediately before the matched line — same file only, in file order, each truncated to `max_line_bytes`. Omitted when empty (no `context_lines_before` requested, or the match is at the start of the file).",
                "items": {
                  "type": "string"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "column": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "line": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "description": "Absolute path under the canonical parent; symlinks at the entry itself are not resolved. Operations on it re-validate through the jail.",
                "type": "string"
              },
              "text": {
                "description": "Matched line; truncated to `max_line_bytes` and never spans newlines.",
                "type": "string"
              }
            },
            "required": [
              "column",
              "line",
              "path",
              "text"
            ],
            "type": "object"
          },
          "PathMatch": {
            "properties": {
              "kind": {
                "allOf": [
                  {
                    "$ref": "#/definitions/PathMatchKind"
                  }
                ],
                "description": "What matched: `file` or `dir`. Directories match by NAME only — content search never reads them."
              },
              "path": {
                "description": "Absolute path under the canonical parent; symlinks at the entry itself are not resolved. Operations on it re-validate through the jail.",
                "type": "string"
              }
            },
            "required": [
              "kind",
              "path"
            ],
            "type": "object"
          },
          "PathMatchKind": {
            "enum": [
              "file",
              "dir"
            ],
            "type": "string"
          }
        },
        "properties": {
          "content_matches": {
            "items": {
              "$ref": "#/definitions/ContentMatch"
            },
            "type": "array"
          },
          "path_matches": {
            "items": {
              "$ref": "#/definitions/PathMatch"
            },
            "type": "array"
          },
          "truncated": {
            "description": "True if results were cut off — either match list hit the `max_matches` cap, or the response hit the `search_response_budget_bytes` byte budget. When true, refine the query or add include_globs rather than paginate.",
            "type": "boolean"
          }
        },
        "required": [
          "content_matches",
          "path_matches",
          "truncated"
        ],
        "title": "SearchOutput",
        "type": "object"
      }
    },
    {
      "description": "Show a directory tree, bounded by max_depth, per_folder_limit and a node budget. Nodes carry only name (child path = parent path + '/' + name); over-limit folders are truncated stubs to paginate with coder::list-folder. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::tree",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "max_depth": 3,
            "path": "."
          }
        ],
        "properties": {
          "include_hidden": {
            "default": true,
            "description": "List dot-prefixed entries; false omits them at every level (they then don't count toward per_folder_limit). The requested root is exempt.",
            "type": "boolean"
          },
          "max_depth": {
            "default": null,
            "description": "Maximum depth to descend; the root node is depth 0.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "default": ".",
            "description": "Base folder for the snapshot (default `.`).",
            "type": "string"
          },
          "per_folder_limit": {
            "default": null,
            "description": "Maximum children listed per folder. When more exist, the folder is flagged `truncated` and callers should switch to `coder::list-folder`.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "use_default_excludes": {
            "default": true,
            "description": "Prune directories matching default_exclude_globs (see coder::info) into childless `truncated` stubs; false lists everything.",
            "type": "boolean"
          }
        },
        "title": "TreeInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "NodeKind": {
            "enum": [
              "file",
              "dir",
              "symlink",
              "other"
            ],
            "type": "string"
          },
          "TreeNode": {
            "properties": {
              "children": {
                "items": {
                  "$ref": "#/definitions/TreeNode"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "kind": {
                "$ref": "#/definitions/NodeKind"
              },
              "mtime": {
                "format": "int64",
                "type": "integer"
              },
              "name": {
                "description": "Entry basename. The ROOT node's path is the response's top-level `path` itself; every other node's path derives by joining from there: child path = parent path + \"/\" + name.",
                "type": "string"
              },
              "non_accessible": {
                "type": "boolean"
              },
              "size": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "truncated": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/TruncationInfo"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Set on directories whose `children` was capped at `per_folder_limit`, whose subtree was cut off by `max_depth`, which matched `default_exclude_globs` or omitted matching non-directory children (reason \"default_exclude\"), or where the snapshot's node budget ran out (reason \"max_nodes\")."
              }
            },
            "required": [
              "kind",
              "mtime",
              "name",
              "non_accessible",
              "size"
            ],
            "type": "object"
          },
          "TruncationInfo": {
            "properties": {
              "hint": {
                "type": "string"
              },
              "reason": {
                "description": "Reason this folder was truncated: hit `per_folder_limit`, cut off by `max_depth`, matched `default_exclude_globs` (`default_exclude`), omitted matching non-directory children, or the snapshot's total node budget ran out (`max_nodes`).",
                "type": "string"
              },
              "shown": {
                "description": "Number of children actually returned.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "total": {
                "description": "Total number of children eligible for listing in the folder, counted after hidden and default-exclude filtering (only populated when `reason == \"per_folder_limit\"`; for depth truncation we don't peek into the folder).",
                "format": "uint32",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "hint",
              "reason",
              "shown"
            ],
            "type": "object"
          }
        },
        "properties": {
          "path": {
            "description": "Canonical absolute path of the requested folder (resolved through the jail). Nodes carry only `name`, and the root node's path IS this `path` — do not join the root's `name` onto it; derive children by joining from here: child path = parent path + \"/\" + name. Operations on derived paths re-validate through the jail.",
            "type": "string"
          },
          "root": {
            "allOf": [
              {
                "$ref": "#/definitions/TreeNode"
              }
            ],
            "description": "Root node of the snapshot; its `name` is the folder's basename."
          }
        },
        "required": [
          "path",
          "root"
        ],
        "title": "TreeOutput",
        "type": "object"
      }
    },
    {
      "description": "Edit one or more files: batched line ops (1-based, inclusive, applied bottom-up), then regex replace ops; each file commits atomically. To replace a large region use two short anchors joined by .*? with dot_matches_newline: true instead of quoting it. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {
        "display": true
      },
      "name": "coder::update-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "UpdateFileSpec": {
            "properties": {
              "ops": {
                "items": {
                  "$ref": "#/definitions/UpdateOp"
                },
                "type": "array"
              },
              "path": {
                "description": "File to edit.",
                "type": "string"
              }
            },
            "required": [
              "ops",
              "path"
            ],
            "type": "object"
          },
          "UpdateOp": {
            "oneOf": [
              {
                "description": "Insert `content` before line `at_line` (1-based). `at_line = lines+1` appends to end of file.",
                "properties": {
                  "at_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  },
                  "content": {
                    "type": "string"
                  },
                  "op": {
                    "enum": [
                      "insert"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "at_line",
                  "content",
                  "op"
                ],
                "type": "object"
              },
              {
                "description": "Delete lines `from_line..=to_line` (1-based, inclusive).",
                "properties": {
                  "from_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  },
                  "op": {
                    "enum": [
                      "remove"
                    ],
                    "type": "string"
                  },
                  "to_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  }
                },
                "required": [
                  "from_line",
                  "op",
                  "to_line"
                ],
                "type": "object"
              },
              {
                "description": "Overwrite lines `from_line..=to_line` with `content`.",
                "properties": {
                  "content": {
                    "type": "string"
                  },
                  "from_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  },
                  "op": {
                    "enum": [
                      "update_lines"
                    ],
                    "type": "string"
                  },
                  "to_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  }
                },
                "required": [
                  "content",
                  "from_line",
                  "op",
                  "to_line"
                ],
                "type": "object"
              },
              {
                "description": "Replace all regex matches in the file body (after line ops).",
                "properties": {
                  "dot_matches_newline": {
                    "default": false,
                    "description": "When true `.` also matches newline, so a short pattern like `start.*?end` spans lines; by default a multi-line pattern matches nothing.",
                    "type": "boolean"
                  },
                  "expect_matches": {
                    "default": null,
                    "description": "Expected match count; a mismatch fails this file with C210 and writes nothing (0 asserts absence). Omit to replace all matches.",
                    "format": "uint64",
                    "minimum": 0,
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "ignore_case": {
                    "default": false,
                    "type": "boolean"
                  },
                  "op": {
                    "enum": [
                      "replace"
                    ],
                    "type": "string"
                  },
                  "pattern": {
                    "type": "string"
                  },
                  "replacement": {
                    "description": "Substitution text; $1/${1}/$name/${name} expand captures ($0 = whole match), a literal $ is written $$ (`$${name}`). Unknown references fail C210.",
                    "type": "string"
                  }
                },
                "required": [
                  "op",
                  "pattern",
                  "replacement"
                ],
                "type": "object"
              }
            ]
          }
        },
        "examples": [
          {
            "files": [
              {
                "ops": [
                  {
                    "at_line": 1,
                    "content": "// generated by coder\n",
                    "op": "insert"
                  },
                  {
                    "content": "pub fn hello() {\n    println!(\"hello\");\n}\n",
                    "from_line": 5,
                    "op": "update_lines",
                    "to_line": 7
                  },
                  {
                    "dot_matches_newline": true,
                    "expect_matches": 1,
                    "op": "replace",
                    "pattern": "// BEGIN legacy.*?// END legacy",
                    "replacement": "// removed"
                  }
                ],
                "path": "src/lib.rs"
              }
            ]
          }
        ],
        "properties": {
          "files": {
            "items": {
              "$ref": "#/definitions/UpdateFileSpec"
            },
            "type": "array"
          }
        },
        "required": [
          "files"
        ],
        "title": "UpdateFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "OpEcho": {
            "description": "Post-apply snapshot of the region affected by one op. Line ops echo the affected region with ±2 context lines; regex `replace` ops emit one echo per match site (up to 5, no context): the FIRST and LAST line of the post-replace region, with `elided` set to the inner line count when the region spans more than 2 lines (single-line replacements echo just that line). Each site carries `total_replacements`. Provides just enough context to verify the edit landed in the right place without flooding the LLM context with the full file body.",
            "properties": {
              "elided": {
                "description": "Number of middle lines elided from a large region: for line ops, set when the affected region exceeded the per-echo cap; for replace sites, the count of inner lines between the region's echoed first and last line (set when the region spans >2 lines).",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "from_line": {
                "description": "1-based line number of the first echoed line (after all ops applied).",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "lines": {
                "description": "The echoed lines, post-apply. When the region is large, middle lines are elided and `elided` is set to indicate how many were skipped.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "op_index": {
                "description": "Index of the op in the request's ops array (0-based).",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "total_replacements": {
                "description": "Total number of replacements the regex op made across the whole file (set only on replace-op site echoes, duplicated on each site). Sites are capped at 5 — when more matched, this count is the only record of the extras.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "from_line",
              "lines",
              "op_index"
            ],
            "type": "object"
          },
          "UpdateFileResult": {
            "properties": {
              "applied": {
                "description": "Number of operations applied (only meaningful when `success`).",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "echoes": {
                "description": "Per-op bounded post-apply echoes for edit verification; each applied op returns a snapshot of the affected region (±2 context lines) so the caller can confirm the edit landed at the right position without receiving the full file body. See `OpEcho` for field semantics. Empty on failure. Always present on the wire.",
                "items": {
                  "$ref": "#/definitions/OpEcho"
                },
                "type": "array"
              },
              "echoes_truncated": {
                "description": "True when the total echo budget (~4 KiB) was exhausted before all op echoes could be emitted. Use `coder::read-file` to inspect the full result if needed. Always present on the wire.",
                "type": "boolean"
              },
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error for this entry. `code` is stable for programmatic branching (e.g. `\"C211\"` for not-found-or-denied; `\"C210\"` for bad input such as overlapping ops). `message` carries the corrective action an LLM agent needs to make a successful second call."
              },
              "new_line_count": {
                "description": "Final line count after applying (only meaningful when `success`).",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "description": "Canonical absolute path (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "applied",
              "echoes",
              "echoes_truncated",
              "new_line_count",
              "path",
              "success"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/UpdateFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "UpdateFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Report the last configuration hot-reload outcome: last_outcome (applied|rejected), last_error, and rejected_reloads since boot. A rejected outcome means shell is enforcing an older policy than the central store. Takes no arguments.",
      "metadata": {
        "internal": true
      },
      "name": "shell::config-status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "`shell::config-status` takes no arguments.",
        "title": "ConfigStatusRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ReloadOutcome": {
            "description": "Outcome of the most recent hot-reload attempt, exposed via `shell::config-status`.",
            "oneOf": [
              {
                "description": "The live runtime reflects the most recent configuration the worker loaded.",
                "enum": [
                  "applied"
                ],
                "type": "string"
              },
              {
                "description": "The most recent configuration:updated delivered a value the worker could NOT build; the previous valid policy is still active and the central store has DIVERGED from what shell is enforcing.",
                "enum": [
                  "rejected"
                ],
                "type": "string"
              }
            ]
          }
        },
        "description": "Operator-visible hot-reload status. `rejected_reloads > 0` (or `last_outcome == Rejected`) means a stored config was refused and shell is enforcing an older policy than the central store — actionable divergence.",
        "properties": {
          "last_error": {
            "description": "Build error from the most recent rejected reload (why it was refused).",
            "type": [
              "string",
              "null"
            ]
          },
          "last_outcome": {
            "$ref": "#/definitions/ReloadOutcome"
          },
          "rejected_reloads": {
            "description": "Cumulative count of rejected reloads since boot (never reset).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "last_outcome",
          "rejected_reloads"
        ],
        "title": "ReloadStatus",
        "type": "object"
      }
    },
    {
      "description": "Run a command in the foreground and return its full output. Put the program in `command` (string) and arguments in `args` (string[]), never argv in `command`. Host-only cwd/env/stdin reject S210 on a sandbox target. Errors return { code, message }; common: S216 host exec error, S300 VM boot failed, S200 in-VM failure.",
      "metadata": {},
      "name": "shell::exec",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "description": "Wire request for `shell::exec`.",
        "properties": {
          "args": {
            "default": null,
            "description": "Arguments, in order (all strings). Omit/null to tokenize `command` shell-words style; pass an array (even empty) to use it verbatim.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "command": {
            "description": "Program name (PATH-resolved) or path to an executable, as a string; put arguments in `args`.",
            "type": "string"
          },
          "cwd": {
            "default": null,
            "description": "Working directory (host only, S210 on sandbox); jail-confined like shell::fs::* paths (S215 on escape), must be an existing directory.",
            "type": [
              "string",
              "null"
            ]
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "default": null,
            "description": "Per-call env values (host only, S210 on sandbox); exec-hijacking keys (PATH, IFS, HOME, LD_*/DYLD_*, …) reject the whole call S210.",
            "type": [
              "object",
              "null"
            ]
          },
          "stdin": {
            "default": null,
            "description": "Bytes written to the program's stdin, then EOF (host only, S210 on sandbox); omit to leave stdin at /dev/null.",
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "Where to run the command. Defaults to the host worker; pass `{ kind: \"sandbox\", sandbox_id }` to forward the call to a microVM."
          },
          "timeout_ms": {
            "default": null,
            "description": "Per-call timeout in milliseconds, capped at the configured max; negative or fractional values fall back to the default.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "command"
        ],
        "title": "ExecRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "duration_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "exit_code": {
            "format": "int32",
            "type": [
              "integer",
              "null"
            ]
          },
          "stderr": {
            "type": "string"
          },
          "stderr_truncated": {
            "type": "boolean"
          },
          "stdout": {
            "type": "string"
          },
          "stdout_truncated": {
            "type": "boolean"
          },
          "timed_out": {
            "type": "boolean"
          }
        },
        "required": [
          "duration_ms",
          "stderr",
          "stderr_truncated",
          "stdout",
          "stdout_truncated",
          "timed_out"
        ],
        "title": "ExecResponse",
        "type": "object"
      }
    },
    {
      "description": "Spawn a background job; returns { job_id, argv } immediately. Same payload as shell::exec. Host jobs ignore timeout_ms and run until exit or shell::kill; poll with shell::status, list with shell::list. Spawn-time failures are plain-string messages; later failures surface in shell::status, not here.",
      "metadata": {},
      "name": "shell::exec_bg",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "description": "Wire request for `shell::exec_bg`.",
        "properties": {
          "args": {
            "default": null,
            "description": "Arguments, in order (all strings). Omit/null to tokenize `command` shell-words style; pass an array (even empty) to use it verbatim.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "command": {
            "description": "Program name (PATH-resolved) or path to an executable, as a string; put arguments in `args`.",
            "type": "string"
          },
          "cwd": {
            "default": null,
            "description": "Working directory (host only, S210 on sandbox); jail-confined like shell::fs::* paths (S215 on escape), must be an existing directory.",
            "type": [
              "string",
              "null"
            ]
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "default": null,
            "description": "Per-call env values (host only, S210 on sandbox); exec-hijacking keys (PATH, IFS, HOME, LD_*/DYLD_*, …) reject the whole call S210.",
            "type": [
              "object",
              "null"
            ]
          },
          "stdin": {
            "default": null,
            "description": "Bytes written to the program's stdin, then EOF (host only, S210 on sandbox); omit to leave stdin at /dev/null.",
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "Where to run: the host (default) or { kind: \"sandbox\", sandbox_id }."
          },
          "timeout_ms": {
            "default": null,
            "description": "Per-call timeout in milliseconds; ignored by host background jobs, forwarded to sandbox targets.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "command"
        ],
        "title": "ExecBgRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "argv": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "job_id": {
            "type": "string"
          }
        },
        "required": [
          "argv",
          "job_id"
        ],
        "title": "ExecBgResponse",
        "type": "object"
      }
    },
    {
      "description": "Change a file or directory's permissions. `mode` is an octal string like \"0644\". `uid`/`gid` optionally chown. `recursive: true` walks the tree (symlinks skipped). Returns { entries_changed, path, recursive }. Errors return { code, message }; common: S210 bad mode, S211 not found or not accessible, S215 jail escape.",
      "metadata": {},
      "name": "shell::fs::chmod",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "gid": {
            "default": null,
            "description": "Optional chown to this numeric gid.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "mode": {
            "description": "Octal permission string, e.g. \"0755\".",
            "type": "string"
          },
          "path": {
            "description": "Jail-relative when fs.host_roots are set, else absolute.",
            "type": "string"
          },
          "recursive": {
            "default": false,
            "description": "Apply mode/owner change to all files under the path recursively.",
            "type": "boolean"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          },
          "uid": {
            "default": null,
            "description": "Optional chown to this numeric uid.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "mode",
          "path"
        ],
        "title": "ChmodRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "entries_changed": {
            "description": "Number of filesystem entries whose mode/owner changed.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "default": "",
            "description": "The path that was targeted. Empty for sandbox targets.",
            "type": "string"
          },
          "recursive": {
            "default": false,
            "description": "Whether the change was applied recursively. Host only.",
            "type": "boolean"
          }
        },
        "required": [
          "entries_changed"
        ],
        "title": "ChmodResponse",
        "type": "object"
      }
    },
    {
      "description": "Search file contents for a pattern (Rust regex, RE2-like); `include_glob`/`exclude_glob` filter paths. Returns { matches, truncated }. Errors return { code, message }; common: S217 bad regex, S215 jail escape. coder::search adds context lines and noise filtering.",
      "metadata": {},
      "name": "shell::fs::grep",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "exclude_glob": {
            "default": [],
            "description": "Glob filters excluding file paths from the search.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "ignore_case": {
            "default": false,
            "description": "Match pattern case-insensitively.",
            "type": "boolean"
          },
          "include_glob": {
            "default": [],
            "description": "Glob filters restricting which file paths are searched.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "max_line_bytes": {
            "default": 4096,
            "description": "Skip lines longer than this many bytes (default 4 096).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "max_matches": {
            "default": 10000,
            "description": "Stop collecting matches after this many results (default 10 000).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Jail-relative when fs.host_roots are set, else absolute.",
            "type": "string"
          },
          "pattern": {
            "description": "Rust regex (RE2-like) matched against each line.",
            "type": "string"
          },
          "recursive": {
            "default": true,
            "description": "Descend into subdirectories (default true).",
            "type": "boolean"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path",
          "pattern"
        ],
        "title": "GrepRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FsMatch": {
            "properties": {
              "content": {
                "type": "string"
              },
              "line": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "type": "string"
              }
            },
            "required": [
              "content",
              "line",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "matches": {
            "description": "All collected match locations up to `max_matches`.",
            "items": {
              "$ref": "#/definitions/FsMatch"
            },
            "type": "array"
          },
          "truncated": {
            "description": "True when the result was capped by `max_matches` or `max_line_bytes`.",
            "type": "boolean"
          }
        },
        "required": [
          "matches",
          "truncated"
        ],
        "title": "GrepResponse",
        "type": "object"
      }
    },
    {
      "description": "List the files and subdirectories of a directory on the filesystem. Errors return { code, message }; common: S210 bad path, S211 not found or not accessible, S212 not a directory, S215 jail escape. For paginated or recursive listings prefer coder::list-folder / coder::tree.",
      "metadata": {},
      "name": "shell::fs::ls",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "description": "Request for `shell::fs::ls`.",
        "properties": {
          "path": {
            "description": "Jail-relative when fs.host_roots are set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "LsRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FsEntry": {
            "properties": {
              "is_dir": {
                "type": "boolean"
              },
              "is_symlink": {
                "type": "boolean"
              },
              "mode": {
                "type": "string"
              },
              "mtime": {
                "format": "int64",
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "size": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "is_dir",
              "is_symlink",
              "mode",
              "mtime",
              "name",
              "size"
            ],
            "type": "object"
          }
        },
        "properties": {
          "entries": {
            "description": "Metadata for each entry in the directory.",
            "items": {
              "$ref": "#/definitions/FsEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "entries"
        ],
        "title": "LsResponse",
        "type": "object"
      }
    },
    {
      "description": "Create a directory. `mode` is an octal string like \"0755\". `parents: true` creates missing parents and is idempotent. Returns { created, path, already_existed }. Errors return { code, message }; common: S210 bad mode, S213 exists, S215 jail escape.",
      "metadata": {},
      "name": "shell::fs::mkdir",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "mode": {
            "default": "0755",
            "description": "Octal permission string, e.g. \"0755\".",
            "type": "string"
          },
          "parents": {
            "default": false,
            "description": "Create missing parent directories.",
            "type": "boolean"
          },
          "path": {
            "description": "Jail-relative when fs.host_roots are set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "MkdirRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "already_existed": {
            "default": false,
            "description": "True when the path already existed and `parents` was set. Host only; sandbox targets default this to false (not a signal there).",
            "type": "boolean"
          },
          "created": {
            "description": "True when a new directory was created; false when it already existed (only possible with `parents: true`, which is idempotent).",
            "type": "boolean"
          },
          "path": {
            "default": "",
            "description": "The directory path that was targeted. Empty for sandbox targets.",
            "type": "string"
          }
        },
        "required": [
          "created"
        ],
        "title": "MkdirResponse",
        "type": "object"
      }
    },
    {
      "description": "Move or rename a file or directory. `overwrite: true` allows replacing an existing dst. Returns { moved, src, dst, overwrote }. Errors return { code, message }; common: S211 src not found or not accessible, S213 dst exists, S215 jail escape. coder::move moves several paths per call.",
      "metadata": {},
      "name": "shell::fs::mv",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "dst": {
            "description": "Destination path; jail-relative when fs.host_roots are set, else absolute.",
            "type": "string"
          },
          "overwrite": {
            "default": false,
            "description": "Replace an existing destination instead of returning an error.",
            "type": "boolean"
          },
          "src": {
            "description": "Source path; jail-relative when fs.host_roots are set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "dst",
          "src"
        ],
        "title": "MvRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "dst": {
            "default": "",
            "description": "Destination path. Empty for sandbox targets.",
            "type": "string"
          },
          "moved": {
            "description": "True when the move/rename succeeded.",
            "type": "boolean"
          },
          "overwrote": {
            "default": false,
            "description": "True when an existing destination was overwritten. Host only (sandbox targets default this to false, not a signal there). Best-effort: derived from a pre-rename existence check, so under a concurrent writer racing the destination it may under-report (and an `overwrite:false` move can still replace a file created in that window).",
            "type": "boolean"
          },
          "src": {
            "default": "",
            "description": "Source path. Empty for sandbox targets.",
            "type": "string"
          }
        },
        "required": [
          "moved"
        ],
        "title": "MvResponse",
        "type": "object"
      }
    },
    {
      "description": "Stream a file: returns a ContentRef handle (channel_id/access_key) plus size/mode/mtime, NOT the text — for text use coder::read-file, which returns content inline. Errors return { code, message }; common: S211 not found or not accessible, S212 is a directory, S218 over max_read_bytes, S216 channel/IO error.",
      "metadata": {},
      "name": "shell::fs::read",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "path": {
            "description": "Jail-relative when fs.host_roots are set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "ReadRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ContentDirection": {
            "enum": [
              "read",
              "write"
            ],
            "type": "string"
          },
          "ContentRef": {
            "description": "Wire-identical mirror of `iii_sdk::channels::StreamChannelRef`. The SDK type lacks `JsonSchema` in 0.11.3, which would block typed registration of `shell::fs::write`/`read`.",
            "properties": {
              "access_key": {
                "description": "Secret key that authorises access to this channel.",
                "type": "string"
              },
              "channel_id": {
                "description": "Opaque identifier for the open stream channel.",
                "type": "string"
              },
              "direction": {
                "allOf": [
                  {
                    "$ref": "#/definitions/ContentDirection"
                  }
                ],
                "default": "read",
                "description": "Direction of data flow: \"read\" (consume) or \"write\" (produce)."
              }
            },
            "required": [
              "access_key",
              "channel_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "content": {
            "allOf": [
              {
                "$ref": "#/definitions/ContentRef"
              }
            ],
            "description": "Channel reference for streaming the file content back to the caller."
          },
          "mode": {
            "description": "Octal permission string of the file, e.g. \"0644\".",
            "type": "string"
          },
          "mtime": {
            "description": "Last-modified time as a Unix timestamp (seconds).",
            "format": "int64",
            "type": "integer"
          },
          "size": {
            "description": "File size in bytes at the time of the read.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "content",
          "mode",
          "mtime",
          "size"
        ],
        "title": "ReadResponseWire",
        "type": "object"
      }
    },
    {
      "description": "Delete a file or directory. `recursive: true` is required for a non-empty directory. Returns { removed, path, was_present }. Errors return { code, message }; common: S211 not found or not accessible, S214 dir not empty, S215 jail escape. coder::delete-file removes several paths per call.",
      "metadata": {},
      "name": "shell::fs::rm",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "path": {
            "description": "Jail-relative when fs.host_roots are set, else absolute.",
            "type": "string"
          },
          "recursive": {
            "default": false,
            "description": "Required to delete a non-empty directory.",
            "type": "boolean"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "RmRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "default": "",
            "description": "The path that was targeted. Empty for sandbox targets.",
            "type": "string"
          },
          "removed": {
            "description": "True when the path was removed.",
            "type": "boolean"
          },
          "was_present": {
            "default": false,
            "description": "True when the path existed before removal. Host only; sandbox targets default this to false, which does NOT mean the path was absent.",
            "type": "boolean"
          }
        },
        "required": [
          "removed"
        ],
        "title": "RmResponse",
        "type": "object"
      }
    },
    {
      "description": "Find-and-replace across files. `pattern` is a Rust regex (regex: false for a literal). Provide either `files` or `path` (+ recursive). Returns { results, total_replacements }. Errors return { code, message }; common: S217 bad regex, S211 not found, S215 jail escape.",
      "metadata": {},
      "name": "shell::fs::sed",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "exclude_glob": {
            "default": [],
            "description": "Glob filters excluding file paths from editing.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "files": {
            "default": [],
            "description": "Explicit list of file paths to edit; provide either this or `path`, not both.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "first_only": {
            "default": false,
            "description": "Replace only the first match per file instead of all matches.",
            "type": "boolean"
          },
          "ignore_case": {
            "default": false,
            "description": "Match pattern case-insensitively.",
            "type": "boolean"
          },
          "include_glob": {
            "default": [],
            "description": "Glob filters restricting which file paths are edited.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "path": {
            "default": null,
            "description": "Root path to walk for files; used with `recursive`, `include_glob`, `exclude_glob`.",
            "type": [
              "string",
              "null"
            ]
          },
          "pattern": {
            "description": "Rust regex by default; set regex:false for a literal string.",
            "type": "string"
          },
          "recursive": {
            "default": true,
            "description": "Descend into subdirectories when `path` is set (default true).",
            "type": "boolean"
          },
          "regex": {
            "default": true,
            "description": "Treat pattern as a regex (default true) or a literal string (false).",
            "type": "boolean"
          },
          "replacement": {
            "description": "String to substitute for each match.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "pattern",
          "replacement"
        ],
        "title": "SedRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FsSedFileResult": {
            "properties": {
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "path": {
                "type": "string"
              },
              "replacements": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "path",
              "replacements",
              "success"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "description": "Per-file replacement details.",
            "items": {
              "$ref": "#/definitions/FsSedFileResult"
            },
            "type": "array"
          },
          "total_replacements": {
            "description": "Sum of replacements made across all files.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "results",
          "total_replacements"
        ],
        "title": "SedResponse",
        "type": "object"
      }
    },
    {
      "description": "Stat one file or directory: its type, size, mode, and mtime (paths jail-relative when fs.host_roots are set). Errors return { code, message }; common: S211 not found or not accessible, S215 jail escape. coder::read-file with stat: true additionally reports total_lines.",
      "metadata": {},
      "name": "shell::fs::stat",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "path": {
            "description": "Jail-relative when fs.host_roots are set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "StatRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "is_dir": {
            "type": "boolean"
          },
          "is_symlink": {
            "type": "boolean"
          },
          "mode": {
            "type": "string"
          },
          "mtime": {
            "format": "int64",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "size": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "is_dir",
          "is_symlink",
          "mode",
          "mtime",
          "name",
          "size"
        ],
        "title": "FsEntry",
        "type": "object"
      }
    },
    {
      "description": "Write a file. A string `content` is written inline (host only); large payloads or a sandbox target need a ContentRef { channel_id, access_key, direction } from a write stream channel. `files: [...]` writes several files (host, inline). Errors return { code, message }; common: S210 bad mode or inline-on-sandbox, S218 over max_write_bytes.",
      "metadata": {},
      "name": "shell::fs::write",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ContentDirection": {
            "enum": [
              "read",
              "write"
            ],
            "type": "string"
          },
          "ContentRef": {
            "description": "Wire-identical mirror of `iii_sdk::channels::StreamChannelRef`. The SDK type lacks `JsonSchema` in 0.11.3, which would block typed registration of `shell::fs::write`/`read`.",
            "properties": {
              "access_key": {
                "description": "Secret key that authorises access to this channel.",
                "type": "string"
              },
              "channel_id": {
                "description": "Opaque identifier for the open stream channel.",
                "type": "string"
              },
              "direction": {
                "allOf": [
                  {
                    "$ref": "#/definitions/ContentDirection"
                  }
                ],
                "default": "read",
                "description": "Direction of data flow: \"read\" (consume) or \"write\" (produce)."
              }
            },
            "required": [
              "access_key",
              "channel_id"
            ],
            "type": "object"
          },
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          },
          "WriteContentWire": {
            "anyOf": [
              {
                "description": "Inline UTF-8 text, written verbatim. HOST target only.",
                "type": "string"
              },
              {
                "allOf": [
                  {
                    "$ref": "#/definitions/ContentRef"
                  }
                ],
                "description": "Open write-stream channel ref. Required for sandbox targets and large or streamed payloads."
              }
            ],
            "description": "Write content: a plain string is written inline (host target only); an object is a streaming ContentRef { channel_id, access_key, direction }."
          },
          "WriteFileSpec": {
            "description": "One file in a batch `shell::fs::write` (`files: [...]`).",
            "properties": {
              "content": {
                "allOf": [
                  {
                    "$ref": "#/definitions/WriteContentWire"
                  }
                ],
                "description": "Inline string (recommended) or a streaming ContentRef."
              },
              "mode": {
                "default": "0644",
                "description": "Octal permission string, e.g. \"0644\".",
                "type": "string"
              },
              "parents": {
                "default": false,
                "description": "Create missing parent directories.",
                "type": "boolean"
              },
              "path": {
                "description": "Jail-relative when fs.host_roots are set, else absolute.",
                "type": "string"
              }
            },
            "required": [
              "content",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "content": {
            "anyOf": [
              {
                "$ref": "#/definitions/WriteContentWire"
              },
              {
                "type": "null"
              }
            ],
            "description": "Single-file content: an inline string (host only) or a ContentRef { channel_id, access_key, direction } for an open write stream; omit with `files`."
          },
          "files": {
            "description": "Batch form: write several files in one call. When present, the single-file fields (`path`/`content`/`mode`/`parents`) must be omitted.",
            "items": {
              "$ref": "#/definitions/WriteFileSpec"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "mode": {
            "default": null,
            "description": "Octal permission string for the single-file form, e.g. \"0644\" (the default); omit when using `files`.",
            "type": [
              "string",
              "null"
            ]
          },
          "parents": {
            "default": null,
            "description": "Create missing parent directories (single-file form, default false); omit when using `files`.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "path": {
            "default": null,
            "description": "Single-file form: the path to write. Jail-relative when fs.host_roots are set, else absolute. Omit when using `files`.",
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "title": "WriteRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "WriteFileResult": {
            "properties": {
              "bytes_written": {
                "description": "Bytes written to this file.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "description": "Path of the written file.",
                "type": "string"
              }
            },
            "required": [
              "bytes_written",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "bytes_written": {
            "description": "Bytes written: this file for a single write; the sum across `files` for a batch write.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "files": {
            "default": [],
            "description": "Per-file results for a batch (`files: [...]`) write; empty for a single-file write.",
            "items": {
              "$ref": "#/definitions/WriteFileResult"
            },
            "type": "array"
          },
          "path": {
            "description": "Path written for a single write; empty for a batch (see `files`).",
            "type": "string"
          }
        },
        "required": [
          "bytes_written",
          "path"
        ],
        "title": "WriteResponse",
        "type": "object"
      }
    },
    {
      "description": "Terminate a running background job by job_id (the UUID from shell::exec_bg). Errors return { code, message }; common: S211 no such job, S216 kill/signal delivery failure.",
      "metadata": {},
      "name": "shell::kill",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "job_id": {
            "type": "string"
          }
        },
        "required": [
          "job_id"
        ],
        "title": "KillRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "JobStatus": {
            "enum": [
              "running",
              "finished",
              "killed",
              "failed"
            ],
            "type": "string"
          }
        },
        "properties": {
          "job_id": {
            "type": "string"
          },
          "killed": {
            "type": "boolean"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/definitions/JobStatus"
          }
        },
        "required": [
          "job_id",
          "killed",
          "status"
        ],
        "title": "KillResponse",
        "type": "object"
      }
    },
    {
      "description": "List background jobs (running + recently completed). Takes no arguments.",
      "metadata": {},
      "name": "shell::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "`shell::list` takes no arguments.",
        "title": "ListRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "JobStatus": {
            "enum": [
              "running",
              "finished",
              "killed",
              "failed"
            ],
            "type": "string"
          },
          "JobSummary": {
            "description": "`shell::list` returns one `JobSummary` per record. argv, stdout, and stderr are deliberately omitted: the global JOBS map is process-wide and has no per-caller scope, so any caller could otherwise read every other caller's command line and captured output (which may embed credentials). Full records remain reachable via `shell::status <job_id>` — the random UUID acts as an unguessable capability for that record.",
            "properties": {
              "exit_code": {
                "format": "int32",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "finished_at_ms": {
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "started_at_ms": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "status": {
                "$ref": "#/definitions/JobStatus"
              },
              "stderr_truncated": {
                "type": "boolean"
              },
              "stdout_truncated": {
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "started_at_ms",
              "status",
              "stderr_truncated",
              "stdout_truncated"
            ],
            "type": "object"
          }
        },
        "properties": {
          "count": {
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "jobs": {
            "items": {
              "$ref": "#/definitions/JobSummary"
            },
            "type": "array"
          }
        },
        "required": [
          "count",
          "jobs"
        ],
        "title": "ListResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal: reload the security policy + fs backend on configuration change.",
      "metadata": {
        "internal": true
      },
      "name": "shell::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Event delivered to the internal `shell::on-config-change` handler. A struct (not `Value`) keeps the request schema concrete; the handler re-fetches the configuration id; unknown fields are ignored.",
        "properties": {
          "id": {
            "default": null,
            "description": "Configuration id that changed (advisory; the handler re-fetches the value). Schema-only: kept to publish a typed request schema; the handler ignores it.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "OnConfigChangeEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Ack returned by the internal `shell::on-config-change` handler.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "OnConfigChangeResponse",
        "type": "object"
      }
    },
    {
      "description": "Take back an unattached PTY session whose reconnect token is gone, from the console page that owns it. Refuses a session someone is attached to, and a page that is not the session's own.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::adopt",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Take back a session whose reconnect token is gone.",
        "properties": {
          "after_sequence": {
            "default": 0,
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "output_function_id": {
            "description": "Where output goes next; must belong to the same console page family as the target it replaces.",
            "type": "string"
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "cols",
          "output_function_id",
          "rows",
          "session_id"
        ],
        "title": "AdoptRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "OutputFrame": {
            "properties": {
              "data": {
                "type": "string"
              },
              "sequence": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "data",
              "sequence"
            ],
            "type": "object"
          },
          "SessionStatus": {
            "oneOf": [
              {
                "enum": [
                  "attached",
                  "detached"
                ],
                "type": "string"
              },
              {
                "additionalProperties": false,
                "properties": {
                  "exited": {
                    "properties": {
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "exit_code": {
                        "format": "uint32",
                        "minimum": 0,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "signal": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "exited"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "access_key": {
            "type": "string"
          },
          "cwd": {
            "type": "string"
          },
          "frames": {
            "items": {
              "$ref": "#/definitions/OutputFrame"
            },
            "type": "array"
          },
          "next_sequence": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "reconnect_token": {
            "type": "string"
          },
          "status": {
            "$ref": "#/definitions/SessionStatus"
          },
          "truncated": {
            "type": "boolean"
          }
        },
        "required": [
          "access_key",
          "cwd",
          "frames",
          "next_sequence",
          "reconnect_token",
          "status",
          "truncated"
        ],
        "title": "AttachResponse",
        "type": "object"
      }
    },
    {
      "description": "Attach to a retained PTY session and replay buffered output.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::attach",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "after_sequence": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "output_function_id": {
            "type": "string"
          },
          "reconnect_token": {
            "type": "string"
          },
          "request_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "after_sequence",
          "cols",
          "output_function_id",
          "reconnect_token",
          "rows",
          "session_id"
        ],
        "title": "AttachRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "OutputFrame": {
            "properties": {
              "data": {
                "type": "string"
              },
              "sequence": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "data",
              "sequence"
            ],
            "type": "object"
          },
          "SessionStatus": {
            "oneOf": [
              {
                "enum": [
                  "attached",
                  "detached"
                ],
                "type": "string"
              },
              {
                "additionalProperties": false,
                "properties": {
                  "exited": {
                    "properties": {
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "exit_code": {
                        "format": "uint32",
                        "minimum": 0,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "signal": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "exited"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "access_key": {
            "type": "string"
          },
          "cwd": {
            "type": "string"
          },
          "frames": {
            "items": {
              "$ref": "#/definitions/OutputFrame"
            },
            "type": "array"
          },
          "next_sequence": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "reconnect_token": {
            "type": "string"
          },
          "status": {
            "$ref": "#/definitions/SessionStatus"
          },
          "truncated": {
            "type": "boolean"
          }
        },
        "required": [
          "access_key",
          "cwd",
          "frames",
          "next_sequence",
          "reconnect_token",
          "status",
          "truncated"
        ],
        "title": "AttachResponse",
        "type": "object"
      }
    },
    {
      "description": "Terminate and close a PTY session.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::close",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "session_id"
        ],
        "title": "CloseRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "closed": {
            "type": "boolean"
          }
        },
        "required": [
          "closed"
        ],
        "title": "CloseResponse",
        "type": "object"
      }
    },
    {
      "description": "Detach a browser output target while retaining its PTY session.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::detach",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "session_id"
        ],
        "title": "DetachRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SessionStatus": {
            "oneOf": [
              {
                "enum": [
                  "attached",
                  "detached"
                ],
                "type": "string"
              },
              {
                "additionalProperties": false,
                "properties": {
                  "exited": {
                    "properties": {
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "exit_code": {
                        "format": "uint32",
                        "minimum": 0,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "signal": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "exited"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "status": {
            "$ref": "#/definitions/SessionStatus"
          }
        },
        "required": [
          "status"
        ],
        "title": "DetachResponse",
        "type": "object"
      }
    },
    {
      "description": "Open a persistent host PTY running the user's login shell, or the program named in `program`.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::open",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "args": {
            "default": null,
            "description": "argv for `program`. Ignored without `program` (a login shell takes no arguments here).",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "cwd": {
            "type": "string"
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "default": null,
            "description": "Extra environment on top of what the worker forwards; exec-hijacking keys (PATH, LD_*, DYLD_*, BASH_ENV, …) fail the call.",
            "type": [
              "object",
              "null"
            ]
          },
          "output_function_id": {
            "type": "string"
          },
          "program": {
            "default": null,
            "description": "Program to run instead of the user's login shell (an agent CLI, REPL, TUI), resolved on the worker's PATH.",
            "type": [
              "string",
              "null"
            ]
          },
          "request_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "cols",
          "cwd",
          "output_function_id",
          "rows"
        ],
        "title": "OpenRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "cwd": {
            "type": "string"
          },
          "pid": {
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "program": {
            "description": "The program the session runs; absent for a login shell.",
            "type": [
              "string",
              "null"
            ]
          },
          "reconnect_token": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "cwd",
          "reconnect_token",
          "session_id"
        ],
        "title": "OpenResponse",
        "type": "object"
      }
    },
    {
      "description": "Resize a PTY session in terminal columns and rows.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::resize",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "cols",
          "rows",
          "session_id"
        ],
        "title": "ResizeRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "cols",
          "rows"
        ],
        "title": "ResizeResponse",
        "type": "object"
      }
    },
    {
      "description": "List live PTY sessions with program, cwd, sequence, replay buffer size, and output target. Diagnostics only — no credentials.",
      "metadata": {},
      "name": "shell::pty::sessions",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "SessionsRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SessionStatus": {
            "oneOf": [
              {
                "enum": [
                  "attached",
                  "detached"
                ],
                "type": "string"
              },
              {
                "additionalProperties": false,
                "properties": {
                  "exited": {
                    "properties": {
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "exit_code": {
                        "format": "uint32",
                        "minimum": 0,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "signal": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "exited"
                ],
                "type": "object"
              }
            ]
          },
          "SessionSummary": {
            "description": "One live session, without credentials: enough to tell a terminal that shows nothing apart from one that was never fed. A page that counts the frames it applied compares its own count against `sequence` — equal means the frames arrived and the browser is at fault, far behind means delivery is.",
            "properties": {
              "cwd": {
                "type": "string"
              },
              "frame_bytes": {
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "frames": {
                "description": "Frames still replayable from the ring buffer.",
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "output_function_id": {
                "description": "Where output is being delivered; absent while detached.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "pid": {
                "format": "uint32",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "program": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "sequence": {
                "description": "Sequence number of the last frame the session produced.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "session_id": {
                "type": "string"
              },
              "status": {
                "$ref": "#/definitions/SessionStatus"
              },
              "truncated": {
                "description": "Whether the buffer has dropped frames a full replay would need.",
                "type": "boolean"
              },
              "ui": {
                "description": "The console page family this session belongs to — the `<name>` in `iii::<name>-ui::pty-output::console-<browser>`. Present whether or not anyone is attached, because it is what a page uses to recognise its own orphaned session; it names a page, never a browser.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "cwd",
              "frame_bytes",
              "frames",
              "sequence",
              "session_id",
              "status",
              "truncated"
            ],
            "type": "object"
          }
        },
        "properties": {
          "sessions": {
            "items": {
              "$ref": "#/definitions/SessionSummary"
            },
            "type": "array"
          }
        },
        "required": [
          "sessions"
        ],
        "title": "SessionsResponse",
        "type": "object"
      }
    },
    {
      "description": "Write base64-encoded keyboard input to a PTY session.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::write",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "data": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "data",
          "session_id"
        ],
        "title": "WriteRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "written": {
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "written"
        ],
        "title": "WriteResponse",
        "type": "object"
      }
    },
    {
      "description": "Fetch the full record (status, exit_code, timing) of a background job by job_id. Errors return { code, message }; common: S211 no such job.",
      "metadata": {},
      "name": "shell::status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "job_id": {
            "type": "string"
          }
        },
        "required": [
          "job_id"
        ],
        "title": "StatusRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "JobRecord": {
            "properties": {
              "argv": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "exit_code": {
                "format": "int32",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "finished_at_ms": {
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "started_at_ms": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "status": {
                "$ref": "#/definitions/JobStatus"
              },
              "stderr": {
                "type": "string"
              },
              "stderr_truncated": {
                "type": "boolean"
              },
              "stdout": {
                "type": "string"
              },
              "stdout_truncated": {
                "type": "boolean"
              }
            },
            "required": [
              "argv",
              "id",
              "started_at_ms",
              "status",
              "stderr",
              "stderr_truncated",
              "stdout",
              "stdout_truncated"
            ],
            "type": "object"
          },
          "JobStatus": {
            "enum": [
              "running",
              "finished",
              "killed",
              "failed"
            ],
            "type": "string"
          }
        },
        "properties": {
          "job": {
            "$ref": "#/definitions/JobRecord"
          }
        },
        "required": [
          "job"
        ],
        "title": "StatusResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal: add a newly created project root to the active Harness turn after a confirmed workspace proposal.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::adopt-root",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "root": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "turn_id": {
            "type": "string"
          }
        },
        "required": [
          "root",
          "session_id",
          "turn_id"
        ],
        "title": "AdoptRootInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "recorded": {
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "recorded"
        ],
        "title": "AdoptRootOutput",
        "type": "object"
      }
    },
    {
      "description": "Fetch one turn of a session's change history: every file it changed, the change kind, the function that made it, the sub-agent that did it when one did, the file's pre-image (revision and body up to 64 KiB each, 1 MiB per response) and, when a later turn kept it, the body the turn left behind (`after`), so the exact patch of that turn can be shown later. Omit turn_id for the newest turn.",
      "metadata": {},
      "name": "shell::turns::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "turn_id": {
            "default": null,
            "description": "Omit for the newest turn.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "session_id"
        ],
        "title": "GetInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "AgentRef": {
            "properties": {
              "name": {
                "description": "The `subagent_display` name the harness stamped, when it did.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "session_id": {
                "type": "string"
              }
            },
            "required": [
              "session_id"
            ],
            "type": "object"
          },
          "FileRecord": {
            "properties": {
              "after": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/PreImage"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The body after this turn's last write, when a later turn kept it as its own pre-image. Never stored; filled in by `shell::turns::get`. Absent means the working copy is the body after this turn."
              },
              "after_revision": {
                "description": "Revision after the last write, when the file could be read back.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "agent": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/AgentRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The sub-agent session that made the change, when it was not the turn's own session. Children's changes are recorded under the turn of the top-level agent that spawned them, so one turn reads as one unit of work whoever did the typing."
              },
              "before": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/PreImage"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "cause": {
                "description": "Function id that made the change.",
                "type": "string"
              },
              "first_seen": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "from": {
                "description": "Set for a `moved` record: where the file came from.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "kind": {
                "description": "`created` | `modified` | `deleted` | `moved`.",
                "type": "string"
              },
              "last_seen": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "description": "Absolute path on the host.",
                "type": "string"
              },
              "root": {
                "description": "The session's workspace root at the time, when the harness named one.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "cause",
              "first_seen",
              "kind",
              "last_seen",
              "path"
            ],
            "type": "object"
          },
          "PreImage": {
            "properties": {
              "binary": {
                "type": "boolean"
              },
              "content": {
                "description": "UTF-8 body before the write. Never stored in the record; filled in by `shell::turns::get` from the blob store, up to `MAX_PRE_IMAGE_BYTES`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "missing": {
                "description": "The path did not exist before the call.",
                "type": "boolean"
              },
              "revision": {
                "description": "`sha256:<hex>` of the bytes before the write; absent when the file did not exist or could not be read. Doubles as the blob key.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "stored": {
                "description": "A blob for `revision` exists in the store.",
                "type": "boolean"
              },
              "truncated": {
                "description": "The body was larger than the cap (not kept) or did not fit the response budget; the revision still identifies it.",
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "TurnRecord": {
            "properties": {
              "ended_at": {
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "files": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/FileRecord"
                },
                "type": "array"
              },
              "started_at": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "title": {
                "description": "The first characters of the message that started the turn, from the harness `turn-started` event; what a timeline calls the turn.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "turn_id": {
                "type": "string"
              }
            },
            "required": [
              "started_at",
              "turn_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "session_id": {
            "type": "string"
          },
          "turn": {
            "anyOf": [
              {
                "$ref": "#/definitions/TurnRecord"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "session_id"
        ],
        "title": "GetOutput",
        "type": "object"
      }
    },
    {
      "description": "List the turns of a harness session with the files each one changed through shell or coder functions, newest first. Paths and kinds only; use shell::turns::get for the pre-images.",
      "metadata": {},
      "name": "shell::turns::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "ListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "AgentRef": {
            "properties": {
              "name": {
                "description": "The `subagent_display` name the harness stamped, when it did.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "session_id": {
                "type": "string"
              }
            },
            "required": [
              "session_id"
            ],
            "type": "object"
          },
          "FileHead": {
            "properties": {
              "agent": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/AgentRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The sub-agent that made the change, when it was not the turn's own session."
              },
              "kind": {
                "type": "string"
              },
              "path": {
                "type": "string"
              },
              "root": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "path"
            ],
            "type": "object"
          },
          "TurnSummary": {
            "properties": {
              "ended_at": {
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "file_count": {
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "files": {
                "description": "Paths with their change kind, without bodies.",
                "items": {
                  "$ref": "#/definitions/FileHead"
                },
                "type": "array"
              },
              "started_at": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "title": {
                "description": "First characters of the message that started the turn.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "turn_id": {
                "type": "string"
              }
            },
            "required": [
              "file_count",
              "files",
              "started_at",
              "turn_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "session_id": {
            "type": "string"
          },
          "turns": {
            "description": "Newest first.",
            "items": {
              "$ref": "#/definitions/TurnSummary"
            },
            "type": "array"
          }
        },
        "required": [
          "session_id",
          "turns"
        ],
        "title": "ListOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: records a file change made through a shell or coder call into the session's durable change history. Observes only; always continues.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-post-trigger",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "HookCall": {
            "properties": {
              "arguments": {
                "default": null
              },
              "function_id": {
                "type": "string"
              }
            },
            "required": [
              "function_id"
            ],
            "type": "object"
          },
          "HookResult": {
            "properties": {
              "is_error": {
                "default": false,
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "description": "The part of the harness hook envelope this module reads.",
        "properties": {
          "call": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookCall"
              },
              {
                "type": "null"
              }
            ]
          },
          "metadata": {
            "default": null
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookResult"
              },
              {
                "type": "null"
              }
            ]
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "HookInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "decision": {
            "type": "string"
          }
        },
        "required": [
          "decision"
        ],
        "title": "HookOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: keeps the pre-image of a file a shell or coder call is about to change. Observes only; always continues.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-pre-trigger",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "HookCall": {
            "properties": {
              "arguments": {
                "default": null
              },
              "function_id": {
                "type": "string"
              }
            },
            "required": [
              "function_id"
            ],
            "type": "object"
          },
          "HookResult": {
            "properties": {
              "is_error": {
                "default": false,
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "description": "The part of the harness hook envelope this module reads.",
        "properties": {
          "call": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookCall"
              },
              {
                "type": "null"
              }
            ]
          },
          "metadata": {
            "default": null
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookResult"
              },
              {
                "type": "null"
              }
            ]
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "HookInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "decision": {
            "type": "string"
          }
        },
        "required": [
          "decision"
        ],
        "title": "HookOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: closes a turn in the session's change history.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-turn-completed",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ParentRef": {
            "properties": {
              "session_id": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              },
              "turn_id": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "properties": {
          "message_preview": {
            "default": null,
            "description": "First characters of the message that started the turn.",
            "type": [
              "string",
              "null"
            ]
          },
          "parent": {
            "anyOf": [
              {
                "$ref": "#/definitions/ParentRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "The spawning turn, for a sub-agent session."
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "TurnEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "Ack",
        "type": "object"
      }
    },
    {
      "description": "Internal: opens a turn in the session's change history.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-turn-started",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ParentRef": {
            "properties": {
              "session_id": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              },
              "turn_id": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "properties": {
          "message_preview": {
            "default": null,
            "description": "First characters of the message that started the turn.",
            "type": [
              "string",
              "null"
            ]
          },
          "parent": {
            "anyOf": [
              {
                "$ref": "#/definitions/ParentRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "The spawning turn, for a sub-agent session."
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "TurnEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "Ack",
        "type": "object"
      }
    },
    {
      "description": "Undo the file changes one turn made, from the pre-images kept in the change history: created files are removed, modified and deleted files get their earlier body back, moved files return to their original path. Pass `paths` to revert a subset. Per-file results say what happened; bodies that were never stored are reported, not guessed.",
      "metadata": {},
      "name": "shell::turns::revert",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "paths": {
            "default": null,
            "description": "Restrict the revert to these absolute paths; omit for every file the turn changed.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          },
          "turn_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "turn_id"
        ],
        "title": "RevertInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "RevertFileResult": {
            "properties": {
              "action": {
                "description": "`restored` (body written back), `removed`, `moved-back`, or `skipped` (nothing to undo, e.g. a delete of a file that never existed).",
                "type": "string"
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "kind": {
                "description": "The recorded change kind that was undone.",
                "type": "string"
              },
              "path": {
                "type": "string"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "action",
              "kind",
              "path",
              "success"
            ],
            "type": "object"
          }
        },
        "properties": {
          "failed": {
            "description": "Files whose revert failed; their `error` says why.",
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "results": {
            "items": {
              "$ref": "#/definitions/RevertFileResult"
            },
            "type": "array"
          },
          "reverted": {
            "description": "Files whose revert succeeded (skips included).",
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "session_id": {
            "type": "string"
          },
          "turn_id": {
            "type": "string"
          }
        },
        "required": [
          "failed",
          "results",
          "reverted",
          "session_id",
          "turn_id"
        ],
        "title": "RevertOutput",
        "type": "object"
      }
    },
    {
      "description": "Serve the shell worker's injected console UI assets (content function for its console:script / console:style triggers).",
      "metadata": {
        "internal": true
      },
      "name": "shell::ui-content",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Input of the content function: the console asks for one asset by path.",
        "properties": {
          "path": {
            "description": "The asset path from the trigger config (e.g. `state/page.js`).",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "UiContentInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Output of the content function.",
        "properties": {
          "content": {
            "description": "The asset source, verbatim.",
            "type": "string"
          },
          "content_type": {
            "description": "MIME type the console should serve the asset with.",
            "type": "string"
          }
        },
        "required": [
          "content",
          "content_type"
        ],
        "title": "UiContentResult",
        "type": "object"
      }
    },
    {
      "description": "Console-only: the explorer page's stored state for one pane (`key` is the console pane id; `legacy_key`, the workspace tab id, is read when the pane has nothing stored). `state` is null when nothing is stored. Kept under the worker's data directory, per pane.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::ui-state::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "key": {
            "description": "The pane's state key (the console pane id).",
            "type": "string"
          },
          "legacy_key": {
            "default": null,
            "description": "The workspace tab id: saves made before panes had ids are read from here when `key` has nothing stored.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "key"
        ],
        "title": "GetRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "key": {
            "type": "string"
          },
          "state": {
            "description": "The stored object, or null when nothing is stored."
          }
        },
        "required": [
          "key"
        ],
        "title": "GetResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only: replace the explorer page's stored state for one pane. Writes only that pane's file, atomically, so panes never clobber each other.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::ui-state::set",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "key": {
            "type": "string"
          },
          "state": {
            "description": "The pane's whole state; replaces what was stored."
          }
        },
        "required": [
          "key",
          "state"
        ],
        "title": "SetRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "bytes": {
            "description": "Size of the stored document.",
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "key": {
            "type": "string"
          }
        },
        "required": [
          "bytes",
          "key"
        ],
        "title": "SetResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only workspace picker control plane: list child directories under an existing host directory. Returns canonical paths and never returns files.",
      "metadata": {},
      "name": "shell::workspace::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "page_size": {
            "default": null,
            "format": "uint",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "WorkspaceListRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "WorkspaceEntry": {
            "properties": {
              "kind": {
                "$ref": "#/definitions/WorkspaceEntryKind"
              },
              "name": {
                "type": "string"
              },
              "path": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "name",
              "path"
            ],
            "type": "object"
          },
          "WorkspaceEntryKind": {
            "enum": [
              "dir"
            ],
            "type": "string"
          }
        },
        "properties": {
          "entries": {
            "items": {
              "$ref": "#/definitions/WorkspaceEntry"
            },
            "type": "array"
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "entries",
          "path"
        ],
        "title": "WorkspaceListResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only: one bounded byte range of a file, base64 (at most 4 MiB raw per call), so a page can stream a large image without one oversized frame.",
      "metadata": {
        "internal": true
      },
      "name": "shell::workspace::read-bytes",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "length": {
            "default": null,
            "description": "Bytes to return; omitted or over the cap = `READ_BYTES_MAX_CHUNK`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "offset": {
            "default": 0,
            "description": "First byte of the range (default 0).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Absolute path, or relative to the primary root (same rules as coder::read-file).",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "WorkspaceReadBytesRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "content": {
            "description": "The chunk, base64 (standard alphabet, padded).",
            "type": "string"
          },
          "eof": {
            "description": "True when `offset + length` reached the end of the file.",
            "type": "boolean"
          },
          "length": {
            "description": "Raw bytes in this chunk (before base64).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "mtime": {
            "description": "Last-modified time as a Unix epoch in seconds.",
            "format": "int64",
            "type": "integer"
          },
          "offset": {
            "description": "Offset the returned range starts at.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Canonical absolute path.",
            "type": "string"
          },
          "size": {
            "description": "Size of the whole file in bytes.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "content",
          "eof",
          "length",
          "mtime",
          "offset",
          "path",
          "size"
        ],
        "title": "WorkspaceReadBytesResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only workspace picker control plane: return canonical host directory anchors that an operator can browse before choosing a per-session working directory.",
      "metadata": {},
      "name": "shell::workspace::roots",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "WorkspaceRootsRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "roots": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "roots"
        ],
        "title": "WorkspaceRootsResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only workspace picker control plane: validate that `path` is an existing host directory and return its canonical path.",
      "metadata": {},
      "name": "shell::workspace::validate",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "WorkspaceValidateRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "WorkspaceValidateResponse",
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "Fires when anything under the watched directory changes, whoever changed it — bind with config: { path } naming the directory (jail-checked like every coder::* path). Ignored paths (git-ignored; outside a repository data/, config/, .iii/, node_modules/) are skipped unless config also sets include_ignored: true.",
      "invocation_schema": {},
      "metadata": {},
      "name": "shell::changed",
      "return_schema": {}
    }
  ]
}
```
