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

7.0 KiB
Raw Permalink Blame History

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 步骤也不会执行。

实测步骤(管理员/普通用户均可)

# 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_* 四个工具)

验证(无需重启即可离线验证)

# 合成配置里出现插件行:
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):

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)声明:

"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):

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 是数组。