@tea-agent/loop-agent 0.39.0-next.8 → 0.39.0

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 (254) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +264 -67
  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 +4 -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.js +2 -1
  12. package/dist/commands/dag-rerun.js +55 -1
  13. package/dist/commands/init-model-catalog.js +464 -0
  14. package/dist/commands/init-upgrade.js +265 -97
  15. package/dist/commands/init.js +428 -27
  16. package/dist/commands/inspect-next.js +8 -0
  17. package/dist/commands/task-advance.js +31 -0
  18. package/dist/executors/dag-pi-executor.js +124 -18
  19. package/dist/executors/pi-event-serializer.js +42 -1
  20. package/dist/executors/pi-executor.js +51 -0
  21. package/dist/executors/pi-extension-resolver.js +233 -0
  22. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  23. package/dist/executors/pi-sdk-executor.js +187 -59
  24. package/dist/executors/shell-executor.js +554 -33
  25. package/dist/executors/shell-write-guard.js +7 -0
  26. package/dist/shared/dag-failure-category.js +138 -0
  27. package/dist/shared/dag-prompt-override.js +28 -0
  28. package/dist/shared/operator/capabilities.js +181 -27
  29. package/dist/shared/playwright-cli-command-policy.js +15 -0
  30. package/dist/shared/update/console-notifier.js +100 -0
  31. package/dist/{cli → shared}/update/npm-client.js +41 -8
  32. package/dist/{cli → shared}/update/state.js +41 -13
  33. package/dist/task/config-types.js +28 -0
  34. package/dist/task/contract/constants.js +1 -0
  35. package/dist/task/contract/diff.js +26 -0
  36. package/dist/task/contract/project.js +5 -0
  37. package/dist/task/contract/schema.js +6 -1
  38. package/dist/task/source-prepare/build-draft.js +15 -0
  39. package/dist/task/source-prepare/parse-intent.js +24 -4
  40. package/dist/worker/cli.js +31 -1
  41. package/dist/worker/console/chat/deferred-turn.js +91 -0
  42. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  43. package/dist/worker/console/chat/operation-card.js +8 -1
  44. package/dist/worker/console/chat/pi-console-config.js +81 -10
  45. package/dist/worker/console/chat/pi-runtime.js +165 -83
  46. package/dist/worker/console/chat/repo-walk.js +10 -1
  47. package/dist/worker/console/chat/routes.js +480 -55
  48. package/dist/worker/console/chat/semantic-activity.js +20 -8
  49. package/dist/worker/console/chat/session-store.js +146 -2
  50. package/dist/worker/console/chat/turn-process.js +10 -10
  51. package/dist/worker/console/dag-execution-receipt.js +33 -0
  52. package/dist/worker/console/inspect-split.js +18 -0
  53. package/dist/worker/console/operation-run-facts.js +19 -3
  54. package/dist/worker/console/operator-actions.js +195 -4
  55. package/dist/worker/console/operator-user-error.js +2 -2
  56. package/dist/worker/console/pi-readiness.js +5 -4
  57. package/dist/worker/console/recovery-cta.js +4 -4
  58. package/dist/worker/console/recovery-error-copy.js +4 -4
  59. package/dist/worker/console/routes.js +43 -2
  60. package/dist/worker/console/security.js +63 -9
  61. package/dist/worker/console/server.js +42 -6
  62. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-Br92EGb7.js} +1 -1
  63. package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-oulImtQq.js} +1 -1
  64. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-tyHc6aqO.js} +1 -1
  65. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-CY86BSpT.js} +1 -1
  66. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-BKKsDmNs.js} +1 -1
  67. package/dist/worker/console/static/assets/channel-TY36zt-i.js +1 -0
  68. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-CXZdjl3B.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-B-aIFDQ5.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-BDgAW_B2.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-DABa4UPR.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-aGbysVOx.js} +1 -1
  73. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-B8MSZDVs.js} +1 -1
  74. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-CpelV73g.js} +1 -1
  75. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-BE04ty3z.js} +1 -1
  76. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-BUxTkaaR.js} +1 -1
  77. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-CYYBVKfX.js} +1 -1
  78. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +1 -0
  79. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +1 -0
  80. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-CEQF_xmQ.js} +1 -1
  81. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-CqpxN5IT.js} +1 -1
  82. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-B3PzanVi.js} +1 -1
  83. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-BSUGCNx1.js} +1 -1
  84. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-CdLevU5T.js} +1 -1
  85. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BjpF_Kok.js} +1 -1
  86. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C4I6NJny.js} +1 -1
  87. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-C8hecCr5.js} +1 -1
  88. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-Daod6tOS.js} +1 -1
  89. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-BvvmazGv.js} +1 -1
  90. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-DeN_vfV4.js} +1 -1
  91. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-D6dq1t9z.js} +1 -1
  92. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-DziIdNY4.js} +1 -1
  93. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js} +1 -1
  94. package/dist/worker/console/static/assets/index-C3GgHg-g.css +1 -0
  95. package/dist/worker/console/static/assets/index-Ci8ksvBm.js +406 -0
  96. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-Czv5eYx2.js} +1 -1
  97. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js} +1 -1
  98. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-DxGmNj5K.js} +1 -1
  99. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-CYCFqkO_.js} +1 -1
  100. package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-B0YzRwrd.js} +1 -1
  101. package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-t3EZtIGh.js} +5 -5
  102. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-BTXpTv0T.js} +1 -1
  103. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-DU8UkKqA.js} +1 -1
  104. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-DP4y8ohp.js} +1 -1
  105. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-B07vNaYR.js} +1 -1
  106. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-xDWktb2i.js} +1 -1
  107. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-BLNMKqeS.js} +1 -1
  108. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-n0adalxH.js} +1 -1
  109. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-BbnS-2DL.js} +1 -1
  110. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-B4uPpCLu.js} +1 -1
  111. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA--MMbB0fX.js} +1 -1
  112. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +1 -0
  113. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-BXUgp2QY.js} +2 -2
  114. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +8 -0
  115. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-CsaMxki7.js} +1 -1
  116. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-C2IuG9iH.js} +1 -1
  117. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-CmEWOXCW.js} +1 -1
  118. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-D_9LE0MG.js} +1 -1
  119. package/dist/worker/console/static/index.html +2 -2
  120. package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
  121. package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
  122. package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
  123. package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
  124. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  125. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +74 -26
  126. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  127. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  128. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  129. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  130. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
  131. package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
  132. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
  133. package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
  134. package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
  135. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  136. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
  137. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  138. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  139. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  140. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  141. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  142. package/dist/worker/observability/read-model.js +22 -0
  143. package/dist/worker/observe/node-input.js +74 -3
  144. package/dist/worker/observe/node-process.js +377 -0
  145. package/dist/worker/observe/routes.js +36 -3
  146. package/dist/worker/observe/shell-handler-keys.js +34 -0
  147. package/dist/worker/observe/static/api.js +66 -0
  148. package/dist/worker/observe/static/constants.js +8 -1
  149. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  150. package/dist/worker/observe/static/format.js +23 -0
  151. package/dist/worker/observe/static/markdown-render.js +20 -1
  152. package/dist/worker/observe/static/operator-chrome.js +4 -0
  153. package/dist/worker/observe/static/state.js +13 -2
  154. package/dist/worker/observe/static/styles.css +73 -0
  155. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  156. package/dist/worker/observe/static/views/dag.js +8 -1
  157. package/dist/worker/observe/static/views/failures.js +5 -2
  158. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  159. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +827 -53
  160. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  161. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  162. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  163. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  164. package/dist/workflows/dag/backend-test-layout.js +133 -0
  165. package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
  166. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  167. package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
  168. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  169. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  170. package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
  171. package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
  172. package/dist/workflows/dag/contract-output-registry.js +15 -0
  173. package/dist/workflows/dag/contract-validator-registrations.js +3 -1
  174. package/dist/workflows/dag/convergence/controller.js +141 -0
  175. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  176. package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
  177. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  178. package/dist/workflows/dag/failure-category.js +7 -116
  179. package/dist/workflows/dag/frontend-implementation-contract.js +377 -4
  180. package/dist/workflows/dag/frontend-plan-render.js +1 -1
  181. package/dist/workflows/dag/frontend-prewrite-gate.js +96 -84
  182. package/dist/workflows/dag/frontend-repair.js +14 -0
  183. package/dist/workflows/dag/frontend-risk.js +15 -2
  184. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  185. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  186. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  187. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  188. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  189. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  190. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  191. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  192. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  193. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  194. package/dist/workflows/dag/frontend-verification-trace.js +29 -4
  195. package/dist/workflows/dag/init-hybrid.js +894 -205
  196. package/dist/workflows/dag/node-execution.js +183 -15
  197. package/dist/workflows/dag/prompt.js +60 -1
  198. package/dist/workflows/dag/rerun-feedback.js +212 -0
  199. package/dist/workflows/dag/rerun-plan.js +224 -27
  200. package/dist/workflows/dag/rerun-run.js +61 -6
  201. package/dist/workflows/dag/rerun-task.js +61 -2
  202. package/dist/workflows/dag/retry-policy.js +109 -0
  203. package/dist/workflows/dag/structured-output-repair.js +712 -0
  204. package/dist/workflows/dag/types.js +143 -1
  205. package/dist/workflows/dag/validate.js +64 -20
  206. package/docs/init-surface.manifest.json +5 -0
  207. package/docs/operations/README.md +1 -1
  208. package/docs/operations/local-development-environment.md +1 -5
  209. package/docs/skills/vetted-skill-registry.md +14 -0
  210. package/docs/templates/README.md +1 -1
  211. package/docs/templates/agent-dag.schema.json +44 -0
  212. package/docs/templates/backend-test-dag.json +60 -35
  213. package/docs/templates/frontend-implementation-contract.schema.json +1 -1
  214. package/docs/templates/frontend-test-case-checklist.md +2 -2
  215. package/docs/templates/frontend-test-dag.json +8 -10
  216. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  217. package/docs/templates/init-managed-agents.md +13 -3
  218. package/harness.json +6 -4
  219. package/package.json +12 -4
  220. package/scripts/next-info.mjs +356 -0
  221. package/scripts/next-publish-gate.mjs +238 -0
  222. package/scripts/release-source-binding.mjs +251 -0
  223. package/skills/analyze-product-requirements/SKILL.md +8 -5
  224. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  225. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  226. package/skills/analyze-product-requirements/references/example.md +50 -0
  227. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  228. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  229. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  230. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  231. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  232. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  233. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  234. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  235. package/skills/codebase-scout/SKILL.md +1 -1
  236. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  237. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  238. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  239. package/skills/frontend-implementation/references/node-contracts.md +2 -2
  240. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  241. package/skills/improve-codebase-architecture/deepening.md +37 -0
  242. package/skills/improve-codebase-architecture/html-report.md +123 -0
  243. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  244. package/skills/improve-codebase-architecture/language.md +53 -0
  245. package/skills/loop-agent/references/command-reference.md +11 -4
  246. package/skills/playwright-cli/SKILL.md +23 -1
  247. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  248. package/dist/worker/console/static/assets/channel-BBOTJHvD.js +0 -1
  249. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
  250. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
  251. package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
  252. package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
  253. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
  254. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
@@ -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 = [
@@ -33,6 +34,7 @@ function unavailableBody(dagRunId, nodeId, reason, warnings = []) {
33
34
  executorInput: null,
34
35
  boundaries: null,
35
36
  promptFingerprint: null,
37
+ piExtensionRunFacts: null,
36
38
  warnings,
37
39
  };
38
40
  }
@@ -137,7 +139,7 @@ function resolveModelHint(spec, task) {
137
139
  }
138
140
  return null;
139
141
  }
140
- function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warnings) {
142
+ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, piExtensionRunFacts, warnings) {
141
143
  const defaults = spec.defaults && typeof spec.defaults === "object"
142
144
  ? spec.defaults
143
145
  : undefined;
@@ -216,6 +218,7 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
216
218
  : null,
217
219
  failFast: typeof shellRaw.failFast === "boolean" ? shellRaw.failFast : null,
218
220
  gates: shellGateNames(shellRaw),
221
+ handlers: namedShellHandlers(shellRaw),
219
222
  }
220
223
  : null;
221
224
  const staticPreview = staticRaw && typeof staticRaw.resultMarkdown === "string"
@@ -268,6 +271,7 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
268
271
  kind,
269
272
  skills,
270
273
  modelHint: resolveModelHint(spec, task),
274
+ piExtensions: redactStringList(task.piExtensions),
271
275
  shell,
272
276
  static: staticBlock,
273
277
  },
@@ -284,9 +288,72 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
284
288
  : null,
285
289
  },
286
290
  promptFingerprint: fingerprint,
291
+ piExtensionRunFacts,
287
292
  warnings,
288
293
  };
289
294
  }
295
+ /**
296
+ * Load run-time Pi extension facts from <node>/pi-extensions.json (plan
297
+ * 2026-08-22 R2). Returns null when the node declares no piExtensions
298
+ * (no expected artifact). Never throws: unreadable/corrupt artifacts degrade
299
+ * to parseError stubs so the node-input endpoint stays 200.
300
+ */
301
+ async function loadPiExtensionRunFacts(repoRoot, dagRunId, nodeId, declared, warnings) {
302
+ if (declared.length === 0)
303
+ return null;
304
+ const artifact = path.posix.join(nodeId, "pi-extensions.json");
305
+ const resolved = resolveDagRunArtifact(repoRoot, dagRunId, [
306
+ nodeId,
307
+ "pi-extensions.json",
308
+ ]);
309
+ if (!resolved.ok) {
310
+ if (resolved.reason === "ambiguous") {
311
+ warnings.push("Ambiguous pi-extensions.json lifecycle copies.");
312
+ }
313
+ return {
314
+ artifact,
315
+ exists: false,
316
+ requested: [...declared],
317
+ resolved: [],
318
+ missing: [],
319
+ };
320
+ }
321
+ const empty = {
322
+ artifact,
323
+ exists: false,
324
+ requested: [...declared],
325
+ resolved: [],
326
+ missing: [],
327
+ };
328
+ try {
329
+ const raw = JSON.parse(await readFile(resolved.result.absolutePath, "utf-8"));
330
+ const resolvedList = Array.isArray(raw.resolved) ? raw.resolved : [];
331
+ const missingList = Array.isArray(raw.missing) ? raw.missing : [];
332
+ const projected = resolvedList
333
+ .filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry))
334
+ .map((entry) => ({
335
+ id: redactSecrets(String(entry.id ?? "")),
336
+ packageName: redactSecrets(String(entry.packageName ?? "")),
337
+ version: redactSecrets(String(entry.version ?? "")),
338
+ }));
339
+ const missingProjected = missingList
340
+ .filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry))
341
+ .map((entry) => ({
342
+ id: redactSecrets(String(entry.id ?? "")),
343
+ reason: redactSecrets(String(entry.reason ?? "")),
344
+ }));
345
+ return {
346
+ artifact,
347
+ exists: true,
348
+ requested: redactStringList(raw.requested ?? declared),
349
+ resolved: projected,
350
+ missing: missingProjected,
351
+ };
352
+ }
353
+ catch {
354
+ return { ...empty, parseError: "read-failed" };
355
+ }
356
+ }
290
357
  async function loadPromptFingerprint(repoRoot, dagRunId, nodeId, warnings) {
291
358
  const resolved = resolveDagNodePromptRedacted(repoRoot, dagRunId, nodeId);
292
359
  if (!resolved.ok) {
@@ -415,8 +482,12 @@ export async function buildDagNodeInput(repoRoot, dagRunId, nodeId) {
415
482
  }
416
483
  const warnings = [];
417
484
  const fingerprint = await loadPromptFingerprint(repoRoot, dagRunId, nodeId, warnings);
485
+ const declaredPiExtensions = Array.isArray(task.piExtensions)
486
+ ? task.piExtensions.filter((item) => typeof item === "string")
487
+ : [];
488
+ const piExtensionRunFacts = await loadPiExtensionRunFacts(repoRoot, dagRunId, nodeId, declaredPiExtensions, warnings);
418
489
  // Reject unknown nested leakage by projecting only through allowlist helpers.
419
- const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, warnings);
490
+ const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, piExtensionRunFacts, warnings);
420
491
  // Defense-in-depth: ensure raw task never sneaks in.
421
492
  if ("task" in body) {
422
493
  return {
@@ -0,0 +1,377 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
3
+ import { isSafeObservabilityIdentifier } from "../observability/event-store.js";
4
+ import { parseExecutionOutputKey, } from "./dag-node-execution-output.js";
5
+ import { resolveDagRunArtifact, resolveDagRunJson, } from "./dag-run-artifacts.js";
6
+ import { namedShellHandlers } from "./shell-handler-keys.js";
7
+ export const NODE_PROCESS_SCHEMA_VERSION = 1;
8
+ export const NODE_PROCESS_FIELD_MAX_BYTES = 16 * 1024;
9
+ function boundField(value) {
10
+ if (value == null || value === "")
11
+ return { text: null, truncated: false };
12
+ const redacted = redactSecrets(value);
13
+ const truncated = truncateUtf8Preview(redacted, NODE_PROCESS_FIELD_MAX_BYTES);
14
+ return {
15
+ text: truncated,
16
+ truncated: truncated !== redacted,
17
+ };
18
+ }
19
+ function asRecord(value) {
20
+ if (!value || typeof value !== "object" || Array.isArray(value))
21
+ return null;
22
+ return value;
23
+ }
24
+ function asString(value) {
25
+ return typeof value === "string" && value.trim() ? value : null;
26
+ }
27
+ function asNumber(value) {
28
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
29
+ }
30
+ function asBoolean(value) {
31
+ return value === true;
32
+ }
33
+ async function readJsonArtifact(repoRoot, dagRunId, segments) {
34
+ const resolved = resolveDagRunArtifact(repoRoot, dagRunId, segments);
35
+ if (!resolved.ok)
36
+ return null;
37
+ try {
38
+ const raw = await readFile(resolved.result.absolutePath, "utf8");
39
+ const parsed = JSON.parse(raw);
40
+ return asRecord(parsed);
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ async function readTextArtifact(repoRoot, dagRunId, segments) {
47
+ const resolved = resolveDagRunArtifact(repoRoot, dagRunId, segments);
48
+ if (!resolved.ok)
49
+ return { text: null, truncated: false };
50
+ try {
51
+ const raw = await readFile(resolved.result.absolutePath, "utf8");
52
+ return boundField(raw);
53
+ }
54
+ catch {
55
+ return { text: null, truncated: false };
56
+ }
57
+ }
58
+ function emptyBody(dagRunId, nodeId, kind, emptyReason, extras = {}) {
59
+ return {
60
+ schemaVersion: NODE_PROCESS_SCHEMA_VERSION,
61
+ dagRunId,
62
+ nodeId,
63
+ kind,
64
+ executor: extras.executor ?? null,
65
+ handler: extras.handler ?? null,
66
+ emptyReason,
67
+ key: extras.key ?? null,
68
+ commands: extras.commands ?? [],
69
+ steps: extras.steps ?? [],
70
+ };
71
+ }
72
+ function classifyKind(task) {
73
+ const executor = asString(task.executor);
74
+ const shell = asRecord(task.shell);
75
+ const handlers = namedShellHandlers(shell);
76
+ const handler = handlers[0] ?? null;
77
+ const commands = shell?.commands;
78
+ const commandsDeclared = Array.isArray(commands) &&
79
+ commands.some((item) => typeof item === "string" && item.trim());
80
+ if (executor === "static" || task.static) {
81
+ return { kind: "static", executor: executor ?? "static", handler, commandsDeclared };
82
+ }
83
+ if (executor === "shell" || shell) {
84
+ if (commandsDeclared) {
85
+ return { kind: "shell-commands", executor: executor ?? "shell", handler, commandsDeclared };
86
+ }
87
+ if (handler) {
88
+ return { kind: "shell-preset", executor: executor ?? "shell", handler, commandsDeclared };
89
+ }
90
+ return { kind: "shell-commands", executor: executor ?? "shell", handler, commandsDeclared };
91
+ }
92
+ return {
93
+ kind: "pi-session",
94
+ executor: executor ?? "pi",
95
+ handler,
96
+ commandsDeclared,
97
+ };
98
+ }
99
+ function receiptAttemptSegments(nodeId, parsed) {
100
+ if (parsed?.kind === "attempt") {
101
+ return [nodeId, `attempt-${parsed.index}`, "shell-command-receipt.json"];
102
+ }
103
+ return [nodeId, "shell-command-receipt.json"];
104
+ }
105
+ async function loadShellCommands(repoRoot, dagRunId, nodeId, parsed) {
106
+ const preferred = await readJsonArtifact(repoRoot, dagRunId, receiptAttemptSegments(nodeId, parsed));
107
+ const receipt = preferred ??
108
+ (parsed
109
+ ? await readJsonArtifact(repoRoot, dagRunId, [
110
+ nodeId,
111
+ "shell-command-receipt.json",
112
+ ])
113
+ : null);
114
+ if (!receipt)
115
+ return null;
116
+ const rawCommands = Array.isArray(receipt.commands) ? receipt.commands : [];
117
+ const commands = [];
118
+ for (let i = 0; i < rawCommands.length; i += 1) {
119
+ const entry = asRecord(rawCommands[i]) ?? {};
120
+ const stdout = await readTextArtifact(repoRoot, dagRunId, [
121
+ nodeId,
122
+ "commands",
123
+ `${i + 1}.stdout.txt`,
124
+ ]);
125
+ const stderr = await readTextArtifact(repoRoot, dagRunId, [
126
+ nodeId,
127
+ "commands",
128
+ `${i + 1}.stderr.txt`,
129
+ ]);
130
+ const commandBound = boundField(asString(entry.command) ?? "");
131
+ commands.push({
132
+ index: i + 1,
133
+ command: commandBound.text ?? "(command)",
134
+ ok: asBoolean(entry.ok),
135
+ exitCode: asNumber(entry.exitCode),
136
+ failureCategory: asString(entry.failureCategory),
137
+ startedAt: asString(entry.startedAt),
138
+ finishedAt: asString(entry.finishedAt),
139
+ durationMs: asNumber(entry.wallDurationMs) ??
140
+ asNumber(entry.executionDurationMs) ??
141
+ null,
142
+ reused: asBoolean(entry.reused),
143
+ stdout: stdout.text,
144
+ stderr: stderr.text,
145
+ truncated: commandBound.truncated || stdout.truncated || stderr.truncated,
146
+ });
147
+ }
148
+ return commands;
149
+ }
150
+ function handlerLabel(handler) {
151
+ if (handler === "frontendBrowserToolPreflight")
152
+ return "浏览器工具能力探测";
153
+ if (handler === "frontendTestStandardScenarios")
154
+ return "标准场景物化";
155
+ if (handler === "frontendTestEnvironmentProbe")
156
+ return "前端环境探测";
157
+ if (handler === "frontendTestCaseChecklist")
158
+ return "用例清单校验";
159
+ if (handler === "frontendTestCaseManifest")
160
+ return "用例清单物化";
161
+ if (handler === "frontendTestEvidenceValidation")
162
+ return "证据校验";
163
+ if (handler === "frontendTestResultFinalize")
164
+ return "结果定稿";
165
+ if (handler === "frontendTestReports" || handler === "frontendTestL5Report" || handler === "frontendTestHtmlReport") {
166
+ return "测试报告";
167
+ }
168
+ if (handler === "frontendPrewriteGate")
169
+ return "前端预写门禁";
170
+ if (handler === "finalWriteSetApprovalGate")
171
+ return "writeSet 终态批准";
172
+ return handler;
173
+ }
174
+ async function loadPresetSteps(repoRoot, dagRunId, nodeId, handler, parsed) {
175
+ const written = await readJsonArtifact(repoRoot, dagRunId, [
176
+ nodeId,
177
+ "process-receipt.json",
178
+ ]);
179
+ if (written && Array.isArray(written.steps)) {
180
+ return written.steps.flatMap((item, index) => {
181
+ const step = asRecord(item);
182
+ if (!step)
183
+ return [];
184
+ const detail = boundField(asString(step.detail));
185
+ return [
186
+ {
187
+ id: asString(step.id) ?? `step-${index + 1}`,
188
+ label: asString(step.label) ?? handlerLabel(handler ?? "preset"),
189
+ ok: asBoolean(step.ok),
190
+ detail: detail.text,
191
+ durationMs: asNumber(step.durationMs),
192
+ truncated: detail.truncated,
193
+ },
194
+ ];
195
+ });
196
+ }
197
+ const capability = await readJsonArtifact(repoRoot, dagRunId, [
198
+ nodeId,
199
+ "frontend-browser-capability.json",
200
+ ]);
201
+ if (capability && handler === "frontendBrowserToolPreflight") {
202
+ const detail = boundField(JSON.stringify(capability, null, 2));
203
+ return [
204
+ {
205
+ id: "capability",
206
+ label: handlerLabel(handler),
207
+ ok: asString(capability.status) !== "failed",
208
+ detail: detail.text,
209
+ durationMs: null,
210
+ truncated: detail.truncated,
211
+ },
212
+ ];
213
+ }
214
+ const attemptIndex = parsed?.kind === "attempt" ? parsed.index : 1;
215
+ const attempt = await readJsonArtifact(repoRoot, dagRunId, [
216
+ nodeId,
217
+ `attempt-${attemptIndex}.json`,
218
+ ]);
219
+ if (attempt) {
220
+ const ok = asBoolean(attempt.ok);
221
+ const detail = boundField(ok
222
+ ? (asString(attempt.stdout) ?? asString(attempt.assistantText))
223
+ : (asString(attempt.stderr) ?? asString(attempt.stdout)));
224
+ return [
225
+ {
226
+ id: "run",
227
+ label: handlerLabel(handler ?? "preset"),
228
+ ok,
229
+ detail: detail.text,
230
+ durationMs: asNumber(attempt.durationMs),
231
+ truncated: detail.truncated,
232
+ },
233
+ ];
234
+ }
235
+ return [];
236
+ }
237
+ export async function loadDagNodeProcess(repoRoot, dagRunId, nodeId, rawKey) {
238
+ if (!isSafeObservabilityIdentifier(dagRunId) ||
239
+ !isSafeObservabilityIdentifier(nodeId)) {
240
+ return {
241
+ status: 400,
242
+ body: { error: "Invalid dag run or node identifier", reason: "invalid-id" },
243
+ };
244
+ }
245
+ let parsedKey = null;
246
+ if (rawKey != null && String(rawKey).trim() !== "") {
247
+ const parsed = parseExecutionOutputKey(rawKey);
248
+ if (!parsed.ok) {
249
+ return {
250
+ status: 400,
251
+ body: {
252
+ error: "Invalid execution key (expected attempt-N or pass-N)",
253
+ reason: "invalid-key",
254
+ },
255
+ };
256
+ }
257
+ parsedKey = parsed;
258
+ }
259
+ const runResolved = resolveDagRunJson(repoRoot, dagRunId);
260
+ if (!runResolved.ok) {
261
+ if (runResolved.reason === "invalid-id") {
262
+ return {
263
+ status: 400,
264
+ body: { error: "Invalid dag run or node identifier", reason: "invalid-id" },
265
+ };
266
+ }
267
+ if (runResolved.reason === "ambiguous") {
268
+ return {
269
+ status: 409,
270
+ body: { error: "Ambiguous dag run lifecycle", reason: "ambiguous" },
271
+ };
272
+ }
273
+ return {
274
+ status: 404,
275
+ body: { error: "DAG run not found", reason: "not-found" },
276
+ };
277
+ }
278
+ let spec = {};
279
+ try {
280
+ const raw = JSON.parse(await readFile(runResolved.result.absolutePath, "utf8"));
281
+ spec = asRecord(raw) ?? {};
282
+ }
283
+ catch {
284
+ return {
285
+ status: 404,
286
+ body: { error: "run.json is unreadable", reason: "not-found" },
287
+ };
288
+ }
289
+ const tasks = Array.isArray(spec.tasks) ? spec.tasks : [];
290
+ const task = tasks
291
+ .map((item) => asRecord(item))
292
+ .find((item) => item && asString(item.id) === nodeId);
293
+ if (!task) {
294
+ return {
295
+ status: 200,
296
+ body: emptyBody(dagRunId, nodeId, "none", "not-executed"),
297
+ };
298
+ }
299
+ const classified = classifyKind(task);
300
+ const key = parsedKey?.key ?? null;
301
+ if (classified.kind === "pi-session") {
302
+ return {
303
+ status: 200,
304
+ body: emptyBody(dagRunId, nodeId, "pi-session", "pi-session", {
305
+ executor: classified.executor,
306
+ key,
307
+ }),
308
+ };
309
+ }
310
+ if (classified.kind === "static") {
311
+ return {
312
+ status: 200,
313
+ body: emptyBody(dagRunId, nodeId, "static", "static-no-process", {
314
+ executor: classified.executor,
315
+ key,
316
+ }),
317
+ };
318
+ }
319
+ if (classified.kind === "shell-commands" || classified.kind === "shell-preset") {
320
+ const commands = await loadShellCommands(repoRoot, dagRunId, nodeId, parsedKey);
321
+ if (commands && commands.length > 0) {
322
+ return {
323
+ status: 200,
324
+ body: {
325
+ schemaVersion: NODE_PROCESS_SCHEMA_VERSION,
326
+ dagRunId,
327
+ nodeId,
328
+ kind: "shell-commands",
329
+ executor: classified.executor,
330
+ handler: classified.handler,
331
+ emptyReason: null,
332
+ key,
333
+ commands,
334
+ steps: [],
335
+ },
336
+ };
337
+ }
338
+ if (classified.handler || classified.kind === "shell-preset") {
339
+ const steps = await loadPresetSteps(repoRoot, dagRunId, nodeId, classified.handler, parsedKey);
340
+ if (steps.length > 0) {
341
+ return {
342
+ status: 200,
343
+ body: {
344
+ schemaVersion: NODE_PROCESS_SCHEMA_VERSION,
345
+ dagRunId,
346
+ nodeId,
347
+ kind: "shell-preset",
348
+ executor: classified.executor,
349
+ handler: classified.handler,
350
+ emptyReason: null,
351
+ key,
352
+ commands: [],
353
+ steps,
354
+ },
355
+ };
356
+ }
357
+ }
358
+ return {
359
+ status: 200,
360
+ body: emptyBody(dagRunId, nodeId, classified.kind, classified.kind === "shell-preset"
361
+ ? "shell-preset-pending"
362
+ : "shell-receipt-missing", {
363
+ executor: classified.executor,
364
+ handler: classified.handler,
365
+ key,
366
+ }),
367
+ };
368
+ }
369
+ return {
370
+ status: 200,
371
+ body: emptyBody(dagRunId, nodeId, "none", "not-executed", {
372
+ executor: classified.executor,
373
+ handler: classified.handler,
374
+ key,
375
+ }),
376
+ };
377
+ }
@@ -16,6 +16,7 @@ import { isAllowedArtifactTextPath, resolveArtifactPath, resolveRepoFilePreview,
16
16
  import { extractSpecEvidence, extractSpecReadContent, } from "./spec-evidence.js";
17
17
  import { buildDagNodeInput } from "./node-input.js";
18
18
  import { loadDagNodeExecutionOutput } from "./dag-node-execution-output.js";
19
+ import { loadDagNodeProcess } from "./node-process.js";
19
20
  import { buildObserveHealthV1 } from "./health.js";
20
21
  import { buildNightJobDetail, buildNightJobsDoctor, buildNightJobsList, buildNightJobsMorning, } from "./night-jobs.js";
21
22
  const ARTIFACT_PREVIEW_MAX_BYTES = 64 * 1024;
@@ -100,6 +101,11 @@ export const ROUTES = [
100
101
  pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/execution-output$/,
101
102
  handler: handleDagNodeExecutionOutput,
102
103
  },
104
+ {
105
+ method: "GET",
106
+ pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/process$/,
107
+ handler: handleDagNodeProcess,
108
+ },
103
109
  {
104
110
  method: "GET",
105
111
  pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/spec-evidence$/,
@@ -221,14 +227,23 @@ export async function serveObserveStatic(req, res, staticDir, options = {}) {
221
227
  sendJson(res, 404, { error: "Not found" });
222
228
  return;
223
229
  }
224
- const content = await readFile(resolvedFile);
230
+ let content = await readFile(resolvedFile);
225
231
  const contentType = contentTypeFor(pathname);
226
- res.writeHead(200, {
232
+ const headers = {
227
233
  "Content-Type": contentType,
228
234
  "Content-Length": content.byteLength,
229
235
  "X-Content-Type-Options": "nosniff",
230
236
  "X-Frame-Options": "SAMEORIGIN",
231
- });
237
+ };
238
+ if (contentType.startsWith("text/html") && options.htmlEnhancer) {
239
+ const enhanced = options.htmlEnhancer(Buffer.from(content));
240
+ content = enhanced.content;
241
+ headers["Content-Length"] = content.byteLength;
242
+ if (enhanced.setCookie) {
243
+ headers["Set-Cookie"] = enhanced.setCookie;
244
+ }
245
+ }
246
+ res.writeHead(200, headers);
232
247
  res.end(content);
233
248
  }
234
249
  async function handleHealth(_req, res, _match, ctx) {
@@ -570,6 +585,24 @@ async function handleDagNodeExecutionOutput(_req, res, match, ctx) {
570
585
  const result = await loadDagNodeExecutionOutput(ctx.repoRoot, dagRunId, nodeId, key);
571
586
  sendJson(res, result.status, result.body);
572
587
  }
588
+ async function handleDagNodeProcess(_req, res, match, ctx) {
589
+ const dagRunId = match.params.id;
590
+ const nodeId = match.params.sub;
591
+ if (!isSafeObservabilityIdentifier(dagRunId) ||
592
+ !isSafeObservabilityIdentifier(nodeId)) {
593
+ sendJson(res, 400, { error: "Invalid dag run or node identifier" });
594
+ return;
595
+ }
596
+ if (match.query.has("path") || match.query.has("artifactPath")) {
597
+ sendJson(res, 400, {
598
+ error: "Artifact path is not accepted; use optional key=attempt-N|pass-N",
599
+ reason: "path-not-allowed",
600
+ });
601
+ return;
602
+ }
603
+ const result = await loadDagNodeProcess(ctx.repoRoot, dagRunId, nodeId, match.query.get("key"));
604
+ sendJson(res, result.status, result.body);
605
+ }
573
606
  async function handleDagNodeSessionEvents(_req, res, match, ctx) {
574
607
  const dagRunId = match.params.id;
575
608
  const nodeId = match.params.sub;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Allowlist of in-process shell handlers (preset/gate flags on task.shell).
3
+ * Observe projects these names; the executor writes process receipts for them.
4
+ */
5
+ export const SHELL_NAMED_HANDLER_KEYS = [
6
+ "verdictGate",
7
+ "projectGovernanceGate",
8
+ "requirementCoverageGate",
9
+ "jsonArtifactGate",
10
+ "frontendPrewriteGate",
11
+ "frontendLintBaseline",
12
+ "frontendVerificationBundle",
13
+ "frontendReviewContext",
14
+ "frontendTestCaseChecklist",
15
+ "frontendBrowserToolPreflight",
16
+ "frontendTestStandardScenarios",
17
+ "frontendTestEnvironmentProbe",
18
+ "frontendTestEvidenceValidation",
19
+ "finalWriteSetApprovalGate",
20
+ "frontendTestL5Report",
21
+ "frontendTestHtmlReport",
22
+ "frontendTestCaseManifest",
23
+ "frontendTestResultFinalize",
24
+ "frontendTestReports",
25
+ "backendTestPipeline",
26
+ "jacocoCoverage",
27
+ "verifyEvidence",
28
+ "repairArtifactGate",
29
+ ];
30
+ export function namedShellHandlers(shell) {
31
+ if (!shell)
32
+ return [];
33
+ return SHELL_NAMED_HANDLER_KEYS.filter((key) => shell[key] != null);
34
+ }
@@ -77,6 +77,14 @@ export async function fetchDagNodeInput(runId, nodeId) {
77
77
  return fetchJsonResult(url);
78
78
  }
79
79
 
80
+ export async function fetchDagNodeProcess(runId, nodeId, key) {
81
+ const params = new URLSearchParams();
82
+ if (key) params.set("key", String(key));
83
+ const qs = params.toString();
84
+ const url = `/api/dag-runs/${encodeURIComponent(runId)}/nodes/${encodeURIComponent(nodeId)}/process${qs ? `?${qs}` : ""}`;
85
+ return fetchJsonResult(url);
86
+ }
87
+
80
88
  /**
81
89
  * Fetch a spec-evidence file preview (binding source or successful read).
82
90
  * Resolves to the parsed JSON body, or throws an Error carrying the HTTP
@@ -104,6 +112,64 @@ export function artifactUrl(artifactPath) {
104
112
  return `/api/artifacts?path=${encodeURIComponent(artifactPath)}&tail=200`;
105
113
  }
106
114
 
115
+ /**
116
+ * Experimental (prompt-override rerun only): POST one allowlisted operator
117
+ * action through the Console mutation gate using the SCOPED inspect token
118
+ * (window.__INSPECT_MUTATION_TOKEN__). The server injects it into /inspect
119
+ * HTML only when HARNESS_DAG_RERUN_PROMPT_OVERRIDE is enabled, and it only
120
+ * authorizes dagRerunPlan / dagRerun — never general Console mutations.
121
+ * Absent token → the call is not attempted.
122
+ */
123
+ export async function postOperatorAction(action, actionParams = {}) {
124
+ const token = globalThis.__INSPECT_MUTATION_TOKEN__;
125
+ if (!token) {
126
+ return {
127
+ ok: false,
128
+ status: 0,
129
+ body: { error: { code: "PROMPT_OVERRIDE_DISABLED", message: "提示词重启未启用(服务端未注入确认 token)" } },
130
+ };
131
+ }
132
+ try {
133
+ const res = await fetch("/api/operator/v1/operations", {
134
+ method: "POST",
135
+ credentials: "same-origin",
136
+ headers: {
137
+ "Content-Type": "application/json",
138
+ "x-loop-console-confirmation": token,
139
+ },
140
+ body: JSON.stringify({ action, actionParams }),
141
+ });
142
+ let body = null;
143
+ try {
144
+ body = await res.json();
145
+ } catch {
146
+ body = null;
147
+ }
148
+ return { ok: res.ok, status: res.status, body };
149
+ } catch {
150
+ return { ok: false, status: 0, body: null };
151
+ }
152
+ }
153
+
154
+ /** Poll a Console operation snapshot (read-only GET). */
155
+ export async function fetchOperatorOperation(operationId) {
156
+ try {
157
+ const res = await fetch(
158
+ `/api/operator/v1/operations/${encodeURIComponent(operationId)}`,
159
+ { credentials: "same-origin" },
160
+ );
161
+ let body = null;
162
+ try {
163
+ body = await res.json();
164
+ } catch {
165
+ body = null;
166
+ }
167
+ return { ok: res.ok, status: res.status, body };
168
+ } catch {
169
+ return { ok: false, status: 0, body: null };
170
+ }
171
+ }
172
+
107
173
  export function parseArtifactPreviewResponse(text) {
108
174
  const parsed = JSON.parse(text);
109
175
  if (!parsed || typeof parsed.content !== "string") {