dsh-agent-board 1.0.1 → 1.0.2

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.
Files changed (2) hide show
  1. package/index.mjs +36 -9
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -34,7 +34,22 @@ export function apply(ctx) {
34
34
 
35
35
  // ===== 工具函数 =====
36
36
  function getActorId() { const a = ctx.agents; if (a) { const i = a.currentInitiator(); if (i) return String(i.id) } return 'unknown' }
37
- function resolveRoot(sid) { const agentsSvc = ctx.agents; if (!agentsSvc) return sid; var cur = sid; var roots = agentsSvc.roots(); var rids = []; for (var i = 0; i < roots.length; i++) rids.push(String(roots[i].id)); if (rids.indexOf(cur) >= 0) return cur; var all = agentsSvc.list(); var visited = {}; while (!visited[cur]) { visited[cur] = true; var o = null; for (var j = 0; j < all.length; j++) { if (agentsSvc.isOwnedBy(cur, all[j])) { o = all[j]; break } }; if (!o) break; cur = String(o.id); if (rids.indexOf(cur) >= 0) return cur }; return cur }
37
+ // resolveRoot 是同步热点(每个工具守卫/RPC/prompt 组装都调),agent 注册表可能有几百个子代理,
38
+ // 每次调用 roots()+list()+全表 isOwnedBy 扫描会反复卡宿主事件循环 → memoize 10s TTL。
39
+ // 父子归属在一个 agent 存活期内不变,10s 过期窗口足够安全。
40
+ var _rootCache = {}
41
+ function resolveRootUncached(sid) { const agentsSvc = ctx.agents; if (!agentsSvc) return sid; var cur = sid; var roots = agentsSvc.roots(); var rids = []; for (var i = 0; i < roots.length; i++) rids.push(String(roots[i].id)); if (rids.indexOf(cur) >= 0) return cur; var all = agentsSvc.list(); var visited = {}; while (!visited[cur]) { visited[cur] = true; var o = null; for (var j = 0; j < all.length; j++) { if (agentsSvc.isOwnedBy(cur, all[j])) { o = all[j]; break } }; if (!o) break; cur = String(o.id); if (rids.indexOf(cur) >= 0) return cur }; return cur }
42
+ function resolveRoot(sid) {
43
+ var now = Date.now()
44
+ var c = _rootCache[sid]
45
+ if (c && now - c.at < 10000) return c.root
46
+ var r = resolveRootUncached(sid)
47
+ // 缓存上限:超 512 个 key 时清掉过期项,防长进程累积
48
+ var keys = Object.keys(_rootCache)
49
+ if (keys.length > 512) { for (var i = 0; i < keys.length; i++) { if (now - _rootCache[keys[i]].at >= 10000) delete _rootCache[keys[i]] } }
50
+ _rootCache[sid] = { root: r, at: now }
51
+ return r
52
+ }
38
53
  function toolSessionId() { var sid = resolveRoot(getActorId()); touchSession(sid); return sid }
39
54
  function rpcSessionId(args) { var sid = (args && typeof args.sessionId === 'string' && args.sessionId.length > 0) ? args.sessionId : resolveRoot(getActorId()); touchSession(sid); return sid }
40
55
  // 已知会话集合:心跳驱动这些会话的 poolCycle(摆脱对客户端轮询的依赖)
@@ -212,8 +227,19 @@ export function apply(ctx) {
212
227
  // 批量聚合:任务多时每任务一条 followup 会把主窗口 turn 队列打满(用户输入排队等回执处理完才刷新),
213
228
  // 改为 45s 窗口(或满 5 条)聚合为一条摘要;发送前等主窗口空闲,不打断对话。
214
229
  var receiptBuf = {}
230
+ // 回执幂等去重表:key = 任务id + 类别 + 完成事件指纹(deliverable/verification/resolvedAt/末条history 时间戳)。
231
+ // 同一完成事件被任何路径(工具直报/run 结算/未来回归)重复通知时指纹一致 → 吞掉;
232
+ // 驳回后重做完成 → 时间戳全换新 → 指纹不同 → 正常回执。
233
+ var receiptedKeys = {}
215
234
  function notifyTaskDone(sid, t, kind) {
216
235
  if (!t || !isDispatched(sid, t.claimedBy)) return
236
+ var lastHist = (t.history && t.history.length) ? String(t.history[t.history.length - 1].timestamp || '') : ''
237
+ var stamp = [kind, (t.deliverable && t.deliverable.at) || '', (t.verification && t.verification.at) || '', t.resolvedAt || '', lastHist].join('|')
238
+ var key = t.id + ':' + stamp
239
+ if (receiptedKeys[key]) return
240
+ var rkeys = Object.keys(receiptedKeys)
241
+ if (rkeys.length > 512) { var rnow = Date.now(); for (var ri = 0; ri < rkeys.length; ri++) { if (rnow - receiptedKeys[rkeys[ri]] > 3600000) delete receiptedKeys[rkeys[ri]] } }
242
+ receiptedKeys[key] = Date.now()
217
243
  var buf = receiptBuf[sid] || (receiptBuf[sid] = { items: [], timer: null })
218
244
  var lastNote = (t.history && t.history.length) ? String(t.history[t.history.length - 1].note || '') : ''
219
245
  buf.items.push({ kind: kind, title: t.title, id: t.id, summary: (t.deliverable && t.deliverable.summary) || '', note: lastNote })
@@ -313,14 +339,12 @@ export function apply(ctx) {
313
339
  text: function (assembleCtx) {
314
340
  var agent = assembleCtx && assembleCtx.agent
315
341
  if (!agent) return ''
316
- var aid = String(agent.id || '')
317
- var sid = resolveRoot(aid)
318
- // P0 修复:只对 root agent 自身注入。Worker/Verifier 的 resolveRoot 也会返回主会话 sid,
319
- // 但 agent.id !== sid 说明是子代理——子代理有自己的 prompt 契约,不需要"你是主窗口"引导。
320
- // 之前缺这行守卫 → Worker 也收到"请用 task_create 派发任务"→ Worker 误认自己是主窗口。
321
- if (aid !== sid) return ''
322
- if (!teamModeCache[sid]) return ''
323
- return '【任务看板 Team 模式已开启】\n本会话的任务看板处于 Team 模式。请遵循以下工作方式:\n1. 涉及代码改动、文件创建、命令执行等实质性工作时,优先用 task_create 提交为看板任务(由一次性 Worker/Verifier 子代理执行与验收),不要自己直接动手实现。\n2. 你仍保有全部工具能力——调研、读代码、讨论方案、回答问题时直接进行,无需提交任务。\n3. 创建任务时,务必在 description 里写清子代理需要的上下文:涉及哪些文件(路径)、相关代码的约束/约定、前置条件等。子代理是全新会话、无你的会话记忆,如果上下文不够,子代理需要从零开始自行调研,效率会大打折扣甚至跑偏方向。\n4. Worker 上报歧义时会通过 task_arbitrate 等待你裁决,请及时响应。驳回重派时同样:新 Worker 没有上一轮的记忆,驳回原因会在 prompt 里,但额外上下文需你在 description 里补上。\n5. 任务尽量一次写清 description/dependsOn/acceptance;需要分步建设的用 task_create draft:true 先建草稿,补全后 publish。'
342
+ // O(1) 快路径:root agent 的 id 就是其会话 id(Agent.id === SessionId),
343
+ // teamModeCache 按会话 id 键——Worker/Verifier 自己的会话 id 不在缓存里,天然不会误注入。
344
+ // 不能走 resolveRoot:该函数在每个 agent 每次 prompt 组装时同步执行,
345
+ // resolveRoot 全表扫 agent 注册表会把宿主事件循环卡死(曾导致全局界面卡顿、用户消息延迟渲染)。
346
+ if (!teamModeCache[String(agent.id)]) return ''
347
+ return '【任务看板 Team 模式已开启】\n本会话的任务看板处于 Team 模式。请遵循以下工作方式:\n1. 涉及代码改动、文件创建、命令执行等实质性工作时,优先用 task_create 提交为看板任务(由一次性 Worker/Verifier 子代理执行与验收),不要自己直接动手实现。\n2. 你仍保有全部工具能力——调研、读代码、讨论方案、回答问题时直接进行,无需提交任务。\n3. 创建任务时,务必在 description 里写清子代理需要的上下文:涉及哪些文件(路径)、相关代码的约束/约定、前置条件等。子代理是全新会话、无你的会话记忆,如果上下文不够,子代理需要从零开始自行调研,效率会大打折扣甚至跑偏方向。\n4. Worker 上报歧义时会通过 task_arbitrate 等待你裁决,请及时响应。驳回重派时同样:新 Worker 没有上一轮的记忆,驳回原因会在 prompt 里,但额外上下文需你在 description 里补上。\n5. 创建多个相互关联的任务时,必须「先草稿后发布」:所有任务先用 task_create draft:true 建为草稿(草稿不会被派发领取),等全部任务创建完成、dependsOn 依赖关系都写好后,再逐个 task_update publish=true 统一发布。严禁直接创建 pending 任务再后补依赖——依赖还没写入,后向任务就会被 Worker 提前领走。'
324
348
  },
325
349
  })
326
350
  ctx.effect(function () { return disposeSection })
@@ -514,6 +538,9 @@ export function apply(ctx) {
514
538
  } else if (args.op === 'set-priority') {
515
539
  if (['low', 'medium', 'high', 'critical'].indexOf(args.value) < 0) { skipped.push(id); return }
516
540
  t.priority = args.value; ah(t, t.status, t.status, actor, '批量设优先级: ' + args.value); done++
541
+ } else if (args.op === 'publish') {
542
+ if (t.status !== 'draft') { skipped.push(id); return }
543
+ t.status = 'pending'; ah(t, 'draft', 'pending', actor, 'batch publish'); done++
517
544
  } else { skipped.push(id) }
518
545
  })
519
546
  return { ok: true, done: done, skipped: skipped }
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.0.1",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
7
7
  "scripts": {