@tea-agent/loop-agent 0.40.0-next.9 → 0.41.0-beta.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 (218) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +130 -3
  3. package/README.md +2 -2
  4. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  5. package/dist/adapters/context-transfer/pi-session.js +61 -0
  6. package/dist/application/dag/generate-task-dag.js +6 -2
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +21 -5
  9. package/dist/cli/program.js +61 -4
  10. package/dist/commands/client-recovery.js +8 -36
  11. package/dist/commands/dag-artifact.js +284 -0
  12. package/dist/commands/dag-context.js +184 -0
  13. package/dist/commands/dag-rerun.js +206 -1
  14. package/dist/commands/init-model-catalog.js +22 -0
  15. package/dist/commands/task-advance.js +23 -1
  16. package/dist/commands/task-source-prepare.js +5 -0
  17. package/dist/commands/task-status.js +1 -0
  18. package/dist/executors/dag-pi-executor.js +39 -12
  19. package/dist/executors/pi-sdk-executor.js +36 -5
  20. package/dist/executors/shell-executor.js +64 -17
  21. package/dist/governance/checks.js +1 -0
  22. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  23. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  24. package/dist/infrastructure/console/context-export-store.js +160 -0
  25. package/dist/infrastructure/console/dir-lock.js +132 -0
  26. package/dist/{worker → infrastructure}/console/operation-store.js +28 -0
  27. package/dist/infrastructure/harness/artifact-store.js +10 -1
  28. package/dist/infrastructure/harness/atomic-write.js +12 -2
  29. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  30. package/dist/shared/context-transfer.js +418 -0
  31. package/dist/shared/operator/capabilities.js +23 -1
  32. package/dist/shared/operator/safe-run-summary.js +1 -0
  33. package/dist/shared/path-safety.js +93 -0
  34. package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
  35. package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
  36. package/dist/shared/pi-context-pressure/env.js +58 -0
  37. package/dist/shared/pi-context-pressure/extension.js +100 -0
  38. package/dist/shared/pi-context-pressure/index.js +7 -0
  39. package/dist/shared/pi-context-pressure/overflow.js +252 -0
  40. package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
  41. package/dist/shared/pi-context-pressure/telemetry.js +51 -0
  42. package/dist/shared/preview.js +28 -4
  43. package/dist/task/config-types.js +2 -1
  44. package/dist/task/task-demand-routing.js +3 -0
  45. package/dist/worker/console/chat/assistant-content.js +7 -0
  46. package/dist/worker/console/chat/chat-event-store.js +84 -13
  47. package/dist/worker/console/chat/goal-round-driver.js +72 -0
  48. package/dist/worker/console/chat/goals.js +300 -0
  49. package/dist/worker/console/chat/pi-runtime.js +285 -8
  50. package/dist/worker/console/chat/provider-error.js +2 -1
  51. package/dist/worker/console/chat/repo-browser.js +94 -12
  52. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  53. package/dist/worker/console/chat/routes.js +487 -30
  54. package/dist/worker/console/chat/session-catalog.js +48 -0
  55. package/dist/worker/console/chat/session-stats.js +96 -0
  56. package/dist/worker/console/chat/session-store.js +28 -19
  57. package/dist/worker/console/chat/shortcuts.js +7 -0
  58. package/dist/worker/console/chat/sift-bridge.js +1 -0
  59. package/dist/worker/console/chat/turn-process.js +32 -3
  60. package/dist/worker/console/chat/user-questions.js +1 -1
  61. package/dist/worker/console/console-update-runtime.js +1 -1
  62. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  63. package/dist/worker/console/dag-confirmation.js +1 -1
  64. package/dist/worker/console/dag-execution-receipt.js +21 -3
  65. package/dist/worker/console/doctor.js +1 -1
  66. package/dist/worker/console/draft-store.js +1 -1
  67. package/dist/worker/console/human-gate-token.js +1 -1
  68. package/dist/worker/console/index.js +3 -3
  69. package/dist/worker/console/interview/assessment.js +1 -1
  70. package/dist/worker/console/interview/session.js +1 -1
  71. package/dist/worker/console/operation-runner.js +45 -4
  72. package/dist/worker/console/operation-sse.js +1 -1
  73. package/dist/worker/console/operation-wait.js +1 -1
  74. package/dist/worker/console/operator-actions.js +124 -8
  75. package/dist/worker/console/operator-selection.js +6 -2
  76. package/dist/worker/console/operator-surface-health.js +1 -1
  77. package/dist/worker/console/operator-user-error.js +169 -0
  78. package/dist/worker/console/pi-plugins.catalog.json +73658 -0
  79. package/dist/worker/console/pi-plugins.js +179 -0
  80. package/dist/worker/console/routes.js +864 -13
  81. package/dist/worker/console/security.js +35 -0
  82. package/dist/worker/console/server.js +177 -44
  83. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-DSVcg1YO.js} +1 -1
  84. package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-BtXhIqui.js} +1 -1
  85. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-B-nh-ilJ.js} +1 -1
  86. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-BIUSVdZI.js} +1 -1
  87. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-Cmw3L4qM.js} +1 -1
  88. package/dist/worker/console/static/assets/channel-BhZFNefx.js +1 -0
  89. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-CGpMMjGm.js} +1 -1
  90. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-BNBkEpEJ.js} +1 -1
  91. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-ubln3eaU.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-_XcBTt1i.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-Ln30Pgrh.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-ESH3lhSx.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-DAJt-qXB.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-Cx10LIcH.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-CENeOxoJ.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-DXqK-D38.js} +1 -1
  99. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Sbv-NJni.js +1 -0
  100. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Sbv-NJni.js +1 -0
  101. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-CE4dmvGY.js} +1 -1
  102. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-CRacN1zK.js} +1 -1
  103. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Cneukjrt.js} +1 -1
  104. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-DPFboRoL.js} +1 -1
  105. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-DIAQgcOP.js} +1 -1
  106. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-VrYMKxuy.js} +1 -1
  107. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-CLHvBghn.js} +1 -1
  108. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-DVCWBif8.js} +1 -1
  109. package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-BTMmbBzJ.js} +1 -1
  110. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-BUZsKJ56.js} +1 -1
  111. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-BsBZ5NXC.js} +1 -1
  112. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-D7fH_kDa.js} +1 -1
  113. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-Bza5OohY.js} +1 -1
  114. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-DpkoGR_Y.js} +1 -1
  115. package/dist/worker/console/static/assets/index-Ius3vxhf.css +1 -0
  116. package/dist/worker/console/static/assets/index-yASzcbBr.js +451 -0
  117. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BYm3g6LT.js} +1 -1
  118. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-C2RS_H4v.js} +1 -1
  119. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-B4slKpCo.js} +1 -1
  120. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-B7orRNpI.js} +1 -1
  121. package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-D1BeBxLQ.js} +1 -1
  122. package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-0TtMP8k4.js} +5 -5
  123. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-XL5EbCtT.js} +1 -1
  124. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-CwF7uas5.js} +1 -1
  125. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-A_Jo-72f.js} +1 -1
  126. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-CxOa4gKL.js} +1 -1
  127. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-DoAoswIH.js} +1 -1
  128. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-C1Lc1MlD.js} +1 -1
  129. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-DFXOkq0U.js} +1 -1
  130. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-B0ufUCuW.js} +1 -1
  131. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-iPbW55g3.js} +1 -1
  132. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-CHiXzEKP.js} +1 -1
  133. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BtMgzo_H.js +1 -0
  134. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-LyFs7TpX.js} +2 -2
  135. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cb-OVd9Y.js +8 -0
  136. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-tNkJnb6z.js} +1 -1
  137. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-DFm7Oo8o.js} +1 -1
  138. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-tpblTp9i.js} +1 -1
  139. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-BCDLWR_D.js} +1 -1
  140. package/dist/worker/console/static/index.html +7 -2
  141. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  142. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
  143. package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
  144. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
  145. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
  146. package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
  147. package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
  148. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
  149. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  150. package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
  151. package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
  152. package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
  153. package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
  154. package/dist/worker/console/workspace-context.js +56 -5
  155. package/dist/worker/console/workspace-registry.js +335 -62
  156. package/dist/worker/continuation/worker-continuation.js +278 -0
  157. package/dist/worker/observe/health.js +1 -1
  158. package/dist/worker/observe/node-transparency.js +572 -0
  159. package/dist/worker/observe/routes.js +51 -1
  160. package/dist/worker/observe/static/api.js +69 -5
  161. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  162. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  163. package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
  164. package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
  165. package/dist/worker/observe/static/inspect-workspace.js +54 -10
  166. package/dist/worker/observe/static/operator-chrome.css +23 -4
  167. package/dist/worker/observe/static/operator-chrome.js +7 -2
  168. package/dist/worker/observe/static/state.js +3 -1
  169. package/dist/worker/observe/static/styles.css +1108 -135
  170. package/dist/worker/observe/static/views/dag-inspector.js +1257 -59
  171. package/dist/worker/observe/static/views/dags.js +7 -5
  172. package/dist/worker/observe/static/views/session-timeline.js +210 -10
  173. package/dist/worker/outcomes/adapters.js +19 -0
  174. package/dist/worker/outcomes/types.js +1 -0
  175. package/dist/worker/pool/attempt-lease.js +97 -66
  176. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  177. package/dist/worker/pool/reconcile.js +46 -1
  178. package/dist/worker/pool/run-store.js +2 -0
  179. package/dist/worker/pool/state-projection.js +2 -0
  180. package/dist/worker/task-spec/workflow-routing.js +8 -3
  181. package/dist/workflows/dag/artifact-bindings.js +149 -0
  182. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  183. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
  184. package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
  185. package/dist/workflows/dag/backend-test-result-contract.js +4 -0
  186. package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
  187. package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
  188. package/dist/workflows/dag/budget-enforcement.js +10 -0
  189. package/dist/workflows/dag/context-receipt.js +305 -0
  190. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  191. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  192. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  193. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  194. package/dist/workflows/dag/init-hybrid.js +151 -4
  195. package/dist/workflows/dag/node-execution.js +112 -27
  196. package/dist/workflows/dag/path-safety.js +1 -0
  197. package/dist/workflows/dag/prompt.js +29 -4
  198. package/dist/workflows/dag/rerun-plan.js +555 -1
  199. package/dist/workflows/dag/rerun-run.js +601 -15
  200. package/dist/workflows/dag/runner.js +20 -1
  201. package/dist/workflows/dag/skill-snapshot.js +17 -0
  202. package/dist/workflows/dag/types.js +183 -7
  203. package/docs/architecture/runtime-boundaries.md +8 -7
  204. package/docs/architecture/worker-and-feature.md +1 -1
  205. package/docs/templates/agent-dag.schema.json +32 -0
  206. package/docs/templates/backend-test-dag.json +6 -5
  207. package/docs/templates/init-managed-agents.md +1 -1
  208. package/docs/templates/product-line/task.yaml +1 -1
  209. package/package.json +5 -2
  210. package/skills/loop-agent/references/command-reference.md +21 -1
  211. package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
  212. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
  213. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
  214. package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
  215. package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
  216. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
  217. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
  218. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -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
+ }
@@ -1,10 +1,34 @@
1
1
  const ENCODER = new TextEncoder();
2
2
  const DECODER = new TextDecoder();
3
+ export function redactSecretsWithFindings(text) {
4
+ let findingCount = 0;
5
+ const replaceChanged = (match, replacement) => {
6
+ if (match !== replacement)
7
+ findingCount += 1;
8
+ return replacement;
9
+ };
10
+ let redacted = text.replace(/(["']?(?:api[_-]?key|token|password|secret|authorization)["']?\s*[:=]\s*["']?)((?:Bearer\s+)?[^"'\s,}]+)/gi, (match, prefix) => replaceChanged(match, `${prefix}***`));
11
+ redacted = redacted.replace(/(\b[A-Z][A-Z0-9_]*=)[^\s]+/g, (match, prefix) => replaceChanged(match, `${prefix}***`));
12
+ redacted = redacted.replace(/\bBearer\s+[^\s"']+/gi, (match) => replaceChanged(match, "Bearer ***"));
13
+ // High-confidence provider-style tokens (OpenAI sk-, GitHub ghp_/gho_,
14
+ // Slack xox*, AWS AKIA) are credential-like even without a key=value
15
+ // prefix (exec-plan 2026-08-25 D4: credential-like 默认阻断).
16
+ redacted = redacted.replace(/\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pos]_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16})\b/g, (match) => replaceChanged(match, "***"));
17
+ return { redacted, findingCount };
18
+ }
19
+ export function redactSensitivePathsWithFindings(text) {
20
+ let findingCount = 0;
21
+ const replaceHome = (match) => {
22
+ if (match !== "<redacted-home>")
23
+ findingCount += 1;
24
+ return "<redacted-home>";
25
+ };
26
+ let redacted = text.replace(/\/(?:Users|home)\/[^/\s"'\\]+/g, replaceHome);
27
+ redacted = redacted.replace(/\b[A-Za-z]:\\Users\\[^\\/\s"']+/gi, replaceHome);
28
+ return { redacted, findingCount };
29
+ }
3
30
  export function redactSecrets(text) {
4
- return text
5
- .replace(/\bBearer\s+[^\s"']+/gi, "Bearer ***")
6
- .replace(/(\b[A-Z][A-Z0-9_]*=)[^\s]+/g, "$1***")
7
- .replace(/(["']?(?:api[_-]?key|token|password|secret|authorization)["']?\s*[:=]\s*["']?)([^"'\s,}]+)/gi, "$1***");
31
+ return redactSecretsWithFindings(text).redacted;
8
32
  }
9
33
  export function truncateUtf8Preview(text, maxBytes = 4096) {
10
34
  if (maxBytes <= 0)
@@ -14,6 +14,7 @@ export const taskKindSchema = z.enum([
14
14
  "frontend-implementation",
15
15
  "frontend-test",
16
16
  "backend-test",
17
+ "backend-test-analysis",
17
18
  "knowledge-sync",
18
19
  "knowledge-graph-bootstrap",
19
20
  ]);
@@ -163,7 +164,7 @@ const taskConfigObjectSchema = z.object({
163
164
  taskId: z.string(),
164
165
  title: z.string(),
165
166
  sourceFiles: z.array(z.string()),
166
- /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 前端实现 DAG;backend-test: 后端测试 DAG;knowledge-sync: 最终验证后回写测试知识库 DAG;knowledge-graph-bootstrap: AI 辅助业务知识图谱初始化 */
167
+ /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 前端实现 DAG;frontend-test: 前端浏览器测试 DAG;backend-test-analysis: 只读后端测试分析 DAG;backend-test: 后端测试 DAG;knowledge-sync: 最终验证后回写测试知识库 DAG;knowledge-graph-bootstrap: AI 辅助业务知识图谱初始化 */
167
168
  taskKind: taskKindSchema.optional().default("standard"),
168
169
  /** true = taskKind 由人类显式选择(含显式 standard);DAG 路由必须尊重,不做语义改写。 */
169
170
  taskKindExplicit: z.boolean().optional(),
@@ -76,6 +76,7 @@ const SPECIAL_TEMPLATE_BY_TASK_KIND = {
76
76
  "frontend-implementation": "frontend-implementation",
77
77
  "frontend-test": "frontend-test-dag",
78
78
  "backend-test": "backend-test-dag",
79
+ "backend-test-analysis": "backend-test-analysis-dag",
79
80
  "knowledge-sync": "knowledge-sync-dag",
80
81
  "knowledge-graph-bootstrap": "knowledge-graph-bootstrap-dag",
81
82
  };
@@ -83,6 +84,7 @@ const TASK_KIND_ROUTING_REASON = {
83
84
  "frontend-implementation": 'taskKind "frontend-implementation" selects the dedicated frontend DAG template',
84
85
  "frontend-test": 'taskKind "frontend-test" selects the dedicated frontend test DAG template',
85
86
  "backend-test": 'taskKind "backend-test" selects the dedicated backend test DAG template',
87
+ "backend-test-analysis": 'taskKind "backend-test-analysis" selects the read-only backend test analysis DAG template',
86
88
  "knowledge-sync": 'taskKind "knowledge-sync" selects the dedicated knowledge-sync DAG template',
87
89
  "knowledge-graph-bootstrap": 'taskKind "knowledge-graph-bootstrap" selects the dedicated knowledge-graph bootstrap DAG template',
88
90
  };
@@ -90,6 +92,7 @@ const SPECIAL_REQUESTED_TEMPLATES = new Set([
90
92
  "frontend-implementation",
91
93
  "frontend-test-dag",
92
94
  "backend-test-dag",
95
+ "backend-test-analysis-dag",
93
96
  "knowledge-sync-dag",
94
97
  "knowledge-graph-bootstrap-dag",
95
98
  ]);
@@ -98,6 +98,13 @@ function isFinalAnswerBlock(block) {
98
98
  }
99
99
  export function splitFinalAssistantBlocks(blocks, options = {}) {
100
100
  const displayable = getDisplayableAssistantBlocks(blocks, options);
101
+ // A streaming Pi message is cumulative: a later update may append a toolCall
102
+ // after text that is already visible. Keep the whole live message in the
103
+ // chronological process subtree so that update appends the tool in place
104
+ // instead of reparenting the earlier text from answer -> process.
105
+ if (options.isStreaming) {
106
+ return { answerBlocks: [], processBlocks: displayable };
107
+ }
101
108
  let lastProcessIndex = -1;
102
109
  for (let i = displayable.length - 1; i >= 0; i--) {
103
110
  if (!isFinalAnswerBlock(displayable[i])) {