dsh-agent-board 1.2.2 → 1.2.4

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
@@ -39,6 +39,22 @@ DeepSeek Harness 智能看板 — 让主窗口把编码工作变成可追踪、
39
39
 
40
40
  > 宿主要求:Node ≥ 22;DSH ≥ `0.1.5-rc.1`(已通过 `peerDependencies` 声明,含预发布分支的版本范围见 package.json)
41
41
 
42
+ ### 版本兼容性(先按宿主选插件版本)
43
+
44
+ | 宿主 DSH 版本 | 应装插件版本 | 原因 |
45
+ | --- | --- | --- |
46
+ | **≥ 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
+ | 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
+
49
+ 指定版本安装:
50
+
51
+ ```sh
52
+ dsh plugin --profile web add dsh-agent-board@1.2.2 # 0.1.7+ 宿主
53
+ dsh plugin --profile web add dsh-agent-board@1.2.1 # 0.1.5/0.1.6 宿主
54
+ ```
55
+
56
+ > 从 ≤1.2.1 升到 ≥1.2.2 必须重启 DSH(host 端代码在启动时加载;1.2.2 之前的老版本还有一个路由残留 bug:禁用/启用热重载会撞 `duplicate exact route`,只能重启恢复,1.2.2 已修复)。
57
+
42
58
  ```sh
43
59
  dsh plugin --profile web add dsh-agent-board
44
60
  ```
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,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-agent-board",
3
3
  "description": "DeepSeek Harness 智能看板:任务卡片 + 一次性 Worker/Verifier 派发 + 依赖调度 + 管线分档 + Team 模式(歧义上报主窗口裁决)",
4
- "version": "1.2.2",
4
+ "version": "1.2.4",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
7
7
  "scripts": {