@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.2

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 (277) hide show
  1. package/CHANGELOG.md +78 -206
  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/application/dag/generate-task-dag.js +19 -75
  5. package/dist/application/task-lifecycle/advance.js +5 -24
  6. package/dist/application/task-lifecycle/observe.js +17 -171
  7. package/dist/application/task-lifecycle/plan-transitions.js +7 -42
  8. package/dist/application/task-lifecycle/recommendations.js +2 -13
  9. package/dist/build-stamp.json +3 -3
  10. package/dist/cli/command-definitions.js +12 -7
  11. package/dist/cli/program.js +23 -6
  12. package/dist/commands/client-recovery.js +0 -3
  13. package/dist/commands/dag-artifact.js +284 -0
  14. package/dist/commands/dag-context.js +184 -0
  15. package/dist/commands/dag-rerun.js +206 -1
  16. package/dist/commands/init.js +1 -27
  17. package/dist/commands/task-advance.js +0 -19
  18. package/dist/executors/dag-pi-executor.js +266 -3792
  19. package/dist/executors/pi-executor.js +4 -15
  20. package/dist/executors/pi-sdk-executor.js +3 -0
  21. package/dist/executors/shell-executor.js +217 -945
  22. package/dist/executors/shell-write-guard.js +0 -7
  23. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  24. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  25. package/dist/infrastructure/console/context-export-store.js +160 -0
  26. package/dist/infrastructure/console/dir-lock.js +132 -0
  27. package/dist/infrastructure/console/operation-store.js +197 -0
  28. package/dist/infrastructure/harness/artifact-store.js +10 -1
  29. package/dist/infrastructure/harness/atomic-write.js +12 -2
  30. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  31. package/dist/shared/context-transfer.js +418 -0
  32. package/dist/shared/dag-failure-category.js +0 -12
  33. package/dist/shared/openspec-spec.js +4 -70
  34. package/dist/shared/operator/capabilities.js +14 -7
  35. package/dist/shared/operator/safe-run-summary.js +1 -0
  36. package/dist/shared/path-safety.js +93 -0
  37. package/dist/shared/preview.js +28 -4
  38. package/dist/task/config-types.js +80 -35
  39. package/dist/task/contract/adopt.js +0 -4
  40. package/dist/task/contract/import-revision.js +0 -4
  41. package/dist/task/contract/project.js +0 -3
  42. package/dist/task/contract/schema.js +1 -2
  43. package/dist/task/frontend-project-capability.js +20 -203
  44. package/dist/task/runtime.js +2 -5
  45. package/dist/task/source-prepare/build-draft.js +3 -3
  46. package/dist/task/source-prepare/fragment-inventory.js +9 -15
  47. package/dist/task/source-prepare/prepare.js +1 -89
  48. package/dist/task/source-prepare/semantic-intake.js +2 -6
  49. package/dist/task/source-references.js +1 -22
  50. package/dist/task/task-demand-routing.js +3 -0
  51. package/dist/worker/console/chat/chat-event-store.js +2 -2
  52. package/dist/worker/console/chat/pi-runtime.js +3 -2
  53. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  54. package/dist/worker/console/chat/routes.js +13 -4
  55. package/dist/worker/console/chat/semantic-activity.js +12 -11
  56. package/dist/worker/console/chat/session-stats.js +96 -0
  57. package/dist/worker/console/chat/session-store.js +1 -1
  58. package/dist/worker/console/chat/turn-process.js +28 -4
  59. package/dist/worker/console/chat/user-questions.js +1 -1
  60. package/dist/worker/console/console-update-runtime.js +1 -1
  61. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  62. package/dist/worker/console/dag-confirmation.js +1 -1
  63. package/dist/worker/console/dag-execution-receipt.js +1 -1
  64. package/dist/worker/console/doctor.js +1 -1
  65. package/dist/worker/console/draft-store.js +1 -1
  66. package/dist/worker/console/human-gate-token.js +1 -1
  67. package/dist/worker/console/index.js +3 -6
  68. package/dist/worker/console/interview/assessment.js +1 -1
  69. package/dist/worker/console/interview/session.js +1 -1
  70. package/dist/worker/console/operation-runner.js +45 -4
  71. package/dist/worker/console/operation-sse.js +1 -1
  72. package/dist/worker/console/operation-wait.js +1 -1
  73. package/dist/worker/console/operator-actions.js +107 -168
  74. package/dist/worker/console/operator-surface-health.js +1 -1
  75. package/dist/worker/console/operator-user-error.js +169 -0
  76. package/dist/worker/console/pi-plugins.js +60 -1
  77. package/dist/worker/console/routes.js +637 -8
  78. package/dist/worker/console/security.js +35 -0
  79. package/dist/worker/console/server.js +6 -6
  80. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
  81. package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-D6PvaVd-.js} +1 -1
  82. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
  83. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
  84. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
  85. package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
  86. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
  87. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
  88. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
  89. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
  90. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
  91. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
  96. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
  97. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
  98. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
  99. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
  100. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
  101. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
  102. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
  103. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
  104. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
  105. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
  106. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
  107. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
  108. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
  109. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
  110. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
  111. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
  112. package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
  113. package/dist/worker/console/static/assets/index-xwu9GxEc.js +451 -0
  114. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
  115. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
  116. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
  117. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
  118. package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CfUiDDB3.js} +1 -1
  119. package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-BQe6fpqj.js} +5 -5
  120. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
  121. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
  122. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
  123. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
  124. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
  125. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
  126. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
  127. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
  128. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
  129. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
  130. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
  131. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
  132. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
  133. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
  134. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
  135. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
  136. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
  137. package/dist/worker/console/static/index.html +7 -2
  138. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  139. package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
  140. package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
  141. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
  142. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
  143. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  144. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
  145. package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
  146. package/dist/worker/console/workspace-context.js +28 -112
  147. package/dist/worker/console/workspace-registry.js +2 -2
  148. package/dist/worker/continuation/worker-continuation.js +278 -0
  149. package/dist/worker/observability/read-model.js +4 -0
  150. package/dist/worker/observe/health.js +1 -1
  151. package/dist/worker/observe/node-transparency.js +572 -0
  152. package/dist/worker/observe/routes.js +51 -1
  153. package/dist/worker/observe/static/api.js +69 -5
  154. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  155. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  156. package/dist/worker/observe/static/dag-node-purpose.js +0 -5
  157. package/dist/worker/observe/static/state.js +3 -1
  158. package/dist/worker/observe/static/styles.css +1037 -126
  159. package/dist/worker/observe/static/views/dag-inspector.js +1217 -65
  160. package/dist/worker/observe/static/views/session-timeline.js +121 -25
  161. package/dist/worker/outcomes/adapters.js +19 -0
  162. package/dist/worker/outcomes/types.js +1 -0
  163. package/dist/worker/pool/attempt-lease.js +97 -66
  164. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  165. package/dist/worker/pool/reconcile.js +46 -1
  166. package/dist/worker/pool/run-store.js +2 -0
  167. package/dist/worker/pool/state-projection.js +2 -0
  168. package/dist/worker/task-spec/workflow-routing.js +8 -3
  169. package/dist/workflows/dag/artifact-bindings.js +149 -0
  170. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  171. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +99 -4
  172. package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
  173. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  174. package/dist/workflows/dag/backend-test-writer-completeness.js +45 -3
  175. package/dist/workflows/dag/budget-enforcement.js +10 -0
  176. package/dist/workflows/dag/context-receipt.js +305 -0
  177. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  178. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  179. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  180. package/dist/workflows/dag/contract-validator-registrations.js +2 -1
  181. package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
  182. package/dist/workflows/dag/frontend-plan-render.js +1 -2
  183. package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
  184. package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
  185. package/dist/workflows/dag/frontend-recovery-run.js +20 -33
  186. package/dist/workflows/dag/frontend-repair.js +432 -1
  187. package/dist/workflows/dag/frontend-review-context.js +15 -261
  188. package/dist/workflows/dag/frontend-verification-trace.js +24 -249
  189. package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
  190. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  191. package/dist/workflows/dag/init-hybrid.js +740 -718
  192. package/dist/workflows/dag/interrupt-request.js +0 -7
  193. package/dist/workflows/dag/node-execution.js +136 -759
  194. package/dist/workflows/dag/path-safety.js +1 -0
  195. package/dist/workflows/dag/prompt.js +30 -38
  196. package/dist/workflows/dag/report.js +1 -37
  197. package/dist/workflows/dag/rerun-feedback.js +1 -256
  198. package/dist/workflows/dag/rerun-plan.js +557 -11
  199. package/dist/workflows/dag/rerun-run.js +601 -38
  200. package/dist/workflows/dag/rerun-task.js +0 -29
  201. package/dist/workflows/dag/retry-policy.js +109 -214
  202. package/dist/workflows/dag/runner.js +142 -430
  203. package/dist/workflows/dag/scheduler.js +16 -114
  204. package/dist/workflows/dag/skill-snapshot.js +17 -0
  205. package/dist/workflows/dag/types.js +195 -246
  206. package/dist/workflows/dag/validate.js +12 -28
  207. package/docs/architecture/runtime-boundaries.md +6 -6
  208. package/docs/architecture/worker-and-feature.md +1 -1
  209. package/docs/init-surface.manifest.json +12 -30
  210. package/docs/skills/vetted-skill-registry.md +2 -4
  211. package/docs/templates/README.md +0 -2
  212. package/docs/templates/agent-dag-report.schema.json +2 -8
  213. package/docs/templates/agent-dag.schema.json +33 -1
  214. package/docs/templates/backend-test-dag.json +18 -18
  215. package/docs/templates/frontend-implementation-contract.schema.json +1 -4
  216. package/docs/templates/product-line/task.yaml +1 -1
  217. package/harness.json +3 -3
  218. package/package.json +3 -1
  219. package/skills/frontend-bounded-implement/SKILL.md +14 -15
  220. package/skills/frontend-design-review/SKILL.md +41 -22
  221. package/skills/frontend-implementation/SKILL.md +52 -0
  222. package/skills/frontend-implementation/references/code-standards.md +33 -0
  223. package/skills/frontend-implementation/references/design-spec.md +56 -0
  224. package/skills/frontend-implementation/references/node-contracts.md +31 -0
  225. package/skills/frontend-review/SKILL.md +15 -20
  226. package/skills/frontend-review/references/review-findings.md +7 -6
  227. package/skills/frontend-verification/SKILL.md +1 -1
  228. package/skills/loop-agent/references/command-reference.md +6 -1
  229. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  230. package/dist/commands/dag-follow-up.js +0 -138
  231. package/dist/executors/pi-read-budget-policy.js +0 -239
  232. package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
  233. package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
  234. package/dist/worker/console/operation-store.js +0 -389
  235. package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
  236. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
  237. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
  238. package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
  239. package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
  240. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
  241. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
  242. package/dist/worker/console/static-src/active-run-badge.js +0 -17
  243. package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
  244. package/dist/workflows/dag/dag-retry-schema.js +0 -138
  245. package/dist/workflows/dag/frontend-closeout.js +0 -221
  246. package/dist/workflows/dag/frontend-design-policy.js +0 -400
  247. package/dist/workflows/dag/frontend-human-decision.js +0 -182
  248. package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
  249. package/dist/workflows/dag/frontend-read-budgets.js +0 -12
  250. package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
  251. package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
  252. package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
  253. package/dist/workflows/dag/frontend-review-findings.js +0 -270
  254. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
  255. package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
  256. package/dist/workflows/dag/frontend-shape-facts.js +0 -409
  257. package/dist/workflows/dag/frontend-shape.js +0 -427
  258. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
  259. package/dist/workflows/dag/frontend-split-application-service.js +0 -203
  260. package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
  261. package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
  262. package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
  263. package/dist/workflows/dag/frontend-writer-admission.js +0 -285
  264. package/dist/workflows/dag/frontend-writer-status.js +0 -256
  265. package/dist/workflows/dag/recovery-lease.js +0 -80
  266. package/docs/templates/frontend-implementation-dag.json +0 -89
  267. package/docs/templates/spec-registry.schema.json +0 -45
  268. package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
  269. package/skills/frontend-contract/SKILL.md +0 -23
  270. package/skills/frontend-contract/references/contract-protocol.md +0 -34
  271. package/skills/frontend-plan/SKILL.md +0 -26
  272. package/skills/frontend-plan/references/decision-contract.md +0 -37
  273. package/skills/frontend-plan/references/design-decisions.md +0 -17
  274. package/skills/frontend-scout/SKILL.md +0 -25
  275. package/skills/frontend-scout/references/design-evidence.md +0 -16
  276. package/skills/frontend-scout/references/scout-evidence.md +0 -23
  277. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -0,0 +1,423 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { z } from "zod";
5
+ import { CONTEXT_BUNDLE_LIMITS, computeBundleManifestSha256, contextBundleManifestV1Schema, effectiveNodeContextV1Schema, } from "../../../shared/context-transfer.js";
6
+ import { redactSecretsWithFindings, redactSensitivePathsWithFindings, } from "../../../shared/preview.js";
7
+ import { evaluatePiSessionCapability, renderPiSessionJsonl, } from "../../../adapters/context-transfer/pi-session.js";
8
+ import { renderGenericHandoff, renderReadableTranscript, } from "./renderers.js";
9
+ import { computeCanonicalHash } from "../../../task/contract/hash.js";
10
+ import { readFileBoundarySafe } from "../path-safety.js";
11
+ function sha256(value) {
12
+ return createHash("sha256").update(value).digest("hex");
13
+ }
14
+ function canonicalJson(value) {
15
+ return `${JSON.stringify(value, null, 2)}\n`;
16
+ }
17
+ const sha256Hex = z.string().regex(/^[a-f0-9]{64}$/);
18
+ /** Strict shape of the materialized managed-task-contract component. */
19
+ const taskContractMaterialSchema = z
20
+ .object({
21
+ schemaVersion: z.literal(1),
22
+ taskId: z.string().min(1),
23
+ revision: z.number().int().nonnegative(),
24
+ projectionVersion: z.literal(1),
25
+ canonicalizerVersion: z.literal(1),
26
+ taskConfigSchemaVersion: z.literal(1),
27
+ sourceHashes: z
28
+ .object({
29
+ requirementSha256: sha256Hex,
30
+ constraintsSha256: sha256Hex,
31
+ referenceManifestSha256: sha256Hex.optional(),
32
+ requirementLedgerSha256: sha256Hex.optional(),
33
+ })
34
+ .strict(),
35
+ taskConfigSha256: sha256Hex,
36
+ canonicalHash: sha256Hex,
37
+ })
38
+ .strict();
39
+ /**
40
+ * Read a run-owned material with full path safety and TOCTOU protection:
41
+ * reject symlinked or hardlinked intermediates/final entry and non-regular
42
+ * files (device/FIFO), open with O_NOFOLLOW and read via the fd so a swapped
43
+ * path can never redirect the bundle to content outside the run. Hash/size
44
+ * verification still runs on the returned bytes.
45
+ */
46
+ async function readBundleMaterial(runDir, relativePath) {
47
+ try {
48
+ return await readFileBoundarySafe({
49
+ boundaryRoot: path.resolve(runDir),
50
+ targetPath: path.resolve(runDir, ...relativePath.split("/")),
51
+ label: `context bundle material ${relativePath}`,
52
+ });
53
+ }
54
+ catch (error) {
55
+ throw new Error(`CONTEXT_COMPONENT_UNAVAILABLE: ${error instanceof Error ? error.message : String(error)}`);
56
+ }
57
+ }
58
+ function isTextualMedia(mediaType) {
59
+ const normalized = mediaType.split(";", 1)[0].trim().toLowerCase();
60
+ return (normalized.startsWith("text/") ||
61
+ normalized.includes("json") ||
62
+ normalized.includes("xml") ||
63
+ normalized.includes("yaml") ||
64
+ normalized.includes("javascript"));
65
+ }
66
+ function applyBundleSecurityPass(files, profile) {
67
+ let detectedFindingCount = 0;
68
+ let redactedFindingCount = 0;
69
+ let credentialFindingCount = 0;
70
+ let sensitivePathFindingCount = 0;
71
+ const securedFiles = files.map((file) => {
72
+ if (file.redaction === "omitted" || !isTextualMedia(file.mediaType)) {
73
+ return file;
74
+ }
75
+ const sourceText = file.bytes.toString("utf8");
76
+ const credentialScan = redactSecretsWithFindings(sourceText);
77
+ const pathScan = redactSensitivePathsWithFindings(sourceText);
78
+ const fileFindingCount = credentialScan.findingCount + pathScan.findingCount;
79
+ credentialFindingCount += credentialScan.findingCount;
80
+ sensitivePathFindingCount += pathScan.findingCount;
81
+ detectedFindingCount += fileFindingCount;
82
+ if (profile === "raw" || fileFindingCount === 0)
83
+ return file;
84
+ redactedFindingCount += fileFindingCount;
85
+ const redacted = redactSensitivePathsWithFindings(credentialScan.redacted).redacted;
86
+ return {
87
+ ...file,
88
+ bytes: Buffer.from(redacted, "utf8"),
89
+ redaction: "redacted",
90
+ sourceSha256: sha256(file.bytes),
91
+ };
92
+ });
93
+ return {
94
+ files: securedFiles,
95
+ detectedFindingCount,
96
+ redactedFindingCount,
97
+ credentialFindingCount,
98
+ sensitivePathFindingCount,
99
+ };
100
+ }
101
+ export async function buildContextBundle(input) {
102
+ const profile = input.profile ?? "redacted";
103
+ const target = input.target ?? "generic";
104
+ if (profile === "raw" && !input.rawAcknowledgement) {
105
+ throw new Error("RAW_EXPORT_ACKNOWLEDGEMENT_REQUIRED");
106
+ }
107
+ const receiptPath = path.join(input.runDir, input.nodeId, "context-receipts", `attempt-${input.attempt}.json`);
108
+ let receiptBytes;
109
+ try {
110
+ receiptBytes = await readFileBoundarySafe({
111
+ boundaryRoot: path.resolve(input.runDir),
112
+ targetPath: receiptPath,
113
+ label: "context receipt",
114
+ });
115
+ }
116
+ catch (error) {
117
+ const message = error instanceof Error ? error.message : String(error);
118
+ if (/missing|ENOENT|no such/i.test(message)) {
119
+ throw new Error("CONTEXT_RECEIPT_MISSING");
120
+ }
121
+ throw new Error(`CONTEXT_COMPONENT_UNAVAILABLE: ${message}`);
122
+ }
123
+ const receipt = effectiveNodeContextV1Schema.parse(JSON.parse(receiptBytes.toString("utf8")));
124
+ if (receipt.identity.runId !== input.runId ||
125
+ receipt.identity.nodeId !== input.nodeId ||
126
+ receipt.identity.attempt !== input.attempt) {
127
+ throw new Error("CONTEXT_COMPONENT_UNAVAILABLE: receipt ownership mismatch");
128
+ }
129
+ let files = [];
130
+ for (const component of receipt.orderedComponents) {
131
+ if (!component.materializedPath || component.inclusion === "omitted")
132
+ continue;
133
+ const source = await readBundleMaterial(input.runDir, component.materializedPath);
134
+ if (source.byteLength !== component.bytes ||
135
+ sha256(source) !== component.sha256) {
136
+ throw new Error("CONTEXT_COMPONENT_UNAVAILABLE: material hash mismatch");
137
+ }
138
+ if (component.kind === "task-contract") {
139
+ // Independently verify the materialized canonical hash: strictly parse
140
+ // the canonical-hash material (versions, sha256s, sourceHashes), then
141
+ // recompute computeCanonicalHash and require it to match both the
142
+ // material's claim and the receipt's authoritative hash. Without strict
143
+ // parsing, malformed materials (e.g. non-object sourceHashes) could be
144
+ // accepted as long as they carry a self-consistent hash.
145
+ let parsed;
146
+ try {
147
+ parsed = taskContractMaterialSchema.safeParse(JSON.parse(source.toString("utf8")));
148
+ }
149
+ catch {
150
+ throw new Error("CONTEXT_COMPONENT_UNAVAILABLE: task contract material is not valid JSON");
151
+ }
152
+ if (!parsed.success) {
153
+ throw new Error(`CONTEXT_COMPONENT_UNAVAILABLE: task contract material failed strict validation: ${parsed.error.issues
154
+ .map((issue) => issue.path.join("."))
155
+ .join(", ")}`);
156
+ }
157
+ const material = parsed.data;
158
+ const recomputed = computeCanonicalHash({
159
+ sourceHashes: material.sourceHashes,
160
+ taskConfigSha256: material.taskConfigSha256,
161
+ projectionVersion: material.projectionVersion,
162
+ canonicalizerVersion: material.canonicalizerVersion,
163
+ taskConfigSchemaVersion: material.taskConfigSchemaVersion,
164
+ });
165
+ if (recomputed !== material.canonicalHash ||
166
+ material.canonicalHash !== component.authoritativeSource?.sha256) {
167
+ throw new Error("CONTEXT_COMPONENT_UNAVAILABLE: task contract canonical hash mismatch");
168
+ }
169
+ }
170
+ const bundlePath = component.kind === "system-prompt"
171
+ ? "context/system-prompt.md"
172
+ : component.kind === "user-prompt"
173
+ ? "context/user-message.md"
174
+ : `context/components/${String(component.index).padStart(4, "0")}.bin`;
175
+ files.push({
176
+ path: bundlePath,
177
+ bytes: source,
178
+ mediaType: component.mediaType,
179
+ origin: component.source.identity,
180
+ redaction: "none",
181
+ });
182
+ }
183
+ files.push({
184
+ path: "context/receipt.json",
185
+ bytes: receiptBytes,
186
+ mediaType: "application/json",
187
+ origin: `${input.nodeId}/context-receipts/attempt-${input.attempt}.json`,
188
+ redaction: "none",
189
+ });
190
+ let sessionEventLines = [];
191
+ if (target === "readable") {
192
+ try {
193
+ sessionEventLines = (await readBundleMaterial(input.runDir, `${input.nodeId}/session-events.jsonl`)).toString("utf8").split(/\r?\n/).filter(Boolean);
194
+ }
195
+ catch (error) {
196
+ // Only a genuinely absent event stream (missing file) is treated as "no
197
+ // events" for historical/non-Pi nodes. Any safety rejection (symlink /
198
+ // hardlink / non-regular material) must fail closed: an attacker must not
199
+ // be able to mask a tampered stream as "node had no events".
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ if (!/missing|no such|ENOENT|not found/i.test(message)) {
202
+ throw error;
203
+ }
204
+ }
205
+ }
206
+ const handoff = renderGenericHandoff({
207
+ runId: input.runId,
208
+ nodeId: input.nodeId,
209
+ attempt: input.attempt,
210
+ receipt,
211
+ profile,
212
+ });
213
+ files.push({
214
+ path: target === "readable" ? "transcript.md" : "handoff.md",
215
+ bytes: Buffer.from(target === "readable"
216
+ ? renderReadableTranscript({ receipt, eventLines: sessionEventLines })
217
+ : handoff, "utf8"),
218
+ mediaType: "text/markdown; charset=utf-8",
219
+ origin: target === "readable"
220
+ ? "loop-agent/readable-transcript-v1"
221
+ : "loop-agent/generic-handoff-v1",
222
+ redaction: "none",
223
+ });
224
+ const piCapability = target === "pi-session"
225
+ ? evaluatePiSessionCapability(receipt)
226
+ : undefined;
227
+ if (target === "pi-session" && piCapability?.compatible) {
228
+ files.push({
229
+ path: "pi/session.jsonl",
230
+ bytes: Buffer.from(renderPiSessionJsonl({
231
+ receipt,
232
+ runId: input.runId,
233
+ nodeId: input.nodeId,
234
+ attempt: input.attempt,
235
+ }), "utf8"),
236
+ mediaType: "application/x-ndjson",
237
+ origin: "loop-agent/pi-session-adapter-v1",
238
+ redaction: "none",
239
+ });
240
+ }
241
+ const securityPass = applyBundleSecurityPass(files, profile);
242
+ files = securityPass.files;
243
+ files.sort((a, b) => a.path.localeCompare(b.path));
244
+ const totalBytes = files.reduce((total, file) => total + file.bytes.byteLength, 0);
245
+ if (files.length > CONTEXT_BUNDLE_LIMITS.fileCount ||
246
+ totalBytes > CONTEXT_BUNDLE_LIMITS.totalUncompressedBytes ||
247
+ files.some((file) => file.bytes.byteLength > CONTEXT_BUNDLE_LIMITS.singleFileBytes)) {
248
+ throw new Error("CONTEXT_BUNDLE_TOO_LARGE");
249
+ }
250
+ const runSpecBytes = await readBundleMaterial(input.runDir, "run.json");
251
+ const bundleSeed = {
252
+ runId: input.runId,
253
+ nodeId: input.nodeId,
254
+ attempt: input.attempt,
255
+ profile,
256
+ target,
257
+ contextDigest: receipt.contextDigest,
258
+ };
259
+ const manifestWithoutHash = {
260
+ schemaVersion: 1,
261
+ bundleId: `ctx-${sha256(JSON.stringify(bundleSeed)).slice(0, 24)}`,
262
+ createdAt: receipt.identity.capturedAt,
263
+ source: {
264
+ repoIdentity: sha256(path.resolve(input.runDir)).slice(0, 24),
265
+ runId: input.runId,
266
+ nodeId: input.nodeId,
267
+ attempt: input.attempt,
268
+ },
269
+ profile,
270
+ target,
271
+ reproducibility: {
272
+ level: target === "readable"
273
+ ? "readable-transcript"
274
+ : piCapability?.reproducibility ?? "semantic-handoff",
275
+ reasons: [
276
+ "dispatch messages and runtime capability facts captured before model call",
277
+ ...(piCapability?.reasonCodes ?? []),
278
+ ],
279
+ limitations: [
280
+ "model output and external tool state are not deterministic",
281
+ ...(piCapability?.limitations ?? []),
282
+ ],
283
+ },
284
+ bindings: {
285
+ frozenRunSpecSha256: sha256(runSpecBytes),
286
+ contextReceiptSha256: sha256(receiptBytes),
287
+ contextDigest: receipt.contextDigest,
288
+ },
289
+ runtime: {
290
+ executor: receipt.runtime.executor,
291
+ ...(receipt.runtime.resolvedModel
292
+ ? { model: receipt.runtime.resolvedModel }
293
+ : {}),
294
+ skills: receipt.runtime.skills.map((id) => ({ id })),
295
+ extensions: receipt.runtime.extensions.map((id) => ({
296
+ id,
297
+ status: "resolved",
298
+ })),
299
+ tools: receipt.runtime.activeTools.map((name) => ({
300
+ name,
301
+ available: true,
302
+ })),
303
+ },
304
+ files: files.map((file) => ({
305
+ path: file.path,
306
+ sha256: sha256(file.bytes),
307
+ bytes: file.bytes.byteLength,
308
+ mediaType: file.mediaType,
309
+ origin: file.origin,
310
+ redaction: file.redaction,
311
+ ...(file.sourceSha256 ? { sourceSha256: file.sourceSha256 } : {}),
312
+ })),
313
+ omissions: [],
314
+ security: {
315
+ secretScan: securityPass.detectedFindingCount > 0 ? "warning" : "pass",
316
+ findingCounts: {
317
+ detected: securityPass.detectedFindingCount,
318
+ credentials: securityPass.credentialFindingCount,
319
+ sensitivePaths: securityPass.sensitivePathFindingCount,
320
+ ...(profile === "redacted"
321
+ ? { redacted: securityPass.redactedFindingCount }
322
+ : {}),
323
+ },
324
+ ...(input.rawAcknowledgement
325
+ ? { rawAcknowledgement: input.rawAcknowledgement }
326
+ : {}),
327
+ },
328
+ };
329
+ const manifest = contextBundleManifestV1Schema.parse({
330
+ ...manifestWithoutHash,
331
+ bundleSha256: computeBundleManifestSha256(manifestWithoutHash),
332
+ });
333
+ const archiveFiles = [
334
+ ...files,
335
+ {
336
+ path: "manifest.json",
337
+ bytes: Buffer.from(canonicalJson(manifest), "utf8"),
338
+ mediaType: "application/json",
339
+ origin: "loop-agent/context-bundle-manifest-v1",
340
+ redaction: "none",
341
+ },
342
+ ].sort((a, b) => a.path.localeCompare(b.path));
343
+ return { manifest, files, zipBytes: deterministicZip(archiveFiles) };
344
+ }
345
+ export async function writeContextBundleDirectory(bundle, outputDir) {
346
+ await mkdir(outputDir, { recursive: true });
347
+ for (const file of bundle.files) {
348
+ const target = path.resolve(outputDir, ...file.path.split("/"));
349
+ if (!target.startsWith(`${path.resolve(outputDir)}${path.sep}`)) {
350
+ throw new Error("unsafe bundle output path");
351
+ }
352
+ await mkdir(path.dirname(target), { recursive: true });
353
+ await writeFile(target, file.bytes, { flag: "wx" });
354
+ }
355
+ await writeFile(path.join(outputDir, "manifest.json"), canonicalJson(bundle.manifest), { flag: "wx" });
356
+ }
357
+ const CRC_TABLE = Array.from({ length: 256 }, (_, index) => {
358
+ let value = index;
359
+ for (let bit = 0; bit < 8; bit += 1) {
360
+ value = (value & 1) !== 0 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
361
+ }
362
+ return value >>> 0;
363
+ });
364
+ function crc32(bytes) {
365
+ let crc = 0xffffffff;
366
+ for (const byte of bytes)
367
+ crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
368
+ return (crc ^ 0xffffffff) >>> 0;
369
+ }
370
+ function deterministicZip(files) {
371
+ const localParts = [];
372
+ const centralParts = [];
373
+ let offset = 0;
374
+ for (const file of files) {
375
+ const name = Buffer.from(file.path, "utf8");
376
+ const data = file.bytes;
377
+ const crc = crc32(data);
378
+ const local = Buffer.alloc(30);
379
+ local.writeUInt32LE(0x04034b50, 0);
380
+ local.writeUInt16LE(20, 4);
381
+ local.writeUInt16LE(0x0800, 6);
382
+ local.writeUInt16LE(0, 8);
383
+ local.writeUInt16LE(0, 10);
384
+ local.writeUInt16LE(33, 12);
385
+ local.writeUInt32LE(crc, 14);
386
+ local.writeUInt32LE(data.byteLength, 18);
387
+ local.writeUInt32LE(data.byteLength, 22);
388
+ local.writeUInt16LE(name.byteLength, 26);
389
+ local.writeUInt16LE(0, 28);
390
+ localParts.push(local, name, data);
391
+ const central = Buffer.alloc(46);
392
+ central.writeUInt32LE(0x02014b50, 0);
393
+ central.writeUInt16LE(20, 4);
394
+ central.writeUInt16LE(20, 6);
395
+ central.writeUInt16LE(0x0800, 8);
396
+ central.writeUInt16LE(0, 10);
397
+ central.writeUInt16LE(0, 12);
398
+ central.writeUInt16LE(33, 14);
399
+ central.writeUInt32LE(crc, 16);
400
+ central.writeUInt32LE(data.byteLength, 20);
401
+ central.writeUInt32LE(data.byteLength, 24);
402
+ central.writeUInt16LE(name.byteLength, 28);
403
+ central.writeUInt16LE(0, 30);
404
+ central.writeUInt16LE(0, 32);
405
+ central.writeUInt16LE(0, 34);
406
+ central.writeUInt16LE(0, 36);
407
+ central.writeUInt32LE(0, 38);
408
+ central.writeUInt32LE(offset, 42);
409
+ centralParts.push(central, name);
410
+ offset += local.byteLength + name.byteLength + data.byteLength;
411
+ }
412
+ const centralBytes = Buffer.concat(centralParts);
413
+ const end = Buffer.alloc(22);
414
+ end.writeUInt32LE(0x06054b50, 0);
415
+ end.writeUInt16LE(0, 4);
416
+ end.writeUInt16LE(0, 6);
417
+ end.writeUInt16LE(files.length, 8);
418
+ end.writeUInt16LE(files.length, 10);
419
+ end.writeUInt32LE(centralBytes.byteLength, 12);
420
+ end.writeUInt32LE(offset, 16);
421
+ end.writeUInt16LE(0, 20);
422
+ return Buffer.concat([...localParts, centralBytes, end]);
423
+ }
@@ -0,0 +1,61 @@
1
+ import path from "node:path";
2
+ import { buildArtifactBindingGraph } from "../artifact-bindings.js";
3
+ import { readFileBoundarySafe } from "../path-safety.js";
4
+ import { locateDagRun, readDagRunSpec, readDagRunState, } from "../lifecycle.js";
5
+ import { executeDagArtifactOverrideRerun, planDagArtifactOverride, } from "../rerun-run.js";
6
+ import { buildContextBundle, } from "./context-bundle.js";
7
+ export async function buildDagRunContextBundle(input) {
8
+ const located = await locateDagRun(input.cwd, input.runId);
9
+ if (!located)
10
+ throw new Error(`dag run not found: ${input.runId}`);
11
+ return buildContextBundle({
12
+ runDir: located.runDir,
13
+ runId: input.runId,
14
+ nodeId: input.nodeId,
15
+ attempt: input.attempt,
16
+ profile: input.profile,
17
+ target: input.target,
18
+ ...(input.rawAcknowledgement
19
+ ? { rawAcknowledgement: input.rawAcknowledgement }
20
+ : {}),
21
+ });
22
+ }
23
+ export async function readRevisionParentArtifact(input) {
24
+ const located = await locateDagRun(input.cwd, input.runId);
25
+ if (!located || located.lifecycle !== "completed") {
26
+ throw new Error("artifact revision requires a completed parent run");
27
+ }
28
+ const spec = await readDagRunSpec(located.runDir);
29
+ const state = await readDagRunState(located.runDir);
30
+ const declaration = buildArtifactBindingGraph(spec).artifacts.find((entry) => entry.artifactId === input.artifactId &&
31
+ entry.producerNodeId === input.producerNodeId);
32
+ if (!declaration)
33
+ throw new Error("ARTIFACT_BINDING_UNAVAILABLE");
34
+ const fact = state.nodes[input.producerNodeId]?.declaredArtifacts?.find((entry) => entry.artifactId === input.artifactId);
35
+ if (fact?.status !== "materialized" || !fact.sha256 || !fact.revisionEligible) {
36
+ throw new Error("ARTIFACT_NOT_REVISION_ELIGIBLE");
37
+ }
38
+ const parentPath = path.resolve(located.runDir, ...declaration.path.split("/"));
39
+ const resolvedRunDir = path.resolve(located.runDir);
40
+ if (!parentPath.startsWith(`${resolvedRunDir}${path.sep}`)) {
41
+ throw new Error("ARTIFACT_REVISION_PATH_INVALID");
42
+ }
43
+ // Final regular single-link check (rejects symlink/hardlink/device/FIFO)
44
+ // on top of the per-segment boundary walk, with a TOCTOU-safe fd read.
45
+ const parentBytes = await readFileBoundarySafe({
46
+ boundaryRoot: resolvedRunDir,
47
+ targetPath: parentPath,
48
+ label: `artifact parent ${input.artifactId}`,
49
+ });
50
+ return {
51
+ declaration,
52
+ parentSha256: fact.sha256,
53
+ parentBytes,
54
+ };
55
+ }
56
+ export async function planArtifactRevisionContinuation(input) {
57
+ return planDagArtifactOverride(input);
58
+ }
59
+ export async function executeArtifactRevisionContinuation(input) {
60
+ return executeDagArtifactOverrideRerun(input);
61
+ }
@@ -0,0 +1,93 @@
1
+ function textFromContent(content) {
2
+ if (typeof content === "string")
3
+ return content;
4
+ if (!Array.isArray(content))
5
+ return "";
6
+ return content
7
+ .map((part) => {
8
+ if (!part || typeof part !== "object")
9
+ return "";
10
+ const record = part;
11
+ if (record.type === "text" && typeof record.text === "string") {
12
+ return record.text;
13
+ }
14
+ if (record.type === "thinking" && typeof record.thinking === "string") {
15
+ return `[thinking captured]\n${record.thinking}`;
16
+ }
17
+ return "";
18
+ })
19
+ .filter(Boolean)
20
+ .join("\n");
21
+ }
22
+ export function renderGenericHandoff(input) {
23
+ const componentTable = input.receipt.orderedComponents.map((component) => `| ${component.index} | ${component.kind} | ${component.logicalName} | ${component.inclusion} | ${component.sha256} |`);
24
+ return [
25
+ "# Agent handoff",
26
+ "",
27
+ `- run: ${input.runId}`,
28
+ `- node: ${input.nodeId}`,
29
+ `- attempt: ${input.attempt}`,
30
+ `- context digest: ${input.receipt.contextDigest}`,
31
+ `- profile: ${input.profile}`,
32
+ "",
33
+ "## Resume instructions",
34
+ "",
35
+ "Use `context/system-prompt.md` as system/developer context and `context/user-message.md` as the user message. Read other materialized components in receipt order. Verify every payload hash against `manifest.json` before importing. The bundle restores captured inputs, not deterministic model output, credentials, or external tool state.",
36
+ "",
37
+ "## Ordered context index",
38
+ "",
39
+ "| # | kind | name | inclusion | source sha256 |",
40
+ "|---:|---|---|---|---|",
41
+ ...componentTable,
42
+ "",
43
+ ].join("\n");
44
+ }
45
+ export function renderReadableTranscript(input) {
46
+ const output = [
47
+ "# Node transcript",
48
+ "",
49
+ `Context digest: ${input.receipt.contextDigest}`,
50
+ "",
51
+ "The exact dispatched system and user messages are stored in `context/`. Missing thinking or tool events are not reconstructed.",
52
+ "",
53
+ ];
54
+ let turn = 0;
55
+ const seenMessages = new Set();
56
+ for (const line of input.eventLines) {
57
+ let event;
58
+ try {
59
+ event = JSON.parse(line);
60
+ }
61
+ catch {
62
+ continue;
63
+ }
64
+ if (event.type === "turn_start") {
65
+ turn += 1;
66
+ output.push(`## Turn ${turn}`, "");
67
+ continue;
68
+ }
69
+ if (event.type === "tool_execution_start") {
70
+ output.push(`### Tool call: ${String(event.toolName ?? "unknown")}`, "", "```json", JSON.stringify(event.args ?? null, null, 2), "```", "");
71
+ continue;
72
+ }
73
+ if (event.type === "tool_execution_end") {
74
+ output.push(`### Tool result: ${String(event.toolName ?? "unknown")}${event.isError === true ? " (error)" : ""}`, "", textFromContent(event.result?.content) || "[result not captured as text]", "");
75
+ continue;
76
+ }
77
+ if (event.type !== "message_end" || !event.message || typeof event.message !== "object") {
78
+ continue;
79
+ }
80
+ const message = event.message;
81
+ const fingerprint = JSON.stringify(message);
82
+ if (seenMessages.has(fingerprint))
83
+ continue;
84
+ seenMessages.add(fingerprint);
85
+ const role = typeof message.role === "string" ? message.role : "unknown";
86
+ const body = textFromContent(message.content);
87
+ output.push(`### ${role}`, "", body || "[message content not captured]", "");
88
+ }
89
+ if (turn === 0 && seenMessages.size === 0) {
90
+ output.push("No session events were captured for this attempt.", "");
91
+ }
92
+ return output.join("\n");
93
+ }
@@ -3,7 +3,8 @@
3
3
  * its schemaId in the contract output registry. Imported once by the node
4
4
  * executor; keeps per-contract imports out of the executor.
5
5
  */
6
- import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput, } from "./frontend-implementation-contract.js";
6
+ import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput, validateFrontendRevisionPatchNodeOutput, } from "./frontend-implementation-contract.js";
7
7
  import { registerStructuredContractValidator } from "./contract-output-registry.js";
8
8
  registerStructuredContractValidator("frontend-implementation-contract-v1", validateFrontendContractNodeOutput);
9
9
  registerStructuredContractValidator("frontend-implementation-contract-plan-patch-v1", validateFrontendPlanPatchNodeOutput);
10
+ registerStructuredContractValidator("frontend-implementation-contract-revision-patch-v1", validateFrontendRevisionPatchNodeOutput);