@tea-agent/loop-agent 0.43.0-next.2 → 0.43.0-next.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/CHANGELOG.md +15 -12
- package/dist/application/dag/run-dag.js +5 -0
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +274 -172
- package/dist/shared/frontend-execution-policy.js +1 -0
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime.js +18 -0
- package/dist/worker/console/chat/routes.js +11 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +65 -9
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +284 -0
- package/dist/worker/console/chat/terminal-tools.js +199 -0
- package/dist/worker/console/chat/tool-preview.js +11 -0
- package/dist/worker/console/chat/tools.js +1 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B0Q0nClf.js → abnfDiagram-N423BO3Z-CCj36aaC.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DCPjC19G.js → arc-Cl3MIVFh.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP5n9jmG.js → architectureDiagram-T3A2C74G-C_n4kMJS.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CmaWi_Wg.js → blockDiagram-VBNYF7ZC-DlWYJ7cK.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ct5tcMfZ.js → c4Diagram-5PPSVZJV-Ud5pS7vS.js} +1 -1
- package/dist/worker/console/static/assets/channel-CEMRpNm-.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C0osm1Zf.js → chunk-2GRJ4B5K-CQPwOv_j.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDWviED7.js → chunk-2Q5K7J3B-CMzjxrWI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-mtSk1-j7.js → chunk-5RXB4S5H-pcnXnD6t.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D5J9PHC7.js → chunk-5VM5RSS4-CEB3AvU2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Begg4WAa.js → chunk-6Q2QTUOP-DSKnbXbp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-sF1AEy7T.js → chunk-GF5L2VYU-CS6-PjCZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BqbsWXZb.js → chunk-JWPE2WC7-f6q8Tnzz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BKJrPcJ6.js → chunk-KBJHAD2P-DkB0QX3q.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CcwNMRho.js → chunk-RYQCIY6F-BdB4jAv6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BEJi3iIs.js → chunk-XXDRQBXY-BVm1bnX6.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C9lhaZkR.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C9lhaZkR.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-DRLgTvVu.js → cose-bilkent-JH36ORCC-FV_P-IEp.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BoAVG3cs.js → cynefin-VYW2F7L2-BM7RtkA2.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bzol06hR.js → cynefinDiagram-MW4NZA55-C1BZwTG5.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UmliJM77.js → dagre-VZM6K2ZE-C6McNKmY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BWLcJkFo.js → diagram-7IWD3JNH-C0aWRUaE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CgApOm-O.js → diagram-B4RE2ZJO-Bfk_SKE8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bh3RgTLs.js → diagram-LBJQPF4R-prx1T4qf.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Dsh-D5nE.js → diagram-Q27KOJAE-DBvty0eU.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dkbbpcpb.js → diagram-UB23O5K3-CF0em8e9.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-3EBaA3t3.js → ebnfDiagram-BXEA7PRR-CF90b4Ws.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bq2Dc5ok.js → erDiagram-JOGREHBK-hfunxzof.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-De7y55ha.js → flowDiagram-UKHOOZJN-D-QSy4Nl.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-fLiDbQRh.js → ganttDiagram-PKOTCBZU-DaBGNcvT.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Du617mp1.js → gitGraphDiagram-DS77QQ5N-BChcubGL.js} +1 -1
- package/dist/worker/console/static/assets/index-CJkCY1XR.js +464 -0
- package/dist/worker/console/static/assets/index-DUBCbiqv.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BwmlF-XP.js → infoDiagram-6WML65LV-BkC67fIa.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-zsGtS59u.js → ishikawaDiagram-WSZJBQD7-ibhLczCD.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DTTPTe3f.js → journeyDiagram-NVQOT4AX-DCRMitay.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BkXg9aP-.js → kanban-definition-27J2QSJJ-DJ_hKaYI.js} +1 -1
- package/dist/worker/console/static/assets/{linear-7U2ue5IE.js → linear-CIlLsYsA.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BUuGHmWO.js → mermaid.core-C-5IQMP_.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-7q6fX0Sv.js → mindmap-definition-FAOFIHXS-TCzV5TQT.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkQPJN55.js → pegDiagram-VL7TDLO6-bfhd2BQH.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CCal_tfX.js → pieDiagram-7S7Q4E2Y-BxFRAm5N.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js → quadrantDiagram-CIZ2JOQS-C-m12lxv.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CJmww7D-.js → railroadDiagram-AXF67PYL-CwmLiGgx.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D6ldWJEv.js → requirementDiagram-LRYGKXZP-oc0Q1jiD.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-VGh33I9e.js → sankeyDiagram-W5VNT64P-9Ght6Yur.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CCkDrIjU.js → sequenceDiagram-SI44F4Z6-ndm3lyz9.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-NC8f5Otb.js → sizeCapture-X5ZJPWSS-7sPlKVcw.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-_92ezZdF.js → stateDiagram-OKZ733FA-Izl21-oS.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CmElLFpP.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BdUCGtbP.js → swimlanes-SLNWSIFB-CtO3D65o.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DKRbGG8V.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CdD1H8ia.js → timeline-definition-Z64GVDOM-Cyg5cY7z.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Dkdk3oFo.js → vennDiagram-T6HMQDX7-CwtE_TFK.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CKu2uPPY.js → wardleyDiagram-T6FBY63Y-Dtt9v8OO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-ClqAnuG8.js → xychartDiagram-ELKLHX3M-B2Y3ryb6.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +122 -10
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +27 -15
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +18 -2
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +39 -4
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/workspace-context.js +9 -1
- package/dist/workflows/dag/frontend-shape.js +30 -1
- package/dist/workflows/dag/node-execution.js +6 -0
- package/package.json +6 -3
- package/dist/worker/console/static/assets/channel-DAS07MdS.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/index-C0O48S_P.js +0 -449
- package/dist/worker/console/static/assets/index-CzKf4U8P.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,23 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
### 新增
|
|
6
|
+
|
|
7
|
+
- Operator Chat 在具备 Aimax 凭据资格时,向对话进程注入 `$AIMAX_AUTH_TOKEN`(明文或 `sm4:v1:` 解密后的 ticket),供 skills 读取;文件缺失、无法解析或解密失败时不注入该变量。
|
|
8
|
+
- Operator Chat 终端管理(DSH 风格):模型获得 `terminal` 持久 PTY 工具(跨平台 shell 目录——Windows 提供 powershell/bash,macOS/Linux 提供 bash/zsh,pwsh 7 装了即用;cwd/环境变量/后台任务跨调用保留,完成标记协议 + 超时与截断保护,枚举与默认值按平台动态生成);Console 右侧辅助栏新增「终端」面板(xterm.js 渲染,实时查看/接管模型的 shell,按平台动态展示可创建的 shell,支持发送输入、Ctrl+C、终止并关闭);模型 shell 与用户面板共用同一会话注册表并开在工作区仓库根目录,命名区分来源。
|
|
9
|
+
|
|
5
10
|
### 改进
|
|
6
11
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- 观测台保留检查器 Tab 导航,日志分类收进下拉框,辅助操作改为带提示的图标;所有执行记录区域(检查器执行记录/产出、运行页最新输出)统一配备对话式「回到底部」悬浮按钮:内容可滚动且视口离开底部即出现,贴底或内容不足一屏时隐藏,点击回到底部;活跃运行同时恢复跟随并提示未读条数,终态运行同样可用。执行分次仅在相关页面固定显示,上翻暂停跟随,并记住分次阅读位置。补充日志搜索、错误过滤、图定位和完整脱敏文本下载。
|
|
10
|
-
- 实际上下文改为中文材料目录优先、技术信息折叠;修复历史轮次混读当前上下文和旧请求覆盖新尝试的问题。保留仅看错误与原始协议事件,原始协议使用直接可达的图标开关,移除单项更多菜单和日志 HTML 导出;交接包明确固定来源并支持刷新。
|
|
11
|
-
- 检查器隐藏无内容或明确不适用的入口:单次执行不显示尝试对比,Shell/静态节点不显示实际上下文,静态节点不显示执行记录;切换节点与新增尝试时自动更新,保留用户当前的比较选择。
|
|
12
|
-
- 资源池筛选覆盖任务标题和全部任务区域,返回列表及历史分页保留阅读状态;重跑草稿和影响预览可恢复,结果不确定时原样重试已确认的请求,避免重复创建运行。
|
|
13
|
-
- 执行记录默认跟随最新内容;上翻自动暂停并出现对话式「回到底部」按钮,点击回到底部;历史记录保留阅读位置,终态运行同样可用。
|
|
14
|
-
- DAG 节点检查器仅对最新且仍运行的执行默认跟随;右下角显示阅读状态、新记录提示和“回到最新”,上翻即暂停。
|
|
12
|
+
- 优化 Operator Chat 长会话的流式渲染:完成回合、消息行、工具卡和用户输入锚点建立稳定的 memo 边界,流式文本更新不再重复渲染整段历史或重装锚点测量监听;缓存会在历史消息或工具实际更新时正确失效;无新增折叠回合时不再提交空状态及重复写入本地存储,工具参数与结果格式化按真实输入变化缓存。
|
|
13
|
+
- 对话页「过程折叠」对齐 DSH 细节:折叠后的内容仍保留在页面中,浏览器 Ctrl+F 可直接命中并自动展开;折叠行展开时不再多出底部空隙;箭头改为 16px、颜色更淡的固定图标(靠旋转而非换图标),并补齐系统「减少动态效果」偏好;汇总文案新增「N 个 subagent」,内置 Explore 子代理不再计入普通工具调用。
|
|
15
14
|
|
|
16
15
|
### 修复
|
|
17
16
|
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
17
|
+
- 修复对话页存在运行活动时,收起状态的右侧辅助栏展开按钮覆盖「运行活动 N」及数字徽标的问题;中大尺寸布局现在保留 8px 操作间距,窄屏布局不变。
|
|
18
|
+
- 修复 Operator Chat 消息流的六处实际使用问题:
|
|
19
|
+
- **回复被吞**:断线/重连(含每 25 秒的 SSE 事件窗口轮转)后,保留事件里已经生成的正文会在事件流恢复时重新应用到对应回合,不再被静默丢弃;仅当该回合已有更新内容时才跳过,避免重复气泡。
|
|
20
|
+
- **用户输入消失或重复**:服务端确认后,提交回滚(排队抢占、被拒绝、连接失败)不再删掉已确认的消息;确认帧到达时按请求 ID 或持久消息 ID 原地合并乐观气泡与会话快照,retained replay 不再追加同一条用户消息。
|
|
21
|
+
- **回复重复**:上下文溢出自动压缩并重新生成后,被打断的旧回复会被取代,不再与重新生成的回复同时显示。
|
|
22
|
+
- **只有思考没有正文**:模型回复以工具调用或思考结尾时,正文会作为回答显示,不再被折叠进「过程」区。
|
|
23
|
+
- **空气泡误删**:只包含思考或工具内容、正文为空的回合不再被清理掉。
|
|
24
|
+
- **停止语义**:被停止中断的工具调用显示「已停止」,不再显示为「出错」并强制展开、计入「N 次出错」。
|
|
22
25
|
|
|
23
26
|
## [0.42.0] - 2026-09-07
|
|
24
27
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { DAG_ABSOLUTE_MAX_WALL_CLOCK_MS } from "../../shared/timeout-policy.js";
|
|
2
3
|
import { createDagEventObserver } from "../../workflows/dag/event-observer.js";
|
|
3
4
|
import { composeDagRunObservers } from "../../workflows/dag/observer-compose.js";
|
|
4
5
|
import { createDagCanvasObserver, resolveCanvasPath, } from "../../workflows/dag/canvas-observer.js";
|
|
@@ -47,6 +48,9 @@ export async function buildDefaultFrontendRecoveryDeps(repoRoot) {
|
|
|
47
48
|
});
|
|
48
49
|
const client = new LoopAgentClient({
|
|
49
50
|
loopAgentBin: resolveSiblingLoopAgentBin(),
|
|
51
|
+
// rerun-task awaits the entire continuation, not just task preparation.
|
|
52
|
+
// Use the same hard ceiling as the DAG runner and Console entry point.
|
|
53
|
+
defaultTimeoutMs: DAG_ABSOLUTE_MAX_WALL_CLOCK_MS,
|
|
50
54
|
artifactRoot: path.join(appData.root, "client-artifacts", appData.fingerprint),
|
|
51
55
|
resolveIdentity: true,
|
|
52
56
|
});
|
|
@@ -54,6 +58,7 @@ export async function buildDefaultFrontendRecoveryDeps(repoRoot) {
|
|
|
54
58
|
store,
|
|
55
59
|
operationRunnerDeps: {
|
|
56
60
|
store,
|
|
61
|
+
defaultTimeoutMs: DAG_ABSOLUTE_MAX_WALL_CLOCK_MS,
|
|
57
62
|
events,
|
|
58
63
|
client,
|
|
59
64
|
repoRoot,
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.43.0-next.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-09-
|
|
3
|
+
"version": "0.43.0-next.4",
|
|
4
|
+
"gitSha": "a40dde963afaa57ff95e0e49aa33e051b5580827",
|
|
5
|
+
"builtAt": "2026-09-08T23:28:40.065Z"
|
|
6
6
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { collectFrontendExecutionGroups, frontendExecutionSchema } from "../workflows/dag/frontend-execution-groups.js";
|
|
2
2
|
import { FRONTEND_SCOPE_TARGET_BYTES, packFrontendInputUnits, parseFrontendInputBlock, projectFrontendContractPrompt, projectFrontendInputScope } from "../workflows/dag/frontend-input-projection.js";
|
|
3
3
|
import { createDurableFrontendTools } from "../workflows/dag/frontend-durable-tools.js";
|
|
4
|
+
import { sha256OfCanonicalJson } from "../task/contract/hash.js";
|
|
5
|
+
import { z } from "zod";
|
|
4
6
|
import { createFrontendReviewScopeProtocol, loadFrontendReviewScopes } from "../workflows/dag/frontend-review-scopes.js";
|
|
5
7
|
import { observeFrontendSession } from "../workflows/dag/frontend-session-budget.js";
|
|
6
8
|
import path from "node:path";
|
|
@@ -3908,18 +3910,30 @@ const FRONTEND_PLAN_COVERAGE_MAX_CONCURRENCY = 4;
|
|
|
3908
3910
|
const FRONTEND_PLAN_BATCH_MAX_SESSIONS = 128;
|
|
3909
3911
|
const FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS = 8;
|
|
3910
3912
|
const FRONTEND_PLAN_SMALL_MAX_ESTIMATED_CALLS = 24;
|
|
3911
|
-
async function mapWithConcurrency(items, limit, worker) {
|
|
3913
|
+
async function mapWithConcurrency(items, limit, worker, shouldReduceConcurrency) {
|
|
3912
3914
|
const results = new Array(items.length);
|
|
3913
3915
|
let nextIndex = 0;
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3916
|
+
let concurrency = Math.max(1, limit);
|
|
3917
|
+
const running = new Map();
|
|
3918
|
+
try {
|
|
3919
|
+
while (nextIndex < items.length || running.size > 0) {
|
|
3920
|
+
while (nextIndex < items.length && running.size < concurrency) {
|
|
3921
|
+
const index = nextIndex++;
|
|
3922
|
+
running.set(index, worker(items[index], index).then(result => ({ index, result })));
|
|
3923
|
+
}
|
|
3924
|
+
const { index, result } = await Promise.race(running.values());
|
|
3925
|
+
running.delete(index);
|
|
3926
|
+
results[index] = result;
|
|
3927
|
+
// Drain existing work; only pending shards use the reduced cap.
|
|
3928
|
+
// Failed shards remain failed and receive no extra retry allowance.
|
|
3929
|
+
if (shouldReduceConcurrency(result))
|
|
3930
|
+
concurrency = Math.max(1, Math.floor(concurrency / 2));
|
|
3921
3931
|
}
|
|
3922
|
-
}
|
|
3932
|
+
}
|
|
3933
|
+
catch (error) {
|
|
3934
|
+
await Promise.allSettled(running.values());
|
|
3935
|
+
throw error;
|
|
3936
|
+
}
|
|
3923
3937
|
return results;
|
|
3924
3938
|
}
|
|
3925
3939
|
function compactPromptString(value, _maxChars) {
|
|
@@ -4522,6 +4536,110 @@ export async function runFrontendContractSegmentedSessions(input) {
|
|
|
4522
4536
|
input.tools.setActiveRequirementScope(null);
|
|
4523
4537
|
}
|
|
4524
4538
|
}
|
|
4539
|
+
/** One workload view for both the outer parallel map and inner session queue.
|
|
4540
|
+
* A declared execution group is an indivisible unit during initial packing. */
|
|
4541
|
+
function buildFrontendPlanWorkload(input) {
|
|
4542
|
+
const allRequirementIds = input.requirementIds;
|
|
4543
|
+
const compiledInput = parseFrontendInputBlock(input.basePrompt, "plan")?.payload;
|
|
4544
|
+
const fullUnits = new Map(compiledInput?.requirements.map(r => [r.id, r]) ?? []);
|
|
4545
|
+
const declaredGroups = Array.isArray(compiledInput?.executionGroups)
|
|
4546
|
+
? compiledInput.executionGroups.filter(isRecordObject) : [];
|
|
4547
|
+
const workGroups = declaredGroups.map(g => ({
|
|
4548
|
+
id: String(g.id), kind: String(g.kind),
|
|
4549
|
+
requirementIds: Array.isArray(g.requirementIds)
|
|
4550
|
+
? g.requirementIds.filter((id) => typeof id === "string" && allRequirementIds.includes(id)) : [],
|
|
4551
|
+
})).filter(g => g.requirementIds.length);
|
|
4552
|
+
const groupedIds = new Set(workGroups.flatMap(g => g.requirementIds));
|
|
4553
|
+
for (const id of allRequirementIds) {
|
|
4554
|
+
if (!groupedIds.has(id))
|
|
4555
|
+
workGroups.push({ id, kind: "unclassified", requirementIds: [id] });
|
|
4556
|
+
}
|
|
4557
|
+
const workCost = (ids) => 1 + ids.reduce((total, id) => total + Math.max(1, (input.requirementCosts?.get(id) ?? 2) - 1), 0);
|
|
4558
|
+
const policy = input.sessionOptions.frontendExecutionPolicy;
|
|
4559
|
+
const targetBytes = policy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES;
|
|
4560
|
+
const userMessageBytes = Buffer.byteLength(input.sessionOptions.userMessage ?? "");
|
|
4561
|
+
const buildWorkBatches = (ids) => {
|
|
4562
|
+
const work = workGroups.flatMap((g, index) => {
|
|
4563
|
+
const members = g.requirementIds.filter(id => ids.includes(id));
|
|
4564
|
+
return members.length ? [{ id: `${index}:${g.id}`, requirementIds: members,
|
|
4565
|
+
requirements: members.map(id => fullUnits.get(id) ?? { id }), estimatedCalls: workCost(members) }] : [];
|
|
4566
|
+
});
|
|
4567
|
+
const scaffoldBytes = Buffer.byteLength(input.basePrompt.replace(/<frontend_plan_input>[\s\S]*?<\/frontend_plan_input>/, JSON.stringify({ ...compiledInput, requirements: [] }))) + userMessageBytes + Buffer.byteLength(JSON.stringify(input.coverageTools));
|
|
4568
|
+
return packFrontendInputUnits(work, {
|
|
4569
|
+
targetBytes: Math.max(1, targetBytes - scaffoldBytes),
|
|
4570
|
+
maxUnits: policy?.maxScopeUnits ?? 4,
|
|
4571
|
+
maxCost: FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS, cost: g => g.estimatedCalls,
|
|
4572
|
+
}).map(batch => batch.flatMap(g => g.requirementIds));
|
|
4573
|
+
};
|
|
4574
|
+
// This renderer also carries the Plan's evolving UX checkpoint on resume.
|
|
4575
|
+
// Only the Contract/Scout-derived input participates in ownership binding.
|
|
4576
|
+
const { committedUx: _committedUx, ...frozenInput } = compiledInput ?? {};
|
|
4577
|
+
return {
|
|
4578
|
+
frozenInput, workGroups, buildWorkBatches,
|
|
4579
|
+
compactEligible: input.requirementCosts !== undefined &&
|
|
4580
|
+
workGroups.length <= FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS &&
|
|
4581
|
+
Buffer.byteLength(input.basePrompt) + userMessageBytes + Buffer.byteLength(JSON.stringify(input.allTools)) <= targetBytes * 2 &&
|
|
4582
|
+
workGroups.reduce((total, group) => total + workCost(group.requirementIds), 0) <= FRONTEND_PLAN_SMALL_MAX_ESTIMATED_CALLS &&
|
|
4583
|
+
countFrontendPlanTargetSurfaces(input.basePrompt) === 1,
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
const frontendPlanCoverageLayoutSchema = z.object({
|
|
4587
|
+
schemaVersion: z.literal(1),
|
|
4588
|
+
bindingSha256: z.string(),
|
|
4589
|
+
coverageBatches: z.array(z.array(z.string().min(1)).min(1)).min(1),
|
|
4590
|
+
layoutSha256: z.string(),
|
|
4591
|
+
}).strict();
|
|
4592
|
+
/** Freeze ledger ownership before any provider call. Session packing remains
|
|
4593
|
+
* adaptive inside each owner, but retry instructions cannot rename its scope. */
|
|
4594
|
+
async function loadOrCreateFrontendPlanCoverageLayout(input) {
|
|
4595
|
+
const fileName = "coverage-layout.json";
|
|
4596
|
+
const file = path.join(input.runDir, input.nodeId, fileName);
|
|
4597
|
+
const bindingSha256 = sha256OfCanonicalJson(input.binding);
|
|
4598
|
+
let raw;
|
|
4599
|
+
try {
|
|
4600
|
+
raw = await readFile(file, "utf8");
|
|
4601
|
+
}
|
|
4602
|
+
catch (error) {
|
|
4603
|
+
if (error.code !== "ENOENT")
|
|
4604
|
+
throw error;
|
|
4605
|
+
}
|
|
4606
|
+
let layout;
|
|
4607
|
+
if (raw !== undefined) {
|
|
4608
|
+
layout = frontendPlanCoverageLayoutSchema.parse(JSON.parse(raw));
|
|
4609
|
+
}
|
|
4610
|
+
else {
|
|
4611
|
+
// Losing the ownership receipt must never repartition acknowledged facts.
|
|
4612
|
+
for (const relative of ["plan-typed-facts.jsonl", "parallel"]) {
|
|
4613
|
+
const existing = await stat(path.join(input.runDir, input.nodeId, relative)).catch(error => {
|
|
4614
|
+
if (error.code !== "ENOENT")
|
|
4615
|
+
throw error;
|
|
4616
|
+
return undefined;
|
|
4617
|
+
});
|
|
4618
|
+
if (existing && (existing.isDirectory() || existing.size > 0)) {
|
|
4619
|
+
throw Error("FRONTEND_PLAN_LAYOUT_MISSING: preserve the existing ledger and restart its owning phase");
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
const descriptor = {
|
|
4623
|
+
schemaVersion: 1, bindingSha256,
|
|
4624
|
+
coverageBatches: input.workload.compactEligible ? [input.requirementIds] : input.workload.buildWorkBatches(input.requirementIds),
|
|
4625
|
+
};
|
|
4626
|
+
layout = { ...descriptor, layoutSha256: sha256OfCanonicalJson(descriptor) };
|
|
4627
|
+
}
|
|
4628
|
+
const { layoutSha256, ...descriptor } = layout;
|
|
4629
|
+
if (layout.bindingSha256 !== bindingSha256 || layoutSha256 !== sha256OfCanonicalJson(descriptor)) {
|
|
4630
|
+
throw Error("FRONTEND_PLAN_LAYOUT_MISMATCH: frozen coverage ownership or input binding changed");
|
|
4631
|
+
}
|
|
4632
|
+
const members = layout.coverageBatches.flat();
|
|
4633
|
+
const owners = new Map(layout.coverageBatches.flatMap((batch, index) => batch.map(id => [id, index])));
|
|
4634
|
+
if (members.length !== owners.size || members.length !== input.requirementIds.length ||
|
|
4635
|
+
input.requirementIds.some(id => !owners.has(id)) ||
|
|
4636
|
+
input.workload.workGroups.some(group => new Set(group.requirementIds.map(id => owners.get(id))).size !== 1)) {
|
|
4637
|
+
throw Error("FRONTEND_PLAN_LAYOUT_INVALID: coverage must partition complete execution groups exactly once");
|
|
4638
|
+
}
|
|
4639
|
+
if (raw === undefined)
|
|
4640
|
+
await writeDagNodeJsonArtifact(input.runDir, input.nodeId, fileName, layout);
|
|
4641
|
+
return layout.coverageBatches;
|
|
4642
|
+
}
|
|
4525
4643
|
export async function runFrontendPlanSegmentedSessions(input) {
|
|
4526
4644
|
const queue = [];
|
|
4527
4645
|
const coverageSegment = FRONTEND_PLAN_SEGMENTS.find((segment) => segment.id === "coverage");
|
|
@@ -4709,25 +4827,12 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4709
4827
|
tokensUsed: 0,
|
|
4710
4828
|
};
|
|
4711
4829
|
}
|
|
4712
|
-
const
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
if (!groupedIds.has(id))
|
|
4719
|
-
workGroups.push({ id, kind: "unclassified", requirementIds: [id] });
|
|
4720
|
-
const workCost = (ids) => 1 + ids.reduce((total, id) => total + Math.max(1, (input.requirementCosts?.get(id) ?? 2) - 1), 0);
|
|
4721
|
-
const buildWorkBatches = (ids) => {
|
|
4722
|
-
const work = workGroups.flatMap((g, index) => {
|
|
4723
|
-
const members = g.requirementIds.filter(id => ids.includes(id));
|
|
4724
|
-
return members.length ? [{ id: `${index}:${g.id}`, requirementIds: members, requirements: members.map(id => fullUnits.get(id) ?? { id }), estimatedCalls: workCost(members) }] : [];
|
|
4725
|
-
});
|
|
4726
|
-
const scaffoldBytes = Buffer.byteLength(input.basePrompt.replace(/<frontend_plan_input>[\s\S]*?<\/frontend_plan_input>/, JSON.stringify({ ...compiledInput, requirements: [] }))) + Buffer.byteLength(JSON.stringify(input.segmentCustomTools(coverageSegment.toolNames)));
|
|
4727
|
-
return packFrontendInputUnits(work, { targetBytes: Math.max(1, (input.sessionOptions.frontendExecutionPolicy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES) - scaffoldBytes), maxUnits: input.sessionOptions.frontendExecutionPolicy?.maxScopeUnits ?? 4, maxCost: FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS, cost: g => g.estimatedCalls }).map(batch => batch.flatMap(g => g.requirementIds));
|
|
4728
|
-
};
|
|
4729
|
-
const estimatedCalls = workGroups.reduce((total, group) => total + workCost(group.requirementIds), 0);
|
|
4730
|
-
const targetSurfaceCount = countFrontendPlanTargetSurfaces(input.basePrompt);
|
|
4830
|
+
const { workGroups, buildWorkBatches, compactEligible } = buildFrontendPlanWorkload({
|
|
4831
|
+
basePrompt: input.basePrompt, requirementIds: allRequirementIds,
|
|
4832
|
+
requirementCosts: input.requirementCosts, sessionOptions: input.sessionOptions,
|
|
4833
|
+
coverageTools: input.segmentCustomTools(coverageSegment.toolNames),
|
|
4834
|
+
allTools: input.segmentCustomTools(null),
|
|
4835
|
+
});
|
|
4731
4836
|
// Small, single-surface requests do not benefit from six isolated Pi
|
|
4732
4837
|
// sessions. Keep the typed ledger as the authority, but let one local
|
|
4733
4838
|
// session establish requirement/UX facts and one final session establish
|
|
@@ -4735,11 +4840,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4735
4840
|
// for larger plans and for the unscoped compatibility path.
|
|
4736
4841
|
const useCompactSmallPlan = requirementIdsProvided &&
|
|
4737
4842
|
input.compactSmallPlan === true &&
|
|
4738
|
-
|
|
4739
|
-
workGroups.length <= FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS &&
|
|
4740
|
-
Buffer.byteLength(input.basePrompt) + Buffer.byteLength(JSON.stringify(input.segmentCustomTools(null))) <= (input.sessionOptions.frontendExecutionPolicy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES) * 2 &&
|
|
4741
|
-
estimatedCalls <= FRONTEND_PLAN_SMALL_MAX_ESTIMATED_CALLS &&
|
|
4742
|
-
targetSurfaceCount === 1;
|
|
4843
|
+
compactEligible;
|
|
4743
4844
|
if (useCompactSmallPlan) {
|
|
4744
4845
|
const compactLocalTools = new Set([
|
|
4745
4846
|
"record_plan_requirement",
|
|
@@ -5954,160 +6055,161 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
5954
6055
|
flushLedger: () => options.ledgerTools.flush(),
|
|
5955
6056
|
});
|
|
5956
6057
|
if (planRequirementIds.length > 1) {
|
|
5957
|
-
const
|
|
5958
|
-
const
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
6058
|
+
const coverageTools = FRONTEND_PLAN_SEGMENTS.find(segment => segment.id === "coverage").toolNames;
|
|
6059
|
+
const workload = buildFrontendPlanWorkload({
|
|
6060
|
+
basePrompt: input.prompt, requirementIds: planRequirementIds,
|
|
6061
|
+
requirementCosts: planRequirementCosts, sessionOptions: piSessionOptions,
|
|
6062
|
+
allTools: planLedgerTools.customTools,
|
|
6063
|
+
coverageTools: planLedgerTools.customTools.filter(tool => isRecordObject(tool) && coverageTools.has(String(tool.name))),
|
|
6064
|
+
});
|
|
6065
|
+
let coverageBatches;
|
|
6066
|
+
try {
|
|
6067
|
+
coverageBatches = await loadOrCreateFrontendPlanCoverageLayout({
|
|
6068
|
+
runDir: meta.runDir, nodeId: input.task.id, workload, requirementIds: planRequirementIds,
|
|
6069
|
+
binding: {
|
|
6070
|
+
runId: meta.runId, nodeId: input.task.id, sourceBinding: meta.spec.sourceBinding,
|
|
6071
|
+
skeleton: input.task.structuredContractOutput?.skeleton, writeSet: input.task.writeSet,
|
|
6072
|
+
policy: meta.spec.frontendExecutionPolicy, requirements: [...planRequirementCosts],
|
|
6073
|
+
compiledInput: workload.frozenInput, workGroups: workload.workGroups,
|
|
6074
|
+
},
|
|
5969
6075
|
});
|
|
5970
6076
|
}
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
6077
|
+
catch (error) {
|
|
6078
|
+
return { ok: false, stdout: "", durationMs: Date.now() - started, failureCategory: "tool-policy",
|
|
6079
|
+
stderr: `frontend plan coverage layout unavailable: ${error instanceof Error ? error.message : String(error)}` };
|
|
6080
|
+
}
|
|
6081
|
+
if (coverageBatches.length > 1) {
|
|
6082
|
+
const shardResults = await mapWithConcurrency(coverageBatches, piSessionOptions.frontendExecutionPolicy?.maxCoverageConcurrency ?? FRONTEND_PLAN_COVERAGE_MAX_CONCURRENCY, async (slice, index) => {
|
|
6083
|
+
let shardTools;
|
|
6084
|
+
let shardResult;
|
|
6085
|
+
try {
|
|
6086
|
+
const { createTypedEventStore } = await import("../workflows/dag/frontend-typed-event-store.js");
|
|
6087
|
+
shardTools = await createFrontendPlanLedgerTools({
|
|
6088
|
+
attemptId: `${meta.runId}:${input.task.id}:parallel:${index + 1}`,
|
|
6089
|
+
store: createTypedEventStore(),
|
|
6090
|
+
runDir: meta.runDir,
|
|
6091
|
+
nodeId: `${input.task.id}/parallel/coverage-${index + 1}`,
|
|
6092
|
+
skeleton: input.task.structuredContractOutput?.skeleton,
|
|
6093
|
+
sourceBinding: meta.spec.sourceBinding,
|
|
6094
|
+
requirementIds: slice,
|
|
6095
|
+
writeSetPatterns: input.task.writeSet,
|
|
6096
|
+
canonicalVerificationTargetIds: await resolveFrontendCanonicalVerificationTargetIds({
|
|
5987
6097
|
runDir: meta.runDir,
|
|
5988
|
-
|
|
5989
|
-
|
|
6098
|
+
}),
|
|
6099
|
+
componentNewSourceReferences: await resolveFrontendPlanNewComponentSourceReferences({
|
|
6100
|
+
cwd: input.cwd,
|
|
5990
6101
|
sourceBinding: meta.spec.sourceBinding,
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
componentNewSourceReferences: await resolveFrontendPlanNewComponentSourceReferences({
|
|
5997
|
-
cwd: input.cwd,
|
|
5998
|
-
sourceBinding: meta.spec.sourceBinding,
|
|
5999
|
-
}),
|
|
6000
|
-
});
|
|
6001
|
-
const shardSessionOptions = {
|
|
6002
|
-
...piSessionOptions,
|
|
6003
|
-
sessionEventsPath: path.join(meta.runDir, input.task.id, "parallel", `coverage-${index + 1}`, "session-events.jsonl"),
|
|
6004
|
-
};
|
|
6005
|
-
shardResult = await runPlanSessions({
|
|
6006
|
-
ledgerTools: shardTools,
|
|
6007
|
-
sessionOptions: shardSessionOptions,
|
|
6008
|
-
requirementIds: slice,
|
|
6009
|
-
basePrompt: `${input.prompt}\n\nPARALLEL COVERAGE SHARD ${index + 1}: use the frozen canonical behavior verification-target ids listed in the record_plan_verification_target tool description — do not prefix ids with a shard namespace or invent variant ids; identical cross-shard targets are deduped, divergent ones fail the merge.`,
|
|
6010
|
-
parallelCoverageOnly: true,
|
|
6011
|
-
observation: {
|
|
6012
|
-
artifactPath: path.join(meta.runDir, input.task.id, "parallel", `coverage-${index + 1}`, "session-budget", `attempt-${input.attempt ?? 1}`),
|
|
6013
|
-
runId: meta.runId,
|
|
6014
|
-
nodeId: `${input.task.id}/parallel/coverage-${index + 1}`,
|
|
6015
|
-
taskId: meta.spec?.sourceBinding?.taskId,
|
|
6016
|
-
attempt: input.attempt ?? 1,
|
|
6017
|
-
model: input.model,
|
|
6018
|
-
sourceDigest: meta.spec?.sourceBinding?.schemaVersion === 2 ? meta.spec.sourceBinding.inputDigest : undefined,
|
|
6019
|
-
contractDigest: meta.spec?.taskContractBinding?.canonicalHash,
|
|
6020
|
-
},
|
|
6021
|
-
});
|
|
6022
|
-
await shardTools.flush();
|
|
6023
|
-
return { index, result: shardResult, tools: shardTools };
|
|
6024
|
-
}
|
|
6025
|
-
catch (error) {
|
|
6026
|
-
const crashMessage = `frontend plan coverage shard ${index + 1} crashed: ${error instanceof Error ? error.message : String(error)}`;
|
|
6027
|
-
return {
|
|
6028
|
-
index,
|
|
6029
|
-
tools: shardTools,
|
|
6030
|
-
result: shardResult
|
|
6031
|
-
? {
|
|
6032
|
-
...shardResult,
|
|
6033
|
-
ok: false,
|
|
6034
|
-
failureCategory: shardResult.ok
|
|
6035
|
-
? "invalid-output"
|
|
6036
|
-
: shardResult.failureCategory,
|
|
6037
|
-
stderr: [shardResult.stderr, crashMessage]
|
|
6038
|
-
.filter(Boolean)
|
|
6039
|
-
.join("\n"),
|
|
6040
|
-
}
|
|
6041
|
-
: {
|
|
6042
|
-
ok: false,
|
|
6043
|
-
assistantText: "",
|
|
6044
|
-
command: [],
|
|
6045
|
-
durationMs: 0,
|
|
6046
|
-
exitCode: null,
|
|
6047
|
-
failureCategory: "tool-policy",
|
|
6048
|
-
modelDisplay: "unknown",
|
|
6049
|
-
parsedEvents: 0,
|
|
6050
|
-
stderr: crashMessage,
|
|
6051
|
-
stdout: "",
|
|
6052
|
-
timedOut: false,
|
|
6053
|
-
attemptedModels: [],
|
|
6054
|
-
fallbackUsed: false,
|
|
6055
|
-
tokensUsed: 0,
|
|
6056
|
-
},
|
|
6057
|
-
};
|
|
6058
|
-
}
|
|
6059
|
-
});
|
|
6060
|
-
const coverageResult = aggregateParallelPiResults(shardResults.map((shard) => shard.result));
|
|
6061
|
-
let mergeFailure;
|
|
6062
|
-
try {
|
|
6063
|
-
// Flatten every shard's facts FIRST, then pre-reduce: shards
|
|
6064
|
-
// partition requirements but a frozen verification target can
|
|
6065
|
-
// span shards, so same-id targets must merge across shards
|
|
6066
|
-
// before adoption, not per shard.
|
|
6067
|
-
await planLedgerTools.adoptCommittedFacts(reduceParallelCoverageShardRecords(shardResults
|
|
6068
|
-
.filter((item) => item.result.ok && item.tools)
|
|
6069
|
-
.flatMap((shard) => normalizeParallelCoverageShardRecords(shard.tools.committedFacts(), shard.index + 1))));
|
|
6070
|
-
}
|
|
6071
|
-
catch (error) {
|
|
6072
|
-
mergeFailure = error;
|
|
6073
|
-
}
|
|
6074
|
-
const failedShard = shardResults.find((shard) => !shard.result.ok);
|
|
6075
|
-
if (mergeFailure) {
|
|
6076
|
-
result = {
|
|
6077
|
-
...coverageResult,
|
|
6078
|
-
ok: false,
|
|
6079
|
-
failureCategory: "invalid-output",
|
|
6080
|
-
stderr: [
|
|
6081
|
-
coverageResult.stderr,
|
|
6082
|
-
`frontend plan coverage shard merge failed: ${mergeFailure instanceof Error ? mergeFailure.message : String(mergeFailure)}`,
|
|
6083
|
-
]
|
|
6084
|
-
.filter(Boolean)
|
|
6085
|
-
.join("\n"),
|
|
6102
|
+
}),
|
|
6103
|
+
});
|
|
6104
|
+
const shardSessionOptions = {
|
|
6105
|
+
...piSessionOptions,
|
|
6106
|
+
sessionEventsPath: path.join(meta.runDir, input.task.id, "parallel", `coverage-${index + 1}`, "session-events.jsonl"),
|
|
6086
6107
|
};
|
|
6108
|
+
shardResult = await runPlanSessions({
|
|
6109
|
+
ledgerTools: shardTools,
|
|
6110
|
+
sessionOptions: shardSessionOptions,
|
|
6111
|
+
requirementIds: slice,
|
|
6112
|
+
basePrompt: `${input.prompt}\n\nPARALLEL COVERAGE SHARD ${index + 1}: use the frozen canonical behavior verification-target ids listed in the record_plan_verification_target tool description — do not prefix ids with a shard namespace or invent variant ids; identical cross-shard targets are deduped, divergent ones fail the merge.`,
|
|
6113
|
+
parallelCoverageOnly: true,
|
|
6114
|
+
observation: {
|
|
6115
|
+
artifactPath: path.join(meta.runDir, input.task.id, "parallel", `coverage-${index + 1}`, "session-budget", `attempt-${input.attempt ?? 1}`),
|
|
6116
|
+
runId: meta.runId,
|
|
6117
|
+
nodeId: `${input.task.id}/parallel/coverage-${index + 1}`,
|
|
6118
|
+
taskId: meta.spec?.sourceBinding?.taskId,
|
|
6119
|
+
attempt: input.attempt ?? 1,
|
|
6120
|
+
model: input.model,
|
|
6121
|
+
sourceDigest: meta.spec?.sourceBinding?.schemaVersion === 2 ? meta.spec.sourceBinding.inputDigest : undefined,
|
|
6122
|
+
contractDigest: meta.spec?.taskContractBinding?.canonicalHash,
|
|
6123
|
+
},
|
|
6124
|
+
});
|
|
6125
|
+
await shardTools.flush();
|
|
6126
|
+
return { index, result: shardResult, tools: shardTools };
|
|
6087
6127
|
}
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6128
|
+
catch (error) {
|
|
6129
|
+
const crashMessage = `frontend plan coverage shard ${index + 1} crashed: ${error instanceof Error ? error.message : String(error)}`;
|
|
6130
|
+
return {
|
|
6131
|
+
index,
|
|
6132
|
+
tools: shardTools,
|
|
6133
|
+
result: shardResult
|
|
6134
|
+
? {
|
|
6135
|
+
...shardResult,
|
|
6136
|
+
ok: false,
|
|
6137
|
+
failureCategory: shardResult.ok
|
|
6138
|
+
? "invalid-output"
|
|
6139
|
+
: shardResult.failureCategory,
|
|
6140
|
+
stderr: [shardResult.stderr, crashMessage]
|
|
6141
|
+
.filter(Boolean)
|
|
6142
|
+
.join("\n"),
|
|
6143
|
+
}
|
|
6144
|
+
: {
|
|
6145
|
+
ok: false,
|
|
6146
|
+
assistantText: "",
|
|
6147
|
+
command: [],
|
|
6148
|
+
durationMs: 0,
|
|
6149
|
+
exitCode: null,
|
|
6150
|
+
failureCategory: "tool-policy",
|
|
6151
|
+
modelDisplay: "unknown",
|
|
6152
|
+
parsedEvents: 0,
|
|
6153
|
+
stderr: crashMessage,
|
|
6154
|
+
stdout: "",
|
|
6155
|
+
timedOut: false,
|
|
6156
|
+
attemptedModels: [],
|
|
6157
|
+
fallbackUsed: false,
|
|
6158
|
+
tokensUsed: 0,
|
|
6159
|
+
},
|
|
6093
6160
|
};
|
|
6094
6161
|
}
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6162
|
+
}, shard => shard.result.failureCategory === "rate-limit");
|
|
6163
|
+
const coverageResult = aggregateParallelPiResults(shardResults.map((shard) => shard.result));
|
|
6164
|
+
let mergeFailure;
|
|
6165
|
+
try {
|
|
6166
|
+
// Flatten every shard's facts FIRST, then pre-reduce: shards
|
|
6167
|
+
// partition requirements but a frozen verification target can
|
|
6168
|
+
// span shards, so same-id targets must merge across shards
|
|
6169
|
+
// before adoption, not per shard.
|
|
6170
|
+
await planLedgerTools.adoptCommittedFacts(reduceParallelCoverageShardRecords(shardResults
|
|
6171
|
+
.filter((item) => item.result.ok && item.tools)
|
|
6172
|
+
.flatMap((shard) => normalizeParallelCoverageShardRecords(shard.tools.committedFacts(), shard.index + 1))));
|
|
6173
|
+
}
|
|
6174
|
+
catch (error) {
|
|
6175
|
+
mergeFailure = error;
|
|
6176
|
+
}
|
|
6177
|
+
const failedShard = shardResults.find((shard) => !shard.result.ok);
|
|
6178
|
+
if (mergeFailure) {
|
|
6179
|
+
result = {
|
|
6180
|
+
...coverageResult,
|
|
6181
|
+
ok: false,
|
|
6182
|
+
failureCategory: "invalid-output",
|
|
6183
|
+
stderr: [
|
|
6184
|
+
coverageResult.stderr,
|
|
6185
|
+
`frontend plan coverage shard merge failed: ${mergeFailure instanceof Error ? mergeFailure.message : String(mergeFailure)}`,
|
|
6186
|
+
]
|
|
6187
|
+
.filter(Boolean)
|
|
6188
|
+
.join("\n"),
|
|
6189
|
+
};
|
|
6190
|
+
}
|
|
6191
|
+
else if (failedShard) {
|
|
6192
|
+
result = {
|
|
6193
|
+
...coverageResult,
|
|
6194
|
+
ok: false,
|
|
6195
|
+
stderr: `${coverageResult.stderr}\nfrontend plan coverage shard ${failedShard.index + 1} failed before reduce`.trim(),
|
|
6196
|
+
};
|
|
6102
6197
|
}
|
|
6103
6198
|
else {
|
|
6104
|
-
|
|
6199
|
+
const reducerResult = await runPlanSessions({
|
|
6105
6200
|
ledgerTools: planLedgerTools,
|
|
6106
6201
|
sessionOptions: piSessionOptions,
|
|
6107
|
-
compactSmallPlan: true,
|
|
6108
6202
|
});
|
|
6203
|
+
result = combineSequentialPiResults(coverageResult, reducerResult);
|
|
6109
6204
|
}
|
|
6110
6205
|
}
|
|
6206
|
+
else {
|
|
6207
|
+
result = await runPlanSessions({
|
|
6208
|
+
ledgerTools: planLedgerTools,
|
|
6209
|
+
sessionOptions: piSessionOptions,
|
|
6210
|
+
compactSmallPlan: true,
|
|
6211
|
+
});
|
|
6212
|
+
}
|
|
6111
6213
|
}
|
|
6112
6214
|
else {
|
|
6113
6215
|
result = await runPlanSessions({
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
export const frontendExecutionPolicySchema = z.object({
|
|
4
4
|
scopeTargetBytes: z.number().int().positive().default(24_000),
|
|
5
5
|
maxScopeUnits: z.number().int().positive().default(4),
|
|
6
|
+
maxCoverageConcurrency: z.number().int().min(1).max(4).default(4),
|
|
6
7
|
outputReserveTokens: z.number().int().positive().default(4096),
|
|
7
8
|
readGrowthReserveTokens: z.number().int().nonnegative().default(2048),
|
|
8
9
|
safetyRatio: z.number().min(0).max(0.5).default(0.15),
|
|
@@ -115,9 +115,30 @@ export function splitFinalAssistantBlocks(blocks, options = {}) {
|
|
|
115
115
|
if (lastProcessIndex === -1) {
|
|
116
116
|
return { answerBlocks: displayable, processBlocks: [] };
|
|
117
117
|
}
|
|
118
|
+
const trailing = displayable.slice(lastProcessIndex + 1);
|
|
119
|
+
if (trailing.length > 0) {
|
|
120
|
+
return {
|
|
121
|
+
answerBlocks: trailing,
|
|
122
|
+
processBlocks: displayable.slice(0, lastProcessIndex + 1),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// The stage ends on a non-text block (a tool call after the prose, or a final
|
|
126
|
+
// thinking block). Returning an empty answer here hid the whole reply inside
|
|
127
|
+
// the collapsed process subtree; keep the last prose block as the visible
|
|
128
|
+
// answer instead (2026-09-08).
|
|
129
|
+
let lastTextIndex = -1;
|
|
130
|
+
for (let i = displayable.length - 1; i >= 0; i--) {
|
|
131
|
+
if (isFinalAnswerBlock(displayable[i])) {
|
|
132
|
+
lastTextIndex = i;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (lastTextIndex === -1) {
|
|
137
|
+
return { answerBlocks: [], processBlocks: displayable };
|
|
138
|
+
}
|
|
118
139
|
return {
|
|
119
|
-
answerBlocks: displayable
|
|
120
|
-
processBlocks: displayable.
|
|
140
|
+
answerBlocks: [displayable[lastTextIndex]],
|
|
141
|
+
processBlocks: displayable.filter((_block, index) => index !== lastTextIndex),
|
|
121
142
|
};
|
|
122
143
|
}
|
|
123
144
|
export function joinAssistantText(blocks) {
|