@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,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
+ }
@@ -47,22 +47,10 @@ const RAW_TO_NORMALIZED = {
47
47
  "verify-failure": "validation",
48
48
  "test-failure": "validation",
49
49
  "verification-plan-stale": "validation",
50
- "read-burst": "validation",
51
- // Writer persisted real file changes but the final provider call hit a
52
- // context overflow; verification never ran. Distinct from empty-output so
53
- // the report shows partial success instead of a spec issue.
54
- "partial-success-with-context-overflow": "validation",
55
- // Writer burned an excessive token budget (unconverged read-edit-test loop)
56
- // and still failed; distinct from empty-output for accurate reporting.
57
- "writer-budget-exhausted": "validation",
58
50
  network: "executor",
59
51
  quota: "executor",
60
52
  "rate-limit": "executor",
61
53
  unavailable: "executor",
62
- // Context window overflow (400 request-too-large): the session needs
63
- // compaction / fresh context before retrying — an executor-side
64
- // environment condition, not a plan-quality defect.
65
- "context-overflow": "executor",
66
54
  // Deterministic capability/policy refusal: missing verified launcher JS
67
55
  // entry, capability allowlist rejection, or missing bounded write tool
68
56
  // profile. These are environment/config defects the operator repairs
@@ -12,19 +12,6 @@ export const OPENSPEC_SPEC_DIRS = [
12
12
  "openspec/project-specs",
13
13
  "ai_workspace",
14
14
  ];
15
- /** Default roots used when a frontend task does not declare specRoots. */
16
- export const DEFAULT_FRONTEND_SPEC_ROOTS = OPENSPEC_SPEC_DIRS;
17
- /** A task-owned spec root must be a normalized, repo-relative POSIX directory. */
18
- export function isValidFrontendSpecRoot(value) {
19
- return (Boolean(value) &&
20
- !value.includes("\\") &&
21
- !value.startsWith("/") &&
22
- !value.includes("//") &&
23
- !value.split("/").some((segment) => !segment ||
24
- segment === "." ||
25
- segment === ".." ||
26
- /\s/.test(segment)));
27
- }
28
15
  /** Allowed spec file extensions (case-insensitive). */
29
16
  export const OPENSPEC_SPEC_EXT_RE = /\.(md|mdx|json|yaml|yml)$/i;
30
17
  /**
@@ -56,17 +43,14 @@ export function isGovernanceSubtreePath(filePath, governanceRoot) {
56
43
  * directories. `filePath` must be a repo-relative POSIX path (backslashes are
57
44
  * normalised internally). Directory names are case-sensitive.
58
45
  */
59
- export function isFrontendSpecPath(filePath, specRoots = DEFAULT_FRONTEND_SPEC_ROOTS) {
46
+ export function isOpenspecSpecPath(filePath) {
60
47
  const normalized = filePath.replaceAll("\\", "/");
61
- for (const dir of specRoots) {
48
+ for (const dir of OPENSPEC_SPEC_DIRS) {
62
49
  if (normalized === dir || normalized.startsWith(dir + "/"))
63
50
  return true;
64
51
  }
65
52
  return false;
66
53
  }
67
- export function isOpenspecSpecPath(filePath) {
68
- return isFrontendSpecPath(filePath);
69
- }
70
54
  /**
71
55
  * Returns `true` when `filename` (the basename only, not a full path) has an
72
56
  * allowed frontend specification extension.
@@ -75,12 +59,9 @@ export function isValidOpenspecExtension(filename) {
75
59
  return OPENSPEC_SPEC_EXT_RE.test(filename);
76
60
  }
77
61
  /** Returns `true` for a supported file inside a canonical openspec spec dir. */
78
- export function isFrontendSpecFilePath(filePath, specRoots = DEFAULT_FRONTEND_SPEC_ROOTS) {
79
- return (isFrontendSpecPath(filePath, specRoots) &&
80
- isValidOpenspecExtension(filePath.replaceAll("\\", "/")));
81
- }
82
62
  export function isOpenspecSpecFilePath(filePath) {
83
- return isFrontendSpecFilePath(filePath);
63
+ return (isOpenspecSpecPath(filePath) &&
64
+ isValidOpenspecExtension(filePath.replaceAll("\\", "/")));
84
65
  }
85
66
  /**
86
67
  * Returns `true` when free-form search input references a canonical frontend
@@ -142,50 +123,3 @@ export function extractTaskSourceOpenspecPaths(markdown, governanceRoot = DEFAUL
142
123
  !isGovernanceSubtreePath(token, governanceRoot))
143
124
  .sort();
144
125
  }
145
- /**
146
- * Extract explicit specification-file citations for a task's frozen spec roots.
147
- * This is intentionally path-only: source preparation decides which roots are
148
- * normative, while DAG execution only consumes that frozen configuration.
149
- */
150
- export function extractTaskSourceFrontendSpecPaths(markdown, specRoots, governanceRoot = DEFAULT_OPENSPEC_GOVERNANCE_ROOT) {
151
- if (!markdown)
152
- return [];
153
- const raw = new Set();
154
- const collect = (token) => {
155
- const normalized = normalizeCitationToken(token);
156
- if (normalized)
157
- raw.add(normalized);
158
- };
159
- for (const match of markdown.matchAll(/`([^`\n]+)`/g))
160
- collect(match[1] ?? "");
161
- for (const match of markdown.matchAll(/\[[^\]]*\]\(([^)\s]+)\)/g))
162
- collect(match[1] ?? "");
163
- for (const root of specRoots) {
164
- const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
165
- for (const match of markdown.matchAll(new RegExp(`(?:^|[\\s\"'([>])(${escaped}/[A-Za-z0-9._/-]+)`, "g"))) {
166
- collect(match[1] ?? "");
167
- }
168
- }
169
- return [...raw]
170
- .filter((token) => isFrontendSpecFilePath(token, specRoots) && !isGovernanceSubtreePath(token, governanceRoot))
171
- .sort();
172
- }
173
- /**
174
- * Source-preparation helper for explicitly named normative directories. A root
175
- * is inferred only on a line that declares a spec/规范/design-system intent;
176
- * arbitrary Markdown links never silently broaden task configuration.
177
- */
178
- export function extractDeclaredFrontendSpecRoots(markdown) {
179
- const roots = new Set();
180
- for (const line of markdown.split(/\r?\n/)) {
181
- if (!/(?:规范|规约|标准|设计系统|\bspec(?:ification)?\b|\bdesign[ -]?system\b|\bguidelines?\b|\brules?\b)/i.test(line))
182
- continue;
183
- for (const match of line.matchAll(/`([^`]+)`|\[[^\]]+\]\(([^)\s]+)\)/g)) {
184
- const raw = (match[1] ?? match[2] ?? "").replaceAll("\\", "/").replace(/\/+$/, "");
185
- if (!raw || !isValidFrontendSpecRoot(raw))
186
- continue;
187
- roots.add(isValidOpenspecExtension(raw) ? raw.split("/").slice(0, -1).join("/") : raw);
188
- }
189
- }
190
- return [...roots].filter(isValidFrontendSpecRoot).sort();
191
- }
@@ -2240,13 +2240,6 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
2240
2240
  source: "loop-agent",
2241
2241
  note: "server-side read-only run-facts check (ineligible plan / explicit rerun-task recommendation) replaces the browser Human Gate (2026-08-11).",
2242
2242
  },
2243
- {
2244
- command: "loop-agent dag follow-up",
2245
- coverage: "excluded",
2246
- action: null,
2247
- source: "loop-agent",
2248
- exclusionReason: "Frontend review recovery helper is a direct advanced CLI composition (create/import task and optional advance), not an Operator action endpoint.",
2249
- },
2250
2243
  {
2251
2244
  command: "loop-agent dag reconcile-tasks",
2252
2245
  coverage: "human-gated-required",
@@ -2259,6 +2252,20 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
2259
2252
  action: "dagFinalVerification",
2260
2253
  source: "loop-agent",
2261
2254
  },
2255
+ {
2256
+ command: "loop-agent dag context",
2257
+ coverage: "excluded",
2258
+ action: null,
2259
+ source: "loop-agent",
2260
+ exclusionReason: "Context export can disclose raw execution bytes and requires a human-selected output path; Console uses dedicated gated actions instead of the generic CLI wrapper.",
2261
+ },
2262
+ {
2263
+ command: "loop-agent dag artifact",
2264
+ coverage: "excluded",
2265
+ action: null,
2266
+ source: "loop-agent",
2267
+ exclusionReason: "Artifact revision staging consumes a human-selected local file and remains on dedicated Console/CLI mutation gates.",
2268
+ },
2262
2269
  {
2263
2270
  command: "loop-agent workflow list",
2264
2271
  coverage: "advanced",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { lstat, open, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+ function assertContained(root, target, label) {
5
+ if (target === root || !target.startsWith(`${root}${path.sep}`)) {
6
+ throw new Error(`${label}: path escapes boundary: ${target}`);
7
+ }
8
+ }
9
+ /**
10
+ * Reject any symlink on the path between `boundaryRoot` and `targetPath`.
11
+ * This is the lexical/path-walk phase; callers that consume file bytes must
12
+ * still use readFileBoundarySafe so the checked object is the opened object.
13
+ */
14
+ export async function assertRealPathInsideBoundary(input) {
15
+ const root = path.resolve(input.boundaryRoot);
16
+ const target = path.resolve(input.targetPath);
17
+ assertContained(root, target, input.label);
18
+ const rootStat = await lstat(root).catch(() => undefined);
19
+ if (!rootStat) {
20
+ throw new Error(`${input.label}: boundary root is not a real directory`);
21
+ }
22
+ if (rootStat.isSymbolicLink()) {
23
+ throw new Error(`${input.label}: boundary root must not be a symlink`);
24
+ }
25
+ if (!rootStat.isDirectory()) {
26
+ throw new Error(`${input.label}: boundary root is not a real directory`);
27
+ }
28
+ const segments = path.relative(root, target).split(path.sep).filter(Boolean);
29
+ let current = root;
30
+ for (let index = 0; index < segments.length; index += 1) {
31
+ current = path.join(current, segments[index]);
32
+ const stat = await lstat(current).catch(() => undefined);
33
+ if (!stat) {
34
+ throw new Error(`${input.label}: path component missing: ${segments[index]}`);
35
+ }
36
+ if (stat.isSymbolicLink()) {
37
+ throw new Error(`${input.label}: symlink is forbidden on path: ${segments[index]}`);
38
+ }
39
+ if (index < segments.length - 1 && !stat.isDirectory()) {
40
+ throw new Error(`${input.label}: path ancestor is not a directory: ${segments[index]}`);
41
+ }
42
+ }
43
+ return target;
44
+ }
45
+ /** Reject a non-regular, symlinked, or hardlinked final entry before opening. */
46
+ export async function assertReadableRegularFile(input) {
47
+ const target = await assertRealPathInsideBoundary(input);
48
+ const stat = await lstat(target).catch(() => undefined);
49
+ if (!stat)
50
+ throw new Error(`${input.label}: path component missing`);
51
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
52
+ throw new Error(`${input.label}: path must be a regular single-link file`);
53
+ }
54
+ return target;
55
+ }
56
+ /**
57
+ * Read one owned regular file without reopening an unchecked pathname.
58
+ *
59
+ * The preflight rejects normal FIFO/device/hardlink cases without opening
60
+ * them. O_NOFOLLOW rejects a final symlink introduced after preflight and
61
+ * O_NONBLOCK prevents a swapped FIFO from blocking. The post-open identity and
62
+ * realpath checks make an ancestor swap detectable before bytes are consumed.
63
+ */
64
+ export async function readFileBoundarySafe(input) {
65
+ const target = await assertReadableRegularFile(input);
66
+ const flags = fsConstants.O_RDONLY |
67
+ (fsConstants.O_NOFOLLOW ?? 0) |
68
+ (fsConstants.O_NONBLOCK ?? 0);
69
+ const handle = await open(target, flags);
70
+ try {
71
+ const opened = await handle.stat();
72
+ if (!opened.isFile() || opened.nlink !== 1) {
73
+ throw new Error(`${input.label}: path must be a regular single-link file`);
74
+ }
75
+ const [resolvedRoot, resolvedTarget, current] = await Promise.all([
76
+ realpath(path.resolve(input.boundaryRoot)),
77
+ realpath(target),
78
+ lstat(target),
79
+ ]);
80
+ assertContained(resolvedRoot, resolvedTarget, input.label);
81
+ if (current.isSymbolicLink() ||
82
+ !current.isFile() ||
83
+ current.nlink !== 1 ||
84
+ current.dev !== opened.dev ||
85
+ current.ino !== opened.ino) {
86
+ throw new Error(`${input.label}: path changed while opening`);
87
+ }
88
+ return await handle.readFile();
89
+ }
90
+ finally {
91
+ await handle.close().catch(() => { });
92
+ }
93
+ }