scheduler-gateway/docs/PLUGIN-MOUNT.md
Liuxinyu176 bbb364cd69 feat: DSH 调度网关(互联网关)v6.0.0 — 四模型合并版 R1+R2+R3
- 零第三方依赖,Node >=20 原生 ESM
- 团队式编排引擎:拆解/路由/执行/审查/合并全真实 LLM
- 阶段心跳、单一权威清单守卫、all-keys-failed 如实上报
- H4 会话视图/amend/watchdog 有界重试/产物区 artifacts.json
- H5 零依赖三栏控制台
2026-10-09 23:20:27 +08:00

140 lines
7.0 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PLUGIN-MOUNT — DSH 桌面端插件实测挂载步骤
> 本机(Windows + DSH Desktop 3.x + pnpm)实测通过:插件行已进入桌面 profile 合成配置,
> 且 `import("scheduler-gateway-merged")` 可从 profile 解析出 4 个 gateway_* 工具。
## 为什么不能直接 `dsh plugin add`
`dsh plugin --profile desktop add link:<dir>` 会把参数转发给 profile 目录里的 pnpm。
本机 Desktop 自带的 pnpm 是 **11.22.0**(要求 store v11),而 desktop profile 的
node_modules 由 **pnpm 10.27.0**(store v10)安装 —— pnpm 11 直接报
`ERR_PNPM_UNEXPECTED_STORE` 拒绝操作,reconcile 步骤也不会执行。
## 实测步骤(管理员/普通用户均可)
```powershell
# 1) 用与现有 store 匹配的 pnpm 安装 link 依赖(PATH 上 pnpm 10.x)
cd C:Userslxy.dshprofilesdesktop
pnpm add link:C:Userslxy互联网关scheduler-gateway-merged
# 2) 把它加入 bundle 层(等价于 dsh plugin 成功后的 reconcile 写回)
# 在 profiles/desktop/package.json 的 dsh.profile.bundles 追加:
# "scheduler-gateway-merged"
# 3) 依赖解析(关键):插件源目录在 profile 之外,Node 按真实路径解析
# 依赖时找不到 @deepseek-ai/dsh-tools,需建立 junction:
mkdir "C:Userslxy互联网关scheduler-gateway-merged
ode_modules@deepseek-ai"
mklink /J "C:Userslxy互联网关scheduler-gateway-merged
ode_modules@deepseek-aidsh-tools" "C:Userslxy.dshprofilesdesktop
ode_modules@deepseek-aidsh-tools"
# 4) 重启 DSH Desktop(宿主进程启动时加载插件,工具表出现 gateway_* 四个工具)
```
## 验证(无需重启即可离线验证)
```powershell
# 合成配置里出现插件行:
node "D:UserslxyAppDataLocalProgramsDeepSeek Harness Desktop
esourcesapp.asar.unpacked
ode_modules@deepseek-aidshlibin.js" --profile desktop --dump-config | findstr /C:"scheduler-gateway-merged"
# 插件入口可从 profile 解析并注册工具:
cd C:Userslxy.dshprofilesdesktop
node -e "import('scheduler-gateway-merged').then(m=>console.log(m.tools.map(t=>t.name)))"
# → [ gateway_status, gateway_run, gateway_board, gateway_nodes ]
```
## 备注
- node_modules/ 已加入 .gitignore,junction 不进入交付清单
- 换机器时:把步骤 1 的 link 指向新路径,重复 2/3;或先运行一次 `dsh plugin add`(若 store 匹配会全自动)
- 插件工具默认连 http://127.0.0.1:4180(GATEWAY_URL 可覆盖);工具 `gateway_run` 的 serve 动作会自动拉起后台网关
## ⚠️ 重要:cordis 插件导出形态(踩坑记录)
DSH 宿主(cordis-plugin-loader)要求插件入口 **default 导出(unwrapExports 取 default)**
必须是「函数」或「带 apply 方法的对象」,否则启动直接崩:
```
Error: failed to apply loader entry scheduler-gateway-merged:
invalid plugin, expect function or object with an "apply" method, received object
```
正确形态(参考 @linxin666/dsh-ssh):
```js
import { defineTool } from "@deepseek-ai/dsh-tools";
const name = "scheduler-gateway-merged";
const inject = ["tools"]; // 需要的宿主服务
function apply(ctx) {
ctx.effect(() => {
const disposers = tools.map((t) => ctx.tools.register(t));
return () => disposers.forEach((d) => d());
}, "scheduler-gateway-merged: tools");
}
export default { name, inject, apply }; // 不能导出工具数组当 default!
```
- 工具在 `apply(ctx)` 里经 `ctx.tools.register` 注册;模块顶层只允许 `defineTool` 构建描述对象
- 若 default 形态错误,宿主会在启动时崩溃;其 plugin-recovery 会把「可疑插件」从 profile 里移除(dep/bundles/links 全回滚),应用恢复可启动但插件消失——重新注册前必须先修好代码
- 修复验证:`node bin.js --profile desktop --dump-config` 出现插件行 + `import("scheduler-gateway-merged")` 后 `typeof mod.default.apply === "function"` + 全量引导 `--port 0` 无报错
## ⚠️ 适配声明(插件管理器的「未声明适配」)
桌面端插件管理器按 package.json 的 `dsh.compatibility` 判定插件是否适配当前 Desktop:
不声明则显示「未声明适配 / 社区」,插件可能不被加载。必须照已验证插件(@linxin666/dsh-ssh)声明:
```json
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" },
"client": "./lib/client.js",
"compatibility": {
"desktop": { "range": ">=2.7.0 <4.0.0", "api": "^1.2.0" },
"runtime": { "range": ">=0.1.1-rc.1 <0.2.0" },
"surfaces": ["main"]
}
}
```
Desktop 3.0.1 在 `>=2.7.0 <4.0.0` 范围内;lock 文件里 compatibility.status 会从 unknown 变为 compatible。
## ⚠️ 最致命坑:dsh.client 形态(桌面端「invalid plugin, received object」)
桌面端会把 `dsh.client` 指向的模块**当成 cordis 插件**加载并要求其有 `apply`。
若写成**字符串**路径且目标文件不是 apply 插件,桌面端启动即崩:
```
failed to apply loader entry scheduler-gateway-merged: invalid plugin, expect function or object with an "apply" method, received object
```
- **正确(如 @linxin666/dsh-ssh)**:`dsh.client` 为对象 `{ inject:[...client runtime 依赖], platform:"web" }`,且 `lib/client.js` default 导出**带 `apply` 的客户端插件**。
- **纯工具型插件(本网关)推荐**:**直接不声明 `dsh.client`**(host-only),只保留 `dsh.bundle.patch` + `dsh.compatibility`。工具由 host 半 `lib/index.js` 注册。
- **为什么 headless CLI 测不出**:`dsh --profile desktop` 只装配 host 插件树,**不加载 web-client 半**;只有桌面 App 会加载 client 半并对它做 cordis 校验。所以「CLI 引导通过 ≠ 桌面端能启动」。
- 排查方法:`import("scheduler-gateway-merged")` 后看 `default` 的 `apply` 是否为 function;再确认 `dsh.client` 是否声明了非 apply 模块。
## ⚠️ 致命坑:工具 result content 必须是内容块数组(不是字符串)
DSH 工具的 `output.render` 必须返回**内容块数组** `[{ type: "text", text: ... }]`,
若返回纯字符串,会写入会话记录为 `tool-result.content = "..."`(字符串)。
而 `@deepseek-ai/dsh-session` 的校验器要求 `Array.isArray(content)`,导致:
- **历史加载失败**:`SessionPersistenceCorruptionError: session event at seq N message must contain one tool-result block`
- **运行时崩溃**:`content.some is not a function`(LLM 运行时把字符串当数组迭代)
- 且会话文件一旦写入坏事件,整份历史校验失败 → 需要在会话文件里把坏事件的字符串 content 包成数组、或用 zstd 修复。
**正确写法**(对照 @linxin666/dsh-ssh):
```js
function textOut(v) {
return {
schema: { type: "object", additionalProperties: true, properties: { text: { type: "string" } } },
render: (_a, v) => [{ type: "text", text: v.text }], // ← 必须返回数组
};
}
```
`nativeExecute`/`makeAdapterExecute` 等执行器返回的 `output` 若是字符串,
经网关 bridge 写回工具结果时也应保证最终 content 是数组。