Implements the LSP capability seam RFC as three packages: dsh-lsp (the
ctx.lsp interface — provider registry by branded id + exclusive extension
mapping, per-query order-independent selection, closed request/result
vocabulary, LspError taxonomy), dsh-lsp-local (a generic stdio language-server
provider — Content-Length JSON-RPC framing, per-(provider, workspace) process
single-flight, transient didOpen/query/didClose, an abortable per-instance
queue, UTF-16 negotiation, host-namespace source reads outside ctx.fs, and
bounded shutdown/kill teardown), and dsh-tool-lsp (the model-facing lsp tool —
four operations, one-based UTF-16 cursor conversion, workspace-grouped location
rendering, hover capping, a required session workspace, and a timeout budget).
Why: an agent had text search and file reads but no way to identify a program
symbol — follow an alias, connect an interface to implementations, or read an
inferred type — before changing code. Splitting model contract, seam, and local
subprocess behavior keeps the four semantic queries stable across future remote
or sandbox-native providers without leaking a JSON-RPC escape hatch.
Move the agent-spine bundle and the stdio/ACP/JSON-RPC app packages out of
core/ and ui/ into a new packages/examples/ group, renamed with a -demo
suffix so the npm name marks them as non-product surface:
core/agent-core -> examples/agent-spine-demo (dsh-agent-spine-demo)
ui/stdio-agent -> examples/stdio-demo (dsh-stdio-demo)
ui/acp-agent -> examples/acp-demo (dsh-acp-demo)
ui/jsonrpc-agent -> examples/jsonrpc-demo (dsh-jsonrpc-demo)
Update every code/config/test reference and reference-only doc mentions, and
regenerate module-graph, config-catalog, and doc-graphs. The jsonrpc bin
(dsh-jsonrpc-agent) and single-file exe (dsh-jsonrpc-agent-pkg) keep their
names; the SDK runtime-startup surface is reconciled separately.
The three-entry cordis.yml (dsh-sandbox-local + dsh-bash-sandbox at a
read-only default + dsh-approval) served over ACP: the first live approval
composition. Recorded snapshot scenarios pin the wire end to end —
config-options advertisement, the mode-switching arc as the suite pinned
header (both switches, the prompt-section delta, one changed-by-the-user
notice per knob, a confined write landing under the switched mode), and
both escalation branches over scripted permissionAnswers (a grant runs
confined under workspace-write; a rejection executes nothing and pins the
fail-closed text). The with-key escalation e2e drives a real model +
real runner + the real bridge answerer, world-verified; ci.yml snapshot
lane and e2e.yml install bubblewrap so the confined replays actually
execute.
Both RFCs move to implemented/ (Decision/Consequences form, deferred
phases tracked in their own sections), with every cross-reference flipped.
dsh-bash grows the per-call policy carrier: BashExecRequest.sandboxMode
(request-optional, spec required-but-nullable — the owner pattern; resolve()
is the one explicit defaulting step) and the BashExecutor.sandboxMode
capability fact (undefined in the base class — composition truth the tool
layer can read). dsh-bash-local carries the field verbatim and confines
nothing.
dsh-bash-sandbox extends LocalBashExecutor and hands ctx.sandbox the exact
argv it is about to spawn. A denial is a RESULT FACT (the command RAN;
result.sandbox.denied is orthogonal to exitCode/signal), classified
conservatively against the wrap own dialect; a RUNNER failure outranks
denial — foreground re-throws the structured SANDBOX_UNAVAILABLE, a settled
background task stamps sandbox.runnerFailed — so a broken sandbox never
reads as a failing command and the command never runs unconfined.
dsh-tool-bash renders the markers and teaches the model not to retry around
a policy denial; escalation and per-session switching are staged follow-ups.
ctx.sandbox (dsh-sandbox): confine(argv, policy) returns the argv to spawn
instead — wrapped so the process and its children run confined — plus the
enforcement completeness and the backend denial/runner-failure dialects;
no usable backend throws the fail-closed SANDBOX_UNAVAILABLE. Policy rides
per call. dsh-sandbox-local selects by platform and caches the verdict:
multi-candidate chains probe FUNCTIONALLY in preference order (Linux:
bwrap → the registry-installed node-addon-landlock-run launcher), a sole
candidate is selected unprobed (darwin: sandbox-exec/Seatbelt) and fails
closed at execution via runnerFailureSignatures; win32 is a reserved empty
chain. Profile parity is honest per backend (documented temp-area and ABI
differences; enforcement full|partial is a structured result fact).
CI: the sandbox-e2e matrix proves real-kernel confinement per rung (bwrap,
Landlock per architecture through the registry-installed launcher,
Seatbelt), failing on a silent all-skip; the packed-install rehearsal
installs the launcher family from the registry and asserts the binary
executable apart from kernel enforcement.