@tea-agent/loop-agent 0.39.0-next.9 → 0.39.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +269 -69
  3. package/README.md +11 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/dag/generate-task-dag.js +9 -0
  6. package/dist/application/task-lifecycle/advance.js +16 -0
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +16 -5
  9. package/dist/cli/program.js +16 -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/dag-rerun.js +55 -1
  14. package/dist/commands/init-model-catalog.js +464 -0
  15. package/dist/commands/init-upgrade.js +265 -97
  16. package/dist/commands/init.js +428 -27
  17. package/dist/commands/inspect-next.js +8 -0
  18. package/dist/commands/task-advance.js +31 -0
  19. package/dist/executors/dag-pi-executor.js +124 -18
  20. package/dist/executors/pi-event-serializer.js +42 -1
  21. package/dist/executors/pi-executor.js +51 -0
  22. package/dist/executors/pi-extension-resolver.js +233 -0
  23. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  24. package/dist/executors/pi-sdk-executor.js +187 -59
  25. package/dist/executors/shell-executor.js +555 -34
  26. package/dist/executors/shell-write-guard.js +7 -0
  27. package/dist/shared/dag-failure-category.js +138 -0
  28. package/dist/shared/dag-prompt-override.js +27 -0
  29. package/dist/shared/operator/capabilities.js +181 -27
  30. package/dist/shared/playwright-cli-command-policy.js +15 -0
  31. package/dist/shared/update/console-notifier.js +100 -0
  32. package/dist/{cli → shared}/update/npm-client.js +70 -15
  33. package/dist/{cli → shared}/update/state.js +41 -13
  34. package/dist/task/config-types.js +28 -0
  35. package/dist/task/contract/apply.js +11 -1
  36. package/dist/task/contract/constants.js +3 -0
  37. package/dist/task/contract/diff.js +26 -0
  38. package/dist/task/contract/hash.js +3 -0
  39. package/dist/task/contract/observe.js +25 -4
  40. package/dist/task/contract/paths.js +2 -1
  41. package/dist/task/contract/project.js +20 -1
  42. package/dist/task/contract/schema.js +8 -1
  43. package/dist/task/contract/transaction.js +12 -0
  44. package/dist/task/source-prepare/build-draft.js +15 -0
  45. package/dist/task/source-prepare/completeness.js +188 -0
  46. package/dist/task/source-prepare/fragment-inventory.js +474 -0
  47. package/dist/task/source-prepare/index.js +3 -0
  48. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  49. package/dist/task/source-prepare/ledger-review.js +214 -0
  50. package/dist/task/source-prepare/ledger.js +545 -0
  51. package/dist/task/source-prepare/parse-intent.js +24 -4
  52. package/dist/task/source-prepare/prepare.js +262 -1
  53. package/dist/task/source-prepare/semantic-intake.js +19 -3
  54. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  55. package/dist/task/source-prepare/types.js +22 -0
  56. package/dist/worker/cli.js +18 -2
  57. package/dist/worker/console/chat/deferred-turn.js +91 -0
  58. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  59. package/dist/worker/console/chat/operation-card.js +8 -1
  60. package/dist/worker/console/chat/pi-console-config.js +81 -10
  61. package/dist/worker/console/chat/pi-runtime.js +165 -83
  62. package/dist/worker/console/chat/repo-walk.js +10 -1
  63. package/dist/worker/console/chat/routes.js +480 -55
  64. package/dist/worker/console/chat/semantic-activity.js +20 -8
  65. package/dist/worker/console/chat/session-store.js +146 -2
  66. package/dist/worker/console/chat/turn-process.js +10 -10
  67. package/dist/worker/console/console-handoff.js +82 -0
  68. package/dist/worker/console/console-update-and-init.js +115 -0
  69. package/dist/worker/console/console-update-runtime.js +84 -0
  70. package/dist/worker/console/dag-execution-receipt.js +33 -0
  71. package/dist/worker/console/draft-store.js +49 -0
  72. package/dist/worker/console/inspect-split.js +18 -0
  73. package/dist/worker/console/operation-run-facts.js +19 -3
  74. package/dist/worker/console/operation-store.js +1 -1
  75. package/dist/worker/console/operator-actions.js +320 -4
  76. package/dist/worker/console/operator-surface-health.js +1 -0
  77. package/dist/worker/console/operator-user-error.js +2 -2
  78. package/dist/worker/console/pi-readiness.js +5 -4
  79. package/dist/worker/console/prd-intake-bridge.js +13 -0
  80. package/dist/worker/console/recovery-cta.js +4 -4
  81. package/dist/worker/console/recovery-error-copy.js +4 -4
  82. package/dist/worker/console/routes.js +46 -2
  83. package/dist/worker/console/security.js +63 -9
  84. package/dist/worker/console/server.js +111 -7
  85. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
  86. package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-B4i9cVxX.js} +1 -1
  87. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
  88. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
  89. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
  90. package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
  91. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
  100. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
  101. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
  102. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
  103. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
  104. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
  105. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
  106. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
  107. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
  108. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
  109. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
  110. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
  111. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
  112. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
  113. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
  114. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
  115. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
  116. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
  117. package/dist/worker/console/static/assets/index-CW88Fjbw.js +406 -0
  118. package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
  119. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
  120. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
  121. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
  122. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
  123. package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-aGmcjgTn.js} +1 -1
  124. package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-Biyo-tqA.js} +5 -5
  125. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
  126. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
  127. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
  128. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
  129. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
  130. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
  131. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
  132. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
  133. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
  134. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
  135. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
  136. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
  137. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
  138. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
  139. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
  140. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
  141. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
  142. package/dist/worker/console/static/index.html +2 -2
  143. package/dist/worker/console/static-src/app/console-types.js +40 -1
  144. package/dist/worker/console/static-src/app/useOperatorActions.js +6 -1
  145. package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
  146. package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
  147. package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
  148. package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
  149. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  150. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +74 -26
  151. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  152. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  153. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  154. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  155. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
  156. package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
  157. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
  158. package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
  159. package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
  160. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  161. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
  162. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  163. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  164. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  165. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  166. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  167. package/dist/worker/observability/init-runtime-activity.js +26 -0
  168. package/dist/worker/observability/read-model.js +22 -0
  169. package/dist/worker/observe/node-input.js +160 -3
  170. package/dist/worker/observe/node-process.js +377 -0
  171. package/dist/worker/observe/routes.js +224 -3
  172. package/dist/worker/observe/shell-handler-keys.js +34 -0
  173. package/dist/worker/observe/static/api.js +67 -1
  174. package/dist/worker/observe/static/constants.js +8 -1
  175. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  176. package/dist/worker/observe/static/format.js +60 -0
  177. package/dist/worker/observe/static/index.html +5 -0
  178. package/dist/worker/observe/static/markdown-render.js +20 -1
  179. package/dist/worker/observe/static/operator-chrome.js +4 -0
  180. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  181. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  182. package/dist/worker/observe/static/state.js +13 -2
  183. package/dist/worker/observe/static/styles.css +85 -0
  184. package/dist/worker/observe/static/views/dag-inspector.js +912 -8
  185. package/dist/worker/observe/static/views/dag.js +8 -1
  186. package/dist/worker/observe/static/views/failures.js +5 -2
  187. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  188. package/dist/worker/observe/static/views/task.js +55 -0
  189. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +856 -54
  190. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  191. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  192. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  193. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  194. package/dist/workflows/dag/backend-test-layout.js +133 -0
  195. package/dist/workflows/dag/backend-test-markdown-workflow.js +343 -33
  196. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  197. package/dist/workflows/dag/backend-test-pytest-collection.js +454 -36
  198. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  199. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  200. package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
  201. package/dist/workflows/dag/backend-test-writer-completeness.js +110 -61
  202. package/dist/workflows/dag/contract-output-registry.js +15 -0
  203. package/dist/workflows/dag/contract-validator-registrations.js +3 -1
  204. package/dist/workflows/dag/convergence/controller.js +141 -0
  205. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  206. package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
  207. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  208. package/dist/workflows/dag/failure-category.js +7 -116
  209. package/dist/workflows/dag/frontend-implementation-contract.js +465 -6
  210. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  211. package/dist/workflows/dag/frontend-prewrite-gate.js +293 -84
  212. package/dist/workflows/dag/frontend-repair.js +14 -0
  213. package/dist/workflows/dag/frontend-review-context.js +92 -10
  214. package/dist/workflows/dag/frontend-risk.js +15 -2
  215. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  216. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  217. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  218. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  219. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  220. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  221. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  222. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  223. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  224. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  225. package/dist/workflows/dag/frontend-verification-trace.js +29 -4
  226. package/dist/workflows/dag/init-hybrid.js +1019 -209
  227. package/dist/workflows/dag/node-execution.js +228 -15
  228. package/dist/workflows/dag/prompt.js +60 -1
  229. package/dist/workflows/dag/rerun-feedback.js +212 -0
  230. package/dist/workflows/dag/rerun-plan.js +224 -27
  231. package/dist/workflows/dag/rerun-run.js +61 -6
  232. package/dist/workflows/dag/rerun-task.js +61 -2
  233. package/dist/workflows/dag/retry-policy.js +109 -0
  234. package/dist/workflows/dag/structured-output-repair.js +712 -0
  235. package/dist/workflows/dag/types.js +185 -12
  236. package/dist/workflows/dag/validate.js +64 -20
  237. package/docs/README.md +1 -0
  238. package/docs/architecture/evolution.md +45 -0
  239. package/docs/init-surface.manifest.json +6 -0
  240. package/docs/operations/README.md +1 -1
  241. package/docs/operations/local-development-environment.md +1 -5
  242. package/docs/skills/vetted-skill-registry.md +14 -0
  243. package/docs/templates/README.md +2 -2
  244. package/docs/templates/agent-dag.schema.json +44 -0
  245. package/docs/templates/backend-test-dag.json +60 -35
  246. package/docs/templates/frontend-implementation-contract.schema.json +681 -24
  247. package/docs/templates/frontend-test-case-checklist.md +2 -2
  248. package/docs/templates/frontend-test-dag.json +8 -10
  249. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  250. package/docs/templates/init-managed-agents.md +13 -3
  251. package/harness.json +6 -4
  252. package/package.json +12 -4
  253. package/scripts/next-info.mjs +356 -0
  254. package/scripts/next-publish-gate.mjs +238 -0
  255. package/scripts/release-source-binding.mjs +251 -0
  256. package/skills/analyze-product-requirements/SKILL.md +8 -5
  257. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  258. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  259. package/skills/analyze-product-requirements/references/example.md +50 -0
  260. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  261. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  262. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  263. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  264. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  265. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  266. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  267. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  268. package/skills/codebase-scout/SKILL.md +1 -1
  269. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  270. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  271. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  272. package/skills/frontend-implementation/references/node-contracts.md +2 -2
  273. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  274. package/skills/improve-codebase-architecture/deepening.md +37 -0
  275. package/skills/improve-codebase-architecture/html-report.md +123 -0
  276. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  277. package/skills/improve-codebase-architecture/language.md +53 -0
  278. package/skills/loop-agent/references/command-reference.md +11 -4
  279. package/skills/loop-agent/references/hybrid-dag.md +7 -0
  280. package/skills/playwright-cli/SKILL.md +23 -1
  281. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  282. package/dist/worker/console/static/assets/channel-BBOTJHvD.js +0 -1
  283. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
  284. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
  285. package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
  286. package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
  287. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
  288. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Pure decision core for the TasksPage run-id resolver (2026-08-23).
3
+ *
4
+ * Extracted from the React effect so the full lifecycle — wizard first run,
5
+ * rerun dispatch, task switch, foreign shared selection, baseline-gated
6
+ * lookup — is unit-testable as state transitions without mounting React.
7
+ */
8
+ export function initialRunResolutionState() {
9
+ return { baseline: null, ownedRunId: "", pendingSelection: "" };
10
+ }
11
+ export function observeRunResolution(state, input) {
12
+ const firstObservation = state.task === undefined;
13
+ const taskChanged = !firstObservation && state.task !== input.task;
14
+ const dispatchChanged = !firstObservation && state.dispatch !== input.dispatchId;
15
+ const next = {
16
+ ...state,
17
+ task: input.task,
18
+ dispatch: input.dispatchId,
19
+ pendingSelection: "",
20
+ };
21
+ if (taskChanged) {
22
+ next.baseline = null;
23
+ next.ownedRunId = "";
24
+ return { state: next, observation: { action: "reset-selection" } };
25
+ }
26
+ if (dispatchChanged) {
27
+ next.ownedRunId = "";
28
+ if (input.selectedRunId) {
29
+ next.baseline = input.selectedRunId;
30
+ return { state: next, observation: { action: "invalidate-selection" } };
31
+ }
32
+ // Nothing shown yet: keep the existing baseline and keep probing.
33
+ }
34
+ const selected = input.selectedRunId;
35
+ if (selected && selected !== next.ownedRunId) {
36
+ next.pendingSelection = selected;
37
+ return { state: next, observation: { action: "verify-ownership" } };
38
+ }
39
+ return {
40
+ state: next,
41
+ observation: { action: selected ? "idle" : "probe" },
42
+ };
43
+ }
44
+ export function markBaselineUnknown(state) {
45
+ return { ...state, baseline: null };
46
+ }
47
+ export function applyBaselineSnapshot(state, dagRunId) {
48
+ return { ...state, baseline: dagRunId ?? "" };
49
+ }
50
+ /** Accept an observeLink lookup: only a run of this task that differs from
51
+ * the pre-dispatch baseline may be adopted — never the previous run, and
52
+ * never anything while the baseline is still unknown. */
53
+ export function applyRunLookup(state, outcome) {
54
+ if (!outcome.ok || !outcome.dagRunId) {
55
+ return { state, acceptedRunId: null };
56
+ }
57
+ if (state.baseline === null || outcome.dagRunId === state.baseline) {
58
+ return { state, acceptedRunId: null };
59
+ }
60
+ return {
61
+ state: { ...state, ownedRunId: outcome.dagRunId },
62
+ acceptedRunId: outcome.dagRunId,
63
+ };
64
+ }
65
+ /** Apply an authoritative ownership answer for the pending selection.
66
+ * `clearSelection` tells the adapter to drop the shared selection and
67
+ * snapshot a fresh baseline; owning marks it as resolver-owned. */
68
+ export function applyOwnership(state, owns) {
69
+ if (owns) {
70
+ return {
71
+ state: { ...state, ownedRunId: state.pendingSelection, pendingSelection: "" },
72
+ clearSelection: false,
73
+ };
74
+ }
75
+ return {
76
+ state: { ...state, ownedRunId: "", pendingSelection: "", baseline: null },
77
+ clearSelection: true,
78
+ };
79
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Ownership re-check backoff for an unverified shared DAG-run selection
3
+ * (2026-08-23): a transient candidates-lookup failure must neither leave
4
+ * another task's run displayed indefinitely (waiting for a state change
5
+ * that may never come) nor hammer the API.
6
+ */
7
+ const OWNERSHIP_RETRY_DELAYS_MS = [
8
+ 2_000, 2_000, 4_000, 4_000, 8_000, 8_000, 15_000, 15_000, 30_000, 30_000,
9
+ ];
10
+ /** `owns`: true = same task, false = foreign task, null = check unavailable. */
11
+ export function planOwnershipVerification(owns, attempt) {
12
+ if (owns === true)
13
+ return { kind: "own" };
14
+ if (owns === false)
15
+ return { kind: "clear" };
16
+ const delayMs = OWNERSHIP_RETRY_DELAYS_MS[attempt];
17
+ return delayMs === undefined
18
+ ? { kind: "give-up" }
19
+ : { kind: "retry", delayMs };
20
+ }
21
+ /** Total bounded re-verification window before giving up (keeps the last
22
+ * selection; the next state change restarts verification). */
23
+ export const OWNERSHIP_RETRY_WINDOW_MS = OWNERSHIP_RETRY_DELAYS_MS.reduce((total, delay) => total + delay, 0);
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Pure projection from a DAG run detail payload (GET /api/dag-runs/:id) into
3
+ * the structured run progress shown on the wizard RunPanel: node counts,
4
+ * current node + elapsed time, failure pointer, and stall signal. The panel
5
+ * used to show only a raw SSE log tail — node/phase information drowned in
6
+ * log noise during 20-40 minute runs.
7
+ */
8
+ export function classifyRunProgressNodeStatus(raw) {
9
+ const status = (raw ?? "").trim().toLowerCase();
10
+ if (!status)
11
+ return "pending";
12
+ if (status.includes("running") || status === "in_progress" || status === "active") {
13
+ return "running";
14
+ }
15
+ if (status.includes("fail") || status.includes("error") || status === "blocked") {
16
+ return "failed";
17
+ }
18
+ if (status.includes("finish") ||
19
+ status.includes("success") ||
20
+ status === "done" ||
21
+ status === "ok") {
22
+ return "finished";
23
+ }
24
+ if (status.includes("skip"))
25
+ return "skipped";
26
+ return "pending";
27
+ }
28
+ function toProgressNode(node) {
29
+ const nodeId = String(node.nodeId ?? node.id ?? "").trim();
30
+ return {
31
+ nodeId,
32
+ label: String((node.label ?? node.name ?? nodeId) || "未命名节点"),
33
+ status: String(node.status ?? ""),
34
+ rank: typeof node.rank === "string" ? node.rank : undefined,
35
+ startedAt: typeof node.startedAt === "string" ? node.startedAt : undefined,
36
+ durationMs: typeof node.durationMs === "number" ? node.durationMs : undefined,
37
+ errorPreview: typeof node.errorPreview === "string" ? node.errorPreview : undefined,
38
+ };
39
+ }
40
+ function elapsedSince(iso, now) {
41
+ if (!iso)
42
+ return null;
43
+ const started = Date.parse(iso);
44
+ if (Number.isNaN(started))
45
+ return null;
46
+ const elapsed = now - started;
47
+ return elapsed >= 0 ? elapsed : null;
48
+ }
49
+ export function summarizeRunProgress(dag, now = Date.now()) {
50
+ if (!dag) {
51
+ return {
52
+ total: 0,
53
+ finished: 0,
54
+ running: null,
55
+ failed: null,
56
+ terminal: false,
57
+ effectiveStatus: null,
58
+ stalled: false,
59
+ currentNodeElapsedMs: null,
60
+ runElapsedMs: null,
61
+ };
62
+ }
63
+ const rawNodes = Array.isArray(dag.nodes) ? dag.nodes : [];
64
+ const nodes = rawNodes.map(toProgressNode);
65
+ const classes = nodes.map((node) => classifyRunProgressNodeStatus(node.status));
66
+ const finished = classes.filter((cls) => cls === "finished" || cls === "skipped").length;
67
+ const runningIndex = classes.indexOf("running");
68
+ const running = runningIndex >= 0 ? nodes[runningIndex] : null;
69
+ const failedIndex = classes.indexOf("failed");
70
+ const failed = failedIndex >= 0 ? nodes[failedIndex] : null;
71
+ const effectiveStatus = String(dag.effectiveStatus ??
72
+ dag.status ??
73
+ "").toLowerCase();
74
+ const terminal = TERMINAL_RUN_PROGRESS_STATUSES.has(effectiveStatus);
75
+ const runStartedAt = typeof dag.startedAt === "string" ? dag.startedAt : undefined;
76
+ const runDurationMs = typeof dag.durationMs === "number" ? dag.durationMs : undefined;
77
+ return {
78
+ total: nodes.length,
79
+ finished,
80
+ running,
81
+ failed,
82
+ terminal,
83
+ effectiveStatus: effectiveStatus || null,
84
+ stalled: effectiveStatus === "running-suspected-stall",
85
+ currentNodeElapsedMs: running?.durationMs ?? elapsedSince(running?.startedAt, now),
86
+ runElapsedMs: terminal
87
+ ? (runDurationMs ?? elapsedSince(runStartedAt, now))
88
+ : elapsedSince(runStartedAt, now),
89
+ };
90
+ }
91
+ const TERMINAL_RUN_PROGRESS_STATUSES = new Set([
92
+ "finished",
93
+ "failed",
94
+ "partial_failed",
95
+ "superseded",
96
+ "abandoned",
97
+ ]);
98
+ export function formatDurationMs(ms) {
99
+ if (ms == null || !Number.isFinite(ms) || ms < 0)
100
+ return "—";
101
+ const totalSeconds = Math.floor(ms / 1000);
102
+ const hours = Math.floor(totalSeconds / 3600);
103
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
104
+ const seconds = totalSeconds % 60;
105
+ if (hours > 0)
106
+ return `${hours} 时 ${minutes} 分`;
107
+ if (minutes > 0)
108
+ return `${minutes} 分 ${seconds} 秒`;
109
+ return `${seconds} 秒`;
110
+ }
@@ -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
@@ -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 {