description: "Plugin management over a dsh profile: the installer the dsh plugin command and the Web host share, and the manager over the booted profile that enables, disables, retries, edits user-layer rows, and reports every package."
English | 中文
dsh-plugin-manager installs packages and manages their declared plugin rows through the CLI and Web host. Installation reads metadata without executing modules. The manager enables whole bundle layers, edits global or preset user patches, retries failures, and reports current runtime issues. Failures carry plugins/* codes; the Host adapter exposes the operations through Remote.
Build a PluginInstaller from the profile directory, the install anchor (the dsh app's package.json), a loadProfile that answers the profile's composed layers, the tooling bounds, a sink for pnpm's output, and whether pnpm colours it; then add(spec) or remove(name):
import { loadProfile } from '@deepseek-ai/dsh-app-boot'
import { PluginInstaller } from '@deepseek-ai/dsh-plugin-manager'
declare const profileDir: string
declare const installAnchor: string
const installer = new PluginInstaller({
profileDir, profileName: 'web', installAnchor,
loadProfile: () => loadProfile('dsh', 'web', installAnchor, undefined, { userLayer: false }),
config: { pnpmCommand: 'pnpm', installTimeoutMs: 600_000, installLogTailBytes: 16_384 },
installLog: (chunk) => process.stdout.write(chunk.text),
color: process.stdout.isTTY,
})
const outcome = await installer.add('@acme/dsh-sql-tool')
console.log(outcome.installed, outcome.removed)
add runs pnpm in the profile, reconciles dependencies, and statically checks new bundle declarations against current row ownership. Conflicting bundles are removed with a reason; undeclared and unreadable packages remain installed. New bundles stay disabled unless the caller enables them. A failed pnpm run restores the pre-run manifest and reports plugins/install-failed with the log tail.
Build a PluginManager over the Cordis context and readers for what it needs per call — the profile runtime, the preset roster's layers, and the running-agent count — so a composition that gains or lacks one of them is answered at call time rather than at mount:
import type { Context } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-agent'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type {} from '@deepseek-ai/dsh-app-boot'
import { PluginManager, type PluginToolingConfig } from '@deepseek-ai/dsh-plugin-manager'
declare const ctx: Context
declare const config: PluginToolingConfig
const manager = new PluginManager(ctx, {
config,
runtime: () => ctx.get('profileRuntime'),
presets: () => ctx.get('agentPresets'),
runningAgents: () => (ctx.get('agents')?.list() ?? []).filter(agent => agent.status === 'running').length,
})
console.log(await manager.list())
list reports package identity, bundle / plugin / unknown classification, trust, stage, installation and enablement. Runtime rows carry actual phases and failures; disabled bundles show static patch declarations. addable comes only from dsh.plugins, including . for the main export and declared defaults. An active row can retain its previous config after an update fails. Package issues also names failed rows its patch overrides, without transferring their ownership.
enable selects the whole bundle layer and recomposes live profiles. Per-row failures retain the enabled choice and successful siblings; results report issues, and the list can show partial or failed. Preparation failures revert the enable selection and raise plugins/enable-failed. disable removes the whole layer, including overrides. retry disables, awaits cleanup, and enables again. Startup-only profiles report effect: restart. uninstall disables the bundle, removes user-inserted references, and runs pnpm remove.
addRow writes an explicitly declared dsh.plugins module to the profile’s global cordis.patch.yml or a preset user layer. It preserves declared defaults, checks the target row id, and performs no pre-mount import. removeRow removes a user insert. setRowDisabled writes or removes disabled: true, preserving the bundle’s own condition. Global edits recompose immediately on live profiles; preset edits apply to subsequent generations. dependents reports injection dependents and user-layer module references.
The manager runs one mutation at a time — a second call while one runs fails with plugins/busy naming the operation in flight — and add and uninstall refuse to change node_modules while a session is running, with plugins/agents-running. Every change is followed by a plugins/changed event on the context, and an install run emits pnpm's output as plugins/install-log chunks, each naming the command line it ran and the profile directory it ran in, and, with colour on, carrying pnpm's SGR escapes.
Every refusal or failure is a PluginOperationError with a stable code and details typed by it: plugins/unavailable (no profile runtime, or no roster for a preset target), plugins/not-installed, plugins/not-enableable, plugins/enable-failed, plugins/install-failed, plugins/row-conflict, plugins/busy, plugins/agents-running, and plugins/bad-request for a request that names nothing the profile has. pluginOperationFailureOf narrows a caught value to the code-discriminated union.
Read these when the manager's contract is not enough: the runtime it drives, the files it edits, and the surfaces that call it.
plugins Remote over this manager.dsh plugin command over the installer.None, as plugin management registers nothing model-facing; the rows it composes own every registration they make.
None; this package neither assembles nor sends a provider request.
These limits define what the manager will not do for a caller. They are current package constraints, not a task backlog.
pnpm update through add rewrites the files but the running tree keeps the old modules until the process restarts.inject edge, so dependents cannot name a row that only reads what the package registered.engines.dsh check yet — the range is reported, not enforced against the running harness version.