dsh-agent-board 1.2.3 → 1.2.5

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
@@ -1,43 +1,19 @@
1
- # dsh-agent-board
1
+ # Task Board Plugin for DeepSeek Harness
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/dsh-agent-board.svg)](https://www.npmjs.com/package/dsh-agent-board)
3
+ [![npm](https://img.shields.io/npm/v/dsh-agent-board)](https://www.npmjs.com/package/dsh-agent-board)
4
4
  [![npm downloads](https://img.shields.io/npm/dw/dsh-agent-board.svg)](https://www.npmjs.com/package/dsh-agent-board)
5
5
  [![node](https://img.shields.io/node/v/dsh-agent-board.svg)](https://www.npmjs.com/package/dsh-agent-board)
6
- [![license](https://img.shields.io/npm/l/dsh-agent-board.svg)](https://github.com/PPawnsir/task-board-plugin/blob/main/LICENSE)
6
+ [![license](https://img.shields.io/npm/l/dsh-agent-board)](https://github.com/PPawnsir/task-board-plugin/blob/main/LICENSE)
7
7
  ![category](https://img.shields.io/badge/awesome--dsh--plugin-workflow-blue)
8
8
 
9
- DeepSeek Harness 智能看板 — 让主窗口把编码工作变成可追踪、可验收、可审计的任务,而不是一段做完就散的对话。
10
-
11
- ## 背景与动机
12
-
13
- 直接在会话里让 agent 干活,随着任务变多变复杂,会遇到四个典型问题:
14
-
15
- 1. **长任务挤占对话** —— 一个改半小时的任务会让主窗口在这期间既不能干别的,也无法并行处理其他请求;多个任务只能排队串行。
16
- 2. **干完没有验收** —— agent 说"完成了"就是完成了,改动是否真的可跑、是否满足要求,没有第二双眼睛独立复核过。
17
- 3. **上下文反复调研** —— 每个新任务都从零读代码、查约定,主窗口调研过的结论和读过的文件无法带给执行者,时间都花在重复调研上。
18
- 4. **过程不可见、结果不可审计** —— 任务散落在对话流里:谁做的、卡在哪、被驳回过几次、为什么这么做,事后无从回溯。
19
-
20
- 看板针对这四点给出机制化的解法:
21
-
22
- | 问题 | 解法 |
23
- | --- | --- |
24
- | 挤占对话 | 每个任务派发给**一次性 Worker 子代理**独立执行,主窗口保持可交互;任务之间依赖调度、可并行 |
25
- | 没有验收 | **独立 Verifier 子代理**复核:Worker 必须实跑自测,Verifier 独立复跑验收脚本后给出结论;驳回自动重派,三次驳回升级人工 |
26
- | 重复调研 | 主窗口调研过的**结论/思路(contextNotes)和读过的文件(contextFiles)**随任务注入子代理的上下文注入区,子代理不必从零开始 |
27
- | 不可见不可审计 | 任务卡全程留痕:状态流转历史、每次验收结论、歧义上报与裁决、干预记录都在任务详情里;Worker/Verifier 会话可从详情页直达回看 |
28
-
29
- ## 核心机制
30
-
31
- - **看板 UI**:会话标题栏「智能看板」按钮打开顶部抽屉(看板 / 团队 / 仪表盘三视图),拖拽流转,多选批量操作(带一步撤销),筛选(文本/优先级/标签)
32
- - **任务模型**:draft → pending → in-progress → verifying → resolved → archived,含 blocked/cancelled;优先级、标签、依赖(DFS 环检测)、流水线分档(full 执行+验证 / work 只做不验 / direct 主窗口直办)
33
- - **一次性派发**:每个任务 spawn 一个一次性子代理,做完即销毁——无常驻池、无状态残留;孤儿回收 + 看门狗卡死标记(不自动杀,裁决权在主窗口/用户)
34
- - **Team 模式**:开启后向主窗口注入提示词引导(实质性改动建议走看板派发,不硬拦截);Worker 遇到歧义上报主窗口裁决(任何模式都通知);任务完成/阻塞时主窗口收到批量聚合回执(等空闲再发,不打断对话)
35
- - **异构模型**:Worker/Verifier 均可下拉配置不同模型(避免同源盲点),默认空 = 继承父级模型;模型故障自动熔断回退父级
36
- - **手动/自动模式**:自动模式看板自动调度派发;手动模式主窗口自行 claim,详情页可「派发 / 派发验收」手动触发
9
+ 智能看板插件 — Agent 自主任务驱动开发:看板管理 + 一次性 Worker/Verifier 派发 + 依赖调度 + Team 模式。
37
10
 
38
11
  ## 安装
39
12
 
40
- > 宿主要求:Node ≥ 22;DSH ≥ `0.1.5-rc.1`(已通过 `peerDependencies` 声明,含预发布分支的版本范围见 package.json)
13
+ ### 前置条件
14
+
15
+ - DeepSeek Harness(dsh)已安装并能正常启动:`dsh --profile web`
16
+ - Node.js ≥ 22(与 dsh 运行时一致)
41
17
 
42
18
  ### 版本兼容性(先按宿主选插件版本)
43
19
 
@@ -46,8 +22,6 @@ DeepSeek Harness 智能看板 — 让主窗口把编码工作变成可追踪、
46
22
  | **≥ 0.1.7**(含 rc) | **≥ 1.2.2(必须)** | 0.1.7 会话日志升级为 format v4:插件消息 `source.kind` 必须是生产者自有 kind。1.2.1 及更早会在**任务完成/阻塞回执**落盘时抛 `SessionFormatError: format v4 message requires a producer-owned source kind`,并连带使主窗口当前轮次失败(表现为「本轮运行失败」);同时「跳转会话」因宿主移除 `sessions.open` 而失效(控制台 `sessionsSvc.open is not a function`),卡片活动心跳读不到 v4 日志(`session.v4.jsonl.zstd`) |
47
23
  | 0.1.5-rc.1 ~ 0.1.6 | ≤ 1.2.1 | 1.2.2 起按 0.1.7 协议编写(v4 source kind、`uiWorkspace.openSession` 跳转),旧宿主未做回归验证,建议停留 1.2.1 |
48
24
 
49
- 指定版本安装:
50
-
51
25
  ```sh
52
26
  dsh plugin --profile web add dsh-agent-board@1.2.2 # 0.1.7+ 宿主
53
27
  dsh plugin --profile web add dsh-agent-board@1.2.1 # 0.1.5/0.1.6 宿主
@@ -55,23 +29,177 @@ dsh plugin --profile web add dsh-agent-board@1.2.1 # 0.1.5/0.1.6 宿主
55
29
 
56
30
  > 从 ≤1.2.1 升到 ≥1.2.2 必须重启 DSH(host 端代码在启动时加载;1.2.2 之前的老版本还有一个路由残留 bug:禁用/启用热重载会撞 `duplicate exact route`,只能重启恢复,1.2.2 已修复)。
57
31
 
32
+ ### 从插件市场安装(推荐)
33
+
34
+ 已发布至 npm 官方 registry([dsh-agent-board](https://www.npmjs.com/package/dsh-agent-board)):
35
+
58
36
  ```sh
59
37
  dsh plugin --profile web add dsh-agent-board
38
+ # 重启 dsh 生效
39
+ dsh --profile web
40
+ ```
41
+
42
+ ### 从源码安装(开发/调试)
43
+
44
+ ```sh
45
+ git clone https://github.com/PPawnsir/task-board-plugin.git
46
+ dsh plugin --profile web add <本仓库绝对路径>/packages/dsh-agent-board
47
+ # 重启 dsh
60
48
  ```
61
49
 
62
- 重启 DSH 后生效。数据存于 `~/.dsh/tasks-<sessionId>.json`,卸载不删数据。
50
+ ### 验证安装
63
51
 
64
- ## 升级
52
+ 1. 启动日志无 `plugin tree failed to load`
53
+ 2. 打开任意会话,标题栏出现 **智能看板** 按钮(Lucide 线性图标)
54
+ 3. RPC 路由可用:
65
55
 
66
56
  ```sh
57
+ curl -X POST http://127.0.0.1:3080/dsh-agent-board \
58
+ -H "Content-Type: application/json" \
59
+ -d '{"method":"get-tasks","args":{"sessionId":"<sessionId>"}}'
60
+ ```
61
+
62
+ 返回 JSON 即正常;405 空 body 说明路由未挂载。
63
+
64
+ ### 升级
65
+
66
+ ```sh
67
+ # 市场版
67
68
  dsh plugin --profile web add dsh-agent-board@latest
68
- # 重启 DSH
69
+ # 源码版
70
+ git pull
71
+ # 两者都需重启 dsh
69
72
  ```
70
73
 
71
- ## 源码与文档
74
+ ### 卸载
75
+
76
+ ```sh
77
+ dsh plugin --profile web remove dsh-agent-board
78
+ # 重启 dsh
79
+ ```
80
+
81
+ > 看板数据存在 `~/.dsh/tasks-<sessionId>.json`,卸载不删数据。
82
+
83
+ ## 功能总览
84
+
85
+ ### 看板 UI
86
+
87
+ - 会话标题栏「智能看板」按钮 → 顶部抽屉面板(看板 / 团队 / 仪表盘三视图)
88
+ - 六列状态流:草稿 → 待办 → 进行中 → 验证中 → 已完成 → 阻塞
89
+ - 拖拽流转、多选批量操作(带一步撤销)、文本/优先级/标签筛选
90
+ - 归档区:时间倒序 + 排序选择器 + 纵向滚动
91
+ - Esc 逐级关闭(详情 → 看板 → 面板)
92
+ - 全部结构性图标为 Lucide 线性 SVG(`currentColor` 跟随主题,浅深色自适应)
93
+
94
+ ### 任务模型
95
+
96
+ ```
97
+ draft → pending → in-progress → verifying → resolved → archived
98
+ ↓ ↑
99
+ blocked ←────── reject
100
+ ```
101
+
102
+ - **草稿态(draft)**:创建时可先进草稿,补全描述/依赖后再发布,杜绝"半成品被派发"
103
+ - **依赖调度**:`dependsOn` 声明依赖(DFS 环检测),依赖全部完成后才会被派发,串行链路自动编排
104
+ - **管线分档**:`full`(执行+验证)/ `work`(只做不验)/ `direct`(不进池,主窗口直接处理),创建时按规则自动分类、可手动覆盖
105
+ - **硬性验收**:`acceptance` 字段写验收脚本命令,Worker 必须实际运行、Verifier 必须独立复跑
106
+ - **子任务**:父子层级 + 上下文继承 + 父任务自动流转 + 级联归档
107
+
108
+ ### 一次性派发(v74 去池化)
109
+
110
+ - 每个任务 spawn 一个**一次性子代理**(Worker/Verifier),上下文全量注入 prompt,做完即销毁——无常驻池、无池化状态残留
111
+ - **Worker/Verifier 均可配置异构模型**(⚙️ 弹出层下拉选择,空 = 继承父级),避免同源盲点;模型故障自动熔断回退父级模型
112
+ - 孤儿回收:子代理 run 结束/丢失超 2 分钟 → 任务自动回待办重派
113
+ - 看门狗:运行超时且事件流停滞 → 标记"疑似卡死"(不自动杀,裁决权交主窗口/用户)
114
+ - 歧义上报:Worker 遇到歧义不猜测,上报等主窗口裁决(任何模式下都通知);裁决后新 Worker 携带裁决答案接手
115
+ - 手动派发:详情页「派发 / 派发验收」按钮可随时手动触发单任务派发(auto 模式补派、manual 模式主通道)
116
+ - 会话隔离:看板按会话分桶,多会话互不干扰
117
+
118
+ ### 手动 / 自动派发模式
119
+
120
+ | | 🤖 自动 | 👤 手动 |
121
+ |---|---|---|
122
+ | Worker 派发 | poolCycle 自动调度(并发上限可配) | 主窗口自行 claim 处理,或详情页手动「派发」 |
123
+ | Verifier 派发 | 自动 | **自动**(主窗口手动做完的 full 档任务也会自动验收) |
124
+ | 孤儿回收 | 开启 | 开启 |
125
+
126
+ Team 模式开启时强制自动派发(防止"引导派发 + 手动模式"死锁组合)。
127
+
128
+ ### Team 模式
129
+
130
+ 开启后(Team 开关):
131
+ - 主窗口 system prompt 注入派发引导(提示词层面建议实质性改动走看板,不硬拦截)
132
+ - 引导含**上下文书写提示**:子代理是全新会话、无会话记忆,description 写不够会自行调研跑偏
133
+ - Worker 歧义自动上报主窗口聊天流,等待裁决
134
+ - 任务完成/阻塞时主窗口收到**批量聚合回执**(45s 窗口或满 5 条聚合,等主窗口空闲再发,不打断对话)
135
+
136
+ ## 13 个 Agent 工具
137
+
138
+ | 类别 | 工具 |
139
+ |---|---|
140
+ | 任务管理 | `task_create` / `task_list` / `task_context` / `task_update` / `task_claim` / `task_resolve` / `task_verify` / `task_archive` |
141
+ | 池治理 | `task_terminate` / `task_intervene` / `task_arbitrate` |
142
+ | 子代理上报 | `board_report` / `board_verdict` |
143
+
144
+ > 管理工具仅主窗口可用(子代理调用会被拒绝);`board_report`/`board_verdict` 是子代理的专用上报通道。
145
+
146
+ ## 仓库结构
147
+
148
+ ```
149
+ └── packages/dsh-agent-board/ # 插件全部源码(直接维护,无构建步骤)
150
+ │ ├── index.mjs # host 端:IO 编排(工具/RPC/一次性派发引擎接线)
151
+ │ ├── lib/core.mjs # 纯逻辑核心:状态机/依赖/分类/prompt/解析(无 IO,可单测)
152
+ │ ├── lib/client.js # client 端(ModuleLoader 包装,图标统一走 ICONS + ic())
153
+ │ ├── test/core.test.mjs # 单元测试(node --test,30 例)
154
+ │ ├── package.json # dsh.bundle.patch + dsh.client 元数据
155
+ │ └── cordis.patch.yml # bundle 挂载行
156
+ └── docs/
157
+ ├── PRD.md # 产品需求文档
158
+ ├── PACKAGING.md # 打包/安装踩坑记录(link 依赖、单例隔离等)
159
+ ├── icon-style-guide.md # 图标风格指南(Lucide 线性 SVG + emoji 分界)
160
+ └── REGRESSION-v59.md # 端到端回归测试记录
161
+ ```
162
+
163
+ > v68 起拆除了"动态源码 → 静态包"的转换层(build-pkg.cjs):插件已稳定,
164
+ > 双形态维护的复杂度大于收益,包内文件即唯一源码,改完重启 dsh 即生效。
165
+ >
166
+ > v74 起去池化(一次性派发)+ 纯逻辑抽到 `lib/core.mjs`,跑
167
+ > `node --test packages/dsh-agent-board/test/` 即可验证状态机/依赖/派发决策,
168
+ > 不用重启 dsh 人肉回归。
169
+
170
+ ## 文档
171
+
172
+ - [docs/PRD.md](docs/PRD.md) — 完整产品需求文档
173
+ - [docs/PACKAGING.md](docs/PACKAGING.md) — 正式安装(Bundle 打包)注意事项
174
+ - [docs/icon-style-guide.md](docs/icon-style-guide.md) — 图标规范
175
+ - [docs/REGRESSION-v59.md](docs/REGRESSION-v59.md) — 回归测试说明
176
+
177
+ ## 发布新版本(维护者)
178
+
179
+ tag 驱动,GitHub Actions 自动发布到 npm(`.github/workflows/publish.yml`)。两种打 tag 方式都支持:
180
+
181
+ **方式 1:命令行**
182
+
183
+ ```sh
184
+ cd packages/dsh-agent-board
185
+ npm version patch # 或 minor / major——改 package.json
186
+ git add -A && git commit -m 'release: vX.Y.Z' && git tag vX.Y.Z
187
+ git push --follow-tags # tag 推送触发流水线
188
+ ```
189
+
190
+ **方式 2:GitHub 网页(Releases 页)**
191
+
192
+ 1. 先把 `packages/dsh-agent-board/package.json` 的 `version` 改成目标版本并合入 main(网页直接编辑即可)
193
+ 2. 仓库页 → **Releases** → **Draft a new release** → **Choose a tag** → 输入 `vX.Y.Z` 选 **Create new tag**(target 选 main)
194
+ 3. 点 **Publish release** —— 触发发布流水线
195
+
196
+ - 流水线会拒绝与 tag 不一致的 `package.json` version(如 tag `v1.0.1` 但包里是 `1.0.0`),防止版本错位
197
+ - **README 单一来源**:本文件(根 README)即唯一来源;发版前在 `packages/dsh-agent-board` 跑一次 `npm run sync-readme` 同步进包(npm 页面展示的是包内 README)
198
+ - 需在仓库 **Settings → Secrets and variables → Actions** 配置 `NPM_TOKEN`
199
+ (npm granular access token:bypass 2FA + direct publish)
200
+ - 日常 push / PR 有 `test.yml` 跑语法检查 + 30 例单测
201
+ - 本地手动发布仍然可用:`npm publish --registry=https://registry.npmjs.org`(本机默认源是镜像时必须显式指定)
202
+
203
+ ## License
72
204
 
73
- - 仓库:<https://github.com/PPawnsir/task-board-plugin>
74
- - 本包即源码,直接维护(v68 起拆除了动态→静态转换层,v74 起去池化)
75
- - 单元测试:`npm test`(node --test,39 例纯逻辑用例)
76
- - E2E 回归:`npm run e2e -- --session <会话id>`(5 场景 21 断言,驱动真实实例)
77
- - 改源码后重启 DSH 生效(host);`lib/client.js` 改动在 dsh ≥0.1.5-rc.2 下同样需重启(client bundle 启动时组合缓存)
205
+ MIT
package/index.mjs CHANGED
@@ -83,7 +83,26 @@ export function apply(ctx) {
83
83
  }
84
84
  // 原子写盘:先写临时文件再 rename——强杀若发生在写盘中途,磁盘上最多留个 .tmp 残件,
85
85
  // 看板本体永远不会是截断的半个 JSON(此前非原子直写,kill 中写 = 看板被 seed 清空)
86
- async function wt(sid, d) { var c = JSON.stringify(d); var p = boardPath(sid); var tmp = p + '.tmp'; try { await fsNode.promises.writeFile(tmp, c, 'utf8'); await fsNode.promises.rename(tmp, p) } catch (e) { console.error('[task-board] write:', String(e)); throw e } }
86
+ // Windows 特有问题:rename 目标被并发读句柄/Defender/索引器短暂占用时抛 EPERM/EBUSY
87
+ // (E2E 实测:GUI 3s 轮询 + 脚本 10s 轮询下偶发,publish 写入整个丢失)。
88
+ // 对这类瞬时占用做有限退避重试;其他错误(只读/不存在目录等)直接抛。
89
+ async function wt(sid, d) {
90
+ var c = JSON.stringify(d); var p = boardPath(sid); var tmp = p + '.tmp'
91
+ var lastErr = null
92
+ for (var attempt = 0; attempt < 6; attempt++) {
93
+ try { await fsNode.promises.writeFile(tmp, c, 'utf8'); await fsNode.promises.rename(tmp, p); return }
94
+ catch (e) {
95
+ lastErr = e
96
+ var code = e && e.code
97
+ if (code === 'EPERM' || code === 'EBUSY' || code === 'ENOTEMPTY' || code === 'EACCES') {
98
+ await new Promise(function (r) { setTimeout(r, 60 * (attempt + 1)) })
99
+ continue
100
+ }
101
+ console.error('[task-board] write:', String(e)); throw e
102
+ }
103
+ }
104
+ console.error('[task-board] write: retry exhausted (6 次) ->', String(lastErr)); throw lastErr
105
+ }
87
106
  // 每会话一条 promise 链,串行化所有 读-改-写,消除并发写竞争
88
107
  var fileLocks = {}
89
108
  function withLock(sid, fn) { var prev = fileLocks[sid] || Promise.resolve(); var p = prev.then(function () { return fn() }); fileLocks[sid] = p.catch(function () {}); return p }
@@ -668,16 +687,27 @@ export function apply(ctx) {
668
687
  })
669
688
  return result
670
689
  }
671
- // 高优介入(v74):有活跃 run 则直接 followup 进其会话;无则只记录 history(下次派发随 prompt 注入)
690
+ // 高优介入(v74):有活跃 run 则直接打进其会话;无则只记录 history(下次派发随 prompt 注入)
691
+ // 通道选择(v1.2.4):steer 优先——下一个 step 边界即消费;followup 要等整个 turn 结束,
692
+ // 长 turn 下干预形同失联(实测:Worker 单 turn 跑 10+ 分钟,「口径重写」类干预到位时活已按旧口径干完)。
693
+ // steer 不可用(老宿主无此方法)或抛错时回退 followup。
672
694
  async function doIntervene(sid, actor, taskId, msg) {
673
695
  if (!(msg || '').trim()) return { ok: false, error: 'message required' }
674
696
  var rec = runsFor(sid)[taskId]
675
697
  var delivered = false
698
+ var channel = ''
676
699
  if (rec && rec.run && rec.run.localAgent) {
677
- try { rec.run.localAgent.followup(makeMsg('[高优先级干预] 来自主窗口/用户的指令:\n\n' + msg + '\n\n请优先响应此指令,然后继续当前任务。', 'notice', '高优干预: ' + taskId)); delivered = true } catch (_) {}
700
+ var agent = rec.run.localAgent
701
+ var m = makeMsg('[高优先级干预] 来自主窗口/用户的指令:\n\n' + msg + '\n\n请优先响应此指令,然后继续当前任务。', 'notice', '高优干预: ' + taskId)
702
+ if (typeof agent.steer === 'function') {
703
+ try { agent.steer(m); delivered = true; channel = 'steer' } catch (_) {}
704
+ }
705
+ if (!delivered) {
706
+ try { agent.followup(m); delivered = true; channel = 'followup' } catch (_) {}
707
+ }
678
708
  }
679
709
  await mutateLocked(sid, function (d) { var t = d.tasks.find(function (x) { return x.id === taskId }); if (t) { if (!Array.isArray(t.messages)) t.messages = []; t.messages.push({ kind: 'intervention', text: msg, at: new Date().toISOString(), by: actor }); ah(t, t.status, t.status, actor, '高优干预: ' + msg.slice(0, 200) + (delivered ? '' : '(无活跃 run,随下次派发注入)')) }; return t })
680
- return { ok: true, delivered: delivered }
710
+ return { ok: true, delivered: delivered, channel: channel }
681
711
  }
682
712
  // 终止执行某任务的 run:dispose 并回 pending(verifying 则保持待审,由新 verifier 接手)
683
713
  async function doTerminate(sid, actor, taskId) {
@@ -735,7 +765,7 @@ export function apply(ctx) {
735
765
  handle('intervene-agent', async function (args) { return doIntervene(rpcSessionId(args), getActorId(), args.taskId, args.message) })
736
766
  // 主 Agent 工具版(Team 模式下主 Agent 通过工具裁决/介入)
737
767
  ctx.tools.register(defineTool({ name: 'task_arbitrate', description: 'Team 模式:裁决 Worker 上报的歧义(escalation)。答案直接转达给原 Worker 继续执行。', parameters: { type: 'object', properties: { taskId: { type: 'string' }, answer: { type: 'string' } }, required: ['taskId', 'answer'] }, output: jo(), execute: async function (args) { var __ra = getActorId(); if (resolveRoot(__ra) !== __ra) return { ok: false, error: '看板管理工具仅主窗口可用(子代理无看板权限)' }; return doResolveEscalation(toolSessionId(), getActorId(), args.taskId, args.answer) } }))
738
- ctx.tools.register(defineTool({ name: 'task_intervene', description: 'Team 模式:向执行某任务的池中 Agent 发起高优先级指令(插入其队列头部,当前 turn 结束后优先处理)。', parameters: { type: 'object', properties: { taskId: { type: 'string' }, message: { type: 'string' } }, required: ['taskId', 'message'] }, output: jo(), execute: async function (args) { var __ra = getActorId(); if (resolveRoot(__ra) !== __ra) return { ok: false, error: '看板管理工具仅主窗口可用(子代理无看板权限)' }; return doIntervene(toolSessionId(), getActorId(), args.taskId, args.message) } }))
768
+ ctx.tools.register(defineTool({ name: 'task_intervene', description: 'Team 模式:向执行某任务的池中 Agent 发起高优先级指令(steer 通道,当前 step 结束即响应;无活跃 run 时记录随下次派发注入)。', parameters: { type: 'object', properties: { taskId: { type: 'string' }, message: { type: 'string' } }, required: ['taskId', 'message'] }, output: jo(), execute: async function (args) { var __ra = getActorId(); if (resolveRoot(__ra) !== __ra) return { ok: false, error: '看板管理工具仅主窗口可用(子代理无看板权限)' }; return doIntervene(toolSessionId(), getActorId(), args.taskId, args.message) } }))
739
769
  // ===== 池中 Agent 结构化回报工具(双模:工具优先,文本分段为降级路径)=====
740
770
  ctx.tools.register(defineTool({ name: 'board_report', description: '[任务看板 Worker 专用] 上报任务结果。kind=complete 时填 summary/changes/selfTest/diffStat(git 仓库内改动附 git diff --stat 概要);kind=escalate 时填 question(歧义上报,等待主窗口裁决)。', parameters: { type: 'object', properties: { taskId: { type: 'string' }, kind: { type: 'string', enum: ['complete', 'escalate'] }, summary: { type: 'string' }, changes: { type: 'string' }, selfTest: { type: 'string' }, diffStat: { type: 'string', description: '变更概要:git diff --stat(含 git status --short)输出,≤1500 字符' }, question: { type: 'string' } }, required: ['taskId', 'kind'] }, output: jo(), execute: async function (args) {
741
771
  var sid = toolSessionId(); var actor = getActorId()
package/lib/core.mjs CHANGED
@@ -139,7 +139,7 @@ export function buildWorkerPrompt(t, pack) {
139
139
  if (notes) p += '\n\n该任务的过程记录(歧义上报/主窗口裁决/驳回/干预,请务必遵循最新裁决方向):\n' + notes
140
140
  if (msgs) p += '\n\n该任务的详细消息(裁决答案/干预指令/歧义原文等,请务必遵循):\n' + msgs
141
141
  if (pack) p += '\n\n' + pack
142
- p += '\n\n完成契约(双模,工具优先):\n1. 完成时:优先调用 board_report 工具(kind=complete, taskId=' + t.id + ',summary=开发描述/changes=改动清单/selfTest=自测情况/diffStat=变更概要);工具不可用则按分段格式输出(## 开发描述 / ## 改动清单 / ## 自测情况 / ## diff 概要)。\n diffStat 要求:若本次改动发生在 git 仓库内,运行 git diff --stat(含 git status --short),把输出贴进 diffStat(≤1500 字符);关键逻辑变更可附 ≤20 行核心片段。非代码任务/无 git 仓库可省略。\n2. 歧义/信息不足/需用户决策时:优先调用 board_report(kind=escalate, taskId=' + t.id + ', question=疑问);工具不可用则输出以 [ESCALATE] 开头的说明。不要猜测。上报歧义后直接结束本轮——裁决后会有新 Worker 带着裁决答案接手。'
142
+ p += '\n\n完成契约(双模,工具优先):\n1. 完成时:优先调用 board_report 工具(kind=complete, taskId=' + t.id + ',summary=开发描述/changes=改动清单/selfTest=自测情况/diffStat=变更概要);工具不可用则按分段格式输出(## 开发描述 / ## 改动清单 / ## 自测情况 / ## diff 概要)。\n diffStat 要求:若本次改动发生在 git 仓库内,运行 git diff --stat(含 git status --short),把输出贴进 diffStat(≤1500 字符);关键逻辑变更可附 ≤20 行核心片段。非代码任务/无 git 仓库可省略。\n **board_report 调用成功即任务终点:立即结束输出,不要再修改/验证任何文件**。上报后任务即刻进入验收,你继续改动会让代码在验收口径之外漂移、且阻塞 Verifier 派发(实测有 Worker 上报后又自测 16 分钟);上报后发现新问题的,写进 selfTest 备注交由 Verifier/主窗口裁决。\n2. 歧义/信息不足/需用户决策时:优先调用 board_report(kind=escalate, taskId=' + t.id + ', question=疑问);工具不可用则输出以 [ESCALATE] 开头的说明。不要猜测。上报歧义后直接结束本轮——裁决后会有新 Worker 带着裁决答案接手。'
143
143
  return p
144
144
  }
145
145
  export function buildVerifierPrompt(t, pack) {
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "dsh-agent-board",
3
3
  "description": "DeepSeek Harness 智能看板:任务卡片 + 一次性 Worker/Verifier 派发 + 依赖调度 + 管线分档 + Team 模式(歧义上报主窗口裁决)",
4
- "version": "1.2.3",
4
+ "version": "1.2.5",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
7
7
  "scripts": {
8
8
  "test": "node --test test/core.test.mjs",
9
+ "sync-readme": "node -e \"require('fs').copyFileSync('../../README.md','README.md')\"",
9
10
  "prepublishOnly": "node --check index.mjs && node --check lib/client.js && node --check lib/core.mjs && npm test",
10
11
  "e2e": "node scripts/e2e.cjs"
11
12
  },