feat(plugin-inventory): offline bundle and pnpm plugin install for the desktop

Adds an install surface to the plugin-inventory gateway: availableBundles lists
the curated offline-installable optional bundles (AVAILABLE_BUNDLES); install
composes an offline bundle into the profile's dsh.profile.bundles, or for a
registry spec runs pnpm against the writable profile via the bundled Node and a
vendored pnpm (gated behind the dshAllowPluginInstall context flag, set only by
the desktop boot); uninstall removes a bundle layer. The reconcile logic from
`dsh plugin add` moves into app-boot as shared helpers. The desktop vendored
pnpm into the harness and sets the allow-install env; the plugin-list SPA gains
an installable-bundles section. Tests cover the guard, install helpers, and the
SPA section at 100% host coverage.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pine
2026-08-14 18:46:39 +08:00
parent 8f1c764614
commit 6cd7c5a590
25 changed files with 929 additions and 110 deletions
+3 -1
View File
@@ -6,7 +6,9 @@ Host projection of the current Cordis Loader tree with per-plugin enable/disable
The phase is `pending`, `loading`, `active`, `failed`, or `unloading`; it is `null` when the entry has no live root Fiber. The snapshot is intentionally point-in-time: Loader remains the sole lifecycle authority, while this package owns no cache, history, provenance model, or event stream. `setEnabled` toggles one entry live through `ctx.loader.update` and persists an explicit `disabled` override into the profile's user patch layer so the choice survives a restart (a bundle-default disable needs the `disabled: false` override to stick).
Every entry carries a `protected` flag. The guard is default-open: every plugin is toggleable unless its module name is in the small `REQUIRED_PLUGINS` set in `src/required.ts` — the load-bearing core (the entry tree, the Remote RPC spine, the session and agent spines) that must never be disabled. `setEnabled` refuses to disable a required plugin and, after enabling, verifies the fiber becomes active (reverting a dependency-missing enable). The Web plugin-list tab renders one flat list of every entry: each shows its real enabled state, a toggleable plugin carries an enable or disable button (so a bundle-default-disabled plugin can be re-enabled), and a required plugin shows only a read-only note. Its public payload types live under `./types`, and Typert generates the Host and Client Remote artifacts exposed by `./typert` and `./remote`.
Every entry carries a `protected` flag. The guard in `src/required.ts` is default-open with two code-editable lists: `REQUIRED_PLUGINS` (the blacklist of load-bearing core that must never be disabled — the entry tree, the Remote RPC spine, the session and agent spines) and `USER_TOGGLEABLE_PLUGINS` (the whitelist, which overrides the blacklist for an explicitly toggleable plugin); a plugin on neither list is toggleable by default. The full dependency-derived taxonomy of the shipped base bundle is in [`docs/plugin-system.md`](../../../docs/plugin-system.md). `setEnabled` refuses to disable a required plugin and, after enabling, verifies the fiber becomes active (reverting a dependency-missing enable). The Web plugin-list tab renders one flat list of every entry: each shows its real enabled state, a toggleable plugin carries an enable or disable button (so a bundle-default-disabled plugin can be re-enabled), and a required plugin shows only a read-only note.
The gateway also manages installation through `availableBundles`/`install`/`uninstall`. `availableBundles` lists the curated offline-installable optional bundles in `src/bundles.ts` (`AVAILABLE_BUNDLES`), each marked installed when present in the profile's `dsh.profile.bundles`. `install` composes an offline bundle into that list (no network), or for a registry spec runs pnpm against the writable profile directory via the bundled Node and vendored pnpm — a registry install is gated behind the `dshAllowPluginInstall` context flag, which only the desktop boot sets. These writes persist the profile manifest and require a restart to take effect. Its public payload types live under `./types`, and Typert generates the Host and Client Remote artifacts exposed by `./typert` and `./remote`.
The service is Remote-only and deliberately declares no same-process Cordis `Context` merge. Client packages consume it through the explicit [`api-remotes`](../../api/remotes/README.md) assembly rather than importing the Host implementation.