@tea-agent/loop-agent 0.40.0-next.10 → 0.40.0-next.12
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 +18 -0
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/commands/client-recovery.js +8 -36
- package/dist/executors/pi-sdk-executor.js +33 -5
- package/dist/executors/shell-executor.js +64 -17
- package/dist/governance/checks.js +1 -0
- package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
- package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
- package/dist/shared/pi-context-pressure/env.js +58 -0
- package/dist/shared/pi-context-pressure/extension.js +100 -0
- package/dist/shared/pi-context-pressure/index.js +7 -0
- package/dist/shared/pi-context-pressure/overflow.js +252 -0
- package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
- package/dist/shared/pi-context-pressure/telemetry.js +51 -0
- package/dist/worker/console/chat/pi-runtime.js +146 -5
- package/dist/worker/console/chat/provider-error.js +2 -1
- package/dist/worker/console/chat/routes.js +3 -0
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +20 -2
- package/dist/worker/console/operator-actions.js +4 -3
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-C_hkwt2J.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-s2Q6r96B.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-CtgsrwU1.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-DZ-A7EiJ.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-D2qv0Nyo.js} +1 -1
- package/dist/worker/console/static/assets/channel-BluaMT3-.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-D-UYpSaW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-DAS3jrrt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-CSbuRcGB.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-BDH1ZO40.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-Dc2cqQIb.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-CkZuemIW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-URwjQnte.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-04RHylJK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-CvQX56Hh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-dUF7qura.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-H-ZpSafr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-H-ZpSafr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-COwbfxJE.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-CaBEJekQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-COWUBdet.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-BLxVqTn4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-WL0yKfEQ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-DaVkKCle.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-DdTgnRGf.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-PG2Axo6A.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-6vuQzlzI.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-BpXeBz-g.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-PxtUnaa1.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-B--Qgvs1.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-BIfQCgtx.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-BRaX_E4j.js} +1 -1
- package/dist/worker/console/static/assets/index-Crrgl8Mx.css +1 -0
- package/dist/worker/console/static/assets/{index-DqJbO3-p.js → index-vPsp-idE.js} +102 -72
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-Cd93r4Ab.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-C4c80iHt.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-Ch5ajf3s.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-DbpyW1Ut.js} +1 -1
- package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-BHVjBnTW.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-DO70kJ34.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-_P44Zpvs.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-Bgi9FmEd.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-C9InDfJn.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-DtKrUouv.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-zgAD_6Yo.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-CQNO6kj4.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-knKu111P.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-D6tshiQ6.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-BMIWJcir.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-BP9wRDqc.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-ClEhytQG.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-DEkM5Dhe.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPg5IMuu.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-ByBKsL35.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-waFbNCUy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-1ljiY0eZ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-B4ZLHr_L.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/observe/static/styles.css +9 -0
- package/dist/worker/observe/static/views/dag-inspector.js +40 -0
- package/dist/worker/observe/static/views/session-timeline.js +135 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +157 -7
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +4 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
- package/dist/workflows/dag/init-hybrid.js +2 -2
- package/docs/architecture/runtime-boundaries.md +2 -1
- package/docs/templates/backend-test-dag.json +4 -3
- package/package.json +3 -2
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
- 修复 Console 轨迹视图引用未声明 CSS 自定义属性导致仓库发布门禁失败的问题,补齐品牌色、警告色与字体别名并保持现有视觉回退值。
|
|
6
|
+
|
|
5
7
|
### 重点更新
|
|
6
8
|
|
|
9
|
+
- Console 对话页「轨迹」视图响应式优化:轨迹列宽跟随面板实际宽度收缩(`.oc-trajectory` 加 `width:100%`、`.oc-trajectory-turn/.step` 加 `min-width:0` 修 flex 收缩链),窄窗口(≤720px)自动隐藏耗时列,避免工具行被固定项挤爆。
|
|
10
|
+
- Console 对话页新增「轨迹」视图(详情侧栏 tab):仿 DSH ui-trajectory,用本仓库 turnGroups 数据把对话按「用户 → 过程 → AI 回复」渲染成统一单元格列表,每行带 kind 标签 + 图标(用户/ AI / 思考 / 工具 / 活动 / 错误 / 压缩);用户与 AI 用标签颜色 + 图标区分,AI 中间文本也标注;思考、工具、以及超长用户/AI 消息默认单行截断、可展开/收起看全文(含工具参数与结果、状态与耗时)。支持一键「导出 HTML」自包含文件(长内容用 `<details>` 折叠,短内容内联)。主会话思考块标签由英文 Think 改为「思考」。
|
|
11
|
+
- DAG 检查器节点「执行过程」的「导出 HTML」改为复用页面真实渲染与样式,导出的独立 HTML 排版与页面一致;Pi 会话节点导出会带上原始协议事件,左上角提供「展开/隐藏原始协议事件」按钮(默认隐藏,每项仍可单独展开),shell/静态节点导出同样与页面一致。
|
|
7
12
|
- 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。
|
|
8
13
|
- Operator Chat 现在可以在同一个页面管理多个工作区;每个会话始终使用创建它的工作区,切换对话时地址栏保持简洁稳定。
|
|
9
14
|
- 对话消息流整体调整为更紧凑、清晰的布局,思考过程、工具执行、阶段性结果和最终回答会按真实发生顺序展示。
|
|
10
15
|
- 上下文过大时会自动压缩并继续原请求,消息流会依次展示触发原因、压缩进度和压缩结果;手动压缩也使用同一套展示方式。
|
|
16
|
+
- Operator Chat 与 DAG Pi 节点默认开启上下文压力处理:发送前接近窗口时压缩一次、识别网关「请求上下文过大」后压缩续跑,并对 OpenAI 格式的大工具输出做线路压缩;可用环境变量关闭,不必改会话记录。
|
|
11
17
|
- 对话进行中可以连续提交多条消息,并支持编辑、删除或立即插入当前回复;刷新页面或重启 Console 后,未发送内容仍会保留。
|
|
12
18
|
|
|
13
19
|
### 新增
|
|
@@ -18,6 +24,9 @@
|
|
|
18
24
|
|
|
19
25
|
### 改进
|
|
20
26
|
|
|
27
|
+
- Operator Chat 发送前会在接近模型窗口时主动压缩,压缩失败仍发送当前输入,不会把消息丢掉。DAG 节点同步启用 overflow 识别与线路压缩,但不在 streaming 中主动 compact。
|
|
28
|
+
- 网关「请求上下文过大」及常见包装/嵌套 JSON 由共享分类器识别;目标项目 `loop-agent init` 生成的 Pi overflow extension 与控制器使用同一套短语表。
|
|
29
|
+
- `@agent-context/sift@0.0.1-alpha.6` 作为可选依赖接入 Console 与 DAG:默认开启,`HARNESS_CONSOLE_SIFT` / `HARNESS_DAG_SIFT` 设为 `0`、`false` 或 `off` 即可关闭。Anthropic 缺省 prompt cache 形态保持 no-op,避免破坏缓存。
|
|
21
30
|
- 后端测试的计划与生成阶段只读取任务明确绑定的需求、环境报告和本轮产物,减少无关仓库内容干扰结果,并让后续步骤继续使用同一份已确认计划。
|
|
22
31
|
- Operator Chat 的任务清单工具描述补齐多步骤与 DAG 使用纪律:开始前按具体步骤规划、保持进行中约束、完成后立即更新完整列表,并用面向用户的阶段呈现 DAG 进度,简单任务自动跳过。
|
|
23
32
|
- 正式发布会自动同步更新版本日志和文档站摘要;发布说明优先生成简洁自然的中文,生成服务不可用时也能安全完成发布。
|
|
@@ -28,6 +37,15 @@
|
|
|
28
37
|
|
|
29
38
|
### 修复
|
|
30
39
|
|
|
40
|
+
- 修复 backend-test 的 Scenario Partition 计划表只提示 `each-value`/`omitted`、却由下游确定性门禁强制要求 `not-in-set` 的合同漂移;计划、Markdown Case 与 pytest 现在统一明确唯一补集槽位,避免 PI 因表格指令遗漏 `TP-<Partition ID>-NOT-IN-SET` 后触发 `MISSING_COMPLEMENT_SLOT`。
|
|
41
|
+
- 修复文档审计扫描本地 `.scratch/` 临时证据与打包目录、导致仓库 preflight 和发布前验证被非治理文件阻断的问题。
|
|
42
|
+
- 修复 backend-test 把 Markdown 明确声明的 enum-invalid、wrong-type、null、empty 和精确非法 custom literal 当作 payload enum 契约漂移、错误消耗唯一一次 pytest repair 的问题;豁免现在按 pytest parameter ID、目标字段和值逐 TP 绑定,positive/nominal drift 与 path 缺陷仍严格阻断。
|
|
43
|
+
- 修复 backend-test 无法从同一生成模块的安全 payload helper 中绑定 field-target positional/keyword/default 参数,以及无法识别精确 `_without(payload, field)` 删除语义的问题;computed、动态、二层、跨模块和危险 helper 仍保持 `UNDETERMINED`。
|
|
44
|
+
- 修复 backend-test 无法静态证明同一生成模块内安全 payload helper、导致 request-level nominal 场景长期停留在 `UNDETERMINED` 的问题;超长边界 observed 现在持久化为有界摘要并保留真实长度,空白修复建议也明确使用真实空格而非字面 escape 文本。
|
|
45
|
+
- 修复 backend-test 把 Markdown 中 `\\u0020`/`\\x20` 空白表示与 pytest 真实空白字符串误判为 scenario-param 不一致的问题;readiness 的逐项排除现在携带有界的 TP、字段、expected/observed raw/normalized 和源码位置诊断,同时不对普通业务字符做 trim、大小写折叠或宽松解码。
|
|
46
|
+
- 修复 Console 把 backend-test writer 的局部保护文件误判为整个 writeSet 越界的问题;任务级禁止路径仍保持硬阻断,合法 DAG 可继续取得 autonomous execution receipt。
|
|
47
|
+
- 修复 backend-test 对 `resource_client.call(..., payload=...)` 一类本地 transport 的 payload AST 假阴性;readiness 新增固定分类/Case 排除汇总和 pytest 启动事实,payload repair finding 同时携带 expected/observed/missing path 清单。
|
|
48
|
+
- 修复后端测试在 payload、fixture 与 Markdown→pytest 对应关系同时异常时只修复其中一类问题的问题;pytest 侧缺陷现在通过结构化 `repairFindings` 合并进入唯一一次有界 repair,Markdown Test Point 未分类或重复分类则在生成阶段定向重试并禁止下游猜修。
|
|
31
49
|
- 修复重命名会话时输入框没有回显当前标题的问题。
|
|
32
50
|
- 修复停止回复后输入区恢复迟缓、排队消息偶发丢失,以及切换会话后旧状态残留的问题。
|
|
33
51
|
- 修复宽表格、文件预览、滚动条和窄屏布局中的多处显示异常。
|
|
@@ -109,8 +109,12 @@ function findForbiddenOverlapsForPacket(task) {
|
|
|
109
109
|
const overlaps = [];
|
|
110
110
|
for (const writeSetEntry of task.writeSet ?? []) {
|
|
111
111
|
for (const forbiddenPath of task.forbiddenPaths ?? []) {
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
// A narrower forbidden child may intentionally carve a protected
|
|
113
|
+
// file out of a broader node writeSet. The runtime write guard gives
|
|
114
|
+
// forbiddenPaths precedence; report a packet conflict only when the
|
|
115
|
+
// writeSet entry itself is inside the forbidden boundary. Console G2
|
|
116
|
+
// separately rechecks task-global forbidden paths with live scope.
|
|
117
|
+
if (pathMatchesPattern(writeSetEntry, forbiddenPath)) {
|
|
114
118
|
overlaps.push({ writeSetEntry, forbiddenPath });
|
|
115
119
|
}
|
|
116
120
|
}
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.40.0-next.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
3
|
+
"version": "0.40.0-next.12",
|
|
4
|
+
"gitSha": "41c2688fb25e22404d7dd6f9a7c884a2e638d500",
|
|
5
|
+
"builtAt": "2026-08-29T08:53:25.216Z"
|
|
6
6
|
}
|
|
@@ -19,36 +19,11 @@ export const MAX_SESSION_RETRIES = 5;
|
|
|
19
19
|
/** Hard cap for OpenCode overflow compact+resume recoveries (≤ 2). */
|
|
20
20
|
export const MAX_OVERFLOW_RECOVERIES = 1;
|
|
21
21
|
import { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY, } from "../shared/pi-retry-settings.js";
|
|
22
|
+
import { CONTEXT_OVERFLOW_PHRASES, buildGeneratedOverflowClassifierSource, isContextOverflow as classifySharedOverflow, } from "../shared/pi-context-pressure/overflow.js";
|
|
22
23
|
export { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY };
|
|
23
|
-
|
|
24
|
-
* Shared overflow phrase sources used by isContextOverflow and generated plugins.
|
|
25
|
-
* Keep English + Chinese intranet gateway messages in one place to avoid drift.
|
|
26
|
-
*/
|
|
27
|
-
export const CONTEXT_OVERFLOW_PHRASES = [
|
|
28
|
-
"请求上下文过大",
|
|
29
|
-
"context_length_exceeded",
|
|
30
|
-
"context overflow",
|
|
31
|
-
"context length overflow",
|
|
32
|
-
"too many tokens",
|
|
33
|
-
"maximum context",
|
|
34
|
-
"prompt is too long",
|
|
35
|
-
"request_too_large",
|
|
36
|
-
"context window",
|
|
37
|
-
];
|
|
38
|
-
function escapeRegExpLiteral(value) {
|
|
39
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40
|
-
}
|
|
41
|
-
const CONTEXT_OVERFLOW_PATTERN = new RegExp(CONTEXT_OVERFLOW_PHRASES.map(escapeRegExpLiteral).join("|"), "i");
|
|
24
|
+
export { CONTEXT_OVERFLOW_PHRASES };
|
|
42
25
|
function buildGeneratedContextOverflowPatternSource() {
|
|
43
|
-
return
|
|
44
|
-
...CONTEXT_OVERFLOW_PHRASES,
|
|
45
|
-
])};
|
|
46
|
-
const OVERFLOW_PATTERN = new RegExp(
|
|
47
|
-
CONTEXT_OVERFLOW_PHRASES.map((phrase) =>
|
|
48
|
-
phrase.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&"),
|
|
49
|
-
).join("|"),
|
|
50
|
-
"i",
|
|
51
|
-
);`;
|
|
26
|
+
return buildGeneratedOverflowClassifierSource();
|
|
52
27
|
}
|
|
53
28
|
const PERMANENT_MESSAGE_PATTERNS = [
|
|
54
29
|
{
|
|
@@ -65,7 +40,7 @@ const PERMANENT_MESSAGE_PATTERNS = [
|
|
|
65
40
|
},
|
|
66
41
|
{
|
|
67
42
|
reason: "context-overflow",
|
|
68
|
-
pattern:
|
|
43
|
+
pattern: /context_length_exceeded|请求上下文过大/i,
|
|
69
44
|
},
|
|
70
45
|
{
|
|
71
46
|
reason: "cancelled",
|
|
@@ -180,6 +155,8 @@ function permanentReason(error) {
|
|
|
180
155
|
return "api-error";
|
|
181
156
|
}
|
|
182
157
|
const text = collectErrorText(error);
|
|
158
|
+
if (isContextOverflow(error))
|
|
159
|
+
return "context-overflow";
|
|
183
160
|
for (const entry of PERMANENT_MESSAGE_PATTERNS) {
|
|
184
161
|
if (entry.pattern.test(text))
|
|
185
162
|
return entry.reason;
|
|
@@ -192,7 +169,7 @@ function permanentReason(error) {
|
|
|
192
169
|
* business validation, or standard 502/LLMRequestError as overflow.
|
|
193
170
|
*/
|
|
194
171
|
export function isContextOverflow(error) {
|
|
195
|
-
return
|
|
172
|
+
return classifySharedOverflow(error);
|
|
196
173
|
}
|
|
197
174
|
function looksTransient(error) {
|
|
198
175
|
const text = collectErrorText(error);
|
|
@@ -887,7 +864,7 @@ function textOf(error) {
|
|
|
887
864
|
}
|
|
888
865
|
|
|
889
866
|
function isContextOverflow(error) {
|
|
890
|
-
return
|
|
867
|
+
return isOverflowMessage(textOf(error));
|
|
891
868
|
}
|
|
892
869
|
|
|
893
870
|
function buildOverflowResumePrompt(sessionId, error, attempt) {
|
|
@@ -1163,11 +1140,6 @@ const EXTENSION_PATH = ${JSON.stringify(PI_CONTEXT_OVERFLOW_EXTENSION_PATH)};
|
|
|
1163
1140
|
${buildGeneratedContextOverflowPatternSource()}
|
|
1164
1141
|
const NORMALIZED_PREFIX = "context_length_exceeded:";
|
|
1165
1142
|
|
|
1166
|
-
function isOverflowMessage(text) {
|
|
1167
|
-
if (typeof text !== "string" || !text) return false;
|
|
1168
|
-
return OVERFLOW_PATTERN.test(text);
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
1143
|
export default function loopAgentContextOverflowExtension(pi) {
|
|
1172
1144
|
void EXTENSION_PATH;
|
|
1173
1145
|
pi.on("message_end", (event) => {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
3
5
|
import { computeExtensionToolAllowlistExtras } from "./pi-extension-resolver.js";
|
|
4
6
|
import { BoundedTextPreview, classifyPiFailure, createPiJsonlStreamCollector, DEFAULT_ABORT_GRACE_MS, DEFAULT_STALL_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, extractPiEventStopReason, extractAssistantTextFromPiJson, } from "./pi-executor.js";
|
|
5
7
|
import { serializeSessionEvent } from "./pi-event-serializer.js";
|
|
6
8
|
import { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY, } from "../shared/pi-retry-settings.js";
|
|
9
|
+
import { createCompactionController, createContextPressureExtension, createSiftBridgeState, inMemoryCompactionSettings, isDagSiftEnabled, parseCompactionPolicy, parseSiftBridgeConfig, SIFT_RETRIEVE_TOOL_NAME, } from "../shared/pi-context-pressure/index.js";
|
|
7
10
|
/** Same-session recovery after a transport stall. Exactly one continue per SDK attempt. */
|
|
8
11
|
export const PI_SDK_STALL_CONTINUE_MAX = 1;
|
|
9
12
|
/**
|
|
@@ -215,15 +218,37 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
215
218
|
// Inject rate-limit-tolerant auto-retry settings for the session lifecycle.
|
|
216
219
|
// Prefer SettingsManager.inMemory so harness runs do not depend on ~/.pi or
|
|
217
220
|
// project .pi/settings.json being present/trusted.
|
|
221
|
+
const policy = parseCompactionPolicy();
|
|
222
|
+
const compaction = {
|
|
223
|
+
...PI_RECOMMENDED_COMPACTION,
|
|
224
|
+
...inMemoryCompactionSettings(policy),
|
|
225
|
+
};
|
|
218
226
|
const settingsManager = typeof SettingsManager?.inMemory === "function"
|
|
219
227
|
? SettingsManager.inMemory({
|
|
220
228
|
retry: {
|
|
221
229
|
...PI_RECOMMENDED_RETRY,
|
|
222
230
|
provider: { ...PI_RECOMMENDED_RETRY.provider },
|
|
223
231
|
},
|
|
224
|
-
compaction
|
|
232
|
+
compaction,
|
|
225
233
|
}, { projectTrusted: true })
|
|
226
234
|
: undefined;
|
|
235
|
+
const nodeSessionId = `dag-${randomUUID()}`;
|
|
236
|
+
const controller = createCompactionController();
|
|
237
|
+
const siftEnabled = isDagSiftEnabled();
|
|
238
|
+
const sift = siftEnabled.enabled
|
|
239
|
+
? createSiftBridgeState(parseSiftBridgeConfig({
|
|
240
|
+
sessionId: nodeSessionId,
|
|
241
|
+
defaultStashRoot: path.join(os.tmpdir(), "loop-agent-dag-sift"),
|
|
242
|
+
}))
|
|
243
|
+
: undefined;
|
|
244
|
+
if (sift)
|
|
245
|
+
sift.counters = controller.counters;
|
|
246
|
+
const pressureExtension = createContextPressureExtension({
|
|
247
|
+
name: "dag-pi-context-pressure",
|
|
248
|
+
policy,
|
|
249
|
+
controller,
|
|
250
|
+
sift,
|
|
251
|
+
});
|
|
227
252
|
const loader = new DefaultResourceLoader({
|
|
228
253
|
cwd: input.cwd,
|
|
229
254
|
agentDir,
|
|
@@ -233,6 +258,7 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
233
258
|
...(input.additionalExtensionPaths && input.additionalExtensionPaths.length > 0
|
|
234
259
|
? { additionalExtensionPaths: input.additionalExtensionPaths }
|
|
235
260
|
: {}),
|
|
261
|
+
...(pressureExtension ? { extensionFactories: [pressureExtension] } : {}),
|
|
236
262
|
...(settingsManager ? { settingsManager } : {}),
|
|
237
263
|
appendSystemPromptOverride: (base) => [
|
|
238
264
|
...base,
|
|
@@ -247,10 +273,12 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
247
273
|
// extras into the create-time allowlist; protected builtins, the deny
|
|
248
274
|
// surface, and write-capable extension tools never join.
|
|
249
275
|
const extensionToolExtras = [];
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
extensionToolExtras.
|
|
276
|
+
const registered = collectLoaderExtensionToolNames(loader);
|
|
277
|
+
extensionToolExtras.push(...computeExtensionToolAllowlistExtras(registered.map((name) => ({ name })), input.toolNames));
|
|
278
|
+
if (siftEnabled.enabled &&
|
|
279
|
+
!extensionToolExtras.includes(SIFT_RETRIEVE_TOOL_NAME) &&
|
|
280
|
+
!input.toolNames.includes(SIFT_RETRIEVE_TOOL_NAME)) {
|
|
281
|
+
extensionToolExtras.push(...computeExtensionToolAllowlistExtras([{ name: SIFT_RETRIEVE_TOOL_NAME }], input.toolNames));
|
|
254
282
|
}
|
|
255
283
|
const sessionToolNames = [...input.toolNames, ...extensionToolExtras];
|
|
256
284
|
if (input.requireWriterCustomTools) {
|
|
@@ -32,11 +32,11 @@ import { materializeBackendTestExecutionContract } from "../workflows/dag/backen
|
|
|
32
32
|
import { resolveBackendTestLayout } from "../workflows/dag/backend-test-layout.js";
|
|
33
33
|
import { frontendTestLayoutFromSpec, } from "../workflows/dag/frontend-test-layout.js";
|
|
34
34
|
import { backendTestGapDocSchema, ingestBackendTestGap, } from "../workflows/dag/backend-test-gap-fill.js";
|
|
35
|
-
import { analyzeBackendTestCaseCoverage, analyzeBackendTestMarkdownPytestCorrespondence, materializeBackendTestCaseManifestFromFacts, } from "../workflows/dag/backend-test-case-coverage-analysis.js";
|
|
35
|
+
import { analyzeBackendTestCaseCoverage, analyzeBackendTestMarkdownPytestCorrespondence, classifyBackendTestCorrespondenceRepair, materializeBackendTestCaseManifestFromFacts, } from "../workflows/dag/backend-test-case-coverage-analysis.js";
|
|
36
36
|
import { materializeBackendTestResultFromPytestHtml, materializeBackendTestResultFromRunDir, parsePytestHtmlReport, } from "../workflows/dag/backend-test-result-contract.js";
|
|
37
37
|
import { collectBackendTestHumanCaseCatalog, collectBackendTestMappedPytestScripts, resolveBackendTestMappedPytestScripts, collectJacocoCoverage, hasBlockingBackendMarkdownSafetyFindings, hasBlockingBackendMarkdownModuleStemFindings, inspectBackendTestEnvironment, markdownFiles, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, renderBackendTestL5Dashboard, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
|
|
38
38
|
import { applyDeterministicScenarioParamRepairs, assessBackendScenarioParamConsistency, classifyBackendTestFailureWithScenarioParam, readBackendScenarioParamFacts, renderBackendTestFailureAnalysis, writeBackendScenarioParamArtifacts, writeScenarioParamRepairAudit, } from "../workflows/dag/backend-test-scenario-param.js";
|
|
39
|
-
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, buildBackendTestItemEligibility, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
|
|
39
|
+
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, buildBackendTestItemEligibility, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, summarizeBackendTestExclusions, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
|
|
40
40
|
import { computeL5ReportMetrics } from "../workflows/dag/l5-report-metrics.js";
|
|
41
41
|
import { buildBackendTestCanonicalResultFromInitialShellSnippet, materializeBackendTestClassification, } from "../workflows/dag/backend-test-classification-contract.js";
|
|
42
42
|
import { backendTestSemanticReviewSchema, materializeBackendTestSemanticReview, } from "../workflows/dag/backend-test-semantic-review-contract.js";
|
|
@@ -870,26 +870,49 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
870
870
|
const correspondenceFactsPath = path.join(contractsDir, "backend-test-markdown-pytest-correspondence-initial.json");
|
|
871
871
|
await writeFile(correspondenceFactsPath, JSON.stringify(correspondence.facts, null, 2), "utf8");
|
|
872
872
|
outputs.push(`initialCorrespondence=${correspondenceReportPath}`, `initialCorrespondenceFacts=${correspondenceFactsPath}`);
|
|
873
|
-
if (correspondence.facts.status === "FAIL"
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
.
|
|
877
|
-
.
|
|
878
|
-
|
|
879
|
-
facts.status = "REPAIRABLE";
|
|
880
|
-
facts.repairEligible = true;
|
|
881
|
-
facts.repairPaths = Array.from(new Set([...facts.repairPaths, ...correspondenceRepairPaths])).sort();
|
|
873
|
+
if (correspondence.facts.status === "FAIL") {
|
|
874
|
+
const repair = classifyBackendTestCorrespondenceRepair(correspondence.facts, backendLayout);
|
|
875
|
+
if (repair.markdownBlockingEntries.length > 0) {
|
|
876
|
+
facts.status = "BLOCKED";
|
|
877
|
+
facts.repairEligible = false;
|
|
878
|
+
facts.repairPaths = [];
|
|
882
879
|
facts.findings.push({
|
|
883
|
-
kind: "markdown-
|
|
880
|
+
kind: "markdown-correspondence-contract",
|
|
884
881
|
classification: "test-asset-defect",
|
|
885
|
-
repairability: "
|
|
886
|
-
detail:
|
|
887
|
-
.
|
|
888
|
-
.flatMap((entry) => entry.findings)
|
|
882
|
+
repairability: "blocked",
|
|
883
|
+
detail: repair.markdownBlockingEntries
|
|
884
|
+
.map((entry) => `${entry.caseId ?? "unknown Case"}: ${entry.reasonCodes.join(", ")}`)
|
|
889
885
|
.join("; ")
|
|
890
|
-
.slice(0, 12_000)
|
|
886
|
+
.slice(0, 12_000),
|
|
891
887
|
});
|
|
892
888
|
}
|
|
889
|
+
else if (repair.pytestRepairPaths.length > 0 &&
|
|
890
|
+
(facts.status === "PASS" || facts.status === "REPAIRABLE")) {
|
|
891
|
+
facts.status = "REPAIRABLE";
|
|
892
|
+
facts.repairEligible = true;
|
|
893
|
+
facts.repairPaths = Array.from(new Set([...facts.repairPaths, ...repair.pytestRepairPaths])).sort();
|
|
894
|
+
const repairEntries = correspondence.facts.entries.filter((entry) => repair.pytestRepairEntries.some((candidate) => candidate.caseId === entry.caseId));
|
|
895
|
+
for (const entry of repairEntries.slice(0, 40)) {
|
|
896
|
+
const classified = repair.pytestRepairEntries.find((candidate) => candidate.caseId === entry.caseId);
|
|
897
|
+
facts.findings.push({
|
|
898
|
+
kind: "markdown-pytest-correspondence",
|
|
899
|
+
classification: "test-asset-defect",
|
|
900
|
+
repairability: "repairable",
|
|
901
|
+
detail: entry.findings.join("; ").slice(0, 2_000) || "Markdown-to-pytest correspondence is incomplete",
|
|
902
|
+
...(entry.caseId ? { caseId: entry.caseId } : {}),
|
|
903
|
+
reasonCodes: classified?.reasonCodes ?? [],
|
|
904
|
+
repairPaths: classified?.paths ?? [],
|
|
905
|
+
payloadExpectedPaths: Array.from(new Set([
|
|
906
|
+
...entry.payloadAssessment.requiredPaths,
|
|
907
|
+
...entry.payloadAssessment.allowedPaths,
|
|
908
|
+
])).sort(),
|
|
909
|
+
payloadObservedPaths: entry.payloadAssessment.observedPaths,
|
|
910
|
+
payloadMissingPaths: entry.payloadAssessment.missingPaths,
|
|
911
|
+
payloadUnexpectedPaths: entry.payloadAssessment.unexpectedPaths,
|
|
912
|
+
payloadEnumMismatches: entry.payloadAssessment.enumMismatches,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
893
916
|
}
|
|
894
917
|
}
|
|
895
918
|
catch (error) {
|
|
@@ -912,6 +935,19 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
912
935
|
collectedItemCount: facts.collectedItemCount,
|
|
913
936
|
missingMappedScripts: facts.missingMappedScripts,
|
|
914
937
|
repairPaths: facts.repairPaths,
|
|
938
|
+
repairFindings: facts.findings.map((finding) => ({
|
|
939
|
+
kind: finding.kind,
|
|
940
|
+
repairability: finding.repairability,
|
|
941
|
+
detail: finding.detail,
|
|
942
|
+
...(finding.caseId ? { caseId: finding.caseId } : {}),
|
|
943
|
+
...(finding.reasonCodes ? { reasonCodes: finding.reasonCodes } : {}),
|
|
944
|
+
...(finding.repairPaths ? { repairPaths: finding.repairPaths } : {}),
|
|
945
|
+
...(finding.payloadExpectedPaths ? { payloadExpectedPaths: finding.payloadExpectedPaths } : {}),
|
|
946
|
+
...(finding.payloadObservedPaths ? { payloadObservedPaths: finding.payloadObservedPaths } : {}),
|
|
947
|
+
...(finding.payloadMissingPaths ? { payloadMissingPaths: finding.payloadMissingPaths } : {}),
|
|
948
|
+
...(finding.payloadUnexpectedPaths ? { payloadUnexpectedPaths: finding.payloadUnexpectedPaths } : {}),
|
|
949
|
+
...(finding.payloadEnumMismatches ? { payloadEnumMismatches: finding.payloadEnumMismatches } : {}),
|
|
950
|
+
})),
|
|
915
951
|
factsPath: "contracts/backend-test-pytest-collection-initial.json",
|
|
916
952
|
}),
|
|
917
953
|
stderr: "",
|
|
@@ -1011,6 +1047,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1011
1047
|
})
|
|
1012
1048
|
: { eligibleItemIds: effective.collectedItemIds, excludedItems: [] };
|
|
1013
1049
|
const eligibilityInputHashes = Object.fromEntries((correspondence?.facts.inputFiles ?? []).map((item) => [item.path, item.sha256]));
|
|
1050
|
+
const exclusionSummary = summarizeBackendTestExclusions(eligibility.excludedItems);
|
|
1014
1051
|
const eligibilityFactsPath = path.join(meta.runDir, "contracts", "backend-test-execution-eligibility.json");
|
|
1015
1052
|
await mkdir(path.dirname(eligibilityFactsPath), { recursive: true });
|
|
1016
1053
|
await writeFile(eligibilityFactsPath, `${JSON.stringify({
|
|
@@ -1021,6 +1058,9 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1021
1058
|
excludedItemCount: eligibility.excludedItems.length,
|
|
1022
1059
|
eligibleItemIds: eligibility.eligibleItemIds,
|
|
1023
1060
|
excludedItems: eligibility.excludedItems,
|
|
1061
|
+
exclusionSummary,
|
|
1062
|
+
pytestSpawned: false,
|
|
1063
|
+
businessTestBodyExecutedCount: 0,
|
|
1024
1064
|
inputHashes: eligibilityInputHashes,
|
|
1025
1065
|
}, null, 2)}\n`, "utf8");
|
|
1026
1066
|
const eligibilityReportPath = await writeRunReport(meta.runDir, "backend-test-execution-eligibility.md", [
|
|
@@ -1033,6 +1073,10 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1033
1073
|
`- Collected items: ${effective.collectedItemIds.length}`,
|
|
1034
1074
|
`- Eligible items: ${eligibility.eligibleItemIds.length}`,
|
|
1035
1075
|
`- Excluded items: ${eligibility.excludedItems.length}`,
|
|
1076
|
+
`- Excluded Cases: ${exclusionSummary.excludedCaseCount}`,
|
|
1077
|
+
`- Exclusion categories: ${Object.entries(exclusionSummary.excludedByReason).map(([category, count]) => `${category}=${count}`).join("; ") || "none"}`,
|
|
1078
|
+
"- pytest spawned: false",
|
|
1079
|
+
"- Business test bodies executed: 0",
|
|
1036
1080
|
"",
|
|
1037
1081
|
"## Excluded Items",
|
|
1038
1082
|
"",
|
|
@@ -1061,6 +1105,9 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1061
1105
|
executionReadinessStatus: readiness.status,
|
|
1062
1106
|
eligibleItemCount: readiness.eligibleItemIds.length,
|
|
1063
1107
|
excludedItemCount: readiness.excludedItems.length,
|
|
1108
|
+
exclusionSummary: readiness.exclusionSummary,
|
|
1109
|
+
pytestSpawned: readiness.pytestSpawned,
|
|
1110
|
+
businessTestBodyExecutedCount: readiness.businessTestBodyExecutedCount,
|
|
1064
1111
|
factsPath: "contracts/backend-test-pytest-collection-effective.json",
|
|
1065
1112
|
readinessPath: "contracts/backend-test-execution-readiness.json",
|
|
1066
1113
|
reportPath: artifacts.reportPath,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const CHECKPOINT_MAX_BYTES = 12 * 1024;
|
|
2
|
+
function boundText(value, maxChars) {
|
|
3
|
+
if (typeof value !== "string" || !value.trim())
|
|
4
|
+
return "";
|
|
5
|
+
return value.length <= maxChars ? value : `${value.slice(0, maxChars - 1)}…`;
|
|
6
|
+
}
|
|
7
|
+
function asRecord(value) {
|
|
8
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
9
|
+
? value
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
function extractLastUserGoal(messages) {
|
|
13
|
+
if (!Array.isArray(messages))
|
|
14
|
+
return "";
|
|
15
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
16
|
+
const message = asRecord(messages[index]);
|
|
17
|
+
if (!message || message.role !== "user")
|
|
18
|
+
continue;
|
|
19
|
+
const content = message.content;
|
|
20
|
+
if (typeof content === "string")
|
|
21
|
+
return boundText(content, 800);
|
|
22
|
+
if (Array.isArray(content)) {
|
|
23
|
+
const texts = content
|
|
24
|
+
.map((block) => {
|
|
25
|
+
const rec = asRecord(block);
|
|
26
|
+
return typeof rec?.text === "string" ? rec.text : "";
|
|
27
|
+
})
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
if (texts.length > 0)
|
|
30
|
+
return boundText(texts.join("\n"), 800);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
function extractTodos(details) {
|
|
36
|
+
const record = asRecord(details);
|
|
37
|
+
const todos = record?.todos ?? record?.items;
|
|
38
|
+
if (!Array.isArray(todos))
|
|
39
|
+
return "";
|
|
40
|
+
const lines = todos
|
|
41
|
+
.slice(0, 24)
|
|
42
|
+
.map((item) => {
|
|
43
|
+
const rec = asRecord(item);
|
|
44
|
+
const status = typeof rec?.status === "string" ? rec.status : "pending";
|
|
45
|
+
const content = typeof rec?.content === "string"
|
|
46
|
+
? rec.content
|
|
47
|
+
: typeof rec?.title === "string"
|
|
48
|
+
? rec.title
|
|
49
|
+
: "";
|
|
50
|
+
return content ? `- [${status}] ${content}` : "";
|
|
51
|
+
})
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
return boundText(lines.join("\n"), 1200);
|
|
54
|
+
}
|
|
55
|
+
function extractFileOps(fileOps) {
|
|
56
|
+
const record = asRecord(fileOps);
|
|
57
|
+
if (!record)
|
|
58
|
+
return "";
|
|
59
|
+
const parts = [];
|
|
60
|
+
for (const key of ["edited", "written", "read", "deleted", "ops"]) {
|
|
61
|
+
const value = record[key];
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
parts.push(`${key}: ${value
|
|
64
|
+
.slice(0, 20)
|
|
65
|
+
.map((item) => (typeof item === "string" ? item : JSON.stringify(item)))
|
|
66
|
+
.join(", ")}`);
|
|
67
|
+
}
|
|
68
|
+
else if (typeof value === "string" && value) {
|
|
69
|
+
parts.push(`${key}: ${value}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (parts.length === 0) {
|
|
73
|
+
try {
|
|
74
|
+
return boundText(JSON.stringify(fileOps), 800);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return boundText(parts.join("\n"), 800);
|
|
81
|
+
}
|
|
82
|
+
export function buildOverflowCheckpoint(input) {
|
|
83
|
+
const preparation = input.preparation;
|
|
84
|
+
const firstKeptEntryId = typeof preparation?.firstKeptEntryId === "string" &&
|
|
85
|
+
preparation.firstKeptEntryId
|
|
86
|
+
? preparation.firstKeptEntryId
|
|
87
|
+
: undefined;
|
|
88
|
+
const tokensBefore = typeof preparation?.tokensBefore === "number" &&
|
|
89
|
+
Number.isFinite(preparation.tokensBefore)
|
|
90
|
+
? preparation.tokensBefore
|
|
91
|
+
: undefined;
|
|
92
|
+
if (!firstKeptEntryId || tokensBefore === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
const sections = [
|
|
95
|
+
"Deterministic overflow checkpoint (no extra summary request).",
|
|
96
|
+
boundText(preparation?.previousSummary, 2000)
|
|
97
|
+
? `Previous summary:\n${boundText(preparation?.previousSummary, 2000)}`
|
|
98
|
+
: "",
|
|
99
|
+
extractLastUserGoal(preparation?.messagesToSummarize)
|
|
100
|
+
? `Latest user goal:\n${extractLastUserGoal(preparation?.messagesToSummarize)}`
|
|
101
|
+
: "",
|
|
102
|
+
extractTodos(input.todos) ? `Open todos:\n${extractTodos(input.todos)}` : "",
|
|
103
|
+
extractFileOps(preparation?.fileOps)
|
|
104
|
+
? `File ops:\n${extractFileOps(preparation?.fileOps)}`
|
|
105
|
+
: "",
|
|
106
|
+
typeof input.lastError === "string" && input.lastError
|
|
107
|
+
? `Latest error:\n${boundText(input.lastError, 400)}`
|
|
108
|
+
: "",
|
|
109
|
+
"Next step: continue the interrupted turn from the kept recent messages. Do not repeat completed file writes.",
|
|
110
|
+
].filter(Boolean);
|
|
111
|
+
let summary = sections.join("\n\n");
|
|
112
|
+
if (Buffer.byteLength(summary, "utf8") > CHECKPOINT_MAX_BYTES) {
|
|
113
|
+
summary = `${summary.slice(0, CHECKPOINT_MAX_BYTES - 1)}…`;
|
|
114
|
+
}
|
|
115
|
+
return { summary, firstKeptEntryId, tokensBefore };
|
|
116
|
+
}
|