@whaletalk/dsh-codex-collab 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,20 +47,21 @@ flowchart LR
47
47
  | 目录 | 内容 | 说明 |
48
48
  |---|---|---|
49
49
  | `codex-plugin/` | Codex 本地市场插件包 | 标准 marketplace 结构(`.agents/plugins/marketplace.json` + 插件目录),含协作技能 `dsh-collab` |
50
- | `mcp/dsh-mcp.mjs` | MCP 服务器(零依赖 Node) | stdio 传输(Codex `[mcp_servers]` 直连)+ Streamable HTTP 传输(`--http`,供 Secure MCP Tunnel / cloudflared 桥接 ChatGPT);5 个工具:`dsh_task` / `dsh_task_status` / `dsh_task_cancel` / `dsh_review` / `dsh_read_file` |
50
+ | `mcp/dsh-mcp.mjs` | MCP 服务器(零依赖 Node) | stdio 传输(Codex `[mcp_servers]` 直连)+ Streamable HTTP 传输(`--http`,供 Secure MCP Tunnel / cloudflared 桥接 ChatGPT);6 个工具:`dsh_task` / `dsh_sessions` / `dsh_task_status` / `dsh_task_cancel` / `dsh_review` / `dsh_read_file` |
51
51
  | `harness/dsh-bridge.mjs` | DeepSeek Harness 宿主组合网关插件 | 提供 HTTP 网关(`/api/dsh-bridge/*`)、WS 推送、原生工具,管理编码/评审子代理会话。**必须装进 harness 宿主组合**(见下文) |
52
52
  | `cordis.patch.yml` | bundle patch | 把网关插件挂进 DSH 宿主组合;`dsh.bundle.patch` 指向它,所以 `dsh plugin add` 能自动 reconcile |
53
53
 
54
54
  ## 功能
55
55
 
56
56
  - **派活**:`dsh_task` / `task.mjs`,任意工作目录(`--cwd`,不存在自动创建并注册为工作区)
57
+ - **接进已有会话**:`dsh_sessions` 只读搜索既有会话 → `dsh_task --session <id>` 把指令投递进去(`queue` 排队 / `steer` 插入当前回合)。**不新建会话、不新建工作区**;"找不到/接不上"一律返回稳定 reason 而不是静默新建
57
58
  - **并行分工**:同目录多 lane(`--lane`)+ 跨目录天然并行
58
59
  - **模型切换**:`fast`(deepseek-v4-flash)或 `pro`(deepseek-v4-pro),动态解析不写死
59
60
  - **双向评审**:独立评审子代理与编码员分离,读磁盘真实文件、静态审查、**实际运行构建/测试验证可跑通**,输出结构化报告
60
61
  - **Git 自动提交**:`--commit`,任务完成后在 cwd 自动 `git add -A` 并提交
61
62
  - **跨任务记忆**:持久子代理会话 + 历史摘要注入(冷恢复失败自动降级为一次性执行,功能不中断)
62
- - **任务管理**:`--list` / `--status <id>` / `--cancel <id>`
63
- - **结果回传**:任务汇报直接回 Codex 对话(`--wait` 默认)
63
+ - **任务管理**:`--list` / `--status <id>` / `--cancel <id> [--force]`
64
+ - **结果回传**:任务汇报直接回 Codex 对话(`--wait` 默认);每个任务响应都回显 `target`,一眼看出是"新建"还是"接进哪条会话"
64
65
 
65
66
  ## 安装
66
67
 
@@ -162,11 +163,31 @@ tunnel-client run --profile dsh
162
163
  **命令行**(npm 安装后直接用;源码方式用 `node <脚本路径>`):
163
164
 
164
165
  ```bash
166
+ # 新建子代理(原有行为)
165
167
  dsh-task --in "<指令>" --cwd "<目录>" [--lane backend] [--model fast|pro] [--commit]
168
+
169
+ # 接进已有会话:先找,再派
170
+ dsh-task --sessions "<标题关键词>" # 只读列候选,拿 sessionId
171
+ dsh-task --in "<指令>" --session "<sessionId>" [--steer]
172
+ dsh-task --in "<指令>" --find "<标题关键词>" # 唯一命中才派;否则退出码 4
173
+
166
174
  dsh-review --cwd "<目录>" [--diff git|@文件|"diff文本"] [--focus "<重点>"]
167
- dsh-task --list | --status <taskId> | --cancel <taskId>
175
+ dsh-task --list | --status <taskId> | --cancel <taskId> [--force]
176
+ ```
177
+
178
+ **已有会话派活**(对应"接进我正在用的那条对话"):
179
+
180
+ ```bash
181
+ # 1) 找到(只读,不创建任何东西)
182
+ dsh-task --sessions "按文档启动OKX策略实验计划"
183
+ # → {"items":[{"sessionId":"session-8d481ad9-...","snippet":"@okx-strategy-lab/ 开始按照文档进行计划"}]}
184
+
185
+ # 2) 接通(指令投递进那条会话,跑完把这一轮的助手回复作为汇报返回)
186
+ dsh-task --in "<继续推进的指令>" --session "session-8d481ad9-..."
168
187
  ```
169
188
 
189
+ 失败一律返回稳定 `reason` 且**不创建任何会话/工作区**:`session-query-empty`(没命中)、`session-ambiguous`(多条命中,附 `candidates`)、`session-not-found`、`session-busy`、`session-writer-held`(会话被别的写入方占用)、`session-archived`、`session-not-accepted`、`session-timeout`、`session-cancel-refused`(取消会话目标默认被拒,需 `--force`)。
190
+
170
191
  ## 环境变量
171
192
 
172
193
  | 变量 | 默认 | 说明 |
@@ -175,10 +196,18 @@ dsh-task --list | --status <taskId> | --cancel <taskId>
175
196
  | `DSH_BRIDGE_WORKSPACE` | `D:\Harness` | 网关默认共享工作目录 |
176
197
  | `DSH_BRIDGE_ROOT` | `D:\Harness` | `dsh_read_file` 相对路径基准 |
177
198
  | `DSH_BRIDGE_HISTORY` | 多级降级 | 协作历史文件位置(默认 D 盘 → 用户目录 → 临时目录逐级降级) |
199
+ | `DSH_BRIDGE_SESSION_DISPATCH_TIMEOUT_MS` | `120000` | 会话任务被接受后、迟迟没开始跑的容忍时间(仍在排队则报 `session-not-accepted`) |
200
+ | `DSH_BRIDGE_SESSION_QUIET_TIMEOUT_MS` | `600000` | 见过 running 之后多久没有新助手文本算跑完 |
201
+ | `DSH_BRIDGE_SESSION_TOTAL_TIMEOUT_MS` | `3600000` | 单个会话任务的总预算 |
178
202
 
179
203
  ## 已知限制
180
204
 
181
- - 网关插件需要 `webServer` 服务(由 `dsh-web-app` 提供),因此**只能挂 web / desktop profile**;headless profile 会一直 pending
205
+ - 网关插件需要 `webServer` 服务(由 `dsh-web-app` 提供),因此**只能挂 web / desktop profile**;headless profile 会一直 pending。session 目标还需要同 bundle 的 `sessionController`,缺失时返回 `session-controller-unavailable`
206
+ - **同一会话同时只允许一个在途 bridge 任务**:DSH 自己会排队,但无法可靠地把两次并发派活的回报各自归属,所以第二个会被明确拒绝(`session-busy`)而不是猜
207
+ - **冷会话可能接不上**:DSH 激活 Agent 需要会话投影(`Agent activation requires a projected Session observation`),未激活的会话会返回 `session-not-activatable`;归档会话的回合会以 `blocked` 收口(`session-archived`)
208
+ - **评审不支持会话目标**:评审员必须与编码会话隔离,"独立评审"才成立
209
+ - session 目标不使用 `cwd`/`lane`/`model`/`--commit`(会话自带这些语义),传了会被判 `invalid-target`
210
+ - 网关重启会丢失内存中的任务表(含 session 观察器),这是既有设计
182
211
  - Codex Desktop Windows 本地市场技能注入 bug(见上,MCP 通道不受影响)
183
212
  - 动态插件环境无 `AbortSignal`,冷恢复失败时自动降级为一次性执行;宿主组合持久化版无此问题
184
213
  - ChatGPT Plus 写入型 MCP 的开放度取决于 OpenAI 产品策略
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Codex 主导的双向编码协作插件:ChatGPT 把编码任务下发给本机 DeepSeek Harness 的持久编码子代理执行,DeepSeek 在共享工作目录 D:\\Harness 中写代码并实时回传进度与汇报,Codex 读文件验收、反馈修正,直到通过。",
5
5
  "author": {
6
6
  "name": "FuJackGao"
@@ -1,59 +1,88 @@
1
1
  ---
2
2
  name: dsh-collab
3
- description: "与 DeepSeek Harness 编码协作。当用户要写代码、建项目、改代码时使用:把编码任务交给本机 DeepSeek Harness 的编码子代理执行(DeepSeek 写代码,你负责验收),在 --cwd 指定的工作目录(通常是 Codex 当前项目目录)中读文件验收;DeepSeek 完成后可自动 git 提交,并可用独立评审子代理审查 diff(评审员会实际跑通构建/测试)。不通过则发修正指令迭代,直到通过再向用户汇报。"
3
+ description: "与 DeepSeek Harness 编码协作。当用户要写代码、建项目、改代码时使用:把编码任务交给本机 DeepSeek Harness 执行(DeepSeek 写代码,你负责验收)。两种派活方式——默认在 --cwd 指定的工作目录新建编码子代理;也可以把任务投递进**用户正在用的那一条已有会话**(保留它自己的上下文,不新建会话/工作区)。DeepSeek 完成后可自动 git 提交,并可用独立评审子代理审查 diff(评审员会实际跑通构建/测试)。不通过则发修正指令迭代,直到通过再向用户汇报。"
4
4
  ---
5
5
 
6
6
  # DeepSeek Harness 编码协作
7
7
 
8
8
  ## 角色
9
9
 
10
- - 你 = 项目经理 + 验收员。DeepSeek 编码子代理 = 程序员,在 `--cwd` 指定的工作目录中写代码(任意目录,Codex 打开哪个项目就传哪个目录)。
10
+ - 你 = 项目经理 + 验收员。DeepSeek 编码子代理 = 程序员。
11
11
  - 子代理保留跨任务记忆;独立评审子代理与编码员分离,不信任口头报告,只认真实文件与真实运行结果。
12
12
 
13
+ ## 两种派活目标(先想清楚要哪一种)
14
+
15
+ | 目标 | 什么时候用 | 效果 |
16
+ |---|---|---|
17
+ | **worker**(默认) | 全新任务、独立项目目录 | 在 `--cwd` 里新建一个持久编码子代理 |
18
+ | **session** | 用户已经在某条会话里做着这件事(例如"按文档启动OKX策略实验计划"),你只是替他继续推进 | 指令**投递进那条会话**,用它自己的上下文继续,**不新建会话/工作区** |
19
+
20
+ > 默认是 worker。如果你把任务派进了新建的会话,而用户期望的其实是"接着我原来那条对话做",那是**派错了目标**——先查会话再派。
21
+
13
22
  ## 工具脚本(固定位置)
14
23
 
15
- - 派活:`D:/Harness/.codex-collab-tools/task.mjs`
16
- - 评审:`D:/Harness/.codex-collab-tools/review.mjs`
24
+ - 派活:`dsh-task`(npm 全局安装后进 PATH)
25
+ - 评审:`dsh-review`
26
+ - MCP 服务器:`dsh-mcp`
27
+
28
+ 若 PATH 里没有,用包内绝对路径:`node "<包目录>/codex-plugin/plugins/dsh/skills/dsh-collab/scripts/task.mjs"`。
29
+
30
+ ## 找到已有会话(只读,不创建任何东西)
31
+
32
+ ```bash
33
+ dsh-task --sessions "<标题或内容关键词>"
34
+ ```
35
+
36
+ 输出候选(`sessionId` + 片段)。**必须唯一命中**才能直接用;多条会返回 `candidates` 让你改用 `--session <id>`。退出码 `4` = 没命中或不唯一。
17
37
 
18
38
  ## 下发任务
19
39
 
20
40
  ```bash
21
- node "D:/Harness/.codex-collab-tools/task.mjs" --in "<完整中文指令>" --cwd "<工作目录>" [--lane <通道>] [--model fast|pro] [--commit] [--timeout 秒]
41
+ # 1) 新建子代理(原有行为)
42
+ dsh-task --in "<完整中文指令>" --cwd "<工作目录>" [--lane <通道>] [--model fast|pro] [--commit] [--timeout 秒]
43
+
44
+ # 2) 投递进已有会话
45
+ dsh-task --in "<完整中文指令>" --session "<sessionId>" [--steer] [--timeout 秒]
46
+ dsh-task --in "<完整中文指令>" --find "<标题关键词>" # 唯一命中才派发,否则退出码 4
22
47
  ```
23
48
 
24
- - `--cwd`:**默认传 Codex 当前打开的项目目录**(绝对路径,正斜杠);不存在时自动创建并注册为 harness 工作区。
25
- - `--lane`:并行通道名(默认 main)。同一目录不同 lane 的任务**并行执行**(如 backend 与 frontend 各一路);跨目录天然并行。
26
- - `--model`:`fast`(deepseek-v4-flash,快而省)或 `pro`(deepseek-v4-pro,质量优先);默认继承当前模型。
27
- - `--commit`:任务完成后在 `--cwd` 自动 `git add -A` 并提交(提交信息含任务摘要);非 git 仓库自动跳过。
28
- - 指令必须包含:目标与产出物、验收标准、约束。`--wait` 为默认行为(结果直接回本对话),最长 10 分钟,长任务加 `--timeout 1800`。
49
+ - `--cwd`:**默认传 Codex 当前打开的项目目录**(绝对路径,正斜杠);不存在时自动创建并注册为 harness 工作区。与 `--session`/`--find` 互斥。
50
+ - `--lane`:并行通道名(默认 main)。同一目录不同 lane 的任务**并行执行**;跨目录天然并行。
51
+ - `--model`:`fast`(deepseek-v4-flash,快而省)或 `pro`(deepseek-v4-pro,质量优先);默认继承当前模型。session 目标不使用此参数(会话自己有模型)。
52
+ - `--commit`:任务完成后在 `--cwd` 自动 `git add -A` 并提交;非 git 仓库自动跳过。session 目标禁止使用(会话自带工作目录,本工具不替它提交)。
53
+ - `--steer`:仅 session 目标。默认 `queue`(排队,等它忙完再跑);`--steer` 会插入当前回合、改变它正在做的事——**只在用户明确要求打断时用**。
54
+ - 指令必须包含:目标与产出物、验收标准、约束。`--wait` 为默认行为(结果直接回本对话),长任务加 `--timeout 1800`。
55
+
56
+ 退出码:`0` 成功 / `1` 失败 / `2` 用法 / `3` 超时 / `4` 会话未命中或不唯一 / `5` session 目标取消被拒。
29
57
 
30
58
  ## 读取汇报与验收
31
59
 
32
60
  1. 脚本最后输出 `=== DeepSeek 汇报 ===`(做了什么、改动文件、如何验证、待决策问题)。
33
- 2. 读 `--cwd` 中它声称改动的文件核对;必要时自己跑构建/测试。
34
- 3. 通过 → 向用户汇报;不通过 → 发修正指令(子代理记得该目录上下文),迭代到通过。
61
+ 2. worker 目标:读 `--cwd` 中它声称改动的文件核对。session 目标:让用户到那条会话里看,或你再派一条只读指令让它回读。
62
+ 3. 通过 → 向用户汇报;不通过 → 发修正指令(子代理/会话记得上下文),迭代到通过。
35
63
 
36
64
  ## 双向评审(Codex 验收 DeepSeek 的变更)
37
65
 
38
- DeepSeek 任务完成后,把它的变更交给独立评审子代理:
39
-
40
66
  ```bash
41
- node "D:/Harness/.codex-collab-tools/review.mjs" --cwd "<工作目录>" [--diff git|git-staged|@文件|"diff 文本"] [--focus "<审查重点>"]
67
+ dsh-review --cwd "<工作目录>" [--diff git|git-staged|@文件|"diff 文本"] [--focus "<审查重点>"]
42
68
  ```
43
69
 
44
70
  - `--diff` 默认取 `git diff`(未提交变更);`git-staged` 取暂存区;也可直接给 diff 文本或文件(`@路径`)。
45
- - 评审员会:读 `--cwd` 真实文件核对 diff → 静态审查(正确性/健壮性/安全/测试缺口)→ **实际运行构建与测试验证可跑通** → 输出结构化报告(结论 + 问题列表 + 跑通验证 + 修改建议)。
46
- - 你阅读 `=== 评审报告 ===`,有问题就发修正指令给编码员,直到评审通过。
71
+ - 评审员会:读 `--cwd` 真实文件核对 diff → 静态审查(正确性/健壮性/安全/测试缺口)→ **实际运行构建与测试验证可跑通** → 输出结构化报告。
72
+ - **评审不支持 session 目标**:评审员必须与编码会话隔离,否则"独立评审"就不成立。
47
73
 
48
74
  ## 任务管理
49
75
 
50
76
  ```bash
51
- node "D:/Harness/.codex-collab-tools/task.mjs" --list # 最近任务列表
52
- node "D:/Harness/.codex-collab-tools/task.mjs" --status <taskId> # 查询单个任务
53
- node "D:/Harness/.codex-collab-tools/task.mjs" --cancel <taskId> # 取消运行中的任务
77
+ dsh-task --list # 最近任务列表
78
+ dsh-task --status <taskId> # 查询单个任务(含 target 回显)
79
+ dsh-task --cancel <taskId> # 取消 worker 任务
80
+ dsh-task --cancel <taskId> --force # 取消 session 目标任务(会打断用户自己的回合,默认被拒)
54
81
  ```
55
82
 
56
83
  ## 故障排查
57
84
 
58
- - 连接失败:DeepSeek Harness 未运行(应监听 http://127.0.0.1:3080),请提示用户启动。
85
+ - 连接失败:DeepSeek Harness 未运行(网关应监听本机端口,默认 3080;挂在 web/desktop profile 时也可能就是 GUI 的端口)。
86
+ - 派 session 任务返回 `session-writer-held` / `session-busy`:那条会话被别的写入方或另一个 bridge 任务占用;稍后重试,或改用 worker 目标。
87
+ - 返回 `session-not-activatable` / `session-archived`:该会话当前接不了(冷会话未能激活,或已归档);请用户先在客户端打开它,或解除归档。
59
88
  - 读不到 `--cwd` 目录:提示用户在 Codex 设置中把该目录加入可访问/可写目录。
@@ -22,7 +22,7 @@ const HISTORY_CANDIDATES = process.env.DSH_BRIDGE_HISTORY
22
22
  const HISTORY_MAX = 40
23
23
 
24
24
  function parseArgs(argv) {
25
- const out = { wait: true, timeoutSec: 600, instruction: '', cwd: '', lane: '', model: '', commit: false, list: false, cancel: '', status: '' }
25
+ const out = { wait: true, timeoutSec: 600, instruction: '', cwd: '', lane: '', model: '', commit: false, list: false, cancel: '', status: '', sessions: '', sessionId: '', find: '', steer: false, force: false }
26
26
  const rest = []
27
27
  for (let i = 0; i < argv.length; i++) {
28
28
  const a = argv[i]
@@ -37,6 +37,11 @@ function parseArgs(argv) {
37
37
  else if (a === '--list') { out.list = true }
38
38
  else if (a === '--cancel') { out.cancel = argv[++i] || '' }
39
39
  else if (a === '--status') { out.status = argv[++i] || '' }
40
+ else if (a === '--sessions') { out.sessions = argv[++i] || '' }
41
+ else if (a === '--session') { out.sessionId = argv[++i] || '' }
42
+ else if (a === '--find') { out.find = argv[++i] || '' }
43
+ else if (a === '--steer') { out.steer = true }
44
+ else if (a === '--force') { out.force = true }
40
45
  else { rest.push(a) }
41
46
  }
42
47
  if (!out.instruction && rest.length > 0) out.instruction = rest.join(' ')
@@ -152,39 +157,84 @@ async function main() {
152
157
  finish(0)
153
158
  }
154
159
  if (opts.cancel) {
155
- const res = await request('POST', GATEWAY + '/api/dsh-bridge/cancel', { taskId: opts.cancel })
160
+ const res = await request('POST', GATEWAY + '/api/dsh-bridge/cancel', { taskId: opts.cancel, force: opts.force === true })
156
161
  console.log(JSON.stringify(res.json, null, 2))
157
- finish(0)
162
+ finish(res.status === 409 ? 5 : 0)
158
163
  }
159
164
  if (opts.status) {
160
165
  const res = await request('GET', GATEWAY + '/api/dsh-bridge/status?taskId=' + encodeURIComponent(opts.status))
161
166
  console.log(JSON.stringify(res.json, null, 2))
162
167
  finish(0)
163
168
  }
169
+ if (opts.sessions) {
170
+ const res = await request('POST', GATEWAY + '/api/dsh-bridge/sessions', { query: opts.sessions, inspect: true, limit: 20 })
171
+ if (res.json === null || typeof res.json !== 'object') {
172
+ console.error('task.mjs: 网关返回 ' + res.status + ',响应不是 JSON —— profile 里的插件可能还是旧版本(session 接口需要 0.1.2+)')
173
+ console.error(String(res.text || '').slice(0, 200))
174
+ finish(1)
175
+ }
176
+ console.log(JSON.stringify(res.json, null, 2))
177
+ const items = Array.isArray(res.json.items) ? res.json.items : []
178
+ finish(items.length > 0 ? 0 : 4)
179
+ }
164
180
 
165
181
  const instruction = opts.instruction.trim()
166
182
  if (!instruction) {
167
183
  console.error('用法: node task.mjs --in "<指令>" [--cwd 目录] [--lane 名] [--model fast|pro] [--commit] [--timeout 秒] [--no-wait]')
168
- console.error('管理: node task.mjs --list | --status <taskId> | --cancel <taskId>')
184
+ console.error(' node task.mjs --in "<指令>" (--session <会话id> | --find "<标题关键词>") [--steer] # 投递进已有会话')
185
+ console.error('查找: node task.mjs --sessions "<标题关键词>" # 只读列出候选, 拿 sessionId 再派活')
186
+ console.error('管理: node task.mjs --list | --status <taskId> | --cancel <taskId> [--force]')
187
+ console.error('退出码: 0 成功 / 1 失败 / 2 用法 / 3 超时 / 4 会话未命中或不唯一 / 5 session 目标取消被拒')
169
188
  finish(2)
170
189
  }
171
190
  const cwd = (opts.cwd || process.env.DSH_BRIDGE_CWD || '').trim()
172
- if (cwd) {
191
+ const sessionId = (opts.sessionId || '').trim()
192
+ const find = (opts.find || '').trim()
193
+ const sessionTarget = sessionId !== '' ? { sessionId } : (find !== '' ? { sessionQuery: find } : null)
194
+ if (sessionTarget !== null && (cwd !== '' || opts.commit)) {
195
+ console.error('task.mjs: --session/--find 不能与 --cwd/--commit 同时使用(已有会话自带工作目录,本工具不替它提交)')
196
+ finish(2)
197
+ }
198
+ if (sessionTarget === null && cwd) {
173
199
  try { fs.mkdirSync(cwd, { recursive: true }) } catch (e) {
174
200
  console.error('task.mjs: 无法创建目录 ' + cwd + ':', e.message)
175
201
  finish(2)
176
202
  }
177
203
  }
178
- const history = readHistory()
204
+ const history = sessionTarget === null ? readHistory() : []
179
205
  const posted = await request('POST', GATEWAY + '/api/dsh-bridge/task', {
180
- instruction, history,
181
- ...(cwd ? { cwd } : {}),
182
- ...(opts.lane ? { lane: opts.lane } : {}),
183
- ...(opts.model ? { model: opts.model } : {}),
206
+ instruction,
207
+ ...(sessionTarget === null
208
+ ? {
209
+ ...{ history },
210
+ ...(cwd ? { cwd } : {}),
211
+ ...(opts.lane ? { lane: opts.lane } : {}),
212
+ ...(opts.model ? { model: opts.model } : {}),
213
+ }
214
+ : {
215
+ ...sessionTarget,
216
+ ...(opts.steer ? { deliver: 'steer' } : {}),
217
+ }),
184
218
  })
185
219
  const task = posted.json
220
+ if (task === null || typeof task !== 'object') {
221
+ console.error('task.mjs: 网关返回 ' + posted.status + ',响应不是 JSON —— profile 里的插件可能还是旧版本(session 目标需要 0.1.2+)')
222
+ console.error(String(posted.text || '').slice(0, 200))
223
+ finish(1)
224
+ }
186
225
  console.log(JSON.stringify({ phase: 'submitted', ...task }))
187
- if (posted.status !== 202 || task.status === 'error') finish(posted.status === 202 ? 0 : 1)
226
+ if (task.reason === 'session-ambiguous' || task.reason === 'session-query-empty') {
227
+ console.error('task.mjs: ' + (task.error || task.reason))
228
+ if (Array.isArray(task.candidates) && task.candidates.length > 0) {
229
+ console.error('候选(改用 --session <id> 指定其一):')
230
+ for (const c of task.candidates) console.error(' ' + c.sessionId + ' ' + c.snippet)
231
+ }
232
+ finish(4)
233
+ }
234
+ if (posted.status !== 202 || task.status === 'error') {
235
+ console.error('task.mjs: ' + (task.error || task.reason || 'submission failed'))
236
+ finish(1)
237
+ }
188
238
  if (!opts.wait) finish(0)
189
239
 
190
240
  const deadline = Date.now() + opts.timeoutSec * 1000
@@ -197,10 +247,13 @@ async function main() {
197
247
  console.log('=== DeepSeek 汇报 ===')
198
248
  console.log(status.result || status.error || '(无内容)')
199
249
  if (status.status === 'done') {
200
- appendHistory([
201
- { role: 'user', text: instruction },
202
- { role: 'assistant', text: status.result || '(无输出)' },
203
- ])
250
+ // session 目标的记忆就在那条会话里,不再往协作历史里塞一份。
251
+ if (sessionTarget === null) {
252
+ appendHistory([
253
+ { role: 'user', text: instruction },
254
+ { role: 'assistant', text: status.result || '(无输出)' },
255
+ ])
256
+ }
204
257
  if (opts.commit && cwd) gitCommit(cwd, '[task ' + task.taskId + '] ' + instruction)
205
258
  }
206
259
  finish(status.status === 'done' ? 0 : 1)