@tea-agent/loop-agent 0.40.0 → 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 (165) hide show
  1. package/CHANGELOG.md +140 -13
  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 +39 -12
  11. package/dist/executors/pi-sdk-executor.js +3 -0
  12. package/dist/executors/shell-executor.js +3 -30
  13. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  14. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  15. package/dist/infrastructure/console/context-export-store.js +160 -0
  16. package/dist/infrastructure/console/dir-lock.js +132 -0
  17. package/dist/{worker → infrastructure}/console/operation-store.js +28 -0
  18. package/dist/infrastructure/harness/artifact-store.js +10 -1
  19. package/dist/infrastructure/harness/atomic-write.js +12 -2
  20. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  21. package/dist/shared/context-transfer.js +418 -0
  22. package/dist/shared/operator/capabilities.js +14 -0
  23. package/dist/shared/operator/safe-run-summary.js +1 -0
  24. package/dist/shared/path-safety.js +93 -0
  25. package/dist/shared/preview.js +28 -4
  26. package/dist/task/config-types.js +2 -1
  27. package/dist/task/task-demand-routing.js +3 -0
  28. package/dist/worker/console/chat/chat-event-store.js +2 -2
  29. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  30. package/dist/worker/console/chat/routes.js +3 -11
  31. package/dist/worker/console/chat/session-store.js +1 -1
  32. package/dist/worker/console/chat/turn-process.js +2 -10
  33. package/dist/worker/console/chat/user-questions.js +1 -1
  34. package/dist/worker/console/console-update-runtime.js +1 -1
  35. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  36. package/dist/worker/console/dag-confirmation.js +1 -1
  37. package/dist/worker/console/dag-execution-receipt.js +1 -1
  38. package/dist/worker/console/doctor.js +1 -1
  39. package/dist/worker/console/draft-store.js +1 -1
  40. package/dist/worker/console/human-gate-token.js +1 -1
  41. package/dist/worker/console/index.js +3 -3
  42. package/dist/worker/console/interview/assessment.js +1 -1
  43. package/dist/worker/console/interview/session.js +1 -1
  44. package/dist/worker/console/operation-runner.js +45 -4
  45. package/dist/worker/console/operation-sse.js +1 -1
  46. package/dist/worker/console/operation-wait.js +1 -1
  47. package/dist/worker/console/operator-actions.js +104 -3
  48. package/dist/worker/console/operator-surface-health.js +1 -1
  49. package/dist/worker/console/operator-user-error.js +169 -0
  50. package/dist/worker/console/routes.js +618 -7
  51. package/dist/worker/console/security.js +35 -0
  52. package/dist/worker/console/server.js +4 -2
  53. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Dy71QJp3.js → abnfDiagram-N423BO3Z-DSVcg1YO.js} +1 -1
  54. package/dist/worker/console/static/assets/{arc-M25fNMTE.js → arc-BtXhIqui.js} +1 -1
  55. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CUWlVl1a.js → architectureDiagram-T3A2C74G-B-nh-ilJ.js} +1 -1
  56. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Dd0Whpx2.js → blockDiagram-VBNYF7ZC-BIUSVdZI.js} +1 -1
  57. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BdfeCkI5.js → c4Diagram-5PPSVZJV-Cmw3L4qM.js} +1 -1
  58. package/dist/worker/console/static/assets/channel-BhZFNefx.js +1 -0
  59. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D_1sUkyD.js → chunk-2GRJ4B5K-CGpMMjGm.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-Cxnxf36E.js → chunk-2Q5K7J3B-BNBkEpEJ.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-D9RG3G_x.js → chunk-5RXB4S5H-ubln3eaU.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-Zz9jgjLX.js → chunk-5VM5RSS4-_XcBTt1i.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BCggDRCH.js → chunk-6Q2QTUOP-Ln30Pgrh.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CFg-5iAS.js → chunk-GF5L2VYU-ESH3lhSx.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DRQTE8x_.js → chunk-JWPE2WC7-DAJt-qXB.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-C_Dw7uZ8.js → chunk-KBJHAD2P-Cx10LIcH.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BjIiApsk.js → chunk-RYQCIY6F-CENeOxoJ.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-B5XQD4KG.js → chunk-XXDRQBXY-DXqK-D38.js} +1 -1
  69. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Sbv-NJni.js +1 -0
  70. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Sbv-NJni.js +1 -0
  71. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-huHqH6WQ.js → cose-bilkent-JH36ORCC-CE4dmvGY.js} +1 -1
  72. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BSi6yZqP.js → cynefin-VYW2F7L2-CRacN1zK.js} +1 -1
  73. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bs_Jp31y.js → cynefinDiagram-MW4NZA55-Cneukjrt.js} +1 -1
  74. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CYxavikT.js → dagre-VZM6K2ZE-DPFboRoL.js} +1 -1
  75. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B6TErl71.js → diagram-7IWD3JNH-DIAQgcOP.js} +1 -1
  76. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CvH4J2ii.js → diagram-B4RE2ZJO-VrYMKxuy.js} +1 -1
  77. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-v1NBAqXS.js → diagram-LBJQPF4R-CLHvBghn.js} +1 -1
  78. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CUNHVAn2.js → diagram-Q27KOJAE-DVCWBif8.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzz1gUu-.js → diagram-UB23O5K3-BTMmbBzJ.js} +1 -1
  80. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CCF8_LjK.js → ebnfDiagram-BXEA7PRR-BUZsKJ56.js} +1 -1
  81. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DObdPVLa.js → erDiagram-JOGREHBK-BsBZ5NXC.js} +1 -1
  82. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DlN8Pxrj.js → flowDiagram-UKHOOZJN-D7fH_kDa.js} +1 -1
  83. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BJKIPTGX.js → ganttDiagram-PKOTCBZU-Bza5OohY.js} +1 -1
  84. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-XcSSnObi.js → gitGraphDiagram-DS77QQ5N-DpkoGR_Y.js} +1 -1
  85. package/dist/worker/console/static/assets/index-Ius3vxhf.css +1 -0
  86. package/dist/worker/console/static/assets/index-yASzcbBr.js +451 -0
  87. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BKrRKMww.js → infoDiagram-6WML65LV-BYm3g6LT.js} +1 -1
  88. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CN5dk-yb.js → ishikawaDiagram-WSZJBQD7-C2RS_H4v.js} +1 -1
  89. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-B4P4KZ-Y.js → journeyDiagram-NVQOT4AX-B4slKpCo.js} +1 -1
  90. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BRlteP32.js → kanban-definition-27J2QSJJ-B7orRNpI.js} +1 -1
  91. package/dist/worker/console/static/assets/{linear-CG8D9fuL.js → linear-D1BeBxLQ.js} +1 -1
  92. package/dist/worker/console/static/assets/{mermaid.core-BuB6P2rX.js → mermaid.core-0TtMP8k4.js} +5 -5
  93. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DYXGl_DR.js → mindmap-definition-FAOFIHXS-XL5EbCtT.js} +1 -1
  94. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BM48yEVG.js → pegDiagram-VL7TDLO6-CwF7uas5.js} +1 -1
  95. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-mT8NL-Ow.js → pieDiagram-7S7Q4E2Y-A_Jo-72f.js} +1 -1
  96. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C4pZ6DH_.js → quadrantDiagram-CIZ2JOQS-CxOa4gKL.js} +1 -1
  97. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BKAcr70m.js → railroadDiagram-AXF67PYL-DoAoswIH.js} +1 -1
  98. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CRxcKpoO.js → requirementDiagram-LRYGKXZP-C1Lc1MlD.js} +1 -1
  99. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Ds4LxQDI.js → sankeyDiagram-W5VNT64P-DFXOkq0U.js} +1 -1
  100. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CPLwPf_p.js → sequenceDiagram-SI44F4Z6-B0ufUCuW.js} +1 -1
  101. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CL65uij0.js → sizeCapture-X5ZJPWSS-iPbW55g3.js} +1 -1
  102. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CO0Mgd1W.js → stateDiagram-OKZ733FA-CHiXzEKP.js} +1 -1
  103. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BtMgzo_H.js +1 -0
  104. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Cxv3TSNw.js → swimlanes-SLNWSIFB-LyFs7TpX.js} +2 -2
  105. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cb-OVd9Y.js +8 -0
  106. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Dw85f9L2.js → timeline-definition-Z64GVDOM-tNkJnb6z.js} +1 -1
  107. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B4QLCqCy.js → vennDiagram-T6HMQDX7-DFm7Oo8o.js} +1 -1
  108. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-B_yKCWwn.js → wardleyDiagram-T6FBY63Y-tpblTp9i.js} +1 -1
  109. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-nniBEGv2.js → xychartDiagram-ELKLHX3M-BCDLWR_D.js} +1 -1
  110. package/dist/worker/console/static/index.html +7 -2
  111. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  112. package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
  113. package/dist/worker/console/workspace-context.js +28 -2
  114. package/dist/worker/console/workspace-registry.js +2 -2
  115. package/dist/worker/continuation/worker-continuation.js +278 -0
  116. package/dist/worker/observe/health.js +1 -1
  117. package/dist/worker/observe/node-transparency.js +572 -0
  118. package/dist/worker/observe/routes.js +51 -1
  119. package/dist/worker/observe/static/api.js +69 -5
  120. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  121. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  122. package/dist/worker/observe/static/state.js +3 -1
  123. package/dist/worker/observe/static/styles.css +1037 -126
  124. package/dist/worker/observe/static/views/dag-inspector.js +1215 -58
  125. package/dist/worker/observe/static/views/session-timeline.js +75 -10
  126. package/dist/worker/outcomes/adapters.js +19 -0
  127. package/dist/worker/outcomes/types.js +1 -0
  128. package/dist/worker/pool/attempt-lease.js +97 -66
  129. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  130. package/dist/worker/pool/reconcile.js +46 -1
  131. package/dist/worker/pool/run-store.js +2 -0
  132. package/dist/worker/pool/state-projection.js +2 -0
  133. package/dist/worker/task-spec/workflow-routing.js +8 -3
  134. package/dist/workflows/dag/artifact-bindings.js +149 -0
  135. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  136. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  137. package/dist/workflows/dag/budget-enforcement.js +10 -0
  138. package/dist/workflows/dag/context-receipt.js +305 -0
  139. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  140. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  141. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  142. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  143. package/dist/workflows/dag/init-hybrid.js +146 -0
  144. package/dist/workflows/dag/node-execution.js +112 -27
  145. package/dist/workflows/dag/path-safety.js +1 -0
  146. package/dist/workflows/dag/prompt.js +29 -4
  147. package/dist/workflows/dag/rerun-plan.js +555 -1
  148. package/dist/workflows/dag/rerun-run.js +601 -15
  149. package/dist/workflows/dag/runner.js +20 -1
  150. package/dist/workflows/dag/skill-snapshot.js +17 -0
  151. package/dist/workflows/dag/types.js +183 -7
  152. package/docs/architecture/runtime-boundaries.md +6 -6
  153. package/docs/architecture/worker-and-feature.md +1 -1
  154. package/docs/templates/agent-dag.schema.json +32 -0
  155. package/docs/templates/product-line/task.yaml +1 -1
  156. package/package.json +3 -1
  157. package/skills/loop-agent/references/command-reference.md +6 -0
  158. package/dist/worker/console/static/assets/channel-Ba0AKiEZ.js +0 -1
  159. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BU3IjVEe.js +0 -1
  160. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BU3IjVEe.js +0 -1
  161. package/dist/worker/console/static/assets/index-CIYzS16i.css +0 -1
  162. package/dist/worker/console/static/assets/index-CS3QOTfl.js +0 -451
  163. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BUewAVYS.js +0 -1
  164. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bhe67Wjl.js +0 -8
  165. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -1,9 +1,202 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
+ import { z } from "zod";
3
4
  import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
4
5
  import { isSafeReadOnlyPiRetryCandidate, isTransportCrashedExclusiveWriter } from "./retry-policy.js";
5
6
  import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
6
7
  import { composeDagPromptOverridePrompt, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
8
+ import { artifactOverrideSetV1Schema, workerContinuationAuthorityV1Schema, } from "../../shared/context-transfer.js";
9
+ import { buildArtifactBindingGraph, computeArtifactImpact, } from "./artifact-bindings.js";
10
+ import { assertValidMaterializedDagTask } from "./validate.js";
11
+ const rerunBindingsV1Schema = z
12
+ .object({
13
+ sourceBindingMatched: z.boolean(),
14
+ taskContractBindingMatched: z.boolean(),
15
+ controllerIdentityMatched: z.boolean(),
16
+ skillSnapshotVerified: z.boolean(),
17
+ workspaceMatched: z.boolean(),
18
+ })
19
+ .strict();
20
+ const hash64Schema = z.string().regex(/^[a-f0-9]{64}$/);
21
+ const highRiskContinuationAuthorityV1Schema = z
22
+ .object({
23
+ schemaVersion: z.literal(1),
24
+ workspaceFingerprint: hash64Schema,
25
+ writer: z
26
+ .object({
27
+ nodeIds: z.array(z.string().min(1)).min(1),
28
+ writeSet: z.array(z.string().min(1)).min(1),
29
+ writeSetSha256: hash64Schema,
30
+ })
31
+ .strict()
32
+ .optional(),
33
+ dynamic: z
34
+ .object({
35
+ nodeIds: z.array(z.string().min(1)).min(1),
36
+ graphSha256: hash64Schema,
37
+ })
38
+ .strict()
39
+ .optional(),
40
+ budget: z
41
+ .object({
42
+ parentLedgerSha256: hash64Schema,
43
+ inheritedConsumed: z
44
+ .object({
45
+ tokens: z.number().int().nonnegative().nullable(),
46
+ wallTimeMs: z.number().nonnegative(),
47
+ executorCalls: z.number().int().nonnegative(),
48
+ repairPasses: z.number().int().nonnegative(),
49
+ peakContextChars: z.number().int().nonnegative(),
50
+ missingTokenNodeIds: z.array(z.string()),
51
+ })
52
+ .strict(),
53
+ })
54
+ .strict()
55
+ .optional(),
56
+ })
57
+ .strict();
58
+ const rerunPlanCommonSchema = {
59
+ eligible: z.boolean(),
60
+ parentRunId: z.string().min(1),
61
+ parentLifecycle: z.enum(["completed", "dry-run", "init-only"]),
62
+ selectedNodeId: z.string().min(1),
63
+ effectiveFromNodeId: z.string().min(1).optional(),
64
+ rewriteApplied: z.boolean(),
65
+ resetNodeIds: z.array(z.string().min(1)),
66
+ importedNodeIds: z.array(z.string().min(1)),
67
+ blockingNodes: z.array(z.object({ nodeId: z.string().min(1), reasonCode: z.string().min(1) }).strict()),
68
+ reasonCodes: z.array(z.string().min(1)),
69
+ blockedReasons: z.array(z.string().min(1)),
70
+ risk: z.enum(["low", "medium", "high"]),
71
+ verificationPolicy: z
72
+ .object({
73
+ mode: z.enum(["inherited", "rerun"]),
74
+ nodeIds: z.array(z.string().min(1)),
75
+ inheritedFromRunId: z.string().min(1).optional(),
76
+ })
77
+ .strict()
78
+ .optional(),
79
+ workerAssociation: z.discriminatedUnion("kind", [
80
+ z.object({ kind: z.literal("standalone") }).strict(),
81
+ z
82
+ .object({
83
+ kind: z.literal("worker-managed"),
84
+ featureId: z.string().min(1).optional(),
85
+ taskId: z.string().min(1).optional(),
86
+ workerRunId: z.string().min(1).optional(),
87
+ })
88
+ .strict(),
89
+ ]),
90
+ estimatedExecution: z
91
+ .object({
92
+ nodeCount: z.number().int().nonnegative(),
93
+ piCallCount: z.number().int().nonnegative(),
94
+ shellNodeCount: z.number().int().nonnegative(),
95
+ avoidedNodeCount: z.number().int().nonnegative(),
96
+ avoidedPiCallCount: z.number().int().nonnegative(),
97
+ })
98
+ .strict(),
99
+ suggestedAction: z.enum([
100
+ "dag-rerun",
101
+ "standalone-task-rerun",
102
+ "worker-task-retry",
103
+ "regenerate",
104
+ "resume",
105
+ "reconcile",
106
+ "manual",
107
+ ]),
108
+ promptOverrides: z
109
+ .array(z
110
+ .object({
111
+ nodeId: z.string().min(1),
112
+ mode: z.enum(["replace", "append"]),
113
+ basePromptSha256: z.string().regex(/^[a-f0-9]{64}$/),
114
+ promptSha256: z.string().regex(/^[a-f0-9]{64}$/),
115
+ })
116
+ .strict())
117
+ .optional(),
118
+ workerContinuationAuthority: workerContinuationAuthorityV1Schema.optional(),
119
+ highRiskAuthority: highRiskContinuationAuthorityV1Schema.optional(),
120
+ };
121
+ export const dagRerunPlanV2Schema = z
122
+ .object({
123
+ schemaVersion: z.literal(2),
124
+ ...rerunPlanCommonSchema,
125
+ trigger: z.enum(["node-rerun", "prompt-override", "artifact-override"]),
126
+ artifactOverrides: artifactOverrideSetV1Schema.optional(),
127
+ impact: z
128
+ .object({
129
+ producerNodeId: z.string().min(1).optional(),
130
+ directConsumerNodeIds: z.array(z.string().min(1)),
131
+ effectiveFromNodeId: z.string().min(1),
132
+ affectedNodeIds: z.array(z.string().min(1)),
133
+ importedNodeIds: z.array(z.string().min(1)),
134
+ blockedNodeIds: z.array(z
135
+ .object({ nodeId: z.string().min(1), reasonCode: z.string().min(1) })
136
+ .strict()),
137
+ })
138
+ .strict(),
139
+ bindings: rerunBindingsV1Schema
140
+ .extend({
141
+ artifactGraphSha256: hash64Schema,
142
+ overrideSetHash: hash64Schema.optional(),
143
+ stagingRevisionSha256: hash64Schema.optional(),
144
+ parentWorkspaceFingerprint: hash64Schema.optional(),
145
+ currentWorkspaceFingerprint: hash64Schema.optional(),
146
+ budgetLedgerSha256: hash64Schema.optional(),
147
+ })
148
+ .strict(),
149
+ planHash: z.string().regex(/^[a-f0-9]{64}$/),
150
+ })
151
+ .strict()
152
+ .superRefine((plan, ctx) => {
153
+ const artifactTrigger = plan.trigger === "artifact-override";
154
+ if (artifactTrigger !== Boolean(plan.artifactOverrides)) {
155
+ ctx.addIssue({
156
+ code: z.ZodIssueCode.custom,
157
+ path: ["artifactOverrides"],
158
+ message: "artifact override trigger and override set must appear together",
159
+ });
160
+ }
161
+ if (plan.artifactOverrides &&
162
+ (plan.bindings.overrideSetHash !== plan.artifactOverrides.overrideSetHash ||
163
+ !plan.bindings.stagingRevisionSha256)) {
164
+ ctx.addIssue({
165
+ code: z.ZodIssueCode.custom,
166
+ path: ["bindings"],
167
+ message: "artifact override bindings must match the override set and revision",
168
+ });
169
+ }
170
+ if (plan.workerContinuationAuthority &&
171
+ plan.workerAssociation.kind !== "worker-managed") {
172
+ ctx.addIssue({
173
+ code: z.ZodIssueCode.custom,
174
+ path: ["workerContinuationAuthority"],
175
+ message: "Worker continuation authority requires a Worker-managed parent",
176
+ });
177
+ }
178
+ if (plan.highRiskAuthority) {
179
+ if (plan.bindings.parentWorkspaceFingerprint !==
180
+ plan.highRiskAuthority.workspaceFingerprint ||
181
+ plan.bindings.currentWorkspaceFingerprint !==
182
+ plan.highRiskAuthority.workspaceFingerprint) {
183
+ ctx.addIssue({
184
+ code: z.ZodIssueCode.custom,
185
+ path: ["highRiskAuthority", "workspaceFingerprint"],
186
+ message: "high-risk authority must bind the matching workspace fingerprints",
187
+ });
188
+ }
189
+ if (plan.highRiskAuthority.budget &&
190
+ plan.bindings.budgetLedgerSha256 !==
191
+ plan.highRiskAuthority.budget.parentLedgerSha256) {
192
+ ctx.addIssue({
193
+ code: z.ZodIssueCode.custom,
194
+ path: ["bindings", "budgetLedgerSha256"],
195
+ message: "budget ledger binding must match high-risk authority",
196
+ });
197
+ }
198
+ }
199
+ });
7
200
  const REVIEW_VERDICT_RECOVERY_NODE_ID = "review-verdict-recovery-pi";
8
201
  const SAFE_SHELL_GATE_KEYS = [
9
202
  "verdictGate",
@@ -117,6 +310,68 @@ function isWriterTask(task, spec) {
117
310
  const writePolicy = resolveWritePolicy(task, spec);
118
311
  return writePolicy === "exclusive" || writePolicy === "docs-only";
119
312
  }
313
+ function passesWriterBoundaryValidation(task, parentSpec) {
314
+ try {
315
+ assertValidMaterializedDagTask(task, parentSpec);
316
+ return true;
317
+ }
318
+ catch {
319
+ return false;
320
+ }
321
+ }
322
+ function isBoundedArtifactContinuationWriter(task, parentSpec) {
323
+ return (task.executor === "pi" &&
324
+ task.toolProfile === "write" &&
325
+ task.writePolicy === "exclusive" &&
326
+ (task.writeSet?.length ?? 0) > 0 &&
327
+ (task.allowedPaths?.length ?? 0) > 0 &&
328
+ (task.forbiddenPaths?.length ?? 0) > 0 &&
329
+ passesWriterBoundaryValidation(task, parentSpec));
330
+ }
331
+ function isDynamicWriterTemplate(task) {
332
+ return (task.toolProfile === "write" ||
333
+ task.writePolicy === "exclusive" ||
334
+ task.writePolicy === "docs-only");
335
+ }
336
+ function isBoundedDynamicWriterTemplate(task, parentSpec) {
337
+ if (!(task.executor === "pi" &&
338
+ task.toolProfile === "write" &&
339
+ task.writePolicy === "exclusive" &&
340
+ (task.writeSet?.length ?? 0) > 0 &&
341
+ (task.allowedPaths?.length ?? 0) > 0 &&
342
+ (task.forbiddenPaths?.length ?? 0) > 0))
343
+ return false;
344
+ return passesWriterBoundaryValidation({
345
+ id: "continuation-dynamic-writer-template",
346
+ depends_on: [],
347
+ executor: task.executor,
348
+ role: task.role,
349
+ complexity: task.complexity,
350
+ subtask_prompt: task.subtaskPromptTemplate,
351
+ toolProfile: task.toolProfile,
352
+ writePolicy: task.writePolicy,
353
+ allowedPaths: task.allowedPaths,
354
+ forbiddenPaths: task.forbiddenPaths,
355
+ writeSet: task.writeSet,
356
+ outputContract: task.outputContract,
357
+ }, parentSpec);
358
+ }
359
+ function collectDynamicWriterTemplates(task) {
360
+ const writers = [];
361
+ const expansionChild = task.dynamicExpansion?.childTask;
362
+ if (expansionChild && isDynamicWriterTemplate(expansionChild)) {
363
+ writers.push({ id: `${task.id}#child`, task: expansionChild });
364
+ }
365
+ for (const bodyTask of task.dynamicLoopUntil?.bodyTasks ?? []) {
366
+ if (isDynamicWriterTemplate(bodyTask)) {
367
+ writers.push({ id: `${task.id}#${bodyTask.id}`, task: bodyTask });
368
+ }
369
+ }
370
+ return writers;
371
+ }
372
+ function hasOnlyBoundedDynamicWriters(task, parentSpec) {
373
+ return collectDynamicWriterTemplates(task).every(({ task: writer }) => isBoundedDynamicWriterTemplate(writer, parentSpec));
374
+ }
120
375
  function hasDynamicRuntime(task) {
121
376
  return Boolean(task.dynamicExpansion ||
122
377
  task.dynamicReduction ||
@@ -174,6 +429,34 @@ function isCanonicalGeneratedVerifyShell(task) {
174
429
  return false;
175
430
  return true;
176
431
  }
432
+ /**
433
+ * Frozen frontend workflow shells are deterministic runtime adapters. They may
434
+ * materialize run-owned evidence or run the already-frozen verification
435
+ * commands, but they do not receive a generic shell-continuation exemption.
436
+ */
437
+ function isCanonicalFrontendArtifactContinuationShell(task) {
438
+ if (task.executor !== "shell" || task.writePolicy !== "read-only" || !task.shell) {
439
+ return false;
440
+ }
441
+ const shell = task.shell;
442
+ if (shell.envAllowlist?.length || shell.nonZeroExitPolicy === "record")
443
+ return false;
444
+ switch (task.id) {
445
+ case "frontend-prewrite-gate-shell":
446
+ return Boolean(shell.frontendPrewriteGate) && (shell.commands?.length ?? 0) === 0;
447
+ case "frontend-lint-baseline-shell":
448
+ return Boolean(shell.frontendLintBaseline &&
449
+ shell.commands?.length === shell.frontendLintBaseline.lintCommands.length &&
450
+ shell.commands.every((command, index) => command === shell.frontendLintBaseline.lintCommands[index]));
451
+ case "frontend-verify-assess-shell":
452
+ case "frontend-reverify-shell":
453
+ return Boolean(shell.frontendVerificationBundle) && (shell.commands?.length ?? 0) === 0;
454
+ case "frontend-review-context-shell":
455
+ return Boolean(shell.frontendReviewContext) && (shell.commands?.length ?? 0) === 0;
456
+ default:
457
+ return false;
458
+ }
459
+ }
177
460
  function isSafeGateOnlyShell(task) {
178
461
  if (task.executor !== "shell")
179
462
  return false;
@@ -323,6 +606,10 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
323
606
  continue;
324
607
  }
325
608
  if (isWriterTask(task, spec)) {
609
+ if (options.artifactContinuation === true &&
610
+ isBoundedArtifactContinuationWriter(task, spec)) {
611
+ continue;
612
+ }
326
613
  if (nodeId === options.allowedTransportWriterNodeId)
327
614
  continue;
328
615
  if (nodeId === options.allowedExclusiveShellFromNodeId &&
@@ -345,6 +632,10 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
345
632
  continue;
346
633
  }
347
634
  if (hasDynamicRuntime(task)) {
635
+ if (options.artifactContinuation === true &&
636
+ hasOnlyBoundedDynamicWriters(task, spec)) {
637
+ continue;
638
+ }
348
639
  blockingNodes.push({
349
640
  nodeId,
350
641
  reasonCode: "dynamic-rerun-unsupported",
@@ -367,7 +658,9 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
367
658
  }
368
659
  if (task.executor === "shell") {
369
660
  if (!isSafeGateOnlyShell(task) &&
370
- !isCanonicalGeneratedVerifyShell(task)) {
661
+ !isCanonicalGeneratedVerifyShell(task) &&
662
+ !(options.artifactContinuation === true &&
663
+ isCanonicalFrontendArtifactContinuationShell(task))) {
371
664
  blockingNodes.push({
372
665
  nodeId,
373
666
  reasonCode: "restart-subgraph-contains-unsafe-shell",
@@ -480,6 +773,267 @@ export function computePlanHash(planWithoutHash) {
480
773
  const canonical = JSON.stringify(sortKeysDeep(planWithoutHash));
481
774
  return createHash("sha256").update(canonical, "utf8").digest("hex");
482
775
  }
776
+ function hashCanonicalValue(value) {
777
+ return createHash("sha256")
778
+ .update(JSON.stringify(sortKeysDeep(value)), "utf8")
779
+ .digest("hex");
780
+ }
781
+ function collectHighRiskContinuationAuthority(input) {
782
+ const tasks = taskById(input.parentSpec);
783
+ const writerNodeIds = [];
784
+ const writeSet = new Set();
785
+ const dynamicNodeIds = [];
786
+ for (const nodeId of input.affectedNodeIds) {
787
+ const task = tasks.get(nodeId);
788
+ if (!task)
789
+ continue;
790
+ if (isBoundedArtifactContinuationWriter(task, input.parentSpec)) {
791
+ writerNodeIds.push(nodeId);
792
+ for (const pattern of task.writeSet ?? [])
793
+ writeSet.add(pattern);
794
+ }
795
+ if (hasDynamicRuntime(task)) {
796
+ dynamicNodeIds.push(nodeId);
797
+ for (const writer of collectDynamicWriterTemplates(task)) {
798
+ writerNodeIds.push(writer.id);
799
+ for (const pattern of writer.task.writeSet ?? [])
800
+ writeSet.add(pattern);
801
+ }
802
+ }
803
+ }
804
+ const ledger = input.parentState.budgetLedger;
805
+ const hasHardBudget = (input.parentSpec.budget?.mode ?? input.parentState.budget?.mode) === "hard";
806
+ const highRisk = writerNodeIds.length > 0 || dynamicNodeIds.length > 0 || hasHardBudget;
807
+ if (!highRisk || !input.workspaceFingerprint)
808
+ return undefined;
809
+ const normalizedWriters = [...new Set(writerNodeIds)].sort();
810
+ const normalizedWriteSet = [...writeSet].sort();
811
+ const normalizedDynamic = [...new Set(dynamicNodeIds)].sort();
812
+ return {
813
+ schemaVersion: 1,
814
+ workspaceFingerprint: input.workspaceFingerprint,
815
+ ...(normalizedWriters.length > 0
816
+ ? {
817
+ writer: {
818
+ nodeIds: normalizedWriters,
819
+ writeSet: normalizedWriteSet,
820
+ writeSetSha256: hashCanonicalValue(normalizedWriteSet),
821
+ },
822
+ }
823
+ : {}),
824
+ ...(normalizedDynamic.length > 0
825
+ ? {
826
+ dynamic: {
827
+ nodeIds: normalizedDynamic,
828
+ graphSha256: hashCanonicalValue(normalizedDynamic.map((nodeId) => tasks.get(nodeId))),
829
+ },
830
+ }
831
+ : {}),
832
+ ...(hasHardBudget && ledger
833
+ ? {
834
+ budget: {
835
+ parentLedgerSha256: hashCanonicalValue(ledger),
836
+ inheritedConsumed: structuredClone(ledger.consumed),
837
+ },
838
+ }
839
+ : {}),
840
+ };
841
+ }
842
+ export function evaluateArtifactOverrideRerunPlan(input) {
843
+ const graph = buildArtifactBindingGraph(input.parentSpec);
844
+ const declaration = graph.artifacts.find((artifact) => artifact.artifactId === input.artifactId);
845
+ const impact = computeArtifactImpact(input.parentSpec, input.artifactId);
846
+ const reasonCodes = [];
847
+ const blockedReasons = [];
848
+ const blockingNodes = [];
849
+ const workerAssociation = input.workerAssociation ?? { kind: "standalone" };
850
+ const block = (code, nodeId) => {
851
+ reasonCodes.push(code);
852
+ blockedReasons.push(code);
853
+ if (nodeId)
854
+ blockingNodes.push({ nodeId, reasonCode: code });
855
+ };
856
+ if (!graph.available)
857
+ block("ARTIFACT_BINDING_UNAVAILABLE");
858
+ if (!declaration)
859
+ block("ARTIFACT_IDENTITY_AMBIGUOUS");
860
+ if (!impact)
861
+ block("ARTIFACT_CONSUMER_UNKNOWN", declaration?.producerNodeId);
862
+ if (input.parentLifecycle !== "completed")
863
+ block("parent-lifecycle-ineligible");
864
+ const overrideRevisionId = input.artifactOverrides.overrides[0]?.revisionId;
865
+ const authority = input.workerContinuationAuthority;
866
+ if (workerAssociation.kind === "worker-managed") {
867
+ if (!authority) {
868
+ block("WORKER_MANAGED_CONTINUATION_UNSUPPORTED");
869
+ }
870
+ else if (!workerAssociation.workerRunId ||
871
+ !workerAssociation.featureId ||
872
+ !workerAssociation.taskId ||
873
+ authority.parentWorkerRunId !== workerAssociation.workerRunId ||
874
+ authority.featureId !== workerAssociation.featureId ||
875
+ authority.taskId !== workerAssociation.taskId ||
876
+ authority.revisionId !== overrideRevisionId) {
877
+ block("WORKER_CONTINUATION_AUTHORITY_MISMATCH");
878
+ }
879
+ }
880
+ else if (authority) {
881
+ block("WORKER_CONTINUATION_AUTHORITY_MISMATCH");
882
+ }
883
+ if (input.parentSpec.convergence?.enabled || input.parentState.convergence?.enabled) {
884
+ block("convergence-rerun-unsupported");
885
+ }
886
+ if (input.parentSpec.evaluation || input.parentState.evaluation) {
887
+ block("evaluation-rerun-unsupported");
888
+ }
889
+ const hardBudget = (input.parentSpec.budget?.mode ?? input.parentState.budget?.mode) === "hard";
890
+ if (hardBudget) {
891
+ const ledger = input.parentState.budgetLedger;
892
+ if (!ledger) {
893
+ block("budget-lineage-missing");
894
+ }
895
+ else if (ledger.status === "breached" || ledger.breaches.length > 0) {
896
+ block("budget-lineage-breached");
897
+ }
898
+ else if (ledger.mode !== "hard" ||
899
+ JSON.stringify(ledger.limits) !==
900
+ JSON.stringify(input.parentSpec.budget?.limits ?? input.parentState.budget?.limits)) {
901
+ block("budget-lineage-drift");
902
+ }
903
+ }
904
+ for (const [name, matched] of Object.entries(input.bindings)) {
905
+ if (!matched)
906
+ block(`${name}-drift`);
907
+ }
908
+ const override = input.artifactOverrides.overrides[0];
909
+ const parentFact = declaration
910
+ ? input.parentState.nodes[declaration.producerNodeId]?.declaredArtifacts?.find((fact) => fact.artifactId === input.artifactId)
911
+ : undefined;
912
+ if (!declaration ||
913
+ declaration.revisionPolicy !== "replace-file" ||
914
+ parentFact?.status !== "materialized" ||
915
+ parentFact.revisionEligible !== true ||
916
+ !parentFact.sha256) {
917
+ block("ARTIFACT_NOT_REVISION_ELIGIBLE", declaration?.producerNodeId);
918
+ }
919
+ if (!override ||
920
+ override.parentArtifact.runId !== input.parentRunId ||
921
+ override.parentArtifact.nodeId !== declaration?.producerNodeId ||
922
+ override.parentArtifact.path !== declaration?.path ||
923
+ override.parentArtifact.sha256 !== parentFact?.sha256 ||
924
+ override.supersedes !== parentFact?.sha256 ||
925
+ override.replacement.sha256 !== input.stagingRevisionSha256) {
926
+ block("ARTIFACT_REVISION_DRIFT", declaration?.producerNodeId);
927
+ }
928
+ const affectedNodeIds = impact?.affectedNodeIds ?? [];
929
+ const importedNodeIds = impact?.importedNodeIds ?? [];
930
+ const closureSafety = assessResetClosureSafety(input.parentSpec, affectedNodeIds, {
931
+ parentNodes: input.parentState.nodes,
932
+ artifactContinuation: true,
933
+ });
934
+ for (const entry of closureSafety.blockingNodes) {
935
+ blockingNodes.push(entry);
936
+ block("ARTIFACT_IMPACT_UNSAFE");
937
+ }
938
+ for (const nodeId of importedNodeIds) {
939
+ const importedTask = taskById(input.parentSpec).get(nodeId);
940
+ if (importedTask?.decisionGate?.enabled) {
941
+ const code = "restart-subgraph-contains-decision-gate";
942
+ blockingNodes.push({ nodeId, reasonCode: code });
943
+ block(code);
944
+ }
945
+ const status = input.parentState.nodes[nodeId]?.status;
946
+ if (status !== "FINISHED" && status !== "SKIPPED") {
947
+ block("parent-facts-invalid", nodeId);
948
+ }
949
+ }
950
+ const highRiskAuthority = collectHighRiskContinuationAuthority({
951
+ parentSpec: input.parentSpec,
952
+ parentState: input.parentState,
953
+ affectedNodeIds,
954
+ workspaceFingerprint: input.parentTerminalWorkspaceFingerprint,
955
+ });
956
+ const highRiskExpected = affectedNodeIds.some((nodeId) => {
957
+ const task = taskById(input.parentSpec).get(nodeId);
958
+ return Boolean(task && (isWriterTask(task, input.parentSpec) || hasDynamicRuntime(task)));
959
+ }) || hardBudget;
960
+ if (highRiskExpected) {
961
+ if (!input.parentTerminalWorkspaceFingerprint ||
962
+ !input.currentWorkspaceFingerprint) {
963
+ block("workspace-checkpoint-missing");
964
+ }
965
+ else if (input.parentTerminalWorkspaceFingerprint !==
966
+ input.currentWorkspaceFingerprint) {
967
+ block("workspace-drift");
968
+ }
969
+ else if (!highRiskAuthority) {
970
+ block("HIGH_RISK_AUTHORITY_MISSING");
971
+ }
972
+ }
973
+ const uniqueReasons = [...new Set(reasonCodes)];
974
+ const uniqueBlocked = [...new Set(blockedReasons)];
975
+ const eligible = uniqueBlocked.length === 0;
976
+ const shellNodeIds = affectedNodeIds.filter((nodeId) => taskById(input.parentSpec).get(nodeId)?.executor === "shell");
977
+ const piCallCount = affectedNodeIds.filter((nodeId) => taskById(input.parentSpec).get(nodeId)?.executor === "pi").length;
978
+ const planWithoutHash = {
979
+ schemaVersion: 2,
980
+ eligible,
981
+ parentRunId: input.parentRunId,
982
+ parentLifecycle: input.parentLifecycle,
983
+ selectedNodeId: declaration?.producerNodeId ?? input.artifactId,
984
+ effectiveFromNodeId: impact?.effectiveFromNodeId,
985
+ rewriteApplied: false,
986
+ resetNodeIds: affectedNodeIds,
987
+ importedNodeIds,
988
+ blockingNodes,
989
+ reasonCodes: uniqueReasons,
990
+ blockedReasons: uniqueBlocked,
991
+ risk: !eligible ? "high" : affectedNodeIds.length <= 2 ? "low" : "medium",
992
+ bindings: {
993
+ ...input.bindings,
994
+ artifactGraphSha256: graph.artifactGraphSha256,
995
+ overrideSetHash: input.artifactOverrides.overrideSetHash,
996
+ stagingRevisionSha256: input.stagingRevisionSha256,
997
+ ...(input.parentTerminalWorkspaceFingerprint
998
+ ? { parentWorkspaceFingerprint: input.parentTerminalWorkspaceFingerprint }
999
+ : {}),
1000
+ ...(input.currentWorkspaceFingerprint
1001
+ ? { currentWorkspaceFingerprint: input.currentWorkspaceFingerprint }
1002
+ : {}),
1003
+ ...(highRiskAuthority?.budget
1004
+ ? { budgetLedgerSha256: highRiskAuthority.budget.parentLedgerSha256 }
1005
+ : {}),
1006
+ },
1007
+ ...(shellNodeIds.length > 0
1008
+ ? { verificationPolicy: { mode: "rerun", nodeIds: shellNodeIds } }
1009
+ : {}),
1010
+ workerAssociation,
1011
+ ...(authority ? { workerContinuationAuthority: authority } : {}),
1012
+ ...(highRiskAuthority ? { highRiskAuthority } : {}),
1013
+ estimatedExecution: {
1014
+ nodeCount: affectedNodeIds.length,
1015
+ piCallCount,
1016
+ shellNodeCount: shellNodeIds.length,
1017
+ avoidedNodeCount: importedNodeIds.length,
1018
+ avoidedPiCallCount: importedNodeIds.filter((nodeId) => taskById(input.parentSpec).get(nodeId)?.executor === "pi").length,
1019
+ },
1020
+ suggestedAction: eligible ? "dag-rerun" : workerAssociation.kind === "worker-managed" ? "worker-task-retry" : "manual",
1021
+ trigger: "artifact-override",
1022
+ artifactOverrides: input.artifactOverrides,
1023
+ impact: {
1024
+ ...(impact ? { producerNodeId: impact.producerNodeId } : {}),
1025
+ directConsumerNodeIds: impact?.directConsumerNodeIds ?? [],
1026
+ effectiveFromNodeId: impact?.effectiveFromNodeId ?? declaration?.producerNodeId ?? input.artifactId,
1027
+ affectedNodeIds,
1028
+ importedNodeIds,
1029
+ blockedNodeIds: blockingNodes,
1030
+ },
1031
+ };
1032
+ return dagRerunPlanV2Schema.parse({
1033
+ ...planWithoutHash,
1034
+ planHash: computePlanHash(planWithoutHash),
1035
+ });
1036
+ }
483
1037
  function countExecutorNodes(spec, nodeIds, executor) {
484
1038
  const tasks = taskById(spec);
485
1039
  return nodeIds.filter((nodeId) => tasks.get(nodeId)?.executor === executor)