@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,418 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ export const CONTEXT_TRANSFER_REASON_CODES = [
4
+ "CONTEXT_RECEIPT_MISSING",
5
+ "CONTEXT_COMPONENT_UNAVAILABLE",
6
+ "CONTEXT_BUNDLE_INEXACT",
7
+ "CONTEXT_BUNDLE_TOO_LARGE",
8
+ "RAW_EXPORT_ACKNOWLEDGEMENT_REQUIRED",
9
+ "SECRET_SCAN_CONFIRMATION_REQUIRED",
10
+ "ARTIFACT_BINDING_UNAVAILABLE",
11
+ "ARTIFACT_NOT_REVISION_ELIGIBLE",
12
+ "ARTIFACT_IDENTITY_AMBIGUOUS",
13
+ "ARTIFACT_REVISION_SCHEMA_INVALID",
14
+ "ARTIFACT_REVISION_PATH_INVALID",
15
+ "ARTIFACT_REVISION_EXPIRED",
16
+ "ARTIFACT_REVISION_DRIFT",
17
+ "ARTIFACT_REVISION_QUOTA_EXCEEDED",
18
+ "ARTIFACT_CONSUMER_UNKNOWN",
19
+ "ARTIFACT_IMPACT_UNSAFE",
20
+ "RERUN_PLAN_DRIFT",
21
+ "WORKER_MANAGED_CONTINUATION_UNSUPPORTED",
22
+ "WORKER_CONTINUATION_AUTHORITY_MISMATCH",
23
+ "WORKER_CONTINUATION_NEEDS_RECONCILE",
24
+ ];
25
+ export const contextTransferReasonCodeSchema = z.enum(CONTEXT_TRANSFER_REASON_CODES);
26
+ export const CONTEXT_BUNDLE_LIMITS = Object.freeze({
27
+ previewBytes: 64 * 1024,
28
+ singleFileBytes: 16 * 1024 * 1024,
29
+ totalUncompressedBytes: 64 * 1024 * 1024,
30
+ fileCount: 2_048,
31
+ });
32
+ const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
33
+ const safeRelativePosixPathSchema = z
34
+ .string()
35
+ .min(1)
36
+ .refine((value) => !value.includes("\0"), "path must not contain NUL")
37
+ .refine((value) => !value.includes("\\"), "path must use POSIX separators")
38
+ .refine((value) => !value.startsWith("/") &&
39
+ !value.startsWith("//") &&
40
+ !/^[A-Za-z]:/.test(value), "path must be relative")
41
+ .refine((value) => value.split("/").every((segment) => segment !== ".." && segment !== ""), "path must not escape or contain empty segments");
42
+ const contextComponentSchema = z
43
+ .object({
44
+ index: z.number().int().nonnegative(),
45
+ kind: z.enum([
46
+ "system-prompt",
47
+ "developer-prompt",
48
+ "user-prompt",
49
+ "task-contract",
50
+ "execution-constraint",
51
+ "context-file",
52
+ "upstream-artifact",
53
+ "tool-contract",
54
+ "runtime-fact",
55
+ ]),
56
+ logicalName: z.string().min(1),
57
+ source: z
58
+ .object({
59
+ type: z.string().min(1),
60
+ identity: z.string().min(1),
61
+ revision: z.string().min(1).optional(),
62
+ })
63
+ .strict(),
64
+ materializedPath: safeRelativePosixPathSchema.optional(),
65
+ sha256: sha256Schema,
66
+ bytes: z.number().int().nonnegative(),
67
+ mediaType: z.string().min(1),
68
+ inclusion: z.enum([
69
+ "full",
70
+ "excerpt",
71
+ "summary",
72
+ "reference-only",
73
+ "omitted",
74
+ ]),
75
+ authoritativeSource: z
76
+ .object({ identity: z.string().min(1), sha256: sha256Schema.optional() })
77
+ .strict()
78
+ .optional(),
79
+ omissionReason: z.string().min(1).optional(),
80
+ })
81
+ .strict();
82
+ export const effectiveNodeContextV1Schema = z
83
+ .object({
84
+ schemaVersion: z.literal(1),
85
+ identity: z
86
+ .object({
87
+ runId: z.string().min(1),
88
+ nodeId: z.string().min(1),
89
+ attempt: z.number().int().positive(),
90
+ capturedAt: z.string().min(1),
91
+ })
92
+ .strict(),
93
+ orderedComponents: z.array(contextComponentSchema),
94
+ promptAssembly: z
95
+ .object({
96
+ strategy: z.string().min(1),
97
+ finalPromptSha256: sha256Schema.optional(),
98
+ tokenEstimate: z.number().int().nonnegative().optional(),
99
+ truncations: z.array(z
100
+ .object({
101
+ componentIndex: z.number().int().nonnegative(),
102
+ reason: z.string().min(1),
103
+ retainedBytes: z.number().int().nonnegative().optional(),
104
+ })
105
+ .strict()),
106
+ })
107
+ .strict(),
108
+ boundaries: z
109
+ .object({
110
+ allowedPaths: z.array(z.string()),
111
+ forbiddenPaths: z.array(z.string()),
112
+ writeSet: z.array(z.string()),
113
+ })
114
+ .strict(),
115
+ runtime: z
116
+ .object({
117
+ executor: z.string().min(1),
118
+ modelHint: z.string().min(1).optional(),
119
+ resolvedModel: z.string().min(1).optional(),
120
+ skills: z.array(z.string()),
121
+ extensions: z.array(z.string()),
122
+ activeTools: z.array(z.string()),
123
+ })
124
+ .strict(),
125
+ contextDigest: sha256Schema,
126
+ })
127
+ .strict()
128
+ .superRefine((value, ctx) => {
129
+ value.orderedComponents.forEach((component, index) => {
130
+ if (component.index !== index) {
131
+ ctx.addIssue({
132
+ code: z.ZodIssueCode.custom,
133
+ path: ["orderedComponents", index, "index"],
134
+ message: "component indexes must be contiguous and ordered",
135
+ });
136
+ }
137
+ });
138
+ });
139
+ const bundleFileSchema = z
140
+ .object({
141
+ path: safeRelativePosixPathSchema,
142
+ sha256: sha256Schema,
143
+ bytes: z.number().int().nonnegative(),
144
+ mediaType: z.string().min(1),
145
+ origin: z.string().min(1),
146
+ redaction: z.enum(["none", "redacted", "omitted"]),
147
+ sourceSha256: sha256Schema.optional(),
148
+ })
149
+ .strict();
150
+ export const contextBundleManifestV1Schema = z
151
+ .object({
152
+ schemaVersion: z.literal(1),
153
+ bundleId: z.string().min(1),
154
+ createdAt: z.string().min(1),
155
+ source: z
156
+ .object({
157
+ repoIdentity: z.string().min(1),
158
+ taskId: z.string().min(1).optional(),
159
+ runId: z.string().min(1),
160
+ nodeId: z.string().min(1),
161
+ attempt: z.number().int().positive(),
162
+ pass: z.number().int().positive().optional(),
163
+ parentRunId: z.string().min(1).optional(),
164
+ })
165
+ .strict(),
166
+ profile: z.enum(["redacted", "raw"]),
167
+ target: z.enum(["generic", "pi-session", "pi-handoff", "readable"]),
168
+ reproducibility: z
169
+ .object({
170
+ level: z.enum([
171
+ "exact-pi-session",
172
+ "semantic-handoff",
173
+ "readable-transcript",
174
+ ]),
175
+ reasons: z.array(z.string()),
176
+ limitations: z.array(z.string()),
177
+ })
178
+ .strict(),
179
+ bindings: z
180
+ .object({
181
+ frozenRunSpecSha256: sha256Schema,
182
+ contextReceiptSha256: sha256Schema,
183
+ contextDigest: sha256Schema,
184
+ taskContractSha256: sha256Schema.optional(),
185
+ sourceBindingSha256: sha256Schema.optional(),
186
+ })
187
+ .strict(),
188
+ runtime: z
189
+ .object({
190
+ executor: z.string().min(1),
191
+ model: z.string().min(1).optional(),
192
+ provider: z.string().min(1).optional(),
193
+ piVersion: z.string().min(1).optional(),
194
+ skills: z.array(z
195
+ .object({
196
+ id: z.string().min(1),
197
+ version: z.string().min(1).optional(),
198
+ sha256: sha256Schema.optional(),
199
+ })
200
+ .strict()),
201
+ extensions: z.array(z
202
+ .object({
203
+ id: z.string().min(1),
204
+ packageName: z.string().min(1).optional(),
205
+ version: z.string().min(1).optional(),
206
+ status: z.string().min(1),
207
+ })
208
+ .strict()),
209
+ tools: z.array(z
210
+ .object({
211
+ name: z.string().min(1),
212
+ contractSha256: sha256Schema.optional(),
213
+ available: z.boolean(),
214
+ })
215
+ .strict()),
216
+ })
217
+ .strict(),
218
+ files: z.array(bundleFileSchema),
219
+ omissions: z.array(z
220
+ .object({
221
+ subject: z.string().min(1),
222
+ reasonCode: z.string().min(1),
223
+ detail: z.string().min(1).optional(),
224
+ })
225
+ .strict()),
226
+ security: z
227
+ .object({
228
+ secretScan: z.enum(["pass", "warning", "not-run"]),
229
+ findingCounts: z.record(z.string(), z.number().int().nonnegative()),
230
+ rawAcknowledgement: z
231
+ .object({
232
+ requestId: z.string().min(1),
233
+ acknowledgedAt: z.string().min(1),
234
+ })
235
+ .strict()
236
+ .optional(),
237
+ })
238
+ .strict(),
239
+ bundleSha256: sha256Schema,
240
+ })
241
+ .strict()
242
+ .superRefine((value, ctx) => {
243
+ const seen = new Set();
244
+ for (const [index, file] of value.files.entries()) {
245
+ const folded = file.path.normalize("NFC").toLocaleLowerCase("en-US");
246
+ if (seen.has(folded)) {
247
+ ctx.addIssue({
248
+ code: z.ZodIssueCode.custom,
249
+ path: ["files", index, "path"],
250
+ message: "bundle file paths must be unique under case folding",
251
+ });
252
+ }
253
+ seen.add(folded);
254
+ }
255
+ if (value.profile === "raw" && !value.security.rawAcknowledgement) {
256
+ ctx.addIssue({
257
+ code: z.ZodIssueCode.custom,
258
+ path: ["security", "rawAcknowledgement"],
259
+ message: "raw bundle requires explicit acknowledgement",
260
+ });
261
+ }
262
+ });
263
+ export const artifactRevisionV1Schema = z
264
+ .object({
265
+ schemaVersion: z.literal(1),
266
+ revisionId: z.string().min(1),
267
+ state: z.enum([
268
+ "staged",
269
+ "validated",
270
+ "rejected",
271
+ "expired",
272
+ "consumed",
273
+ "discarded",
274
+ ]),
275
+ parent: z
276
+ .object({
277
+ runId: z.string().min(1),
278
+ nodeId: z.string().min(1),
279
+ artifactPath: safeRelativePosixPathSchema,
280
+ artifactSha256: sha256Schema,
281
+ artifactSchemaId: z.string().min(1).optional(),
282
+ })
283
+ .strict(),
284
+ candidate: z
285
+ .object({
286
+ stagingPath: z.string().min(1),
287
+ sha256: sha256Schema,
288
+ bytes: z.number().int().nonnegative(),
289
+ mediaType: z.string().min(1),
290
+ uploadedAt: z.string().min(1),
291
+ uploadedBy: z.literal("operator"),
292
+ })
293
+ .strict(),
294
+ validation: z
295
+ .object({
296
+ status: z.enum(["pending", "pass", "fail"]),
297
+ checks: z.array(z
298
+ .object({
299
+ id: z.string().min(1),
300
+ status: z.enum(["pass", "warning", "fail"]),
301
+ detail: z.string().min(1).optional(),
302
+ })
303
+ .strict()),
304
+ })
305
+ .strict(),
306
+ diff: z
307
+ .object({
308
+ kind: z.enum(["text", "json", "binary"]),
309
+ summary: z.string(),
310
+ previewPath: safeRelativePosixPathSchema.optional(),
311
+ truncated: z.boolean(),
312
+ })
313
+ .strict(),
314
+ expiresAt: z.string().min(1),
315
+ /** Explicit staging lease (M4c): fresh lease blocks conflicting holders. */
316
+ lease: z
317
+ .object({
318
+ holder: z.string().min(1),
319
+ acquiredAt: z.string().min(1),
320
+ expiresAt: z.string().min(1),
321
+ })
322
+ .strict()
323
+ .optional(),
324
+ })
325
+ .strict();
326
+ export const artifactOverrideSetV1Schema = z
327
+ .object({
328
+ schemaVersion: z.literal(1),
329
+ overrides: z
330
+ .array(z
331
+ .object({
332
+ revisionId: z.string().min(1),
333
+ parentArtifact: z
334
+ .object({
335
+ runId: z.string().min(1),
336
+ nodeId: z.string().min(1),
337
+ path: safeRelativePosixPathSchema,
338
+ sha256: sha256Schema,
339
+ })
340
+ .strict(),
341
+ replacement: z
342
+ .object({
343
+ sha256: sha256Schema,
344
+ bytes: z.number().int().nonnegative(),
345
+ mediaType: z.string().min(1),
346
+ })
347
+ .strict(),
348
+ supersedes: z.string().min(1),
349
+ })
350
+ .strict())
351
+ .length(1),
352
+ overrideSetHash: sha256Schema,
353
+ })
354
+ .strict();
355
+ const workerContinuationIdSchema = z
356
+ .string()
357
+ .min(1)
358
+ .regex(/^[A-Za-z0-9._-]+$/);
359
+ /**
360
+ * Console-to-kernel authority for a Worker-owned artifact continuation.
361
+ * Every field is immutable and plan-hash-bound; the child identity is derived
362
+ * from the parent Worker run plus the single-use revision id.
363
+ */
364
+ export const workerContinuationAuthorityV1Schema = z
365
+ .object({
366
+ schemaVersion: z.literal(1),
367
+ parentWorkerRunId: workerContinuationIdSchema,
368
+ childWorkerRunId: workerContinuationIdSchema,
369
+ featureId: workerContinuationIdSchema,
370
+ taskId: workerContinuationIdSchema,
371
+ batchRunId: workerContinuationIdSchema,
372
+ revisionId: workerContinuationIdSchema,
373
+ })
374
+ .strict()
375
+ .superRefine((authority, ctx) => {
376
+ const expected = deriveWorkerContinuationRunId(authority.parentWorkerRunId, authority.revisionId);
377
+ if (authority.childWorkerRunId !== expected) {
378
+ ctx.addIssue({
379
+ code: z.ZodIssueCode.custom,
380
+ path: ["childWorkerRunId"],
381
+ message: `childWorkerRunId must equal ${expected}`,
382
+ });
383
+ }
384
+ });
385
+ export function deriveWorkerContinuationRunId(parentWorkerRunId, revisionId) {
386
+ const digest = createHash("sha256")
387
+ .update(`worker-continuation-v1\0${parentWorkerRunId}\0${revisionId}`)
388
+ .digest("hex")
389
+ .slice(0, 32);
390
+ return `wrc-${digest}`;
391
+ }
392
+ export function sortKeysDeep(value) {
393
+ if (Array.isArray(value))
394
+ return value.map(sortKeysDeep);
395
+ if (value && typeof value === "object") {
396
+ return Object.fromEntries(Object.entries(value)
397
+ .sort(([a], [b]) => a.localeCompare(b))
398
+ .map(([key, child]) => [key, sortKeysDeep(child)]));
399
+ }
400
+ return value;
401
+ }
402
+ export function canonicalSha256(value) {
403
+ return createHash("sha256")
404
+ .update(JSON.stringify(sortKeysDeep(value)))
405
+ .digest("hex");
406
+ }
407
+ export function computeContextDigest(context) {
408
+ return canonicalSha256(context);
409
+ }
410
+ export function computeBundleManifestSha256(manifest) {
411
+ return canonicalSha256(manifest);
412
+ }
413
+ export function computeOverrideSetHash(overrides) {
414
+ return canonicalSha256({ schemaVersion: 1, overrides });
415
+ }
416
+ export function isKnownContextTransferReasonCode(value) {
417
+ return contextTransferReasonCodeSchema.safeParse(value).success;
418
+ }
@@ -51,6 +51,8 @@ const OFFICIAL_ACTION_DESCRIPTIONS = {
51
51
  workerSchedulerStatus: "Night-scheduler status overview (agent-worker). Use to check whether overnight jobs are healthy.",
52
52
  workerSchedulerLedger: "Night-scheduler harvest ledger (agent-worker). Use to audit what overnight runs produced.",
53
53
  workerSchedulerDoctor: "Diagnose night-scheduler health (agent-worker). Use when overnight jobs fail to fire or harvest.",
54
+ dagResume: "Unpause a run paused at a human decision checkpoint. Orphaned runs (dead runner, liveness=orphaned) are NOT continuable — do not resume them: use dagReconcileRun --action supersede to archive and then advance/rerun the task fresh.",
55
+ dagReconcileRun: "Reconcile an orphaned run (dead runner, liveness=orphaned). These are NOT continuable: supersede archives the run for a fresh same-task advance/rerun; abandon discards it. resume only unpauses human-decision checkpoints — never for orphaned runs.",
54
56
  };
55
57
  const officialAction = (action, cli, kind, coverage) => ({
56
58
  action,
@@ -817,7 +819,7 @@ export function buildOperatorCapabilitiesDocument() {
817
819
  "BINDING_DRIFT",
818
820
  "HUMAN_CONFIRMATION_REQUIRED",
819
821
  ],
820
- description: "Issue a session-bound single-use execution receipt after server-side G2 assessment (bounded writeSet ⊆ allowedPaths, no forbidden overlap, no broad/destructive risk, structured verification). The model never passes a raw DAG path.",
822
+ description: "Returns SYNCHRONOUSLY (not an operation): it immediately yields a session-bound single-use executionId + receipt, which you then pass to runDag to actually execute. It is a server-side G2 assessment (bounded writeSet ⊆ allowedPaths, no forbidden overlap, no broad/destructive risk, structured verification). The model never passes a raw DAG path.",
821
823
  inputParams: [
822
824
  {
823
825
  name: "taskId",
@@ -1067,6 +1069,12 @@ export function buildOperatorCapabilitiesDocument() {
1067
1069
  required: false,
1068
1070
  description: "verification entries as label:command strings",
1069
1071
  },
1072
+ {
1073
+ name: "approveGate",
1074
+ type: "string",
1075
+ required: false,
1076
+ description: "optional write-set gate approval token `<gate-id>:sha256:<64-hex>` exactly as reported by task status gate.approveCommand; forwards as --approve-gate and executes the approved DAG as this background operation (preferred over a foreground terminal)",
1077
+ },
1070
1078
  ],
1071
1079
  modelCallable: "always",
1072
1080
  humanConfirmation: "none",
@@ -2244,6 +2252,20 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
2244
2252
  action: "dagFinalVerification",
2245
2253
  source: "loop-agent",
2246
2254
  },
2255
+ {
2256
+ command: "loop-agent dag context",
2257
+ coverage: "excluded",
2258
+ action: null,
2259
+ source: "loop-agent",
2260
+ exclusionReason: "Context export can disclose raw execution bytes and requires a human-selected output path; Console uses dedicated gated actions instead of the generic CLI wrapper.",
2261
+ },
2262
+ {
2263
+ command: "loop-agent dag artifact",
2264
+ coverage: "excluded",
2265
+ action: null,
2266
+ source: "loop-agent",
2267
+ exclusionReason: "Artifact revision staging consumes a human-selected local file and remains on dedicated Console/CLI mutation gates.",
2268
+ },
2247
2269
  {
2248
2270
  command: "loop-agent workflow list",
2249
2271
  coverage: "advanced",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { lstat, open, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+ function assertContained(root, target, label) {
5
+ if (target === root || !target.startsWith(`${root}${path.sep}`)) {
6
+ throw new Error(`${label}: path escapes boundary: ${target}`);
7
+ }
8
+ }
9
+ /**
10
+ * Reject any symlink on the path between `boundaryRoot` and `targetPath`.
11
+ * This is the lexical/path-walk phase; callers that consume file bytes must
12
+ * still use readFileBoundarySafe so the checked object is the opened object.
13
+ */
14
+ export async function assertRealPathInsideBoundary(input) {
15
+ const root = path.resolve(input.boundaryRoot);
16
+ const target = path.resolve(input.targetPath);
17
+ assertContained(root, target, input.label);
18
+ const rootStat = await lstat(root).catch(() => undefined);
19
+ if (!rootStat) {
20
+ throw new Error(`${input.label}: boundary root is not a real directory`);
21
+ }
22
+ if (rootStat.isSymbolicLink()) {
23
+ throw new Error(`${input.label}: boundary root must not be a symlink`);
24
+ }
25
+ if (!rootStat.isDirectory()) {
26
+ throw new Error(`${input.label}: boundary root is not a real directory`);
27
+ }
28
+ const segments = path.relative(root, target).split(path.sep).filter(Boolean);
29
+ let current = root;
30
+ for (let index = 0; index < segments.length; index += 1) {
31
+ current = path.join(current, segments[index]);
32
+ const stat = await lstat(current).catch(() => undefined);
33
+ if (!stat) {
34
+ throw new Error(`${input.label}: path component missing: ${segments[index]}`);
35
+ }
36
+ if (stat.isSymbolicLink()) {
37
+ throw new Error(`${input.label}: symlink is forbidden on path: ${segments[index]}`);
38
+ }
39
+ if (index < segments.length - 1 && !stat.isDirectory()) {
40
+ throw new Error(`${input.label}: path ancestor is not a directory: ${segments[index]}`);
41
+ }
42
+ }
43
+ return target;
44
+ }
45
+ /** Reject a non-regular, symlinked, or hardlinked final entry before opening. */
46
+ export async function assertReadableRegularFile(input) {
47
+ const target = await assertRealPathInsideBoundary(input);
48
+ const stat = await lstat(target).catch(() => undefined);
49
+ if (!stat)
50
+ throw new Error(`${input.label}: path component missing`);
51
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
52
+ throw new Error(`${input.label}: path must be a regular single-link file`);
53
+ }
54
+ return target;
55
+ }
56
+ /**
57
+ * Read one owned regular file without reopening an unchecked pathname.
58
+ *
59
+ * The preflight rejects normal FIFO/device/hardlink cases without opening
60
+ * them. O_NOFOLLOW rejects a final symlink introduced after preflight and
61
+ * O_NONBLOCK prevents a swapped FIFO from blocking. The post-open identity and
62
+ * realpath checks make an ancestor swap detectable before bytes are consumed.
63
+ */
64
+ export async function readFileBoundarySafe(input) {
65
+ const target = await assertReadableRegularFile(input);
66
+ const flags = fsConstants.O_RDONLY |
67
+ (fsConstants.O_NOFOLLOW ?? 0) |
68
+ (fsConstants.O_NONBLOCK ?? 0);
69
+ const handle = await open(target, flags);
70
+ try {
71
+ const opened = await handle.stat();
72
+ if (!opened.isFile() || opened.nlink !== 1) {
73
+ throw new Error(`${input.label}: path must be a regular single-link file`);
74
+ }
75
+ const [resolvedRoot, resolvedTarget, current] = await Promise.all([
76
+ realpath(path.resolve(input.boundaryRoot)),
77
+ realpath(target),
78
+ lstat(target),
79
+ ]);
80
+ assertContained(resolvedRoot, resolvedTarget, input.label);
81
+ if (current.isSymbolicLink() ||
82
+ !current.isFile() ||
83
+ current.nlink !== 1 ||
84
+ current.dev !== opened.dev ||
85
+ current.ino !== opened.ino) {
86
+ throw new Error(`${input.label}: path changed while opening`);
87
+ }
88
+ return await handle.readFile();
89
+ }
90
+ finally {
91
+ await handle.close().catch(() => { });
92
+ }
93
+ }
@@ -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
+ }