@tea-agent/loop-agent 0.36.3-beta.0 → 0.37.0
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/CHANGELOG.md +56 -55
- package/dist/build-stamp.json +3 -3
- package/dist/task/contract/project.js +4 -0
- package/dist/task/source-prepare/completeness.js +36 -0
- package/dist/task/source-prepare/parse-intent.js +67 -11
- package/dist/task/source-prepare/prepare.js +102 -5
- package/dist/worker/console/chat/chat-event-store.js +91 -0
- package/dist/worker/console/chat/pi-runtime.js +25 -0
- package/dist/worker/console/chat/routes.js +7 -2
- package/dist/worker/console/chat/semantic-activity.js +7 -1
- package/dist/worker/console/chat/session-store.js +6 -1
- package/dist/worker/console/chat/turn-process.js +119 -12
- package/dist/worker/console/operator-actions.js +18 -1
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +1 -0
- package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +1 -0
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +36 -0
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +132 -0
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +10 -0
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +1 -0
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +1 -0
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +1 -0
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +231 -0
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +15 -0
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +88 -0
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +206 -0
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +1 -0
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +1 -0
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +1 -0
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +1 -0
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +1 -0
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +166 -0
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +62 -0
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +4 -0
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +30 -0
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +3 -0
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +24 -0
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +24 -0
- package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +41 -0
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +1 -0
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +85 -0
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +156 -0
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +292 -0
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +106 -0
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
- package/dist/worker/console/static/assets/index-DQxiso1w.js +325 -0
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +1 -0
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +2 -0
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +70 -0
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +139 -0
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +89 -0
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
- package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +1 -0
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
- package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +308 -0
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +96 -0
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +1 -0
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +39 -0
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +7 -0
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +1 -0
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +84 -0
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +40 -0
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +162 -0
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +1 -0
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +2 -0
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +8 -0
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +120 -0
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +34 -0
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +78 -0
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +7 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +25 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +37 -2
- package/dist/worker/observe/static/operator-chrome.js +4 -2
- package/dist/workflows/dag/failure-routing.js +4 -9
- package/dist/workflows/dag/init-hybrid.js +2 -47
- package/dist/workflows/dag/lifecycle.js +0 -4
- package/dist/workflows/dag/node-execution.js +6 -16
- package/dist/workflows/dag/report.js +0 -6
- package/dist/workflows/dag/retry-policy.js +0 -11
- package/dist/workflows/dag/runner.js +19 -3
- package/dist/workflows/dag/types.js +0 -2
- package/dist/workflows/dag/validate.js +2 -7
- package/docs/templates/frontend-task-constraints.md +7 -13
- package/package.json +6 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
- package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,92 +1,93 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
### 改进
|
|
8
|
-
|
|
9
|
-
- 前端 DAG 的 Mock 策略约束注入设计评审链:`mockContextBlock`(含冻结 allowlist 的「HARD CONSTRAINT」与修复路径)现在也注入 `frontend-design-review-pi` / `frontend-plan-revision-pi` / `frontend-final-design-review-pi`,消除「design-review 按项目规范要求 native、但生成期 allowlist 冻结为 not-needed、prewrite 硬阻断」的策略断环——评审链会明确提示这是生成期契约缺口(需声明 `frontendMock.verifyCommands` 或 `policy: "required"` 后重新生成 DAG)
|
|
10
|
-
- 前端任务生成期 Mock 预警:新增 `DagSpec.advisories`,任务源提到接口/API/Mock 但 auto 模式把策略收窄为 `not-needed` 时,`task advance` 的 `warnings` 会给出可操作提示(声明 `frontendMock.verifyCommands` 或 `policy: required` 后重新生成 DAG),把契约缺口提前到生成期暴露
|
|
11
|
-
|
|
12
|
-
## [0.36.1] - 2026-08-16
|
|
5
|
+
## [0.37.0] - 2026-08-16
|
|
13
6
|
|
|
14
7
|
### 新增
|
|
15
8
|
|
|
16
|
-
-
|
|
9
|
+
- markdown syntax highlight, math formulas and mermaid diagrams; operate entry lands on chat
|
|
10
|
+
- label rerun titles with round number from -rN id suffix
|
|
11
|
+
- enter-DAG action, artifact rail projection, zh changelog
|
|
12
|
+
- surface compaction progress and timeline summary
|
|
13
|
+
- floating auto-dismiss toasts replace in-flow error alerts
|
|
14
|
+
- add running spinners to session titles and stop button
|
|
15
|
+
- show context ratio, semantic cache icons, align process entry typography
|
|
16
|
+
- show context usage and semantic cache labels in usage bar
|
|
17
|
+
- stream live turn process in strict order
|
|
17
18
|
|
|
18
19
|
### 修复
|
|
19
20
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
21
|
+
- isolate interrupt kernel behind worker adapter
|
|
22
|
+
- constrain session title width so hover tooltip engages
|
|
23
|
+
- backfill operation link for semantic activity refs
|
|
24
|
+
- fail fast on non-canonical newTask ids with suggested fix
|
|
25
|
+
- accept projected references and normalize PRD headings
|
|
22
26
|
|
|
23
|
-
## [0.36.
|
|
27
|
+
## [0.36.2] - 2026-08-16
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### 改进
|
|
28
|
-
|
|
29
|
-
- 标准 DAG 的 planner 节点(`contract-pi` / `plan-pi`)输出超长(`output-too-large`)时自动紧凑重试:新增 `PLANNER_OUTPUT_LIMIT_RETRY_POLICY`(默认重试集合 + `output-too-large`),紧凑重试指令按节点类型区分——结构化契约节点只返回单个紧凑 JSON,普通计划节点返回最小计划(有序步骤、writeSet 边界、验证命令),并丢弃上游原文引用与长证据摘录
|
|
29
|
+
### 修复
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
- exempt changelog history from task-pool-root scan
|
|
32
|
+
- exempt gitignore managed-block doc references in task-pool-root check
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
## [0.36.1] - 2026-08-16
|
|
34
35
|
|
|
35
|
-
###
|
|
36
|
+
### 新增
|
|
36
37
|
|
|
37
|
-
-
|
|
38
|
+
- cooperative live DAG interrupt and recovery UX
|
|
38
39
|
|
|
39
|
-
###
|
|
40
|
+
### 修复
|
|
40
41
|
|
|
41
|
-
-
|
|
42
|
-
-
|
|
42
|
+
- move interrupt eligibility to observability to honor kernel import boundary
|
|
43
|
+
- retry semantic intake across Pi tiers with 300s timeout
|
|
43
44
|
|
|
44
45
|
## [0.36.0] - 2026-08-15
|
|
45
46
|
|
|
46
47
|
### 新增
|
|
47
48
|
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
49
|
+
- 新增任务旅程活动栏,把任务创建、人工门禁、多次运行、失败和恢复记录聚合在同一张卡片中
|
|
50
|
+
- 强化 Operator Chat 的 DAG 恢复能力,运行失败后可以直接查看原因、恢复路径和后续动作
|
|
51
|
+
- 会话删除改为列表行内二次确认,减少误删,也不再被浏览器原生弹窗打断
|
|
52
|
+
- Operator Chat 可以持续监督长时间运行的 DAG,直到任务结束、失败或确实需要人工处理
|
|
53
|
+
- 优化斜杠命令面板的卡片布局和键盘操作,在较小窗口中也能完整浏览和选择命令
|
|
54
|
+
- 运行状态、恢复提示和关键操作文案统一为自然中文,减少内部状态码和生硬英文直接暴露给用户
|
|
54
55
|
|
|
55
56
|
### 修复
|
|
56
57
|
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
58
|
+
- 任务推进期间的监督更加可靠,减少因短暂静默或状态更新延迟造成的误判
|
|
59
|
+
- 运行器心跳不再提前唤醒 `operationWait`;Markdown 写入统一使用安全编辑工具,降低误覆盖风险
|
|
60
|
+
- 写入目录文档时必须同步更新对应索引,避免内容已经生成却无法从目录入口找到
|
|
61
|
+
- PRD 验收标准支持更多常见中文标题写法,不再因括号、破折号或措辞差异漏掉验收条件
|
|
62
|
+
- 要求执行节点真正生成计划文件;没有触碰禁写路径时,不再错误声称任务受阻
|
|
63
|
+
- 恢复重跑可以正确读取 `dag report` 返回的运行列表,不再因为响应结构差异误判为不可恢复
|
|
64
|
+
- 变更范围统一以 Git 工作区的真实差异为准,不再根据模型回复文字推测是否修改过文件
|
|
65
|
+
- Operator 自定义工具可以正常进入 Pi SDK 会话,不再被工具白名单意外过滤
|
|
66
|
+
- 斜杠命令面板会根据实际可用高度自动调整,不再被窗口边缘裁切
|
|
67
|
+
- 补齐 Operator Chat 的本地命令处理、动态命令降级和错误提示等交互缺口
|
|
67
68
|
|
|
68
69
|
## [0.35.3] - 2026-08-15
|
|
69
70
|
|
|
70
71
|
### 新增
|
|
71
72
|
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
73
|
+
- Console 默认进入「对话」页面;创建新任务时先分析需求和依赖,再进入正式任务流程
|
|
74
|
+
- 计划中的文件验证会优先落到允许写入的范围内,命令级检查则使用稳定的静态验证入口
|
|
75
|
+
- Recovery 页面新增恢复链路和最终结果展示,能够看清一次失败是如何被接管和收敛的
|
|
76
|
+
- 前端实现 DAG 增加恢复根清单,并去除重复统计,恢复报告更清晰
|
|
77
|
+
- 写入节点支持部分写入回滚和子任务恢复,局部失败不再轻易污染后续执行
|
|
78
|
+
- 写入节点遇到短暂故障时可以回退到稳定基线,再决定重试或恢复
|
|
79
|
+
- 调度器增加激活门禁和恢复触发条件,只有满足执行条件的子任务才会进入运行
|
|
80
|
+
- 新增分阶段创建子任务的基础能力,为后续恢复和分批执行提供统一入口
|
|
81
|
+
- 补齐恢复状态、恢复结果和终态恢复意图的数据结构,前后端对失败状态的理解保持一致
|
|
82
|
+
- 产出节点新增契约自检,并强化长时间运行时的稳定性和异常收敛能力
|
|
83
|
+
- 统一写入节点的公共辅助逻辑,修复判断改为结构化结果,不再依赖模糊文本
|
|
84
|
+
- 精简前端实现 DAG 的第一阶段,在正式写入前完成结果合并和写入准入检查
|
|
84
85
|
|
|
85
86
|
### 修复
|
|
86
87
|
|
|
87
88
|
- 规范证据面板不再单列 openspec,统一为「规范已读取/规范检索」
|
|
88
|
-
-
|
|
89
|
-
-
|
|
89
|
+
- 恢复全量测试稳定性:没有 `python3` 时自动使用可用 Python,统一构建版本格式,并处理 Node 弃用警告造成的干扰
|
|
90
|
+
- 修正续跑候选测试数据,使 `failureCode` 与真实失败语义一致,避免变基后出现错误判断
|
|
90
91
|
|
|
91
92
|
## [0.35.2] - 2026-08-15
|
|
92
93
|
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
3
|
+
"version": "0.37.0",
|
|
4
|
+
"gitSha": "7b2fcb1072e85a7dbe0d92311d4b457cc34e7735",
|
|
5
|
+
"builtAt": "2026-08-16T20:27:37.113Z"
|
|
6
6
|
}
|
|
@@ -46,6 +46,10 @@ export function projectRequirementMarkdown(draft) {
|
|
|
46
46
|
lines.push(`- ${item}`);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
+
// References / Source Resolutions remain projected into 需求.md (durable
|
|
50
|
+
// provenance for human readers); the managed read-back validator accepts
|
|
51
|
+
// the projector's own canonical forms instead of rejecting them
|
|
52
|
+
// (2026-08-16 L1: writer/reader round-trip invariant).
|
|
49
53
|
if (draft.sourceResolutions && draft.sourceResolutions.length > 0) {
|
|
50
54
|
lines.push("", "## Source Resolutions", "");
|
|
51
55
|
for (const res of draft.sourceResolutions) {
|
|
@@ -68,6 +68,15 @@ export function listPrepareGaps(input) {
|
|
|
68
68
|
level: "blocking",
|
|
69
69
|
field: "objective",
|
|
70
70
|
message: "requirement.objective is required",
|
|
71
|
+
details: { headingAliases: ["目标", "objective", "业务目标", "需求概述"] },
|
|
72
|
+
selfHealing: {
|
|
73
|
+
action: "reimport-prd",
|
|
74
|
+
steps: [
|
|
75
|
+
"检查 PRD 是否含「## 目标」或「## Objective」章节;中文括号后缀(如「目标(概述)」)已被识别",
|
|
76
|
+
"若确实缺失:补全 PRD 后重新 importPrd(同 taskId),advance 会自动重投影",
|
|
77
|
+
"不要手改 source/需求.md(会触发 DIRTY_SOURCE);不要在 PRD 目录生成 sidecar draft JSON",
|
|
78
|
+
],
|
|
79
|
+
},
|
|
71
80
|
});
|
|
72
81
|
}
|
|
73
82
|
if (draft.requirement.acceptanceCriteria.length === 0) {
|
|
@@ -76,6 +85,25 @@ export function listPrepareGaps(input) {
|
|
|
76
85
|
level: "blocking",
|
|
77
86
|
field: "acceptanceCriteria",
|
|
78
87
|
message: "at least one acceptance criterion is required",
|
|
88
|
+
details: {
|
|
89
|
+
recognizedHeadings: [
|
|
90
|
+
"验收标准",
|
|
91
|
+
"acceptance criteria",
|
|
92
|
+
"完成标准",
|
|
93
|
+
"完成定义",
|
|
94
|
+
"验收",
|
|
95
|
+
"dod",
|
|
96
|
+
],
|
|
97
|
+
note: "标题含括号/破折号后缀会被自动归一;AC 需为列表项(- / 1.)或 AC-NNN 标题/行内格式",
|
|
98
|
+
},
|
|
99
|
+
selfHealing: {
|
|
100
|
+
action: "reimport-prd",
|
|
101
|
+
steps: [
|
|
102
|
+
"检查 PRD 验收章节标题(如「完成标准(验收)」已可识别)与列表格式:需为「- 项目」或「AC-001: 文本」",
|
|
103
|
+
"格式修正后重新 importPrd(同 taskId);advance 内置 semantic intake 兜底,文档语义完整时会自动结构化",
|
|
104
|
+
"若 advance 已返回 SEMANTIC_INTAKE_FAILED:补全 PRD 后重试,不要手改 source/ 需求.md",
|
|
105
|
+
],
|
|
106
|
+
},
|
|
79
107
|
});
|
|
80
108
|
}
|
|
81
109
|
if (input.acceptanceConflicts && input.acceptanceConflicts.length > 0) {
|
|
@@ -188,6 +216,14 @@ export function listPrepareGaps(input) {
|
|
|
188
216
|
code: "DIRTY_SOURCE",
|
|
189
217
|
level: "blocking",
|
|
190
218
|
message: "existing non-empty or externally-modified source refuses --apply; use show/diff/adopt/recover or --force-overwrite-source",
|
|
219
|
+
selfHealing: {
|
|
220
|
+
action: "adopt-contract",
|
|
221
|
+
steps: [
|
|
222
|
+
"task status --json 读取 contract.observedCanonicalHash 与当前 revision",
|
|
223
|
+
"若变更确属预期:task contract adopt --task <id> --expected-revision <n> --expected-observed-hash <hash> --request-id <id> --request-payload-sha256 <sha>",
|
|
224
|
+
"若变更不是预期的:改 PRD 后重新 importPrd 走完整重投影;禁止手改 lifecycle.json / .transactions/ 绕过",
|
|
225
|
+
],
|
|
226
|
+
},
|
|
191
227
|
});
|
|
192
228
|
risks.push({
|
|
193
229
|
code: "DIRTY_SOURCE",
|
|
@@ -44,15 +44,6 @@ function stripFencedAndComments(text) {
|
|
|
44
44
|
.replace(/~~~[\s\S]*?~~~/g, "\n")
|
|
45
45
|
.replace(/<!--[\s\S]*?-->/g, "\n");
|
|
46
46
|
}
|
|
47
|
-
function normalizeHeadingText(raw) {
|
|
48
|
-
return raw
|
|
49
|
-
.trim()
|
|
50
|
-
// Strip leading section numbers: "2. 业务目标" / "3.1 已确认范围" / "3.2、非目标"
|
|
51
|
-
.replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "")
|
|
52
|
-
.toLowerCase()
|
|
53
|
-
.replace(/[::]\s*$/u, "")
|
|
54
|
-
.replace(/\s+/g, " ");
|
|
55
|
-
}
|
|
56
47
|
function normalizeAcceptanceId(raw) {
|
|
57
48
|
return raw
|
|
58
49
|
.trim()
|
|
@@ -69,12 +60,67 @@ function parseEmbeddedAcHeading(headingText) {
|
|
|
69
60
|
const text = match[2]?.trim() || id;
|
|
70
61
|
return { id, text };
|
|
71
62
|
}
|
|
63
|
+
function normalizeHeadingText(raw) {
|
|
64
|
+
return raw
|
|
65
|
+
.trim()
|
|
66
|
+
// Strip leading section numbers: "2. 业务目标" / "3.1 已确认范围" / "3.2、非目标"
|
|
67
|
+
.replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "")
|
|
68
|
+
.toLowerCase()
|
|
69
|
+
.replace(/[::]\s*$/u, "")
|
|
70
|
+
.replace(/\s+/g, " ");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Strip decorative suffixes that real PRD writers attach to section
|
|
74
|
+
* headings: "完成标准(验收)" → "完成标准", "Acceptance Criteria (DoD):" →
|
|
75
|
+
* "acceptance criteria", "验收标准 — 交付门槛" → "验收标准". Handles zh/en
|
|
76
|
+
* parens, dashes, colons and trailing separators before the suffix.
|
|
77
|
+
*/
|
|
78
|
+
function stripHeadingDecorations(normalized) {
|
|
79
|
+
return normalized
|
|
80
|
+
.replace(/[((][^(())]*[))]\s*$/u, "")
|
|
81
|
+
.replace(/\s*[—–\-|]\s*[^—–\-|]+$/u, "")
|
|
82
|
+
.replace(/[::]\s*$/u, "")
|
|
83
|
+
.trim();
|
|
84
|
+
}
|
|
85
|
+
const HEADING_PREFIX_HINTS = [
|
|
86
|
+
{ key: "objective", hint: "目标" },
|
|
87
|
+
{ key: "objective", hint: "objective" },
|
|
88
|
+
{ key: "scope", hint: "范围" },
|
|
89
|
+
{ key: "scope", hint: "scope" },
|
|
90
|
+
{ key: "nonGoals", hint: "非目标" },
|
|
91
|
+
{ key: "nonGoals", hint: "out of scope" },
|
|
92
|
+
{ key: "acceptance", hint: "验收" },
|
|
93
|
+
{ key: "acceptance", hint: "acceptance" },
|
|
94
|
+
{ key: "acceptance", hint: "完成标准" },
|
|
95
|
+
{ key: "acceptance", hint: "完成定义" },
|
|
96
|
+
{ key: "acceptance", hint: "dod" },
|
|
97
|
+
{ key: "constraints", hint: "约束" },
|
|
98
|
+
{ key: "constraints", hint: "constraints" },
|
|
99
|
+
{ key: "openQuestions", hint: "待决问题" },
|
|
100
|
+
{ key: "openQuestions", hint: "open questions" },
|
|
101
|
+
{ key: "assumptions", hint: "假设" },
|
|
102
|
+
{ key: "assumptions", hint: "assumptions" },
|
|
103
|
+
];
|
|
72
104
|
function classifyHeading(text) {
|
|
73
105
|
const normalized = normalizeHeadingText(text);
|
|
74
106
|
for (const [key, aliases] of Object.entries(HEADING_ALIASES)) {
|
|
75
107
|
if (aliases.has(normalized))
|
|
76
108
|
return key;
|
|
77
109
|
}
|
|
110
|
+
// Decoration-stripped exact match: "完成标准(验收)" → "完成标准".
|
|
111
|
+
const stripped = stripHeadingDecorations(normalized);
|
|
112
|
+
if (stripped && stripped !== normalized) {
|
|
113
|
+
for (const [key, aliases] of Object.entries(HEADING_ALIASES)) {
|
|
114
|
+
if (aliases.has(stripped))
|
|
115
|
+
return key;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Prefix-contains fallback: "验收标准与测试" starts with "验收"; keep the
|
|
119
|
+
// hint list narrow (whole-word/leading-substring) to avoid false hits.
|
|
120
|
+
for (const { key, hint } of HEADING_PREFIX_HINTS) {
|
|
121
|
+
if (stripped === hint || stripped.startsWith(hint))
|
|
122
|
+
return key;
|
|
123
|
+
}
|
|
78
124
|
return "other";
|
|
79
125
|
}
|
|
80
126
|
function isAtxHeading(line) {
|
|
@@ -408,10 +454,20 @@ export function fillMissingRequirementFacts(base, supplemental) {
|
|
|
408
454
|
export function parseVerifyFlag(raw) {
|
|
409
455
|
const idx = raw.indexOf(":");
|
|
410
456
|
if (idx <= 0 || idx === raw.length - 1) {
|
|
411
|
-
|
|
457
|
+
// Actionable error with concrete examples (2026-08-16 L3'-style):
|
|
458
|
+
// models were guessing flag names (--verify-label) and separator
|
|
459
|
+
// shapes; the example makes the contract self-evident.
|
|
460
|
+
const hint = !raw.includes(":")
|
|
461
|
+
? `no label separator found; write it as <label>:<command>, e.g. --verify "typecheck:npm run typecheck"`
|
|
462
|
+
: `empty ${idx === 0 ? "label" : "command"}; write it as <label>:<command>, e.g. --verify "typecheck:npm run typecheck"`;
|
|
463
|
+
throw new Error(`invalid --verify value "${raw}"; expected <label>:<command> — ${hint}`);
|
|
464
|
+
}
|
|
465
|
+
const label = raw.slice(0, idx).trim();
|
|
466
|
+
if (!/^[a-z0-9][a-z0-9-]*$/i.test(label)) {
|
|
467
|
+
throw new Error(`invalid --verify label "${label}"; use short kebab-case letters/digits/hyphens (e.g. typecheck, unit-tests, build) — value was "${raw}"`);
|
|
412
468
|
}
|
|
413
469
|
return {
|
|
414
|
-
label
|
|
470
|
+
label,
|
|
415
471
|
command: raw.slice(idx + 1).trim(),
|
|
416
472
|
};
|
|
417
473
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { lstat, readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
import { ContractMutationError, applyTaskContract, logicalRevisionForState, observeTaskContractState, projectConstraintsMarkdown, projectRequirementMarkdown, sha256OfCanonicalJson, validateDraftForTask, } from "../contract/index.js";
|
|
4
5
|
import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
|
|
5
6
|
import { getTaskContractPaths } from "../contract/paths.js";
|
|
@@ -28,26 +29,114 @@ async function loadExistingTaskConfig(repoRoot, taskId) {
|
|
|
28
29
|
return null;
|
|
29
30
|
}
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Check whether a "## References" section in a managed 需求.md matches the
|
|
34
|
+
* projector's canonical form: every entry is `- [role] <manifest-path>` and
|
|
35
|
+
* every path exists in source-manifest.json. The projector itself writes
|
|
36
|
+
* this section, so matching entries are the writer's own round-trip output
|
|
37
|
+
* and must be accepted (2026-08-16 L1). Unknown/free-form entries stay
|
|
38
|
+
* blocking — they indicate hand-edited provenance.
|
|
39
|
+
*/
|
|
40
|
+
function isProjectedReferencesSection(sectionBody, manifestPaths) {
|
|
41
|
+
const entryRe = /^- \[(?<role>[^\]]+)\] (?<ref>\S.*)$/;
|
|
42
|
+
const lines = sectionBody
|
|
43
|
+
.split(/\r?\n/)
|
|
44
|
+
.map((line) => line.trim())
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
if (lines.length === 0)
|
|
47
|
+
return true; // empty section: nothing to reject
|
|
48
|
+
return lines.every((line) => {
|
|
49
|
+
const match = entryRe.exec(line);
|
|
50
|
+
if (!match?.groups)
|
|
51
|
+
return false;
|
|
52
|
+
const ref = match.groups.ref?.replace(/\\/g, "/").trim() ?? "";
|
|
53
|
+
return manifestPaths.has(ref);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Source Resolutions canonical projector shape:
|
|
58
|
+
* `- <sourceRef>: <conflict> → <decision> (<reason>; decidedBy=<who>)`.
|
|
59
|
+
*/
|
|
60
|
+
function isProjectedSourceResolutionsSection(sectionBody) {
|
|
61
|
+
const entryRe = /^- \S[^:]*: .+ → .+ \(.+; decidedBy=.+\)$/u;
|
|
62
|
+
const lines = sectionBody
|
|
63
|
+
.split(/\r?\n/)
|
|
64
|
+
.map((line) => line.trim())
|
|
65
|
+
.filter(Boolean);
|
|
66
|
+
if (lines.length === 0)
|
|
67
|
+
return true;
|
|
68
|
+
return lines.every((line) => entryRe.test(line));
|
|
69
|
+
}
|
|
70
|
+
/** Section titles the read-back validator may inspect (static whitelist). */
|
|
71
|
+
const MANAGED_PROJECTION_SECTION_TITLES = [
|
|
72
|
+
"References",
|
|
73
|
+
"Source Resolutions",
|
|
74
|
+
];
|
|
75
|
+
/** Extract the body of the first `## <Title>` section, if present. */
|
|
76
|
+
function extractSectionBody(markdown, title) {
|
|
77
|
+
const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
|
|
78
|
+
const match = re.exec(markdown);
|
|
79
|
+
if (!match)
|
|
80
|
+
return null;
|
|
81
|
+
const rest = markdown.slice(match.index + match[0].length);
|
|
82
|
+
const nextHeading = /^##\s+/m.exec(rest);
|
|
83
|
+
return nextHeading ? rest.slice(0, nextHeading.index) : rest;
|
|
84
|
+
}
|
|
85
|
+
/** Materialized paths registered in the task's source-manifest.json. */
|
|
86
|
+
async function loadSourceManifestPaths(requirementPath) {
|
|
87
|
+
const manifestPath = path.join(path.dirname(requirementPath), "source-manifest.json");
|
|
88
|
+
try {
|
|
89
|
+
const raw = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
90
|
+
return new Set((raw.documents ?? [])
|
|
91
|
+
.map((doc) => doc.materializedPath?.replace(/\\/g, "/"))
|
|
92
|
+
.filter((p) => Boolean(p)));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return new Set();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
31
98
|
async function loadManagedProjectionDraft(input) {
|
|
32
99
|
const config = input.existingTaskConfig;
|
|
33
100
|
if (!config) {
|
|
34
101
|
return { ok: false, message: "managed task config could not be loaded" };
|
|
35
102
|
}
|
|
36
103
|
try {
|
|
37
|
-
const [
|
|
104
|
+
const [requirementRaw, constraints] = await Promise.all([
|
|
38
105
|
readFile(input.requirementPath, "utf-8"),
|
|
39
106
|
readFile(input.constraintsPath, "utf-8"),
|
|
40
107
|
]);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
108
|
+
// L1 (2026-08-16): the requirement projector itself writes `## References`
|
|
109
|
+
// (from source-manifest) and `## Source Resolutions` sections into
|
|
110
|
+
// 需求.md. The read-back validator used to reject ANY such section —
|
|
111
|
+
// writer/reader contract drift that deadlocked every PRD import whose
|
|
112
|
+
// draft carried references. Now accept the projector's own canonical
|
|
113
|
+
// forms: References entries must all be `- [role] <path>` with paths
|
|
114
|
+
// present in source-manifest.json; Source Resolutions entries must
|
|
115
|
+
// match the projector's `- ref: conflict → decision (...)` shape.
|
|
116
|
+
// Free-form/hand-edited entries remain blocking.
|
|
117
|
+
const manifest = await loadSourceManifestPaths(input.requirementPath);
|
|
118
|
+
const referencesBody = extractSectionBody(requirementRaw, "References");
|
|
119
|
+
if (referencesBody !== null &&
|
|
120
|
+
!isProjectedReferencesSection(referencesBody, manifest)) {
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
message: "managed requirement projection contains hand-edited or unknown References entries (not backed by source-manifest); use loop-agent task advance " +
|
|
124
|
+
input.taskId +
|
|
125
|
+
" --from-draft <reviewed-draft.json> --json",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const sourceResolutionsBody = extractSectionBody(requirementRaw, "Source Resolutions");
|
|
129
|
+
if (sourceResolutionsBody !== null &&
|
|
130
|
+
!isProjectedSourceResolutionsSection(sourceResolutionsBody)) {
|
|
44
131
|
return {
|
|
45
132
|
ok: false,
|
|
46
|
-
message: "managed requirement projection contains
|
|
133
|
+
message: "managed requirement projection contains hand-edited or malformed Source Resolutions entries; use loop-agent task advance " +
|
|
47
134
|
input.taskId +
|
|
48
135
|
" --from-draft <reviewed-draft.json> --json",
|
|
49
136
|
};
|
|
50
137
|
}
|
|
138
|
+
const requirementFacts = extractRequirementFactsFromMarkdown(requirementRaw);
|
|
139
|
+
const constraintFacts = extractRequirementFactsFromMarkdown(constraints);
|
|
51
140
|
if (!requirementFacts.objective?.trim()) {
|
|
52
141
|
return {
|
|
53
142
|
ok: false,
|
|
@@ -517,6 +606,14 @@ export async function prepareTaskSource(input) {
|
|
|
517
606
|
code: "MANAGED_PROJECTION_INCOMPLETE",
|
|
518
607
|
level: "blocking",
|
|
519
608
|
message: managedProjectionError,
|
|
609
|
+
selfHealing: {
|
|
610
|
+
action: "adopt-draft",
|
|
611
|
+
steps: [
|
|
612
|
+
"task status --json 读取 contract.observedCanonicalHash / observedSourceHashes,确认现有源文件状态",
|
|
613
|
+
"从 Console drafts(<app-data>/repos/<repo>/drafts/<taskId>.json)或 semantic intake 产物(artifacts/intake/semantic-draft.json 的 draft 字段)取回结构化 draft",
|
|
614
|
+
"补充缺失字段(scope/invariants 等)后:task advance --from-draft <reviewed-draft.json> --json",
|
|
615
|
+
],
|
|
616
|
+
},
|
|
520
617
|
});
|
|
521
618
|
}
|
|
522
619
|
// If --use-imported-prd and no parseable requirement and no AC from flags/text → blocking
|
|
@@ -152,6 +152,75 @@ export function projectOperationForChat(operation) {
|
|
|
152
152
|
previewHash,
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
|
+
/** Action families that leave a user-inspectable durable outcome. */
|
|
156
|
+
const ARTIFACT_EMITTING_ACTIONS = new Set([
|
|
157
|
+
"taskAdvance",
|
|
158
|
+
"taskCreate",
|
|
159
|
+
"contractApply",
|
|
160
|
+
"dagReport",
|
|
161
|
+
"taskImportPrd",
|
|
162
|
+
"importPrd",
|
|
163
|
+
]);
|
|
164
|
+
/**
|
|
165
|
+
* Project a terminal operation into an「产物」rail card (2026-08-16).
|
|
166
|
+
* Mirrors `ArtifactCardState` (schemaVersion 1) so the existing frontend
|
|
167
|
+
* parser renders it without any change. Best-effort: operations without a
|
|
168
|
+
* durable outcome (pure status reads) return undefined.
|
|
169
|
+
*/
|
|
170
|
+
function artifactFromTerminalOperation(operation, projection) {
|
|
171
|
+
if (!ARTIFACT_EMITTING_ACTIONS.has(operation.action))
|
|
172
|
+
return undefined;
|
|
173
|
+
// Failed/interrupted actions leave nothing inspectable as a deliverable.
|
|
174
|
+
if (operation.state !== "succeeded")
|
|
175
|
+
return undefined;
|
|
176
|
+
const taskId = projection.taskId;
|
|
177
|
+
const kind = taskId
|
|
178
|
+
? "contract"
|
|
179
|
+
: "operation";
|
|
180
|
+
const title = taskId ? `任务 ${taskId}` : `执行 ${operation.action}`;
|
|
181
|
+
const summary = projection.runSummary?.status
|
|
182
|
+
? `运行 ${projection.runSummary.runId} · ${projection.runSummary.status}`
|
|
183
|
+
: projection.summaryPreview || undefined;
|
|
184
|
+
const reportLines = [];
|
|
185
|
+
if (projection.runSummary) {
|
|
186
|
+
const rs = projection.runSummary;
|
|
187
|
+
reportLines.push(`运行: ${rs.runId}`);
|
|
188
|
+
reportLines.push(`状态: ${rs.status}`);
|
|
189
|
+
if (rs.verdict)
|
|
190
|
+
reportLines.push(`结论: ${rs.verdict}`);
|
|
191
|
+
if (rs.failureCategory)
|
|
192
|
+
reportLines.push(`失败归类: ${rs.failureCategory}`);
|
|
193
|
+
if (Array.isArray(rs.nodes)) {
|
|
194
|
+
for (const node of rs.nodes.slice(0, 30)) {
|
|
195
|
+
reportLines.push(`- ${node.id}: ${node.status}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (projection.stdoutPreview) {
|
|
200
|
+
reportLines.push("", "--- stdout 摘要 ---", projection.stdoutPreview);
|
|
201
|
+
}
|
|
202
|
+
const previewHash = createHash("sha256")
|
|
203
|
+
.update(JSON.stringify({
|
|
204
|
+
operationId: operation.operationId,
|
|
205
|
+
state: operation.state,
|
|
206
|
+
taskId: taskId ?? null,
|
|
207
|
+
runSummary: projection.runSummary ?? null,
|
|
208
|
+
stdoutPreview: projection.stdoutPreview ?? null,
|
|
209
|
+
}))
|
|
210
|
+
.digest("hex");
|
|
211
|
+
return {
|
|
212
|
+
schemaVersion: 1,
|
|
213
|
+
kind,
|
|
214
|
+
artifactId: `operation-${operation.operationId}`,
|
|
215
|
+
title,
|
|
216
|
+
...(summary ? { summary } : {}),
|
|
217
|
+
...(reportLines.length > 0
|
|
218
|
+
? { reportText: reportLines.join("\n").slice(0, 8000) }
|
|
219
|
+
: {}),
|
|
220
|
+
inspectHref: projection.inspectHref,
|
|
221
|
+
previewHash,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
155
224
|
export function projectCompactSnapshot(value) {
|
|
156
225
|
const record = value && typeof value === "object"
|
|
157
226
|
? value
|
|
@@ -254,6 +323,7 @@ export function projectChatToolEventPayload(input) {
|
|
|
254
323
|
resultPreview: preview,
|
|
255
324
|
previewHash,
|
|
256
325
|
isError: Boolean(input.isError),
|
|
326
|
+
...(input.operationId ? { operationId: input.operationId } : {}),
|
|
257
327
|
};
|
|
258
328
|
}
|
|
259
329
|
export function projectOperationEventSummary(event) {
|
|
@@ -411,6 +481,27 @@ export function createChatOperationLinker(options) {
|
|
|
411
481
|
},
|
|
412
482
|
});
|
|
413
483
|
}
|
|
484
|
+
// Artifact projection (2026-08-16): the sidebar「产物」tab was permanently
|
|
485
|
+
// empty because nothing in the server ever emitted `artifact-ref`. On a
|
|
486
|
+
// terminal operation with a durable outcome (task created / advanced /
|
|
487
|
+
// contract applied / report produced) project one artifact card so the
|
|
488
|
+
// user can inspect the deliverable straight from the rail.
|
|
489
|
+
if (isTerminalOperationState(operation.state)) {
|
|
490
|
+
const artifact = artifactFromTerminalOperation(operation, projection);
|
|
491
|
+
if (artifact) {
|
|
492
|
+
const artifactDuplicate = options.chatEvents
|
|
493
|
+
.snapshot(input.sessionId)
|
|
494
|
+
.some((event) => event.kind === "artifact-ref" &&
|
|
495
|
+
event.data
|
|
496
|
+
.artifact?.previewHash === artifact.previewHash);
|
|
497
|
+
if (!artifactDuplicate) {
|
|
498
|
+
options.chatEvents.append(input.sessionId, input.turnId, {
|
|
499
|
+
kind: "artifact-ref",
|
|
500
|
+
data: { artifact },
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
414
505
|
if (isTerminalOperationState(operation.state)) {
|
|
415
506
|
const key = keyFor(input.sessionId, input.operationId);
|
|
416
507
|
followers.get(key)?.();
|
|
@@ -1963,6 +1963,31 @@ export class ConsolePiRuntime {
|
|
|
1963
1963
|
if (usage)
|
|
1964
1964
|
onEvent({ type: "usage", sessionId, usage });
|
|
1965
1965
|
}
|
|
1966
|
+
// Context usage rides the usage event: refresh on agent_settled so the
|
|
1967
|
+
// bar reflects the post-turn context window fill (pi-web parity).
|
|
1968
|
+
if (event.type === "agent_settled") {
|
|
1969
|
+
try {
|
|
1970
|
+
const stats = session.getSessionStats?.();
|
|
1971
|
+
const contextUsage = stats?.contextUsage;
|
|
1972
|
+
if (contextUsage) {
|
|
1973
|
+
onEvent({
|
|
1974
|
+
type: "usage",
|
|
1975
|
+
sessionId,
|
|
1976
|
+
usage: {
|
|
1977
|
+
inputTokens: 0,
|
|
1978
|
+
outputTokens: 0,
|
|
1979
|
+
totalTokens: 0,
|
|
1980
|
+
...(contextUsage.tokens === null
|
|
1981
|
+
? {}
|
|
1982
|
+
: { contextUsage }),
|
|
1983
|
+
},
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
catch {
|
|
1988
|
+
// Stats getter is best-effort; never break the event stream.
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1966
1991
|
});
|
|
1967
1992
|
options?.signal?.addEventListener("abort", onAbort);
|
|
1968
1993
|
if (options?.signal?.aborted) {
|