# @deepseek-ai/dsh-host-plugin-inventory English | [中文](README.zh.md) Host projection of the current Cordis Loader tree with per-plugin enable/disable. `PluginInventoryGateway` registers the `pluginInventory` service and publishes generated direct Remotes (`list`, `setEnabled`, `availableBundles`, `installPlugin`, `installedBundles`, `uninstall`). `list` reads `ctx.loader.entries()` directly, skips structural group rows, and returns the remaining entries in Loader order with only their Loader entry id, module specifier, effective enablement, and current root Fiber phase. 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 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; a plugin whose `apply` actually fails rejects through the loader's own start error, while a plugin merely pending on a dependency is left enabled and activates once the dependency resolves. 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`/`installPlugin`/`installedBundles`/`uninstall`. `availableBundles` lists the curated offline-installable optional bundles in `src/bundles.ts` (`AVAILABLE_BUNDLES`); that catalog is empty until an optional bundle ships — the profile's default bundles (`dsh-base`, `dsh-web-app`, `dsh-image-recognition-bundle`) are part of the deployment, not optional add-ons, and `uninstall` refuses to remove them. `installedBundles` lists the profile's user-installed dependencies (the ones the user can uninstall). `uninstall` fully removes a user-installed dependency with `pnpm remove` (dropping both the dependency and any bundle layer it declared, gated by `dshAllowPluginInstall` like install), un-composes an offline optional bundle that was composed, and refuses the in-box default bundles. `installPlugin` runs pnpm against the writable profile directory for any `pnpm add` specifier — via the pnpm vendored into the harness, or the `pnpm` on PATH when no vendored pnpm is present (a development checkout), failing loudly only when neither exists — a bare npm name, a tarball path or URL (including a GitHub archive `.tar.gz`), or a git/GitHub URL — so every community install source shares one path. A registry install is gated behind the `dshAllowPluginInstall` context flag, which only the desktop boot sets; a registry-name spec tries the ordered `INSTALL_REGISTRIES` list (`src/install.ts`) until one succeeds, with the official npm registry last as the fallback, and errors only when every registry is unreachable, while a git, tarball, or path spec runs once with no registry. A registry-name spec also passes `--minimum-release-age-exclude` so a freshly published plugin installs. Because pnpm 11 blocks dependency build scripts by default, install is two-phase when one is blocked: the first call returns the blocked packages as `pendingBuilds` instead of failing, the settings tab shows them with a sandbox warning for the user's per-package consent, and the retry carries the approved set in `consentBuilds` — the host writes the per-package `allowBuilds` map (pnpm ≥11) and `onlyBuiltDependencies` array (pnpm 10) directly into the profile's `pnpm-workspace.yaml` for exactly those packages, then reinstalls. Writing the config directly, rather than running `pnpm approve-builds`, does not depend on the packages being in pnpm's pending-build state. When the boot provides a `dshReloadProfile` handle, the gateway recomposes the running tree after the write so the plugin activates immediately (`restartRequired: false`); without it, the install persists the manifest and requires a restart (`restartRequired: true`). Its public payload types live under `./types`, and Typert generates the Host and Client Remote artifacts exposed by `./typert` and `./remote`. A plugin marketplace is exposed through `marketplaceList`/`marketplaceInstall`/`marketplaceUninstall`. The catalog lives on the static web host (default `https://deepseek.pinesound.cn/plugins/plugins.json`, overridable via `DSH_MARKETPLACE_URL`): one index JSON lists plugins, and a per-plugin JSON beside it prescribes the install method (`git`, `npm`, `tarball`, or `bundle`) with the pnpm specifier and the dependency name. `marketplaceList` overlays the durable install table — a small JSON document under `$DSH_HOME/plugin-marketplace/installed.json` — to mark each entry installed; that table is the authoritative installed check. `marketplaceInstall` maps a method onto the existing install path (git/npm/tarball via the registry Remote, bundle via the offline compose), records the install in the table on success, and honors the same two-phase build-consent flow; `marketplaceUninstall` resolves the dependency from the table, runs the existing uninstall, and drops the row. Per-plugin spec URLs are derived from the fixed catalog base plus the plugin id (never from catalog content), so a hostile catalog cannot point the app at an arbitrary URL. 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. ## Model Experience None, as this Host-only inventory projection registers no prompt, tool, message, or provider request. #### KV Cache effect None; this package never assembles model input. ## Known Limitations and Deferred Work - **Point-in-time state only** — the result contains no durable failure history or subscription; a missing root Fiber is reported as `null`, regardless of why no live root exists. - **No provenance** — the service does not identify which bundle, profile, or override introduced an entry, and a row the profile does not mount (absent from every bundle) cannot be toggled from here. Enable/disable persists to the profile's user patch layer; user-installed dependencies can be uninstalled (`installedBundles`/`uninstall`), but the in-box default bundles are part of the installation and are not removable.