跳到主要内容

node-plugin

已验证

@niumoteam/node-plugin · v0.2.6 · MIT · Web 界面

TaskSync node plugin for deepseek-harness: keeps this harness connected to the TaskSync cloud as a worker node and runs dispatched tasks in-process through the harness's own agent engine (no ACP subprocess)

安装

dsh plugin add @niumoteam/node-plugin

用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。

源码

发布到 npm 但没有公开仓库。安装前请检查包内容。

标签

作者

说明文档

@niumoteam/node-plugin

TaskSync 工作节点插件 —— 直接运行在 deepseek-harness 进程内,反向出站连接 云端 TaskSync 并注册为工作节点;收到派发后通过 harness 自身的 agent 引擎 (ctx.agents)进程内执行任务,实时回传事件流。

替代原先 scripts/sidecar.ts 的独立 sidecar 进程方案:

旧 sidecar 本插件
进程模型 独立进程 + spawn deepseek-harness ACP 子进程 harness 进程内直接执行
资源占用 双份 harness 零额外进程
实时事件 WS → ACP → WS 转发 进程内事件直接映射
HARNESS_PATH 配置 必需 不再需要(插件即 harness 的一部分)

目录结构

plugins/tasksync-node/
├── build.mjs            # esbuild 构建脚本(host 半身自包含 + client 半身 __ModuleLoader__ bundle)
├── cordis.patch.yml     # dsh bundle patch:把插件插入 profile 组合树
├── package.json         # 声明 dsh.bundle / dsh.client + 构建/类型检查脚本
├── tsconfig.json        # 类型检查(paths 指向 harness 本地 .d.ts)
└── src/
    ├── index.ts         # 插件入口:Config / apply / 事件监听注册 / settings 接线
    ├── settings.ts      # settings 命名空间 schema + 本地 installSettingsSection 等价实现
    ├── cloud.ts         # 云端 WS 客户端(注册、心跳、重连、complete 缓存)
    ├── runner.ts        # 进程内任务执行器(agents.create + followup/whenIdle)
    └── client/          # 客户端半身:设置导航里的独立分区(与"插件"并列)
        ├── index.ts                    # client 插件 apply:注册 settings.section 分区
        ├── tasksync-card-controller.ts # staged-edit 表单 + credentials 写入(token)
        ├── TasksyncSection.tsx         # 分区页面(完整配置表单)
        ├── locales.ts                  # zh/en 文案(含导航 label)
        └── card.css.ts                 # 分区样式(<style data-plugin> 注入)

前置条件

  • deepseek-harness 仓库 checkout,与 TaskSync 仓库同级(或通过 HARNESS_REPO 环境变量指向其它位置)
  • Node.js ≥ 22(使用全局 WebSocket)
  • pnpm(dsh plugin 依赖)

构建

cd plugins/tasksync-node
npm install            # 安装 esbuild / typescript / react 类型等 devDependencies
npm run build          # 产物:lib/index.js(host 半身,自包含)+ lib/client.js(浏览器半身)
npm run typecheck      # tsc --noEmit

host 产物(lib/index.js)把唯一的运行时第三方依赖(@deepseek-ai/schemastery) 打进 bundle,因此无论插件以 link:/file:/registry 哪种方式安装都能加载。 这与 harness 兼容:cordis 校验插件 Config 只走 Standard Schema 接口 (Config['~standard'].validate),不依赖 schemastery 实例身份。

客户端产物(lib/client.js)是 harness 的 client-bundle 格式 (window.__ModuleLoader__.load({id, factory})):react / @deepseek-ai/cordis / dsh-client-* 等平台模块保持 external,由浏览器端模块表在运行时提供, 其余代码全部内联,产物除平台表外自包含。包通过 package.json 的 dsh.client + exports["./client"] 声明被发现。

构建脚本默认从与 TaskSync 同级目录找 deepseek-harness checkout(取其 vendor/schemastery 源码打包);harness 在其它位置时设 HARNESS_REPO。

开发机注意:连接性测试要求插件与网关共享同一 @deepseek-ai/dsh-typert-protocol 实例(装饰器标记表是模块私有 WeakMap)。插件目录用 npm install 会装 registry 副本导致测试端点失效;请把该包 link 到 harness 工作区: ln -s <harness>/packages/typert/protocol node_modules/@deepseek-ai/dsh-typert-protocol (link 之后不要再跑 npm install,否则会被替换)。通过 pnpm 安装发布版时 会自动去重到 harness 工作区同版本,无此问题。

安装到 profile

方式一:从 registry(发布后)

cd deepseek-harness
pnpm dsh plugin --profile <name> add @niumoteam/node-plugin

方式二:本地路径开发

cd deepseek-harness
pnpm dsh plugin --profile <name> add /path/to/TaskSync/plugins/tasksync-node

dsh plugin add 会把插件写入 profile 的 package.json 依赖并加入 dsh.profile.bundles(reconcilePlugins 依据 dsh.bundle 声明自动完成), 插件随 profile 一起 boot。

打包与发布

完整发布流程 + 排障速查见 PUBLISH.md(含 2FA/token/免费组织 access 等踩坑记录)。

cd plugins/tasksync-node
npm pack            # 生成 tasksync-node-plugin-<version>.tgz(prepack 自动跑 typecheck + build)
npm publish         # 发布;默认 registry 若是镜像需显式指定官方源:
                    #   npm publish --registry https://registry.npmjs.org

发布到公共 npm 时 @niumoteam 组织必须授予你(niumo)发布权限;若发布到 其它组织/scope,改 package.json 的 name 并同步修改 cordis.patch.yml 中的 name 字段(loader 按包名 import)。

包内只含 lib/index.js(自包含,含 schemastery 副本)、lib/client.js、 cordis.patch.yml、README.md、package.json。唯一运行时外部依赖是 @deepseek-ai/dsh-typert-protocol(连接性测试端点的装饰器标记必须与网关 共享同一模块实例;经 pnpm 安装时会去重到 harness 工作区同版本包)。其它 @deepseek-ai/* 均为编译期 type-only import,产物不含其运行时引用。

配置

必需:nodeId + token。三种配置入口,优先级从高到低:

  1. 设置面板(GUI):harness 的 设置 → TaskSync 节点(与"插件"/"模型" 并列的独立分区),所有字段可编辑;token 走凭证库(credentials 域),不会 进入设置文档。保存后重启 harness 生效。
  2. profile 的 cordis.patch.yml(~/.dsh/profiles/<name>/cordis.patch.yml)
  3. 环境变量(推荐承载机密)

方式一:设置面板(GUI)

设置导航里的 TaskSync 节点独立分区(不是插件页里的卡片):

字段 说明
节点 ID 云端生成的节点 ID(TASKSYNC_NODE_ID)
Token 注册 token,写入凭证库(默认引用 TASKSYNC_NODE_TOKEN)
Token 凭证引用 凭证引用名,默认 TASKSYNC_NODE_TOKEN
云端地址 默认 wss://niumo.leonsoft.cn
工作区白名单 每行一个 projectId=绝对路径
模型名 注册时广告的模型名(不影响实际执行模型)
权限模式 workspace-write / danger-full-access
派发超时(毫秒) 默认 1800000
调试帧输出 打印云端下发的每一帧

方式二:profile 的 cordis.patch.yml

- id: tasksync-node
  config:
    nodeId: '<云端节点 ID>'
    token: '<云端注册 token>'

方式三:环境变量

变量 说明 默认
TASKSYNC_NODE_ID 节点 ID(必需) —
TASKSYNC_NODE_TOKEN 注册 token(必需) —
TASKSYNC_CLOUD_URL 云端根地址 wss://niumo.leonsoft.cn
WORKSPACES 工作区白名单:projectId=绝对路径,逗号分隔 空(可空,注册后自动同步云端 dev_path 兜底)
MODEL 注册时 capabilities.models 广告的模型名(不影响 agent 实际模型,agent 沿用 harness 自身默认模型) 空
PERMISSION_MODE workspace-write(越界权限请求一律拒绝)/ danger-full-access workspace-write
TIMEOUT_MS 单轮派发超时(毫秒) 1800000
SIDECAR_DEBUG_FRAMES 1 时打印云端下发的每一帧 关闭

注意:GUI 可写优先级最高。patch 配置作为 settings 的 composition base 层, 一旦在 GUI 里保存过某字段,它以 user 层覆盖 patch/env,需在 GUI 卡片里 点"重置"才能回到 patch/env 值。

连接性测试

设置 → TaskSync 节点 分区底部的"测试连接"按钮:

  • 非破坏性探测:host 侧建一条临时 WebSocket 连云端 /ws/agent,发一帧 {"type":"ping"} 并等待服务端应答(云端对未知帧回 error 帧,任何应答即证明 握手与帧往返正常),随后关闭。不注册节点、不触碰正在运行的主连接 (云端对同一 nodeId 重复 register 是会话替换,因此测试绝不发 register)。
  • 展示:成功/失败徽标、往返耗时(毫秒)、服务端应答内容、主连接在线状态。
  • 端点:网关路由 tasksync-node/testConnection(TypertRemoteService 的 SRC 反射发现),浏览器经 /api RPC 调用。
  • 超时 8 秒;云端地址不可达、被拒、无应答均报失败。

设置面板的 harness 前提

2026-08-12 之后的 harness 无需任何改动:上游已改为 "registering is exposing" —— 插件注册 settings 命名空间即自动对浏览器暴露(api-proxy 不再持有任何白名单, WEB_SETTINGS_NAMESPACES 与 settings-not-exposed 错误码均已删除)。

旧版 harness(2026-08-12 之前) 才需要在 packages/host/apiproxy/src/api-proxy.ts 的 WEB_SETTINGS_NAMESPACES 数组里加 'tasksync-node' 并重新构建该包 —— 否则设置页该分区报 settings-not-exposed。

运行

cd deepseek-harness
pnpm dsh --profile <name>

启动后插件注册节点并保持连接:周期心跳(30s)、断线指数退避重连、断线期间完成的 任务 complete 帧缓存待重连补发。收到 dispatch 帧后校验目标路径在 workspaces 白名单内(云端 dev_path 兜底的工作区同样生效),随后在进程内 创建 agent 会话执行,assistant/message、tool/call 实时映射为云端 sessionUpdate 帧回传,整轮结束上报 complete。

安装排坑实录(务必先读)

以下问题均在真机安装中踩过,按出现频率排列。症状 → 原因 → 处理:

症状 原因 处理
启动即抛 tasksync-node 缺少 nodeId/token nodeId/token 三种入口都没配到 按上文「配置」任一方式配好;profile 的 cordis.patch.yml 用 - id: tasksync-node 覆写
harness 起不来,报 Cannot find package 'node-pty'/'open' 等 仓库依赖没装全或构建产物缺失 仓库根目录 pnpm install && pnpm run build 后再启动
harness 起不来,报 EPERM ... ~/.dsh/... 安装/启动命令跑在了 IDE agent 沙箱里,~/.dsh 写入被拦 在宿主终端执行 pnpm install 与启动命令
设置面板能打开、其它 RPC 正常,唯独「测试连接」404 源码启动下的 src/lib 双模块分裂(见下节) 按下节装 src 垫片
built bin 启动后行为和源码不一致,或报 client bundles not found lib/、web 前端 dist/ 产物过期/缺失 —— built bin 不经 tsx,源码改动完全无效 仓库根目录 pnpm run build 后重启
旧版插件(0.2.4 及更早)只设了 TASKSYNC_NODE_ID/TOKEN,节点连不上云端 该版本 env 兜底的 cloudUrl 默认是 ws://,云端强制 TLS 时握手失败 显式设 TASKSYNC_CLOUD_URL=wss://niumo.leonsoft.cn 或升级到 0.2.5+(默认已改 wss://)
挪动/删除 harness 仓库后,profile 里插件解析报模块找不到 $DSH_HOME/profiles/node_modules 的 heal 符号链接锚定启动时的 checkout 位置,悬空后对解析不可见 从新 checkout 位置启动一次 harness(heal 幂等重指)

大坑:源码启动(开发机)下 testConnection 404

机理:开发机上从源码跑 harness(npm run dsh,即 node --import tsx/esm)时, tsx 让仓库内 TS 代码经 tsconfig.base.json 的 paths 解析到工作区包的 src/;而 profile 里安装的本插件是纯 JS,经包 exports 解析到同一包的 lib/。于是同一进程加载了两份 @deepseek-ai/dsh-typert-protocol, @Remote 装饰器的标记表是模块私有 WeakMap —— 插件写进 lib 那份, Typert 网关(collectSrcClaims → remoteMethods)读 src 那份,永远读不到 → tasksync-node/testConnection 无人认领 → /api 落到固定路由表 → 404。

诊断(一条命令对比两侧解析结果,src vs lib 即中招):

cd deepseek-harness
echo "console.log(import.meta.resolve('@deepseek-ai/dsh-typert-protocol'))" \
  > packages/api/gateway/src/__probe.ts
node --import tsx/esm packages/api/gateway/src/__probe.ts        # → .../src/index.ts
node --import tsx/esm ~/.dsh/profiles/<name>/node_modules/@niumoteam/node-plugin/__probe.mjs \
  # 同目录放一个内容相同的 __probe.mjs             # → .../lib/index.js
rm packages/api/gateway/src/__probe.ts

Workaround(src 垫片):把插件嵌套目录里该包换成指向源码的垫片:

SHIM=~/.dsh/profiles/<name>/node_modules/@niumoteam/node-plugin/node_modules/@deepseek-ai/dsh-typert-protocol
rm -rf "$SHIM" && mkdir -p "$SHIM"
cat > "$SHIM/package.json" <<'EOF'
{ "name": "@deepseek-ai/dsh-typert-protocol", "version": "0.0.0-src-shim",
  "private": true, "type": "module", "main": "index.mjs", "exports": { ".": "./index.mjs" } }
EOF
cat > "$SHIM/index.mjs" <<'EOF'
export * from '/绝对路径/deepseek-harness/packages/typert/protocol/src/index.ts'
EOF

重启 harness 后 testConnection 应返回 200(可用 curl -X POST http://127.0.0.1:3080/api/tasksync-node/testConnection -H 'content-type: application/json' \ -d '{"type":"client-request","rpcId":"v","method":"tasksync-node/testConnection","payload":{"args":{}}}' 验证)。

注意:

  • profile 里执行 pnpm install 会清掉这个手工垫片,需重做;
  • 用发布产物/构建产物启动 harness(artifact 平面统一走 lib)则天然无此问题;
  • 根治要靠 harness 侧(如 @Remote 标记改用全局符号注册表而非模块私有 WeakMap), 已建议上游。

非源码启动(built bin / 安装版):分裂消失,剩这些坑

用构建产物启动(node apps/cli/lib/bin.js web,或未来 npm 安装版 dsh)时, 整进程统一走 lib/ 平面,上面的 src/lib 分裂与垫片都不需要。仍需注意:

  1. 产物新鲜度:built bin 不经 tsx 转译,源码改动完全无效;lib/ 或 web 前端 dist/ 缺失/过期会启动失败。拉新代码后先 pnpm run build。
  2. profile 升级切换:从旧版(0.2.4 + 手工垫片)切换过来时,重装即达干净状态 —— 0.2.5 依赖已 peer 化,pnpm 不会再把 @deepseek-ai/* 副本装进 profile。
  3. peer 版本范围:插件 peer 声明 ^0.1.0-rc.5,不含 harness 未来的 0.2.x 正式版;harness 大版本升级时要同步改插件 peer 范围并重新发布。
  4. cloudUrl 历史差异:0.2.4 env 兜底默认 ws://,0.2.5+ 默认 wss://; 只靠 env 回退的旧版部署建议显式设置 TASKSYNC_CLOUD_URL=wss://...。

给维护者:依赖声明铁律

@deepseek-ai/dsh-* 只能出现在 peerDependencies(+ 本地 typecheck 走 tsconfig.json 的 paths 指向 harness 仓库的 .d.ts),绝不能放进 dependencies。否则 pnpm 会把 npm 上的副本装进 profile 的 node_modules, 遮蔽 harness 的安装回退符号链接,同样造成进程内双模块(v0.2.4 → v0.2.5 已修复)。

卸载

cd deepseek-harness
pnpm dsh plugin --profile <name> remove @niumoteam/node-plugin