@tea-agent/loop-agent 0.40.0-next.9 → 0.41.0-next.1

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.
Files changed (148) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +29 -20
  3. package/README.md +2 -2
  4. package/dist/application/dag/generate-task-dag.js +6 -2
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/cli/command-definitions.js +9 -5
  7. package/dist/cli/program.js +38 -3
  8. package/dist/commands/client-recovery.js +8 -36
  9. package/dist/commands/init-model-catalog.js +22 -0
  10. package/dist/commands/task-advance.js +23 -1
  11. package/dist/commands/task-source-prepare.js +5 -0
  12. package/dist/commands/task-status.js +1 -0
  13. package/dist/executors/pi-sdk-executor.js +33 -5
  14. package/dist/executors/shell-executor.js +94 -20
  15. package/dist/governance/checks.js +1 -0
  16. package/dist/shared/operator/capabilities.js +9 -1
  17. package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
  18. package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
  19. package/dist/shared/pi-context-pressure/env.js +58 -0
  20. package/dist/shared/pi-context-pressure/extension.js +100 -0
  21. package/dist/shared/pi-context-pressure/index.js +7 -0
  22. package/dist/shared/pi-context-pressure/overflow.js +252 -0
  23. package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
  24. package/dist/shared/pi-context-pressure/telemetry.js +51 -0
  25. package/dist/worker/console/chat/assistant-content.js +7 -0
  26. package/dist/worker/console/chat/chat-event-store.js +82 -11
  27. package/dist/worker/console/chat/goal-round-driver.js +72 -0
  28. package/dist/worker/console/chat/goals.js +300 -0
  29. package/dist/worker/console/chat/pi-runtime.js +286 -8
  30. package/dist/worker/console/chat/provider-error.js +2 -1
  31. package/dist/worker/console/chat/repo-browser.js +94 -12
  32. package/dist/worker/console/chat/routes.js +495 -30
  33. package/dist/worker/console/chat/semantic-activity.js +12 -11
  34. package/dist/worker/console/chat/session-catalog.js +48 -0
  35. package/dist/worker/console/chat/session-stats.js +96 -0
  36. package/dist/worker/console/chat/session-store.js +27 -18
  37. package/dist/worker/console/chat/shortcuts.js +7 -0
  38. package/dist/worker/console/chat/sift-bridge.js +1 -0
  39. package/dist/worker/console/chat/turn-process.js +60 -7
  40. package/dist/worker/console/dag-execution-receipt.js +20 -2
  41. package/dist/worker/console/operator-actions.js +20 -5
  42. package/dist/worker/console/operator-selection.js +6 -2
  43. package/dist/worker/console/pi-plugins.catalog.json +73658 -0
  44. package/dist/worker/console/pi-plugins.js +179 -0
  45. package/dist/worker/console/routes.js +246 -6
  46. package/dist/worker/console/server.js +173 -42
  47. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-CV1qyRbn.js} +1 -1
  48. package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-B_ljC4ma.js} +1 -1
  49. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-DTBqsauL.js} +1 -1
  50. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-CMZj6nYE.js} +1 -1
  51. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-C4ey_S7v.js} +1 -1
  52. package/dist/worker/console/static/assets/channel-BvYm_83n.js +1 -0
  53. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-CGcAlY6Z.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-XRSM64J1.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-COSIFYbz.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-BwuaXiOK.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-DNbFuVlv.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-C4DZKffE.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-M3A6iczx.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-C5zDzQEM.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-uAcUDub8.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-EPpz2uSP.js} +1 -1
  63. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BNdy3f9F.js +1 -0
  64. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BNdy3f9F.js +1 -0
  65. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-BAkfFGTf.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-Cjb4d22A.js} +1 -1
  67. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Cf7QSgcu.js} +1 -1
  68. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-UlyVBCtl.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-DWdRAZ8p.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-Y2ag2Pm0.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-DBHcuPXI.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-BK7eno2C.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-Dbyku82r.js} +1 -1
  74. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR--rS0Q41A.js} +1 -1
  75. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-Cms8pJeK.js} +1 -1
  76. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-DouONwBy.js} +1 -1
  77. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-DuKB5yDe.js} +1 -1
  78. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-DE6zp0SI.js} +1 -1
  79. package/dist/worker/console/static/assets/index-C39zQYik.js +451 -0
  80. package/dist/worker/console/static/assets/index-Crixt9tN.css +1 -0
  81. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BLETv9SH.js} +1 -1
  82. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-DyU-2lJb.js} +1 -1
  83. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-BotFSygt.js} +1 -1
  84. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-CqcAMgVk.js} +1 -1
  85. package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-Bf4U8h5G.js} +1 -1
  86. package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-D0JEVefG.js} +5 -5
  87. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-DEFgNgOo.js} +1 -1
  88. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-CirKhxo1.js} +1 -1
  89. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-BlaQYInF.js} +1 -1
  90. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-BbyzqH4U.js} +1 -1
  91. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-qA-XykV6.js} +1 -1
  92. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-Cc85Zcxc.js} +1 -1
  93. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-CS7BVnoH.js} +1 -1
  94. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-DMQ-kKmZ.js} +1 -1
  95. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-DXKs1dcE.js} +1 -1
  96. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-xG1ti87X.js} +1 -1
  97. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-tYINJZ8x.js +1 -0
  98. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-BQwmKGHd.js} +2 -2
  99. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-OoBOXPiG.js +8 -0
  100. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-BwL9oHtP.js} +1 -1
  101. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-BWk-i_Y1.js} +1 -1
  102. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-DAvETpEZ.js} +1 -1
  103. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-Cd4pASnF.js} +1 -1
  104. package/dist/worker/console/static/index.html +2 -2
  105. package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
  106. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
  107. package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
  108. package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
  109. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
  110. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
  111. package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
  112. package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
  113. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
  114. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  115. package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
  116. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
  117. package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
  118. package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
  119. package/dist/worker/console/workspace-context.js +28 -3
  120. package/dist/worker/console/workspace-registry.js +333 -60
  121. package/dist/worker/observability/read-model.js +4 -0
  122. package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
  123. package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
  124. package/dist/worker/observe/static/inspect-workspace.js +54 -10
  125. package/dist/worker/observe/static/operator-chrome.css +23 -4
  126. package/dist/worker/observe/static/operator-chrome.js +7 -2
  127. package/dist/worker/observe/static/styles.css +87 -25
  128. package/dist/worker/observe/static/views/dag-inspector.js +42 -1
  129. package/dist/worker/observe/static/views/dags.js +7 -5
  130. package/dist/worker/observe/static/views/session-timeline.js +135 -0
  131. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
  132. package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
  133. package/dist/workflows/dag/backend-test-result-contract.js +5 -1
  134. package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
  135. package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
  136. package/dist/workflows/dag/init-hybrid.js +5 -4
  137. package/docs/architecture/runtime-boundaries.md +2 -1
  138. package/docs/templates/backend-test-dag.json +6 -5
  139. package/docs/templates/init-managed-agents.md +1 -1
  140. package/package.json +3 -2
  141. package/skills/loop-agent/references/command-reference.md +15 -1
  142. package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
  143. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
  144. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
  145. package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
  146. package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
  147. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
  148. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
@@ -0,0 +1,7 @@
1
+ export { isContextOverflow, CONTEXT_OVERFLOW_PHRASES, OVERFLOW_FIXTURES, NORMALIZED_OVERFLOW_PREFIX, shouldRewriteAssistantOverflow, normalizeOverflowErrorMessage, buildGeneratedOverflowClassifierSource, } from "./overflow.js";
2
+ export { parseCompactionPolicy, deriveEffectiveWindow, conservativeTokenEstimate, projectPromptPressure, applyProcessCompactionOverride, inMemoryCompactionSettings, PI_DEFAULT_RESERVE_TOKENS, DEFAULT_KEEP_RECENT_TOKENS, } from "./compaction-policy.js";
3
+ export { buildOverflowCheckpoint } from "./checkpoint.js";
4
+ export { createContextPressureExtension, createCompactionController, } from "./extension.js";
5
+ export { createSiftBridgeState, parseSiftBridgeConfig, compressProviderPayload, executeSiftRetrieve, housekeepSiftStash, createSiftRetrieveToolDefinition, SIFT_RETRIEVE_TOOL_NAME, SIFT_POLICY_VERSION, } from "./sift-bridge.js";
6
+ export { isConsoleSiftEnabled, isDagSiftEnabled, readContextPressureMode, parseEnabledFlag, parseContextPressureMode, } from "./env.js";
7
+ export { emptyContextPressureCounters, projectSafeTelemetry, } from "./telemetry.js";
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Unique overflow classifier for Console UI, SDK extensions, and generated
3
+ * client-recovery plugins. Phrase match wins; type/code never classify alone.
4
+ */
5
+ export const CONTEXT_OVERFLOW_PHRASES = [
6
+ "请求上下文过大",
7
+ "上下文过大",
8
+ "上下文太大",
9
+ "上下文过长",
10
+ "上下文超限",
11
+ "context_length_exceeded",
12
+ "context overflow",
13
+ "context length overflow",
14
+ "too many tokens",
15
+ "maximum context",
16
+ "prompt is too long",
17
+ "prompt too long",
18
+ "request_too_large",
19
+ "reduce the length of the messages",
20
+ "context window",
21
+ ];
22
+ const OVERFLOW_PATTERN = new RegExp(CONTEXT_OVERFLOW_PHRASES.map(escapeRegExpLiteral).join("|"), "i");
23
+ const AUTH_STATUS = new Set([401, 403]);
24
+ export const OVERFLOW_FIXTURES = {
25
+ positives: {
26
+ P1: `{"object":"error","message":"请求上下文过大","type":"BadRequestError","code":400}`,
27
+ P2: `data:{"object":"error","message":"请求上下文过大","type":"BadRequestError","code":400}`,
28
+ P3: `400 {"object":"error","message":"请求上下文过大","type":"BadRequestError","code":400}`,
29
+ P4: "请求上下文过大",
30
+ P5: "请求上下文过大 (BadRequestError 400)",
31
+ P6: "LLMRequestError: 请求上下文过大 (BadRequestError 400)",
32
+ P7: "LLM upstream returned an error payload in a 200 stream: 请求上下文过大 (BadRequestError 400)",
33
+ P8: "LLM upstream returned HTTP 400: 请求上下文过大",
34
+ P9: `{"object":"error","type":"BadRequestError","code":"200","message":"{\\"object\\":\\"error\\",\\"code\\":400,\\"type\\":\\"BadRequestError\\",\\"param\\":null,\\"message\\":\\"请求上下文过大\\"}"}`,
35
+ P10: {
36
+ stopReason: "error",
37
+ errorMessage: "请求上下文过大 (BadRequestError 400)",
38
+ errorCode: "stream_payload_error",
39
+ providerType: "BadRequestError",
40
+ providerCode: "400",
41
+ },
42
+ // P11/P12: 2026-08-27 真实企业网关捕获(openai-completions / wizard-local /
43
+ // aicoder-plus)。HTTP 状态行 + 换行 + `data:` SSE 前缀 + JSON + 尾部空行
44
+ // 同串组合;完整事件样本见 exec-plan §1.1 附录 A。
45
+ P11: "400\n data:{\"object\":\"error\",\"message\":\"请求上下文过大\",\"type\":\"BadRequestError\",\"code\":400}\n\n",
46
+ P12: {
47
+ message: {
48
+ role: "assistant",
49
+ content: [],
50
+ api: "openai-completions",
51
+ provider: "wizard-local",
52
+ model: "aicoder-plus",
53
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
54
+ stopReason: "error",
55
+ errorMessage: "400\n data:{\"object\":\"error\",\"message\":\"请求上下文过大\",\"type\":\"BadRequestError\",\"code\":400}\n\n",
56
+ },
57
+ },
58
+ },
59
+ negatives: {
60
+ N1: `{"object":"error","message":"认证错误!","type":"BadRequestError","code":401}`,
61
+ N2: "当前请求超时, 请重试 (BadRequestError 502)",
62
+ N2b: "LLMRequestError: 当前请求超时, 请重试 (BadRequestError 502)",
63
+ N3: "The requested resource was not found. (BadRequestError 404)",
64
+ N3b: "The requested resource was not found. (NotFoundError 404)",
65
+ N4: "LLMRequestError: (BadRequestError 400)",
66
+ N5: `{"object":"error","type":"BadRequestError","code":"200","message":"{\\"object\\":\\"error\\",\\"code\\":502,\\"type\\":\\"BadRequestError\\",\\"message\\":\\"当前请求超时, 请重试\\"}"}`,
67
+ },
68
+ };
69
+ export const NORMALIZED_OVERFLOW_PREFIX = "context_length_exceeded:";
70
+ function escapeRegExpLiteral(value) {
71
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
72
+ }
73
+ function asText(value) {
74
+ if (typeof value === "string")
75
+ return value;
76
+ if (value == null)
77
+ return "";
78
+ try {
79
+ return JSON.stringify(value);
80
+ }
81
+ catch {
82
+ return String(value);
83
+ }
84
+ }
85
+ function tryParseJson(text) {
86
+ const trimmed = text.trim();
87
+ const candidates = [trimmed];
88
+ const dataPrefix = trimmed.match(/^data:\s*(\{[\s\S]*)$/i);
89
+ if (dataPrefix)
90
+ candidates.push(dataPrefix[1] ?? "");
91
+ const statusPrefix = trimmed.match(/^\d{3}\s+(\{[\s\S]*)$/);
92
+ if (statusPrefix)
93
+ candidates.push(statusPrefix[1] ?? "");
94
+ const start = trimmed.indexOf("{");
95
+ const end = trimmed.lastIndexOf("}");
96
+ if (start >= 0 && end > start)
97
+ candidates.push(trimmed.slice(start, end + 1));
98
+ for (const candidate of candidates) {
99
+ if (!candidate)
100
+ continue;
101
+ try {
102
+ return JSON.parse(candidate);
103
+ }
104
+ catch {
105
+ // Keep scanning other envelopes.
106
+ }
107
+ }
108
+ return undefined;
109
+ }
110
+ function collectStringFields(value, depth, out) {
111
+ if (depth > 6 || value == null)
112
+ return;
113
+ if (typeof value === "string") {
114
+ if (value)
115
+ out.push(value);
116
+ const nested = tryParseJson(value);
117
+ if (nested !== undefined && nested !== value) {
118
+ collectStringFields(nested, depth + 1, out);
119
+ }
120
+ return;
121
+ }
122
+ if (typeof value !== "object")
123
+ return;
124
+ const record = value;
125
+ for (const key of ["message", "errorMessage", "error"]) {
126
+ if (key in record)
127
+ collectStringFields(record[key], depth + 1, out);
128
+ }
129
+ }
130
+ function extractStatus(text) {
131
+ const suffix = text.match(/\((?:BadRequestError|NotFoundError|AuthenticationError|RateLimitError)\s+(\d{3})\)/i);
132
+ if (suffix)
133
+ return Number(suffix[1]);
134
+ const http = text.match(/\bHTTP\s+(\d{3})\b/i);
135
+ if (http)
136
+ return Number(http[1]);
137
+ const parsed = tryParseJson(text);
138
+ if (parsed && typeof parsed === "object") {
139
+ const record = parsed;
140
+ const code = record.code ?? record.status ?? record.statusCode;
141
+ if (typeof code === "number" && Number.isFinite(code))
142
+ return code;
143
+ if (typeof code === "string" && /^\d{3}$/.test(code.trim())) {
144
+ return Number(code.trim());
145
+ }
146
+ }
147
+ return undefined;
148
+ }
149
+ export function collectOverflowHaystack(error) {
150
+ if (typeof error === "string")
151
+ return error;
152
+ if (!error || typeof error !== "object")
153
+ return asText(error);
154
+ const record = error;
155
+ const parts = [
156
+ asText(record.message),
157
+ asText(record.errorMessage),
158
+ asText(record.error),
159
+ asText(record.name),
160
+ asText(record.status),
161
+ asText(record.code),
162
+ asText(record.data),
163
+ ];
164
+ return parts.filter(Boolean).join("\n");
165
+ }
166
+ export function isContextOverflow(error) {
167
+ const haystack = collectOverflowHaystack(error);
168
+ const fields = [haystack];
169
+ collectStringFields(error, 0, fields);
170
+ collectStringFields(tryParseJson(haystack), 0, fields);
171
+ const combined = fields.join("\n");
172
+ if (!OVERFLOW_PATTERN.test(combined))
173
+ return false;
174
+ const status = extractStatus(combined);
175
+ if (status !== undefined && AUTH_STATUS.has(status))
176
+ return false;
177
+ return true;
178
+ }
179
+ export function normalizeOverflowErrorMessage(current) {
180
+ if (!current)
181
+ return undefined;
182
+ if (current.startsWith(NORMALIZED_OVERFLOW_PREFIX) ||
183
+ /context_length_exceeded/i.test(current)) {
184
+ return undefined;
185
+ }
186
+ if (!isContextOverflow(current))
187
+ return undefined;
188
+ const bounded = current.length <= 1500 ? current : `${current.slice(0, 1499)}…`;
189
+ return `${NORMALIZED_OVERFLOW_PREFIX} ${bounded}`;
190
+ }
191
+ export function shouldRewriteAssistantOverflow(message) {
192
+ if (!message || typeof message !== "object")
193
+ return undefined;
194
+ const record = message;
195
+ if (record.role !== "assistant")
196
+ return undefined;
197
+ if (record.stopReason !== undefined && record.stopReason !== "error") {
198
+ return undefined;
199
+ }
200
+ const current = typeof record.errorMessage === "string" ? record.errorMessage : "";
201
+ if (!current)
202
+ return undefined;
203
+ return normalizeOverflowErrorMessage(current);
204
+ }
205
+ export function buildGeneratedOverflowClassifierSource() {
206
+ return `const CONTEXT_OVERFLOW_PHRASES = ${JSON.stringify([
207
+ ...CONTEXT_OVERFLOW_PHRASES,
208
+ ])};
209
+ const OVERFLOW_PATTERN = new RegExp(
210
+ CONTEXT_OVERFLOW_PHRASES.map((phrase) =>
211
+ phrase.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&"),
212
+ ).join("|"),
213
+ "i",
214
+ );
215
+ function tryParseJson(text) {
216
+ if (typeof text !== "string") return undefined;
217
+ const trimmed = text.trim();
218
+ const candidates = [trimmed];
219
+ const dataPrefix = trimmed.match(/^data:\\s*(\\{[\\s\\S]*)$/i);
220
+ if (dataPrefix) candidates.push(dataPrefix[1]);
221
+ const start = trimmed.indexOf("{");
222
+ const end = trimmed.lastIndexOf("}");
223
+ if (start >= 0 && end > start) candidates.push(trimmed.slice(start, end + 1));
224
+ for (const candidate of candidates) {
225
+ try { return JSON.parse(candidate); } catch {}
226
+ }
227
+ return undefined;
228
+ }
229
+ function collectFields(value, depth, out) {
230
+ if (depth > 6 || value == null) return;
231
+ if (typeof value === "string") {
232
+ if (value) out.push(value);
233
+ const nested = tryParseJson(value);
234
+ if (nested !== undefined && nested !== value) collectFields(nested, depth + 1, out);
235
+ return;
236
+ }
237
+ if (typeof value !== "object") return;
238
+ if (typeof value.message === "string") collectFields(value.message, depth + 1, out);
239
+ if (typeof value.errorMessage === "string") collectFields(value.errorMessage, depth + 1, out);
240
+ if (value.error) collectFields(value.error, depth + 1, out);
241
+ }
242
+ function isOverflowMessage(text) {
243
+ if (typeof text !== "string" || !text) return false;
244
+ const fields = [text];
245
+ collectFields(text, 0, fields);
246
+ collectFields(tryParseJson(text), 0, fields);
247
+ const combined = fields.join("\\n");
248
+ if (!OVERFLOW_PATTERN.test(combined)) return false;
249
+ if (/\\((?:BadRequestError|AuthenticationError)\\s+40[13]\\)/i.test(combined)) return false;
250
+ return true;
251
+ }`;
252
+ }
@@ -0,0 +1,386 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { parseBoundedNumber, parseEnabledFlag } from "./env.js";
5
+ import { emptyContextPressureCounters, incrementCounter, } from "./telemetry.js";
6
+ export const SIFT_POLICY_VERSION = "sift-v1-query-undefined";
7
+ export const SIFT_RETRIEVE_TOOL_NAME = "sift_retrieve";
8
+ export const SIFT_STASH_KEY_PATTERN = /^[0-9a-f]{24}$/;
9
+ export const SIFT_RETRIEVE_DEFAULT_BYTES = 24 * 1024;
10
+ export const SIFT_RETRIEVE_MAX_BYTES = 48 * 1024;
11
+ export const DEFAULT_MEMO_MAX_ENTRIES = 128;
12
+ export const DEFAULT_MEMO_MAX_BYTES = 32 * 1024 * 1024;
13
+ export const DEFAULT_STASH_RETENTION_DAYS = 14;
14
+ class BoundedMemo {
15
+ maxEntries;
16
+ maxBytes;
17
+ entries = new Map();
18
+ bytes = 0;
19
+ constructor(maxEntries, maxBytes) {
20
+ this.maxEntries = maxEntries;
21
+ this.maxBytes = maxBytes;
22
+ }
23
+ get(key) {
24
+ const hit = this.entries.get(key);
25
+ if (!hit)
26
+ return undefined;
27
+ this.entries.delete(key);
28
+ this.entries.set(key, hit);
29
+ try {
30
+ return JSON.parse(hit.json);
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ set(key, value) {
37
+ const json = JSON.stringify(value);
38
+ const size = Buffer.byteLength(json);
39
+ if (size > this.maxBytes)
40
+ return;
41
+ this.delete(key);
42
+ while (this.entries.size >= this.maxEntries ||
43
+ this.bytes + size > this.maxBytes) {
44
+ const oldest = this.entries.keys().next().value;
45
+ if (oldest === undefined)
46
+ break;
47
+ this.delete(oldest);
48
+ }
49
+ this.entries.set(key, { json, bytes: size });
50
+ this.bytes += size;
51
+ }
52
+ delete(key) {
53
+ const current = this.entries.get(key);
54
+ if (!current)
55
+ return;
56
+ this.entries.delete(key);
57
+ this.bytes -= current.bytes;
58
+ }
59
+ }
60
+ export function parseSiftBridgeConfig(input) {
61
+ const env = input.env ?? process.env;
62
+ const override = env.HARNESS_CONSOLE_SIFT_STASH_DIR?.trim();
63
+ const root = override || input.defaultStashRoot;
64
+ const stashDir = path.join(root, input.sessionId);
65
+ const entries = parseBoundedNumber(env.HARNESS_CONSOLE_SIFT_MEMO_MAX_ENTRIES, DEFAULT_MEMO_MAX_ENTRIES, 8, 4096);
66
+ const bytes = parseBoundedNumber(env.HARNESS_CONSOLE_SIFT_MEMO_MAX_BYTES, DEFAULT_MEMO_MAX_BYTES, 1024, 256 * 1024 * 1024);
67
+ const retention = parseBoundedNumber(env.HARNESS_CONSOLE_SIFT_STASH_RETENTION_DAYS, DEFAULT_STASH_RETENTION_DAYS, 1, 365);
68
+ return {
69
+ sessionId: input.sessionId,
70
+ stashDir,
71
+ memoMaxEntries: entries.value,
72
+ memoMaxBytes: bytes.value,
73
+ retentionDays: retention.value,
74
+ ...(input.siftFactory ? { siftFactory: input.siftFactory } : {}),
75
+ ...(input.importSift ? { importSift: input.importSift } : {}),
76
+ };
77
+ }
78
+ export function createSiftBridgeState(config) {
79
+ return {
80
+ sessionId: config.sessionId,
81
+ stashDir: config.stashDir,
82
+ config,
83
+ counters: emptyContextPressureCounters(),
84
+ retrieveBypassIds: new Set(),
85
+ diagnostics: new Set(),
86
+ memo: new BoundedMemo(config.memoMaxEntries, config.memoMaxBytes),
87
+ importFailed: false,
88
+ };
89
+ }
90
+ function noteOnce(state, code) {
91
+ if (state.diagnostics.has(code))
92
+ return;
93
+ state.diagnostics.add(code);
94
+ incrementCounter(state.counters, "fail-open");
95
+ }
96
+ function memoKey(payload) {
97
+ return createHash("sha256")
98
+ .update(SIFT_POLICY_VERSION)
99
+ .update("\0")
100
+ .update(JSON.stringify(payload))
101
+ .digest("hex");
102
+ }
103
+ function ensureStashDir(stashDir) {
104
+ mkdirSync(stashDir, { recursive: true, mode: 0o700 });
105
+ }
106
+ async function ensureSift(state) {
107
+ if (state.sift)
108
+ return state.sift;
109
+ if (state.importFailed)
110
+ return undefined;
111
+ try {
112
+ ensureStashDir(state.stashDir);
113
+ if (state.config.siftFactory) {
114
+ state.sift = state.config.siftFactory({ stashDir: state.stashDir });
115
+ return state.sift;
116
+ }
117
+ const importer = state.config.importSift ??
118
+ (async () => (await import("@agent-context/sift")));
119
+ const mod = await importer();
120
+ if (typeof mod.createSift !== "function") {
121
+ state.importFailed = true;
122
+ noteOnce(state, "sift-import-invalid");
123
+ return undefined;
124
+ }
125
+ state.sift = mod.createSift({ stashDir: state.stashDir });
126
+ return state.sift;
127
+ }
128
+ catch {
129
+ state.importFailed = true;
130
+ noteOnce(state, "sift-import-failed");
131
+ return undefined;
132
+ }
133
+ }
134
+ function isRecord(value) {
135
+ return value !== null && typeof value === "object" && !Array.isArray(value);
136
+ }
137
+ function hasTrailingAnthropicCacheAnchor(payload) {
138
+ if (!isRecord(payload) || !Array.isArray(payload.messages))
139
+ return false;
140
+ const last = payload.messages[payload.messages.length - 1];
141
+ if (!isRecord(last))
142
+ return false;
143
+ const content = last.content;
144
+ if (!Array.isArray(content) || content.length === 0)
145
+ return false;
146
+ const lastBlock = content[content.length - 1];
147
+ return isRecord(lastBlock) && lastBlock.cache_control !== undefined;
148
+ }
149
+ function restoreProtectedToolResults(originalPayload, compressed, protectedIds) {
150
+ if (protectedIds.size === 0)
151
+ return compressed;
152
+ const original = structuredClone(originalPayload);
153
+ const next = structuredClone(compressed);
154
+ restoreInMessages(original, next, protectedIds);
155
+ restoreInResponsesInput(original, next, protectedIds);
156
+ return next;
157
+ }
158
+ function restoreInMessages(original, next, protectedIds) {
159
+ if (!isRecord(original) || !isRecord(next))
160
+ return;
161
+ if (!Array.isArray(original.messages) || !Array.isArray(next.messages))
162
+ return;
163
+ for (let index = 0; index < next.messages.length; index += 1) {
164
+ const origMsg = original.messages[index];
165
+ const nextMsg = next.messages[index];
166
+ if (!isRecord(origMsg) || !isRecord(nextMsg))
167
+ continue;
168
+ const origId = typeof origMsg.tool_call_id === "string"
169
+ ? origMsg.tool_call_id
170
+ : undefined;
171
+ if (origId && protectedIds.has(origId)) {
172
+ next.messages[index] = origMsg;
173
+ continue;
174
+ }
175
+ if (Array.isArray(origMsg.content) && Array.isArray(nextMsg.content)) {
176
+ for (let blockIndex = 0; blockIndex < nextMsg.content.length; blockIndex += 1) {
177
+ const origBlock = origMsg.content[blockIndex];
178
+ if (!isRecord(origBlock))
179
+ continue;
180
+ const toolUseId = typeof origBlock.tool_use_id === "string"
181
+ ? origBlock.tool_use_id
182
+ : undefined;
183
+ if (toolUseId && protectedIds.has(toolUseId)) {
184
+ nextMsg.content[blockIndex] = origBlock;
185
+ }
186
+ }
187
+ }
188
+ }
189
+ }
190
+ function restoreInResponsesInput(original, next, protectedIds) {
191
+ if (!isRecord(original) || !isRecord(next))
192
+ return;
193
+ if (!Array.isArray(original.input) || !Array.isArray(next.input))
194
+ return;
195
+ for (let index = 0; index < next.input.length; index += 1) {
196
+ const origItem = original.input[index];
197
+ if (!isRecord(origItem))
198
+ continue;
199
+ const callId = typeof origItem.call_id === "string" ? origItem.call_id : undefined;
200
+ if (callId && protectedIds.has(callId)) {
201
+ next.input[index] = origItem;
202
+ }
203
+ }
204
+ }
205
+ export async function compressProviderPayload(state, payload) {
206
+ if (payload === undefined)
207
+ return undefined;
208
+ if (!isRecord(payload)) {
209
+ noteOnce(state, "unknown-format");
210
+ return undefined;
211
+ }
212
+ const key = memoKey(payload);
213
+ const cached = state.memo.get(key);
214
+ if (cached !== undefined)
215
+ return cached;
216
+ if (hasTrailingAnthropicCacheAnchor(payload)) {
217
+ const sift = await ensureSift(state);
218
+ if (!sift)
219
+ return undefined;
220
+ try {
221
+ const result = sift.siftRequest(payload, undefined);
222
+ if (result.changed === false &&
223
+ result.blocksExamined === 0 &&
224
+ result.frozenMessages ===
225
+ (Array.isArray(payload.messages)
226
+ ? (payload.messages.length)
227
+ : 0)) {
228
+ return undefined;
229
+ }
230
+ if (!result.changed)
231
+ return undefined;
232
+ noteOnce(state, "anthropic-unexpected-change");
233
+ return undefined;
234
+ }
235
+ catch {
236
+ noteOnce(state, "sift-handler-error");
237
+ return undefined;
238
+ }
239
+ }
240
+ const sift = await ensureSift(state);
241
+ if (!sift)
242
+ return undefined;
243
+ try {
244
+ const result = sift.siftRequest(payload, undefined);
245
+ if (!result || result.changed !== true)
246
+ return undefined;
247
+ const restored = restoreProtectedToolResults(payload, result.body, state.retrieveBypassIds);
248
+ state.counters.tokensSavedEstimate += Math.max(0, result.tokensSaved ?? 0);
249
+ state.counters.blocksCompressed += Math.max(0, result.blocksCompressed ?? 0);
250
+ incrementCounter(state.counters, "wire-compression");
251
+ state.memo.set(key, restored);
252
+ return restored;
253
+ }
254
+ catch {
255
+ noteOnce(state, "sift-handler-error");
256
+ return undefined;
257
+ }
258
+ }
259
+ export function clearRetrieveBypass(state) {
260
+ state.retrieveBypassIds.clear();
261
+ }
262
+ export function sliceUtf8Range(text, offset, maxBytes) {
263
+ const totalBytes = Buffer.byteLength(text, "utf8");
264
+ const start = Math.max(0, Math.floor(offset));
265
+ const limit = Math.min(SIFT_RETRIEVE_MAX_BYTES, Math.max(1, Math.floor(maxBytes)));
266
+ if (start >= totalBytes) {
267
+ return { slice: "", nextOffset: null, totalBytes, complete: true };
268
+ }
269
+ const buffer = Buffer.from(text, "utf8");
270
+ let end = Math.min(totalBytes, start + limit);
271
+ while (end > start && (buffer[end] & 0b1100_0000) === 0b1000_0000) {
272
+ end -= 1;
273
+ }
274
+ const slice = buffer.subarray(start, end).toString("utf8");
275
+ const complete = end >= totalBytes;
276
+ return {
277
+ slice,
278
+ nextOffset: complete ? null : end,
279
+ totalBytes,
280
+ complete,
281
+ };
282
+ }
283
+ export async function executeSiftRetrieve(state, toolCallId, params) {
284
+ const key = typeof params.key === "string" ? params.key.trim() : "";
285
+ if (!SIFT_STASH_KEY_PATTERN.test(key)) {
286
+ return {
287
+ content: [
288
+ {
289
+ type: "text",
290
+ text: JSON.stringify({
291
+ error: "STASH_NOT_FOUND_OR_EXPIRED",
292
+ complete: true,
293
+ }),
294
+ },
295
+ ],
296
+ };
297
+ }
298
+ const sift = await ensureSift(state);
299
+ const original = sift?.retrieve(key) ?? null;
300
+ if (original === null) {
301
+ return {
302
+ content: [
303
+ {
304
+ type: "text",
305
+ text: JSON.stringify({
306
+ error: "STASH_NOT_FOUND_OR_EXPIRED",
307
+ complete: true,
308
+ }),
309
+ },
310
+ ],
311
+ };
312
+ }
313
+ const offset = typeof params.offset === "number" && Number.isFinite(params.offset)
314
+ ? Math.max(0, Math.floor(params.offset))
315
+ : 0;
316
+ const maxBytes = typeof params.maxBytes === "number" && Number.isFinite(params.maxBytes)
317
+ ? params.maxBytes
318
+ : SIFT_RETRIEVE_DEFAULT_BYTES;
319
+ const page = sliceUtf8Range(original, offset, maxBytes);
320
+ if (toolCallId)
321
+ state.retrieveBypassIds.add(toolCallId);
322
+ return {
323
+ content: [
324
+ {
325
+ type: "text",
326
+ text: JSON.stringify({
327
+ key,
328
+ offset,
329
+ maxBytes: Math.min(SIFT_RETRIEVE_MAX_BYTES, Math.max(1, Math.floor(maxBytes))),
330
+ totalBytes: page.totalBytes,
331
+ nextOffset: page.nextOffset,
332
+ complete: page.complete,
333
+ text: page.slice,
334
+ }),
335
+ },
336
+ ],
337
+ };
338
+ }
339
+ export function housekeepSiftStash(input) {
340
+ let removed = 0;
341
+ try {
342
+ const entries = readdirSync(input.root, { withFileTypes: true });
343
+ const cutoff = (input.now ?? Date.now()) - input.retentionDays * 24 * 60 * 60 * 1000;
344
+ for (const entry of entries) {
345
+ if (!entry.isDirectory())
346
+ continue;
347
+ if (input.activeSessionId && entry.name === input.activeSessionId)
348
+ continue;
349
+ const full = path.join(input.root, entry.name);
350
+ let mtime = 0;
351
+ try {
352
+ mtime = statSync(full).mtimeMs;
353
+ }
354
+ catch {
355
+ continue;
356
+ }
357
+ if (mtime <= cutoff) {
358
+ rmSync(full, { recursive: true, force: true });
359
+ removed += 1;
360
+ }
361
+ }
362
+ return { removed, failed: false };
363
+ }
364
+ catch {
365
+ return { removed, failed: true };
366
+ }
367
+ }
368
+ export function createSiftRetrieveToolDefinition(state) {
369
+ return {
370
+ name: SIFT_RETRIEVE_TOOL_NAME,
371
+ label: "Sift Retrieve",
372
+ description: "Retrieve a previously compressed tool-output page by 24-hex stash key. Use offset/maxBytes to page; default 24KiB, max 48KiB.",
373
+ parameters: {
374
+ type: "object",
375
+ additionalProperties: false,
376
+ properties: {
377
+ key: { type: "string", description: "24-hex stash key" },
378
+ offset: { type: "number", description: "UTF-8 byte offset" },
379
+ maxBytes: { type: "number", description: "Page size in bytes" },
380
+ },
381
+ required: ["key"],
382
+ },
383
+ execute: (toolCallId, params) => executeSiftRetrieve(state, toolCallId, params),
384
+ };
385
+ }
386
+ export { parseEnabledFlag };
@@ -0,0 +1,51 @@
1
+ export function emptyContextPressureCounters() {
2
+ return {
3
+ prePromptThreshold: 0,
4
+ overflow: 0,
5
+ manual: 0,
6
+ wireCompression: 0,
7
+ failOpen: 0,
8
+ usageUnavailable: 0,
9
+ l0CompactFailed: 0,
10
+ tokensSavedEstimate: 0,
11
+ blocksCompressed: 0,
12
+ };
13
+ }
14
+ export function incrementCounter(counters, reason, amount = 1) {
15
+ switch (reason) {
16
+ case "pre-prompt-threshold":
17
+ counters.prePromptThreshold += amount;
18
+ break;
19
+ case "overflow":
20
+ counters.overflow += amount;
21
+ break;
22
+ case "manual":
23
+ counters.manual += amount;
24
+ break;
25
+ case "wire-compression":
26
+ counters.wireCompression += amount;
27
+ break;
28
+ case "fail-open":
29
+ counters.failOpen += amount;
30
+ break;
31
+ case "usage-unavailable":
32
+ counters.usageUnavailable += amount;
33
+ break;
34
+ case "l0-compact-failed":
35
+ counters.l0CompactFailed += amount;
36
+ break;
37
+ default:
38
+ break;
39
+ }
40
+ }
41
+ export function projectSafeTelemetry(counters) {
42
+ return {
43
+ prePromptThreshold: counters.prePromptThreshold,
44
+ overflow: counters.overflow,
45
+ manual: counters.manual,
46
+ wireCompression: counters.wireCompression,
47
+ failOpen: counters.failOpen,
48
+ tokensSavedEstimate: counters.tokensSavedEstimate,
49
+ blocksCompressed: counters.blocksCompressed,
50
+ };
51
+ }