node-plugin
Đã xác minh@niumoteam/node-plugin · v0.2.6 · MIT · Giao diện 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)
Cài đặt
dsh plugin add @niumoteam/node-plugin Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.
Mã nguồn
Phát hành lên npm mà không có repository công khai. Hãy kiểm tra nội dung package trước khi cài.
Thẻ
Tác giả
Readme
@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-harnesscheckout(取其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。三种配置入口,优先级从高到低:
- 设置面板(GUI):harness 的 设置 → TaskSync 节点(与"插件"/"模型" 并列的独立分区),所有字段可编辑;token 走凭证库(credentials 域),不会 进入设置文档。保存后重启 harness 生效。
- profile 的 cordis.patch.yml(
~/.dsh/profiles/<name>/cordis.patch.yml) - 环境变量(推荐承载机密)
方式一:设置面板(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 反射发现),浏览器经/apiRPC 调用。 - 超时 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 分裂与垫片都不需要。仍需注意:
- 产物新鲜度:built bin 不经 tsx 转译,源码改动完全无效;
lib/或 web 前端dist/缺失/过期会启动失败。拉新代码后先pnpm run build。 - profile 升级切换:从旧版(0.2.4 + 手工垫片)切换过来时,重装即达干净状态
—— 0.2.5 依赖已 peer 化,pnpm 不会再把
@deepseek-ai/*副本装进 profile。 - peer 版本范围:插件 peer 声明
^0.1.0-rc.5,不含 harness 未来的0.2.x正式版;harness 大版本升级时要同步改插件 peer 范围并重新发布。 - 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