@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.11

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 (338) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +349 -97
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/advance.js +12 -0
  6. package/dist/application/task-lifecycle/recommendations.js +13 -2
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +23 -5
  9. package/dist/cli/program.js +21 -2
  10. package/dist/cli/update/notifier.js +2 -2
  11. package/dist/cli/update/runtime-activity.js +1 -29
  12. package/dist/cli.js +2 -1
  13. package/dist/commands/client-recovery.js +3 -0
  14. package/dist/commands/dag-follow-up.js +138 -0
  15. package/dist/commands/dag-rerun.js +55 -1
  16. package/dist/commands/init-model-catalog.js +464 -0
  17. package/dist/commands/init-upgrade.js +265 -97
  18. package/dist/commands/init.js +455 -28
  19. package/dist/commands/inspect-next.js +8 -0
  20. package/dist/commands/task-advance.js +19 -0
  21. package/dist/executors/dag-pi-executor.js +2370 -64
  22. package/dist/executors/pi-executor.js +11 -2
  23. package/dist/executors/pi-extension-resolver.js +233 -0
  24. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  25. package/dist/executors/pi-read-budget-policy.js +239 -0
  26. package/dist/executors/pi-sdk-executor.js +225 -33
  27. package/dist/executors/pi-writer-tool-policy.js +57 -1
  28. package/dist/executors/shell-executor.js +1258 -224
  29. package/dist/executors/shell-write-guard.js +14 -0
  30. package/dist/executors/workspace-write-snapshot.js +68 -0
  31. package/dist/infrastructure/harness/atomic-write.js +23 -0
  32. package/dist/shared/dag-failure-category.js +150 -0
  33. package/dist/shared/dag-prompt-override.js +27 -0
  34. package/dist/shared/openspec-spec.js +70 -4
  35. package/dist/shared/operator/capabilities.js +39 -0
  36. package/dist/shared/playwright-cli-command-policy.js +15 -0
  37. package/dist/shared/update/console-notifier.js +100 -0
  38. package/dist/{cli → shared}/update/npm-client.js +70 -15
  39. package/dist/{cli → shared}/update/state.js +41 -13
  40. package/dist/task/config-types.js +50 -5
  41. package/dist/task/contract/apply.js +11 -1
  42. package/dist/task/contract/constants.js +2 -0
  43. package/dist/task/contract/hash.js +3 -0
  44. package/dist/task/contract/observe.js +25 -4
  45. package/dist/task/contract/paths.js +2 -1
  46. package/dist/task/contract/project.js +18 -1
  47. package/dist/task/contract/schema.js +4 -1
  48. package/dist/task/contract/transaction.js +12 -0
  49. package/dist/task/frontend-project-capability.js +199 -18
  50. package/dist/task/source-prepare/completeness.js +188 -0
  51. package/dist/task/source-prepare/fragment-inventory.js +477 -0
  52. package/dist/task/source-prepare/index.js +3 -0
  53. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  54. package/dist/task/source-prepare/ledger-review.js +214 -0
  55. package/dist/task/source-prepare/ledger.js +545 -0
  56. package/dist/task/source-prepare/parse-intent.js +24 -4
  57. package/dist/task/source-prepare/prepare.js +298 -1
  58. package/dist/task/source-prepare/semantic-intake.js +25 -5
  59. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  60. package/dist/task/source-prepare/types.js +22 -0
  61. package/dist/task/source-references.js +22 -1
  62. package/dist/worker/cli.js +18 -2
  63. package/dist/worker/console/chat/chat-event-store.js +75 -5
  64. package/dist/worker/console/chat/context-panel.js +7 -0
  65. package/dist/worker/console/chat/deferred-turn.js +12 -0
  66. package/dist/worker/console/chat/operation-card.js +8 -1
  67. package/dist/worker/console/chat/pi-console-config.js +81 -10
  68. package/dist/worker/console/chat/pi-runtime.js +331 -18
  69. package/dist/worker/console/chat/provider-error.js +98 -0
  70. package/dist/worker/console/chat/routes.js +395 -59
  71. package/dist/worker/console/chat/session-stats.js +179 -0
  72. package/dist/worker/console/chat/session-store.js +93 -63
  73. package/dist/worker/console/chat/todos.js +115 -0
  74. package/dist/worker/console/chat/tool-preview.js +42 -0
  75. package/dist/worker/console/chat/turn-process.js +20 -55
  76. package/dist/worker/console/chat/user-questions.js +266 -0
  77. package/dist/worker/console/chat/workspace-landing.js +79 -0
  78. package/dist/worker/console/console-handoff.js +82 -0
  79. package/dist/worker/console/console-update-and-init.js +115 -0
  80. package/dist/worker/console/console-update-runtime.js +84 -0
  81. package/dist/worker/console/dag-execution-receipt.js +33 -0
  82. package/dist/worker/console/draft-store.js +49 -0
  83. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  84. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  85. package/dist/worker/console/index.js +3 -0
  86. package/dist/worker/console/inspect-split.js +18 -0
  87. package/dist/worker/console/native-directory-picker.js +13 -0
  88. package/dist/worker/console/operation-run-facts.js +19 -3
  89. package/dist/worker/console/operation-store.js +227 -7
  90. package/dist/worker/console/operator-actions.js +388 -0
  91. package/dist/worker/console/operator-surface-health.js +1 -0
  92. package/dist/worker/console/operator-user-error.js +2 -2
  93. package/dist/worker/console/prd-intake-bridge.js +13 -0
  94. package/dist/worker/console/routes.js +355 -3
  95. package/dist/worker/console/security.js +67 -13
  96. package/dist/worker/console/server.js +609 -164
  97. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-CXj_GnSb.js} +1 -1
  98. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BZp6JAp7.js} +1 -1
  99. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-DfpcEuYU.js} +1 -1
  100. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-_Gf0xadb.js} +1 -1
  101. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Ct2QPCmv.js} +1 -1
  102. package/dist/worker/console/static/assets/channel-3TxJgYaH.js +1 -0
  103. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BfklkzKl.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-Dy25vZJV.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-BFlCRZep.js} +1 -1
  106. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-op3oVxIE.js} +1 -1
  107. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-C0R7rzF2.js} +1 -1
  108. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-Bt44TCGy.js} +1 -1
  109. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-_uEE_XFx.js} +1 -1
  110. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-C3TOYGZ9.js} +1 -1
  111. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-Cz60oBPV.js} +1 -1
  112. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-8Bik0qis.js} +1 -1
  113. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BHIkXpp3.js +1 -0
  114. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BHIkXpp3.js +1 -0
  115. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C2QIOA4a.js} +1 -1
  116. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CSH_yUUd.js} +1 -1
  117. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BDLw2XFM.js} +1 -1
  118. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-CcD9ZtF4.js} +1 -1
  119. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DqwtkBsI.js} +1 -1
  120. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BWVEcqsC.js} +1 -1
  121. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-M-WAbtQi.js} +1 -1
  122. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DKW7SixP.js} +1 -1
  123. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-PHHPPtrj.js} +1 -1
  124. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-BQD-B4RX.js} +1 -1
  125. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-BFvULb51.js} +1 -1
  126. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-Bw-aXTCb.js} +1 -1
  127. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BGKw04Qx.js} +1 -1
  128. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-RqKaPR-I.js} +1 -1
  129. package/dist/worker/console/static/assets/index-B_D8rbWc.js +407 -0
  130. package/dist/worker/console/static/assets/index-BdNx6fj0.css +1 -0
  131. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-YO5dnzrJ.js} +1 -1
  132. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-Bi1VZHMq.js} +1 -1
  133. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-Bszls1DD.js} +1 -1
  134. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-PhzeaZ09.js} +1 -1
  135. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-BsjbDoXi.js} +1 -1
  136. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-0B7NnWKk.js} +5 -5
  137. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BJr4Fj-q.js} +1 -1
  138. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-moC4fpGB.js} +1 -1
  139. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BEw37-2c.js} +1 -1
  140. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-Cq6LyasU.js} +1 -1
  141. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-DUCMcK0D.js} +1 -1
  142. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-C3upTZm7.js} +1 -1
  143. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-BI_gMsCW.js} +1 -1
  144. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-YFOIRzfN.js} +1 -1
  145. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-dOnB7UDD.js} +1 -1
  146. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BXUniaIh.js} +1 -1
  147. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cdi6UhLa.js +1 -0
  148. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-2tA4wTNu.js} +2 -2
  149. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D-RJBbb0.js +8 -0
  150. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-DO0HkJXC.js} +1 -1
  151. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DvIOzixv.js} +1 -1
  152. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DXDZ0cTj.js} +1 -1
  153. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-B32Ark0D.js} +1 -1
  154. package/dist/worker/console/static/index.html +2 -2
  155. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  156. package/dist/worker/console/static-src/app/console-types.js +68 -4
  157. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  158. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  159. package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
  160. package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
  161. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  162. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  163. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  164. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  165. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  166. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  167. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  168. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  169. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  170. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  171. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  172. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  173. package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
  174. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  175. package/dist/worker/console/static-src/operator-chat/useChatStream.js +183 -48
  176. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  177. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  178. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  179. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  180. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  181. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  182. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  183. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  184. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  185. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  186. package/dist/worker/console/workspace-context.js +344 -0
  187. package/dist/worker/console/workspace-registry.js +214 -0
  188. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  189. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  190. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  191. package/dist/worker/observability/init-runtime-activity.js +26 -0
  192. package/dist/worker/observability/read-model.js +22 -0
  193. package/dist/worker/observe/node-input.js +160 -3
  194. package/dist/worker/observe/node-process.js +377 -0
  195. package/dist/worker/observe/routes.js +224 -3
  196. package/dist/worker/observe/server.js +4 -0
  197. package/dist/worker/observe/shell-handler-keys.js +34 -0
  198. package/dist/worker/observe/static/api.js +90 -4
  199. package/dist/worker/observe/static/app.js +18 -70
  200. package/dist/worker/observe/static/constants.js +8 -1
  201. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  202. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  203. package/dist/worker/observe/static/dag-node-purpose.js +208 -0
  204. package/dist/worker/observe/static/format.js +60 -0
  205. package/dist/worker/observe/static/index.html +6 -1
  206. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  207. package/dist/worker/observe/static/markdown-render.js +20 -1
  208. package/dist/worker/observe/static/operator-chrome.css +145 -20
  209. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  210. package/dist/worker/observe/static/operator-chrome.js +330 -96
  211. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  212. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  213. package/dist/worker/observe/static/router.d.ts +46 -0
  214. package/dist/worker/observe/static/router.js +25 -1
  215. package/dist/worker/observe/static/state.d.ts +50 -0
  216. package/dist/worker/observe/static/state.js +184 -1
  217. package/dist/worker/observe/static/styles.css +395 -0
  218. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  219. package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
  220. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  221. package/dist/worker/observe/static/views/dag.js +14 -5
  222. package/dist/worker/observe/static/views/dashboard.js +6 -4
  223. package/dist/worker/observe/static/views/failures.js +5 -2
  224. package/dist/worker/observe/static/views/night.js +2 -2
  225. package/dist/worker/observe/static/views/pool.js +1 -0
  226. package/dist/worker/observe/static/views/run.js +3 -1
  227. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  228. package/dist/worker/observe/static/views/task.js +56 -1
  229. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
  230. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  231. package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
  232. package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
  233. package/dist/workflows/dag/contract-output-registry.js +15 -0
  234. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  235. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  236. package/dist/workflows/dag/dynamic-runtime/map.js +2 -1
  237. package/dist/workflows/dag/failure-category.js +7 -116
  238. package/dist/workflows/dag/frontend-closeout.js +221 -0
  239. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  240. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  241. package/dist/workflows/dag/frontend-implementation-contract.js +538 -166
  242. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  243. package/dist/workflows/dag/frontend-prewrite-gate.js +388 -392
  244. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  245. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  246. package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
  247. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  248. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  249. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  250. package/dist/workflows/dag/frontend-repair.js +7 -424
  251. package/dist/workflows/dag/frontend-review-context.js +288 -14
  252. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  253. package/dist/workflows/dag/frontend-risk.js +15 -2
  254. package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
  255. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  256. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  257. package/dist/workflows/dag/frontend-shape.js +427 -0
  258. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  259. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  260. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  261. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  262. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  263. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  264. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  265. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  266. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  267. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  268. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  269. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  270. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  271. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  272. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  273. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  274. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  275. package/dist/workflows/dag/frontend-writer-admission.js +272 -0
  276. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  277. package/dist/workflows/dag/init-hybrid.js +1072 -660
  278. package/dist/workflows/dag/interrupt-request.js +10 -3
  279. package/dist/workflows/dag/lifecycle.js +3 -3
  280. package/dist/workflows/dag/node-execution.js +552 -58
  281. package/dist/workflows/dag/prompt.js +44 -19
  282. package/dist/workflows/dag/recovery-lease.js +80 -0
  283. package/dist/workflows/dag/report.js +37 -1
  284. package/dist/workflows/dag/rerun-feedback.js +124 -1
  285. package/dist/workflows/dag/rerun-plan.js +144 -5
  286. package/dist/workflows/dag/rerun-run.js +81 -6
  287. package/dist/workflows/dag/rerun-task.js +29 -0
  288. package/dist/workflows/dag/retry-policy.js +318 -8
  289. package/dist/workflows/dag/runner.js +429 -122
  290. package/dist/workflows/dag/scheduler.js +114 -16
  291. package/dist/workflows/dag/structured-output-repair.js +712 -0
  292. package/dist/workflows/dag/types.js +338 -31
  293. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  294. package/dist/workflows/dag/validate.js +92 -32
  295. package/docs/README.md +2 -0
  296. package/docs/architecture/evolution.md +45 -0
  297. package/docs/init-surface.manifest.json +9 -0
  298. package/docs/operations/README.md +1 -1
  299. package/docs/operations/local-development-environment.md +21 -0
  300. package/docs/templates/README.md +2 -1
  301. package/docs/templates/agent-dag-report.schema.json +8 -2
  302. package/docs/templates/agent-dag.schema.json +63 -1
  303. package/docs/templates/backend-test-dag.json +265 -237
  304. package/docs/templates/frontend-implementation-contract.schema.json +684 -24
  305. package/docs/templates/frontend-test-case-checklist.md +2 -2
  306. package/docs/templates/frontend-test-dag.json +8 -10
  307. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  308. package/docs/templates/init-managed-agents.md +13 -3
  309. package/docs/templates/spec-registry.schema.json +45 -0
  310. package/harness.json +1 -1
  311. package/package.json +7 -1
  312. package/scripts/next-info.mjs +356 -0
  313. package/scripts/next-publish-gate.mjs +238 -0
  314. package/scripts/release-source-binding.mjs +251 -0
  315. package/skills/codebase-scout/SKILL.md +1 -1
  316. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  317. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  318. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  319. package/skills/frontend-bounded-implement/SKILL.md +8 -14
  320. package/skills/frontend-design-review/SKILL.md +20 -41
  321. package/skills/frontend-implementation/SKILL.md +3 -4
  322. package/skills/frontend-implementation/references/design-spec.md +15 -10
  323. package/skills/frontend-implementation/references/node-contracts.md +23 -13
  324. package/skills/frontend-review/SKILL.md +22 -14
  325. package/skills/frontend-review/references/review-findings.md +6 -7
  326. package/skills/loop-agent/references/command-reference.md +12 -4
  327. package/skills/loop-agent/references/hybrid-dag.md +8 -1
  328. package/skills/playwright-cli/SKILL.md +23 -1
  329. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  330. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  331. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  332. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  333. package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
  334. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  335. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  336. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
  337. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  338. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
@@ -0,0 +1,214 @@
1
+ /**
2
+ * User-level Workspace Registry (2026-08-22 multi-workspace foundation, W1).
3
+ *
4
+ * Persists the set of registered Console workspaces at
5
+ * `<appDataRoot>/workspaces.json` (user level, shared across Console boots),
6
+ * independent of per-repo fingerprint namespaces.
7
+ *
8
+ * Invariants:
9
+ * - workspaceId is stable per normalized realpath: it is derived from
10
+ * sha256("loop-console-workspace-v1\0" + realpath) and never minted twice.
11
+ * - REMOVE is soft (hidden=true); on-disk directories, AppData namespaces,
12
+ * Sessions, Operations and DAGs are never deleted.
13
+ * - Re-adding a previously removed workspace RESTORES the original record
14
+ * (same workspaceId + repoFingerprint; only lastAccessedAt advances).
15
+ * - Duplicate visible realpath is rejected; add failures leave the registry
16
+ * byte-identical (same ids and order).
17
+ */
18
+ import { createHash, randomBytes } from "node:crypto";
19
+ import { realpathSync, statSync } from "node:fs";
20
+ import path from "node:path";
21
+ import { readJsonIfExists, writeSecureJson, } from "./app-data.js";
22
+ import { normalizeWorktreeRealpath, repoFingerprintV1, } from "./repo-fingerprint.js";
23
+ export const WORKSPACE_REGISTRY_SCHEMA_VERSION = 1;
24
+ /** Stable workspaceId: sha256("loop-console-workspace-v1\0" + normalizedRealpath). */
25
+ export function workspaceIdForRealpath(normalizedRealpath) {
26
+ return createHash("sha256")
27
+ .update(`loop-console-workspace-v1\0${normalizedRealpath}`, "utf8")
28
+ .digest("hex");
29
+ }
30
+ export class WorkspaceRegistry {
31
+ appData;
32
+ options;
33
+ file;
34
+ cache;
35
+ constructor(appData, options = {}) {
36
+ this.appData = appData;
37
+ this.options = options;
38
+ this.file = path.join(appData.root, "workspaces.json");
39
+ }
40
+ /** Auto-register the startup repoRoot (idempotent). */
41
+ async ensureStartupWorkspace(repoRoot) {
42
+ const record = await this.add(repoRoot);
43
+ if (record.ok)
44
+ return record.record;
45
+ // Startup root may already be visible (duplicate) or previously hidden —
46
+ // re-adding a hidden record restores it via add(); the only expected
47
+ // failure left is DUPLICATE (already visible), which returns the existing.
48
+ const existing = await this.findByRealpath(repoRoot);
49
+ if (existing)
50
+ return existing;
51
+ throw new Error(`workspace registry: startup root rejected: ${record.code}`);
52
+ }
53
+ /**
54
+ * Register (or restore) a workspace directory.
55
+ * Failures never mutate the persisted registry.
56
+ * `options.name` overrides the default directory-name label.
57
+ */
58
+ async add(rawPath, options = {}) {
59
+ const trimmed = rawPath.trim();
60
+ if (!trimmed) {
61
+ return {
62
+ ok: false,
63
+ code: "INVALID_PATH",
64
+ message: "path is required",
65
+ };
66
+ }
67
+ let real;
68
+ try {
69
+ real = realpathSync(path.resolve(trimmed));
70
+ }
71
+ catch {
72
+ return {
73
+ ok: false,
74
+ code: "NOT_A_DIRECTORY",
75
+ message: `path does not exist: ${trimmed}`,
76
+ };
77
+ }
78
+ let st;
79
+ try {
80
+ st = statSync(real);
81
+ }
82
+ catch {
83
+ return {
84
+ ok: false,
85
+ code: "UNREADABLE",
86
+ message: `path is not readable: ${trimmed}`,
87
+ };
88
+ }
89
+ if (!st.isDirectory()) {
90
+ return {
91
+ ok: false,
92
+ code: "NOT_A_DIRECTORY",
93
+ message: `path is not a directory: ${trimmed}`,
94
+ };
95
+ }
96
+ const normalized = normalizeWorktreeRealpath(real);
97
+ const existing = await this.findByRealpathNormalized(normalized);
98
+ if (existing && !existing.hidden) {
99
+ return {
100
+ ok: false,
101
+ code: "DUPLICATE",
102
+ message: `workspace already registered: ${existing.displayPath}`,
103
+ };
104
+ }
105
+ const nowIso = (this.options.now ?? (() => new Date()))().toISOString();
106
+ if (existing) {
107
+ // Restore the soft-removed record; identity (workspaceId,
108
+ // repoFingerprint, createdAt, order) is preserved.
109
+ existing.hidden = undefined;
110
+ existing.lastAccessedAt = nowIso;
111
+ if (options.name && options.name.trim()) {
112
+ existing.name = options.name.trim();
113
+ }
114
+ const registry = await this.load();
115
+ await this.persist(registry);
116
+ return { ok: true, record: existing, registryUnchanged: false };
117
+ }
118
+ const record = {
119
+ workspaceId: workspaceIdForRealpath(normalized),
120
+ name: options.name?.trim() || path.basename(normalized) || normalized,
121
+ displayPath: normalized,
122
+ repoRoot: real,
123
+ repoFingerprint: repoFingerprintV1(real),
124
+ createdAt: nowIso,
125
+ lastAccessedAt: nowIso,
126
+ };
127
+ const registry = await this.load();
128
+ registry.workspaces.push(record);
129
+ await this.persist(registry);
130
+ return { ok: true, record, registryUnchanged: false };
131
+ }
132
+ /** Visible list in canonical insertion order. */
133
+ async listVisible() {
134
+ const registry = await this.load();
135
+ return registry.workspaces.filter((workspace) => {
136
+ if (workspace.hidden)
137
+ return false;
138
+ try {
139
+ return statSync(workspace.repoRoot).isDirectory();
140
+ }
141
+ catch {
142
+ // Keep historical registry data, but do not project stale test roots
143
+ // into the Workspace browser after their directories disappear.
144
+ return false;
145
+ }
146
+ });
147
+ }
148
+ async get(workspaceId) {
149
+ const registry = await this.load();
150
+ return registry.workspaces.find((w) => w.workspaceId === workspaceId);
151
+ }
152
+ /** Get a VISIBLE workspace by id (removed workspaces are not operable). */
153
+ async getVisible(workspaceId) {
154
+ const record = await this.get(workspaceId);
155
+ if (!record || record.hidden)
156
+ return undefined;
157
+ try {
158
+ return statSync(record.repoRoot).isDirectory() ? record : undefined;
159
+ }
160
+ catch {
161
+ return undefined;
162
+ }
163
+ }
164
+ /** Soft-remove: hides the record; never touches directories or AppData. */
165
+ async remove(workspaceId) {
166
+ const registry = await this.load();
167
+ const record = registry.workspaces.find((w) => w.workspaceId === workspaceId);
168
+ if (!record || record.hidden)
169
+ return false;
170
+ record.hidden = true;
171
+ await this.persist(registry);
172
+ return true;
173
+ }
174
+ async findByRealpath(rawPath) {
175
+ let normalized;
176
+ try {
177
+ normalized = normalizeWorktreeRealpath(path.resolve(rawPath.trim()));
178
+ }
179
+ catch {
180
+ return undefined;
181
+ }
182
+ return this.findByRealpathNormalized(normalized);
183
+ }
184
+ async findByRealpathNormalized(normalized) {
185
+ const registry = await this.load();
186
+ return registry.workspaces.find((w) => w.displayPath === normalized);
187
+ }
188
+ async load() {
189
+ if (this.cache)
190
+ return this.cache;
191
+ const raw = await readJsonIfExists(this.file).catch(() => undefined);
192
+ const registry = {
193
+ schemaVersion: 1,
194
+ workspaces: raw && Array.isArray(raw.workspaces)
195
+ ? raw.workspaces.filter((w) => Boolean(w) &&
196
+ typeof w === "object" &&
197
+ typeof w.workspaceId === "string" &&
198
+ typeof w.displayPath === "string" &&
199
+ typeof w.repoRoot === "string")
200
+ : [],
201
+ };
202
+ this.cache = registry;
203
+ return registry;
204
+ }
205
+ async persist(registry) {
206
+ // writeSecureJson is atomic; a reader mid-write sees the previous bytes.
207
+ await writeSecureJson(this.file, registry);
208
+ this.cache = registry;
209
+ }
210
+ }
211
+ /** Deterministic-looking nonce reserved for tests requiring unique roots. */
212
+ export function mintWorkspaceNonce() {
213
+ return randomBytes(8).toString("hex");
214
+ }
@@ -292,9 +292,13 @@ function assertControllerIdentityUnchangedBeforeSpawn(identity) {
292
292
  }
293
293
  }
294
294
  function controllerIdentityDriftError(message, cause) {
295
- return new Error(`controller identity changed before spawn: ${message}`, {
296
- cause,
297
- });
295
+ // Preserve the raw drift detail verbatim; the actionable suffix serves
296
+ // every consumer surface — operation errorMessage, SSE events, and agent/
297
+ // API callers that never reach the UI translation layer — with the exact
298
+ // recovery step: the running Console holds a stale controller fingerprint
299
+ // after rebuilds or commits touching fingerprint-scoped paths
300
+ // (package.json, bin/**, dist/**, skills/**).
301
+ return new Error(`controller identity changed before spawn: ${message}. Fix: restart the Console process so it re-resolves the current build, then retry the operation; avoid rebuilding or committing fingerprint-scoped files (package.json, bin/**, dist/**, skills/**) mid-session.`, { cause });
298
302
  }
299
303
  /**
300
304
  * Detect whether loopAgentBin is a Node-like executable and baseArgs
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ import { computeFrontendSplitDigest } from "../../workflows/dag/frontend-split-orchestrator.js";
3
+ const idSchema = z.string().trim().min(1);
4
+ const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
5
+ const positiveIntegerSchema = z.number().int().positive();
6
+ const pathSchema = z.string().trim().min(1);
7
+ export const frontendSplitTaskMaterializationRequestSchema = z.object({
8
+ kind: z.enum(["child", "integration"]),
9
+ parentOperationId: idSchema,
10
+ manifestDigest: digestSchema,
11
+ generation: positiveIntegerSchema,
12
+ taskId: idSchema,
13
+ operationId: idSchema,
14
+ payloadDigest: digestSchema,
15
+ allowedPaths: z.array(pathSchema),
16
+ writeSet: z.array(pathSchema),
17
+ verifyRefs: z.array(idSchema),
18
+ }).strict();
19
+ export const frontendSplitTaskMaterializationResultSchema = z.object({
20
+ idempotencyKey: digestSchema,
21
+ requestDigest: digestSchema,
22
+ taskId: idSchema,
23
+ operationId: idSchema,
24
+ }).strict();
25
+ function identityPayload(request) {
26
+ return { kind: request.kind, parentOperationId: request.parentOperationId, manifestDigest: request.manifestDigest, generation: request.generation, taskId: request.taskId, operationId: request.operationId };
27
+ }
28
+ export class FrontendSplitTaskMaterializer {
29
+ backend;
30
+ completed = new Map();
31
+ inFlight = new Map();
32
+ constructor(backend) {
33
+ this.backend = backend;
34
+ }
35
+ async materialize(raw) {
36
+ const request = frontendSplitTaskMaterializationRequestSchema.parse(raw);
37
+ const idempotencyKey = computeFrontendSplitDigest(identityPayload(request));
38
+ const requestDigest = computeFrontendSplitDigest(request);
39
+ const existing = this.completed.get(idempotencyKey);
40
+ if (existing) {
41
+ if (existing.requestDigest !== requestDigest)
42
+ throw new Error("frontend split materialization payload drift");
43
+ return existing;
44
+ }
45
+ const pending = this.inFlight.get(idempotencyKey);
46
+ if (pending) {
47
+ const result = await pending;
48
+ if (result.requestDigest !== requestDigest)
49
+ throw new Error("frontend split materialization payload drift");
50
+ return result;
51
+ }
52
+ const promise = this.perform(request, idempotencyKey, requestDigest);
53
+ this.inFlight.set(idempotencyKey, promise);
54
+ try {
55
+ const result = await promise;
56
+ this.completed.set(idempotencyKey, result);
57
+ return result;
58
+ }
59
+ finally {
60
+ this.inFlight.delete(idempotencyKey);
61
+ }
62
+ }
63
+ async perform(request, idempotencyKey, requestDigest) {
64
+ const task = await this.backend.ensureTask({ idempotencyKey, taskId: request.taskId, payloadDigest: requestDigest, request });
65
+ if (!task.complete || task.taskId !== request.taskId)
66
+ throw new Error("frontend split task materialization incomplete or drifted");
67
+ const operation = await this.backend.ensureOperation({ idempotencyKey, taskId: request.taskId, operationId: request.operationId, payloadDigest: requestDigest, request });
68
+ if (!operation.complete || operation.taskId !== request.taskId || operation.operationId !== request.operationId)
69
+ throw new Error("frontend split operation materialization incomplete or drifted");
70
+ return frontendSplitTaskMaterializationResultSchema.parse({ idempotencyKey, requestDigest, taskId: request.taskId, operationId: request.operationId });
71
+ }
72
+ }
@@ -129,11 +129,14 @@ export async function resolveRevisionDigestsForTaskSpec(input) {
129
129
  };
130
130
  }
131
131
  /** Union TaskSpec allowed_paths with workflow-owned write roots. */
132
- export function expandAllowedPathsForWorkflow(workflow, allowedPaths) {
132
+ export function expandAllowedPathsForWorkflow(workflow, allowedPaths, options) {
133
133
  const extras = workflow === "backend-test"
134
134
  ? ["testcase/**", "docs/test-reports/**"]
135
135
  : workflow === "frontend-test"
136
- ? ["testcase/frontend/**", "docs/test-reports/**"]
136
+ ? [
137
+ `${options?.frontendTestRoot ?? "testcase/frontend"}/**`,
138
+ "docs/test-reports/**",
139
+ ]
137
140
  : [];
138
141
  return Array.from(new Set([...allowedPaths, ...extras]));
139
142
  }
@@ -0,0 +1,26 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { buildGlobalSnapshot } from "./read-model.js";
4
+ /**
5
+ * Read-only runtime guard shared by Console maintenance and init-surface
6
+ * writes. Worker activity remains owned by the Observe projection so both
7
+ * callers use one canonical liveness interpretation.
8
+ */
9
+ export async function readInitRuntimeActivity(repoRoot) {
10
+ const resolvedRoot = path.resolve(repoRoot);
11
+ const activeDagRoot = path.join(resolvedRoot, ".harness", "dag-runs", "active");
12
+ const entries = await readdir(activeDagRoot, { withFileTypes: true }).catch(() => []);
13
+ const activeDagRunIds = entries
14
+ .filter((entry) => entry.isDirectory() && entry.name !== ".gitkeep")
15
+ .map((entry) => entry.name)
16
+ .sort();
17
+ const snapshot = await buildGlobalSnapshot({ repoRoot: resolvedRoot });
18
+ return {
19
+ activeDagRunIds,
20
+ activeWorkerBatches: snapshot.health.worker.activeBatches,
21
+ activeWorkerTasks: snapshot.health.worker.activeTasks,
22
+ ...(snapshot.projectionError
23
+ ? { workerProjectionError: snapshot.projectionError.message }
24
+ : {}),
25
+ };
26
+ }
@@ -1483,6 +1483,9 @@ function mergeDagRun(existing, incoming) {
1483
1483
  computeDurationMs(startedAt, finishedAt);
1484
1484
  return {
1485
1485
  dagRunId: incoming.dagRunId,
1486
+ ...((incoming.taskId ?? existing.taskId)
1487
+ ? { taskId: incoming.taskId ?? existing.taskId }
1488
+ : {}),
1486
1489
  status: incoming.status ?? existing.status,
1487
1490
  lifecycle: incoming.lifecycle ?? existing.lifecycle,
1488
1491
  executionMode: incoming.executionMode ?? existing.executionMode,
@@ -1914,8 +1917,10 @@ async function parseDagStateFile(statePath, now) {
1914
1917
  }
1915
1918
  : undefined;
1916
1919
  const failureCategory = readString(parsed, "failureCategory");
1920
+ const boundTaskId = await readRunSpecTaskBinding(runSpecPath);
1917
1921
  return {
1918
1922
  dagRunId,
1923
+ ...(boundTaskId ? { taskId: boundTaskId } : {}),
1919
1924
  status,
1920
1925
  ...(lifecycle ? { lifecycle } : {}),
1921
1926
  ...(executionMode ? { executionMode } : {}),
@@ -2435,6 +2440,23 @@ async function safeReadJson(filePath) {
2435
2440
  return undefined;
2436
2441
  return parsed;
2437
2442
  }
2443
+ /** Task binding for a DAG run from run.json — same precedence as
2444
+ * console/resolve-dag-run-for-task (taskContractBinding → sourceBinding →
2445
+ * Hybrid DAG title). */
2446
+ async function readRunSpecTaskBinding(runSpecPath) {
2447
+ const parsed = await safeReadJson(runSpecPath);
2448
+ if (!parsed)
2449
+ return undefined;
2450
+ const binding = parsed.taskContractBinding;
2451
+ const source = parsed.sourceBinding;
2452
+ const fromBinding = (typeof binding?.taskId === "string" && binding.taskId.trim()) ||
2453
+ (typeof source?.taskId === "string" && source.taskId.trim());
2454
+ if (fromBinding)
2455
+ return fromBinding;
2456
+ const title = typeof parsed.title === "string" ? parsed.title.trim() : "";
2457
+ const hybrid = /^Hybrid DAG:\s*(.+)$/i.exec(title);
2458
+ return hybrid?.[1]?.trim() || undefined;
2459
+ }
2438
2460
  function safeParseJson(raw) {
2439
2461
  try {
2440
2462
  const parsed = JSON.parse(raw);
@@ -2,7 +2,8 @@ import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
4
4
  import { resolveDagNodeSkills } from "../../workflows/dag/skills.js";
5
- import { resolveDagNodePromptRedacted, resolveDagRunJson, } from "./dag-run-artifacts.js";
5
+ import { resolveDagNodePromptRedacted, resolveDagRunArtifact, resolveDagRunJson, } from "./dag-run-artifacts.js";
6
+ import { namedShellHandlers } from "./shell-handler-keys.js";
6
7
  export const NODE_INPUT_SCHEMA_VERSION = 1;
7
8
  export const NODE_INPUT_PREVIEW_MAX_BYTES = 64 * 1024;
8
9
  const SHELL_GATE_KEYS = [
@@ -30,9 +31,11 @@ function unavailableBody(dagRunId, nodeId, reason, warnings = []) {
30
31
  summary: null,
31
32
  dependency: null,
32
33
  prompt: null,
34
+ promptCandidates: [],
33
35
  executorInput: null,
34
36
  boundaries: null,
35
37
  promptFingerprint: null,
38
+ piExtensionRunFacts: null,
36
39
  warnings,
37
40
  };
38
41
  }
@@ -137,7 +140,7 @@ function resolveModelHint(spec, task) {
137
140
  }
138
141
  return null;
139
142
  }
140
- function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warnings) {
143
+ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, piExtensionRunFacts, promptCandidates, warnings) {
141
144
  const defaults = spec.defaults && typeof spec.defaults === "object"
142
145
  ? spec.defaults
143
146
  : undefined;
@@ -216,6 +219,7 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
216
219
  : null,
217
220
  failFast: typeof shellRaw.failFast === "boolean" ? shellRaw.failFast : null,
218
221
  gates: shellGateNames(shellRaw),
222
+ handlers: namedShellHandlers(shellRaw),
219
223
  }
220
224
  : null;
221
225
  const staticPreview = staticRaw && typeof staticRaw.resultMarkdown === "string"
@@ -264,10 +268,12 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
264
268
  maxBytes: promptBody.maxBytes,
265
269
  source: promptSource,
266
270
  },
271
+ promptCandidates,
267
272
  executorInput: {
268
273
  kind,
269
274
  skills,
270
275
  modelHint: resolveModelHint(spec, task),
276
+ piExtensions: redactStringList(task.piExtensions),
271
277
  shell,
272
278
  static: staticBlock,
273
279
  },
@@ -284,9 +290,155 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
284
290
  : null,
285
291
  },
286
292
  promptFingerprint: fingerprint,
293
+ piExtensionRunFacts,
287
294
  warnings,
288
295
  };
289
296
  }
297
+ async function loadFailedPromptCandidates(repoRoot, dagRunId, nodeId, warnings) {
298
+ const stateResolved = resolveDagRunArtifact(repoRoot, dagRunId, ["state.json"]);
299
+ if (!stateResolved.ok)
300
+ return [];
301
+ let state;
302
+ try {
303
+ state = JSON.parse(await readFile(stateResolved.result.absolutePath, "utf-8"));
304
+ }
305
+ catch {
306
+ warnings.push("state.json could not be read for failed prompt candidates.");
307
+ return [];
308
+ }
309
+ const nodes = state.nodes && typeof state.nodes === "object" && !Array.isArray(state.nodes)
310
+ ? state.nodes
311
+ : null;
312
+ const node = nodes?.[nodeId] &&
313
+ typeof nodes[nodeId] === "object" &&
314
+ !Array.isArray(nodes[nodeId])
315
+ ? nodes[nodeId]
316
+ : null;
317
+ if (!node)
318
+ return [];
319
+ const failedAttempts = new Set();
320
+ if (Array.isArray(node.attempts)) {
321
+ for (const entry of node.attempts) {
322
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
323
+ continue;
324
+ const record = entry;
325
+ if (record.ok === false &&
326
+ typeof record.attempt === "number" &&
327
+ Number.isSafeInteger(record.attempt) &&
328
+ record.attempt > 0) {
329
+ failedAttempts.add(record.attempt);
330
+ }
331
+ }
332
+ }
333
+ if (node.status === "ERROR" &&
334
+ typeof node.currentAttempt === "number" &&
335
+ Number.isSafeInteger(node.currentAttempt) &&
336
+ node.currentAttempt > 0) {
337
+ failedAttempts.add(node.currentAttempt);
338
+ }
339
+ const candidates = [];
340
+ for (const attempt of [...failedAttempts].sort((a, b) => a - b)) {
341
+ const fileName = `prompt-candidate-attempt-${attempt}.json`;
342
+ const artifact = path.posix.join(nodeId, fileName);
343
+ const resolved = resolveDagRunArtifact(repoRoot, dagRunId, [nodeId, fileName]);
344
+ if (!resolved.ok) {
345
+ warnings.push(`Failed prompt candidate missing for attempt ${attempt}.`);
346
+ continue;
347
+ }
348
+ try {
349
+ const raw = JSON.parse(await readFile(resolved.result.absolutePath, "utf-8"));
350
+ if (raw.schemaVersion !== 1 ||
351
+ raw.attempt !== attempt ||
352
+ typeof raw.failureCategory !== "string" ||
353
+ typeof raw.prompt !== "string" ||
354
+ typeof raw.truncated !== "boolean" ||
355
+ typeof raw.maxBytes !== "number" ||
356
+ !Number.isSafeInteger(raw.maxBytes) ||
357
+ raw.maxBytes < 1 ||
358
+ raw.maxBytes > NODE_INPUT_PREVIEW_MAX_BYTES ||
359
+ typeof raw.sha256 !== "string" ||
360
+ !/^([a-f0-9]{64})$/.test(raw.sha256)) {
361
+ throw new Error("invalid candidate schema");
362
+ }
363
+ const prompt = truncatedField(raw.prompt, raw.maxBytes);
364
+ candidates.push({
365
+ attempt,
366
+ failureCategory: redactSecrets(raw.failureCategory),
367
+ prompt: prompt.text,
368
+ truncated: raw.truncated || prompt.truncated,
369
+ maxBytes: raw.maxBytes,
370
+ sha256Prefix: raw.sha256.slice(0, 12),
371
+ artifact,
372
+ });
373
+ }
374
+ catch {
375
+ warnings.push(`Failed prompt candidate is invalid for attempt ${attempt}.`);
376
+ }
377
+ }
378
+ return candidates;
379
+ }
380
+ /**
381
+ * Load run-time Pi extension facts from <node>/pi-extensions.json (plan
382
+ * 2026-08-22 R2). Returns null when the node declares no piExtensions
383
+ * (no expected artifact). Never throws: unreadable/corrupt artifacts degrade
384
+ * to parseError stubs so the node-input endpoint stays 200.
385
+ */
386
+ async function loadPiExtensionRunFacts(repoRoot, dagRunId, nodeId, declared, warnings) {
387
+ if (declared.length === 0)
388
+ return null;
389
+ const artifact = path.posix.join(nodeId, "pi-extensions.json");
390
+ const resolved = resolveDagRunArtifact(repoRoot, dagRunId, [
391
+ nodeId,
392
+ "pi-extensions.json",
393
+ ]);
394
+ if (!resolved.ok) {
395
+ if (resolved.reason === "ambiguous") {
396
+ warnings.push("Ambiguous pi-extensions.json lifecycle copies.");
397
+ }
398
+ return {
399
+ artifact,
400
+ exists: false,
401
+ requested: [...declared],
402
+ resolved: [],
403
+ missing: [],
404
+ };
405
+ }
406
+ const empty = {
407
+ artifact,
408
+ exists: false,
409
+ requested: [...declared],
410
+ resolved: [],
411
+ missing: [],
412
+ };
413
+ try {
414
+ const raw = JSON.parse(await readFile(resolved.result.absolutePath, "utf-8"));
415
+ const resolvedList = Array.isArray(raw.resolved) ? raw.resolved : [];
416
+ const missingList = Array.isArray(raw.missing) ? raw.missing : [];
417
+ const projected = resolvedList
418
+ .filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry))
419
+ .map((entry) => ({
420
+ id: redactSecrets(String(entry.id ?? "")),
421
+ packageName: redactSecrets(String(entry.packageName ?? "")),
422
+ version: redactSecrets(String(entry.version ?? "")),
423
+ }));
424
+ const missingProjected = missingList
425
+ .filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry))
426
+ .map((entry) => ({
427
+ id: redactSecrets(String(entry.id ?? "")),
428
+ reason: redactSecrets(String(entry.reason ?? "")),
429
+ }));
430
+ return {
431
+ artifact,
432
+ exists: true,
433
+ requested: redactStringList(raw.requested ?? declared),
434
+ resolved: projected,
435
+ missing: missingProjected,
436
+ };
437
+ }
438
+ catch {
439
+ return { ...empty, parseError: "read-failed" };
440
+ }
441
+ }
290
442
  async function loadPromptFingerprint(repoRoot, dagRunId, nodeId, warnings) {
291
443
  const resolved = resolveDagNodePromptRedacted(repoRoot, dagRunId, nodeId);
292
444
  if (!resolved.ok) {
@@ -415,8 +567,13 @@ export async function buildDagNodeInput(repoRoot, dagRunId, nodeId) {
415
567
  }
416
568
  const warnings = [];
417
569
  const fingerprint = await loadPromptFingerprint(repoRoot, dagRunId, nodeId, warnings);
570
+ const declaredPiExtensions = Array.isArray(task.piExtensions)
571
+ ? task.piExtensions.filter((item) => typeof item === "string")
572
+ : [];
573
+ const piExtensionRunFacts = await loadPiExtensionRunFacts(repoRoot, dagRunId, nodeId, declaredPiExtensions, warnings);
574
+ const promptCandidates = await loadFailedPromptCandidates(repoRoot, dagRunId, nodeId, warnings);
418
575
  // Reject unknown nested leakage by projecting only through allowlist helpers.
419
- const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, warnings);
576
+ const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, piExtensionRunFacts, promptCandidates, warnings);
420
577
  // Defense-in-depth: ensure raw task never sneaks in.
421
578
  if ("task" in body) {
422
579
  return {