dsh-agent-board 1.9.2 → 1.9.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 +6 -4
- package/lib/client/task-detail.js +30 -1
- package/lib/client.js +30 -1
- package/lib/core.mjs +18 -1
- package/lib/dispatch.mjs +13 -2
- package/lib/hung.mjs +11 -0
- package/lib/policy.mjs +34 -3
- package/lib/rpc.mjs +52 -6
- package/lib/settle.mjs +136 -1
- package/package.json +2 -2
- package/test/core.test.mjs +240 -2
- package/test/hung-frame-signal.test.mjs +197 -0
- package/test/settle-stop-interrupt.test.mjs +172 -0
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
| **0.1.7 ~ 0.1.7-x**(含 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`) |
|
|
42
42
|
| 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 |
|
|
43
43
|
|
|
44
|
-
> 当前版本 **v1.9.
|
|
44
|
+
> 当前版本 **v1.9.4** 已在宿主 **0.2.1-rc.5** 全量实测:746 断言全绿 + 拆解官/力度路由/健康嗅探/帧活性挂死检测真机 E2E 全链。
|
|
45
45
|
|
|
46
46
|
```sh
|
|
47
47
|
dsh plugin --profile web add dsh-agent-board@latest # 0.1.7+/0.2.x 宿主(推荐)
|
|
@@ -120,7 +120,7 @@ dsh plugin --profile web remove dsh-agent-board
|
|
|
120
120
|
- **看板**:六列状态流、拖拽 / 多选批量 / 文本·优先级·标签筛选、归档留档、会话隔离
|
|
121
121
|
- **派发**:一次性 Worker / 独立 Verifier、依赖调度(DFS 环检测)、touches 文件级排他、可续跑 Worker、Team 托管、**spawn 落闸锁内重验**(spawnGateOk:claim 占位与落闸决策读同一把锁的同一份快照,terminate/dependsOn 后补 落在 spawn 在途窗口即弃权——竞态与后补依赖双双根治)、**高优干预分态**(intervene 无活跃 run 时区分「续跑窗口,干预随续跑注入」与「真无 run」,返回体带 state 字段)、**建卡派发预警**(将自动派发时 lintWarnings 瞬态提示「约 1s 内被领取,想占位请 draft:true」)、**宿主 spawn API 双兼容**(v0.2.1 宿主把 `subagents.start/startContinuable` 统一重构为 `startActivation(spec)`——能力探测自动选路:新契约按 `delivery` 语义分发(continuable=`parent` 结算留言 / 一次性=`caller` 只回 result),Activation 适配旧 run 形状下游零改动;老宿主逐字走旧双路径)+ **continuable 结算兜底臂**(`activation.result` 在子会话被宿主拆除时落定即接失败结算——rate-limit/模型崩溃的死子不再挂到硬超时)
|
|
122
122
|
- **智能调度** `☆`:**拆解官工作流**(自然语言需求卡 → `task_decompose` 拆解官先调研后提案(outputSchema 强箍,子卡必带档位/touches/验收命令 + **dependsOn 序号接线**:提案内以 1 起序号互相引用,物化时映射真实任务 id,前排约束天然无环、坏序号丢弃留痕)→ 主窗口呈现、你确认调档 → 逐张 publish;拆解官在池条「🧩 拆解中」与源卡徽标可见;**落定走正式回执**:「🧩 已拆解 N 个(待确认发布)」聚合分区——split 列子卡清单 / keep 列理由,源卡停 draft 也必送达(投递过滤豁免,archived 才丢),失败仍走 ⚠️ 系统异常)+ **力度路由**(轻/标/重三档 × Worker/Verifier 六条有序链,四级取模链:卡级特例→档位链→板级→父模型,每跳过健康闸)+ **备用模型链** `fallbackModels`(额度到顶/故障先跨供应商同档切换,链尽才降档)+ **冷却与熔断**(限流带重置时间解析、连接/网关故障 10 分钟短窗自恢复)+ **模型健康嗅探**(`llm.stream` 极简探测定时扫已配置模型,统一 modelHealth 视图——冷却/熔断/近 5 次 run 聚合,弹层链芯片带 🟢🟡🔴⚪ 健康点,事前调度可见;**探测错误码分治**:额度>凭据>连接信号才判失败,纯空响应(供应商怪癖)判 🟡 degraded 存活——不喂冷却、路由仍可选用;**探测预算独立**:每次尝试各 20s(慢模型重试不再被共享窗口饿死),重试预算耗尽沿用首探判定不判死、超时不覆盖既有观测;**探测恢复熔断解除**:已熔断模型连续 2 个探测周期通过即自动翻绿+解除拉黑+连击归零(单次成功不复位,过滤偶然响应——探测管可调性的对称面:能判死就能证活))
|
|
123
|
-
- **执行纪律面** `☆`:**计划闸口**(Worker 动笔前报「计划改动」清单,越界+撞他卡锁双查警示,点名持锁卡;清单解析容错:正则残片/中文散文片不误判为路径)+ **仲裁扩权入锁**(`task_arbitrate` 的 `expandTouches` 把放行文件并入有效锁集——闸口比对即认扩权边界+冲突预检+hold
|
|
123
|
+
- **执行纪律面** `☆`:**计划闸口**(Worker 动笔前报「计划改动」清单,越界+撞他卡锁双查警示,点名持锁卡;清单解析容错:正则残片/中文散文片不误判为路径)+ **仲裁扩权入锁**(`task_arbitrate` 的 `expandTouches` 把放行文件并入有效锁集——闸口比对即认扩权边界+冲突预检+hold 撞锁自动等锁复工)+ **touches 更新通道**(`task_update` 带 `touches` 替换式更新:规划期 draft/pending/blocked/cancelled 与落定后 resolved 主窗口可直改(GUI 详情页亦可编辑);**运行态 in-progress/verifying 拒写**——持锁期边界以 Worker 实际覆盖面申报为准,走 escalate→仲裁扩权;归一化与建卡同款,history 留「N 项→M 项」审计轨)+ **续跑健康闸**(病模型续跑自动改 fresh spawn、宿主秒拆喂熔断、死会话哨兵 5s 即重派)+ **sizeHint 动态调档**(Worker 执行中自报档位修正,下次 spawn 生效)+ **opsLog 人工留痕**(GUI/脚本写集落盘截尾 50 条 + Team 注入「最近人工操作」摘要)
|
|
124
124
|
- **验收治理** `★`:硬性验收脚本独立复跑、驳回全量带回重派、Verifier 自测指南(userTest)、风险队列「待你过目」、验收时序防护(Verifier 在场时主窗口抢批需显式 `force:true`;有活跃 run 的卡禁止归档;Verifier 迟到结论落 `lateVerdict` 留痕+直投告警)、**迟到 verdict 三保险**(①入参校验:`verdict` 非 approved/rejected 拒收并返回重调指引——漏带字段不再被当驳回落账;②通知分态:approved 迟到=「✅ 迟到复核·全过追认」notice 级,🚨「迟到驳回」只留给真 rejected;③指纹去重:同 taskId+by+verdict 只投一次,重复落账零重复通知)
|
|
125
125
|
- **度量** `★`:Token 消耗(总量 / 有效 / 缓存读,按模型·按任务·按日)+ 记分卡(模型表现 / 质量趋势 / 驳回聚类)
|
|
126
126
|
- **可运维性** `★`:运行时健康自检(派发冻结 / 结算停滞告警)、异常驱动审视(风险队列 / 质量异动告警 / 批次摘要)、起草 lint、数据安全(原子写盘 / 腐坏隔离 / 自动 salvage)、**僵尸 run 留档清扫**(host 重启后「落定卡上的 running 无 endedAt 留档」在派发周期自动关账 `stale-closed` + 清认领位,归档门禁不再被幻影 run 永久拦截)、**子会话生命周期收口**(一次性 run 落定/迟到 verdict 落账后主动 interrupt 子会话——「结算≠停止」空转根治;continuable 收紧为「续跑位真在本 child 手上」才豁免,残影 Worker 无处藏身)、**terminate 三保险**(no-rec 也按认领位盲杀补刀 + interrupt 失败 err 级可见化 + `hold:true` 原子「终止+转手动」)、**配置审计**(set-board-config 成功写入落板级 configLog {key,from,to,actor,at} 截尾 50 条;模型下拉两段式选中≠保存)
|
|
@@ -206,7 +206,7 @@ draft → pending → in-progress → verifying → resolved → archived
|
|
|
206
206
|
- 详情父子区块:父卡详情列子任务清单(状态色点 + 标题,**含已归档留档**——归档行灰化 + 行尾「已归档」徽章并沉底,点击直达子卡详情,标题行汇总 settled/total);子卡详情顶行「↳ 史诗:父标题」点击回跳父卡
|
|
207
207
|
- **可选 hooks(史诗可选点位:前置/后置各是一次真实 agent 运行)**:`epic.hooks = { pre: { enabled, prompt, state, runId }, post: 同构 }`——**前置**让子任务具备开跑条件(串行闸门:未完成前该史诗的子任务一张都不派),**后置**把这批活收口(全部子任务了结后跑一遍,完成后史诗才转验证中);prompt 只给薄框架契约,具体动作由 hook agent **自行决策**,**吃不准就走歧义上报**(失败即转阻塞等人裁决:重试/跳过/放弃,不自动重跑)。hook run 记在 `epic.runs[]`(role `hook-pre`/`hook-post`),卡片带相位徽章(`⏳ 前置准备中` / `🧪 收尾中` / `⚠️ hook 失败待裁决`,无 hooks 的老卡零渲染),详情页 hooks 区可编辑 enabled+prompt、查看 state/耗时并跳转 run 会话、失败一键跳裁决区;**hooks 仅主窗口可设**(工具与 RPC 双通道门禁),commit/push 不入任何默认形态
|
|
208
208
|
- **删除通道(真删,无 undo)**:`delete-task` RPC(卡片 hover 垃圾桶按钮 / 详情页「删除」按钮,均先 `confirm('删除不可恢复,确认删除「标题」?')`)+ `batch-op op='delete'`(多选模式底部「批量删除」,同样 confirm)。状态门禁:**草稿/待办/阻塞可删**;进行中/验证中拒绝并提示先用 `terminate-agent` 终止(避免在跑的 run 变孤儿);已完成/取消引导改用归档(`archive-task`,留档可检索);有**未归档子任务**时拒删(防 `parentId` 悬空破坏父任务自动流转);已归档任务幂等返回 ok。是真删(从 `tasks` 数组移除),因此**不产生 `batch-undo` 撤销快照**(批量条对 delete 不显示「↩️ 撤销」),删除操作在 host 端 `console.error` 留一行日志便于溯源
|
|
209
|
-
- **任务粒度建议**:单任务 **10~30 分钟**可独立完成为甜区;预计超过 30 分钟的大任务先建一张 **epic 父卡**(`pipeline: direct`,不进池派发),再挂若干 10~30 分钟的子任务(`task_create` 传 `parentId=父卡 id`,有先后顺序用 `dependsOn` 串联),子任务全部完成后父卡自动流转(`checkParentAuto`)——`task_create` 工具描述与 Team 模式提示词都写了这条契约;**容器卡结构性不派发**:凡有子任务的卡(epic)永不进派发候选(与 `pipeline` 字段无关,不再只靠 `direct` 口头约定——手滑建 `full` 档 epic 也不会被派出占 worker
|
|
209
|
+
- **任务粒度建议**:单任务 **10~30 分钟**可独立完成为甜区;预计超过 30 分钟的大任务先建一张 **epic 父卡**(`pipeline: direct`,不进池派发),再挂若干 10~30 分钟的子任务(`task_create` 传 `parentId=父卡 id`,有先后顺序用 `dependsOn` 串联),子任务全部完成后父卡自动流转(`checkParentAuto`)——`task_create` 工具描述与 Team 模式提示词都写了这条契约;**容器卡结构性不派发**:凡有子任务的卡(epic)永不进派发候选(与 `pipeline` 字段无关,不再只靠 `direct` 口头约定——手滑建 `full` 档 epic 也不会被派出占 worker 槽),子任务全了结后按父卡 `pipeline` 分态收口——`full`/`work` 父卡转 verifying 派 Verifier 验收,`direct` 父卡直接 resolved(`direct` 不进派发池对 verifier 同成立,推进 verifying 只会把它推进 verifier 过滤闸够不到的状态)
|
|
210
210
|
- **suggestSplit 软提示**:`task_create` / `create-task` 发现描述超 500 字符、或标题/描述命中「全量 / 整体 / 系统级 / 全面 / 重构 / 所有模块 / 整个」等史诗特征词时,返回体附带一行 `suggestSplit` 建议文案(**只提示,不阻断创建与派发**;未命中则不出现该字段,老调用方无感)
|
|
211
211
|
- **开关 `epicSplit`(史诗拆分总开关)**(⚙️ 入池配置弹层「**功能**」小节,默认**开**):关掉只停**引导**——Team 提示词第 6~8 条拆分 doctrine 整条不注入、`create-task`/`task_create` 响应不再附 `suggestSplit` 软提示;**机制不禁**(显式传 `parentId` 建子卡、史诗自动收口与 hooks 状态机照常工作,明确要拆时不受阻);老看板文件没有该字段 → 读路径自动补 `true`(与升级前行为一致)。工具描述里的粒度契约是静态工具定义(随 request header 快照),不在开关范围
|
|
212
212
|
- **模型字段写保护**:`set-board-config` 对 `workerModel`/`verifierModel` 的非空值校验当前网关可用清单(`list-models` 同款枚举)——手写非法 id(如把熔断通知里的显示名抄成 id)直接拒写并返回合法清单前 8 个,验收 spawn 不再 UNKNOWN_MODEL 秒死;空串=清除放行;宿主无 llm 服务时降级跳过校验不炸。配套熔断通知分态:`UNKNOWN_MODEL` 类失败报「配置非法(不在可用清单)」,与「验收连续失败」区分——mistake 归 mistake,quality 归 quality
|
|
@@ -221,7 +221,7 @@ draft → pending → in-progress → verifying → resolved → archived
|
|
|
221
221
|
- **Worker/Verifier 均可配置异构模型**(⚙️ 弹出层下拉选择,空 = 继承父级),避免同源盲点;模型故障自动熔断回退父级模型
|
|
222
222
|
- **力度路由(三档 × 双角色模型表,`task-mvnemvf5` 地基 + `task-mvneq4mw` GUI)**:卡级 `size`(light/standard/heavy,对话 `task_update` 调档,缺省=标档语义、存量卡零迁移)查板级 `laneModels` 路由表定模——取模链四级:**卡级 `model` 特例(最高)→ 档位 lane 槽 → 板级 workerModel/verifierModel 行 → 父模型**(每跳照常过熔断,命中熔断降下一跳;实际命中跳随 run 落 `modelSource` 留档审计,档位随 run 落 `size` 留档供记分卡按档位聚合);「模型路由」弹层(工具栏 cpu 图标)「**力度路由**」小节:三档 × Worker/Verifier 共 **6 条有序链编辑器**(下拉添加+芯片 × 移除/↑ 前移,改动即写),空链 =「继承板级」(继承上方 Worker/Verifier 模型行);写通道 = `set-board-config key='laneModels'` **部分合并**(只合显式给出的槽位,非法模型整体拒写不落半截 + configLog 审计行);**档位槽支持有序数组=同档优先序**(`task-mvnxckhq`:槽位 `string[]` 第一位=首选,按序取首个健康模型——首选冷却/熔断则次选赢,槽内全病照旧 fallback→降档→父模型;GUI 为链式编辑器——下拉添加+芯片 ×/↑ 排序,读写整个有序数组,`task-mvokikl3`;老单字符串槽位读入自动归一单元素数组,零迁移);卡面 size/卡级 model 徽章只读展示(GUI 不编辑,调档走对话 `task_update`)
|
|
223
223
|
- 孤儿回收:子代理 run 结束/丢失超 2 分钟 → 任务自动回待办重派
|
|
224
|
-
- 看门狗:**帧活性双层判定**——运行中 assistant 帧静默 ≥120s → 标「🟡 疑似卡顿」(仅标卡、不杀,裁决权交主窗口/用户);帧静默 ≥600s → 判死按模型面故障结算重派并计入熔断连击;无事件订阅能力/零帧观测的宿主退回日志零增长兜底(连续 40 轮零增长 × ≥600s
|
|
224
|
+
- 看门狗:**帧活性双层判定**——运行中 assistant 帧静默 ≥120s → 标「🟡 疑似卡顿」(仅标卡、不杀,裁决权交主窗口/用户);帧静默 ≥600s → 判死按模型面故障结算重派并计入熔断连击;无事件订阅能力/零帧观测的宿主退回日志零增长兜底(连续 40 轮零增长 × ≥600s 墙钟);**「继续观察」有真语义**(点 dismiss 即重置帧静默基线 `frameSilenceSince=now` + 120s 防抖窗 `stuckRearmUntil`——模型需再静默满 120s 才会重新标卡,防抖窗不拦 600s 判死闸;日志兜底路径的 `zeroGrowthSince` 同款重置)
|
|
225
225
|
- 歧义上报:Worker 遇到歧义不猜测,上报等主窗口裁决(任何模式下都通知);裁决后新 Worker 携带裁决答案接手
|
|
226
226
|
- 驳回详情全量带回:三条驳回路径(`board_verdict` 工具 / Verifier 文本结算 / 手动 `task_verify`·`verify-task`)统一往 `t.messages` 落一条 `kind: "rejection"` 完整驳回包(summary + checks 逐条核对证据;手动路径补写 `t.verification`),经 `buildMessages` 全量注入重派 Worker prompt——新 Worker 据此返工,不再只看到 300 字截断的 history 记录
|
|
227
227
|
- **Verifier 自测指南(userTest)**:Verifier prompt 末尾追加「## 自测指南」段契约——四字段 `gist`(一句人话说改了什么)/ `steps[]`(用户操作步骤,每条一步)/ `expect`(预期看到什么)/ `tier`(`ui`=界面可操作 | `metric`=看指标变化 | `internal`=纯内部无用户可感知面;`internal` 时 steps 可空、expect 写「验证靠测试套件」);**诚实护栏写死在 prompt**:只给亲自验过/从 diff 可推导的步骤,不许编没验过的操作,UI 特性给具体路径(哪个区哪个按钮),host-only 改动如实标 `internal`。双模落账 `t.verification.userTest`(工具通道 `board_verdict` 的 `userTest` 参数 / 文本通道收「## 自测指南」段;tier 非法/缺省一律归 `internal` 保守档,缺段/字段全空不挂字段);详情页验证记录区渲染「📋 自测指南」块(gist + 编号步骤 + 预期 + tier 徽章:ui 绿 / metric 蓝 / internal 灰),报告导出聚合「## 本版自测清单」段(按验收通过时间倒序近 10 张已验收卡,`internal` 收末尾并标注「无用户可感知面」)
|
|
@@ -258,6 +258,7 @@ draft → pending → in-progress → verifying → resolved → archived
|
|
|
258
258
|
- **重派冷复活续跑**:命中「上次 continuable Worker 结局=超时/失败」的待办卡时,不 spawn 新会话,改 `subagents.sendMessage(活父 Agent, childId, 断点续跑指令)`——子会话带着上一轮全部上下文复活,先盘点工作树再从断点继续;`t.runs[]` 追加一条 `resume: true` 记录(详情页历史会话按钮带 **↻** 标记),续跑同样计入三连败计数;续跑不可用(`NOT_RESUMABLE` 等)→ 回落全新 Worker,并把原因写进任务消息随首条 prompt 注入
|
|
259
259
|
- **续跑基线(不拿旧文本冒充交付物)**:续跑轮结算只认**基线字节之后**新写的助手文本——一个字没产出就走「空文本按失败」重排,绝不把上一轮(被中断那次)的残留文本当成本轮交付物推进验收
|
|
260
260
|
- **重启 reconcile(找回活跃续跑 Worker)**:host 重启会清空内存里的活跃 run 表,但持久子会话还活着。首轮派发周期对「进行中且无活跃 run」的卡查一次 `listChildren(root)`:仍在列 → 重建 rec 观测(监听器本就在)并重挂两级超时臂、基线取当前日志字节数;不在列 → 视为会话已死,走硬超时等价物(回待办重排 + 留一行流转记录),不占 Worker 并发位
|
|
261
|
+
- **野跑看门狗(wall-clock 兜底,v1.9.4)**:两级超时是内存 setTimeout 臂——**host 重启/插件热重载即清零**,而 reconcile 只重建可续跑 Worker 的臂,**一次性 run(Verifier/hook)的臂永不恢复** → 硬超时彻底失效、run 可无限野跑(实证:本体板一条 verifier run 跑 8 小时 2 分仍在活跃写盘,且因日志持续增长零增长挂死闸也抓不到)。修法=不依赖内存计时器:每轮派发周期扫**所有非落定卡**上的 unning 且无 endedAt 留档,年龄超该留档 hardMin(缺省 360 分钟,留档自带值优先)→ 按失败结算收口(野跑文案 + 清占位 + 喂熔断连击 + 重派),与「落定卡无 endedAt 留档 → stale-closed 关账」的僵尸清扫分工不重叠
|
|
261
262
|
- **usage 增量计账(按 seq 水位线)**:注意「一个持久子会话被结算多次」是新形态——整份日志全量累加会把前几轮的 token 反复记账(实测同一 childId 结算两次=双倍)。现按 v4 日志事件自带的 `seq` 记水位线(落在 `t.runs[].usageSeq`):每次结算只累加水位线之后的 `assistant/message` 增量,本轮没新增量就一行都不记;one-shot 路径(每 run 独立日志)行为逐字不变
|
|
262
263
|
- **开关 `workerContinuable`(默认开)**:关掉即逐字回退旧的一次性路径(零 `startContinuable`/零 `sendMessage`/零 `interrupt`,结算仍走 `run.result`+`dispose`);Verifier 与 hooks(`hook-pre`/`hook-post`)**保持一次性**,不受该开关影响
|
|
263
264
|
- **结算双通道 + 池韧性(2026-10-06 事故修复)**:continuable 结算有事件通道(`agent/status` 的 running→idle,身份取 `agent.session.id`)与上报通道(`board_report` 落定即收尾)两条入口,任一到达即关账(结局/usage/超时臂三件套,幂等);派发周期自带**幽灵活跃表项 GC**(卡面证据核对回收残留 rec,防残留把派发容量顶到 0 拖死全池)+ 整轮 try/catch 与逐卡隔离(单点异常只作废该卡该轮)+ 去抖 latch 时间戳兜底复位
|
|
@@ -287,6 +288,7 @@ Team 托管档独有(调度员体验):
|
|
|
287
288
|
- 主窗口 system prompt 注入派发引导(提示词层面建议实质性改动走看板,不硬拦截);引导含**上下文书写提示**——子代理是全新会话、无会话记忆,description 写不够会自行调研跑偏
|
|
288
289
|
- **默认草稿护栏**:`task_create` / `create-task` 缺省建为草稿(草稿不派发),先把所有任务的 dependsOn、contextNotes/contextFiles 补齐,再逐个 `task_update publish=true` 统一发布;确实要立即派发的单个任务才显式传 `draft:false`
|
|
289
290
|
- **歧义通知 25s 去抖**:通知延迟 25s 投递,投递前重读看板——歧义已被裁决、或任务已 resolved/archived 就静默跳过(消除主窗口 turn 排队导致的过期回声);同一任务连续多次上报只投最新一条
|
|
291
|
+
- **判档与管线指引**(v1.9.3,修「全落标档」「全落 full」两个提示词盲区):Team 注入带三档具象样例——`light`=单文件小改/文案/配置/断言增补/≤15 分钟(样例:改一句提示词、加一条断言、调一个常量);`standard`=单模块多文件/常规特性/需新断言(样例:加一个开关、修一个判定分支);`heavy`=跨模块/改判定链核心/拆分类/高风险(样例:新机制、重构、动结算链)——配**判档三问**(几个文件?动不动判定核心?要不要全新机制?)与**「拿不准按低一档报」**(低报有 `sizeHint` 执行中自纠通道,高报只会白烧额度);同步 pipeline 场景指引——`work`=回归测试/巡检/文档/纯查询/纯报表(只做不验);`full`=代码改动/修 bug/新特性(默认,要 Verifier 独立复跑验收);`direct`=主窗口亲办不进池;**「用户说做回归/巡检/盘点 → 归 work」显式点名**。`task_create` 工具描述与拆解官判档文案同口径同步
|
|
290
292
|
- 任务完成/阻塞时主窗口收到**批量聚合回执**(45s 窗口或满 5 条聚合,等主窗口空闲再发,不打断对话);⚙️ 设置区「通知」小节有两个回执开关——`notifyDispatch`(⚡ 派发回执:任务被 Worker/Verifier 领走时播报)与 `notifyDone`(✅ 完成回执:完成/阻塞时聚合播报),缺省均**开**(老看板文件缺字段自动补 `true`);**歧义裁决通知不受开关影响**(裁决通道不是回执,任务等人裁决必须提醒)
|
|
291
293
|
|
|
292
294
|
> 兼容:旧的 `set-board-mode` / `set-team-mode` 两个 RPC 原样保留(旧客户端与脚本不受影响),
|
|
@@ -283,6 +283,35 @@
|
|
|
283
283
|
React.createElement('button', { onClick: commit, title: '保存标签(逗号分隔,自动 trim + 去空)', style: { fontSize: 10, padding: '3px 10px', border: 'none', borderRadius: 4, background: C.brand, color: C_INV, cursor: 'pointer', flexShrink: 0 } }, '保存')))
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
// touches 声明编辑(task-mvxw7hn5):文件锁声明主窗口/GUI 可改(生命周期门禁在 host——运行中拒写)。
|
|
287
|
+
// 未持锁态(draft/pending/blocked/cancelled/resolved)渲染 textarea + 保存,提交走 update-task RPC 的
|
|
288
|
+
// touches 通道(替换式更新,空清单=清除声明回「无锁」态);in-progress/verifying 只读展示 + 申报指引
|
|
289
|
+
//(此阶段边界由 Worker 以实际覆盖面申报:escalate→仲裁扩权;落定后可在此修正)。
|
|
290
|
+
// 分隔符兼容逗号/顿号/分号/换行;Ctrl+Enter 快捷提交。
|
|
291
|
+
function TouchesEditor(props) {
|
|
292
|
+
var task = props.task
|
|
293
|
+
var running = task.status === 'in-progress' || task.status === 'verifying'
|
|
294
|
+
var _R = React; var useState = _R.useState
|
|
295
|
+
var _s = useState((task.touches || []).join(', ')), draft = _s[0], setDraft = _s[1]
|
|
296
|
+
var _m = useState(''), msg = _m[0], setMsg = _m[1]
|
|
297
|
+
function commit() {
|
|
298
|
+
var touches = draft.split(/[,,、;;\n]+/).map(function (s) { return s.trim() }).filter(function (s) { return s.length > 0 }).slice(0, 20)
|
|
299
|
+
rpc('update-task', { taskId: task.id, touches: touches }).then(function (r) {
|
|
300
|
+
if (r && r.ok === false) setMsg('⛔ ' + ((r && r.error) || '保存失败'))
|
|
301
|
+
else setMsg('✅ touches 已更新(' + touches.length + ' 项' + (touches.length ? '' : '——空清单=清除声明,回「无锁」态') + ')')
|
|
302
|
+
fetchTasks()
|
|
303
|
+
}).catch(function (e) { setMsg('⛔ ' + String(e)) })
|
|
304
|
+
}
|
|
305
|
+
return React.createElement('div', { style: { marginBottom: 6, padding: '4px 6px', background: C.nested, borderRadius: 4 } },
|
|
306
|
+
React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '🔒 文件级排他(touches · ' + (running ? '运行中只读' : '逗号/换行分隔,空=清除声明') + ')'),
|
|
307
|
+
running ? React.createElement('div', null,
|
|
308
|
+
React.createElement('div', { style: { fontSize: 10, color: C.text2, wordBreak: 'break-all' } }, '🔒 ' + ((Array.isArray(task.touches) && task.touches.length) ? task.touches.join('、') : '(未声明)')),
|
|
309
|
+
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 2 } }, '运行中任务的 touches 由 Worker 以实际覆盖面申报(escalate→仲裁扩权);落定后可在此修正。')) : React.createElement('div', { style: { display: 'flex', gap: 4, alignItems: 'flex-start' } },
|
|
310
|
+
React.createElement('textarea', { value: draft, onChange: function (e) { setDraft(e.target.value) }, onKeyDown: function (e) { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) commit() }, placeholder: '如 src/x.mjs、src/**(每行一条或逗号分隔;空=清除声明)', rows: 2, style: { flex: 1, minWidth: 0, fontSize: 11, padding: '4px 8px', border: '1px solid ' + C.border2, borderRadius: 4, background: C.card, color: C.text, fontFamily: 'inherit', boxSizing: 'border-box', resize: 'vertical' } }),
|
|
311
|
+
React.createElement('button', { onClick: commit, title: '保存 touches 声明(替换式更新;空清单=清除声明回「无锁」态)', style: { fontSize: 10, padding: '3px 10px', border: 'none', borderRadius: 4, background: C.brand, color: C_INV, cursor: 'pointer', flexShrink: 0 } }, '保存')),
|
|
312
|
+
msg ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 3, wordBreak: 'break-all' } }, msg) : null)
|
|
313
|
+
}
|
|
314
|
+
|
|
286
315
|
function DetailView() {
|
|
287
316
|
var _R = React; var useState = _R.useState, useEffect = _R.useEffect; var task = getTask(state.detailId)
|
|
288
317
|
var _a = useState(task ? task.title : ''), editTitle = _a[0], setEditTitle = _a[1]; var _b = useState(task ? task.description || '' : ''), editDesc = _b[0], setEditDesc = _b[1]; var _c = useState(false), saving = _c[0], setSaving = _c[1]; var _d = useState(state.boardMode), mode = _d[0], setMode = _d[1]
|
|
@@ -362,7 +391,7 @@
|
|
|
362
391
|
React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 4 } }, '本任务声明的 touches 与以下进行中任务重叠(同一批文件并行改会互踩);它们提交验收/完成后自动派发。'),
|
|
363
392
|
React.createElement('div', { style: { fontSize: 10, display: 'flex', flexWrap: 'wrap', gap: 5 } }, task.waitingForTouches.map(function (id) { var t2 = getTask(id); return React.createElement('span', { key: id, onClick: function () { if (t2) { state.detailId = id; notify() } }, title: t2 ? (t2.title + ' (' + id + ')') : id, style: { padding: '1px 5px', borderRadius: 3, background: C.nested, border: '1px solid ' + C.border, color: t2 ? C.brand : C.text2, cursor: t2 ? 'pointer' : 'default' } }, (t2 ? t2.title.slice(0, 20) : shortId(id)) + ' · ' + shortId(id)) })),
|
|
364
393
|
Array.isArray(task.touches) && task.touches.length ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 4 } }, '本任务 touches: ' + task.touches.join('、')) : null) : null,
|
|
365
|
-
|
|
394
|
+
React.createElement(TouchesEditor, { key: task.id + '|touches', task: task }),
|
|
366
395
|
// 起草 lint(task.lintWarnings[],host 建卡/更新卡现算落盘,get-tasks 透出):
|
|
367
396
|
// 四规则软警告(粒度过粗/双仓库歧义/缺验收/标题缺动词或描述空)随卡展示,缺字段/空数组整块不渲染(老卡零影响)。
|
|
368
397
|
Array.isArray(task.lintWarnings) && task.lintWarnings.length ? React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 6, padding: '5px 8px', border: '1px solid ' + C.warn, borderRadius: 6, background: 'color-mix(in srgb, ' + C.warn + ' 8%, transparent)' } },
|
package/lib/client.js
CHANGED
|
@@ -1392,6 +1392,35 @@ function apply(ctx) {
|
|
|
1392
1392
|
React.createElement('button', { onClick: commit, title: '保存标签(逗号分隔,自动 trim + 去空)', style: { fontSize: 10, padding: '3px 10px', border: 'none', borderRadius: 4, background: C.brand, color: C_INV, cursor: 'pointer', flexShrink: 0 } }, '保存')))
|
|
1393
1393
|
}
|
|
1394
1394
|
|
|
1395
|
+
// touches 声明编辑(task-mvxw7hn5):文件锁声明主窗口/GUI 可改(生命周期门禁在 host——运行中拒写)。
|
|
1396
|
+
// 未持锁态(draft/pending/blocked/cancelled/resolved)渲染 textarea + 保存,提交走 update-task RPC 的
|
|
1397
|
+
// touches 通道(替换式更新,空清单=清除声明回「无锁」态);in-progress/verifying 只读展示 + 申报指引
|
|
1398
|
+
//(此阶段边界由 Worker 以实际覆盖面申报:escalate→仲裁扩权;落定后可在此修正)。
|
|
1399
|
+
// 分隔符兼容逗号/顿号/分号/换行;Ctrl+Enter 快捷提交。
|
|
1400
|
+
function TouchesEditor(props) {
|
|
1401
|
+
var task = props.task
|
|
1402
|
+
var running = task.status === 'in-progress' || task.status === 'verifying'
|
|
1403
|
+
var _R = React; var useState = _R.useState
|
|
1404
|
+
var _s = useState((task.touches || []).join(', ')), draft = _s[0], setDraft = _s[1]
|
|
1405
|
+
var _m = useState(''), msg = _m[0], setMsg = _m[1]
|
|
1406
|
+
function commit() {
|
|
1407
|
+
var touches = draft.split(/[,,、;;\n]+/).map(function (s) { return s.trim() }).filter(function (s) { return s.length > 0 }).slice(0, 20)
|
|
1408
|
+
rpc('update-task', { taskId: task.id, touches: touches }).then(function (r) {
|
|
1409
|
+
if (r && r.ok === false) setMsg('⛔ ' + ((r && r.error) || '保存失败'))
|
|
1410
|
+
else setMsg('✅ touches 已更新(' + touches.length + ' 项' + (touches.length ? '' : '——空清单=清除声明,回「无锁」态') + ')')
|
|
1411
|
+
fetchTasks()
|
|
1412
|
+
}).catch(function (e) { setMsg('⛔ ' + String(e)) })
|
|
1413
|
+
}
|
|
1414
|
+
return React.createElement('div', { style: { marginBottom: 6, padding: '4px 6px', background: C.nested, borderRadius: 4 } },
|
|
1415
|
+
React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '🔒 文件级排他(touches · ' + (running ? '运行中只读' : '逗号/换行分隔,空=清除声明') + ')'),
|
|
1416
|
+
running ? React.createElement('div', null,
|
|
1417
|
+
React.createElement('div', { style: { fontSize: 10, color: C.text2, wordBreak: 'break-all' } }, '🔒 ' + ((Array.isArray(task.touches) && task.touches.length) ? task.touches.join('、') : '(未声明)')),
|
|
1418
|
+
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 2 } }, '运行中任务的 touches 由 Worker 以实际覆盖面申报(escalate→仲裁扩权);落定后可在此修正。')) : React.createElement('div', { style: { display: 'flex', gap: 4, alignItems: 'flex-start' } },
|
|
1419
|
+
React.createElement('textarea', { value: draft, onChange: function (e) { setDraft(e.target.value) }, onKeyDown: function (e) { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) commit() }, placeholder: '如 src/x.mjs、src/**(每行一条或逗号分隔;空=清除声明)', rows: 2, style: { flex: 1, minWidth: 0, fontSize: 11, padding: '4px 8px', border: '1px solid ' + C.border2, borderRadius: 4, background: C.card, color: C.text, fontFamily: 'inherit', boxSizing: 'border-box', resize: 'vertical' } }),
|
|
1420
|
+
React.createElement('button', { onClick: commit, title: '保存 touches 声明(替换式更新;空清单=清除声明回「无锁」态)', style: { fontSize: 10, padding: '3px 10px', border: 'none', borderRadius: 4, background: C.brand, color: C_INV, cursor: 'pointer', flexShrink: 0 } }, '保存')),
|
|
1421
|
+
msg ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 3, wordBreak: 'break-all' } }, msg) : null)
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1395
1424
|
function DetailView() {
|
|
1396
1425
|
var _R = React; var useState = _R.useState, useEffect = _R.useEffect; var task = getTask(state.detailId)
|
|
1397
1426
|
var _a = useState(task ? task.title : ''), editTitle = _a[0], setEditTitle = _a[1]; var _b = useState(task ? task.description || '' : ''), editDesc = _b[0], setEditDesc = _b[1]; var _c = useState(false), saving = _c[0], setSaving = _c[1]; var _d = useState(state.boardMode), mode = _d[0], setMode = _d[1]
|
|
@@ -1471,7 +1500,7 @@ function apply(ctx) {
|
|
|
1471
1500
|
React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 4 } }, '本任务声明的 touches 与以下进行中任务重叠(同一批文件并行改会互踩);它们提交验收/完成后自动派发。'),
|
|
1472
1501
|
React.createElement('div', { style: { fontSize: 10, display: 'flex', flexWrap: 'wrap', gap: 5 } }, task.waitingForTouches.map(function (id) { var t2 = getTask(id); return React.createElement('span', { key: id, onClick: function () { if (t2) { state.detailId = id; notify() } }, title: t2 ? (t2.title + ' (' + id + ')') : id, style: { padding: '1px 5px', borderRadius: 3, background: C.nested, border: '1px solid ' + C.border, color: t2 ? C.brand : C.text2, cursor: t2 ? 'pointer' : 'default' } }, (t2 ? t2.title.slice(0, 20) : shortId(id)) + ' · ' + shortId(id)) })),
|
|
1473
1502
|
Array.isArray(task.touches) && task.touches.length ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 4 } }, '本任务 touches: ' + task.touches.join('、')) : null) : null,
|
|
1474
|
-
|
|
1503
|
+
React.createElement(TouchesEditor, { key: task.id + '|touches', task: task }),
|
|
1475
1504
|
// 起草 lint(task.lintWarnings[],host 建卡/更新卡现算落盘,get-tasks 透出):
|
|
1476
1505
|
// 四规则软警告(粒度过粗/双仓库歧义/缺验收/标题缺动词或描述空)随卡展示,缺字段/空数组整块不渲染(老卡零影响)。
|
|
1477
1506
|
Array.isArray(task.lintWarnings) && task.lintWarnings.length ? React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 6, padding: '5px 8px', border: '1px solid ' + C.warn, borderRadius: 6, background: 'color-mix(in srgb, ' + C.warn + ' 8%, transparent)' } },
|
package/lib/core.mjs
CHANGED
|
@@ -463,7 +463,9 @@ export function claimApply(d, t, sid, note) { var ps = t.status; t.status = 'in-
|
|
|
463
463
|
// 状态闸门即拦,级联不会反向误推动父卡。
|
|
464
464
|
export function isChildSettled(t) { return !!t && (t.status === 'resolved' || t.status === 'cancelled' || t.status === 'archived') }
|
|
465
465
|
// 史诗父卡自动收口(共享 helper,唯一判定口径):父卡存在、父卡 in-progress、且全部子任务 ∈ 终态完成集
|
|
466
|
-
// →
|
|
466
|
+
// → 按父卡 pipeline 分态收口:full/work 转 verifying(交人/Verifier 验收);direct 直接 resolved
|
|
467
|
+
// (direct 不进派发池对 verifier 同成立,推进 verifying 等于推进它够不到的状态)。
|
|
468
|
+
// 幂等:父卡已 verifying/resolved/archived 一律返回 null,重复调用无副作用。
|
|
467
469
|
export function maybeAutoCloseParent(d, childTask) {
|
|
468
470
|
if (!d || !childTask || !isb(childTask)) return null
|
|
469
471
|
var p = gpt(childTask, d.tasks)
|
|
@@ -489,6 +491,21 @@ export function maybeAutoCloseParent(d, childTask) {
|
|
|
489
491
|
}
|
|
490
492
|
}
|
|
491
493
|
if (p.hooks && p.hooks.post && p.hooks.post.pending) delete p.hooks.post.pending
|
|
494
|
+
// ===== 收口分态(task-mvxmxl00):direct 父卡直接 resolved,full/work 维持推进 verifying =====
|
|
495
|
+
// 设计矛盾实证(反馈 n-mvxmue8zggw0,本体板三张 direct 史诗 task-mvwk48c2/48rr/49fw):旧实现
|
|
496
|
+
// 一律推进 verifying,但 pickDispatch 的 verifier 候选过滤是 `(!t.pipeline || t.pipeline === 'full')`
|
|
497
|
+
// ——direct 卡永远不进 verifs,卡尸停在 verifying(派 Verifier 永不可能),把人锁死在这个状态。
|
|
498
|
+
// 口径修正:direct 语义「不进派发池」对 verifier 同样成立,故 direct 父卡在子任务全了结后直接
|
|
499
|
+
// resolved(子任务每张都已独立走完 worker+verifier 验收,父卡验收是形式收口;父卡本就是人建的容器)。
|
|
500
|
+
// full/work 零变化:照旧推进 verifying 走验收(work 卡在 resolveApply 侧本就落 resolved,不经此分支)。
|
|
501
|
+
// 边界:只修推进侧不再把 direct 卡推进它够不到的状态;派发侧过滤闸(pickDispatch)一字不动。
|
|
502
|
+
if (p.pipeline === 'direct') {
|
|
503
|
+
p.status = 'resolved'
|
|
504
|
+
p.resolvedAt = new Date().toISOString()
|
|
505
|
+
p.resolution = 'all subtasks resolved'
|
|
506
|
+
ah(p, 'in-progress', 'resolved', 'system', 'auto: all subtasks resolved(direct 父卡直接收口)')
|
|
507
|
+
return p
|
|
508
|
+
}
|
|
492
509
|
p.status = 'verifying'
|
|
493
510
|
p.resolvedAt = new Date().toISOString()
|
|
494
511
|
p.resolution = 'all subtasks resolved'
|
package/lib/dispatch.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { statSync } from 'node:fs'
|
|
11
11
|
import * as core from './core.mjs'
|
|
12
12
|
import { findRunLog } from './usage.mjs'
|
|
13
|
-
import { splitRuleOf, DECOMPOSE_SCHEMA, buildDecomposerPrompt, TEAM_DECOMPOSE_FLOW } from './policy.mjs'
|
|
13
|
+
import { splitRuleOf, DECOMPOSE_SCHEMA, buildDecomposerPrompt, TEAM_DECOMPOSE_FLOW, TEAM_SIZE_PIPELINE_GUIDE } from './policy.mjs'
|
|
14
14
|
import { createHung } from './hung.mjs'
|
|
15
15
|
import { createSettle } from './settle.mjs'
|
|
16
16
|
// 模型健康域(拆分第 2 刀 task-mvt8c1yx-2 + 第 3 刀 task-mvt8c1yx-3 + 第 4 刀 task-mvt8c1yx-4):冷却嗅探纯函数
|
|
@@ -1008,6 +1008,14 @@ export function createDispatch(ctx, state, deps) {
|
|
|
1008
1008
|
// 落定卡的僵尸留档无活跃 rec,reconcile(只在飞卡)/reapGhostRecs(只清内存表项)都管不到它,
|
|
1009
1009
|
// 而它若留在 running 会卡死 archive;安静板(只有已落定卡)会走空闲快进早退,清扫必须排在其前才能兜到。
|
|
1010
1010
|
try { await settle.sweepZombieRuns(sid, snap) } catch (e) { console.error('[task-board] 僵尸留档清扫失败:', String(e)) }
|
|
1011
|
+
// 野跑看门狗(task-mvyzshi7):非落定卡 running 留档超 hardMin 墙钟即结算——重启把 armTimeouts 两级
|
|
1012
|
+
// 超时臂(内存 setTimeout)清零后,一次性 run(verifier 等)的超时臂永不恢复,run 无上限可无限野跑
|
|
1013
|
+
// (本体板实证 8h)。位置在 sweepZombieRuns(落定卡辖区)之后、幽灵回收之前:结算会把超龄卡翻回
|
|
1014
|
+
// pending/重排审查,本轮持锁段的 pickDispatch 读的是锁内新鲜 d,当轮即可重派;重读快照同回收纪律
|
|
1015
|
+
//(结算写盘后 stalePool/staleInfo 判定不能再拿旧 snap 误判)。
|
|
1016
|
+
var wildN = 0
|
|
1017
|
+
try { wildN = await settle.sweepWildRuns(sid, snap) } catch (e) { console.error('[task-board] 野跑看门狗失败(本轮跳过,派发照常):', String(e)) }
|
|
1018
|
+
if (wildN > 0) { try { snap = await rt(sid) } catch (_) {} }
|
|
1011
1019
|
// 幽灵占位回收(池冻结根修):必须排在活跃度计数与 pickDispatch 之前——残留表项正是把 capW 拉到 0、
|
|
1012
1020
|
// 并把卡挡在 Verifier 派发之外的元凶(见 reapGhostRecs 头注释)。收尾会写盘(结局/usage),
|
|
1013
1021
|
// 故有回收时重读一次快照——否则下面空闲快进的 stalePool/staleInfo 判定会拿旧快照误判(写盘已归一进锁)。
|
|
@@ -1333,13 +1341,16 @@ export function createDispatch(ctx, state, deps) {
|
|
|
1333
1341
|
// 显式 parentId 建子卡 / 史诗自动收口 / hooks 状态机全部照常(机制不禁)。
|
|
1334
1342
|
// 第 9 条(需求卡→拆解官→确认发布工作流,task-mvneoy1u)**不受 epicSplit 门禁**——
|
|
1335
1343
|
// 它是流程引导不是劝拆,恒拼接在 splitRuleOf(...) 之后(teamMode 总闸仍由上面 teamModeCache 把守)。
|
|
1344
|
+
// 第 10/11 条(判档指引 + pipeline 场景,task-mvxpzcr9)同族纪律:**同样不受 epicSplit 门禁**——
|
|
1345
|
+
// 档位怎么判 / pipeline 怎么选与「要不要拆」正交,关掉拆分引导不能让主窗口重新失去选档标准
|
|
1346
|
+
//(「全落标档」「全落 full」两个提示词盲区的根修点就在这里)。
|
|
1336
1347
|
// ===== 「最近人工/外部操作」小节(task-mvnf3gly):opsCache 尾部 5 条单行,LESSON_RECALL_HINT 之前 =====
|
|
1337
1348
|
// 无操作时不出现该节(零噪音);section text() 同步纪律——只读 opsCache 缓存(rt() 同步点与 teamModeCache 同点),
|
|
1338
1349
|
// 每轮 prompt 组装现算现拼,不扫注册表、不读文件。
|
|
1339
1350
|
var __ops = opsCache[String(agent.id)]
|
|
1340
1351
|
var __opsLines = (Array.isArray(__ops) ? __ops : []).map(opsLogLine).filter(function (s) { return s.length > 0 })
|
|
1341
1352
|
var __opsSec = __opsLines.length ? '\n【最近人工/外部操作】HTTP 通道写集留痕(GUI/脚本等「非 agent 工具通道」的盘面改动)——人动了什么,规划下一步前先对齐:\n' + __opsLines.join('\n') : ''
|
|
1342
|
-
return '【任务看板 Team 模式已开启】\n本会话的任务看板处于 Team 模式。请遵循以下工作方式:\n1. 涉及代码改动、文件创建、命令执行等实质性工作时,优先用 task_create 提交为看板任务(由一次性 Worker/Verifier 子代理执行与验收),不要自己直接动手实现。\n2. 你仍保有全部工具能力——调研、读代码、讨论方案、回答问题时直接进行,无需提交任务。\n3. 创建任务时,务必在 description 里写清任务目标和约束;调研结论/原始需求/思路用 contextNotes 带上,调研时读过的关键文件用 contextFiles 把路径带上(可写「路径:L1-L2 — 一句用途」,只给行号不给正文)——两者都会随子代理的首条 prompt 一次性注入(调研笔记全文 + 文件清单),文件内容由子代理按需用 read 工具按行号范围自取。子代理是全新会话、无你的会话记忆,上下文不够它需要从零自行调研,效率大打折扣甚至跑偏方向——开发类任务(代码改动/修复/特性)务必带文件调研,实测可省 Worker 10~15 分钟自行 grep 定位;未带调研上下文的开发类任务返回会附 warning。\n4. Worker 上报歧义时会通过 task_arbitrate 等待你裁决,请及时响应。驳回重派时同样:新 Worker 没有上一轮的记忆,驳回原因会在 prompt 里,但额外上下文需你在 description 里补上。\n5. Team 模式下 task_create 默认建为草稿(草稿不会被派发领取)。把所有任务的 dependsOn 依赖关系、contextNotes/contextFiles 都补完后,再逐个 task_update publish=true 统一发布。确实需要立即派发的单个任务才显式传 draft:false。' + splitRuleOf(epicSplitOn(String(agent.id))) + '\n' + TEAM_DECOMPOSE_FLOW + __opsSec + (feedbackOn(String(agent.id)) ? '\n' + LESSON_RECALL_HINT + '把检索到的相关历史教训写进任务的 contextNotes,让子代理少踩重复的坑。' : '')
|
|
1353
|
+
return '【任务看板 Team 模式已开启】\n本会话的任务看板处于 Team 模式。请遵循以下工作方式:\n1. 涉及代码改动、文件创建、命令执行等实质性工作时,优先用 task_create 提交为看板任务(由一次性 Worker/Verifier 子代理执行与验收),不要自己直接动手实现。\n2. 你仍保有全部工具能力——调研、读代码、讨论方案、回答问题时直接进行,无需提交任务。\n3. 创建任务时,务必在 description 里写清任务目标和约束;调研结论/原始需求/思路用 contextNotes 带上,调研时读过的关键文件用 contextFiles 把路径带上(可写「路径:L1-L2 — 一句用途」,只给行号不给正文)——两者都会随子代理的首条 prompt 一次性注入(调研笔记全文 + 文件清单),文件内容由子代理按需用 read 工具按行号范围自取。子代理是全新会话、无你的会话记忆,上下文不够它需要从零自行调研,效率大打折扣甚至跑偏方向——开发类任务(代码改动/修复/特性)务必带文件调研,实测可省 Worker 10~15 分钟自行 grep 定位;未带调研上下文的开发类任务返回会附 warning。\n4. Worker 上报歧义时会通过 task_arbitrate 等待你裁决,请及时响应。驳回重派时同样:新 Worker 没有上一轮的记忆,驳回原因会在 prompt 里,但额外上下文需你在 description 里补上。\n5. Team 模式下 task_create 默认建为草稿(草稿不会被派发领取)。把所有任务的 dependsOn 依赖关系、contextNotes/contextFiles 都补完后,再逐个 task_update publish=true 统一发布。确实需要立即派发的单个任务才显式传 draft:false。' + splitRuleOf(epicSplitOn(String(agent.id))) + '\n' + TEAM_DECOMPOSE_FLOW + '\n' + TEAM_SIZE_PIPELINE_GUIDE + __opsSec + (feedbackOn(String(agent.id)) ? '\n' + LESSON_RECALL_HINT + '把检索到的相关历史教训写进任务的 contextNotes,让子代理少踩重复的坑。' : '')
|
|
1343
1354
|
},
|
|
1344
1355
|
})
|
|
1345
1356
|
ctx.effect(function () { return disposeSection })
|
package/lib/hung.mjs
CHANGED
|
@@ -29,6 +29,10 @@ export var HUNG_ZERO_GROWTH_ROUNDS = 40 // ≈ HUNG_WALL_FLOOR_MS / 15s 心跳
|
|
|
29
29
|
// 120s 只标疑似不杀;600s 才走既有判死结算。帧时间戳只在内存刷新,持久化仅承担 sweep 基线与状态转换。
|
|
30
30
|
export var HUNG_FRAME_SUSPECT_MS = 120000
|
|
31
31
|
export var HUNG_FRAME_KILL_MS = 600000
|
|
32
|
+
// 「继续观察」豁免防抖窗(task-mvxmxkq1):dismiss 重置帧静默基线后,sweep 可能已在飞(持锁前读到旧基线),
|
|
33
|
+
// 下轮仍可能按旧基线重标。窗口期内本卡跳过重标疑似——与疑似阈值同值 120s(「观察一轮」本义:模型要再静默
|
|
34
|
+
// 满 120s 才可能重标)。一次性:重标成功或帧恢复即清除;判死 600s 闸不受本窗影响(豁免不永续)。
|
|
35
|
+
export var HUNG_SUSPECT_REARM_MS = 120000
|
|
32
36
|
export var HUNG_KILL_PREFIX = 'run 挂死:' // 判死失败文本前缀(进 isModelKillFailure 白名单 = 计入熔断连击)
|
|
33
37
|
export var HUNG_CIRCUIT_WHY = '挂死零产出(会话日志字节持续不涨),已熔断' // 挂死触发的熔断文案分态(挂死≠秒拆)
|
|
34
38
|
// 挂死型模型面失败识别:判死结算的错误文本(HUNG_KILL_PREFIX 开头)与宿主秒拆/activation 异常
|
|
@@ -133,6 +137,8 @@ export function createHung(opts) {
|
|
|
133
137
|
if (hadFrame) {
|
|
134
138
|
var recoverySilence = frameSince ? nowFrame - frameSince : 0
|
|
135
139
|
if (frameSince) { delete row.frameSilenceSince; any = true }
|
|
140
|
+
// 帧恢复即「活体证据」:dismiss 留下的防抖窗一并清除(同帧恢复同款一次性语义,task-mvxmxkq1)
|
|
141
|
+
if (typeof t.stuckRearmUntil === 'number') { delete t.stuckRearmUntil; any = true }
|
|
136
142
|
if (t.stuckSince && recoverySilence < HUNG_FRAME_KILL_MS) {
|
|
137
143
|
delete t.stuckSince
|
|
138
144
|
ah(t, t.status, t.status, String(rec.id), 'assistant 帧恢复(静默不足 600s),清除疑似卡顿标记')
|
|
@@ -154,7 +160,12 @@ export function createHung(opts) {
|
|
|
154
160
|
return
|
|
155
161
|
}
|
|
156
162
|
if (frameSilentMs >= HUNG_FRAME_SUSPECT_MS && !t.stuckSince) {
|
|
163
|
+
// 继续观察防抖窗(task-mvxmxkq1):窗内跳过重标——dismiss 已重置基线,此处兜 sweep 在飞读到
|
|
164
|
+
// 旧基线的竞态边缘;窗过期后按重置后的新基线正常判定(豁免不永续)。
|
|
165
|
+
var rearmUntil = typeof t.stuckRearmUntil === 'number' ? t.stuckRearmUntil : 0
|
|
166
|
+
if (rearmUntil && nowFrame < rearmUntil) return
|
|
157
167
|
t.stuckSince = nowFrame
|
|
168
|
+
if (rearmUntil) { delete t.stuckRearmUntil; any = true } // 一次性窗口:重标成功即消耗
|
|
158
169
|
ah(t, t.status, t.status, String(rec.id), '🟡 疑似卡顿:assistant 帧静默 ' + Math.round(HUNG_FRAME_SUSPECT_MS / 1000) + 's(仅标卡,不终止)')
|
|
159
170
|
any = true
|
|
160
171
|
}
|
package/lib/policy.mjs
CHANGED
|
@@ -18,7 +18,22 @@ const { cfg, lessonText, pushLesson, ah, classifyPipeline } = core
|
|
|
18
18
|
// 关的是「主动劝你拆」,不是「不许你拆」。
|
|
19
19
|
// 第 1 条(TASK_SIZE_CONTRACT)留在 task_create 的**静态**工具描述里:工具定义会快照进 request header
|
|
20
20
|
// (dsh-session 校验 description 必须是 string),没有按板动态能力,故不在开关范围内。
|
|
21
|
-
|
|
21
|
+
// ===== 判档 / pipeline 引导文案(task-mvxpzcr9,用户 2026-11-01 两连报的提示词层根修)=====
|
|
22
|
+
// 实证病灶(都是「文案只有定义、没有场景」的产物,零机制问题):
|
|
23
|
+
// ① 「Team 模式实测大部分任务都标记为标档」——判档语义原先只写在拆解官 prompt 一处,且原文是
|
|
24
|
+
// 「standard=常规模块改动(缺省)」=引力井:主窗口 Team 注入里完全没有判档引导,agent 建卡时
|
|
25
|
+
// 没见过判档标准,拿不准自然落缺省 → 全落标档;
|
|
26
|
+
// ② 「agent 发起的任务都是 full,即便我明确说做回归」——pipeline 说明原先只有一句定义
|
|
27
|
+
// (full(默认)/work(只做不验)/direct(不进池)),从没说过「回归/巡检类归 work」,default=full 自然一律 full。
|
|
28
|
+
// 修法:同一份文案常量三处同源,消灭「(缺省)」引力井表述:
|
|
29
|
+
// ① Team 注入(dispatch.mjs,第 10/11 条;与 epicSplit 正交,关拆分引导也照样注入——档位与拆分是两件事);
|
|
30
|
+
// ② task_create 工具描述(静态契约层:TASK_SIZE_CONTRACT 扩样例 + PIPELINE_SCENE_CONTRACT);
|
|
31
|
+
// ③ 拆解官 prompt(buildDecomposerPrompt 第 3 条,与注入同口径)。
|
|
32
|
+
// 边界:纯文案,判档机制(laneModels/healthyModel/sizeHint)、pipeline 分类(classifyPipeline)、
|
|
33
|
+
// 拆解官 schema 一律不动——只让 agent 看得见「怎么选」。
|
|
34
|
+
export var SIZE_TIER_SAMPLES = 'light=单文件小改/文案/配置/断言增补/≤15 分钟(样例:改一句提示文案、加一条断言、调一个常量);standard=单模块多文件/常规特性/需新断言(样例:加一个开关、修一个判定分支);heavy=跨模块/改判定链核心/拆分类/高风险(样例:新机制、重构、动结算链)'
|
|
35
|
+
export var SIZE_PICK_QUESTIONS = '判档三问:几个文件?动不动判定核心?要不要全新机制?——拿不准按低一档报(宁小勿大:档位可在执行/验收中用 board_report 的 sizeHint 自修正,当前 run 模型不变、对下一次派发生效;低报有执行中纠错通道,高报只会白烧额度)。'
|
|
36
|
+
export var TASK_SIZE_CONTRACT = '建议粒度:单任务 10~30 分钟可独立完成。超出此范围的大任务请先拆分——建一张 epic 卡(parentId 体系)再挂子任务,别整坨塞进来。力度档位 size 三档样例:' + SIZE_TIER_SAMPLES + '。' + SIZE_PICK_QUESTIONS
|
|
22
37
|
export var SUGGEST_SPLIT_TEXT = '任务看起来偏大(建议单任务 10~30 分钟):考虑拆分子任务(parentId)或收窄边界'
|
|
23
38
|
// Team 模式提示词第 6 条:大任务的 epic 拆分流程(父卡 pipeline=direct 不派发,子任务全 resolved 后父卡由 checkParentAuto 自动转 verifying)
|
|
24
39
|
export var TEAM_SPLIT_RULE = '6. 大任务必须拆分:预计超过 30 分钟的任务,先建一张 epic 父卡(pipeline 传 direct,不派发),再拆成若干 10~30 分钟的子任务(task_create 传 parentId=父卡 id,有先后顺序的用 dependsOn 串联)。子任务全部完成后父卡会自动标记完成(checkParentAuto)。'
|
|
@@ -32,6 +47,21 @@ export var TEAM_CONTEXT_PACKING = '8. 拆解后的上下文装配:每张子卡
|
|
|
32
47
|
// ⚠️ 本条**不受 epicSplit 门禁**:它是「确认流怎么用」的流程引导,不是「主动劝你拆」——
|
|
33
48
|
// 拆解官机制本身也不看 epicSplit(task_decompose 工具常驻)。dispatch 注入段在 splitRuleOf(...) 之后恒拼接本条。
|
|
34
49
|
export var TEAM_DECOMPOSE_FLOW = '9. 需求卡工作流:用户用自然语言下达的大需求,先建成 draft 需求卡(保留需求原文),再用 task_decompose 派拆解官出实现侧子卡提案;提案以草稿子卡落位后,向用户呈现一览(标题/档位/模型/touches),用户确认或调档(task_update 改 size/model)后统一 publish。拆解官提案不是终稿——你的呈现与用户的那一眼确认是语义质量的最后防线。命中拆分树信号的需求,即使主窗口已在讨论中形成设计稿,也一律走拆解官——设计稿写进需求卡 contextNotes 作为参考方案;拆解官读它当起点,职责是验证/修正设计而非从零重来。禁止以『我已经设计好了』为由整卡直派。'
|
|
50
|
+
// Team 提示词第 10 条:力度档位判档指引(样例化口径,与 TASK_SIZE_CONTRACT / 拆解官 prompt 同源)。
|
|
51
|
+
// ⚠️ 与第 9 条同款纪律:**不受 epicSplit 门禁**——档位怎么判与「要不要拆」正交,关掉拆分引导
|
|
52
|
+
// 也不能让主窗口重新失去判档标准(那正是「全落标档」的成因)。dispatch 恒拼接。
|
|
53
|
+
export var TEAM_SIZE_GUIDE = '10. 力度档位(size)判档——按样例选档,别一律落到标档:' + SIZE_TIER_SAMPLES + '。' + SIZE_PICK_QUESTIONS
|
|
54
|
+
// pipeline 三场景(工具描述与 Team 注入共用同一份文案,口径唯一)
|
|
55
|
+
export var PIPELINE_SCENES = 'work=回归测试/巡检/文档/纯查询/纯报表(自己跑通即可,不需 Verifier 复跑验收);full=代码改动/修 bug/新特性(默认,要 Verifier 独立复跑验收);direct=主窗口亲办、不进派发池'
|
|
56
|
+
export var PIPELINE_NAMED_CASES = '显式点名:用户说做回归/巡检/盘点 → 归 work(别默认 full);纯查询/纯报表同理。'
|
|
57
|
+
// Team 提示词第 11 条:pipeline 场景指引(同上,不受 epicSplit 门禁)
|
|
58
|
+
export var TEAM_PIPELINE_GUIDE = '11. pipeline 场景——按「要不要 Verifier 复核」选,别一律 full:' + PIPELINE_SCENES + '。' + PIPELINE_NAMED_CASES
|
|
59
|
+
// Team 注入出口:第 10/11 条合成一段(dispatch 在 TEAM_DECOMPOSE_FLOW 之后恒拼接)
|
|
60
|
+
export var TEAM_SIZE_PIPELINE_GUIDE = '\n' + TEAM_SIZE_GUIDE + '\n' + TEAM_PIPELINE_GUIDE
|
|
61
|
+
// task_create 静态描述尾部:pipeline 场景指引(紧跟 TASK_SIZE_CONTRACT 拼接;工具描述会快照进 request header,
|
|
62
|
+
// 没有按板动态能力,故与粒度契约同处静态层)
|
|
63
|
+
export var PIPELINE_SCENE_CONTRACT = ' pipeline 场景:' + PIPELINE_SCENES + '。' + PIPELINE_NAMED_CASES
|
|
64
|
+
|
|
35
65
|
// Team 提示词第 6~8 条拆分 doctrine 的注入出口(每条含前导换行,调用方直接字符串相加):
|
|
36
66
|
// epicSplit 缺省/true → 第 6 条拆分流程 + 第 7 条决策树 + 第 8 条上下文装配;
|
|
37
67
|
// epicSplit === false → ''(第 6~8 条整条不出现,编号 1~5 连续不受影响)。
|
|
@@ -112,7 +142,8 @@ export var DECOMPOSE_SCHEMA = {
|
|
|
112
142
|
// 拆解官 prompt 契约(写死纪律):
|
|
113
143
|
// ① 第一步必须 read contextFiles 清单里的文件再动手拆(实现侧拆解,禁止需求侧转述);
|
|
114
144
|
// ② 每张子卡 10~30 分钟可独立验收、touches 精确到文件、acceptance 是可执行命令;
|
|
115
|
-
// ③ size
|
|
145
|
+
// ③ size 判档=样例化同源(SIZE_TIER_SAMPLES + SIZE_PICK_QUESTIONS,与 Team 注入 / task_create 描述逐字同口径;
|
|
146
|
+
// 旧文案「standard=常规模块改动(缺省)」是「全落标档」的引力井,已消灭);
|
|
116
147
|
// modelHint 仅在有特殊理由时填(如超长上下文);
|
|
117
148
|
// ④ children 上限 DECOMPOSE_MAX_CHILDREN 张;verdict=keep 表示源卡不用拆;
|
|
118
149
|
// ⑤ dependsOn 只许引用本批**前排**子卡的序号('1' 起,string)——物化侧翻译成本批真实 id,按构造天然无环;无法解析的序号丢弃并在该子卡 messages 留痕。
|
|
@@ -128,7 +159,7 @@ export function buildDecomposerPrompt(t, pack) {
|
|
|
128
159
|
p += '\n拆解纪律(硬约束):\n'
|
|
129
160
|
p += '1. 第一步必须先用 read 工具读上面文件清单里的文件(按所给行号范围),从**实现侧**拆(按模块/文件边界切分);禁止不读代码就拍脑袋拆,禁止只做需求侧转述。\n'
|
|
130
161
|
p += '2. 每张子卡必须 10~30 分钟可独立完成并验收:touches 精确到文件(文件级排他锁,同批子卡 touches 不得互相重叠),acceptance 必须是可执行命令(如 "node --test test/xxx.test.mjs")。\n'
|
|
131
|
-
p += '3. size
|
|
162
|
+
p += '3. size 判档(按样例落档,别一律标档;与主窗口 Team 注入同口径):' + SIZE_TIER_SAMPLES + '。' + SIZE_PICK_QUESTIONS + 'modelHint 仅在有特殊理由时填(如某子卡需超长上下文模型),否则留空。\n'
|
|
132
163
|
p += '4. children 上限 ' + DECOMPOSE_MAX_CHILDREN + ' 张;有先后顺序时用 dependsOn 引用同批序号(只许引用本批**前排**子卡,序号 1 起计:如第 2 张依赖第 1 张填 ["1"])。\n'
|
|
133
164
|
p += '5. 源卡本身不大、不必拆时:verdict=keep,并在 rationale 写清理由。\n'
|
|
134
165
|
p += '\n权限边界:你没有任何建卡工具——不要尝试创建/修改看板任务。提案由宿主读结构化结果后自动物化为 draft 子卡(源卡状态不变,由主窗口确认后逐张 publish)。\n'
|