- 零第三方依赖,Node >=20 原生 ESM - 团队式编排引擎:拆解/路由/执行/审查/合并全真实 LLM - 阶段心跳、单一权威清单守卫、all-keys-failed 如实上报 - H4 会话视图/amend/watchdog 有界重试/产物区 artifacts.json - H5 零依赖三栏控制台
7.0 KiB
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.jsdefault 导出带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 是数组。