@tea-agent/loop-agent 0.39.0-beta.0 → 0.39.0-beta.10

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 (331) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +248 -95
  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 +2306 -61
  22. package/dist/executors/pi-executor.js +7 -0
  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 +180 -33
  27. package/dist/executors/shell-executor.js +1249 -224
  28. package/dist/executors/shell-write-guard.js +14 -0
  29. package/dist/executors/workspace-write-snapshot.js +68 -0
  30. package/dist/infrastructure/harness/atomic-write.js +23 -0
  31. package/dist/shared/dag-failure-category.js +150 -0
  32. package/dist/shared/dag-prompt-override.js +27 -0
  33. package/dist/shared/openspec-spec.js +70 -4
  34. package/dist/shared/operator/capabilities.js +39 -0
  35. package/dist/shared/playwright-cli-command-policy.js +15 -0
  36. package/dist/shared/update/console-notifier.js +100 -0
  37. package/dist/{cli → shared}/update/npm-client.js +70 -15
  38. package/dist/{cli → shared}/update/state.js +41 -13
  39. package/dist/task/config-types.js +50 -5
  40. package/dist/task/contract/apply.js +11 -1
  41. package/dist/task/contract/constants.js +2 -0
  42. package/dist/task/contract/hash.js +3 -0
  43. package/dist/task/contract/observe.js +25 -4
  44. package/dist/task/contract/paths.js +2 -1
  45. package/dist/task/contract/project.js +18 -1
  46. package/dist/task/contract/schema.js +4 -1
  47. package/dist/task/contract/transaction.js +12 -0
  48. package/dist/task/frontend-project-capability.js +199 -18
  49. package/dist/task/source-prepare/completeness.js +188 -0
  50. package/dist/task/source-prepare/fragment-inventory.js +477 -0
  51. package/dist/task/source-prepare/index.js +3 -0
  52. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  53. package/dist/task/source-prepare/ledger-review.js +214 -0
  54. package/dist/task/source-prepare/ledger.js +545 -0
  55. package/dist/task/source-prepare/parse-intent.js +24 -4
  56. package/dist/task/source-prepare/prepare.js +298 -1
  57. package/dist/task/source-prepare/semantic-intake.js +25 -5
  58. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  59. package/dist/task/source-prepare/types.js +22 -0
  60. package/dist/task/source-references.js +22 -1
  61. package/dist/worker/cli.js +18 -2
  62. package/dist/worker/console/chat/chat-event-store.js +62 -4
  63. package/dist/worker/console/chat/context-panel.js +7 -0
  64. package/dist/worker/console/chat/deferred-turn.js +12 -0
  65. package/dist/worker/console/chat/operation-card.js +8 -1
  66. package/dist/worker/console/chat/pi-console-config.js +81 -10
  67. package/dist/worker/console/chat/pi-runtime.js +264 -16
  68. package/dist/worker/console/chat/provider-error.js +98 -0
  69. package/dist/worker/console/chat/routes.js +298 -32
  70. package/dist/worker/console/chat/session-stats.js +179 -0
  71. package/dist/worker/console/chat/session-store.js +93 -63
  72. package/dist/worker/console/chat/todos.js +115 -0
  73. package/dist/worker/console/chat/tool-preview.js +42 -0
  74. package/dist/worker/console/chat/turn-process.js +20 -55
  75. package/dist/worker/console/chat/user-questions.js +266 -0
  76. package/dist/worker/console/chat/workspace-landing.js +14 -0
  77. package/dist/worker/console/console-handoff.js +82 -0
  78. package/dist/worker/console/console-update-and-init.js +115 -0
  79. package/dist/worker/console/console-update-runtime.js +84 -0
  80. package/dist/worker/console/dag-execution-receipt.js +33 -0
  81. package/dist/worker/console/draft-store.js +49 -0
  82. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  83. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  84. package/dist/worker/console/index.js +3 -0
  85. package/dist/worker/console/inspect-split.js +18 -0
  86. package/dist/worker/console/native-directory-picker.js +13 -0
  87. package/dist/worker/console/operation-run-facts.js +19 -3
  88. package/dist/worker/console/operation-store.js +227 -7
  89. package/dist/worker/console/operator-actions.js +388 -0
  90. package/dist/worker/console/operator-surface-health.js +1 -0
  91. package/dist/worker/console/operator-user-error.js +2 -2
  92. package/dist/worker/console/prd-intake-bridge.js +13 -0
  93. package/dist/worker/console/routes.js +355 -3
  94. package/dist/worker/console/security.js +67 -13
  95. package/dist/worker/console/server.js +609 -164
  96. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-BAsjg7ew.js} +1 -1
  97. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-CyIlrwZw.js} +1 -1
  98. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-Bo4tobhH.js} +1 -1
  99. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dfpow9gu.js} +1 -1
  100. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-DPmzG_B4.js} +1 -1
  101. package/dist/worker/console/static/assets/channel-DSrJAn0-.js +1 -0
  102. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-pKQpB1UW.js} +1 -1
  103. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-5n9YYquZ.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DP3CtA2a.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DYxMJPUy.js} +1 -1
  106. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-CS3D50Io.js} +1 -1
  107. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-gSdDfo5_.js} +1 -1
  108. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-A48q0ZNt.js} +1 -1
  109. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-ow8Mm3DY.js} +1 -1
  110. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-CMKfLWYE.js} +1 -1
  111. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BiTBdQPX.js} +1 -1
  112. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +1 -0
  113. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +1 -0
  114. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-Cm7exRz_.js} +1 -1
  115. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-D9O5qGOu.js} +1 -1
  116. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-CkvQnako.js} +1 -1
  117. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-B1rzp7CB.js} +1 -1
  118. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BP07Ipcc.js} +1 -1
  119. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BlPzweQb.js} +1 -1
  120. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-CxPa7n0S.js} +1 -1
  121. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CMTZs53A.js} +1 -1
  122. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-CxG_X52r.js} +1 -1
  123. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D8axtJM0.js} +1 -1
  124. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DIsGzn7b.js} +1 -1
  125. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CU9zkDG0.js} +1 -1
  126. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-DCjTmz5P.js} +1 -1
  127. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BksdmFzg.js} +1 -1
  128. package/dist/worker/console/static/assets/index-B-O5ChYq.css +1 -0
  129. package/dist/worker/console/static/assets/index-CfnY8AQx.js +407 -0
  130. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-CGzzzJtL.js} +1 -1
  131. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BdseB80N.js} +1 -1
  132. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-DM9gsgha.js} +1 -1
  133. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-3q-_9R2b.js} +1 -1
  134. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-B8N-Mxsd.js} +1 -1
  135. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-EPIHUUsu.js} +5 -5
  136. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CxwO12aa.js} +1 -1
  137. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-bPqbVX6Y.js} +1 -1
  138. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-KzqIvzeX.js} +1 -1
  139. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-CyofpWiV.js} +1 -1
  140. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-cTWJvLTP.js} +1 -1
  141. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-BK--q27B.js} +1 -1
  142. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-C0SDOHXK.js} +1 -1
  143. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-Cong6YIo.js} +1 -1
  144. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DUVkgZx8.js} +1 -1
  145. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C7XgxuZg.js} +1 -1
  146. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +1 -0
  147. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-D9Ayvwcy.js} +2 -2
  148. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +8 -0
  149. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-Byb000cH.js} +1 -1
  150. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DMLegPt4.js} +1 -1
  151. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-ChMMeOsP.js} +1 -1
  152. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-bEMEPisW.js} +1 -1
  153. package/dist/worker/console/static/index.html +2 -2
  154. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  155. package/dist/worker/console/static-src/app/console-types.js +68 -4
  156. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  157. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  158. package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
  159. package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
  160. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  161. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  162. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  163. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
  164. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  165. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  166. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  167. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  168. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  169. package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
  170. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
  171. package/dist/worker/console/static-src/operator-chat/useChatStream.js +102 -21
  172. package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
  173. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  174. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  175. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  176. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  177. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  178. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  179. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  180. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  181. package/dist/worker/console/static-src/shell/workspace-route.js +337 -0
  182. package/dist/worker/console/workspace-context.js +344 -0
  183. package/dist/worker/console/workspace-registry.js +214 -0
  184. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  185. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  186. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  187. package/dist/worker/observability/init-runtime-activity.js +26 -0
  188. package/dist/worker/observability/read-model.js +22 -0
  189. package/dist/worker/observe/node-input.js +160 -3
  190. package/dist/worker/observe/node-process.js +377 -0
  191. package/dist/worker/observe/routes.js +224 -3
  192. package/dist/worker/observe/server.js +4 -0
  193. package/dist/worker/observe/shell-handler-keys.js +34 -0
  194. package/dist/worker/observe/static/api.js +90 -4
  195. package/dist/worker/observe/static/app.js +18 -70
  196. package/dist/worker/observe/static/constants.js +8 -1
  197. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  198. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  199. package/dist/worker/observe/static/dag-node-purpose.js +208 -0
  200. package/dist/worker/observe/static/format.js +60 -0
  201. package/dist/worker/observe/static/index.html +6 -1
  202. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  203. package/dist/worker/observe/static/markdown-render.js +20 -1
  204. package/dist/worker/observe/static/operator-chrome.css +145 -20
  205. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  206. package/dist/worker/observe/static/operator-chrome.js +330 -96
  207. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  208. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  209. package/dist/worker/observe/static/router.d.ts +46 -0
  210. package/dist/worker/observe/static/router.js +25 -1
  211. package/dist/worker/observe/static/state.d.ts +50 -0
  212. package/dist/worker/observe/static/state.js +184 -1
  213. package/dist/worker/observe/static/styles.css +395 -0
  214. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  215. package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
  216. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  217. package/dist/worker/observe/static/views/dag.js +14 -5
  218. package/dist/worker/observe/static/views/dashboard.js +6 -4
  219. package/dist/worker/observe/static/views/failures.js +5 -2
  220. package/dist/worker/observe/static/views/night.js +2 -2
  221. package/dist/worker/observe/static/views/pool.js +1 -0
  222. package/dist/worker/observe/static/views/run.js +3 -1
  223. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  224. package/dist/worker/observe/static/views/task.js +56 -1
  225. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
  226. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  227. package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
  228. package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
  229. package/dist/workflows/dag/contract-output-registry.js +15 -0
  230. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  231. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  232. package/dist/workflows/dag/dynamic-runtime/map.js +1 -1
  233. package/dist/workflows/dag/failure-category.js +7 -116
  234. package/dist/workflows/dag/frontend-closeout.js +221 -0
  235. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  236. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  237. package/dist/workflows/dag/frontend-implementation-contract.js +534 -178
  238. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  239. package/dist/workflows/dag/frontend-prewrite-gate.js +371 -393
  240. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  241. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  242. package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
  243. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  244. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  245. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  246. package/dist/workflows/dag/frontend-repair.js +7 -424
  247. package/dist/workflows/dag/frontend-review-context.js +288 -14
  248. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  249. package/dist/workflows/dag/frontend-risk.js +15 -2
  250. package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
  251. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  252. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  253. package/dist/workflows/dag/frontend-shape.js +427 -0
  254. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  255. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  256. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  257. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  258. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  259. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  260. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  261. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  262. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  263. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  264. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  265. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  266. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  267. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  268. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  269. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  270. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  271. package/dist/workflows/dag/frontend-writer-admission.js +272 -0
  272. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  273. package/dist/workflows/dag/init-hybrid.js +984 -618
  274. package/dist/workflows/dag/interrupt-request.js +10 -3
  275. package/dist/workflows/dag/lifecycle.js +3 -3
  276. package/dist/workflows/dag/node-execution.js +468 -34
  277. package/dist/workflows/dag/prompt.js +44 -19
  278. package/dist/workflows/dag/recovery-lease.js +80 -0
  279. package/dist/workflows/dag/report.js +37 -1
  280. package/dist/workflows/dag/rerun-feedback.js +124 -1
  281. package/dist/workflows/dag/rerun-plan.js +144 -5
  282. package/dist/workflows/dag/rerun-run.js +81 -6
  283. package/dist/workflows/dag/rerun-task.js +29 -0
  284. package/dist/workflows/dag/retry-policy.js +318 -8
  285. package/dist/workflows/dag/runner.js +429 -122
  286. package/dist/workflows/dag/scheduler.js +114 -16
  287. package/dist/workflows/dag/structured-output-repair.js +712 -0
  288. package/dist/workflows/dag/types.js +321 -31
  289. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  290. package/dist/workflows/dag/validate.js +92 -32
  291. package/docs/README.md +2 -0
  292. package/docs/architecture/evolution.md +45 -0
  293. package/docs/init-surface.manifest.json +9 -0
  294. package/docs/operations/README.md +1 -1
  295. package/docs/operations/local-development-environment.md +21 -0
  296. package/docs/templates/README.md +2 -1
  297. package/docs/templates/agent-dag-report.schema.json +8 -2
  298. package/docs/templates/agent-dag.schema.json +45 -1
  299. package/docs/templates/frontend-implementation-contract.schema.json +684 -24
  300. package/docs/templates/frontend-test-case-checklist.md +2 -2
  301. package/docs/templates/frontend-test-dag.json +8 -10
  302. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  303. package/docs/templates/init-managed-agents.md +13 -3
  304. package/docs/templates/spec-registry.schema.json +45 -0
  305. package/harness.json +3 -3
  306. package/package.json +7 -1
  307. package/scripts/next-info.mjs +356 -0
  308. package/scripts/next-publish-gate.mjs +238 -0
  309. package/scripts/release-source-binding.mjs +251 -0
  310. package/skills/codebase-scout/SKILL.md +1 -1
  311. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  312. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  313. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  314. package/skills/frontend-bounded-implement/SKILL.md +8 -14
  315. package/skills/frontend-design-review/SKILL.md +20 -41
  316. package/skills/frontend-implementation/SKILL.md +3 -4
  317. package/skills/frontend-implementation/references/design-spec.md +15 -10
  318. package/skills/frontend-implementation/references/node-contracts.md +23 -13
  319. package/skills/frontend-review/SKILL.md +22 -14
  320. package/skills/frontend-review/references/review-findings.md +6 -7
  321. package/skills/loop-agent/references/command-reference.md +12 -4
  322. package/skills/loop-agent/references/hybrid-dag.md +8 -1
  323. package/skills/playwright-cli/SKILL.md +23 -1
  324. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  325. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  326. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  327. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  328. package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
  329. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  330. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  331. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -11,14 +11,26 @@ import {
11
11
  } from "../constants.js";
12
12
  import { clearNode, el } from "../dom.js";
13
13
  import { attemptFailureLabel } from "../dag-history-labels.js";
14
- import { fetchJson, fetchSpecEvidenceFile, fetchDagNodeInput } from "../api.js";
15
- import { badge, formatMs } from "../format.js";
16
- import { renderMarkdown } from "../markdown-render.js";
17
14
  import {
15
+ fetchJson,
16
+ fetchSpecEvidenceFile,
17
+ fetchDagNodeInput,
18
+ fetchDagNodeProcess,
19
+ postOperatorAction,
20
+ fetchOperatorOperation,
21
+ } from "../api.js";
22
+ import { badge, formatLedgerSummary, formatMs, prettyJsonBlock } from "../format.js";
23
+ import { renderMarkdown, renderInspectorDocument } from "../markdown-render.js";
24
+ import { describeDagNodePurpose } from "../dag-node-purpose.js";
25
+ import { buildPromptRestartCandidateOptions } from "../prompt-restart-candidates.js";
26
+ import { copyText } from "../copy.js";
27
+ import {
28
+ fetchWithWorkspaceScope,
18
29
  uiState,
19
30
  makeSessionEventIdentity,
20
31
  makeDagNodeInputIdentity,
21
32
  makeDagNodeExecutionOutputIdentity,
33
+ makeDagNodeProcessIdentity,
22
34
  getDagNodeExecutionOutputCache,
23
35
  setDagNodeExecutionOutputCache,
24
36
  clearDagNodeExecutionOutputCache,
@@ -44,6 +56,254 @@ import { createCustomSelect } from "../custom-select.js";
44
56
  // reference, always resolving the currently selected node identity.
45
57
  const inspectorSelection = { dagRunId: null, node: null };
46
58
 
59
+ function createPromptRestartCopyButton(readCandidate) {
60
+ const buildIcon = (kind) => {
61
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
62
+ svg.setAttribute(
63
+ "class",
64
+ `dags-copy-icon dags-copy-icon-${kind === "check" ? "check" : "rects"}`,
65
+ );
66
+ svg.setAttribute("viewBox", "0 0 16 16");
67
+ svg.setAttribute("width", "16");
68
+ svg.setAttribute("height", "16");
69
+ svg.setAttribute("aria-hidden", "true");
70
+ svg.setAttribute("focusable", "false");
71
+ if (kind === "check") {
72
+ const check = document.createElementNS(
73
+ "http://www.w3.org/2000/svg",
74
+ "path",
75
+ );
76
+ check.setAttribute("d", "M3.5 8.5 6.5 11.5 12.5 4.5");
77
+ check.setAttribute("fill", "none");
78
+ check.setAttribute("stroke", "currentColor");
79
+ check.setAttribute("stroke-width", "1.8");
80
+ check.setAttribute("stroke-linecap", "round");
81
+ check.setAttribute("stroke-linejoin", "round");
82
+ svg.appendChild(check);
83
+ return svg;
84
+ }
85
+ for (const [x, y] of [
86
+ [2, 2],
87
+ [5, 5],
88
+ ]) {
89
+ const rect = document.createElementNS(
90
+ "http://www.w3.org/2000/svg",
91
+ "rect",
92
+ );
93
+ rect.setAttribute("x", String(x));
94
+ rect.setAttribute("y", String(y));
95
+ rect.setAttribute("width", "9");
96
+ rect.setAttribute("height", "9");
97
+ rect.setAttribute("rx", "1.5");
98
+ rect.setAttribute("fill", "none");
99
+ rect.setAttribute("stroke", "currentColor");
100
+ rect.setAttribute("stroke-width", "1.5");
101
+ svg.appendChild(rect);
102
+ }
103
+ return svg;
104
+ };
105
+ const button = el(
106
+ "button",
107
+ "dags-copy-btn dags-copy-btn-icon prompt-restart-copy-icon",
108
+ );
109
+ button.type = "button";
110
+ button.setAttribute("data-testid", "prompt-restart-copy");
111
+ button.setAttribute("aria-label", "复制当前提示词候选");
112
+ button.setAttribute("title", "复制当前提示词候选");
113
+ button.appendChild(buildIcon("copy"));
114
+ button.appendChild(buildIcon("check"));
115
+ button.addEventListener("click", (event) => {
116
+ event.preventDefault();
117
+ event.stopPropagation();
118
+ const candidate = readCandidate();
119
+ void copyText(candidate?.prompt ?? "").then((ok) => {
120
+ const feedback = ok
121
+ ? candidate?.truncated
122
+ ? "已复制截断预览"
123
+ : "已复制"
124
+ : "复制失败";
125
+ button.classList.toggle("is-copied", ok);
126
+ button.setAttribute("aria-label", feedback);
127
+ button.setAttribute("title", feedback);
128
+ setTimeout(() => {
129
+ button.classList.remove("is-copied");
130
+ button.setAttribute("aria-label", "复制当前提示词候选");
131
+ button.setAttribute("title", "复制当前提示词候选");
132
+ }, 1500);
133
+ });
134
+ });
135
+ return button;
136
+ }
137
+
138
+ function formatPromptRestartMeta(text) {
139
+ const value = String(text ?? "");
140
+ const chars = [...value].length;
141
+ const lines = value.length === 0 ? 0 : value.split("\n").length;
142
+ return `${lines} 行 · ${chars.toLocaleString("zh-CN")} 字`;
143
+ }
144
+
145
+ function createPromptRestartBanner({ tone, kicker, title, body }) {
146
+ const banner = el("div", `prompt-restart-banner is-${tone}`);
147
+ banner.setAttribute("role", "status");
148
+ banner.appendChild(el("span", "prompt-restart-banner-kicker", kicker));
149
+ banner.appendChild(el("p", "prompt-restart-banner-title", title));
150
+ banner.appendChild(el("p", "prompt-restart-banner-body", body));
151
+ return banner;
152
+ }
153
+
154
+ function createPromptRestartCandidatePicker({
155
+ options,
156
+ value,
157
+ onChange,
158
+ label,
159
+ hint,
160
+ }) {
161
+ const field = el("div", "prompt-restart-field");
162
+ field.appendChild(el("span", "prompt-restart-label", label));
163
+ const select = createCustomSelect({
164
+ ariaLabel: label,
165
+ className: "prompt-restart-candidate-select",
166
+ value,
167
+ disabled: options.length < 2,
168
+ options: options.map((option) => ({
169
+ value: option.key,
170
+ label: option.label,
171
+ })),
172
+ onChange: (nextKey) => {
173
+ onChange(String(nextKey));
174
+ },
175
+ });
176
+ select.setAttribute("data-testid", "prompt-restart-candidate");
177
+ field.appendChild(select);
178
+ const hintEl = el("p", "prompt-restart-hint", hint ?? "");
179
+ if (!hint) hintEl.hidden = true;
180
+ field.appendChild(hintEl);
181
+ return { field, select, hintEl };
182
+ }
183
+
184
+ function createPromptRestartModeSwitch({ value = "append", onChange }) {
185
+ const field = el("div", "prompt-restart-field prompt-restart-mode-field");
186
+ field.appendChild(el("span", "prompt-restart-label", "编辑模式"));
187
+ const group = el("div", "prompt-restart-mode-switch");
188
+ group.setAttribute("role", "radiogroup");
189
+ group.setAttribute("aria-label", "编辑模式");
190
+ group.setAttribute("data-testid", "prompt-restart-mode");
191
+ let current = value === "replace" ? "replace" : "append";
192
+ const buttons = [];
193
+ const sync = () => {
194
+ for (const btn of buttons) {
195
+ const selected = btn.dataset.mode === current;
196
+ btn.setAttribute("aria-checked", selected ? "true" : "false");
197
+ btn.classList.toggle("is-selected", selected);
198
+ }
199
+ };
200
+ for (const mode of [
201
+ { value: "append", label: "追加指令" },
202
+ { value: "replace", label: "全文替换" },
203
+ ]) {
204
+ const btn = document.createElement("button");
205
+ btn.type = "button";
206
+ btn.className = "prompt-restart-mode-option";
207
+ btn.dataset.mode = mode.value;
208
+ btn.setAttribute("role", "radio");
209
+ btn.setAttribute("aria-label", mode.label);
210
+ btn.textContent = mode.label;
211
+ btn.addEventListener("click", () => {
212
+ if (current === mode.value) return;
213
+ current = mode.value;
214
+ sync();
215
+ onChange?.(current);
216
+ });
217
+ buttons.push(btn);
218
+ group.appendChild(btn);
219
+ }
220
+ Object.defineProperty(group, "value", {
221
+ configurable: true,
222
+ enumerable: true,
223
+ get() {
224
+ return current;
225
+ },
226
+ set(next) {
227
+ current = next === "replace" ? "replace" : "append";
228
+ sync();
229
+ },
230
+ });
231
+ sync();
232
+ field.appendChild(group);
233
+ const hint = el(
234
+ "p",
235
+ "prompt-restart-hint",
236
+ "追加会保留原提示词;全文替换会改写整份底稿。",
237
+ );
238
+ field.appendChild(hint);
239
+ return { field, group, hint };
240
+ }
241
+
242
+ function createPromptRestartPromptCard({
243
+ title,
244
+ badge,
245
+ readCandidate,
246
+ preTestId,
247
+ collapsible = false,
248
+ startCollapsed = false,
249
+ }) {
250
+ const card = el("div", "prompt-restart-prompt-card");
251
+ const head = el("div", "prompt-restart-prompt-head");
252
+ const titles = el("div", "prompt-restart-prompt-titles");
253
+ const titleEl = el("span", "prompt-restart-prompt-title", title);
254
+ titles.appendChild(titleEl);
255
+ if (badge) {
256
+ titles.appendChild(el("span", "prompt-restart-prompt-badge", badge));
257
+ }
258
+ const metaEl = el("span", "prompt-restart-prompt-meta", "");
259
+ titles.appendChild(metaEl);
260
+ head.appendChild(titles);
261
+ const headActions = el("div", "prompt-restart-prompt-actions");
262
+ let fold = null;
263
+ let collapsed = Boolean(collapsible && startCollapsed);
264
+ const syncFold = () => {
265
+ if (!fold) return;
266
+ card.classList.toggle("is-collapsed", collapsed);
267
+ fold.setAttribute("aria-expanded", collapsed ? "false" : "true");
268
+ fold.textContent = collapsed ? "展开" : "收起";
269
+ };
270
+ if (collapsible) {
271
+ card.classList.add("is-collapsible");
272
+ fold = document.createElement("button");
273
+ fold.type = "button";
274
+ fold.className = "prompt-restart-prompt-fold";
275
+ fold.setAttribute("aria-controls", preTestId);
276
+ fold.addEventListener("click", (event) => {
277
+ event.preventDefault();
278
+ event.stopPropagation();
279
+ collapsed = !collapsed;
280
+ syncFold();
281
+ });
282
+ headActions.appendChild(fold);
283
+ }
284
+ headActions.appendChild(createPromptRestartCopyButton(readCandidate));
285
+ head.appendChild(headActions);
286
+ card.appendChild(head);
287
+ const pre = document.createElement("pre");
288
+ pre.className = "prompt-restart-readonly";
289
+ pre.setAttribute("data-testid", preTestId);
290
+ if (preTestId) pre.id = preTestId;
291
+ card.appendChild(pre);
292
+ const sync = (candidate) => {
293
+ const next = candidate ?? readCandidate?.();
294
+ titleEl.textContent =
295
+ next?.key === "frozen"
296
+ ? "冻结原始提示词"
297
+ : (next?.label ?? title);
298
+ pre.textContent = next?.prompt ?? "";
299
+ metaEl.textContent = formatPromptRestartMeta(next?.prompt ?? "");
300
+ if (next?.truncated) metaEl.textContent += " · 已截断";
301
+ };
302
+ sync(readCandidate?.());
303
+ syncFold();
304
+ return { card, pre, titleEl, metaEl, sync };
305
+ }
306
+
47
307
  function renderDagInspectorForCurrent() {
48
308
  if (!inspectorSelection.dagRunId || !inspectorSelection.node) return;
49
309
  renderDagInspector(
@@ -68,6 +328,10 @@ function resetInspectorNodeSelection(dagRunId, nodeId) {
68
328
  uiState.dagNodeInputData = null;
69
329
  uiState.dagNodeInputError = null;
70
330
  uiState.dagNodeInputLoading = false;
331
+ uiState.dagNodeProcessData = null;
332
+ uiState.dagNodeProcessError = null;
333
+ uiState.dagNodeProcessLoading = false;
334
+ uiState.dagNodeProcessIdentity = null;
71
335
  // Drop execution-output cache/identity so node A's bodies never leak into B.
72
336
  clearDagNodeExecutionOutputCache();
73
337
  // Bump the session-event identity token so any in-flight response for the
@@ -725,6 +989,11 @@ function renderDagNodeInputDetail(content, data) {
725
989
  }
726
990
  section.appendChild(dl);
727
991
  appendCodeList(section, "skills", data.executorInput.skills);
992
+ appendCodeList(
993
+ section,
994
+ "piExtensions(声明)",
995
+ data.executorInput.piExtensions,
996
+ );
728
997
  if (data.executorInput.shell) {
729
998
  const shell = data.executorInput.shell;
730
999
  const shellDl = el("dl", "node-input-summary");
@@ -743,6 +1012,12 @@ function renderDagNodeInputDetail(content, data) {
743
1012
  (shell.gates ?? []).join(", ") || "(none)",
744
1013
  null,
745
1014
  );
1015
+ appendMetaRow(
1016
+ shellDl,
1017
+ "handlers",
1018
+ (shell.handlers ?? []).join(", ") || "(none)",
1019
+ null,
1020
+ );
746
1021
  section.appendChild(shellDl);
747
1022
  if ((shell.commands ?? []).length > 0) {
748
1023
  const list = el("ol", "node-input-command-list");
@@ -799,6 +1074,52 @@ function renderDagNodeInputDetail(content, data) {
799
1074
  wrap.appendChild(section);
800
1075
  }
801
1076
 
1077
+ if (data.piExtensionRunFacts) {
1078
+ const facts = data.piExtensionRunFacts;
1079
+ const section = el("section", "node-input-section");
1080
+ section.appendChild(el("h4", null, "Pi 扩展运行事实"));
1081
+ if (!facts.exists) {
1082
+ section.appendChild(
1083
+ el(
1084
+ "p",
1085
+ "muted",
1086
+ facts.parseError
1087
+ ? `产物 ${facts.artifact} 读取失败(${facts.parseError})。`
1088
+ : `产物 ${facts.artifact} 尚未生成(节点未执行或缺失)。`,
1089
+ ),
1090
+ );
1091
+ } else {
1092
+ const dl = el("dl", "node-input-summary");
1093
+ appendMetaRow(
1094
+ dl,
1095
+ "resolved",
1096
+ facts.resolved
1097
+ .map((entry) => `${entry.id} → ${entry.packageName}@${entry.version}`)
1098
+ .join(", ") || "(none)",
1099
+ null,
1100
+ );
1101
+ section.appendChild(dl);
1102
+ appendCodeList(
1103
+ section,
1104
+ "requested",
1105
+ facts.requested,
1106
+ );
1107
+ for (const missingEntry of facts.missing ?? []) {
1108
+ section.appendChild(
1109
+ el(
1110
+ "p",
1111
+ "node-input-warning",
1112
+ `降级:${missingEntry.id} 未加载(${missingEntry.reason || "unknown"})`,
1113
+ ),
1114
+ );
1115
+ }
1116
+ if ((facts.missing ?? []).length === 0) {
1117
+ section.appendChild(el("p", "muted", "全部声明扩展均已解析加载。"));
1118
+ }
1119
+ }
1120
+ wrap.appendChild(section);
1121
+ }
1122
+
802
1123
  if (data.promptFingerprint?.exists) {
803
1124
  const section = el("section", "node-input-section");
804
1125
  section.appendChild(el("h4", null, "Assembled prompt 指纹"));
@@ -914,10 +1235,488 @@ async function renderDagNodeInput(content, dagRunId, node) {
914
1235
 
915
1236
  renderDagNodeInputDetail(content, data);
916
1237
  finishInputRender();
1238
+ // Frontend prewrite 门禁 / source fidelity ledger 展示(AC-009):只读安全摘要,
1239
+ // 从 run 产物(frontend-prewrite-result.json)与 /api/tasks/:id/ledger 拉取;
1240
+ // 接口失败进入安全降级,不泄漏敏感内容。
1241
+ if (/prewrite/.test(nodeId)) {
1242
+ void renderFrontendGateLedger(content, dagRunId);
1243
+ }
1244
+ }
1245
+
1246
+ /**
1247
+ * Frontend prewrite 门禁 + ledger 摘要行(AC-BLOCK-005):纯函数,测试直接断言。
1248
+ * prewriteResult = {ok:true, body}|{ok:false, status}|null(网络失败);ledgerBody 为
1249
+ * /api/tasks/:id/ledger 响应或 null。绝不把 envelope/预览原文塞入 <pre>(删除
1250
+ * 旧 lines.push(previewText) 路径),缺失/拒绝显示明确 fail-closed 文案。
1251
+ */
1252
+ export function frontendGateLedgerLines(input) {
1253
+ const { prewriteResult, ledgerBody, taskId } = input;
1254
+ const lines = [];
1255
+ if (prewriteResult?.ok && prewriteResult.body) {
1256
+ const parsed = prewriteResult.body;
1257
+ if (parsed?.classification) {
1258
+ lines.push(`classification: ${String(parsed.classification)}`);
1259
+ if (parsed.failureCode) {
1260
+ lines.push(`failureCode: ${String(parsed.failureCode)}`);
1261
+ }
1262
+ if (parsed.failureReason) {
1263
+ lines.push(`failureReason: ${String(parsed.failureReason)}`);
1264
+ }
1265
+ } else {
1266
+ lines.push("frontend-prewrite-result.json 内容无法解析为结构化结果。");
1267
+ }
1268
+ } else if (prewriteResult && prewriteResult.status === 404) {
1269
+ lines.push("frontend-prewrite-result.json 尚未生成(门禁未执行或缺失)。");
1270
+ } else if (prewriteResult) {
1271
+ lines.push(
1272
+ "frontend-prewrite-result.json 读取失败(只读接口拒绝/服务异常,安全降级)。",
1273
+ );
1274
+ } else {
1275
+ lines.push("frontend-prewrite-result.json 尚未生成(门禁未执行或缺失)。");
1276
+ }
1277
+ if (ledgerBody && ledgerBody.present === true) {
1278
+ lines.push(formatLedgerSummary(ledgerBody));
1279
+ } else if (ledgerBody) {
1280
+ lines.push("ledger: 无(任务无引用或 ledger 缺失,不伪造计数)");
1281
+ } else if (taskId) {
1282
+ lines.push("ledger: 暂不可查(只读接口失败,安全降级)");
1283
+ }
1284
+ return lines;
1285
+ }
1286
+
1287
+ /** DAG run 专属 prewrite-result 只读拉取(内联 fetch,不新增 api.js 依赖)。 */
1288
+ async function fetchPrewriteResultJson(dagRunId) {
1289
+ try {
1290
+ const res = await fetchWithWorkspaceScope(
1291
+ `/api/dag-runs/${encodeURIComponent(dagRunId)}/prewrite-result`,
1292
+ { credentials: "same-origin" },
1293
+ );
1294
+ if (!res.ok) return { ok: false, status: res.status };
1295
+ return { ok: true, body: await res.json() };
1296
+ } catch {
1297
+ return null; // 网络失败
1298
+ }
1299
+ }
1300
+
1301
+ /**
1302
+ * Resolve the DAG-owned task identity, prewrite result, and safe ledger summary.
1303
+ * Exported so the real browser request sequence can be regression-tested without
1304
+ * relying on source-string assertions.
1305
+ */
1306
+ export async function fetchFrontendGateLedgerData(dagRunId) {
1307
+ const run = await fetchJson(
1308
+ `/api/dag-runs/${encodeURIComponent(dagRunId)}`,
1309
+ );
1310
+ const taskId = typeof run?.taskId === "string" ? run.taskId : "";
1311
+ const prewriteResult = await fetchPrewriteResultJson(dagRunId);
1312
+ let ledgerBody = null;
1313
+ if (taskId) {
1314
+ ledgerBody = await fetchJson(
1315
+ `/api/tasks/${encodeURIComponent(taskId)}/ledger`,
1316
+ );
1317
+ }
1318
+ return { taskId, prewriteResult, ledgerBody };
1319
+ }
1320
+
1321
+ /**
1322
+ * Inspect 节点面板:frontend prewrite 门禁分类/稳定 failure code + ledger 安全摘要。
1323
+ * 数据全部来自只读接口与确定性产物(AC-BLOCK-005):task identity 从 DAG run
1324
+ * GET /api/dag-runs/:dagRunId 读取,prewrite 门禁调 DAG run 专属
1325
+ * GET /api/dag-runs/:dagRunId/prewrite-result;任何失败仅显示安全降级文案,
1326
+ * 绝不 push envelope。
1327
+ */
1328
+ async function renderFrontendGateLedger(content, dagRunId) {
1329
+ const section = el("section", "node-input-section");
1330
+ section.appendChild(
1331
+ el("h4", null, "Frontend prewrite 门禁 / source fidelity ledger"),
1332
+ );
1333
+ const status = el("p", "muted", "加载中…");
1334
+ status.setAttribute("role", "status");
1335
+ section.appendChild(status);
1336
+ content.appendChild(section);
1337
+ try {
1338
+ const { taskId, prewriteResult, ledgerBody } =
1339
+ await fetchFrontendGateLedgerData(dagRunId);
1340
+ const lines = frontendGateLedgerLines({
1341
+ prewriteResult,
1342
+ ledgerBody,
1343
+ taskId,
1344
+ });
1345
+ const pre = el("pre", "cmd", lines.join("\n"));
1346
+ pre.dataset.testid = "frontend-prewrite-ledger-summary";
1347
+ status.replaceWith(pre);
1348
+ } catch (error) {
1349
+ status.textContent = `加载失败:${
1350
+ error instanceof Error ? error.message : String(error)
1351
+ }(安全降级,不展示敏感内容)`;
1352
+ }
1353
+ }
1354
+
1355
+ /**
1356
+ * Experimental prompt-override rerun tab. Only reachable when the server
1357
+ * injected the mutation token (switch on). Every node shows its frozen
1358
+ * subtask_prompt here; only FAILED (ERROR) nodes additionally get the editor +
1359
+ * restart flow: preflight (dagRerunPlan) binds the edit into planHash, dagRerun
1360
+ * executes it as a Console operation, and on success we jump to the new run.
1361
+ */
1362
+ async function renderPromptRestart(content, dagRunId, node) {
1363
+ const nodeId = node.nodeId;
1364
+ clearNode(content);
1365
+
1366
+ const loading = el("p", "muted", "正在加载冻结提示词…");
1367
+ loading.setAttribute("role", "status");
1368
+ loading.setAttribute("aria-live", "polite");
1369
+ content.appendChild(loading);
1370
+ const input = await fetchDagNodeInput(dagRunId, nodeId);
1371
+ if (
1372
+ !content.isConnected ||
1373
+ content.dataset.dagNodeId !== String(nodeId) ||
1374
+ content.dataset.dagRunId !== String(dagRunId) ||
1375
+ activeInspectorTab() !== "prompt-restart"
1376
+ ) {
1377
+ return;
1378
+ }
1379
+ clearNode(content);
1380
+
1381
+ if (!input.ok || !input.body?.available) {
1382
+ const error = el("p", "node-input-error", "无法读取该节点的冻结提示词。");
1383
+ error.setAttribute("role", "alert");
1384
+ content.appendChild(error);
1385
+ return;
1386
+ }
1387
+ const frozenBasePrompt =
1388
+ typeof input.body.prompt?.text === "string" ? input.body.prompt.text : "";
1389
+ if (!frozenBasePrompt) {
1390
+ const error = el("p", "node-input-error", "该节点没有冻结提示词。");
1391
+ error.setAttribute("role", "alert");
1392
+ content.appendChild(error);
1393
+ return;
1394
+ }
1395
+ const promptCandidateOptions = buildPromptRestartCandidateOptions(
1396
+ frozenBasePrompt,
1397
+ input.body.promptCandidates,
1398
+ );
1399
+ let selectedCandidate = promptCandidateOptions[0];
1400
+ const historicalCandidatesMissing = Array.isArray(input.body.warnings)
1401
+ ? input.body.warnings.some((warning) =>
1402
+ String(warning).startsWith("Failed prompt candidate missing for attempt "),
1403
+ )
1404
+ : false;
1405
+
1406
+ if (node.status !== "ERROR") {
1407
+ // Non-failed nodes still show their frozen prompt — read-only.
1408
+ const panel = el("div", "prompt-restart-panel is-readonly");
1409
+ panel.appendChild(
1410
+ createPromptRestartBanner({
1411
+ tone: "readonly",
1412
+ kicker: "只读",
1413
+ title: `当前节点 ${node.status ?? "unknown"}`,
1414
+ body: "仅失败(ERROR)节点支持编辑提示词并重启。以下可只读查看冻结提示词与历史失败候选。",
1415
+ }),
1416
+ );
1417
+ const promptCard = createPromptRestartPromptCard({
1418
+ title: "冻结原始提示词",
1419
+ badge: "只读",
1420
+ readCandidate: () => selectedCandidate,
1421
+ preTestId: "prompt-restart-readonly",
1422
+ });
1423
+ const picker = createPromptRestartCandidatePicker({
1424
+ options: promptCandidateOptions,
1425
+ value: selectedCandidate.key,
1426
+ label: "提示词版本(只读)",
1427
+ hint:
1428
+ promptCandidateOptions.length === 1
1429
+ ? historicalCandidatesMissing
1430
+ ? "该历史运行未保存失败 attempt 的实际提示词;新运行会在此列出。"
1431
+ : "该节点没有已保存的失败 attempt。"
1432
+ : "可切换查看冻结底稿与历史失败候选。",
1433
+ onChange: (nextKey) => {
1434
+ selectedCandidate =
1435
+ promptCandidateOptions.find((option) => option.key === nextKey) ??
1436
+ promptCandidateOptions[0];
1437
+ promptCard.sync(selectedCandidate);
1438
+ },
1439
+ });
1440
+ panel.appendChild(picker.field);
1441
+ panel.appendChild(promptCard.card);
1442
+ content.appendChild(panel);
1443
+ return;
1444
+ }
1445
+
1446
+ if (input.body.prompt?.truncated === true) {
1447
+ const error = el(
1448
+ "p",
1449
+ "node-input-error",
1450
+ "冻结提示词被截断,为避免误替换请改用 CLI(dag rerun --prompt-override)。",
1451
+ );
1452
+ error.setAttribute("role", "alert");
1453
+ content.appendChild(error);
1454
+ return;
1455
+ }
1456
+
1457
+ const box = el("div", "prompt-restart-box prompt-restart-panel is-edit");
1458
+ box.appendChild(
1459
+ createPromptRestartBanner({
1460
+ tone: "edit",
1461
+ kicker: "可编辑",
1462
+ title: "从失败节点编辑提示词并重启",
1463
+ body: "父 run spec 保持冻结,将创建带修改提示词的续跑 run。",
1464
+ }),
1465
+ );
1466
+ const promptCard = createPromptRestartPromptCard({
1467
+ title: `冻结提示词(只读底稿 · ${nodeId})`,
1468
+ badge: "底稿",
1469
+ readCandidate: () => selectedCandidate,
1470
+ preTestId: "prompt-restart-base",
1471
+ collapsible: true,
1472
+ startCollapsed: true,
1473
+ });
1474
+ const picker = createPromptRestartCandidatePicker({
1475
+ options: promptCandidateOptions,
1476
+ value: selectedCandidate.key,
1477
+ label: "提示词版本",
1478
+ hint:
1479
+ promptCandidateOptions.length > 1
1480
+ ? "选择失败候选会载入全文替换;冻结原始提示词仍可使用追加模式。"
1481
+ : historicalCandidatesMissing
1482
+ ? "该历史运行未保存失败 attempt 的实际提示词;新运行会在此列出。"
1483
+ : "该节点没有已保存的失败 attempt;当前仅有冻结原始提示词。",
1484
+ onChange: (nextKey) => {
1485
+ selectedCandidate =
1486
+ promptCandidateOptions.find((option) => option.key === nextKey) ??
1487
+ promptCandidateOptions[0];
1488
+ promptCard.sync(selectedCandidate);
1489
+ replaceArea.value = selectedCandidate.prompt;
1490
+ if (selectedCandidate.key !== "frozen") {
1491
+ modeSelect.value = "replace";
1492
+ }
1493
+ picker.hintEl.hidden = false;
1494
+ picker.hintEl.textContent = selectedCandidate.truncated
1495
+ ? "该候选已截断,仅供查看,不能直接提交重启。"
1496
+ : selectedCandidate.key === "frozen"
1497
+ ? "冻结原始提示词可使用追加或全文替换。"
1498
+ : "已载入该失败候选;可在全文替换编辑器中修订后重启。";
1499
+ applyModeVisibility();
1500
+ },
1501
+ });
1502
+ const modeSwitch = createPromptRestartModeSwitch({
1503
+ value: "append",
1504
+ onChange: () => applyModeVisibility(),
1505
+ });
1506
+ const modeSelect = modeSwitch.group;
1507
+ const toolbar = el("div", "prompt-restart-toolbar");
1508
+ toolbar.appendChild(picker.field);
1509
+ toolbar.appendChild(modeSwitch.field);
1510
+ box.appendChild(toolbar);
1511
+
1512
+ const addendumLabel = el("label", "prompt-restart-field prompt-restart-work");
1513
+ addendumLabel.appendChild(
1514
+ el("span", "prompt-restart-label", "追加指令"),
1515
+ );
1516
+ addendumLabel.appendChild(
1517
+ el(
1518
+ "p",
1519
+ "prompt-restart-hint",
1520
+ "将包裹为 <operator_addendum> 块附加到原提示词末尾",
1521
+ ),
1522
+ );
1523
+ const addendumArea = document.createElement("textarea");
1524
+ addendumArea.className = "prompt-restart-textarea prompt-restart-textarea-work";
1525
+ addendumArea.rows = 8;
1526
+ addendumArea.placeholder = "例如:重点检查 X 边界;输出前先自检 Y 约束…";
1527
+ addendumArea.setAttribute("data-testid", "prompt-restart-addendum");
1528
+ addendumLabel.appendChild(addendumArea);
1529
+ box.appendChild(addendumLabel);
1530
+
1531
+ const replaceLabel = el("label", "prompt-restart-field prompt-restart-work");
1532
+ replaceLabel.appendChild(
1533
+ el("span", "prompt-restart-label", `完整提示词(${nodeId})`),
1534
+ );
1535
+ const replaceArea = document.createElement("textarea");
1536
+ replaceArea.className = "prompt-restart-textarea prompt-restart-textarea-work";
1537
+ replaceArea.rows = 12;
1538
+ replaceArea.value = selectedCandidate.prompt;
1539
+ replaceArea.setAttribute("data-testid", "prompt-restart-textarea");
1540
+ replaceLabel.appendChild(replaceArea);
1541
+ box.appendChild(replaceLabel);
1542
+
1543
+ const baseLabel = promptCard.card;
1544
+ box.appendChild(baseLabel);
1545
+
1546
+ const applyModeVisibility = () => {
1547
+ const append = modeSelect.value === "append";
1548
+ baseLabel.classList.toggle("is-hidden", !append);
1549
+ addendumLabel.classList.toggle("is-hidden", !append);
1550
+ replaceLabel.classList.toggle("is-hidden", append);
1551
+ };
1552
+ applyModeVisibility();
1553
+
1554
+ const reasonLabel = el("label", "prompt-restart-field");
1555
+ reasonLabel.appendChild(el("span", "prompt-restart-label", "重启原因"));
1556
+ reasonLabel.appendChild(
1557
+ el("p", "prompt-restart-hint", "结构化说明,会写入续跑 provenance"),
1558
+ );
1559
+ const reasonArea = document.createElement("textarea");
1560
+ reasonArea.className = "prompt-restart-textarea prompt-restart-textarea-compact";
1561
+ reasonArea.rows = 3;
1562
+ reasonArea.value = `operator-prompt-override / run=${dagRunId} / node=${nodeId} / 手工编辑失败节点提示词后重启`;
1563
+ reasonArea.setAttribute("data-testid", "prompt-restart-reason");
1564
+ reasonLabel.appendChild(reasonArea);
1565
+ box.appendChild(reasonLabel);
1566
+
1567
+ const row = el("div", "prompt-restart-actions prompt-restart-footer");
1568
+ const status = el("p", "prompt-restart-status", "");
1569
+ status.setAttribute("role", "status");
1570
+ status.setAttribute("aria-live", "polite");
1571
+ const submit = document.createElement("button");
1572
+ submit.type = "button";
1573
+ submit.className = "prompt-restart-submit";
1574
+ submit.textContent = "资格预检并重启";
1575
+ submit.setAttribute("data-testid", "prompt-restart-submit");
1576
+ row.appendChild(status);
1577
+ row.appendChild(submit);
1578
+ box.appendChild(row);
1579
+
1580
+ content.appendChild(box);
1581
+
1582
+ const setStatus = (text, isError = false) => {
1583
+ status.textContent = text ?? "";
1584
+ status.classList.toggle("prompt-restart-error", isError);
1585
+ };
1586
+
1587
+ submit.addEventListener("click", () => {
1588
+ void (async () => {
1589
+ if (selectedCandidate.truncated) {
1590
+ setStatus("所选失败提示词已截断,不能作为完整提示词重启。", true);
1591
+ return;
1592
+ }
1593
+ const mode = modeSelect.value === "replace" ? "replace" : "append";
1594
+ const prompt =
1595
+ mode === "append" ? addendumArea.value : replaceArea.value;
1596
+ const reason = reasonArea.value.trim();
1597
+ if (!prompt.trim()) {
1598
+ setStatus(
1599
+ mode === "append" ? "追加指令不能为空。" : "提示词不能为空。",
1600
+ true,
1601
+ );
1602
+ return;
1603
+ }
1604
+ if (
1605
+ mode === "replace" &&
1606
+ selectedCandidate.key === "frozen" &&
1607
+ prompt === frozenBasePrompt
1608
+ ) {
1609
+ setStatus("提示词未修改;如需按原提示词重跑请使用恢复工作台。", true);
1610
+ return;
1611
+ }
1612
+ if (!reason) {
1613
+ setStatus("需要填写重启原因。", true);
1614
+ return;
1615
+ }
1616
+ submit.disabled = true;
1617
+ try {
1618
+ setStatus("正在做重跑资格预检(dagRerunPlan)…");
1619
+ const overrides = [{ nodeId, prompt, mode }];
1620
+ const planRes = await postOperatorAction("dagRerunPlan", {
1621
+ runId: dagRunId,
1622
+ fromNode: nodeId,
1623
+ promptOverrides: overrides,
1624
+ });
1625
+ const plan = planRes.body?.result ?? null;
1626
+ if (!planRes.ok || plan?.eligible !== true || !plan?.planHash) {
1627
+ setStatus(
1628
+ planRes.body?.error?.message ??
1629
+ plan?.blockedReasons?.join(", ") ??
1630
+ "资格预检未通过。",
1631
+ true,
1632
+ );
1633
+ return;
1634
+ }
1635
+
1636
+ const est = plan.estimatedExecution;
1637
+ const estText = est
1638
+ ? `(将执行 ${est.nodeCount ?? "?"} 个节点 / ${est.piCallCount ?? "?"} 次 Pi 调用)`
1639
+ : "";
1640
+ setStatus(`预检通过${estText},正在提交续跑(dagRerun)…`);
1641
+ const clientRequestId = `inspect-prompt-restart-${Date.now()}-${Math.random()
1642
+ .toString(36)
1643
+ .slice(2, 10)}`;
1644
+ const runRes = await postOperatorAction(
1645
+ "dagRerun",
1646
+ {
1647
+ runId: dagRunId,
1648
+ fromNode: nodeId,
1649
+ planHash: plan.planHash,
1650
+ reason,
1651
+ promptOverrides: overrides,
1652
+ },
1653
+ clientRequestId,
1654
+ );
1655
+ const operationId = runRes.body?.operationId;
1656
+ if (!runRes.ok || !operationId) {
1657
+ setStatus(runRes.body?.error?.message ?? "提交续跑失败。", true);
1658
+ return;
1659
+ }
1660
+
1661
+ const op = await pollOperationToTerminal(operationId, (text) =>
1662
+ setStatus(text),
1663
+ );
1664
+ if (op?.state === "succeeded") {
1665
+ const newRunId =
1666
+ op.dagRunId ?? op.result?.newRunId ?? op.runSummary?.runId ?? null;
1667
+ if (newRunId) {
1668
+ setStatus(`重启完成,新 run:${newRunId}`);
1669
+ const open = document.createElement("button");
1670
+ open.type = "button";
1671
+ open.className = "prompt-restart-submit is-secondary";
1672
+ open.textContent = "打开新 run";
1673
+ open.addEventListener("click", () => {
1674
+ window.location.hash = `#/dag/${encodeURIComponent(String(newRunId))}`;
1675
+ });
1676
+ row.appendChild(open);
1677
+ } else {
1678
+ setStatus("重启完成(未能解析新 run id,请在 DAG 历史中查看)。");
1679
+ }
1680
+ } else if (op) {
1681
+ setStatus(
1682
+ `续跑操作终态:${op.state}${op.errorMessage ? ` — ${op.errorMessage}` : ""}`,
1683
+ true,
1684
+ );
1685
+ } else {
1686
+ setStatus("等待超时:操作仍在运行,可在操作台查看进度。", true);
1687
+ }
1688
+ } catch (err) {
1689
+ setStatus(err instanceof Error ? err.message : String(err), true);
1690
+ } finally {
1691
+ submit.disabled = false;
1692
+ }
1693
+ })();
1694
+ });
1695
+ }
1696
+
1697
+ /** Poll a Console operation until a terminal state; null on poll budget end. */
1698
+ async function pollOperationToTerminal(operationId, onProgress) {
1699
+ const terminal = new Set([
1700
+ "succeeded",
1701
+ "failed",
1702
+ "timed-out",
1703
+ "needs-reconcile",
1704
+ ]);
1705
+ for (let attempt = 0; attempt < 1200; attempt += 1) {
1706
+ const res = await fetchOperatorOperation(operationId);
1707
+ const op = res.body?.operation ?? res.body;
1708
+ const state = op?.state ?? "";
1709
+ if (res.ok && terminal.has(state)) {
1710
+ return op;
1711
+ }
1712
+ onProgress(`续跑执行中…(${state || "queued"})`);
1713
+ await new Promise((resolve) => setTimeout(resolve, 3000));
1714
+ }
1715
+ return null;
917
1716
  }
918
1717
 
919
1718
  function activeInspectorTab() {
920
- return ["input", "timeline", "spec-evidence"].includes(
1719
+ return ["input", "timeline", "spec-evidence", "prompt-restart"].includes(
921
1720
  uiState.dagInspectorTab,
922
1721
  )
923
1722
  ? uiState.dagInspectorTab
@@ -1214,7 +2013,7 @@ function effectiveExecutionKey(dagRunId, node) {
1214
2013
  async function fetchExecutionOutput(dagRunId, nodeId, key) {
1215
2014
  const url = `/api/dag-runs/${encodeURIComponent(dagRunId)}/nodes/${encodeURIComponent(nodeId)}/execution-output?key=${encodeURIComponent(key)}`;
1216
2015
  try {
1217
- const res = await fetch(url);
2016
+ const res = await fetchWithWorkspaceScope(url);
1218
2017
  let body = null;
1219
2018
  try {
1220
2019
  body = await res.json();
@@ -1254,7 +2053,7 @@ function appendOutputBody(content, outputText, errorText, options = null) {
1254
2053
  content.appendChild(el("p", "empty", UI_TEXT.noOutput));
1255
2054
  return;
1256
2055
  }
1257
- if (outputText) content.appendChild(renderMarkdown(outputText));
2056
+ if (outputText) content.appendChild(renderInspectorDocument(outputText));
1258
2057
  // Truncation note only for full/cached execution-output bodies (AC-003);
1259
2058
  // preview path never invents truncated/maxBytes.
1260
2059
  if (options?.truncated === true) {
@@ -1270,7 +2069,7 @@ function appendOutputBody(content, outputText, errorText, options = null) {
1270
2069
  if (errorText) {
1271
2070
  const error = el("section", "markdown-error");
1272
2071
  error.appendChild(el("p", "markdown-error-title", "错误输出"));
1273
- error.appendChild(renderMarkdown(errorText));
2072
+ error.appendChild(renderInspectorDocument(errorText));
1274
2073
  content.appendChild(error);
1275
2074
  }
1276
2075
  }
@@ -1404,6 +2203,228 @@ function repaintOutputTabBody(content, dagRunId, node) {
1404
2203
  restoreInspectorContentScroll(content, dagRunId, node.nodeId, "output");
1405
2204
  }
1406
2205
 
2206
+ function processEmptyMessage(body) {
2207
+ const reason = body?.emptyReason;
2208
+ if (reason === "static-no-process") return UI_TEXT.staticNoProcess;
2209
+ if (reason === "shell-receipt-missing") return UI_TEXT.noShellProcess;
2210
+ if (reason === "shell-preset-pending") return UI_TEXT.noPresetProcess;
2211
+ if (reason === "pi-session") return UI_TEXT.noSessionEvents;
2212
+ return UI_TEXT.noSessionEvents;
2213
+ }
2214
+
2215
+ function formatProcessBlock(text) {
2216
+ if (!text) return "";
2217
+ return prettyJsonBlock(text) ?? text;
2218
+ }
2219
+
2220
+ function appendProcessPre(parent, text) {
2221
+ if (!text) return;
2222
+ const pre = el("pre", "process-timeline-code");
2223
+ pre.textContent = formatProcessBlock(text);
2224
+ parent.appendChild(pre);
2225
+ }
2226
+
2227
+ function renderProcessCommandCard(command) {
2228
+ const failed = command.ok === false;
2229
+ const card = el(
2230
+ "div",
2231
+ `process-timeline-card process-timeline-tool-card${failed ? " is-error" : ""}`,
2232
+ );
2233
+ const details = document.createElement("details");
2234
+ details.className = "process-timeline-details";
2235
+ details.open = failed;
2236
+ const summary = el("summary", "process-timeline-summary");
2237
+ summary.appendChild(
2238
+ el("span", "process-timeline-summary-title", command.command || "command"),
2239
+ );
2240
+ summary.appendChild(
2241
+ el(
2242
+ "span",
2243
+ `process-timeline-summary-status status-${failed ? "failed" : "succeeded"}`,
2244
+ failed ? "失败" : command.reused ? "复用" : "成功",
2245
+ ),
2246
+ );
2247
+ details.appendChild(summary);
2248
+ const body = el("div", "process-timeline-card-body");
2249
+ const meta = [
2250
+ command.exitCode != null ? `exit ${command.exitCode}` : null,
2251
+ command.durationMs != null ? `耗时 ${formatMs(command.durationMs)}` : null,
2252
+ command.failureCategory ? command.failureCategory : null,
2253
+ ].filter(Boolean);
2254
+ if (meta.length > 0) {
2255
+ body.appendChild(el("div", "process-timeline-section-label", meta.join(" · ")));
2256
+ }
2257
+ if (command.stdout) {
2258
+ body.appendChild(el("div", "process-timeline-section-label", "stdout"));
2259
+ appendProcessPre(body, command.stdout);
2260
+ }
2261
+ if (command.stderr) {
2262
+ body.appendChild(el("div", "process-timeline-section-label", "stderr"));
2263
+ appendProcessPre(body, command.stderr);
2264
+ }
2265
+ if (command.truncated) {
2266
+ body.appendChild(el("p", "muted", "内容过长,已截断。"));
2267
+ }
2268
+ details.appendChild(body);
2269
+ card.appendChild(details);
2270
+ return card;
2271
+ }
2272
+
2273
+ function renderProcessStepCard(step) {
2274
+ const failed = step.ok === false;
2275
+ const card = el(
2276
+ "div",
2277
+ `process-timeline-card process-timeline-tool-card${failed ? " is-error" : ""}`,
2278
+ );
2279
+ const details = document.createElement("details");
2280
+ details.className = "process-timeline-details";
2281
+ details.open = true;
2282
+ const summary = el("summary", "process-timeline-summary");
2283
+ summary.appendChild(
2284
+ el("span", "process-timeline-summary-title", step.label || step.id || "step"),
2285
+ );
2286
+ summary.appendChild(
2287
+ el(
2288
+ "span",
2289
+ `process-timeline-summary-status status-${failed ? "failed" : "succeeded"}`,
2290
+ failed ? "失败" : "成功",
2291
+ ),
2292
+ );
2293
+ details.appendChild(summary);
2294
+ const body = el("div", "process-timeline-card-body");
2295
+ if (step.durationMs != null) {
2296
+ body.appendChild(
2297
+ el("div", "process-timeline-section-label", `耗时 ${formatMs(step.durationMs)}`),
2298
+ );
2299
+ }
2300
+ if (step.detail) appendProcessPre(body, step.detail);
2301
+ if (step.truncated) {
2302
+ body.appendChild(el("p", "muted", "内容过长,已截断。"));
2303
+ }
2304
+ details.appendChild(body);
2305
+ card.appendChild(details);
2306
+ return card;
2307
+ }
2308
+
2309
+ function renderNonPiProcess(content, body) {
2310
+ content.classList.add("process-timeline-scroll");
2311
+ if (!body || body.emptyReason || ((body.commands ?? []).length === 0 && (body.steps ?? []).length === 0)) {
2312
+ content.appendChild(el("p", "empty", processEmptyMessage(body)));
2313
+ return;
2314
+ }
2315
+ const heading =
2316
+ body.kind === "shell-preset"
2317
+ ? UI_TEXT.shellPresetHeading
2318
+ : UI_TEXT.shellCommandsHeading;
2319
+ if (body.handler) {
2320
+ content.appendChild(
2321
+ el("p", "process-timeline-note", `${heading} · ${body.handler}`),
2322
+ );
2323
+ }
2324
+ const ul = el("ul", "process-timeline process-timeline-rich");
2325
+ const items = (body.commands ?? []).length > 0 ? body.commands : body.steps ?? [];
2326
+ for (let index = 0; index < items.length; index += 1) {
2327
+ const item = items[index];
2328
+ const failed = item.ok === false;
2329
+ const li = el(
2330
+ "li",
2331
+ `process-timeline-item process-timeline-tool-end${failed ? " is-error" : ""}${index === 0 ? " is-first" : ""}${index === items.length - 1 ? " is-last" : ""}`,
2332
+ );
2333
+ const rail = el("span", "process-timeline-rail");
2334
+ rail.appendChild(el("span", "process-timeline-dot"));
2335
+ li.appendChild(rail);
2336
+ const cardBody = el("div", "process-timeline-body");
2337
+ cardBody.appendChild(
2338
+ body.kind === "shell-preset"
2339
+ ? renderProcessStepCard(item)
2340
+ : renderProcessCommandCard(item),
2341
+ );
2342
+ li.appendChild(cardBody);
2343
+ ul.appendChild(li);
2344
+ }
2345
+ content.appendChild(ul);
2346
+ }
2347
+
2348
+ export async function mergeNodeProcess(dagRunId, node) {
2349
+ if (!dagRunId || !node?.nodeId) return;
2350
+ const nodeId = node.nodeId;
2351
+ const key = effectiveExecutionKey(dagRunId, node);
2352
+ const identity = makeDagNodeProcessIdentity(dagRunId, nodeId, key);
2353
+ uiState.dagNodeProcessIdentity = identity;
2354
+ uiState.dagNodeProcessLoading = true;
2355
+ const result = await fetchDagNodeProcess(dagRunId, nodeId, key);
2356
+ if (uiState.dagNodeProcessIdentity !== identity) return;
2357
+ if (!result.ok || !result.body) {
2358
+ uiState.dagNodeProcessError =
2359
+ result.body?.error || UI_TEXT.processError;
2360
+ uiState.dagNodeProcessData = {
2361
+ schemaVersion: 1,
2362
+ dagRunId,
2363
+ nodeId,
2364
+ kind: "none",
2365
+ executor: node.executor ?? null,
2366
+ handler: null,
2367
+ emptyReason: "not-executed",
2368
+ key: key ?? null,
2369
+ commands: [],
2370
+ steps: [],
2371
+ };
2372
+ uiState.dagNodeProcessLoading = false;
2373
+ return;
2374
+ }
2375
+ uiState.dagNodeProcessError = null;
2376
+ uiState.dagNodeProcessData = result.body;
2377
+ uiState.dagNodeProcessLoading = false;
2378
+ }
2379
+
2380
+ function renderExecutorAwareTimeline(content, dagRunId, node) {
2381
+ content.dataset.dagRunId = String(dagRunId ?? "");
2382
+ content.dataset.nodeId = String(node.nodeId ?? "");
2383
+ const key = effectiveExecutionKey(dagRunId, node);
2384
+ const cached = uiState.dagNodeProcessData;
2385
+ const sameNode =
2386
+ cached &&
2387
+ cached.dagRunId === dagRunId &&
2388
+ cached.nodeId === node.nodeId &&
2389
+ (cached.key ?? null) === (key ?? null);
2390
+ if (sameNode && uiState.dagNodeProcessError) {
2391
+ content.appendChild(el("p", "empty", uiState.dagNodeProcessError));
2392
+ return;
2393
+ }
2394
+ if (sameNode && cached.kind === "pi-session") {
2395
+ renderSessionTimeline(content, node.nodeId, {
2396
+ executionKey: effectiveExecutionKey(dagRunId, node),
2397
+ });
2398
+ return;
2399
+ }
2400
+ if (sameNode && cached.kind !== "pi-session") {
2401
+ renderNonPiProcess(content, cached);
2402
+ return;
2403
+ }
2404
+ if ((node.executor ?? "pi") === "pi") {
2405
+ renderSessionTimeline(content, node.nodeId, {
2406
+ executionKey: effectiveExecutionKey(dagRunId, node),
2407
+ });
2408
+ return;
2409
+ }
2410
+ if (uiState.dagNodeProcessError && sameNode) {
2411
+ content.appendChild(el("p", "empty", uiState.dagNodeProcessError));
2412
+ return;
2413
+ }
2414
+ const loading = el("p", "muted", UI_TEXT.processLoading);
2415
+ loading.setAttribute("role", "status");
2416
+ content.appendChild(loading);
2417
+ void mergeNodeProcess(dagRunId, node).then(() => {
2418
+ if (
2419
+ uiState.dagInspectorTab === "timeline" &&
2420
+ uiState.selectedDagNodeId === node.nodeId &&
2421
+ uiState.currentDagRunId === dagRunId
2422
+ ) {
2423
+ renderDagInspectorForCurrent();
2424
+ }
2425
+ });
2426
+ }
2427
+
1407
2428
  function fillDagInspectorContent(content, dagRunId, node) {
1408
2429
  const previousRenderedNode = content.dataset.dagNodeId ?? "";
1409
2430
  const previousRenderedRun = content.dataset.dagRunId ?? "";
@@ -1450,10 +2471,12 @@ function fillDagInspectorContent(content, dagRunId, node) {
1450
2471
  );
1451
2472
  if (activeTab === "spec-evidence") {
1452
2473
  void renderSpecEvidence(content, dagRunId, node.nodeId);
2474
+ } else if (activeTab === "prompt-restart") {
2475
+ void renderPromptRestart(content, dagRunId, node);
1453
2476
  } else if (activeTab === "timeline") {
1454
2477
  const attemptBuilt = buildAttemptBar(dagRunId, node);
1455
2478
  if (attemptBuilt?.bar) content.appendChild(attemptBuilt.bar);
1456
- renderSessionTimeline(content, node.nodeId);
2479
+ renderExecutorAwareTimeline(content, dagRunId, node);
1457
2480
  } else if (activeTab === "input") {
1458
2481
  void renderDagNodeInput(content, dagRunId, node);
1459
2482
  } else {
@@ -1488,6 +2511,16 @@ function fillDagInspectorContent(content, dagRunId, node) {
1488
2511
  }
1489
2512
  }
1490
2513
 
2514
+ function inspectorNodePurpose(node) {
2515
+ return describeDagNodePurpose(node?.nodeId, node);
2516
+ }
2517
+
2518
+ function appendInspectorPurpose(title, node) {
2519
+ const purpose = inspectorNodePurpose(node);
2520
+ if (!purpose) return;
2521
+ title.appendChild(el("p", "dag-inspector-purpose", purpose));
2522
+ }
2523
+
1491
2524
  function buildDagInspectorHeader(dagRunId, node) {
1492
2525
  const header = el("div", "dag-inspector-header");
1493
2526
  header.dataset.dagRunId = String(dagRunId ?? "");
@@ -1495,6 +2528,7 @@ function buildDagInspectorHeader(dagRunId, node) {
1495
2528
  const title = el("div", "dag-inspector-title");
1496
2529
  title.appendChild(el("h3", null, "节点检查器"));
1497
2530
  title.appendChild(el("div", "dag-inspector-node", node.nodeId));
2531
+ appendInspectorPurpose(title, node);
1498
2532
  header.appendChild(title);
1499
2533
  const controls = el("div", "dag-inspector-controls");
1500
2534
  const status = badge(node.status ?? "unknown");
@@ -1517,6 +2551,13 @@ function buildDagInspectorHeader(dagRunId, node) {
1517
2551
  ["timeline", "执行过程", "ri-route-line"],
1518
2552
  ["spec-evidence", "规范证据", "ri-book-open-line"],
1519
2553
  ];
2554
+ // Prompt-override rerun: the server injects the SCOPED inspect mutation
2555
+ // token into /inspect HTML by default (it only authorizes
2556
+ // dagRerunPlan/dagRerun). The emergency kill switch suppresses the token;
2557
+ // no token → no tab → the inspector stays strictly read-only.
2558
+ if (globalThis.__INSPECT_MUTATION_TOKEN__) {
2559
+ tabDefs.push(["prompt-restart", "提示词重启", "ri-restart-line"]);
2560
+ }
1520
2561
  const tabs = el("div", "dag-inspector-tabs");
1521
2562
  tabs.setAttribute("role", "tablist");
1522
2563
  tabs.setAttribute("aria-label", "节点检查器面板");
@@ -1567,6 +2608,16 @@ function syncDagInspectorHeader(panel, dagRunId, node) {
1567
2608
  header.dataset.dagNodeId = String(node.nodeId ?? "");
1568
2609
  const nodeLabel = header.querySelector(".dag-inspector-node");
1569
2610
  if (nodeLabel) nodeLabel.textContent = node.nodeId;
2611
+ const title = header.querySelector(".dag-inspector-title");
2612
+ const purposeLine = header.querySelector(".dag-inspector-purpose");
2613
+ const purpose = inspectorNodePurpose(node);
2614
+ if (purposeLine && purpose) {
2615
+ purposeLine.textContent = purpose;
2616
+ } else if (purposeLine && !purpose) {
2617
+ purposeLine.remove();
2618
+ } else if (!purposeLine && purpose && title) {
2619
+ title.appendChild(el("p", "dag-inspector-purpose", purpose));
2620
+ }
1570
2621
  const status = header.querySelector(".dag-inspector-status");
1571
2622
  if (status) {
1572
2623
  const next = badge(node.status ?? "unknown");
@@ -1606,7 +2657,14 @@ function dagInspectorSignature(dagRunId, node) {
1606
2657
  uiState.dagNodeExecutionOutputLoading ? "loading" : "idle",
1607
2658
  ]
1608
2659
  : tab === "timeline"
1609
- ? [uiState.sessionEventOffset]
2660
+ ? [
2661
+ uiState.sessionEventOffset,
2662
+ uiState.dagNodeProcessData?.kind ?? "",
2663
+ uiState.dagNodeProcessData?.emptyReason ?? "",
2664
+ (uiState.dagNodeProcessData?.commands ?? []).length,
2665
+ (uiState.dagNodeProcessData?.steps ?? []).length,
2666
+ uiState.dagNodeProcessLoading ? "loading" : "idle",
2667
+ ]
1610
2668
  : [node.status ?? ""];
1611
2669
  // Attempt dimension: selection identity + history shape. Poll refreshes keep
1612
2670
  // the DOM while selection/history are unchanged; switching attempt or a