@tea-agent/loop-agent 0.39.0-beta.10 → 0.39.0-beta.11
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 +99 -0
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +93 -32
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +46 -1
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +10 -1
- package/dist/worker/console/chat/chat-event-store.js +13 -1
- package/dist/worker/console/chat/pi-runtime.js +70 -5
- package/dist/worker/console/chat/routes.js +99 -29
- package/dist/worker/console/chat/workspace-landing.js +65 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BAsjg7ew.js → abnfDiagram-N423BO3Z-CXj_GnSb.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CyIlrwZw.js → arc-BZp6JAp7.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Bo4tobhH.js → architectureDiagram-T3A2C74G-DfpcEuYU.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Dfpow9gu.js → blockDiagram-VBNYF7ZC-_Gf0xadb.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPmzG_B4.js → c4Diagram-5PPSVZJV-Ct2QPCmv.js} +1 -1
- package/dist/worker/console/static/assets/channel-3TxJgYaH.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-pKQpB1UW.js → chunk-2GRJ4B5K-BfklkzKl.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-5n9YYquZ.js → chunk-2Q5K7J3B-Dy25vZJV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DP3CtA2a.js → chunk-5RXB4S5H-BFlCRZep.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DYxMJPUy.js → chunk-5VM5RSS4-op3oVxIE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CS3D50Io.js → chunk-6Q2QTUOP-C0R7rzF2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-gSdDfo5_.js → chunk-GF5L2VYU-Bt44TCGy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-A48q0ZNt.js → chunk-JWPE2WC7-_uEE_XFx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-ow8Mm3DY.js → chunk-KBJHAD2P-C3TOYGZ9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CMKfLWYE.js → chunk-RYQCIY6F-Cz60oBPV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BiTBdQPX.js → chunk-XXDRQBXY-8Bik0qis.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BHIkXpp3.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BHIkXpp3.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-Cm7exRz_.js → cose-bilkent-JH36ORCC-C2QIOA4a.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-D9O5qGOu.js → cynefin-VYW2F7L2-CSH_yUUd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-CkvQnako.js → cynefinDiagram-MW4NZA55-BDLw2XFM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-B1rzp7CB.js → dagre-VZM6K2ZE-CcD9ZtF4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BP07Ipcc.js → diagram-7IWD3JNH-DqwtkBsI.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BlPzweQb.js → diagram-B4RE2ZJO-BWVEcqsC.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CxPa7n0S.js → diagram-LBJQPF4R-M-WAbtQi.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CMTZs53A.js → diagram-Q27KOJAE-DKW7SixP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CxG_X52r.js → diagram-UB23O5K3-PHHPPtrj.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-D8axtJM0.js → ebnfDiagram-BXEA7PRR-BQD-B4RX.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DIsGzn7b.js → erDiagram-JOGREHBK-BFvULb51.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CU9zkDG0.js → flowDiagram-UKHOOZJN-Bw-aXTCb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DCjTmz5P.js → ganttDiagram-PKOTCBZU-BGKw04Qx.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BksdmFzg.js → gitGraphDiagram-DS77QQ5N-RqKaPR-I.js} +1 -1
- package/dist/worker/console/static/assets/index-B_D8rbWc.js +407 -0
- package/dist/worker/console/static/assets/index-BdNx6fj0.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-CGzzzJtL.js → infoDiagram-6WML65LV-YO5dnzrJ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BdseB80N.js → ishikawaDiagram-WSZJBQD7-Bi1VZHMq.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DM9gsgha.js → journeyDiagram-NVQOT4AX-Bszls1DD.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-3q-_9R2b.js → kanban-definition-27J2QSJJ-PhzeaZ09.js} +1 -1
- package/dist/worker/console/static/assets/{linear-B8N-Mxsd.js → linear-BsjbDoXi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-EPIHUUsu.js → mermaid.core-0B7NnWKk.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CxwO12aa.js → mindmap-definition-FAOFIHXS-BJr4Fj-q.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-bPqbVX6Y.js → pegDiagram-VL7TDLO6-moC4fpGB.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-KzqIvzeX.js → pieDiagram-7S7Q4E2Y-BEw37-2c.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CyofpWiV.js → quadrantDiagram-CIZ2JOQS-Cq6LyasU.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-cTWJvLTP.js → railroadDiagram-AXF67PYL-DUCMcK0D.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BK--q27B.js → requirementDiagram-LRYGKXZP-C3upTZm7.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C0SDOHXK.js → sankeyDiagram-W5VNT64P-BI_gMsCW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-Cong6YIo.js → sequenceDiagram-SI44F4Z6-YFOIRzfN.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DUVkgZx8.js → sizeCapture-X5ZJPWSS-dOnB7UDD.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-C7XgxuZg.js → stateDiagram-OKZ733FA-BXUniaIh.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cdi6UhLa.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-D9Ayvwcy.js → swimlanes-SLNWSIFB-2tA4wTNu.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D-RJBbb0.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Byb000cH.js → timeline-definition-Z64GVDOM-DO0HkJXC.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DMLegPt4.js → vennDiagram-T6HMQDX7-DvIOzixv.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-ChMMeOsP.js → wardleyDiagram-T6FBY63Y-DXDZ0cTj.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-bEMEPisW.js → xychartDiagram-ELKLHX3M-B32Ark0D.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +33 -32
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +54 -56
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +81 -27
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -25
- package/dist/worker/console/static-src/shell/workspace-route.js +7 -5
- package/dist/workflows/dag/backend-test-writer-completeness.js +7 -12
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- package/dist/workflows/dag/init-hybrid.js +96 -55
- package/dist/workflows/dag/node-execution.js +84 -0
- package/dist/workflows/dag/types.js +17 -0
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +265 -237
- package/harness.json +3 -3
- package/package.json +1 -1
- package/dist/worker/console/static/assets/channel-DSrJAn0-.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +0 -1
- package/dist/worker/console/static/assets/index-B-O5ChYq.css +0 -1
- package/dist/worker/console/static/assets/index-CfnY8AQx.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
package/CHANGELOG.md
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
|
|
30
30
|
### 重点更新
|
|
31
31
|
|
|
32
|
+
- backend-test 的 pytest repair 现在继续使用 run-owned plan,不再搜索测试目录 README;correspondence finding 会直接指向 docstring、参数 ID、符号、脚本或 payload 的修复位置,collection/scenario repair 与 generated-local Python 写边界保持一致,并在单次 Pi 会话达到 24 轮或 80K 最大 usage snapshot 前受控返回 partial failure。
|
|
33
|
+
|
|
32
34
|
- Operator Chat 多次输入排队对齐 DSH Web:运行中的连续输入改为持久化 FIFO 队列,不再用后一条覆盖前一条;单条直接展示,多条默认折叠为数量栏,展开后逐行提供内联编辑、删除和插话发送。队列 Dock 使用与 DSH 一致的外层定位与内层面板结构,标题固定、仅消息列表滚动,并在编辑或请求期间锁定折叠操作。编辑/删除以消息 ID + revision 做并发保护,插话复用 Pi `steer` 注入当前回合,其余消息继续按顺序在回合结束后自动发送;队列通过会话快照与 SSE 权威数组恢复,刷新或 Console 重启不丢失。
|
|
33
35
|
- Operator Chat 右侧辅助栏统一上下文与调用记录:输入框的上下文环、调用记录入口现在复用文件/DAG 侧栏;「文件」常驻,「上下文 / 调用记录」按入口临时添加并支持独立 `×` 关闭,关闭后回到文件 Tab;点击已选入口可收起侧栏,切换到上下文时同步开启注入状态,收起时仅关闭注入而保留上下文数据。上下文与工具调用内容改为内嵌布局,不再叠加独立弹窗、遮罩、焦点陷阱或重复关闭按钮,并补充 Tab 切换与侧栏开合动画;修复折叠态右侧展开按钮无法恢复侧栏的问题。
|
|
34
36
|
- Operator Chat 手动 compact 消息流对齐 DSH Web:压缩中在时间线显示状态行,完成后显示默认折叠的 `compact` 摘要边界,点击可展开完整 Markdown 压缩结果;compact 事件通过 SSE 与 session 详情快照双路径恢复,使用事件 ID 去重,刷新/重新进入会话不丢失。Pi compaction 会把摘要写入 session context,后续模型请求继续使用压缩后的上下文。
|
|
@@ -109,6 +111,10 @@
|
|
|
109
111
|
- `src/shared/openspec-spec.ts` 新增 `extractTaskSourceOpenspecPaths` 纯函数(反引号内联、markdown 链接目标、裸路径 token → 归一化 → spec 过滤 → 治理子树排除 → 去重排序)
|
|
110
112
|
- 生成期把 `openspecPolicy` 与候选来源元数据(`openspecCandidateSources.declared` / `taskSourceCited` / `scanStrict`)冻结进 prewrite gate 配置;`task advance` 的 advisories 说明当前模式与候选来源
|
|
111
113
|
|
|
114
|
+
- 右侧辅助栏统一承载文件、上下文、调用记录、运行活动和当前 DAG;点击消息流中的运行状态即可在同一侧栏查看运行、门禁与产物,并可自由调整侧栏宽度。
|
|
115
|
+
- 新增任务清单与结构化提问卡片,让长任务的进度和需要用户选择的内容更容易理解。
|
|
116
|
+
- 「对话 / 操作 / 观测」使用统一导航;操作和观测页面可以切换工作区查看对应数据。
|
|
117
|
+
|
|
112
118
|
### 改进
|
|
113
119
|
|
|
114
120
|
- Windows 上读取运行中的 DAG `state.json` / `run.json` / `interrupt.json` 时,对原子替换窗口内的短暂文件占用与半成品 JSON 做有界重试;Console 协作中止不再因杀毒软件或文件系统竞争偶发误报输入非法。I/O preflight 测试同步采用与该资源分组一致的 60 秒上限,避免进程启动压力造成假失败。
|
|
@@ -158,6 +164,14 @@
|
|
|
158
164
|
- Operator Chat 的命令说明和参数提示与实际能力保持一致,减少选错恢复入口或误用占位参数。
|
|
159
165
|
- 测试套件按 CPU、内存、平台和当前 DAG 压力动态分配资源,并减少重复启动进程,完整验证耗时更稳定。
|
|
160
166
|
|
|
167
|
+
- 后端测试的计划与生成阶段只读取任务明确绑定的需求、环境报告和本轮产物,减少无关仓库内容干扰结果,并让后续步骤继续使用同一份已确认计划。
|
|
168
|
+
- Operator Chat 的任务清单工具描述补齐多步骤与 DAG 使用纪律:开始前按具体步骤规划、保持进行中约束、完成后立即更新完整列表,并用面向用户的阶段呈现 DAG 进度,简单任务自动跳过。
|
|
169
|
+
- 正式发布会自动同步更新版本日志和文档站摘要;发布说明优先生成简洁自然的中文,生成服务不可用时也能安全完成发布。
|
|
170
|
+
- 输入框整合模型与推理等级选择,并只展示有真实数据的轮次、耗时、速度和缓存统计。
|
|
171
|
+
- 工作区、会话列表、文件标签和侧栏交互更加紧凑自然,折叠、切换和页面过渡更加流畅。
|
|
172
|
+
- 设置页改用自然中文展示当前工作区的模型状态,减少内部状态和重复信息。
|
|
173
|
+
- DAG 检查器简化节点说明、执行记录和提示词重启面板,长内容更容易阅读和定位。
|
|
174
|
+
|
|
161
175
|
### 修复
|
|
162
176
|
|
|
163
177
|
- backend-test 用例覆盖分析的 payload 形状提取在无 `python` 可执行文件的环境(现代 macOS 只有 `python3`)静默降级为空结果,导致 payload 观测路径全空、`PAYLOAD_SHAPE_MISMATCH` 误报;解释器解析改为 POSIX 优先 `python3`、Windows 优先 `python`(与 scenario-param 语法校验一致),仅在解释器缺失时尝试下一候选,分析输入自身的失败不再重试。
|
|
@@ -172,11 +186,96 @@
|
|
|
172
186
|
- Windows 上执行后端测试场景参数的 Python 语法检查时,会跳过不可用的 Microsoft Store 占位程序,避免错误回滚合法改动。
|
|
173
187
|
- 旧版前端 DAG 也能识别增量计划修订,不再因契约格式差异反复重试;无法合并的修订会提示重新生成 DAG。
|
|
174
188
|
|
|
189
|
+
- 修复重命名会话时输入框没有回显当前标题的问题。
|
|
190
|
+
- 修复停止回复后输入区恢复迟缓、排队消息偶发丢失,以及切换会话后旧状态残留的问题。
|
|
191
|
+
- 修复宽表格、文件预览、滚动条和窄屏布局中的多处显示异常。
|
|
192
|
+
- 修复部分 Windows 环境读取运行状态时的偶发失败,并提高前后端任务验证的可靠性。
|
|
193
|
+
|
|
175
194
|
### 文档
|
|
176
195
|
|
|
177
196
|
- 整理 `0.36.0` 以来的更新日志,改为面向用户描述结果,并补齐 `0.38.0`、`0.38.1` 的正式发布记录。
|
|
178
197
|
- 新增检查以阻止纯英文条目、重复版本、版本倒序和非标准 `Unreleased` 标题。
|
|
179
198
|
|
|
199
|
+
## [0.39.1] - 2026-08-25
|
|
200
|
+
|
|
201
|
+
### 重点更新
|
|
202
|
+
|
|
203
|
+
- 前端实现流程会逐项核对原始需求与验收标准,减少长需求被遗漏、误解或静默排除的情况。
|
|
204
|
+
- Console 新增可选择稳定版或预发布版的更新入口,更新前会检查正在运行的任务,完成后自动恢复当前页面。
|
|
205
|
+
|
|
206
|
+
### 新增
|
|
207
|
+
|
|
208
|
+
- DAG 检查器可以查看失败节点的提示词版本,并在确认后从选定内容重新执行。
|
|
209
|
+
- 后端测试会核对本轮文件清单与实际引用关系,避免历史文件或错误依赖混入测试结果。
|
|
210
|
+
|
|
211
|
+
### 改进
|
|
212
|
+
|
|
213
|
+
- 设置页移除重复的模型配置入口,模型管理继续集中在对话页。
|
|
214
|
+
- 关键治理文档完成复核,当前能力、限制和操作方式保持一致。
|
|
215
|
+
|
|
216
|
+
## [0.39.0] - 2026-08-23
|
|
217
|
+
|
|
218
|
+
### 重点更新
|
|
219
|
+
|
|
220
|
+
- Operator Chat 支持通过方向键找回历史输入,并可在当前回复进行时预先排队下一条消息。
|
|
221
|
+
- 任务向导新增实时进度、当前节点、运行时长和失败摘要,长时间任务更容易判断是否正常推进。
|
|
222
|
+
|
|
223
|
+
### 新增
|
|
224
|
+
|
|
225
|
+
- 前端测试支持自定义测试目录和文件布局,适配不同项目结构。
|
|
226
|
+
|
|
227
|
+
### 改进
|
|
228
|
+
|
|
229
|
+
- 运行结束后会自动降低刷新频率,并为失败状态提供更直接的处理建议。
|
|
230
|
+
- npm 预发布包会记录准确版本,安装后显示的版本与实际发布制品保持一致。
|
|
231
|
+
|
|
232
|
+
## [0.38.4] - 2026-08-23
|
|
233
|
+
|
|
234
|
+
### 重点更新
|
|
235
|
+
|
|
236
|
+
- 初始化与升级流程更好地支持企业 npm 镜像、已有模型配置和一次性安全更新。
|
|
237
|
+
- Console 与 DAG 检查器补充了运行状态、失败节点和提示词重启等诊断能力。
|
|
238
|
+
|
|
239
|
+
### 新增
|
|
240
|
+
|
|
241
|
+
- 新增 `inspect next`,可以查看当前 npm 预发布版本对应的源码和本地尚未发布的变化。
|
|
242
|
+
- 前端测试可以从项目治理目录和初始化模板读取标准场景,并提供更完整的执行报告。
|
|
243
|
+
|
|
244
|
+
### 修复
|
|
245
|
+
|
|
246
|
+
- 修复 npm 包缺少运行文件、macOS Python 命令差异、Windows 场景检查和部分滚动显示问题。
|
|
247
|
+
- 修复前端计划引用、失败恢复和测试回执在复杂项目中的多处误判。
|
|
248
|
+
|
|
249
|
+
## [0.38.3] - 2026-08-22
|
|
250
|
+
|
|
251
|
+
### 重点更新
|
|
252
|
+
|
|
253
|
+
- Operator Chat 的文件预览可以在新窗口打开,并能在排版预览与原始内容之间切换。
|
|
254
|
+
- 长时间模型任务遇到输出截断或暂时停滞时,恢复过程更加可靠。
|
|
255
|
+
|
|
256
|
+
### 新增
|
|
257
|
+
|
|
258
|
+
- 安装 loop-agent 时会默认包含 Pi 运行能力,减少额外配置步骤。
|
|
259
|
+
- DAG 检查器可以查看不同执行器的运行过程和更清晰的节点输出。
|
|
260
|
+
|
|
261
|
+
### 改进
|
|
262
|
+
|
|
263
|
+
- 后端测试增强了自定义目录、场景分区和生成文件的校验。
|
|
264
|
+
- 文件浏览、结束原因和前端计划恢复信息更容易查看和诊断。
|
|
265
|
+
|
|
266
|
+
## [0.38.2] - 2026-08-20
|
|
267
|
+
|
|
268
|
+
### 重点更新
|
|
269
|
+
|
|
270
|
+
- 后端测试支持自定义输出目录、公共前置说明、查询场景分区和增量补缺模式。
|
|
271
|
+
- Operator Chat 的停止与后续发送更加可靠,停止过程中提交的下一条消息不会因刷新或重启丢失。
|
|
272
|
+
|
|
273
|
+
### 改进
|
|
274
|
+
|
|
275
|
+
- DAG 重新执行时会继承上一轮尚未解决的验证和评审反馈,减少重复失败。
|
|
276
|
+
- 恢复入口会根据失败原因选择从节点继续或完整重跑,并给出更明确的操作建议。
|
|
277
|
+
- npm 预发布改为按提交数量节流,减少没有必要的重复发布。
|
|
278
|
+
|
|
180
279
|
## [0.38.1] - 2026-08-18
|
|
181
280
|
|
|
182
281
|
### 重点更新
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.39.0-beta.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
3
|
+
"version": "0.39.0-beta.11",
|
|
4
|
+
"gitSha": "5614959a483efbdc1f3a598246fee5cee57595d4",
|
|
5
|
+
"builtAt": "2026-08-28T03:23:49.173Z"
|
|
6
6
|
}
|
|
@@ -7,10 +7,11 @@ import { mapContractBlockedOwner } from "../workflows/dag/frontend-human-decisio
|
|
|
7
7
|
import { routeFrontendProviderCapability } from "../workflows/dag/frontend-provider-capability-matrix.js";
|
|
8
8
|
import { executePiStep, resolvePiBackend, } from "./pi-executor.js";
|
|
9
9
|
import { resolveDagPiExtensions, } from "./pi-extension-resolver.js";
|
|
10
|
-
import { buildPiWriterToolPolicyContext, createPiWriterCustomTools, } from "./pi-writer-tool-policy.js";
|
|
10
|
+
import { buildPiWriterToolPolicyContext, createPiReaderCustomTools, createPiWriterCustomTools, } from "./pi-writer-tool-policy.js";
|
|
11
11
|
import { createPiReadBudgetCustomTools, } from "./pi-read-budget-policy.js";
|
|
12
12
|
import { cleanupPlaywrightCliDefaultSession, createPlaywrightCliTool, PI_COMMAND_CAPABILITY_REGISTRY, resolveCaseIdFromWriteSet, resolveEvidenceDirFromWriteSet, } from "./pi-playwright-cli-tool.js";
|
|
13
13
|
import { dagCommandPolicyAllows, resolveDagCommandPolicy, } from "../workflows/dag/types.js";
|
|
14
|
+
import { parseLedgerJson } from "../task/source-prepare/ledger.js";
|
|
14
15
|
import { redactPromptForLog, truncateOutput, } from "../shared/output-truncation.js";
|
|
15
16
|
import { GitStatusUnavailableError, pathsChangedDuringRun, readGitStatusPorcelain, recoverRootNulArtifact, snapshotGitStatusPathFingerprints, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
|
|
16
17
|
import { captureWorkspaceWriteSnapshot, diffWorkspaceWriteSnapshots, } from "./workspace-write-snapshot.js";
|
|
@@ -287,14 +288,17 @@ export function resolveDagPiToolNames(task) {
|
|
|
287
288
|
}
|
|
288
289
|
if (isFrontendPlanLedgerNode(task)) {
|
|
289
290
|
return [
|
|
290
|
-
// Plan
|
|
291
|
-
//
|
|
292
|
-
|
|
291
|
+
// Plan is a decision-only node. Contract/scout own source and repository
|
|
292
|
+
// discovery; omitting read tools prevents a planner from spending its
|
|
293
|
+
// output budget reconstructing already-frozen evidence.
|
|
293
294
|
...FRONTEND_PLAN_RECORD_TOOL_NAMES,
|
|
294
295
|
...FRONTEND_PLAN_TERMINAL_TOOL_NAMES,
|
|
295
296
|
...FRONTEND_PLAN_ADOPT_TOOL_NAMES,
|
|
296
297
|
];
|
|
297
298
|
}
|
|
299
|
+
if ((task.readSet?.length ?? 0) > 0) {
|
|
300
|
+
return isDagPiWriteTask(task) ? ["read", "edit", "write"] : ["read"];
|
|
301
|
+
}
|
|
298
302
|
if (!isDagPiWriteTask(task))
|
|
299
303
|
return [...DAG_PI_READONLY_TOOLS];
|
|
300
304
|
const tools = [...DAG_PI_WRITE_TOOLS];
|
|
@@ -823,6 +827,44 @@ async function loadContractRequirementInheritance(runDir) {
|
|
|
823
827
|
}
|
|
824
828
|
return byId;
|
|
825
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* Resolve task-source citations from the source-fidelity ledger before the
|
|
832
|
+
* planner starts. The planner names a frozen requirement id; it never needs
|
|
833
|
+
* to re-read a PRD merely to recover a path/section/line triple.
|
|
834
|
+
*/
|
|
835
|
+
async function resolveFrontendPlanNewComponentSourceReferences(input) {
|
|
836
|
+
const binding = input.sourceBinding;
|
|
837
|
+
if (!binding || binding.schemaVersion !== 2)
|
|
838
|
+
return new Map();
|
|
839
|
+
const ledgerPath = binding.ledgerPath;
|
|
840
|
+
const absolutePath = path.resolve(input.cwd, ledgerPath);
|
|
841
|
+
const workspaceRoot = path.resolve(input.cwd);
|
|
842
|
+
if (absolutePath !== workspaceRoot &&
|
|
843
|
+
!absolutePath.startsWith(`${workspaceRoot}${path.sep}`)) {
|
|
844
|
+
return new Map();
|
|
845
|
+
}
|
|
846
|
+
try {
|
|
847
|
+
const ledger = parseLedgerJson(await readFile(absolutePath, "utf8"));
|
|
848
|
+
const fragmentsById = new Map(ledger.fragments.map((fragment) => [fragment.id, fragment]));
|
|
849
|
+
const references = new Map();
|
|
850
|
+
for (const requirement of ledger.canonicalRequirements) {
|
|
851
|
+
const fragment = requirement.sourceFragmentIds
|
|
852
|
+
.map((fragmentId) => fragmentsById.get(fragmentId))
|
|
853
|
+
.find((candidate) => candidate !== undefined);
|
|
854
|
+
if (!fragment)
|
|
855
|
+
continue;
|
|
856
|
+
references.set(requirement.id, {
|
|
857
|
+
path: fragment.path,
|
|
858
|
+
section: fragment.headingPath,
|
|
859
|
+
line: fragment.lineRange.start,
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
return references;
|
|
863
|
+
}
|
|
864
|
+
catch {
|
|
865
|
+
return new Map();
|
|
866
|
+
}
|
|
867
|
+
}
|
|
826
868
|
/**
|
|
827
869
|
* A+B: `frontend-plan-pi` now records its decision ledger through seven
|
|
828
870
|
* incremental `record_*` tools (origin=plan) and closes with exactly one
|
|
@@ -1003,10 +1045,11 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
1003
1045
|
const recordComponentChoiceTool = defineTool({
|
|
1004
1046
|
name: "record_component_choice",
|
|
1005
1047
|
label: "record_component_choice",
|
|
1006
|
-
description: "Record ONE component choice (origin=plan component-choice fact). Declare every UI purpose's component selection.
|
|
1048
|
+
description: "Record ONE component choice (origin=plan component-choice fact). Declare every UI purpose's component selection. For decision=new, pass sourceRequirementIds containing the frozen requirement ID(s) that mandate the component; the runtime derives its exact PRD specReference from the source-fidelity ledger. Do not read the PRD or invent a path/line. decision=reuse-existing is only for components that already exist in the repo (e.g. reusing ActiveRunBadge's styling convention). Omit rationale for reuse-existing; it is optional. Call once per component — one tool call per message. Optionally include stylingStrategy (set it once, on the first call).",
|
|
1007
1049
|
promptSnippet: "Record one component choice (one tool call per message).",
|
|
1008
1050
|
parameters: Type.Object({
|
|
1009
1051
|
choice: uiComponentChoiceSchema,
|
|
1052
|
+
sourceRequirementIds: Type.Optional(stringArray),
|
|
1010
1053
|
stylingStrategy: optionalString,
|
|
1011
1054
|
}, { additionalProperties: false }),
|
|
1012
1055
|
async execute(_toolCallId, params) {
|
|
@@ -1018,7 +1061,28 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
1018
1061
|
error: "record_component_choice requires a non-empty choice object",
|
|
1019
1062
|
});
|
|
1020
1063
|
}
|
|
1021
|
-
const
|
|
1064
|
+
const sourceRequirementIds = stringList(params?.sourceRequirementIds);
|
|
1065
|
+
const choice = { ...rawChoice };
|
|
1066
|
+
if (choice.decision === "new") {
|
|
1067
|
+
if (sourceRequirementIds.length === 0) {
|
|
1068
|
+
return planToolReceipt({
|
|
1069
|
+
ok: false,
|
|
1070
|
+
kind: "component-choice",
|
|
1071
|
+
error: "decision=new requires sourceRequirementIds so runtime can materialize the task-source specReference",
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
const specReference = sourceRequirementIds
|
|
1075
|
+
.map((id) => input.componentNewSourceReferences?.get(id))
|
|
1076
|
+
.find((reference) => reference !== undefined);
|
|
1077
|
+
if (!specReference) {
|
|
1078
|
+
return planToolReceipt({
|
|
1079
|
+
ok: false,
|
|
1080
|
+
kind: "component-choice",
|
|
1081
|
+
error: `decision=new sourceRequirementIds have no frozen task-source citation: ${sourceRequirementIds.join(", ")}`,
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
choice.specReference = specReference;
|
|
1085
|
+
}
|
|
1022
1086
|
const components = typeof choice.component === "string" ? [choice.component] : [];
|
|
1023
1087
|
const result = await adoptPlanFact("component-choice", `${attemptId}:record_component_choice:${randomUUID()}`, {
|
|
1024
1088
|
kind: "component-choice",
|
|
@@ -1803,23 +1867,6 @@ export async function createFrontendScoutEvidenceTools(input) {
|
|
|
1803
1867
|
},
|
|
1804
1868
|
};
|
|
1805
1869
|
}
|
|
1806
|
-
async function resolveFrontendPlanReadAccess(input) {
|
|
1807
|
-
const { readCommittedOriginFacts, scoutConfirmedReadPaths } = await import("../workflows/dag/frontend-shadow-dual-write.js");
|
|
1808
|
-
const scoutFacts = await readCommittedOriginFacts(input.runDir, "frontend-scout-pi", "scout-typed-facts.jsonl");
|
|
1809
|
-
const workspaceRoot = path.resolve(input.cwd);
|
|
1810
|
-
const allowedReadPaths = scoutConfirmedReadPaths(scoutFacts)
|
|
1811
|
-
.map((relativePath) => path.resolve(workspaceRoot, relativePath))
|
|
1812
|
-
.filter((absolutePath) => absolutePath === workspaceRoot ||
|
|
1813
|
-
absolutePath.startsWith(`${workspaceRoot}${path.sep}`));
|
|
1814
|
-
return {
|
|
1815
|
-
allowedReadPaths,
|
|
1816
|
-
// Upstream artifacts are runner-owned evidence, not repository discovery.
|
|
1817
|
-
allowedReadRoots: [
|
|
1818
|
-
path.join(input.runDir, "frontend-contract-pi"),
|
|
1819
|
-
path.join(input.runDir, "frontend-scout-pi"),
|
|
1820
|
-
],
|
|
1821
|
-
};
|
|
1822
|
-
}
|
|
1823
1870
|
export function buildDagPiUserMessage(task, persona, step) {
|
|
1824
1871
|
const role = task.role ?? "unspecified";
|
|
1825
1872
|
const writePolicy = task.writePolicy ?? "read-only (default)";
|
|
@@ -1872,6 +1919,9 @@ export function buildDagPiUserMessage(task, persona, step) {
|
|
|
1872
1919
|
`You are executing hybrid DAG node "${task.id}" (role=${role}, piPersona=${persona}, piStep=${step}, writePolicy=${writePolicy}).`,
|
|
1873
1920
|
"The system prompt contains the full DAG envelope: objective, constraints, upstream context, and task.",
|
|
1874
1921
|
"Use read-only tools only. Do not edit, write, or commit repository files.",
|
|
1922
|
+
(task.readSet?.length ?? 0) > 0
|
|
1923
|
+
? `Strict read set: ${task.readSet.join(", ")}. The read tool rejects every other repository path; do not search for substitutes.`
|
|
1924
|
+
: undefined,
|
|
1875
1925
|
"Return your conclusion as plain Markdown text suitable for downstream DAG nodes.",
|
|
1876
1926
|
"Do not wrap the output in code fences and do not add conversational preamble.",
|
|
1877
1927
|
].join(" ");
|
|
@@ -2292,9 +2342,17 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
2292
2342
|
ok: false, stdout: "", stderr: "pi command capability requires an explicit bounded write tool profile", failureCategory: "tool-policy", durationMs: Date.now() - started,
|
|
2293
2343
|
};
|
|
2294
2344
|
}
|
|
2295
|
-
|
|
2345
|
+
const strictReadSet = (input.task.readSet?.length ?? 0) > 0;
|
|
2346
|
+
if (isWriteTask || allowsPlaywrightCli || strictReadSet) {
|
|
2296
2347
|
try {
|
|
2297
2348
|
const customTools = [];
|
|
2349
|
+
if (strictReadSet) {
|
|
2350
|
+
const readContext = buildPiWriterToolPolicyContext({
|
|
2351
|
+
repoRoot: input.cwd,
|
|
2352
|
+
readSet: input.task.readSet,
|
|
2353
|
+
});
|
|
2354
|
+
customTools.push(...(await createPiReaderCustomTools(readContext)));
|
|
2355
|
+
}
|
|
2298
2356
|
if (isWriteTask) {
|
|
2299
2357
|
const policyContext = buildPiWriterToolPolicyContext({
|
|
2300
2358
|
repoRoot: input.cwd,
|
|
@@ -2302,6 +2360,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
2302
2360
|
writeSet: input.task.writeSet,
|
|
2303
2361
|
forbiddenPaths: input.task.forbiddenPaths,
|
|
2304
2362
|
writePolicy: input.task.writePolicy,
|
|
2363
|
+
readSet: input.task.readSet,
|
|
2305
2364
|
});
|
|
2306
2365
|
customTools.push(...(await createPiWriterCustomTools(policyContext)));
|
|
2307
2366
|
}
|
|
@@ -2465,6 +2524,10 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
2465
2524
|
runDir: meta.runDir,
|
|
2466
2525
|
nodeId: input.task.id,
|
|
2467
2526
|
skeleton: input.task.structuredContractOutput?.skeleton,
|
|
2527
|
+
componentNewSourceReferences: await resolveFrontendPlanNewComponentSourceReferences({
|
|
2528
|
+
cwd: input.cwd,
|
|
2529
|
+
sourceBinding: meta.spec.sourceBinding,
|
|
2530
|
+
}),
|
|
2468
2531
|
});
|
|
2469
2532
|
writerToolPolicy = {
|
|
2470
2533
|
requireSdk: true,
|
|
@@ -2486,14 +2549,6 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
2486
2549
|
readBudgetTools = await createPiReadBudgetCustomTools({
|
|
2487
2550
|
repoRoot: input.cwd,
|
|
2488
2551
|
budget: input.task.readBudget,
|
|
2489
|
-
...(isFrontendPlanLedgerNode(input.task)
|
|
2490
|
-
? {
|
|
2491
|
-
readAccess: await resolveFrontendPlanReadAccess({
|
|
2492
|
-
cwd: input.cwd,
|
|
2493
|
-
runDir: meta.runDir,
|
|
2494
|
-
}),
|
|
2495
|
-
}
|
|
2496
|
-
: {}),
|
|
2497
2552
|
});
|
|
2498
2553
|
writerToolPolicy = {
|
|
2499
2554
|
requireSdk: true,
|
|
@@ -2577,6 +2632,9 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
2577
2632
|
timeoutMs: input.timeoutMs,
|
|
2578
2633
|
stallTimeoutMs: input.stallTimeoutMs,
|
|
2579
2634
|
abortGraceMs: input.abortGraceMs,
|
|
2635
|
+
...(input.task.contextBudget
|
|
2636
|
+
? { contextBudget: input.task.contextBudget }
|
|
2637
|
+
: {}),
|
|
2580
2638
|
...(writerToolPolicy ? { writerToolPolicy } : {}),
|
|
2581
2639
|
...(piExtensionPaths && piExtensionPaths.length > 0
|
|
2582
2640
|
? { piExtensionPaths }
|
|
@@ -2625,6 +2683,9 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
2625
2683
|
persona,
|
|
2626
2684
|
step,
|
|
2627
2685
|
});
|
|
2686
|
+
if (input.task.id === "generate-backend-md-plan-pi" && result.assistantText?.trim()) {
|
|
2687
|
+
await writeTextArtifactFile(path.join(meta.runDir, input.task.id, "plan.md"), result.assistantText.trim() + "\n");
|
|
2688
|
+
}
|
|
2628
2689
|
const mapped = mapPiResultToDagNodeResult(result, isFrontendFactsWriter(input.task)
|
|
2629
2690
|
? undefined
|
|
2630
2691
|
: input.task.writerOutcomePolicy
|
|
@@ -1005,12 +1005,14 @@ export function shouldRetryWithFallback(stderr, stdout) {
|
|
|
1005
1005
|
return ["quota", "rate-limit", "unavailable", "network"].includes(category);
|
|
1006
1006
|
}
|
|
1007
1007
|
export function classifyPiFailure(input) {
|
|
1008
|
+
if (input.exitCode === 0 && input.assistantText.trim())
|
|
1009
|
+
return "success";
|
|
1010
|
+
if (input.stderr.toLowerCase().includes("context-budget-exhausted:"))
|
|
1011
|
+
return "context-budget-exhausted";
|
|
1008
1012
|
if (input.timedOut)
|
|
1009
1013
|
return "timeout";
|
|
1010
1014
|
if (input.outputTooLarge)
|
|
1011
1015
|
return "output-too-large";
|
|
1012
|
-
if (input.exitCode === 0 && input.assistantText.trim())
|
|
1013
|
-
return "success";
|
|
1014
1016
|
const combined = `${input.stderr}\n${input.stdout}`.toLowerCase();
|
|
1015
1017
|
// Context overflow (400 request-too-large / context window exceeded).
|
|
1016
1018
|
// Must be detected BEFORE the generic rate-limit/quota phrases: an overflow
|
|
@@ -485,6 +485,9 @@ function extractSdkUsageSample(event) {
|
|
|
485
485
|
const responseKey = responseKeyCandidates.find((value) => typeof value === "string" && value.length > 0);
|
|
486
486
|
return responseKey ? { responseKey, tokens } : { tokens };
|
|
487
487
|
}
|
|
488
|
+
function maxSdkContextTokens(samples) {
|
|
489
|
+
return samples.reduce((maximum, sample) => Math.max(maximum, sample.tokens), 0);
|
|
490
|
+
}
|
|
488
491
|
function aggregateSdkTokenUsage(samples) {
|
|
489
492
|
const identified = new Map();
|
|
490
493
|
let anonymousMaximum = 0;
|
|
@@ -537,6 +540,9 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
537
540
|
const stdoutPreview = new BoundedTextPreview("stdout");
|
|
538
541
|
const stdoutCollector = createPiJsonlStreamCollector();
|
|
539
542
|
const usageSamples = [];
|
|
543
|
+
let observedTurns = 0;
|
|
544
|
+
let contextBudgetExhausted = false;
|
|
545
|
+
let contextBudgetDetail = "";
|
|
540
546
|
// Writer-thinking-exhausted diagnosis evidence (AC-002). Collected in the
|
|
541
547
|
// subscribe callback regardless of shouldPersistSessionEvent filtering, so a
|
|
542
548
|
// length-stopped thinking-only attempt with no persisted write tool calls is
|
|
@@ -625,6 +631,7 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
625
631
|
});
|
|
626
632
|
let resolveStall;
|
|
627
633
|
let resolveSettlement;
|
|
634
|
+
let resolveContextBudget;
|
|
628
635
|
let settled = false;
|
|
629
636
|
let abortConfirmed = true;
|
|
630
637
|
const persistRuntimeNote = (event) => {
|
|
@@ -655,6 +662,30 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
655
662
|
clearTimeout(stallHandle);
|
|
656
663
|
stallHandle = setTimeout(() => resolveStall?.("stall"), stallTimeoutMs);
|
|
657
664
|
};
|
|
665
|
+
const checkContextBudget = () => {
|
|
666
|
+
if (contextBudgetExhausted || !options.contextBudget)
|
|
667
|
+
return;
|
|
668
|
+
// Context protection follows the largest provider-reported response
|
|
669
|
+
// snapshot, not cumulative billing usage across turns. Summing repeated
|
|
670
|
+
// input contexts would abort healthy multi-turn sessions far below the
|
|
671
|
+
// model's actual context window.
|
|
672
|
+
const observedTokens = maxSdkContextTokens(usageSamples);
|
|
673
|
+
const turnExceeded = options.contextBudget.maxTurns !== undefined &&
|
|
674
|
+
observedTurns >= options.contextBudget.maxTurns;
|
|
675
|
+
const tokenExceeded = options.contextBudget.maxTokens !== undefined &&
|
|
676
|
+
observedTokens >= options.contextBudget.maxTokens;
|
|
677
|
+
if (!turnExceeded && !tokenExceeded)
|
|
678
|
+
return;
|
|
679
|
+
contextBudgetExhausted = true;
|
|
680
|
+
contextBudgetDetail = [
|
|
681
|
+
"context-budget-exhausted:",
|
|
682
|
+
`observedTurns=${observedTurns}`,
|
|
683
|
+
`observedTokens=${observedTokens}`,
|
|
684
|
+
`maxTurns=${options.contextBudget.maxTurns ?? "unbounded"}`,
|
|
685
|
+
`maxTokens=${options.contextBudget.maxTokens ?? "unbounded"}`,
|
|
686
|
+
].join(" ");
|
|
687
|
+
resolveContextBudget?.("context-budget");
|
|
688
|
+
};
|
|
658
689
|
unsubscribe = session.subscribe((event) => {
|
|
659
690
|
if (event.type === "agent_settled" && !settled) {
|
|
660
691
|
settled = true;
|
|
@@ -678,6 +709,9 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
678
709
|
const usageSample = extractSdkUsageSample(event);
|
|
679
710
|
if (usageSample)
|
|
680
711
|
usageSamples.push(usageSample);
|
|
712
|
+
if (event.type === "turn_end")
|
|
713
|
+
observedTurns += 1;
|
|
714
|
+
checkContextBudget();
|
|
681
715
|
// Capture writer-thinking-exhausted evidence BEFORE the
|
|
682
716
|
// shouldPersistSessionEvent filter: thinking_delta is intentionally
|
|
683
717
|
// excluded from persisted JSONL, but must still count toward the
|
|
@@ -700,6 +734,11 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
700
734
|
const runTurn = async (userMessage) => {
|
|
701
735
|
const settlementPromise = resetSettlement();
|
|
702
736
|
const stallPromise = resetStallPromise();
|
|
737
|
+
const contextBudgetPromise = options.contextBudget
|
|
738
|
+
? new Promise((resolve) => {
|
|
739
|
+
resolveContextBudget = resolve;
|
|
740
|
+
})
|
|
741
|
+
: null;
|
|
703
742
|
const promptPromise = session.prompt(userMessage);
|
|
704
743
|
// A settlement event is permitted to win the race while prompt() remains
|
|
705
744
|
// pending. Observe a later rejection so it never becomes unhandled.
|
|
@@ -710,6 +749,7 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
710
749
|
settlementPromise,
|
|
711
750
|
...(timeoutPromise ? [timeoutPromise] : []),
|
|
712
751
|
...(stallPromise ? [stallPromise] : []),
|
|
752
|
+
...(contextBudgetPromise ? [contextBudgetPromise] : []),
|
|
713
753
|
]);
|
|
714
754
|
};
|
|
715
755
|
const disposeSession = async () => {
|
|
@@ -737,7 +777,12 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
737
777
|
: options.userMessage;
|
|
738
778
|
let continuesSent = 0;
|
|
739
779
|
let raced = await runTurn(promptMessage);
|
|
740
|
-
if (raced === "
|
|
780
|
+
if (raced === "context-budget") {
|
|
781
|
+
appendStderr(contextBudgetDetail);
|
|
782
|
+
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
783
|
+
await disposeSession();
|
|
784
|
+
}
|
|
785
|
+
else if (raced === "stall") {
|
|
741
786
|
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
742
787
|
const withinAbsoluteTimeout = timeoutMs <= 0 || Date.now() - startedAt < timeoutMs;
|
|
743
788
|
const canContinue = abortConfirmed &&
|
|
@@ -154,6 +154,28 @@ function assertMkdirRelative(relative, ctx) {
|
|
|
154
154
|
throw new PiWriterPathDeniedError("forbidden", relative || ".");
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
+
export async function assertReadablePath(targetPath, ctx) {
|
|
158
|
+
const raw = String(targetPath ?? "").trim();
|
|
159
|
+
if (!raw)
|
|
160
|
+
throw new PiWriterPathDeniedError("empty-path", targetPath ?? "");
|
|
161
|
+
const repoRoot = path.resolve(ctx.repoRoot);
|
|
162
|
+
const absoluteTarget = path.isAbsolute(raw)
|
|
163
|
+
? path.resolve(raw)
|
|
164
|
+
: path.resolve(repoRoot, raw);
|
|
165
|
+
if (!isPathInside(repoRoot, absoluteTarget)) {
|
|
166
|
+
throw new PiWriterPathDeniedError("outside-repo", raw);
|
|
167
|
+
}
|
|
168
|
+
const effective = await resolveEffectiveMutationTarget(repoRoot, absoluteTarget);
|
|
169
|
+
const relative = repoRelativePath(repoRoot, absoluteTarget, raw, false);
|
|
170
|
+
const effectiveRelative = repoRelativePath(effective.realRoot, effective.absoluteTarget, raw, false);
|
|
171
|
+
const readSet = ctx.readSet ?? [];
|
|
172
|
+
for (const candidate of [relative, effectiveRelative]) {
|
|
173
|
+
if (readSet.length === 0 || !matchesAnyPattern(candidate, readSet)) {
|
|
174
|
+
throw new PiWriterPathDeniedError("read-outside-read-set", candidate, "path is outside the node readSet");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return relative;
|
|
178
|
+
}
|
|
157
179
|
/**
|
|
158
180
|
* Assert a mutation target is writable under the DAG writer policy.
|
|
159
181
|
* Returns the normalized lexical repo-relative path on success.
|
|
@@ -201,11 +223,30 @@ export async function assertMkdirPath(dirPath, ctx) {
|
|
|
201
223
|
assertMkdirRelative(effectiveRelative, ctx);
|
|
202
224
|
return lexicalRelative;
|
|
203
225
|
}
|
|
226
|
+
export function createPiReaderFsOperations(ctx) {
|
|
227
|
+
return {
|
|
228
|
+
readFile: async (absolutePath) => {
|
|
229
|
+
await assertReadablePath(absolutePath, ctx);
|
|
230
|
+
return fsReadFile(absolutePath);
|
|
231
|
+
},
|
|
232
|
+
access: async (absolutePath) => {
|
|
233
|
+
await assertReadablePath(absolutePath, ctx);
|
|
234
|
+
await fsAccess(absolutePath);
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
204
238
|
/** Build FS operations that enforce writer policy before mutations. */
|
|
205
239
|
export function createPiWriterFsOperations(ctx) {
|
|
240
|
+
const strictReadSet = (ctx.readSet?.length ?? 0) > 0;
|
|
206
241
|
return {
|
|
207
|
-
readFile: (absolutePath) =>
|
|
242
|
+
readFile: async (absolutePath) => {
|
|
243
|
+
if (strictReadSet)
|
|
244
|
+
await assertReadablePath(absolutePath, ctx);
|
|
245
|
+
return fsReadFile(absolutePath);
|
|
246
|
+
},
|
|
208
247
|
access: async (absolutePath) => {
|
|
248
|
+
if (strictReadSet)
|
|
249
|
+
await assertReadablePath(absolutePath, ctx);
|
|
209
250
|
await fsAccess(absolutePath);
|
|
210
251
|
},
|
|
211
252
|
writeFile: async (absolutePath, content) => {
|
|
@@ -218,6 +259,20 @@ export function createPiWriterFsOperations(ctx) {
|
|
|
218
259
|
},
|
|
219
260
|
};
|
|
220
261
|
}
|
|
262
|
+
export async function createPiReaderCustomTools(ctx) {
|
|
263
|
+
const sdk = (await import("@earendil-works/pi-coding-agent"));
|
|
264
|
+
const createReadToolDefinition = sdk.createReadToolDefinition;
|
|
265
|
+
if (typeof createReadToolDefinition !== "function") {
|
|
266
|
+
throw new Error("pi read tool policy unavailable: createReadToolDefinition missing from SDK");
|
|
267
|
+
}
|
|
268
|
+
const read = createReadToolDefinition(path.resolve(ctx.repoRoot), {
|
|
269
|
+
operations: createPiReaderFsOperations(ctx),
|
|
270
|
+
});
|
|
271
|
+
if (read?.name !== "read") {
|
|
272
|
+
throw new Error("pi read tool policy unavailable: SDK tool must be named read");
|
|
273
|
+
}
|
|
274
|
+
return [read];
|
|
275
|
+
}
|
|
221
276
|
/**
|
|
222
277
|
* Create same-name custom edit/write tool definitions that override built-ins
|
|
223
278
|
* via Pi SDK customTools registration. Fails closed when SDK factories are missing.
|
|
@@ -262,5 +317,6 @@ export function buildPiWriterToolPolicyContext(input) {
|
|
|
262
317
|
writeSet: input.writeSet ?? [],
|
|
263
318
|
forbiddenPaths: input.forbiddenPaths ?? [],
|
|
264
319
|
writePolicy: input.writePolicy ?? "exclusive",
|
|
320
|
+
readSet: input.readSet ?? [],
|
|
265
321
|
};
|
|
266
322
|
}
|
|
@@ -550,13 +550,22 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
550
550
|
const started = Date.now();
|
|
551
551
|
// Plan A: prefer the frozen layout carried by the compiled spec; fall back
|
|
552
552
|
// to the historical default for hand-written legacy DAGs.
|
|
553
|
-
const
|
|
553
|
+
const baseBackendLayout = resolveBackendTestLayout(meta.spec.backendTestLayout
|
|
554
554
|
? {
|
|
555
555
|
testRoot: meta.spec.backendTestLayout.testRoot,
|
|
556
556
|
markdownRel: meta.spec.backendTestLayout.markdownRel,
|
|
557
557
|
scriptRel: meta.spec.backendTestLayout.scriptRel,
|
|
558
558
|
}
|
|
559
559
|
: undefined);
|
|
560
|
+
const markdownPlanArtifact = path.join(meta.runDir, "generate-backend-md-plan-pi", "plan.md");
|
|
561
|
+
const backendLayout = existsSync(markdownPlanArtifact)
|
|
562
|
+
? {
|
|
563
|
+
...baseBackendLayout,
|
|
564
|
+
readmePath: path
|
|
565
|
+
.relative(input.cwd, markdownPlanArtifact)
|
|
566
|
+
.replaceAll(path.sep, "/"),
|
|
567
|
+
}
|
|
568
|
+
: baseBackendLayout;
|
|
560
569
|
try {
|
|
561
570
|
if (!pipeline)
|
|
562
571
|
throw new Error("missing backend-test pipeline id");
|
|
@@ -231,6 +231,10 @@ export function projectCompactSnapshot(value) {
|
|
|
231
231
|
Number.isFinite(record.tokensBefore)
|
|
232
232
|
? Math.max(0, Math.floor(record.tokensBefore))
|
|
233
233
|
: undefined;
|
|
234
|
+
const estimatedTokensAfter = typeof record.estimatedTokensAfter === "number" &&
|
|
235
|
+
Number.isFinite(record.estimatedTokensAfter)
|
|
236
|
+
? Math.max(0, Math.floor(record.estimatedTokensAfter))
|
|
237
|
+
: undefined;
|
|
234
238
|
const details = record.details === undefined
|
|
235
239
|
? undefined
|
|
236
240
|
: boundedRedactedJson(record.details).preview;
|
|
@@ -239,9 +243,16 @@ export function projectCompactSnapshot(value) {
|
|
|
239
243
|
summary,
|
|
240
244
|
...(firstKeptEntryId ? { firstKeptEntryId } : {}),
|
|
241
245
|
...(tokensBefore !== undefined ? { tokensBefore } : {}),
|
|
246
|
+
...(estimatedTokensAfter !== undefined ? { estimatedTokensAfter } : {}),
|
|
242
247
|
...(details ? { details } : {}),
|
|
243
248
|
previewHash: createHash("sha256")
|
|
244
|
-
.update(JSON.stringify({
|
|
249
|
+
.update(JSON.stringify({
|
|
250
|
+
summary,
|
|
251
|
+
firstKeptEntryId,
|
|
252
|
+
tokensBefore,
|
|
253
|
+
estimatedTokensAfter,
|
|
254
|
+
details,
|
|
255
|
+
}))
|
|
245
256
|
.digest("hex"),
|
|
246
257
|
};
|
|
247
258
|
}
|
|
@@ -785,6 +796,7 @@ export function createChatEventStore(options) {
|
|
|
785
796
|
"assessment",
|
|
786
797
|
"taskKind-confirmed",
|
|
787
798
|
"human-gate-card",
|
|
799
|
+
"compaction_end",
|
|
788
800
|
"compact",
|
|
789
801
|
"artifact-ref",
|
|
790
802
|
]);
|