Protocol - cruisinme30/spelunk GitHub Wiki
Every message that crosses a process boundary is defined once, in
protocol/protocol.schema.json. npm run gen writes the TypeScript and Go
types from it; nothing else defines a cross-boundary type. See ADR 0002.
flowchart LR
S["protocol.schema.json"] -- "gen.mjs" --> A["extension/src/protocol.gen.ts"]
S -- "gen.mjs" --> B["webview/src/protocol.gen.ts"]
S -- "gen.mjs" --> C["daemon/internal/protocol/protocol_gen.go"]
Webview ↔ extension host
Envelope: { v: 1, type, seq?, payload } over postMessage.
| Webview → host | Host does |
|---|---|
query.changed |
Parses; if valid, cancels the running search and starts a new one |
result.select |
Fetches a preview |
result.open |
Opens the file at the line (or, with position, from a double-click in a file preview, at the word double-clicked), or the commit diff; a file at a revision (at:) read-only, or, with where: "compare", as a diff against the file now (read-only when the file was deleted since) |
results.more |
Loads the next page |
replace.preview |
Asks the daemon what replacing the query's matches would change (replace/plan) |
replace.apply |
Plans again and, if the matches are still the previewed count, applies the edits as one WorkspaceEdit |
replace.save |
Saves the files the last replace changed |
panel.close |
Hides the panel, keeps the query |
settings.open |
Opens Settings at Spelunk's settings, or at the spelunk.* setting it names, as a folder note's fix does; any other setting opens Spelunk's |
pinned.save / pinned.remove |
Pins, renames or unpins a query in spelunk.ui.pinnedQueries |
pinned.watch |
Starts or stops watching a pinned query (the bell), for a query that searches history only |
context.set |
Saves spelunk.results.contextLines from the Context buttons; the settings change runs the query again, and each code result then carries contextBefore and contextAfter |
| Host → webview | Drives |
|---|---|
parse.result |
Chips, errors, autocomplete, Aa, ab and .* toggles |
search.batch / search.done |
Streaming results, counts, Load more, hidden-result notes, facets (each bucket's count and the filter that keeps it), each result's CODEOWNERS owners, the commits a watch found since the query last ran (newCommits, tagged New), and for at: the commit each repo read or why it has none (revisions) and how each result's file differs now (sinceRevision, with renamedTo for a file renamed since). A commit result's files carry a rename's oldPath, a file's currentPath after later renames, and a merge's conflict resolution; the commit carries a merge's parents and, with index.mergedBranches, the merge it came in by (mergedIn, mergedAt). After Search in Folder, folderNotes says which picked folders' files aren't searched, and why, and, in a history search, which are in a Git repo of their own |
preview.result |
The right-hand preview |
index.status |
Header dot and indexing banner |
state.restore |
Reopening where you left off: the box, recent queries (each with the question Ask wrote it for, if any) and pinned queries, and settings; after a settings change, all but the box, which keeps what is being typed. seeded marks a query from the editor, which the box types in over the last one, selected; with keepTyping too, as Search in Folder's scope is, the cursor goes to its end instead |
watch.state |
Which pins search history (and get a bell), and each watched pin's count of new commits |
replace.plan / replace.done |
The Replace row: the preview, then what Replace all changed and skipped |
remotes.state |
Which kind of Git host each repo's links go to (github, gitlab, bitbucket, gitea, azure, other, none, or pick for several remotes), sent each time the panel gets its state. Each result row puts it, with its ref and kind, in data-vscode-context, which VS Code's right-click menu reads to name its link items |
syntax.lines |
Syntax colors: the theme's colors, and each line's tokens by its file and text, sent after the batch or preview that shows the line; reset drops the colors sent before (a theme change, or colors turned off) |
Extension host ↔ daemon
JSON-RPC 2.0 on the daemon's stdin/stdout with LSP-style Content-Length framing. No network port.
See ADR 0001.
| Method | Kind |
|---|---|
initialize, shutdown / exit |
Lifecycle |
workspace/setRoots, workspace/didChangeFiles, settings/update, watch/set |
Notifications from the host (watch/set lists the watched pins). The daemon applies new roots and changed files in the background, in the order they came, so no request waits for the indexer |
query/parse |
Request |
search/start → search/batch × n |
Request, results streamed as notifications |
$/cancelRequest |
Cancel a running search |
preview/get, open/resolve |
Requests on a result's ref; a result at a revision (at:) previews the file from its commit, and opens with the commit's revision |
revision/text |
Request: the whole text of the file a result at a revision names, for its read-only editor |
replace/plan |
Request: every edit replacing a query's matches would make, as UTF-16 ranges into whole lines |
repo/remotes |
Request: a repo's Git remotes, and the one links go to (the one its branch tracks, else origin, else the only one) |
folder/explain |
Request: for a folder of an open repo, why none of its files is indexed when the index holds none (ignored by .gitignore, excluded by index.exclude, reached through a link, or empty), and the Git repo inside the open one that holds it (gitRepo), whose commits history doesn't read |
symbols/find |
Request: the current definitions whose names hold text's letters in order, ignoring case, best first, with each name's path, line and UTF-16 column, for VS Code's Go to Symbol in Workspace; exact wants the whole name, case and all, for Go to Definition, and languageOf keeps to a file's language when it has any and returns that language. Generated and vendored files only with includeGeneratedAndVendored, marked by rankReason; at most limit (200, up to 1,000) |
link/resolve |
Request: for each ref, what its permalink names: the remote, the commit (HEAD when the remote has it, else the newest commit of the remote's branch HEAD builds on; an at: result's revision; a commit result's own), the path in the repository (the old path of a file renamed since) and the lines, mapped from the file on disk by git's diff. Or why there's none: notGit, noRemote, pickRemote (with the remotes), notPushed, lineChanged or notOnRemote. An export sends every result at once |
index/status, index/progress, index/rebuild |
Index state. Each repo's status names its root, when its index was saved (savedAt), how many files were re-read since (reread), and whether this daemon writes it (writer) |
share/hello, share/open |
Requests from the spelunk command and agents on a daemon's socket: which repos it has, and (the background reader only) to load the saved index of the root that holds a folder. A guest may also call index/status, query/parse, search/start (with roots, timeLimitMs and count) and preview/get, and nothing else (Agents and the Terminal) |
watch/matched |
Notification from the daemon: a watched query matched commits a history update brought in |
Changing the protocol
- Edit the schema, run
npm run gen, commit both together. - Additive changes (a new optional field or message) keep the protocol version.
- Anything an older peer would misread bumps it.
Full field-level detail: implementation plan, contracts 2 and 3.
Next: Invariants: the rules a change must not break.