Skip to content

feat(plugin-host): load upstream DSH plugins on Verve - #14

Merged
Quorafind merged 19 commits into
mainfrom
feat/dsh-direct
Oct 26, 2026
Merged

Quorafind merged 19 commits into
mainfrom
feat/dsh-direct

Conversation

@Quorafind

@Quorafind Quorafind commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Run unmodified upstream DSH npm host plugins on Rebon's Verve runtime and native Cordis plane. This implements the host side of the DSH client contract without embedding the DSH web shell.

  • Install npm packages and bundles, probe them in isolation, resolve dependency order, and expose native services.
  • Support settings discovery and mutation, persisted configuration, volatile live updates, credentials, plugin inventory and management, and client module manifests.
  • Route authenticated $events publishing through plane-scoped kernel events and a bounded transport. Cover provenance checks, cross-plane isolation, ordering, lag, shutdown, and listener cleanup.
  • Handle subprocess output gaps and cancellation, and reap direct child processes during host shutdown.
  • Fix the race where a process exits between liveness and identity checks while retaining errors for live processes with unverifiable identities.
  • Serialize tests that share the process-wide sub-agent switch and restore its previous value.

Dependencies

CI validation

CI run 40279261725 passed for 78f4610:

  • verify and test.
  • macOS DSH integration against the production dependency graph.
  • Boa runtime tests on Windows and Linux.

Local validation

  • Type checks passed for rebon-plugin-host library and tests, and rebon-session-runtime tests.
  • rebon-plugin-host library tests: 111 passed.
  • Windows status-line performance test: three consecutive targeted passes with the 250ms threshold.
  • Checked workflow YAML, locked dependency sources, acceptance commands, permissions, and runner paths.

Out of scope

Native slot hosting, session-scoped RPC, waterfall remotes, settings propagation to existing workers, and Rebon Desktop DOM/drawer integration.

The payload carried upstream schemastery 3.18.0 and cosmokit 1.8.1, and
every importer of `@deepseek-ai/schemastery` got them. Current dsh
packages (0.2.x) are written against DeepSeek Harness's forks of the
two: their config schemas mark live fields `.volatile()` and secrets
`.role('secret')`, and `cordis-plugin-loader` imports `updateVolatile`.
None of that exists in the upstream builds, so those packages failed
to import ("not a callable function"). The forks are supersets of what
rebon's own payload uses, so both names now resolve to them.
Every bare specifier in `provided-modules.json` answered every
importer with the vendored build, so a dsh package from npm importing
`@deepseek-ai/dsh-session` got the 0.0.1-era copy rebon's own loop is
built from and failed on exports added since (`SessionSeq`,
`SessionLogOffset`, `ToolCallId`, ...).

The map is now two kinds. `owned` modules are the other half of a seat
rebon implements (cordis, dsh-tools' scheduler symbol, dsh-llm's
adapter base, the environment shims) and answer everyone. `payload`
modules are what the vendored payload is built from: they answer the
payload and this runtime, while a package installed from npm resolves
its own copy and falls back to the vendored one only when it has none.
The installer now installs every peer that is not owned, including
the real `@deepseek-ai/dsh-settings` in place of the static shim.
Current dsh packages inject services that dsh's boot layer and session
model provide, which rebon's realm did not have: `dsh-tool-fs`,
`dsh-tool-bash` and `dsh-tool-skill` need `workingDirectory`, and
`dsh-settings` needs `configEditor`, `profileContext` and
`ctx.root.loader`. Rebon is that layer, so the realm answers them from
its own facts rather than mounting dsh's:

- `workingDirectory` is the workspace the session's scope was opened
  on; changing it from a plugin is refused, since a second answer to
  "where is this session" would not be shared by rebon's tools;
- `loader`, `configEditor` and `profileContext` are the realm's entry
  table. An edit is checked against the plugin's schema, persisted as
  that entry through the `settings` seat's new `entryConfig` (which
  replaces only the caller's own `kernelPlugins.plugins` row), and its
  volatile fields committed into the running plugin as dsh's loader
  does. An ordinary field waits for the next load: what an entry
  registers is reported once per load.

The prompt and tools seats also learn what these packages use:
provider-function section text, resolved once the entry has loaded,
dsh's named section and context orders, and `ctx.tools.get`.
The realm refuses an entry whose required service is not mounted yet,
by design, so the order of `kernelPlugins.plugins` decided whether a
dsh consumer (`dsh-tool-fs`) could mount at all: written before its
provider (`dsh-fs-local`), it was refused. A marketplace install lists
a package's kernel plugins by name, which makes that order arbitrary.

A kernel plugin's manifest can now say which realm services it
injects and which it provides, and the composition loads every entry
after the entries providing what it injects, keeping written order
otherwise. These are ordering facts, not capabilities: services in one
realm are how dsh packages reach each other, and nothing here reaches
rebon.
dsh packages reach each other through a realm they share: the
filesystem tools inject the `fs` service a filesystem backend provides.
Installed one package at a time they land in separate containers, each
with its own realm and its own copy of the seams, and a consumer can
never see its provider.

A package naming a dsh bundle patch (`dsh.bundle.patch`, the format of
dsh's own profile bundles) is now installed as one rebon package: its
dependencies and every peer they declare that rebon does not own go
into one `node_modules`, `probe.mjs --bundle` mounts its rows in one
realm in the order their injections allow, and each row that mounts
becomes a kernel plugin of the one manifest, with its row config as
its default and its `inject`/`provides`. One package is one container,
so the rows share a host as they shared one in dsh. Rows that cannot
run are named with the reason (a service nobody provides, a Loader
expression in their config, not installed) and left out.
A dsh package's browser half reaches its Host through `ctx.remote`,
which dsh's gateway answers in one shape: `{ ok, value }` or
`{ ok: false, error: { code, message, details } }`. In rebon the plane
is the Host, so `PluginPlane::call_remote(endpoint, args)` is that
gateway. Plane facts are answered in Rust (`pluginManager`, read-only;
`credentials.describe`, which reads whether a variable is set and
never its value). Realm facts are asked of the composition as
`kind: 'remote'`: `settings.*` through upstream `dsh-settings` with
its redaction and revision fence, `pluginInventory.list`, and
`rebon/clientModules`, rebon's counterpart of `__DSH_BOOT__`. A
settings write goes to the host serving its entry; listings are
gathered from every host. The Host events a client hears are
published on `compose:remote-event`.
A sample bundle of current dsh packages, in dsh's own bundle format, is
installed from the npm registry the way rebon installs one, loaded on
a real plane in an order that is the reverse of what its rows need,
and used: dsh-tool-fs's read and write over dsh-fs-local, the storage
and skills seams from a consumer in the same realm, dsh-settings'
forms described and written through the client contract and applied
live, a search through dsh-web-search-deepseek sent where that write
said, and the plugin manager, inventory and credentials endpoints.
The rows that cannot run here are asserted by name: dsh-tool-skill
needs dsh's agent loop, and the PowerShell executor a `subprocess`
provider. It needs the network, so it is run by hand.
How dsh finds, serves and boots a package's browser half; its slots
and where each lands in rebon's UI; the client `ctx`; the remotes wire
and how the plane answers it; which process runs host halves and which
should run the client realm; and the recommendation: keep dsh's client
libraries unchanged and implement the slot host natively, the way
su-editor implements Obsidian's app. The composition runtime's README
says how current upstream packages run on it.
Installing `@deepseek-ai/dsh-base` as a bundle runs the rows that are
plugins on an engine and refuses the rest. Two kinds of refusal said
the wrong thing:

- dsh's own services for the seats rebon implements (`llm`, `tools`,
  `web`, `systemPrompt`, `commands`) failed as a name collision; they
  are now `[REBON_PROVIDES]`, which is what they are;
- a row's `disabled` was ignored, so a row the bundle turns off was
  mounted. A literal `true` is now `[DISABLED]`, and a Loader
  expression is `[CONFIG_EXPRESSION]`, as an expression in its config
  already was.

A registry test installs dsh-base and holds which rows run and that
every other one names its reason.
dsh's shell executors (`dsh-pwsh-local`, `dsh-bash-local`) run
commands through `ctx.subprocess`, and dsh's own backend,
`dsh-subprocess-local`, is `child_process` plus native addons (node-pty,
koffi for Windows job objects). A plugin on Verve has none of those, so
the shell rows of a bundle could never mount.

The realm now answers the seam: `spawn` returns dsh's live handle,
collected output is read by offset with the tail kept in `maxBytes`,
`terminate` and the abort signal end the program, `waitForExit` waits
for it; raw pipes, control channels, spill files and terminals are
refused by name. The program is started by rebon through a new
`process` seat: argv, cwd, stdin data, and dsh's scrubbed environment
(no credential-shaped names, no `DSH_*`) plus what the plugin forwards.
It answers the shared host only, since a program started for a
contained plugin would be outside its container, and the tools that
reach it are composition tools, which ask before they run. A package,
or a bundle with a row, that injects `subprocess` asks for the seat at
install. Tool executions also carry the session header dsh's shell
environment reads.
Route authenticated remote events through one scoped kernel listener
instead of a second publisher-side path. Preserve plane provenance,
bounded delivery, explicit lag errors and symmetric shutdown cleanup.

Reap spawned processes when their host stops and fix subprocess stream
and cancellation boundaries. Exercise the public event transport with
unchanged upstream packages, and verify the companion Verve revision in
an isolated macOS dependency graph without changing production sources.
Check out Rebon-Code and the companion Verve revision as siblings so
Boa inherits its own workspace metadata instead of the enclosing CLI
workspace. Keep the temporary dependency graph isolated from verify.

Build the working-directory contract fixture from an absolute path for
the current platform instead of a Windows-only drive path.
The upstream JSON storage plugin syncs its parent directory after an
atomic rename. Validate the corrected Verve revision in the isolated
DSH graph while retaining the normal production dependency checks.
Use the verified Verve revision that closes server responses on peer
shutdown and completes each response independently of keep-alive reuse.
Keep this integration graph separate from the published dependency pin.
A worker can exit between the liveness check and the identity read.
Confirm that it is no longer running before treating a missing
identity as a completed exit, while preserving errors for live or
unverifiable owners.

Cover the observation boundaries deterministically and preserve the
full error chain in stop and takeover test failures.
Prompt and projection tests protect the same process-wide switch
with independent locks, or restore it without locking. Share one
restoring guard across these tests and explicitly enable agents
where the projection requires them.

Keep the production availability policy unchanged.
Use the normal locked Verve and Boa dependency graph for DSH acceptance.
Remove the companion checkout and job-local Cargo path patches so a
passing integration run verifies the dependencies shipped to users.
Keep the production dependency history from the merged JIT phase 2
update before advancing Verve and Boa to the DSH-compatible runtime.
Advance Verve and its Boa crates together to the merged main revision
containing upstream DSH loading and HTTP lifecycle compatibility.
Keep the production dependency graph identical to DSH CI acceptance.
@Quorafind
Quorafind marked this pull request as ready for review October 26, 2026 07:06
@Quorafind
Quorafind merged commit 2843004 into main Oct 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant