Add an in-app plugin marketplace fed by a remote web catalog: the host
plugin-inventory gateway gains marketplaceList/Install/Uninstall remotes,
a durable per-user install table reconciled against the actual profile, and
git/npm/tarball/bundle install paths with non-interactive git and vendored
pnpm. The Web Settings surface gains a sibling 插件市场 tab that lists the
catalog with recommended badges, repository links, and a combined sort
(recommended first, then catalog priority, then id).
Add a host + client skill manager: list local skills by direct filesystem
discovery, and install/uninstall/toggle/edit their SKILL.md from git/npm/
tarball/local sources in the writable user root.
Co-Authored-By: Claude <noreply@anthropic.com>
Installing a plugin now activates it without a restart: the CLI boot provides a
dshReloadProfile handle that re-runs the profile composition and applies it to
the running root Include, and the install/uninstall Remotes recompose live when
the handle is present (restartRequired: false). Registry installs try the
ordered INSTALL_REGISTRIES mirrors with the official npm registry as the final
fallback, erroring only when every source is unreachable. The enable/disable
guard splits into a REQUIRED_PLUGINS blacklist and a USER_TOGGLEABLE_PLUGINS
whitelist (default toggleable) generated from the running plugin list, and the
offline optional-bundle catalog is emptied (default bundles are not
installable/uninstallable). The plugin-list tab becomes a registry install form
and shows immediate-activation instead of a restart notice.
Co-Authored-By: Claude <noreply@anthropic.com>
The RemoteNamespaceService reserves `install` as its private registration
method, so a Remote method also named `install` collided at client-api boot
(`pluginInventory/install conflicts with its namespace service`). Rename the
plugin-install Remote to `installPlugin` across host, client, and tests.
Co-Authored-By: Claude <noreply@anthropic.com>
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>
The plugin-list tab now splits by current state: disabled plugins sit in the
main list with an enable button (so a bundle-default-disabled plugin can be
re-enabled), while enabled plugins sit in the collapsible system section —
a user-added enabled plugin keeps a disable toggle, a required one shows
none. setEnabled refuses to disable a required plugin but allows re-enabling
a disabled one, verifying the fiber activates and reverting a
dependency-missing enable.
Co-Authored-By: Claude <noreply@anthropic.com>
Every entry now carries a protected flag. The guard is default-protect:
disabling a plugin another plugin injects breaks the dependent, and enabling
one whose service is unavailable fails the boot (dsh-tool-ralph: pending on
workflowEngine). setEnabled refuses and the UI hides the toggle for every
shipped plugin; only opt-in-bundle plugins (USER_TOGGLEABLE_PLUGINS in
required.ts) are toggleable.
Co-Authored-By: Claude <noreply@anthropic.com>
pluginInventory/setEnabled calls ctx.loader.update({disabled}) for a live
effect and writes an explicit disabled override into the profile's user
patch layer so the choice survives a restart. The patch row id is the bare
entry options.id, not the group-prefixed tree id.
Co-Authored-By: Claude <noreply@anthropic.com>