@tea-agent/loop-agent 0.39.2 → 0.40.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 (215) hide show
  1. package/CHANGELOG.md +133 -92
  2. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  3. package/dist/adapters/context-transfer/pi-session.js +61 -0
  4. package/dist/build-stamp.json +3 -3
  5. package/dist/cli/command-definitions.js +12 -0
  6. package/dist/cli/program.js +23 -1
  7. package/dist/commands/dag-artifact.js +284 -0
  8. package/dist/commands/dag-context.js +184 -0
  9. package/dist/commands/dag-rerun.js +206 -1
  10. package/dist/executors/dag-pi-executor.js +108 -19
  11. package/dist/executors/pi-executor.js +4 -2
  12. package/dist/executors/pi-sdk-executor.js +49 -1
  13. package/dist/executors/pi-writer-tool-policy.js +57 -1
  14. package/dist/executors/shell-executor.js +42 -2
  15. package/dist/executors/workspace-write-snapshot.js +68 -0
  16. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  17. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  18. package/dist/infrastructure/console/context-export-store.js +160 -0
  19. package/dist/infrastructure/console/dir-lock.js +132 -0
  20. package/dist/infrastructure/harness/artifact-store.js +10 -1
  21. package/dist/infrastructure/harness/atomic-write.js +35 -2
  22. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  23. package/dist/shared/context-transfer.js +418 -0
  24. package/dist/shared/operator/capabilities.js +14 -0
  25. package/dist/shared/operator/safe-run-summary.js +1 -0
  26. package/dist/shared/path-safety.js +93 -0
  27. package/dist/shared/preview.js +28 -4
  28. package/dist/task/config-types.js +10 -0
  29. package/dist/worker/console/chat/chat-event-store.js +77 -7
  30. package/dist/worker/console/chat/context-panel.js +7 -0
  31. package/dist/worker/console/chat/deferred-turn.js +12 -0
  32. package/dist/worker/console/chat/pi-runtime.js +331 -18
  33. package/dist/worker/console/chat/provider-error.js +98 -0
  34. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  35. package/dist/worker/console/chat/routes.js +389 -56
  36. package/dist/worker/console/chat/session-stats.js +179 -0
  37. package/dist/worker/console/chat/session-store.js +94 -64
  38. package/dist/worker/console/chat/todos.js +115 -0
  39. package/dist/worker/console/chat/tool-preview.js +42 -0
  40. package/dist/worker/console/chat/turn-process.js +20 -55
  41. package/dist/worker/console/chat/user-questions.js +266 -0
  42. package/dist/worker/console/chat/workspace-landing.js +79 -0
  43. package/dist/worker/console/console-update-runtime.js +1 -1
  44. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  45. package/dist/worker/console/dag-confirmation.js +1 -1
  46. package/dist/worker/console/dag-execution-receipt.js +1 -1
  47. package/dist/worker/console/doctor.js +1 -1
  48. package/dist/worker/console/draft-store.js +1 -1
  49. package/dist/worker/console/human-gate-token.js +1 -1
  50. package/dist/worker/console/index.js +3 -3
  51. package/dist/worker/console/interview/assessment.js +1 -1
  52. package/dist/worker/console/interview/session.js +1 -1
  53. package/dist/worker/console/native-directory-picker.js +13 -0
  54. package/dist/worker/console/operation-runner.js +45 -4
  55. package/dist/worker/console/operation-sse.js +1 -1
  56. package/dist/worker/console/operation-wait.js +1 -1
  57. package/dist/worker/console/operator-actions.js +104 -3
  58. package/dist/worker/console/operator-surface-health.js +1 -1
  59. package/dist/worker/console/operator-user-error.js +128 -0
  60. package/dist/worker/console/routes.js +927 -8
  61. package/dist/worker/console/security.js +39 -4
  62. package/dist/worker/console/server.js +532 -187
  63. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-CpWvwARc.js} +1 -1
  64. package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-DctXgO-g.js} +1 -1
  65. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-D4mIkoQl.js} +1 -1
  66. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-ClG0--KO.js} +1 -1
  67. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-CwdLk78T.js} +1 -1
  68. package/dist/worker/console/static/assets/channel-C9VQZ-ru.js +1 -0
  69. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-DKczcu-O.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-BV6F-vBI.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-DtojVnvA.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-BjDQRGnO.js} +1 -1
  73. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-I0fV6ngU.js} +1 -1
  74. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-BM8C-mmC.js} +1 -1
  75. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-BEK-a15e.js} +1 -1
  76. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-CQsg_fSR.js} +1 -1
  77. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-B-BcaUm3.js} +1 -1
  78. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-D1Bu08fg.js} +1 -1
  79. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BsCkw5MF.js +1 -0
  80. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BsCkw5MF.js +1 -0
  81. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-q8nwtxBR.js} +1 -1
  82. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-CruhpURw.js} +1 -1
  83. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-CJne6iYF.js} +1 -1
  84. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-CbeLvBU5.js} +1 -1
  85. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-DmCxH1Pe.js} +1 -1
  86. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-DJ5OYhtC.js} +1 -1
  87. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-B-UN-hln.js} +1 -1
  88. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-CcEro8Ey.js} +1 -1
  89. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-BBBzDden.js} +1 -1
  90. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-xKEyH-oo.js} +1 -1
  91. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-C7xl63VC.js} +1 -1
  92. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-nthQQ9nR.js} +1 -1
  93. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-CWN5mE33.js} +1 -1
  94. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-Wn86tQ6N.js} +1 -1
  95. package/dist/worker/console/static/assets/index-D-0NVyMk.css +1 -0
  96. package/dist/worker/console/static/assets/index-gwyOxQiS.js +407 -0
  97. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-_WQymGtW.js} +1 -1
  98. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-COBeivBm.js} +1 -1
  99. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-CwGOJl3Q.js} +1 -1
  100. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-Ci0CJ5Vi.js} +1 -1
  101. package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-CPjMq6vN.js} +1 -1
  102. package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-Z0wGoe0b.js} +5 -5
  103. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-dISked0d.js} +1 -1
  104. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-BJxgof7Y.js} +1 -1
  105. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-CIblU8eW.js} +1 -1
  106. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-vwYiqh8E.js} +1 -1
  107. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-_iOsVeFm.js} +1 -1
  108. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-Dk0MLxO_.js} +1 -1
  109. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-Dx7HtLVW.js} +1 -1
  110. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-DRuWjlpL.js} +1 -1
  111. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-Bhy9DQAA.js} +1 -1
  112. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-DAx9twnU.js} +1 -1
  113. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dmdf8cGy.js +1 -0
  114. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-okMlvVrM.js} +2 -2
  115. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-W1IcAFff.js +8 -0
  116. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-DXqCKe37.js} +1 -1
  117. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-CeIUkwBh.js} +1 -1
  118. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-BP76BKTN.js} +1 -1
  119. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-BpVI0OpF.js} +1 -1
  120. package/dist/worker/console/static/index.html +7 -2
  121. package/dist/worker/console/static-src/app/console-types.js +29 -4
  122. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  123. package/dist/worker/console/static-src/app/useOperatorActions.js +36 -6
  124. package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
  125. package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
  126. package/dist/worker/console/static-src/app/useTaskWizard.js +2 -2
  127. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  128. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  129. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  130. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  131. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  132. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  133. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  134. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  135. package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
  136. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  137. package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
  138. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  139. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  140. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  141. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  142. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  143. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  144. package/dist/worker/console/workspace-context.js +260 -0
  145. package/dist/worker/console/workspace-registry.js +214 -0
  146. package/dist/worker/continuation/worker-continuation.js +278 -0
  147. package/dist/worker/observe/health.js +1 -1
  148. package/dist/worker/observe/node-transparency.js +572 -0
  149. package/dist/worker/observe/routes.js +51 -1
  150. package/dist/worker/observe/server.js +4 -0
  151. package/dist/worker/observe/static/api.js +94 -10
  152. package/dist/worker/observe/static/app.js +18 -70
  153. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  154. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  155. package/dist/worker/observe/static/index.html +1 -1
  156. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  157. package/dist/worker/observe/static/operator-chrome.css +145 -20
  158. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  159. package/dist/worker/observe/static/operator-chrome.js +330 -100
  160. package/dist/worker/observe/static/router.d.ts +46 -0
  161. package/dist/worker/observe/static/router.js +25 -1
  162. package/dist/worker/observe/static/state.d.ts +50 -0
  163. package/dist/worker/observe/static/state.js +175 -1
  164. package/dist/worker/observe/static/styles.css +1039 -128
  165. package/dist/worker/observe/static/views/dag-inspector.js +1218 -60
  166. package/dist/worker/observe/static/views/dag.js +2 -1
  167. package/dist/worker/observe/static/views/dashboard.js +6 -4
  168. package/dist/worker/observe/static/views/night.js +2 -2
  169. package/dist/worker/observe/static/views/pool.js +1 -0
  170. package/dist/worker/observe/static/views/run.js +3 -1
  171. package/dist/worker/observe/static/views/session-timeline.js +75 -10
  172. package/dist/worker/observe/static/views/task.js +1 -1
  173. package/dist/worker/pool/attempt-lease.js +97 -66
  174. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  175. package/dist/worker/pool/reconcile.js +46 -1
  176. package/dist/worker/pool/run-store.js +2 -0
  177. package/dist/worker/pool/state-projection.js +2 -0
  178. package/dist/workflows/dag/artifact-bindings.js +149 -0
  179. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  180. package/dist/workflows/dag/backend-test-pytest-collection.js +11 -10
  181. package/dist/workflows/dag/backend-test-writer-completeness.js +7 -12
  182. package/dist/workflows/dag/context-receipt.js +305 -0
  183. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  184. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  185. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  186. package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
  187. package/dist/workflows/dag/init-hybrid.js +178 -47
  188. package/dist/workflows/dag/interrupt-request.js +3 -3
  189. package/dist/workflows/dag/lifecycle.js +3 -3
  190. package/dist/workflows/dag/node-execution.js +112 -27
  191. package/dist/workflows/dag/path-safety.js +1 -0
  192. package/dist/workflows/dag/prompt.js +29 -4
  193. package/dist/workflows/dag/rerun-plan.js +272 -0
  194. package/dist/workflows/dag/rerun-run.js +390 -12
  195. package/dist/workflows/dag/skill-snapshot.js +1 -0
  196. package/dist/workflows/dag/types.js +203 -8
  197. package/docs/README.md +1 -0
  198. package/docs/architecture/runtime-boundaries.md +6 -6
  199. package/docs/operations/local-development-environment.md +21 -0
  200. package/docs/templates/agent-dag.schema.json +50 -0
  201. package/docs/templates/backend-test-dag.json +265 -237
  202. package/harness.json +3 -3
  203. package/package.json +5 -1
  204. package/skills/loop-agent/references/command-reference.md +6 -0
  205. package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
  206. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
  207. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
  208. package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
  209. package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
  210. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
  211. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
  212. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  213. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
  214. /package/dist/{worker → infrastructure}/console/operation-store.js +0 -0
  215. /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
+ }
@@ -2244,6 +2244,20 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
2244
2244
  action: "dagFinalVerification",
2245
2245
  source: "loop-agent",
2246
2246
  },
2247
+ {
2248
+ command: "loop-agent dag context",
2249
+ coverage: "excluded",
2250
+ action: null,
2251
+ source: "loop-agent",
2252
+ 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.",
2253
+ },
2254
+ {
2255
+ command: "loop-agent dag artifact",
2256
+ coverage: "excluded",
2257
+ action: null,
2258
+ source: "loop-agent",
2259
+ exclusionReason: "Artifact revision staging consumes a human-selected local file and remains on dedicated Console/CLI mutation gates.",
2260
+ },
2247
2261
  {
2248
2262
  command: "loop-agent workflow list",
2249
2263
  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
+ }
@@ -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)
@@ -147,6 +147,8 @@ export const backendTestConfigSchema = z
147
147
  gapDoc: z.string().min(1).optional(),
148
148
  /** gap-fill pytest execution scope: only affected items (default) or all eligible. */
149
149
  executeScope: z.enum(["affected", "all"]).optional(),
150
+ /** Backend-test-only workspace mutation control; omitted means Git guard. */
151
+ workspaceControl: z.enum(["git", "filesystem-only"]).optional(),
150
152
  })
151
153
  .strict()
152
154
  .optional();
@@ -237,5 +239,13 @@ export const taskConfigSchema = z.preprocess((raw) => {
237
239
  if ("executor" in obj || "cursorModel" in obj) {
238
240
  throw new Error(CURSOR_TASK_FIELD_REMOVED_ERROR);
239
241
  }
242
+ const backendTest = obj.backendTest;
243
+ if (backendTest &&
244
+ typeof backendTest === "object" &&
245
+ !Array.isArray(backendTest) &&
246
+ "workspaceControl" in backendTest &&
247
+ obj.taskKind !== "backend-test") {
248
+ throw new Error("backendTest.workspaceControl is only valid when taskKind is backend-test");
249
+ }
240
250
  return raw;
241
251
  }, taskConfigObjectSchema);