skip to content
$worker

editor

v0.1.4-experimental

A shared code workspace — open buffers, a file tree, unified diffs, fuzzy find and conflict-safe saves that an agent and a person see the same view of, plus a console editor page.

iiiverified
35 installs9 in 7d0 today
install
$iii worker add editor@0.1.4-experimental
binarylicense: Apache-2.0editordiffgitcodereview
  • macOS: arm64 · x64
  • Linux: arm64 · armv7 · x64
  • Windows: arm64 · x64 · x86

exact versions are immutable; binary and bundle artifacts are digest-pinned.

agent-ready brief for v0.1.4-experimental
install + config + dependencies + readme + api reference, all in one place. fetch as agent-context.md for an llm to consume.
the same content rendered as discrete blocks below is exposed as a single markdown document at /workers/editor.md?version=0.1.4-experimental. paste it into an llm prompt or pipe it through curl from a worker.

install

install
$iii worker add editor@0.1.4-experimental

configuration

iii-config.yaml
- workers:

dependencies

dependencies3

readme

README.md

editor

The editor page: an agent's turn on the left, the changes it made in the middle, and the diff of one of them on the right

A code workspace that an agent and a person share. Open a folder, and the buffers you have open, the folders you have expanded, and the version each buffer was read at are one record on the bus — so the file an agent opens appears in your tabs, and the file you open is one the agent can see.

The unit is a folder, not a repository. The tree, the tabs, the editor and the finder all work in a plain directory; git adds a branch label and change marks when the root happens to be a repo, and nothing else changes when it is not.

When an agent is working, its edits land in the changes tab as they happen: one group per turn with the files it touched and the lines it moved, and the diff of any one of them a click away. Nothing is polled — the worker observes every filesystem call the agent makes and pushes an editor::changed event, so a write made by anything shows up, including tools that never call this worker.

It opens no files itself. Reads, writes, moves, listings and git all go through the shell worker, so shell's jail and denylist are the only filesystem boundary; the workspace record lives in state. What editor adds is the model on top: diffing, ranking paths, refusing a stale write, and keeping open buffers correct when a folder moves under them.

Install

iii worker add editor
iii worker add shell   # required — editor has no filesystem access of its own
iii worker add state   # required — the workspace record lives here

Companion workers

Worker Why
shell Required. Every read, write, move, listing (coder::tree) and git invocation. Its fs.host_roots jail governs which paths editor can reach.
state Required. Holds the active root and one session per project (open buffers, expanded folders).
console Optional. Renders the #/ext/editor page and the editor::* chat cards.

Quickstart

use iii_sdk::protocol::TriggerRequest;
use iii_sdk::{register_worker, InitOptions};
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let iii = register_worker("ws://localhost:49134", InitOptions::default());
    let call = |id: &str, payload| iii.trigger(TriggerRequest {
        function_id: id.into(), payload, action: None, timeout_ms: Some(30_000),
    });

    // Any folder. No repository required.
    call("editor::workspace::open", json!({ "root": "/srv/app" })).await?;

    let file = call("editor::open", json!({ "path": "src/main.rs" })).await?;
    let edited = file["content"].as_str().unwrap().replace("TODO", "done");

    // Show the change before making it.
    let preview = call("editor::diff", json!({
        "before": file["content"], "after": edited, "path": "src/main.rs",
    })).await?;
    println!("{}", preview["patch"].as_str().unwrap());

    // The version from the open is what makes this safe: if anything else
    // touched the file in between, nothing is written and the divergence
    // comes back as a patch. `expected_mtime` also works and is still
    // honoured, but it cannot see a write that landed inside the same second.
    let saved = call("editor::save", json!({
        "path": "src/main.rs", "content": edited, "expected_version": file["version"],
    })).await?;
    if saved["conflict"] == true {
        println!("refused:\n{}", saved["conflict_patch"].as_str().unwrap());
    }
    Ok(())
}

Functions

Function Does
editor::workspace::open Point the workspace at a folder. Returns the buffers and expanded folders remembered for it.
editor::workspace::get The active root, open buffers, and expanded folders — what every surface sees.
editor::tree List a folder, with the workspace's expansion state. The walk, the noise-folder excludes and the jail are shell's.
editor::open Read a text file and record it as an open buffer, with its language id and the content version to save against.
editor::save Whole-file write, refused when the file changed since the open it started from. The refusal carries the disk-vs-yours diff.
editor::buffers::list Files currently open.
editor::buffers::close Close one buffer. The file on disk is untouched.
editor::move Move or rename, then rewrite every open buffer and expanded folder at or under the path.
editor::create Create a file or folder, parents included. A file may be seeded with content.
editor::delete Remove a path and close any buffer it held.
editor::find Fuzzy file finder, ranked basename-first. Candidates from git in a repo, from the folder listing otherwise.
editor::search Search file contents across the workspace, grouped by file. shell's recursive grep, shaped for a results panel.
editor::diff Unified patch between two texts. Pure — no path is read, so it works on content that is not on disk yet.
editor::git::status Branch, upstream, ahead/behind, and one typed row per changed path.
editor::git::hunks What changed in one file: the rendered patch plus its line ranges.
editor::git::show A file's contents at a revision, HEAD by default. Pair it with the working copy to render a diff without parsing a patch.
editor::git::commit Stage and commit. committed: false when there was nothing staged.
editor::git::sync Fetch, fast-forward pull, or push, with ahead/behind after.
editor::git::stash Stash the working tree, or pop the most recent stash.
editor::git::undo-commit reset --soft HEAD~1, returning the SHA and message undone.

Pull is --ff-only on purpose: a merge under open buffers is how an editor ends up showing a conflicted tree nobody asked for. A repository that needs interactive credentials will hit git_timeout_ms rather than hang, because shell::exec owns the process.

Two are worth calling out. editor::diff is the one an agent reaches for most: it can show exactly what a write will change before making it. And editor::move exists because shell::fs::mv alone leaves open buffers pointing at the old path — the next save then writes them back there, silently recreating the folder that was just moved.

Custom trigger types

Trigger type Fires when Payload
editor::changed A file in the workspace changed, whoever changed it path, cause (the function id that did it), kind (created | modified | deleted | unknown), added, removed, patch, truncated, root

The event is how a surface follows an agent without polling. It does not require the agent to cooperate: the worker binds a harness::hook::post-trigger hook on the shell::* and coder::* write paths, so an edit made by anything becomes an event. The hook is advisory and fail-open — it never delays or denies the write that produced it.

use iii_sdk::protocol::RegisterTriggerInput;

iii.register_trigger(RegisterTriggerInput {
    trigger_type: "editor::changed".to_string(),
    function_id: "my-worker::on-edit".to_string(),
    config: serde_json::json!({}),
    metadata: None,
})?;

Bindings take no config. Delivery is fire-and-forget: a slow or absent subscriber is logged and skipped. patch is capped at 16 KiB with truncated set — ask editor::git::hunks when you need the whole thing.

Console page

#/ext/editor is a view over the same workspace: a collapsible file tree on the left with a files/search switch, tabs on the right, and a save that surfaces the conflict guard as a dialog. The open file has its own view strip — read, edit, preview on a markdown file, unsaved while there is something unsaved, and head for the diff against the last commit. A status line under it carries the path, language, line count, git deltas, saved state and the most recent observed edit. A git strip along the bottom does commit, fetch, pull, push, stash and pop. Folder expansion round-trips through the worker, so it survives a reload and both surfaces agree on it.

Nothing is polled: one read on mount seeds the git overlay, and after that the page reacts to editor::changed and to the workspace's own state scope. So a file an agent edits lights up as the edit lands and an open tab you have not typed in reloads under you. A tab you have edited is never reloaded; it is flagged, and the conflict guard decides the outcome.

editor::* calls also render as themselves in chat and traces: a diff as a diff, a save as a file card with its line counts.

Files and diffs are drawn with @pierre/diffs — real line numbers, a syntax theme, and its own add/delete colouring, all inside its own shadow root. editor::diff and editor::git::hunks already return unified patch text, which is exactly what it parses. Editing stays on the console's shared Monaco CodeEditor: it is the one editing surface, and the SOP forbids bundling a second editor to get chrome back. Bundling @pierre/diffs as-is costs 10.3 MB — over the console's 8 MiB per-asset cap, and almost all of it shiki's full grammar and theme catalogs — so ui/build.mjs narrows those catalogs to what this worker opens and asserts both the size budget and that highlighting still works.

Configuration

Runtime config lives in the configuration worker under id editor, so the console's Workers tab can edit it and every field hot-reloads — handlers read the live snapshot per call, and nothing needs a restart. The block below is what gets seeded when nothing is stored yet, and --config takes a file in that shape as an optional one-time seed that never overwrites a stored value. The committed config.yaml is not that file: it is a bare engine config (workers: []) for running this worker from source, and it would be rejected as a worker seed.

If the configuration worker cannot be reached at boot, the worker retries, then starts on the --config seed rather than exiting — with a warning naming which numbers are in force — and keeps asking in the background until the authoritative value lands. The built-in defaults are used only when nothing was seeded either.

Every field is a bound. Nothing here grants access — that is shell's config.

max_diff_bytes: 2000000     # per side of editor::diff, bytes
diff_context_lines: 3       # editor::diff's default context; hunks defaults to 0
find_limit: 50              # rows returned by editor::find
max_find_candidates: 50000  # paths scanned per editor::find call
max_file_bytes: 2000000     # largest file editor::open will pull back
search_max_matches: 2000    # matching lines editor::search collects
git_timeout_ms: 15000       # per git invocation handed to shell::exec

Local development & testing

iii -c config.yaml   # a bare engine (workers: []) so this worker runs from source
cargo run --release -- --url ws://127.0.0.1:49134
cargo test

--url also reads III_URL, which the worker manager injects — inside a sandbox the engine is on the VM's gateway, never on its loopback.

The console assets are built from ui/ by build.rs. For the hot-reload loop:

cd ui && pnpm install && pnpm watch    # esbuild --watch → dist/
III_EDITOR_UI_WATCH=1 cargo run        # re-registers each changed asset

api reference (json)

agent-api-reference.json
{
  "functions": [
    {
      "description": "Close one open buffer. The file on disk is untouched.",
      "metadata": {},
      "name": "editor::buffers::close",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "Root-relative path of the buffer to close.",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "BufferCloseInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Buffer": {
            "description": "One open file. `mtime` is what the conflict guard compares against, so it is part of the shared record rather than per-surface bookkeeping — two surfaces editing one file must agree on which version they started from.",
            "properties": {
              "language": {
                "description": "Monaco language id for the path.",
                "type": "string"
              },
              "mtime": {
                "description": "Last-modified time this buffer was read at, Unix seconds.",
                "format": "int64",
                "type": "integer"
              },
              "path": {
                "description": "Path relative to the workspace root.",
                "type": "string"
              },
              "version": {
                "default": "",
                "description": "Opaque version of the content this buffer was read at — the same fact `mtime` carries, in the form that survives two writes inside one second. A surface saves against it by sending it as `expected_version`.\n\nDefaulted so a session persisted before this field existed still loads. Empty means \"unknown\": a surface holding an empty version has only the mtime to save against, which is the behaviour it had all along.",
                "type": "string"
              }
            },
            "required": [
              "language",
              "mtime",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "buffers": {
            "items": {
              "$ref": "#/definitions/Buffer"
            },
            "type": "array"
          },
          "closed": {
            "description": "False when nothing was open at that path.",
            "type": "boolean"
          },
          "root": {
            "type": "string"
          }
        },
        "required": [
          "buffers",
          "closed",
          "root"
        ],
        "title": "BufferCloseOutput",
        "type": "object"
      }
    },
    {
      "description": "Files currently open in the workspace.",
      "metadata": {},
      "name": "editor::buffers::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "EmptyInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Buffer": {
            "description": "One open file. `mtime` is what the conflict guard compares against, so it is part of the shared record rather than per-surface bookkeeping — two surfaces editing one file must agree on which version they started from.",
            "properties": {
              "language": {
                "description": "Monaco language id for the path.",
                "type": "string"
              },
              "mtime": {
                "description": "Last-modified time this buffer was read at, Unix seconds.",
                "format": "int64",
                "type": "integer"
              },
              "path": {
                "description": "Path relative to the workspace root.",
                "type": "string"
              },
              "version": {
                "default": "",
                "description": "Opaque version of the content this buffer was read at — the same fact `mtime` carries, in the form that survives two writes inside one second. A surface saves against it by sending it as `expected_version`.\n\nDefaulted so a session persisted before this field existed still loads. Empty means \"unknown\": a surface holding an empty version has only the mtime to save against, which is the behaviour it had all along.",
                "type": "string"
              }
            },
            "required": [
              "language",
              "mtime",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "buffers": {
            "items": {
              "$ref": "#/definitions/Buffer"
            },
            "type": "array"
          },
          "root": {
            "type": "string"
          }
        },
        "required": [
          "buffers",
          "root"
        ],
        "title": "BuffersOutput",
        "type": "object"
      }
    },
    {
      "description": "Recent file changes in the workspace, newest first, one entry per path. Recorded by the observer for every change however it was made, so it answers what happened while nothing was watching. Each entry carries the patch, the line counts, the function that performed the write, and the harness session and turn it happened in.",
      "metadata": {},
      "name": "editor::changes",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "EmptyInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ChangeRecord": {
            "description": "One recorded change, as `editor::changes` returns it. Mirrors the `editor::changed` event: the log is those events, kept.",
            "properties": {
              "added": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "cause": {
                "description": "Function that performed the write, e.g. `shell::fs::write`.",
                "type": "string"
              },
              "kind": {
                "description": "`created`, `modified`, `deleted`, or `moved`.",
                "type": "string"
              },
              "patch": {
                "default": "",
                "description": "Unified patch for the change, empty when there was nothing to compare.",
                "type": "string"
              },
              "path": {
                "description": "Path relative to the workspace root the change was recorded against.",
                "type": "string"
              },
              "removed": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "root": {
                "default": "",
                "type": "string"
              },
              "session_id": {
                "description": "The harness session and turn the write happened in, absent when it happened outside a turn.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "truncated": {
                "default": false,
                "type": "boolean"
              },
              "turn_id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "added",
              "cause",
              "kind",
              "path",
              "removed"
            ],
            "type": "object"
          }
        },
        "description": "Response of `editor::changes`.",
        "properties": {
          "changes": {
            "description": "Newest first, one entry per path.",
            "items": {
              "$ref": "#/definitions/ChangeRecord"
            },
            "type": "array"
          }
        },
        "required": [
          "changes"
        ],
        "title": "ChangesView",
        "type": "object"
      }
    },
    {
      "description": "Create a file or folder in the workspace, with parents as needed. A file may be seeded with content.",
      "metadata": {},
      "name": "editor::create",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "EntryKind": {
            "description": "Create a file or a folder.",
            "enum": [
              "file",
              "folder"
            ],
            "type": "string"
          }
        },
        "properties": {
          "content": {
            "default": null,
            "description": "Initial contents for a file. Ignored for a folder.",
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "allOf": [
              {
                "$ref": "#/definitions/EntryKind"
              }
            ],
            "default": "file",
            "description": "What to create. Defaults to a file."
          },
          "path": {
            "description": "Root-relative path to create. Missing parent folders are created.",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "CreateInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "EntryKind": {
            "description": "Create a file or a folder.",
            "enum": [
              "file",
              "folder"
            ],
            "type": "string"
          }
        },
        "properties": {
          "created": {
            "description": "Always true on success; the call errors rather than reporting false.",
            "type": "boolean"
          },
          "kind": {
            "$ref": "#/definitions/EntryKind"
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "created",
          "kind",
          "path"
        ],
        "title": "CreateOutput",
        "type": "object"
      }
    },
    {
      "description": "Remove a path and close any buffer it held. An open buffer for a deleted file would recreate it on the next save.",
      "metadata": {},
      "name": "editor::delete",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "Root-relative path to remove.",
            "type": "string"
          },
          "recursive": {
            "default": false,
            "description": "Required to remove a non-empty folder.",
            "type": "boolean"
          }
        },
        "required": [
          "path"
        ],
        "title": "DeleteInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Buffer": {
            "description": "One open file. `mtime` is what the conflict guard compares against, so it is part of the shared record rather than per-surface bookkeeping — two surfaces editing one file must agree on which version they started from.",
            "properties": {
              "language": {
                "description": "Monaco language id for the path.",
                "type": "string"
              },
              "mtime": {
                "description": "Last-modified time this buffer was read at, Unix seconds.",
                "format": "int64",
                "type": "integer"
              },
              "path": {
                "description": "Path relative to the workspace root.",
                "type": "string"
              },
              "version": {
                "default": "",
                "description": "Opaque version of the content this buffer was read at — the same fact `mtime` carries, in the form that survives two writes inside one second. A surface saves against it by sending it as `expected_version`.\n\nDefaulted so a session persisted before this field existed still loads. Empty means \"unknown\": a surface holding an empty version has only the mtime to save against, which is the behaviour it had all along.",
                "type": "string"
              }
            },
            "required": [
              "language",
              "mtime",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "buffers": {
            "items": {
              "$ref": "#/definitions/Buffer"
            },
            "type": "array"
          },
          "buffers_closed": {
            "description": "Buffers closed because their file is gone. Leaving them open would let the next save recreate a file the user just deleted.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "deleted": {
            "type": "boolean"
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "buffers",
          "buffers_closed",
          "deleted",
          "path"
        ],
        "title": "DeleteOutput",
        "type": "object"
      }
    },
    {
      "description": "Unified diff between two texts. Pure: nothing is read from disk. Use it to show what an edit will do before writing it, or to explain what a write did.",
      "metadata": {},
      "name": "editor::diff",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "after": {
            "description": "The text as it will be.",
            "type": "string"
          },
          "before": {
            "description": "The text as it was.",
            "type": "string"
          },
          "context_lines": {
            "default": null,
            "description": "Unchanged lines kept around each hunk (`-U` of `git diff`). Defaults to the worker's `diff_context_lines`.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "default": null,
            "description": "Path used to label the patch header. Nothing is read from disk — this is presentation only.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "after",
          "before"
        ],
        "title": "DiffInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Hunk": {
            "description": "One `@@` block: the line ranges it covers on each side, plus the counts a gutter needs without re-reading the patch body.",
            "properties": {
              "added": {
                "description": "Lines added within this hunk.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "new_lines": {
                "description": "Number of \"after\" lines the hunk spans.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "new_start": {
                "description": "First line of the hunk on the \"after\" side, 1-based.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "old_lines": {
                "description": "Number of \"before\" lines the hunk spans.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "old_start": {
                "description": "First line of the hunk on the \"before\" side, 1-based. `0` when the before side is empty (pure addition), matching unified-diff convention.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "removed": {
                "description": "Lines removed within this hunk.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "added",
              "new_lines",
              "new_start",
              "old_lines",
              "old_start",
              "removed"
            ],
            "type": "object"
          }
        },
        "description": "A rendered patch plus the structured view of the same edits.",
        "properties": {
          "added": {
            "description": "Total lines added across every hunk.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "hunks": {
            "description": "One entry per `@@` block, in file order.",
            "items": {
              "$ref": "#/definitions/Hunk"
            },
            "type": "array"
          },
          "identical": {
            "description": "True when `before` and `after` are byte-identical.",
            "type": "boolean"
          },
          "patch": {
            "description": "Unified diff. Empty when the two sides are identical.",
            "type": "string"
          },
          "removed": {
            "description": "Total lines removed across every hunk.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "truncated": {
            "description": "True when either side exceeded `max_bytes` and the diff was skipped. `patch` and `hunks` are empty in that case — a caller that ignores this flag would read \"no changes\" from a file that was simply too big.",
            "type": "boolean"
          }
        },
        "required": [
          "added",
          "hunks",
          "identical",
          "patch",
          "removed",
          "truncated"
        ],
        "title": "DiffResult",
        "type": "object"
      }
    },
    {
      "description": "Fuzzy file finder over the workspace, ranked the way an editor's open-file palette ranks. Candidates come from git when the root is a repository and from the folder listing when it is not.",
      "metadata": {},
      "name": "editor::find",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "include_untracked": {
            "default": true,
            "description": "Include files git does not track yet (still honouring `.gitignore`). On by default — a file the agent just created is exactly the one you are looking for. Ignored outside a repository, where the workspace listing is the source of candidates.",
            "type": "boolean"
          },
          "limit": {
            "default": null,
            "description": "Rows to return. Defaults to the worker's `find_limit`.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "query": {
            "description": "Fuzzy query. Matched as a subsequence against every tracked path, with basename and word-boundary hits ranked highest. Empty returns the first `limit` paths unranked.",
            "type": "string"
          }
        },
        "required": [
          "query"
        ],
        "title": "FindInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FindMatch": {
            "properties": {
              "path": {
                "type": "string"
              },
              "positions": {
                "description": "Byte offsets into `path` that matched, in order — enough to highlight the match without re-running the matcher in the UI.",
                "items": {
                  "format": "uint32",
                  "minimum": 0,
                  "type": "integer"
                },
                "type": "array"
              },
              "score": {
                "description": "Higher is better. Comparable only within one response.",
                "format": "int32",
                "type": "integer"
              }
            },
            "required": [
              "path",
              "positions",
              "score"
            ],
            "type": "object"
          }
        },
        "properties": {
          "from_git": {
            "description": "True when candidates came from git's listing (so `.gitignore` was honoured), false when they came from the folder walk.",
            "type": "boolean"
          },
          "matches": {
            "items": {
              "$ref": "#/definitions/FindMatch"
            },
            "type": "array"
          },
          "scanned": {
            "description": "Paths considered. Compare against `truncated` to know whether the whole repo was ranked.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "truncated": {
            "description": "True when not every path in the workspace was ranked: it held more than `max_find_candidates`, or the folder walk stopped at its visit budget. Either way this ranking is over a prefix of the workspace.",
            "type": "boolean"
          }
        },
        "required": [
          "from_git",
          "matches",
          "scanned",
          "truncated"
        ],
        "title": "FindOutput",
        "type": "object"
      }
    },
    {
      "description": "Stage and commit. Returns the new SHA, or committed:false when there was nothing staged.",
      "metadata": {},
      "name": "editor::git::commit",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "cwd": {
            "default": null,
            "description": "Repository to run in. Defaults to the workspace root.",
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "description": "Commit message. Passed as a single `-m` argument.",
            "type": "string"
          },
          "stage_all": {
            "default": true,
            "description": "Stage every change first (`git add -A`). On by default, matching what an editor's commit command does; pass false to commit only the index.",
            "type": "boolean"
          }
        },
        "required": [
          "message"
        ],
        "title": "GitCommitInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "committed": {
            "description": "False when there was nothing staged to commit.",
            "type": "boolean"
          },
          "sha": {
            "description": "Full SHA of the new commit, when one was made.",
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "description": "git's own summary line, verbatim.",
            "type": "string"
          }
        },
        "required": [
          "committed",
          "summary"
        ],
        "title": "GitCommitOutput",
        "type": "object"
      }
    },
    {
      "description": "What changed in one file: the rendered patch plus its line ranges. Compares the working tree against the index, the index against HEAD, or the working tree against HEAD — so it shows an edit made by anything, including an agent that never called this worker.",
      "metadata": {},
      "name": "editor::git::hunks",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Against": {
            "description": "Which copy of the file the working tree is compared against.",
            "oneOf": [
              {
                "description": "Unstaged edits: working tree vs the index.",
                "enum": [
                  "worktree"
                ],
                "type": "string"
              },
              {
                "description": "Staged edits: index vs HEAD.",
                "enum": [
                  "index"
                ],
                "type": "string"
              },
              {
                "description": "Everything since the last commit: working tree vs HEAD.",
                "enum": [
                  "head"
                ],
                "type": "string"
              },
              {
                "description": "Everything not yet pushed: working tree vs the branch's upstream (`@{upstream}`). Fails when the branch has no upstream configured, which is a real answer rather than an error to swallow.",
                "enum": [
                  "upstream"
                ],
                "type": "string"
              }
            ]
          }
        },
        "properties": {
          "against": {
            "allOf": [
              {
                "$ref": "#/definitions/Against"
              }
            ],
            "default": "worktree",
            "description": "Which comparison to make. Defaults to `worktree`."
          },
          "context_lines": {
            "default": null,
            "description": "Unchanged lines kept around each hunk in `patch`. Defaults to 0, which keeps `hunks` exactly the lines that changed — the ranges a gutter paints. Pass 3 or so for a patch a person will read, and note that the reported ranges widen to include the context you asked for.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "cwd": {
            "default": null,
            "description": "Directory to run git in. Defaults to the workspace root.",
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "description": "Repository-relative path to inspect.",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "GitHunksInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Against": {
            "description": "Which copy of the file the working tree is compared against.",
            "oneOf": [
              {
                "description": "Unstaged edits: working tree vs the index.",
                "enum": [
                  "worktree"
                ],
                "type": "string"
              },
              {
                "description": "Staged edits: index vs HEAD.",
                "enum": [
                  "index"
                ],
                "type": "string"
              },
              {
                "description": "Everything since the last commit: working tree vs HEAD.",
                "enum": [
                  "head"
                ],
                "type": "string"
              },
              {
                "description": "Everything not yet pushed: working tree vs the branch's upstream (`@{upstream}`). Fails when the branch has no upstream configured, which is a real answer rather than an error to swallow.",
                "enum": [
                  "upstream"
                ],
                "type": "string"
              }
            ]
          },
          "Hunk": {
            "description": "One `@@` block: the line ranges it covers on each side, plus the counts a gutter needs without re-reading the patch body.",
            "properties": {
              "added": {
                "description": "Lines added within this hunk.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "new_lines": {
                "description": "Number of \"after\" lines the hunk spans.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "new_start": {
                "description": "First line of the hunk on the \"after\" side, 1-based.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "old_lines": {
                "description": "Number of \"before\" lines the hunk spans.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "old_start": {
                "description": "First line of the hunk on the \"before\" side, 1-based. `0` when the before side is empty (pure addition), matching unified-diff convention.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "removed": {
                "description": "Lines removed within this hunk.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "added",
              "new_lines",
              "new_start",
              "old_lines",
              "old_start",
              "removed"
            ],
            "type": "object"
          }
        },
        "properties": {
          "added": {
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "against": {
            "allOf": [
              {
                "$ref": "#/definitions/Against"
              }
            ],
            "description": "Echo of the comparison performed, so a cached response is self-describing."
          },
          "hunks": {
            "description": "Changed ranges, in file order. Empty when the file matches.",
            "items": {
              "$ref": "#/definitions/Hunk"
            },
            "type": "array"
          },
          "patch": {
            "description": "The rendered unified patch, for showing a person what changed. Empty when there is no difference, and capped by `max_diff_bytes` — a caller that only wants the ranges can ignore it.",
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "removed": {
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "untracked": {
            "description": "True when git reports the path as untracked, in which case there is nothing to compare against and `hunks` is empty.",
            "type": "boolean"
          }
        },
        "required": [
          "added",
          "against",
          "hunks",
          "patch",
          "path",
          "removed",
          "untracked"
        ],
        "title": "GitHunksOutput",
        "type": "object"
      }
    },
    {
      "description": "Read a file's contents at a revision (HEAD by default). Pair it with the working copy to render a real side-by-side or unified diff, rather than parsing a patch.",
      "metadata": {},
      "name": "editor::git::show",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "cwd": {
            "default": null,
            "description": "Repository to run in. Defaults to the workspace root.",
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "description": "Root-relative path.",
            "type": "string"
          },
          "rev": {
            "default": null,
            "description": "Revision to read the file at. Defaults to `HEAD`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "path"
        ],
        "title": "GitShowInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "content": {
            "description": "The file's contents at that revision. Empty when the path did not exist there — which is what `exists: false` distinguishes from an empty file.",
            "type": "string"
          },
          "exists": {
            "description": "False when the path is absent at that revision (a file being added).",
            "type": "boolean"
          },
          "path": {
            "type": "string"
          },
          "rev": {
            "type": "string"
          }
        },
        "required": [
          "content",
          "exists",
          "path",
          "rev"
        ],
        "title": "GitShowOutput",
        "type": "object"
      }
    },
    {
      "description": "Stash the working tree, or pop the most recent stash.",
      "metadata": {},
      "name": "editor::git::stash",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "StashAction": {
            "enum": [
              "push",
              "pop"
            ],
            "type": "string"
          }
        },
        "properties": {
          "action": {
            "allOf": [
              {
                "$ref": "#/definitions/StashAction"
              }
            ],
            "description": "Stash the working tree, or restore the most recent stash."
          },
          "cwd": {
            "default": null,
            "description": "Repository to run in. Defaults to the workspace root.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "action"
        ],
        "title": "GitStashInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "StashAction": {
            "enum": [
              "push",
              "pop"
            ],
            "type": "string"
          }
        },
        "properties": {
          "action": {
            "$ref": "#/definitions/StashAction"
          },
          "ok": {
            "type": "boolean"
          },
          "summary": {
            "type": "string"
          }
        },
        "required": [
          "action",
          "ok",
          "summary"
        ],
        "title": "GitStashOutput",
        "type": "object"
      }
    },
    {
      "description": "Working-tree status as typed rows: branch, upstream, ahead/behind, and one entry per changed path. Fails when the root is not a repository — that is an absent overlay, not a broken workspace.",
      "metadata": {},
      "name": "editor::git::status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "cwd": {
            "default": null,
            "description": "Directory to run git in. Defaults to the workspace root. Confined by shell's jail exactly like any other `shell::exec` call.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "GitStatusInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "StatusEntry": {
            "description": "A single changed path as git sees it.",
            "properties": {
              "index": {
                "description": "Status of the staged copy: `modified`, `added`, `deleted`, `renamed`, `copied`, `untracked`, `ignored`, `conflicted`, or `unchanged`.",
                "type": "string"
              },
              "path": {
                "description": "Path relative to the repository root.",
                "type": "string"
              },
              "renamed_from": {
                "description": "Original path for a rename or copy.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "staged": {
                "description": "True when the staged copy differs from HEAD.",
                "type": "boolean"
              },
              "worktree": {
                "description": "Status of the working-tree copy, same vocabulary as `index`.",
                "type": "string"
              }
            },
            "required": [
              "index",
              "path",
              "staged",
              "worktree"
            ],
            "type": "object"
          }
        },
        "description": "Branch header plus every changed path.",
        "properties": {
          "ahead": {
            "description": "Commits on this branch the upstream does not have.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "behind": {
            "description": "Commits on the upstream this branch does not have.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "branch": {
            "description": "Current branch, or `None` on a detached HEAD.",
            "type": [
              "string",
              "null"
            ]
          },
          "clean": {
            "description": "True when nothing is modified, staged, or untracked.",
            "type": "boolean"
          },
          "entries": {
            "description": "One row per changed path, in git's order.",
            "items": {
              "$ref": "#/definitions/StatusEntry"
            },
            "type": "array"
          },
          "upstream": {
            "description": "Configured upstream, e.g. `origin/main`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "ahead",
          "behind",
          "clean",
          "entries"
        ],
        "title": "StatusReport",
        "type": "object"
      }
    },
    {
      "description": "Fetch, fast-forward pull, or push. Pull is --ff-only on purpose: a merge under open buffers is how an editor ends up showing a conflicted tree it never asked for.",
      "metadata": {},
      "name": "editor::git::sync",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SyncAction": {
            "description": "Which remote operation to run.",
            "oneOf": [
              {
                "enum": [
                  "fetch",
                  "push"
                ],
                "type": "string"
              },
              {
                "description": "Fast-forward only. A pull that would merge fails instead, so this can never produce a conflicted tree under open buffers.",
                "enum": [
                  "pull"
                ],
                "type": "string"
              }
            ]
          }
        },
        "properties": {
          "action": {
            "allOf": [
              {
                "$ref": "#/definitions/SyncAction"
              }
            ],
            "description": "Which remote operation to run."
          },
          "cwd": {
            "default": null,
            "description": "Repository to run in. Defaults to the workspace root.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "action"
        ],
        "title": "GitSyncInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SyncAction": {
            "description": "Which remote operation to run.",
            "oneOf": [
              {
                "enum": [
                  "fetch",
                  "push"
                ],
                "type": "string"
              },
              {
                "description": "Fast-forward only. A pull that would merge fails instead, so this can never produce a conflicted tree under open buffers.",
                "enum": [
                  "pull"
                ],
                "type": "string"
              }
            ]
          }
        },
        "properties": {
          "action": {
            "$ref": "#/definitions/SyncAction"
          },
          "ahead": {
            "description": "Ahead/behind after the operation, so a caller does not need a second round trip to find out whether it changed anything.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "behind": {
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "ok": {
            "type": "boolean"
          },
          "summary": {
            "description": "git's output, trimmed. Both streams: git reports progress on stderr.",
            "type": "string"
          }
        },
        "required": [
          "action",
          "ahead",
          "behind",
          "ok",
          "summary"
        ],
        "title": "GitSyncOutput",
        "type": "object"
      }
    },
    {
      "description": "Undo the last commit, keeping its changes staged (reset --soft HEAD~1). Returns the SHA and message that were undone.",
      "metadata": {},
      "name": "editor::git::undo-commit",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "cwd": {
            "default": null,
            "description": "Repository to run in. Defaults to the workspace root.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "GitUndoCommitInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "message": {
            "description": "Its message, so a caller can put it straight back in a commit box.",
            "type": "string"
          },
          "undone_sha": {
            "description": "SHA that was undone.",
            "type": "string"
          }
        },
        "required": [
          "message",
          "undone_sha"
        ],
        "title": "GitUndoCommitOutput",
        "type": "object"
      }
    },
    {
      "description": "Move or rename a path and rewrite every open buffer and expanded folder at or under it. Moving a folder with `shell::fs::mv` alone leaves buffers pointing at the old location, which silently recreates it on the next save.",
      "metadata": {},
      "name": "editor::move",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "from": {
            "description": "Existing root-relative path.",
            "type": "string"
          },
          "to": {
            "description": "Destination root-relative path.",
            "type": "string"
          }
        },
        "required": [
          "from",
          "to"
        ],
        "title": "MoveInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Buffer": {
            "description": "One open file. `mtime` is what the conflict guard compares against, so it is part of the shared record rather than per-surface bookkeeping — two surfaces editing one file must agree on which version they started from.",
            "properties": {
              "language": {
                "description": "Monaco language id for the path.",
                "type": "string"
              },
              "mtime": {
                "description": "Last-modified time this buffer was read at, Unix seconds.",
                "format": "int64",
                "type": "integer"
              },
              "path": {
                "description": "Path relative to the workspace root.",
                "type": "string"
              },
              "version": {
                "default": "",
                "description": "Opaque version of the content this buffer was read at — the same fact `mtime` carries, in the form that survives two writes inside one second. A surface saves against it by sending it as `expected_version`.\n\nDefaulted so a session persisted before this field existed still loads. Empty means \"unknown\": a surface holding an empty version has only the mtime to save against, which is the behaviour it had all along.",
                "type": "string"
              }
            },
            "required": [
              "language",
              "mtime",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "buffers": {
            "items": {
              "$ref": "#/definitions/Buffer"
            },
            "type": "array"
          },
          "from": {
            "type": "string"
          },
          "remapped": {
            "description": "Open buffers and expanded folders rewritten to the new location. A folder move rewrites everything beneath it, which is the whole reason moves go through this function instead of `shell::fs::mv` directly.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "root": {
            "type": "string"
          },
          "to": {
            "type": "string"
          }
        },
        "required": [
          "buffers",
          "from",
          "remapped",
          "root",
          "to"
        ],
        "title": "MoveOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: hot-reload the editor's limits from the authoritative configuration when it changes.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "editor::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Internal `editor::on-config-change` payload. The handler re-fetches the authoritative value, so this carries only the (advisory) id; a struct rather than a `Value` keeps the request schema concrete.",
        "properties": {
          "id": {
            "default": null,
            "description": "Configuration id that changed (advisory; the handler re-fetches).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "OnConfigChangeEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "OnConfigChangeResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal: turns a filesystem call made by anything into an editor::changed event. Observes only — always continues.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "editor::on-file-change",
      "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": {
            "description": "The part of the hook's result payload that says whether the call worked.",
            "properties": {
              "is_error": {
                "default": false,
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "description": "The subset of the harness hook payload this worker reads.\n\n`session_id` and `turn_id` come from the hook envelope, which is the only place the identity of the writer exists: the call itself says a file was written, not who was writing. Carrying them onto the event is what lets a surface say *this* agent session made *this* change, rather than reporting an anonymous edit. Both stay optional — a hook fired outside a turn (or by an operator reproducing one) has no session, and that is not an error.",
        "properties": {
          "call": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookCall"
              },
              {
                "type": "null"
              }
            ]
          },
          "metadata": {
            "default": null
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookResult"
              },
              {
                "type": "null"
              }
            ],
            "description": "Outcome of the call this hook is reporting on. A write that failed did not change anything, and the hook fires either way."
          },
          "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#",
        "description": "Always `continue`. This hook observes; it never decides.",
        "properties": {
          "decision": {
            "type": "string"
          }
        },
        "required": [
          "decision"
        ],
        "title": "HookOutput",
        "type": "object"
      }
    },
    {
      "description": "Read a text file and record it as an open buffer, with the metadata needed to write it back safely: its language id, and the mtime and content version to hand to editor::save.",
      "metadata": {},
      "name": "editor::open",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "Jail-relative when `shell`'s `fs.host_roots` are set, else absolute — the same path vocabulary as `shell::fs::read`.",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "OpenInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "content": {
            "description": "File contents as text. Binary files are refused rather than mangled.",
            "type": "string"
          },
          "language": {
            "description": "Monaco language id for the path, e.g. `rust`, `typescript`, `plaintext`.",
            "type": "string"
          },
          "mtime": {
            "description": "Last-modified time, Unix seconds. Pass it back as `expected_mtime` on `editor::save` to get the conflict guard.",
            "format": "int64",
            "type": "integer"
          },
          "path": {
            "type": "string"
          },
          "size": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "truncated": {
            "description": "True when the file exceeded `max_file_bytes` and `content` holds only its beginning. Saving a truncated buffer back would delete the rest of the file, so `editor::save` refuses one.",
            "type": "boolean"
          },
          "version": {
            "description": "Opaque version of the `content` above. Pass it back as `expected_version` on `editor::save` for a guard that does not depend on the filesystem's timestamp resolution: it catches a write that landed inside the same second, which `mtime` cannot. Compare it for equality only — the encoding is this worker's business and may change.",
            "type": "string"
          }
        },
        "required": [
          "content",
          "language",
          "mtime",
          "path",
          "size",
          "truncated",
          "version"
        ],
        "title": "OpenOutput",
        "type": "object"
      }
    },
    {
      "description": "Write a file, refusing the write when it changed underneath since the editor::open it started from. On refusal the divergence comes back as a patch.",
      "metadata": {},
      "name": "editor::save",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "content": {
            "description": "Full new contents. This is a whole-file write, not a patch.",
            "type": "string"
          },
          "expected_mtime": {
            "default": null,
            "description": "The `mtime` from the `editor::open` this edit started from. When it no longer matches the file on disk, the write is refused and the divergence comes back as a patch. Omit only when deliberately overwriting whatever is there.\n\nResolution is one second, which is all the filesystem reports. Two writes inside the same second are therefore indistinguishable to this field, and the second one wins silently — send `expected_version` instead to close that window.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "expected_version": {
            "default": null,
            "description": "The `version` from the `editor::open` — or from the previous `editor::save` — this edit started from. It is a version of the *content*, so it catches a write that landed inside the same filesystem second and it does not care whether a clock or a checkout moved the mtime.\n\nWhen this is present it is the guard and `expected_mtime` is ignored. When it is absent the `expected_mtime` comparison applies exactly as before, so a caller that has never heard of a version is unaffected.",
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "content",
          "path"
        ],
        "title": "SaveInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "added": {
            "description": "Lines this save added relative to what was on disk before it.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "conflict": {
            "description": "True when the write was refused because the file changed underneath.",
            "type": "boolean"
          },
          "conflict_patch": {
            "description": "On conflict: a unified diff from the current disk contents to the contents you tried to write, so the divergence is reviewable without a second round trip.",
            "type": [
              "string",
              "null"
            ]
          },
          "created": {
            "description": "True when the file did not exist and was created.",
            "type": "boolean"
          },
          "disk_mtime": {
            "description": "What was on disk when a conflict was detected.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "disk_version": {
            "description": "Version of the disk contents when a conflict was detected — the value to send as `expected_version` once you have reconciled with them.",
            "type": [
              "string",
              "null"
            ]
          },
          "mtime": {
            "description": "Last-modified time after the write. Feed it into the next save.",
            "format": "int64",
            "type": "integer"
          },
          "path": {
            "type": "string"
          },
          "removed": {
            "description": "Lines this save removed.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "saved": {
            "description": "True when the file was written.",
            "type": "boolean"
          },
          "version": {
            "description": "Version of the content this file now holds: what was written on success, what is on disk on a conflict. Feed it into the next save as `expected_version`.",
            "type": "string"
          }
        },
        "required": [
          "added",
          "conflict",
          "created",
          "mtime",
          "path",
          "removed",
          "saved",
          "version"
        ],
        "title": "SaveOutput",
        "type": "object"
      }
    },
    {
      "description": "Search file contents across the workspace, grouped by file — the shell worker's recursive grep, shaped into what a results panel renders.",
      "metadata": {},
      "name": "editor::search",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ignore_case": {
            "default": false,
            "description": "Match case-insensitively.",
            "type": "boolean"
          },
          "include_glob": {
            "default": [],
            "description": "Glob filters restricting which files are searched, e.g. `[\"**/*.rs\"]`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "max_matches": {
            "default": null,
            "description": "Stop after this many matching lines. Defaults to the worker's `search_max_matches`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "pattern": {
            "description": "Rust regex matched against each line.",
            "type": "string"
          }
        },
        "required": [
          "pattern"
        ],
        "title": "SearchInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SearchFile": {
            "properties": {
              "hits": {
                "items": {
                  "$ref": "#/definitions/SearchHit"
                },
                "type": "array"
              },
              "path": {
                "type": "string"
              }
            },
            "required": [
              "hits",
              "path"
            ],
            "type": "object"
          },
          "SearchHit": {
            "properties": {
              "line": {
                "description": "1-based line number.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "text": {
                "description": "The matching line, as shell returned it.",
                "type": "string"
              }
            },
            "required": [
              "line",
              "text"
            ],
            "type": "object"
          }
        },
        "properties": {
          "files": {
            "description": "Matches grouped by file, in first-match order — the shape a result panel renders, rather than a flat list every caller has to group.",
            "items": {
              "$ref": "#/definitions/SearchFile"
            },
            "type": "array"
          },
          "total": {
            "description": "Total matching lines across every file.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "truncated": {
            "description": "True when the search stopped at `max_matches`.",
            "type": "boolean"
          }
        },
        "required": [
          "files",
          "total",
          "truncated"
        ],
        "title": "SearchOutput",
        "type": "object"
      }
    },
    {
      "description": "List a folder in the workspace, with the expansion state the workspace remembers. The walk, the noise-folder excludes and the jail are the shell worker's.",
      "metadata": {},
      "name": "editor::tree",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "collapse": {
            "default": [],
            "description": "Root-relative folders to collapse. Collapsing takes its descendants with it.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "expand": {
            "default": [],
            "description": "Root-relative folders to mark expanded before listing. Expansion is part of the shared workspace, so it survives a reload and both surfaces agree on it.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "max_depth": {
            "default": null,
            "description": "Levels to descend. Defaults to 4 — deep enough to navigate, shallow enough that one call does not walk a whole monorepo.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "default": null,
            "description": "Folder to list, relative to the workspace root. Defaults to the root.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "TreeInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "expanded": {
            "description": "Folders the workspace has expanded, root-relative.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "path": {
            "description": "Canonical absolute path of the listed folder, as shell resolved it.",
            "type": "string"
          },
          "root": {
            "type": "string"
          },
          "tree": {
            "description": "The listing exactly as the shell worker returned it (nested `{name, kind, size, mtime, children}` nodes). Passed through rather than re-modelled so this worker does not pin shell's response shape."
          }
        },
        "required": [
          "expanded",
          "path",
          "root",
          "tree"
        ],
        "title": "TreeOutput",
        "type": "object"
      }
    },
    {
      "description": "Serve the editor worker's injected console UI assets (content function for its console:script / console:style triggers).",
      "metadata": {
        "internal": true
      },
      "name": "editor::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": "The active workspace: its root, the files open against it, and which folders are expanded. Shared by every surface, so this is what the agent and the console both see.",
      "metadata": {},
      "name": "editor::workspace::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "EmptyInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Buffer": {
            "description": "One open file. `mtime` is what the conflict guard compares against, so it is part of the shared record rather than per-surface bookkeeping — two surfaces editing one file must agree on which version they started from.",
            "properties": {
              "language": {
                "description": "Monaco language id for the path.",
                "type": "string"
              },
              "mtime": {
                "description": "Last-modified time this buffer was read at, Unix seconds.",
                "format": "int64",
                "type": "integer"
              },
              "path": {
                "description": "Path relative to the workspace root.",
                "type": "string"
              },
              "version": {
                "default": "",
                "description": "Opaque version of the content this buffer was read at — the same fact `mtime` carries, in the form that survives two writes inside one second. A surface saves against it by sending it as `expected_version`.\n\nDefaulted so a session persisted before this field existed still loads. Empty means \"unknown\": a surface holding an empty version has only the mtime to save against, which is the behaviour it had all along.",
                "type": "string"
              }
            },
            "required": [
              "language",
              "mtime",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "buffers": {
            "description": "Files currently open against this root, shared by every surface.",
            "items": {
              "$ref": "#/definitions/Buffer"
            },
            "type": "array"
          },
          "expanded": {
            "description": "Folders expanded in the tree, root-relative.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "root": {
            "description": "The active root every other path in this response is relative to.",
            "type": "string"
          }
        },
        "required": [
          "buffers",
          "expanded",
          "root"
        ],
        "title": "WorkspaceView",
        "type": "object"
      }
    },
    {
      "description": "Set the directory the editor works in. Any folder will do — a git repository is an overlay, not a requirement. Returns the buffers and expanded folders remembered for it.",
      "metadata": {},
      "name": "editor::workspace::open",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "root": {
            "description": "Directory to work in. Jail-relative when shell's `fs.host_roots` are set, else absolute. A plain folder is enough — a git repository is an overlay, never a requirement.",
            "type": "string"
          }
        },
        "required": [
          "root"
        ],
        "title": "WorkspaceOpenInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Buffer": {
            "description": "One open file. `mtime` is what the conflict guard compares against, so it is part of the shared record rather than per-surface bookkeeping — two surfaces editing one file must agree on which version they started from.",
            "properties": {
              "language": {
                "description": "Monaco language id for the path.",
                "type": "string"
              },
              "mtime": {
                "description": "Last-modified time this buffer was read at, Unix seconds.",
                "format": "int64",
                "type": "integer"
              },
              "path": {
                "description": "Path relative to the workspace root.",
                "type": "string"
              },
              "version": {
                "default": "",
                "description": "Opaque version of the content this buffer was read at — the same fact `mtime` carries, in the form that survives two writes inside one second. A surface saves against it by sending it as `expected_version`.\n\nDefaulted so a session persisted before this field existed still loads. Empty means \"unknown\": a surface holding an empty version has only the mtime to save against, which is the behaviour it had all along.",
                "type": "string"
              }
            },
            "required": [
              "language",
              "mtime",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "buffers": {
            "description": "Files currently open against this root, shared by every surface.",
            "items": {
              "$ref": "#/definitions/Buffer"
            },
            "type": "array"
          },
          "expanded": {
            "description": "Folders expanded in the tree, root-relative.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "root": {
            "description": "The active root every other path in this response is relative to.",
            "type": "string"
          }
        },
        "required": [
          "buffers",
          "expanded",
          "root"
        ],
        "title": "WorkspaceView",
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "Fires when a file in the workspace changes, whoever changed it — including an agent that never called this worker.",
      "invocation_schema": {},
      "metadata": {},
      "name": "editor::changed",
      "return_schema": {}
    }
  ]
}