@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
@@ -35,8 +35,25 @@ function toProgressNode(node) {
35
35
  startedAt: typeof node.startedAt === "string" ? node.startedAt : undefined,
36
36
  durationMs: typeof node.durationMs === "number" ? node.durationMs : undefined,
37
37
  errorPreview: typeof node.errorPreview === "string" ? node.errorPreview : undefined,
38
+ lastMeaningfulProgressAt: typeof node.lastMeaningfulProgressAt === "string"
39
+ ? node.lastMeaningfulProgressAt
40
+ : undefined,
41
+ livenessStatus: typeof node.livenessStatus === "string" ? node.livenessStatus : undefined,
38
42
  };
39
43
  }
44
+ function latestIso(values) {
45
+ let latest = null;
46
+ for (const iso of values) {
47
+ if (!iso)
48
+ continue;
49
+ const time = Date.parse(iso);
50
+ if (!Number.isFinite(time))
51
+ continue;
52
+ if (!latest || time > latest.time)
53
+ latest = { iso, time };
54
+ }
55
+ return latest?.iso ?? null;
56
+ }
40
57
  function elapsedSince(iso, now) {
41
58
  if (!iso)
42
59
  return null;
@@ -58,6 +75,8 @@ export function summarizeRunProgress(dag, now = Date.now()) {
58
75
  stalled: false,
59
76
  currentNodeElapsedMs: null,
60
77
  runElapsedMs: null,
78
+ lastMeaningfulProgressAt: null,
79
+ observedAtMs: now,
61
80
  };
62
81
  }
63
82
  const rawNodes = Array.isArray(dag.nodes) ? dag.nodes : [];
@@ -86,6 +105,8 @@ export function summarizeRunProgress(dag, now = Date.now()) {
86
105
  runElapsedMs: terminal
87
106
  ? (runDurationMs ?? elapsedSince(runStartedAt, now))
88
107
  : elapsedSince(runStartedAt, now),
108
+ lastMeaningfulProgressAt: latestIso(nodes.map((node) => node.lastMeaningfulProgressAt)),
109
+ observedAtMs: now,
89
110
  };
90
111
  }
91
112
  const TERMINAL_RUN_PROGRESS_STATUSES = new Set([
@@ -108,3 +129,88 @@ export function formatDurationMs(ms) {
108
129
  return `${minutes} 分 ${seconds} 秒`;
109
130
  return `${seconds} 秒`;
110
131
  }
132
+ function friendlyRunPhase(node) {
133
+ if (!node)
134
+ return "正在等待下一阶段";
135
+ const searchable = `${node.nodeId} ${node.label}`.toLowerCase();
136
+ if (/contract|boundary|intake/.test(searchable))
137
+ return "正在确认执行边界";
138
+ if (/scout|explore|research/.test(searchable))
139
+ return "正在梳理影响范围";
140
+ if (/plan|design/.test(searchable))
141
+ return "正在规划实施步骤";
142
+ if (/implement|repair|fix|write/.test(searchable))
143
+ return "正在实现本次修改";
144
+ if (/verify|test|lint|review|check/.test(searchable))
145
+ return "正在验证本次修改";
146
+ if (/closeout|handoff|report/.test(searchable))
147
+ return "正在整理执行结果";
148
+ if (node.label && node.label !== node.nodeId)
149
+ return `正在进行${node.label}`;
150
+ return "正在推进当前阶段";
151
+ }
152
+ function formatMeaningfulAge(iso, now) {
153
+ if (!iso)
154
+ return "等待首个执行进展";
155
+ const time = Date.parse(iso);
156
+ if (!Number.isFinite(time))
157
+ return "等待首个执行进展";
158
+ const seconds = Math.max(0, Math.floor((now - time) / 1000));
159
+ if (seconds < 60)
160
+ return `最近进展 ${seconds} 秒前`;
161
+ const minutes = Math.floor(seconds / 60);
162
+ if (minutes < 60)
163
+ return `最近进展 ${minutes} 分钟前`;
164
+ return `最近进展 ${Math.floor(minutes / 60)} 小时前`;
165
+ }
166
+ /** Human-facing copy shared by the task panel and Operator Chat progress row. */
167
+ export function presentRunProgress(summary, now = Date.now()) {
168
+ if (!summary || !summary.effectiveStatus) {
169
+ return {
170
+ kind: "pending",
171
+ title: "DAG 正在启动",
172
+ detail: "正在等待首个执行进展",
173
+ };
174
+ }
175
+ const status = summary.effectiveStatus;
176
+ const liveElapsed = summary.runElapsedMs == null
177
+ ? null
178
+ : summary.runElapsedMs +
179
+ (summary.terminal ? 0 : Math.max(0, now - summary.observedAtMs));
180
+ const elapsed = formatDurationMs(liveElapsed);
181
+ const progressDetail = `${friendlyRunPhase(summary.running)} · 已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`;
182
+ if (status === "running") {
183
+ return { kind: "progress", title: `DAG 执行中 · 已运行 ${elapsed}`, detail: progressDetail };
184
+ }
185
+ if (status === "running-quiet") {
186
+ return { kind: "quiet", title: `DAG 仍在执行 · 已运行 ${elapsed}`, detail: progressDetail };
187
+ }
188
+ if (status === "running-suspected-stall") {
189
+ return { kind: "warning", title: `执行可能停滞 · 已运行 ${elapsed}`, detail: progressDetail };
190
+ }
191
+ if (["needs-attention", "paused", "interrupted"].includes(status)) {
192
+ return { kind: "warning", title: "DAG 需要处理", detail: progressDetail };
193
+ }
194
+ if (status === "finished") {
195
+ return {
196
+ kind: "success",
197
+ title: `DAG 已完成 · 总用时 ${elapsed}`,
198
+ detail: `已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`,
199
+ };
200
+ }
201
+ if (status === "partial_failed" || status === "failed") {
202
+ return {
203
+ kind: "error",
204
+ title: `DAG 未完成 · 总用时 ${elapsed}`,
205
+ detail: `已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`,
206
+ };
207
+ }
208
+ if (status === "pending") {
209
+ return { kind: "pending", title: "DAG 正在启动", detail: progressDetail };
210
+ }
211
+ return {
212
+ kind: "warning",
213
+ title: `DAG 已结束 · 总用时 ${elapsed}`,
214
+ detail: `已完成 ${summary.finished}/${summary.total} 步`,
215
+ };
216
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Poll /api/health until a (possibly brand-new) Console instance answers,
3
+ * then reload. A handoff restart kills the old process before the new one
4
+ * starts listening, so reloading immediately after "restarting" hits a dead
5
+ * port and reads as "the page is gone".
6
+ */
7
+ export async function reloadWhenConsoleReady(timeoutMs = 30_000) {
8
+ const started = Date.now();
9
+ while (Date.now() - started < timeoutMs) {
10
+ try {
11
+ const res = await fetch(`/api/health?ts=${Date.now()}`, {
12
+ cache: "no-store",
13
+ });
14
+ if (res.ok) {
15
+ window.location.reload();
16
+ return;
17
+ }
18
+ }
19
+ catch {
20
+ // old process gone, new one not listening yet — keep polling
21
+ }
22
+ await new Promise((resolve) => setTimeout(resolve, 1000));
23
+ }
24
+ window.location.reload();
25
+ }
@@ -19,10 +19,9 @@
19
19
  */
20
20
  import { randomBytes } from "node:crypto";
21
21
  import { existsSync } from "node:fs";
22
- import { rm } from "node:fs/promises";
23
22
  import path from "node:path";
24
23
  import { LoopAgentClient } from "../loop-agent/loop-agent-client.js";
25
- import { openConsoleAppData, sha256Json, writeSecureJson, } from "./app-data.js";
24
+ import { openConsoleAppData, } from "../../infrastructure/console/app-data.js";
26
25
  import { DagConfirmationStore } from "./dag-confirmation.js";
27
26
  import { DagExecutionReceiptStore } from "./dag-execution-receipt.js";
28
27
  import { DraftStore } from "./draft-store.js";
@@ -38,10 +37,10 @@ import { GoalRegistry } from "./chat/goals.js";
38
37
  import { GoalRoundDriver } from "./chat/goal-round-driver.js";
39
38
  import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
40
39
  import { createOperationEventStore, } from "./operation-sse.js";
41
- import { OperationStore } from "./operation-store.js";
42
- import { createFrontendSplitOperationAdapter, } from "./frontend-split-operation-adapter.js";
43
- import { createFrontendHumanDecisionAdapter } from "./frontend-human-decision-adapter.js";
44
- import { FrontendSplitTaskMaterializer, } from "../materialize/frontend-split-task-materializer.js";
40
+ import { OperationStore } from "../../infrastructure/console/operation-store.js";
41
+ import { reconcileContextExports } from "../../infrastructure/console/context-export-store.js";
42
+ import { reconcileArtifactRevisions } from "../../infrastructure/console/artifact-revision-store.js";
43
+ import { recordContextTransferEvent } from "./context-transfer-diagnostics.js";
45
44
  import { probePiReadiness, } from "./pi-readiness.js";
46
45
  import { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
47
46
  import { NightAuxTicker } from "./night-aux-ticker.js";
@@ -77,106 +76,6 @@ export async function createWorkspaceContext(record, deps) {
77
76
  const events = createOperationEventStore({
78
77
  persistenceDir: path.join(appData.operations, "events"),
79
78
  });
80
- // Frontend split runtime is repo-scoped: materialization, durable schedule
81
- // intents, and aggregate closeout must use this workspace's app-data and
82
- // OperationStore rather than the process startup workspace.
83
- const splitScheduleDir = path.join(appData.root, "frontend-split-schedule");
84
- const splitAuditDir = path.join(appData.root, "frontend-split-audit");
85
- const splitAggregateDir = path.join(appData.root, "frontend-split-aggregate");
86
- const splitMaterializationBackend = {
87
- async ensureTask({ taskId }) {
88
- return { taskId, complete: true };
89
- },
90
- async ensureOperation({ taskId, operationId, payloadDigest, request }) {
91
- const action = request.kind === "child"
92
- ? "frontendSplitChild"
93
- : "frontendSplitIntegration";
94
- const { operation } = await operations.ensureSplitMaterializedOperation({
95
- operationId,
96
- taskId,
97
- action,
98
- actionParams: request,
99
- payloadHash: payloadDigest,
100
- });
101
- return {
102
- taskId: operation.taskId ?? taskId,
103
- operationId: operation.operationId,
104
- complete: true,
105
- };
106
- },
107
- };
108
- const materializer = new FrontendSplitTaskMaterializer(splitMaterializationBackend);
109
- const schedule = async (input) => {
110
- const fact = {
111
- schemaVersion: 1,
112
- kind: "frontend-split-schedule-intent",
113
- operationId: input.operationId,
114
- generation: input.generation,
115
- bindingDigest: input.bindingDigest,
116
- recordedAt: new Date().toISOString(),
117
- };
118
- const name = `${sha256Json({
119
- operationId: input.operationId,
120
- generation: input.generation,
121
- bindingDigest: input.bindingDigest,
122
- })}.json`;
123
- await writeSecureJson(path.join(splitScheduleDir, name), fact);
124
- };
125
- const auditStale = async (input) => {
126
- const fact = {
127
- schemaVersion: 1,
128
- kind: "frontend-split-stale-audit",
129
- operationId: input.operationId,
130
- reason: input.reason,
131
- fact: input.fact,
132
- recordedAt: new Date().toISOString(),
133
- };
134
- await writeSecureJson(path.join(splitAuditDir, `${sha256Json(fact)}.json`), fact);
135
- };
136
- const prepareAggregate = async ({ operationId, aggregate, }) => {
137
- const before = await operations.get(operationId);
138
- if (!before) {
139
- throw new Error(`cannot prepare aggregate: parent operation not found: ${operationId}`);
140
- }
141
- const aggregateFile = path.join(splitAggregateDir, `${aggregate.digest}.json`);
142
- let parentRecordPath = "";
143
- let wroteAggregate = false;
144
- let wroteParent = false;
145
- return {
146
- committedRef: {
147
- ref: aggregateFile,
148
- digest: aggregate.digest,
149
- commitState: "committed",
150
- },
151
- async commit(input) {
152
- parentRecordPath = input.parentRecordPath;
153
- await writeSecureJson(aggregateFile, {
154
- schemaVersion: 1,
155
- kind: "frontend-split-aggregate-closeout",
156
- operationId,
157
- aggregate,
158
- });
159
- wroteAggregate = true;
160
- await writeSecureJson(input.parentRecordPath, input.parentRecord);
161
- wroteParent = true;
162
- },
163
- async rollback() {
164
- if (wroteAggregate)
165
- await rm(aggregateFile, { force: true });
166
- if (wroteParent && parentRecordPath) {
167
- await writeSecureJson(parentRecordPath, before);
168
- }
169
- },
170
- };
171
- };
172
- const splitOrchestrator = createFrontendSplitOperationAdapter({
173
- store: operations,
174
- materializer,
175
- schedule,
176
- auditStale,
177
- prepareAggregate,
178
- });
179
- const humanDecisionAdapter = createFrontendHumanDecisionAdapter(operations);
180
79
  const drafts = new DraftStore(appData);
181
80
  const confirmations = new DagConfirmationStore(appData, deps.bootToken.confirmationToken);
182
81
  const dagExecutionReceipts = new DagExecutionReceiptStore(appData);
@@ -184,6 +83,29 @@ export async function createWorkspaceContext(record, deps) {
184
83
  const assessments = new AssessmentStore(appData);
185
84
  if (!deps.skipReconcile) {
186
85
  await operations.reconcileOnBoot();
86
+ const [expiredExports, expiredRevisions] = await Promise.all([
87
+ reconcileContextExports({ appData }),
88
+ reconcileArtifactRevisions({ appData }),
89
+ ]);
90
+ // Boot-time lifecycle evidence for what reconcile reclaimed. Only
91
+ // 32-hex ids are real export/revision identities; uploads/ and lock/
92
+ // entries are blob housekeeping and get no event.
93
+ await Promise.all([
94
+ ...expiredExports.removed
95
+ .filter((exportId) => /^[a-f0-9]{32}$/.test(exportId))
96
+ .map((exportId) => recordContextTransferEvent({
97
+ appData,
98
+ event: "context_export.expired",
99
+ fields: { exportId },
100
+ }).catch(() => undefined)),
101
+ ...expiredRevisions
102
+ .filter((revisionId) => /^[a-f0-9]{32}$/.test(revisionId))
103
+ .map((revisionId) => recordContextTransferEvent({
104
+ appData,
105
+ event: "artifact_revision.expired",
106
+ fields: { revisionId },
107
+ }).catch(() => undefined)),
108
+ ]);
187
109
  }
188
110
  const getReadiness = () => readinessCache;
189
111
  const refreshReadiness = async () => {
@@ -201,9 +123,6 @@ export async function createWorkspaceContext(record, deps) {
201
123
  client,
202
124
  operations,
203
125
  events,
204
- splitOrchestrator,
205
- humanDecisionAdapter,
206
- materializer,
207
126
  drafts,
208
127
  confirmations,
209
128
  dagExecutionReceipts,
@@ -299,9 +218,6 @@ export async function createWorkspaceContext(record, deps) {
299
218
  client,
300
219
  operations,
301
220
  events,
302
- splitOrchestrator,
303
- humanDecisionAdapter,
304
- materializer,
305
221
  drafts,
306
222
  confirmations,
307
223
  dagExecutionReceipts,
@@ -19,8 +19,8 @@ import { createHash, randomBytes } from "node:crypto";
19
19
  import { realpathSync, statSync, unwatchFile, watchFile } from "node:fs";
20
20
  import { mkdir, open, readFile, rm, stat } from "node:fs/promises";
21
21
  import path from "node:path";
22
- import { readJsonIfExists, writeSecureJson, } from "./app-data.js";
23
- import { normalizeWorktreeRealpath, repoFingerprintV1, } from "./repo-fingerprint.js";
22
+ import { readJsonIfExists, writeSecureJson, } from "../../infrastructure/console/app-data.js";
23
+ import { normalizeWorktreeRealpath, repoFingerprintV1, } from "../../infrastructure/console/repo-fingerprint.js";
24
24
  export const WORKSPACE_REGISTRY_SCHEMA_VERSION = 3;
25
25
  /** Stable workspaceId: sha256("loop-console-workspace-v1\0" + normalizedRealpath). */
26
26
  export function workspaceIdForRealpath(normalizedRealpath) {
@@ -0,0 +1,278 @@
1
+ import path from "node:path";
2
+ import { deriveWorkerContinuationRunId, workerContinuationAuthorityV1Schema, } from "../../shared/context-transfer.js";
3
+ import { controllerIdentityAnchor } from "../../shared/package-metadata.js";
4
+ import { locateDagRun, readDagRunState, } from "../../workflows/dag/lifecycle.js";
5
+ import { beginWorkerAttemptWithLease } from "../pool/begin-attempt-with-lease.js";
6
+ import { readAttemptLease, releaseAttemptLease, } from "../pool/attempt-lease.js";
7
+ import { readRunOwner } from "../pool/run-owner-store.js";
8
+ import { findRunByWorkerRunId, recordTaskPoolRun, readTaskPoolState, writeTaskPoolState, } from "../pool/run-store.js";
9
+ /**
10
+ * Resolve a Worker parent into an immutable continuation authority. Standalone
11
+ * and missing fixture parents return undefined; a detected Worker parent with
12
+ * incomplete Task Pool ownership fails closed.
13
+ */
14
+ export async function resolveWorkerContinuationAuthority(input) {
15
+ const located = await locateDagRun(input.repoRoot, input.parentDagRunId);
16
+ if (!located)
17
+ return undefined;
18
+ const state = await readDagRunState(located.runDir);
19
+ const association = state.workerAssociation;
20
+ if (!association)
21
+ return undefined;
22
+ if (located.lifecycle !== "completed" || state.status !== "finished") {
23
+ throw new Error(`WORKER_CONTINUATION_AUTHORITY_MISMATCH: parent DAG is not completed: ${input.parentDagRunId}`);
24
+ }
25
+ const expectedChildWorkerRunId = deriveWorkerContinuationRunId(association.workerRunId, input.revisionId);
26
+ const [owner, parentRun, childRun, poolState, activeLease] = await Promise.all([
27
+ readRunOwner(input.repoRoot, association.workerRunId),
28
+ findRunByWorkerRunId(input.repoRoot, association.workerRunId),
29
+ findRunByWorkerRunId(input.repoRoot, expectedChildWorkerRunId),
30
+ readTaskPoolState(input.repoRoot, {
31
+ featureId: association.featureId,
32
+ taskId: association.taskId,
33
+ }),
34
+ readAttemptLease(input.repoRoot, association.featureId, association.taskId),
35
+ ]);
36
+ const parentOutcomeReady = poolState?.status === "Done" &&
37
+ poolState.workerRunId === association.workerRunId;
38
+ const matchingActiveContinuation = input.allowActiveRevision === true &&
39
+ poolState?.status === "Running" &&
40
+ poolState.workerRunId === expectedChildWorkerRunId &&
41
+ poolState.attemptKind === "artifact-continuation" &&
42
+ poolState.continuation?.parentWorkerRunId === association.workerRunId &&
43
+ poolState.continuation.parentDagRunId === input.parentDagRunId &&
44
+ poolState.continuation.revisionId === input.revisionId &&
45
+ activeLease?.workerRunId === expectedChildWorkerRunId &&
46
+ activeLease.revisionId === input.revisionId &&
47
+ activeLease.claimId === poolState.continuation.operationId;
48
+ const matchingTerminalContinuation = input.allowActiveRevision === true &&
49
+ poolState?.status === "Failed" &&
50
+ poolState.workerRunId === expectedChildWorkerRunId &&
51
+ poolState.attemptKind === "artifact-continuation" &&
52
+ poolState.continuation?.parentWorkerRunId === association.workerRunId &&
53
+ poolState.continuation.parentDagRunId === input.parentDagRunId &&
54
+ poolState.continuation.revisionId === input.revisionId &&
55
+ childRun?.status === "failed" &&
56
+ childRun.attemptKind === "artifact-continuation" &&
57
+ childRun.continuation?.operationId ===
58
+ poolState.continuation.operationId &&
59
+ activeLease === undefined;
60
+ if (!owner ||
61
+ !parentRun ||
62
+ owner.workerRunId !== association.workerRunId ||
63
+ owner.featureId !== association.featureId ||
64
+ owner.taskId !== association.taskId ||
65
+ parentRun.featureId !== association.featureId ||
66
+ parentRun.taskId !== association.taskId ||
67
+ parentRun.batchRunId !== owner.batchRunId ||
68
+ parentRun.status !== "succeeded" ||
69
+ (!parentOutcomeReady &&
70
+ !matchingActiveContinuation &&
71
+ !matchingTerminalContinuation)) {
72
+ throw new Error(`WORKER_CONTINUATION_AUTHORITY_MISMATCH: parent Worker ownership is incomplete for ${association.workerRunId}`);
73
+ }
74
+ const authority = workerContinuationAuthorityV1Schema.parse({
75
+ schemaVersion: 1,
76
+ parentWorkerRunId: association.workerRunId,
77
+ childWorkerRunId: expectedChildWorkerRunId,
78
+ featureId: association.featureId,
79
+ taskId: association.taskId,
80
+ batchRunId: owner.batchRunId,
81
+ revisionId: input.revisionId,
82
+ });
83
+ return { authority, parentDagRunId: input.parentDagRunId };
84
+ }
85
+ export async function beginWorkerContinuation(input) {
86
+ const { authority } = input.resolved;
87
+ const priorState = await readTaskPoolState(input.repoRoot, {
88
+ featureId: authority.featureId,
89
+ taskId: authority.taskId,
90
+ });
91
+ const existingLease = await readAttemptLease(input.repoRoot, authority.featureId, authority.taskId);
92
+ const freshParentOutcome = priorState?.status === "Done" &&
93
+ priorState.workerRunId === authority.parentWorkerRunId &&
94
+ priorState.lastContinuation?.revisionId !== authority.revisionId;
95
+ const sameCanonicalRunning = priorState?.status === "Running" &&
96
+ priorState.workerRunId === authority.childWorkerRunId &&
97
+ priorState.continuation?.operationId === input.operationId &&
98
+ existingLease?.workerRunId === authority.childWorkerRunId &&
99
+ existingLease.revisionId === authority.revisionId &&
100
+ existingLease.claimId === input.operationId;
101
+ if (!freshParentOutcome && !sameCanonicalRunning) {
102
+ throw new Error(`state-conflict for ${authority.featureId}/${authority.taskId}: continuation admission is not fresh`);
103
+ }
104
+ if (existingLease) {
105
+ const sameCanonicalOperation = existingLease.workerRunId === authority.childWorkerRunId &&
106
+ existingLease.revisionId === authority.revisionId &&
107
+ existingLease.claimId === input.operationId &&
108
+ priorState?.continuation?.operationId === input.operationId;
109
+ if (!sameCanonicalOperation) {
110
+ throw new Error(`attempt-lease-conflict for ${authority.featureId}/${authority.taskId}: held by ${existingLease.workerRunId}`);
111
+ }
112
+ }
113
+ const identity = input.client.getIdentity?.();
114
+ const lineage = buildLineage(input.resolved, input.operationId);
115
+ try {
116
+ await beginWorkerAttemptWithLease({
117
+ repoRoot: input.repoRoot,
118
+ featureId: authority.featureId,
119
+ taskId: authority.taskId,
120
+ workerRunId: authority.childWorkerRunId,
121
+ batchRunId: authority.batchRunId,
122
+ revisionId: authority.revisionId,
123
+ leaseClaimId: input.operationId,
124
+ ...(identity
125
+ ? {
126
+ controllerAnchor: controllerIdentityAnchor(identity),
127
+ skipProtocolPreflight: true,
128
+ }
129
+ : { skipProtocolPreflight: true }),
130
+ stateExtras: {
131
+ attemptKind: "artifact-continuation",
132
+ continuation: lineage,
133
+ },
134
+ });
135
+ }
136
+ catch (error) {
137
+ if (!existingLease) {
138
+ const lease = await readAttemptLease(input.repoRoot, authority.featureId, authority.taskId);
139
+ const ownsLease = lease?.workerRunId === authority.childWorkerRunId &&
140
+ lease.revisionId === authority.revisionId &&
141
+ lease.claimId === input.operationId;
142
+ if (ownsLease && lease) {
143
+ await releaseAttemptLease({
144
+ repoRoot: input.repoRoot,
145
+ featureId: authority.featureId,
146
+ taskId: authority.taskId,
147
+ token: lease.token,
148
+ workerRunId: authority.childWorkerRunId,
149
+ }).catch(() => undefined);
150
+ }
151
+ const current = await readTaskPoolState(input.repoRoot, {
152
+ featureId: authority.featureId,
153
+ taskId: authority.taskId,
154
+ });
155
+ if (ownsLease &&
156
+ priorState &&
157
+ current?.workerRunId === authority.childWorkerRunId &&
158
+ current.continuation?.operationId === input.operationId) {
159
+ await writeTaskPoolState(input.repoRoot, priorState);
160
+ }
161
+ }
162
+ throw error;
163
+ }
164
+ }
165
+ /**
166
+ * Terminal projection is idempotent. needs-reconcile deliberately retains the
167
+ * Running state and Task lease so recovery cannot be raced by a new attempt.
168
+ */
169
+ export async function settleWorkerContinuation(input) {
170
+ if (input.state === "needs-reconcile")
171
+ return;
172
+ if (input.state !== "succeeded" &&
173
+ input.state !== "failed" &&
174
+ input.state !== "timed-out")
175
+ return;
176
+ const { authority } = input.resolved;
177
+ const lineage = buildLineage(input.resolved, input.operation.operationId);
178
+ const existing = await findRunByWorkerRunId(input.repoRoot, authority.childWorkerRunId);
179
+ let terminalRun = existing;
180
+ if (existing) {
181
+ assertMatchingTerminalRecord(existing, lineage, authority);
182
+ }
183
+ else {
184
+ const located = await locateDagRun(input.repoRoot, authority.childWorkerRunId);
185
+ const succeeded = input.state === "succeeded" && input.info.ok;
186
+ terminalRun = {
187
+ schemaVersion: 1,
188
+ batchRunId: authority.batchRunId,
189
+ workerRunId: authority.childWorkerRunId,
190
+ taskId: authority.taskId,
191
+ featureId: authority.featureId,
192
+ status: succeeded ? "succeeded" : "failed",
193
+ attemptKind: "artifact-continuation",
194
+ continuation: lineage,
195
+ harnessTaskId: authority.childWorkerRunId,
196
+ ...(located
197
+ ? { dagPath: path.relative(input.repoRoot, located.runDir).split(path.sep).join("/") }
198
+ : {}),
199
+ recordedAt: input.operation.finishedAt ?? new Date().toISOString(),
200
+ ...(succeeded
201
+ ? {}
202
+ : {
203
+ error: input.operation.errorMessage ?? input.info.errorCode ?? "artifact continuation failed",
204
+ failure: {
205
+ category: "Unknown",
206
+ recommendedFollowUpKind: "manual-review",
207
+ derivedFollowUpTaskId: `${authority.taskId}-continuation-review`,
208
+ source: "runner-error",
209
+ },
210
+ }),
211
+ };
212
+ await recordTaskPoolRun({
213
+ repoRoot: input.repoRoot,
214
+ run: terminalRun,
215
+ });
216
+ }
217
+ if (terminalRun?.status === "succeeded") {
218
+ const parentRun = await findRunByWorkerRunId(input.repoRoot, authority.parentWorkerRunId);
219
+ if (!parentRun || parentRun.status !== "succeeded") {
220
+ throw new Error(`parent Worker outcome is unavailable after continuation: ${authority.parentWorkerRunId}`);
221
+ }
222
+ await writeTaskPoolState(input.repoRoot, {
223
+ schemaVersion: 2,
224
+ featureId: authority.featureId,
225
+ taskId: authority.taskId,
226
+ status: "Done",
227
+ updatedAt: terminalRun.recordedAt,
228
+ workerRunId: authority.parentWorkerRunId,
229
+ ...(parentRun.runRecordPath
230
+ ? { lastRunRecordPath: parentRun.runRecordPath }
231
+ : {}),
232
+ lastContinuation: {
233
+ ...lineage,
234
+ status: "succeeded",
235
+ recordedAt: terminalRun.recordedAt,
236
+ },
237
+ });
238
+ }
239
+ const lease = await readAttemptLease(input.repoRoot, authority.featureId, authority.taskId);
240
+ if (!lease)
241
+ return;
242
+ if (lease.workerRunId !== authority.childWorkerRunId ||
243
+ lease.revisionId !== authority.revisionId ||
244
+ lease.claimId !== input.operation.operationId) {
245
+ throw new Error(`attempt lease ownership drift for ${authority.featureId}/${authority.taskId}`);
246
+ }
247
+ await releaseAttemptLease({
248
+ repoRoot: input.repoRoot,
249
+ featureId: authority.featureId,
250
+ taskId: authority.taskId,
251
+ token: lease.token,
252
+ workerRunId: authority.childWorkerRunId,
253
+ });
254
+ }
255
+ function buildLineage(resolved, operationId) {
256
+ return {
257
+ schemaVersion: 1,
258
+ kind: "artifact-continuation",
259
+ parentWorkerRunId: resolved.authority.parentWorkerRunId,
260
+ parentDagRunId: resolved.parentDagRunId,
261
+ childWorkerRunId: resolved.authority.childWorkerRunId,
262
+ revisionId: resolved.authority.revisionId,
263
+ operationId,
264
+ };
265
+ }
266
+ function assertMatchingTerminalRecord(run, lineage, authority) {
267
+ if (run.attemptKind !== "artifact-continuation" ||
268
+ run.featureId !== authority.featureId ||
269
+ run.taskId !== authority.taskId ||
270
+ run.batchRunId !== authority.batchRunId ||
271
+ run.continuation?.parentWorkerRunId !== lineage.parentWorkerRunId ||
272
+ run.continuation?.parentDagRunId !== lineage.parentDagRunId ||
273
+ run.continuation?.childWorkerRunId !== lineage.childWorkerRunId ||
274
+ run.continuation?.revisionId !== lineage.revisionId ||
275
+ run.continuation?.operationId !== lineage.operationId) {
276
+ throw new Error(`Worker continuation terminal record conflict: ${authority.childWorkerRunId}`);
277
+ }
278
+ }
@@ -2362,6 +2362,8 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
2362
2362
  durationMs: readNumber(node, "durationMs"),
2363
2363
  startedAt: readString(node, "startedAt"),
2364
2364
  finishedAt: readString(node, "finishedAt"),
2365
+ lastMeaningfulProgressAt: readString(node, "lastMeaningfulProgressAt"),
2366
+ livenessStatus: readString(node, "livenessStatus"),
2365
2367
  ...(outputPreview ? { outputPreview } : {}),
2366
2368
  ...(errorPreview ? { errorPreview } : {}),
2367
2369
  ...(attempts && attempts.length > 0
@@ -2407,6 +2409,8 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
2407
2409
  durationMs: readNumber(node, "durationMs"),
2408
2410
  startedAt: readString(node, "startedAt"),
2409
2411
  finishedAt: readString(node, "finishedAt"),
2412
+ lastMeaningfulProgressAt: readString(node, "lastMeaningfulProgressAt"),
2413
+ livenessStatus: readString(node, "livenessStatus"),
2410
2414
  ...(outputPreview ? { outputPreview } : {}),
2411
2415
  ...(errorPreview ? { errorPreview } : {}),
2412
2416
  ...(attempts && attempts.length > 0
@@ -1,7 +1,7 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import path from "node:path";
3
3
  import { findPackageRoot, readPackageName, readPackageVersion, } from "../../shared/package-metadata.js";
4
- import { repoFingerprintV1 } from "../console/repo-fingerprint.js";
4
+ import { repoFingerprintV1 } from "../../infrastructure/console/repo-fingerprint.js";
5
5
  /** Observe `/api/health` schema version (independent of Console product health). */
6
6
  export const OBSERVE_HEALTH_SCHEMA_VERSION = 1;
7
7
  function routeExists(routes, method, pathname) {