Merge branch 'feat/plugin-mgmt-3-settings' into feat/plugin-mgmt-4-web

This commit is contained in:
Yichen Jiang committed 2026-09-07 23:53:44 +08:00
commit 4032e87014
3 files changed
+36 -2

No files matched your search

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/boot/plugin-manager/README.md
README.md: e2b4b90fd2d1bbc0fcd80969407bbc3e571ec311
README.zh.md: e5aa740c89a2ed2f4badb8ef1b2319bf9c72274c
README.md: 860b28f7b6b1379db3ab7d9179024ea36df6e1f2
README.zh.md: da368436b5a76070d626afb6eb7a51e7c76a26b5
+17
View File
@@ -30,6 +30,12 @@ English | [中文](README.zh.md)
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, and a sink for pnpm's output; then `add(spec)` or `remove(name)`:
```ts
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 }),
@@ -37,6 +43,7 @@ const installer = new PluginInstaller({
installLog: (chunk) => process.stdout.write(chunk.text),
})
const outcome = await installer.add('@acme/dsh-sql-tool')
console.log(outcome.installed, outcome.removed)
```
`add` takes a pnpm spec — a registry name, a `github:` or git URL, a tarball, an absolute path — runs `pnpm add`, records what pnpm wrote to `dependencies`, and probes every new package. A successful `pnpm add` is not yet an installed plugin: a package that declares neither a bundle nor a plugin module, or a bundle whose row id a composed layer already owns, is removed again with `pnpm remove` and listed under `removed` with the reason; a package the probe refused stays installed for a view to explain. New bundles are left disabled and listed under `installedOnly`, so the caller decides whether to enable them — the CLI always does, the Web host only when asked. A non-zero exit, a spawn failure, or the timeout fails the call with `plugins/install-failed` and the tail of the log, and the profile manifest is restored to what it was before the run.
@@ -46,12 +53,22 @@ const outcome = await installer.add('@acme/dsh-sql-tool')
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:
```ts
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` returns one view per package the profile knows: its template and installed bundles and every other installed dependency. A view carries the manifest facts (name, version, title, description, `engines.dsh`), what the package is (`bundle`, `plugin`, or `library`), who supplied it (`builtin` or `external`), when its rows mount (`boot` or `runtime`), whether it is installed and enabled, and a folded `status`: `running`, `partial`, or `failed` for an enabled bundle by how many of its rows are active; `disabled` for an installed bundle outside the layer list; `not-enableable` when the probe refused it, with the reason; `restart-required` when the manifest and the live tree disagree on a profile that applies changes at its next start; `plain` for a library or plugin module, which is added to a composition rather than enabled. Rows come from the live tree while the bundle is composed — phase, disabled-by, and the recorded failure of an isolated row — and from the probe record otherwise, under the ids their patches declare. `addable` lists the modules the package declares in `dsh.plugins`, each with its default config and the probe's verdict, and for a plugin module its main export as `.` — the entry `addRow` accepts without a declaration.
+17
View File
@@ -30,6 +30,12 @@ kind: "package-reference"
用 profile 目录、安装锚点(dsh 应用的 `package.json`)、一个回答 profile 已组合层列表的 `loadProfile`、工具边界,以及 pnpm 输出的去处构造 `PluginInstaller`,然后 `add(spec)` 或 `remove(name)`:
```ts
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 }),
@@ -37,6 +43,7 @@ const installer = new PluginInstaller({
installLog: (chunk) => process.stdout.write(chunk.text),
})
const outcome = await installer.add('@acme/dsh-sql-tool')
console.log(outcome.installed, outcome.removed)
```
`add` 接受一个 pnpm spec——registry 名字、`github:` 或 git URL、tarball、绝对路径——运行 `pnpm add`,记录 pnpm 写进 `dependencies` 的内容,并探测每个新包。`pnpm add` 成功还不等于装好了插件:既不声明组合包也不声明插件模块的包,或者某个行 id 已被已组合层占有的组合包,会再以 `pnpm remove` 移除并连同原因列在 `removed` 里;探针拒绝的包保留在原处,留给视图说明。新组合包保持停用并列在 `installedOnly` 里,由调用方决定是否启用——CLI 一律启用,Web 宿主只在被要求时启用。非零退出、spawn 失败或超时都以 `plugins/install-failed` 与日志尾部让调用失败,并把 profile manifest 恢复到运行前的样子。
@@ -46,12 +53,22 @@ const outcome = await installer.add('@acme/dsh-sql-tool')
在 Cordis 上下文之上构造 `PluginManager`,并交给它按调用读取所需之物的读取器——profile runtime、preset roster 的层、运行中的 agent 数——这样一个后来才有或始终没有其中之一的组合在调用时得到回答,而不是在挂载时:
```ts
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` 为 profile 知道的每个包返回一份视图:模板组合包与已安装的组合包,以及其他每个已安装的依赖。视图携带 manifest 事实(名字、版本、标题、描述、`engines.dsh`),这个包是什么(`bundle`、`plugin` 或 `library`),谁提供它(`builtin` 或 `external`),它的行何时挂载(`boot` 或 `runtime`),是否已安装与已启用,以及折叠出的 `status`:已启用的组合包按活跃行的多少是 `running`、`partial` 或 `failed`;已安装但不在层列表中的组合包是 `disabled`;探针拒绝时是 `not-enableable` 并附原因;在启动时才应用变更的 profile 上 manifest 与在线树不一致时是 `restart-required`;库或插件模块是 `plain`,它们被添加进组合而不是被启用。组合包已组合时行来自在线树——阶段、被谁停用,以及隔离行记录的失败——否则来自探针记录,id 保持各自 patch 声明的样子。`addable` 列出包在 `dsh.plugins` 里声明的模块,各自带默认配置与探针的判定;插件模块还以 `.` 列出它的主导出——`addRow` 无需声明即接受的那一项。