DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

xuanyuanli /

xuanyuanli/dsh-subagent-cursor

Verified

在 DeepSeek Harness 里把 Cursor CLI 当作一次性子代理:在会话工作区启动 cursor-agent,只返回一条权威答案|Cursor CLI one-shot subagent provider for DSH

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@f6baa8a1

description: "面向用户与维护者的 Cursor CLI 一次性 subagent 提供方:把 cursor-agent 作为 DSH 子智能体挂进 Profile,配置权限档、排障与安全边界。" kind: "package-bundle"

dsh-subagent-cursor

简体中文 · English

把 Cursor CLI(cursor-agent)作为 DSH 子智能体提供方:每次委派在父会话的工作区里 spawn 一个全新的 cursor-agent 进程,跑完一个任务,只返回它的单条权威答案。

目录

  • 为什么需要它
  • 使用本包
  • 前提条件
  • 配置
  • 权限档对照
  • 你会得到什么
  • 踩坑必读
  • 与官方 codex 提供方的差异
  • 理解实现
  • 开发与测试
  • 已知限制

为什么需要它

DSH 官方提供两个子智能体后端:@deepseek-ai/dsh-subagent-codex(Codex)和 @deepseek-ai/dsh-subagent-claude-code(Claude Code)。没有 Cursor。

而 Cursor CLI 是一个值得接进来的后端:它用的是 Cursor 订阅额度,跑的是真实的 agent 循环(读写文件、跑命令、调 MCP),启动和鉴权都走 cursor-agent 自己的配置。装上这个包,模型就能用 subagent_cursor 把一段重活丢给 Cursor 去跑,父会话只收一条结论,token 不随上下文线性膨胀。

如果你已经有 Cursor 订阅,这个包让你不必额外买 API key 就能多一个能真正干活的委派后端。


使用本包

1. 装 Cursor CLI 并登录

见前提条件。这一步不做完,插件装上也不会跑起来。

2. 在 Profile 里加依赖

编辑 Profile 的 package.json(Web/Desktop 安装默认在 ~/.dsh/profiles/desktop/package.json)。本地开发用 link:(与 DSH 其他本地插件一致);发布后改成版本号 "dsh-subagent-cursor": "0.1.1":

{
  "dependencies": {
    "dsh-subagent-cursor": "link:D:/workspace/self/dsh-plugins/dsh-subagent-cursor"
  }
}

3. 在 dsh.profile.bundles 里追加条目

追加到数组末尾(加载层顺序:base / web 在前,本包在后):

{
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "dsh-subagent-cursor"
      ]
    }
  }
}

4. 安装并重启

cd ~/.dsh/profiles/desktop
pnpm install

然后重启 DSH。安装器提示需要重启时必须重启。

本包的 cordis.patch.yml 只插入一行——这个插件同时注册 provider 和它自己的工具:

- insert:
    - id: subagent-cursor
      name: dsh-subagent-cursor

为什么不用官方的 @deepseek-ai/dsh-tool-subagent:它在 install 时通过 ctx.subagents.getProvider('cursor') 找 provider,找不到就等 subagent/provider-added 事件。当 provider 和它是同层兄弟行时,两条路都可能错过,结果是工具静默缺席(只记一条 info 日志,不报错)。自己注册工具就没有这个时序依赖。细节见 spec §14。

5. 验证

让模型调一次最小任务。期望:拿到一段文本(例如 OK),stopReason 为 completed。

用一个 subagent_cursor 任务,只回答 OK,不要调用任何工具。

不放心就先跑一条命令行自检,绕开 DSH 只测 CLI:

node "$LOCALAPPDATA/cursor-agent/versions/<版本>/node.exe" \
     "$LOCALAPPDATA/cursor-agent/versions/<版本>/index.js" \
     -p --output-format stream-json --trust --approve-mcps "Reply with exactly: PONG"

<版本> 取 %LOCALAPPDATA%\cursor-agent\versions\ 下最新目录名(本机实测写作时的最新是 2026.10.22-27dcf5f)。这条命令等价于插件默认档 trust + approveMcps: true 生成的 argv,能跑通就说明 CLI 侧的鉴权和模型选择都没问题。

暴露的工具

subagent_cursor 由本插件自己用 ctx.tools.register() 注册(不依赖 dsh-tool-subagent)。它是 Host 级全局工具,所有 Agent 可见。工具名可用配置项 toolName 改;同机挂两套 Cursor 实例时 providerName 与 toolName 都要改——前者区分 provider,后者区分工具,两个实例共用一个工具名会互相抢占。

工具接受任务后返回 child id;任务完成后把答案作为通知送回父 agent。这是一次性委派:后端不接受追问(工具描述原文:This backend is single-shot: the agent cannot be steered while running and its conversation cannot be continued afterwards.)。要接着追问,就在新的委派里把必要上下文重新写进 prompt。


前提条件

  1. 已登录 Cursor CLI。 插件不复用 DSH 的凭据,也不读 CURSOR_API_KEY;它只依赖 cursor-agent 自己的登录态。先登录:

    cursor-agent login
    
  2. 自检通过。 下面这条应当打印已登录账号(本机实测输出 ✓ Logged in as <你的邮箱>):

    cursor-agent status
    

    在 Windows 上如果 cursor-agent 不在 PATH,用 §5 的 node.exe index.js 形式:... index.js status。

  3. CLI 装在默认位置,或配好 cliPath。 默认位置是 %LOCALAPPDATA%\cursor-agent\,插件会自己按版本目录名挑最新的一版。装在别处(或想锁定某一版)就配 cliPath。

  4. Node.js ≥ 22(package.json 的 engines)。实际执行用的是 Cursor CLI 自带打包的 node.exe,宿主 Node 只用于加载插件。

  5. 模型可用。 不配 model 时用 CLI 默认/账号设置。要固定模型就先确认它在你的账号下存在:

    cursor-agent models
    

    配了不存在的模型,CLI 会在 stderr 打出 Cannot use this model: ... Available models: ... 然后失败。

版本参考:本包实测基线是 Cursor CLI 2026.10.22-27dcf5f。CLI 的 stream-json 输出没有版本化的契约承诺,解析器按"宽容 + 可诊断"实现;换版本后建议重跑一次验证步骤。


配置

字段定义以 src/index.js 的 Config 为唯一真源。

字段 默认值 含义
providerName cursor ctx.subagents 上的注册名。同一台机器挂两套 Cursor(比如一套 trust、一套 plan)时改它区分
model 省略 固定的 CLI 模型 id(非空字符串)。省略则不传 --model,由 CLI 默认/账号设置决定
env {} 显式子进程环境条目,叠在共享 subprocess seam 已清洗过的父环境之上
permissionMode trust 非交互权限档,五选一,见权限档对照
approveMcps true 为真时加 --approve-mcps,自动批准已配置的 MCP server。独立开关,与 permissionMode 正交
extraArgs [] 逃生舱:原样追加到 argv 里(prompt 之前)。不做任何校验,见下方警告
disposeGraceMs 3000 分级终止之间的宽限毫秒数。必须是正的有限数,且不超过 timer 上界
toolName subagent_cursor 本插件自己注册的工具名(Host 级全局)。同机挂两套实例时必须各不相同(见暴露的工具)
cliPath 省略 显式 CLI 入口:一个 index.js 路径,或含 index.js + node.exe 的目录。省略则自动发现最新版本

permissionMode 与 approveMcps 不能混为一谈:--force(yolo 档)覆盖的是 shell 与文件编辑,不覆盖第三方 MCP server。所以 MCP 自动批准是一个独立布尔量,不在任何权限档里。

extraArgs 警告:这里传进去的东西不经过任何校验,直接进 argv。不要传会改变输出格式的 flag(例如换 --output-format),否则解析器拿不到 result 事件,运行必然失败。也不要把 prompt 塞进 extraArgs——prompt 是最后一个位置参数,插件自己会追加。

disposeGraceMs 在加载期就被校验:非正数、非有限数或超过上界都会让插件加载失败,而不是留到销毁时才出问题。

能力与深度治理

本提供方的能力标志全部为 false:agentOptions、agentPreset、outputSchema、depthLimit 都不支持。原因很直接——子进程在另一个进程里,无法兑现父级强制的能力;共享服务会在 start 之前就拒绝需要这些能力的请求(不是"接了再忽略")。

因此深度治理由本插件自己拥有:startActivation 的 request 里不传 maxDepth,provider 也不接受父级下发的深度上限。如果你需要限制 Cursor 子任务的嵌套,只能靠 prompt 约束或在 CLI 侧限制。

同理,Cursor 是单文本入口,没有"传 schema""传工具白名单""传 persona"的原生通道。这些约束只能写在 prompt 里,属于 prompt 工程,不算 provider 能力。


权限档对照

五个取值,互斥枚举(不允许自由组合,因为组合语义未验证)。--trust 是每一档都带的 base flag,所以 trust 档不额外追加任何 flag。

permissionMode 追加的 CLI 参数 平台 / 行为
trust(默认) 无(只有 base 的 --trust) 全平台可用。工作区写操作自动放行
safe --sandbox enabled 仅 macOS / Linux。Windows 上 CLI exit 1 且 stdout 全空,插件在 start() 期就报错
yolo --force 全放行,含 shell / 命令
plan --mode plan 只读 / 规划
ask --mode ask 只读问答

为什么默认是 trust 而不是 safe

这是实测结论,不是口味选择。Windows 上三档真跑一次「创建文件并写入 OK」:

档位 参数 结果
trust --trust 22.8s,exit 0,文件真的被写了,is_error: false,正常 result 事件
yolo --trust --force 23.2s,exit 0,文件被写,正常
safe --trust --sandbox enabled 2.6s,exit 1,stdout 完全空、零 result 事件

safe 的 stderr 是:Error: Sandbox mode is enabled but not available on this system. Sandbox requires macOS or Linux.

结论:safe 在 Windows 上 100% 起不来。如果默认给 safe,这台机器上每一次委派都会失败。能跑的默认值胜过名义上更严但永远跑不起来的默认值。

safe 在 Windows 上的守卫

只改默认值还不够——用户仍可能显式配 safe 跑在 Windows 上。不拦的话,子进程会跑 2.6 秒、吐一个空 stdout,然后被记成一句泛泛的 child run failed (error),根本看不出是"沙箱不可用"这种配置问题。

所以插件在 start() 期、spawn 之前就做平台守卫并抛错,错误消息直接说明 safe 仅 macOS/Linux 并给出替代档位(trust / yolo / plan / ask)。零进程开销、即时失败、错误可读。

未验证:safe 在 macOS / Linux 上按 CLI 报错文案应当可用,但我们没有在这两个平台上实测过。要用请自建验证:跑一次带文件写入的任务,确认 stopReason 为 completed 且文件确实落了盘。


你会得到什么

完成通知里是 Cursor 给出的最终答案文本,或者停止原因加一段安全的诊断。

进父会话的:最终答案(或失败诊断)。 不进父会话的:Cursor 的中间 assistant 消息、thinking 推理、工具调用事件、原始 stderr、token 用量、工作区 diff。

这一点是有意的:assistant 事件只是中间消息,一次运行可能出现多条,拼起来会把模型的思考过程当答案交回去。详见踩坑必读第 5 条。


踩坑必读

以下都是拿真机实测换来的,写 prompt 或排障前先读。

1. prompt 必须以祈使指令开头

实测三组 prompt 结构的工具调用次数:

prompt 结构 长度 工具调用
只有核心指令 138 78 次
核心指令在前 + 约束在后 394 92 次
「工作目录:xxx」放在最前 443 0 次

把路径 / 环境说明放在最前面,agent 会把它当成环境交代,回一句「已确认工作目录」就结束了,一个工具都不调。

对本插件的含义:模型写 subagent_cursor 的 task 时,第一句必须是祈使句("读取 X 并总结""在 Y 里创建 Z")。工作目录、日期、时区、仓库根这类上下文一律放在指令之后。插件不会替你重排 prompt —— 它按原样传给 CLI,顺序变了就是调用方写错了。

另外,子进程的 cwd 已经由 request.cwd 决定,不要在 prompt 里重复交代工作目录,那正是触发上面那个 0 次退化的写法。

2. 工具事件叫 tool_call,不是 tool_use

stream-json 里统计工具调用要看 "type": "tool_call",subtype 是 started / completed。早期误用 tool_use 统计,把一次成功运行误判成 0 次工具调用。

对本插件的含义:你自己写脚本解析 CLI 输出、或排查"它到底动没动手"时,按 tool_call 数。插件的解析器(src/wire.js)也是按 tool_call 计数,并且只计入这两个已知 subtype,不猜。

3. 终端中文乱码的根因在消费端,不是 CLI

实测:CLI 输出是合法的 UTF-8。 乱码来自消费端按 GBK 解码——Windows 控制台的默认代码页是 936,把 UTF-8 字节按 GBK 读就成了乱码。

对本插件的含义:插件用 StringDecoder('utf8') 按块解码 stdout(src/wire.js),不是 chunk.toString()。这一点很关键:一次 UTF-8 序列可能被 chunk 边界切断,逐块 toString() 会产生替换字符,StringDecoder 会把不完整的尾字节留到下一块再拼。

对你自测的含义:用 PowerShell 手跑 CLI 看到中文乱码时,别急着怪 CLI;那只是控制台代码页。要干净地看输出,重定向到文件再用 UTF-8 读,或在 PowerShell 里设 $OutputEncoding / [Console]::OutputEncoding 为 UTF-8。

4. Windows 上 cursor-agent 是三层包装,插件绕开它

%LOCALAPPDATA%\cursor-agent\cursor-agent.cmd 只是转调 powershell.exe,PowerShell 再调 versions\<最新>\node.exe 跑 versions\<最新>\index.js。即 .cmd → PowerShell → node.exe 三层。

本插件的做法:直接 spawn node.exe index.js。

  • 绝不 spawn .cmd:Node 不加 shell: true 根本执行不了 .cmd。
  • 绝不用 shell: true:那会把 prompt 丢回 cmd.exe,%VAR% 会被展开,&、| 会被当命令分隔符——中文和空格 prompt 尤其危险。

顺带的好处是省掉 PowerShell 启动(每次约 300–800 ms)、没有引号地狱、argv 原样传递。版本目录解析复刻了 cursor-agent.ps1 的规则:目录名匹配 ^\d{4}\.\d{1,2}\.\d{1,2}(-\d{2}-\d{2}-\d{2})?-[a-f0-9]+$,按日期段降序取最新。

注意:C:\Program Files\cursor\...\cursor 是 IDE 启动器,不是 CLI agent,别指到那里去。

5. 答案只认 result.result

一次运行里 assistant 事件有多条(实测一次"ls → pwd → 总结"的运行有 3 条,另一类运行出现过 8 条),result 只有 1 条。实测 result.result 恰好等于全部 assistant 文本按空串拼接。

所以:

  • 只取最后一条 assistant,会丢掉前面的内容(上面那例丢了 22%)。
  • 把 assistant 事件拼起来当答案,会把思考过程("先执行 ls,完成后执行 pwd")掺进结论里。

本插件的做法:output 只由 result.result 构成。 assistant、thinking、user 事件一律不参与答案构造。result 缺失(进程被杀等)时不会退回到 thinking 文本——宁可报 error,也不吐半成品推理。

对你排障的含义:自己写脚本时,只看 result 那一行。

6. 其它

  • prompt 走命令行位置参数,受长度上限约束。 Windows 上大约 32 KB。超长 prompt 会直接失败,而不是被静默截断——这比截断好,但把大段材料写进文件再让 Cursor 去读,别塞进 prompt。
  • plan / ask 是只读的。 别指望 plan 档干活。
  • 冷启动慢。 CLI 冷启动实测 3~6 s,但 start() 不等这段——spawn 成功即发布 run,冷启动体现在拿到 result 的延迟上。超时要按这个量级算。
  • 每次委派有约 10K input token 的地板价(实测 9880 / 9883 等固定开销)。很短的任务用 Cursor 不划算。

与官方 codex 提供方的差异

维度 dsh-subagent-codex 本包(cursor)
进程/协议模型 Codex app-server,JSON-RPC 双向(要回审批请求) Cursor CLI stream-json NDJSON,单向(stdout 只往下流)
握手 initialize → initialized → thread/start 无握手;system/init 只是元数据事件,不设 ready 门(spawn 成功即发布 run)
答案选取 final → partial → 末条无 phase 消息 只认 result.result,无回退链
能力标志 全 false 全 false(同)
人在环审批 逐次请求,可 decline / cancel 没有请求通道;启动时一次性定死(--trust / --force / --approve-mcps)
中途 steering 有 interrupt 没有,只有 kill 进程
续接 / resume 不支持 不支持(CLI 有 --resume,但本插件不实现 resume)
冷启动 几百 ms 3~6 s(实测 4.4s / 5.8s / 3.2s)
单次 token 地板 小 约 10K input tokens
子进程入口 随包的平台载荷 外部安装的 cursor-agent(不随包分发)

一句话:codex 走的是双向协议、能做逐次审批;cursor 走的是单向事件流、审批全部前置到启动参数。后者更粗,但无人值守场景下反而更简单——不存在"没人回答审批请求导致挂死"这种失败模式。


理解实现

实现细节——点击展开

设计理念

  • 一次委派 = 一个进程 = 一个 chat。 每次运行 spawn 全新 cursor-agent,跑完一个 prompt 就退出。没有续接、恢复、池化。
  • 原生配置是权威。 登录态、模型默认值、MCP 配置都来自 Cursor CLI 自己的设置。插件只覆盖可选 model、显式 env、权限档与 MCP 自动批准。
  • 刻意无人值守。 CLI 单向输出、stdin 关闭,没有回话通道。所有授权在启动前一次性定死。
  • 失败要 loud。 能在启动期判定的配置错误(CLI 缺失、平台不支持某权限档、cwd 不可用)一律在 start() 抛错,绝不拖到运行期用一句通用错误掩盖。

源码地图

文件 职责
src/index.js 插件入口:name / inject / Config schema / CursorProvider 类 / apply(注册 provider 与工具)/ Windows 平台守卫
src/tool.js subagent_cursor 工具定义:defineTool 参数与输出契约 + startActivation 调用
src/argv.js CLI 定位(版本目录解析)+ buildArgv + permissionArgs(权限档 → CLI flag)
src/run.js 运行生命周期:spawn → 读流 → 等终态 → settleRunResult → 销毁
src/wire.js CursorStreamReader:NDJSON 行解析、UTF-8 分块解码、答案选取、诊断摘要
cordis.patch.yml 插入单个 bundle 行(provider 与工具都由本插件自己注册)
locale/zh.json · locale/en.json 插件页显示名与描述
tests/ 单测:argv / wire / run / index 四组(见开发与测试)

运行流程

  1. 校验与定位。 start() 先 assertUsableCwd 校验 request.cwd,再做平台守卫(safe + win32 直接抛错),然后 resolveCursorCli 定位 node.exe + index.js(优先级:cliPath → %LOCALAPPDATA%\cursor-agent\{index.js,node.exe} → versions\<最新>\{index.js,node.exe})。

  2. 拼 argv。 buildArgv 组装固定顺序:

    node.exe index.js -p --output-format stream-json --trust [--model m] [<权限档 flag>] [--approve-mcps] [<extraArgs>] <prompt>
    

    prompt 永远是最后一个位置参数,其后不能有任何 flag(否则会被当成 prompt 的一部分而不是 flag)。实测默认档的 argv 是:

    ["-p","--output-format","stream-json","--trust","--approve-mcps","Reply with exactly: PONG"]
    
  3. spawn。 经共享 ctx.subprocess.spawn 起子进程,stdio.stdin = 'ignore'(prompt 是位置参数,stdin 保持关闭,同时避免 CLI 阻塞在一个永远不会有人写的管道上),cwd 取 request.cwd,env 是 seam 清洗过的父环境叠加显式 env。spawn 之后先校验 seam 返回的 handle 形状(stdout/stderr/done/terminate/waitForExit),不符合就 loud 报错并尽力清理。子进程 stderr 原样转发到宿主 stderr(process.stderr.write,遇到背压就 pause/resume),同时只滚存末 8 KiB 供失败诊断。

  4. 读 NDJSON。 stdout 按块喂给 CursorStreamReader,StringDecoder 负责跨块的 UTF-8 拼接,按 \n 切行、逐行 JSON.parse。不是 JSON 对象的行记成噪音跳过(CLI 会往 stdout 混横幅和进度文本),最多留 5 条供失败诊断,不致命;单行无换行且超过 8 MiB 则判流 framing 失败。stdout 'end' 时还会调 reader.end() 收尾,让没有尾换行的最后一行(尤其最后那条 result)也能解析。

  5. 只取 result.result。 收到 result 事件即算终态。is_error: true 判失败;答案为空(或全空白)也判失败。第二个 result 事件视为流畸形,保留第一条,让结果稳定。

  6. 销毁。 requestCancel() 先触发,然后 terminate() 终止托管进程组、等退出、等 done,最后 reader.end() 并摘掉流监听。失败诊断里会带退出码、信号、result 事件数与事件类型集合、stderr 摘录(头 1 KiB + 尾 1 KiB,剥 ANSI)和最多 5 条 stdout 噪音行,但绝不带答案正文。

启动期失败(CLI 找不到、cwd 不可用、spawn 抛错、平台守卫)直接 throw,不发布 run;发布之后的失败全部折叠成 stopReason 与安全诊断。


开发与测试

单测用 Node.js 内置 test runner,不需要构建,也不需要真正的 Cursor CLI:DSH 运行时包由 tests/_support/hooks.mjs 的模块解析钩子顶替,子进程用假 child(PassThrough 流 + thenable done)代替,时间只推进微任务,不 sleep、不联网。

npm test                              # 运行 package.json 的 test script(Node 内置 test runner)
node --test "tests/**/*.test.mjs"     # 等价:直接调用 runner

四组文件:argv.test.mjs(CLI 定位与 argv 顺序)、wire.test.mjs(NDJSON 解析与分块 UTF-8)、run.test.mjs(运行生命周期与失败折叠)、index.test.mjs(Config 默认值、win32 守卫、provider/工具注册)。当前基线:114 个用例全部通过。

本仓库没有 CI workflow:peer 依赖是私有的 @deepseek-ai 包,托管 CI 上 npm ci 装不起来;测试只在本地跑(npm test)。


已知限制

  • 一次委派 = 一个进程 = 一个 chat。 没有续接、恢复、池化,也不复用 chat id。工具描述已经如实写明单发模型:不能中途 steering,也不能在同一 chat 上继续。
  • 没有中途 steering。 CLI 的 --print 模式没有输入通道(stdout 单向、stdin 关闭),所以不实现 sendMessage。取消是终止式的:只能 kill 进程。
  • 子级 token 不进父会话。 父会话只收到最终答案或失败诊断;中间消息、thinking、工具活动、stderr、用量、工作区 diff 都不复制过来。子级自己的 token 成本由它自己承担。
  • 取消/失败后已改的文件不回滚。 没有副作用回滚,也没有按实际经过时间触发的超时。trust / yolo 档下 Cursor 可能真的写过文件、跑过命令——那些都不恢复原状。
  • 不支持 outputSchema / agentPreset / agentOptions / depthLimit。 能力标志全 false,请求这些能力会在 start 前被共享服务拒绝。
  • CLI 必须外部安装。 本包不随附 Cursor CLI,也没有"回退到宿主 CLI"这回事。CLI 缺失是启动期错误,会指出试过的路径。
  • prompt 受命令行长度上限约束。 约 32 KB(Windows)。超长会直接失败而不是截断。
  • stdout 单行有 8 MiB 上限。 解析器拒绝无限缓冲一条无换行的行(MAX_LINE_CHARS);超限判 stream framing failed → stopReason: error。正常事件行远小于该值,纯属防御。
  • 每次委派约 10K input token 起步,冷启动 3~6 s。 短任务不划算。
  • stream-json 无版本化契约。 解析器按宽容实现,但 CLI 大改版后仍可能需要跟着改。实测基线 2026.10.22-27dcf5f。
  • safe 档未在 macOS / Linux 实测。 按 CLI 报错文案应当可用,未验证。
—/ 5

No ratings yet

Verified DSH bundle

Commit f6baa8a12a5d

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout