@tea-agent/loop-agent 0.38.2 → 0.38.4

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 (192) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +171 -96
  3. package/README.md +11 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/advance.js +4 -0
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +16 -5
  8. package/dist/cli/program.js +16 -2
  9. package/dist/cli/update/notifier.js +2 -2
  10. package/dist/cli.js +2 -1
  11. package/dist/commands/dag-rerun.js +55 -1
  12. package/dist/commands/init-model-catalog.js +464 -0
  13. package/dist/commands/init-upgrade.js +265 -97
  14. package/dist/commands/init.js +428 -27
  15. package/dist/commands/inspect-next.js +8 -0
  16. package/dist/commands/task-advance.js +31 -0
  17. package/dist/executors/dag-pi-executor.js +76 -10
  18. package/dist/executors/pi-event-serializer.js +42 -1
  19. package/dist/executors/pi-executor.js +51 -0
  20. package/dist/executors/pi-extension-resolver.js +233 -0
  21. package/dist/executors/pi-playwright-cli-tool.js +51 -12
  22. package/dist/executors/pi-sdk-executor.js +187 -59
  23. package/dist/executors/shell-executor.js +265 -15
  24. package/dist/executors/shell-write-guard.js +7 -0
  25. package/dist/shared/dag-prompt-override.js +28 -0
  26. package/dist/shared/operator/capabilities.js +32 -0
  27. package/dist/shared/playwright-cli-command-policy.js +15 -0
  28. package/dist/shared/update/console-notifier.js +100 -0
  29. package/dist/{cli → shared}/update/npm-client.js +41 -8
  30. package/dist/{cli → shared}/update/state.js +41 -13
  31. package/dist/task/contract/constants.js +1 -0
  32. package/dist/task/contract/diff.js +26 -0
  33. package/dist/task/contract/project.js +5 -0
  34. package/dist/task/contract/schema.js +6 -1
  35. package/dist/task/source-prepare/build-draft.js +15 -0
  36. package/dist/task/source-prepare/parse-intent.js +24 -4
  37. package/dist/worker/cli.js +31 -1
  38. package/dist/worker/console/chat/routes.js +1 -0
  39. package/dist/worker/console/dag-execution-receipt.js +33 -0
  40. package/dist/worker/console/operator-actions.js +116 -0
  41. package/dist/worker/console/operator-user-error.js +2 -2
  42. package/dist/worker/console/pi-readiness.js +4 -4
  43. package/dist/worker/console/routes.js +43 -2
  44. package/dist/worker/console/security.js +63 -9
  45. package/dist/worker/console/server.js +36 -5
  46. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4bmo3iY.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
  47. package/dist/worker/console/static/assets/{arc-SAD7McOS.js → arc-pm3c2Rsl.js} +1 -1
  48. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DAVLUdK1.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
  49. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CPPdv4vj.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
  50. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D1GRGZax.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
  51. package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
  52. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D_Bq2ZqK.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CPLVtamK.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DnaZWcL3.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BRTmFgnq.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BRlfYZoH.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjNybD4u.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3e-74fG.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Br3ir6f2.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-1NgVuRbh.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BTYuvuWN.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
  62. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
  63. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
  64. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BC6Bgosf.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
  65. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DV-HypfL.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DhTaddO-.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
  67. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BnEcOmiI.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
  68. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-xphGRVKp.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CrdD1mRG.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D4YdLsaF.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LCKyGZ5U.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BeiTe7t0.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
  73. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BO2cq8GS.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
  74. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmWww3Nt.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
  75. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C2kkUYg5.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
  76. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DY_JZ2iQ.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
  77. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BizhCSw9.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
  78. package/dist/worker/console/static/assets/index-C8rOIAJr.css +1 -0
  79. package/dist/worker/console/static/assets/{index-BXN89VPX.js → index-DEbyrEfW.js} +147 -68
  80. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Dlo2eK_d.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
  81. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
  82. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DiMDe5Yh.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
  83. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-sN553oIN.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
  84. package/dist/worker/console/static/assets/{linear-D9Py31Ld.js → linear-CAyXjQjN.js} +1 -1
  85. package/dist/worker/console/static/assets/{mermaid.core-D7Occzk_.js → mermaid.core-CcWKn6tS.js} +5 -5
  86. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DjDRboiR.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
  87. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cgc4qlzp.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
  88. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DxZj8pKd.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
  89. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CJ50MngR.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
  90. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BY-OD8Tr.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
  91. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQFButfK.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
  92. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BB_QUK_b.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
  93. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CqhXZjyx.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
  94. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-2l1U4rgJ.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
  95. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BX1FbrhJ.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
  96. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
  97. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DOSnT7hb.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
  98. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
  99. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DVJQA7AN.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
  100. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-74uxl9gx.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
  101. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DsBxlcg_.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
  102. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-snuW9Mjw.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
  103. package/dist/worker/console/static/index.html +2 -2
  104. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  105. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  106. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  107. package/dist/worker/observe/node-input.js +74 -3
  108. package/dist/worker/observe/node-process.js +377 -0
  109. package/dist/worker/observe/routes.js +36 -3
  110. package/dist/worker/observe/shell-handler-keys.js +34 -0
  111. package/dist/worker/observe/static/api.js +66 -0
  112. package/dist/worker/observe/static/constants.js +8 -1
  113. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  114. package/dist/worker/observe/static/format.js +23 -0
  115. package/dist/worker/observe/static/markdown-render.js +20 -1
  116. package/dist/worker/observe/static/operator-chrome.js +4 -0
  117. package/dist/worker/observe/static/state.js +13 -2
  118. package/dist/worker/observe/static/styles.css +73 -0
  119. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  120. package/dist/worker/observe/static/views/dag.js +8 -1
  121. package/dist/worker/observe/static/views/failures.js +5 -2
  122. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  123. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +76 -6
  124. package/dist/workflows/dag/backend-test-scenario-partitions.js +73 -1
  125. package/dist/workflows/dag/contract-output-registry.js +15 -0
  126. package/dist/workflows/dag/failure-category.js +12 -0
  127. package/dist/workflows/dag/frontend-implementation-contract.js +145 -40
  128. package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
  129. package/dist/workflows/dag/frontend-repair.js +14 -0
  130. package/dist/workflows/dag/frontend-risk.js +15 -2
  131. package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
  132. package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
  133. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  134. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  135. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  136. package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
  137. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  138. package/dist/workflows/dag/frontend-verification-trace.js +29 -4
  139. package/dist/workflows/dag/init-hybrid.js +196 -117
  140. package/dist/workflows/dag/node-execution.js +139 -10
  141. package/dist/workflows/dag/prompt.js +15 -1
  142. package/dist/workflows/dag/rerun-plan.js +134 -3
  143. package/dist/workflows/dag/rerun-run.js +58 -6
  144. package/dist/workflows/dag/retry-policy.js +91 -0
  145. package/dist/workflows/dag/structured-output-repair.js +712 -0
  146. package/dist/workflows/dag/types.js +32 -1
  147. package/dist/workflows/dag/validate.js +41 -1
  148. package/docs/init-surface.manifest.json +5 -0
  149. package/docs/operations/README.md +1 -1
  150. package/docs/operations/local-development-environment.md +1 -5
  151. package/docs/skills/vetted-skill-registry.md +14 -0
  152. package/docs/templates/README.md +1 -1
  153. package/docs/templates/agent-dag.schema.json +44 -0
  154. package/docs/templates/backend-test-dag.json +2 -2
  155. package/docs/templates/frontend-test-dag.json +8 -10
  156. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  157. package/docs/templates/init-managed-agents.md +13 -3
  158. package/harness.json +2 -2
  159. package/package.json +12 -4
  160. package/scripts/next-info.mjs +356 -0
  161. package/scripts/next-publish-gate.mjs +238 -0
  162. package/scripts/release-source-binding.mjs +251 -0
  163. package/skills/analyze-product-requirements/SKILL.md +8 -5
  164. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  165. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  166. package/skills/analyze-product-requirements/references/example.md +50 -0
  167. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  168. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  169. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  170. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  171. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  172. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  173. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  174. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  175. package/skills/codebase-scout/SKILL.md +1 -1
  176. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  177. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  178. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  179. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  180. package/skills/improve-codebase-architecture/deepening.md +37 -0
  181. package/skills/improve-codebase-architecture/html-report.md +123 -0
  182. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  183. package/skills/improve-codebase-architecture/language.md +53 -0
  184. package/skills/loop-agent/references/command-reference.md +11 -4
  185. package/skills/playwright-cli/SKILL.md +22 -0
  186. package/skills/playwright-cli-case-generator/SKILL.md +20 -1
  187. package/dist/worker/console/static/assets/channel-R6oQIDkw.js +0 -1
  188. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +0 -1
  189. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +0 -1
  190. package/dist/worker/console/static/assets/index-CuH5CNFu.css +0 -1
  191. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +0 -1
  192. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +0 -8
@@ -11,14 +11,22 @@ 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";
14
+ import {
15
+ fetchJson,
16
+ fetchSpecEvidenceFile,
17
+ fetchDagNodeInput,
18
+ fetchDagNodeProcess,
19
+ postOperatorAction,
20
+ fetchOperatorOperation,
21
+ } from "../api.js";
22
+ import { badge, formatMs, prettyJsonBlock } from "../format.js";
23
+ import { renderMarkdown, renderInspectorDocument } from "../markdown-render.js";
17
24
  import {
18
25
  uiState,
19
26
  makeSessionEventIdentity,
20
27
  makeDagNodeInputIdentity,
21
28
  makeDagNodeExecutionOutputIdentity,
29
+ makeDagNodeProcessIdentity,
22
30
  getDagNodeExecutionOutputCache,
23
31
  setDagNodeExecutionOutputCache,
24
32
  clearDagNodeExecutionOutputCache,
@@ -68,6 +76,10 @@ function resetInspectorNodeSelection(dagRunId, nodeId) {
68
76
  uiState.dagNodeInputData = null;
69
77
  uiState.dagNodeInputError = null;
70
78
  uiState.dagNodeInputLoading = false;
79
+ uiState.dagNodeProcessData = null;
80
+ uiState.dagNodeProcessError = null;
81
+ uiState.dagNodeProcessLoading = false;
82
+ uiState.dagNodeProcessIdentity = null;
71
83
  // Drop execution-output cache/identity so node A's bodies never leak into B.
72
84
  clearDagNodeExecutionOutputCache();
73
85
  // Bump the session-event identity token so any in-flight response for the
@@ -725,6 +737,11 @@ function renderDagNodeInputDetail(content, data) {
725
737
  }
726
738
  section.appendChild(dl);
727
739
  appendCodeList(section, "skills", data.executorInput.skills);
740
+ appendCodeList(
741
+ section,
742
+ "piExtensions(声明)",
743
+ data.executorInput.piExtensions,
744
+ );
728
745
  if (data.executorInput.shell) {
729
746
  const shell = data.executorInput.shell;
730
747
  const shellDl = el("dl", "node-input-summary");
@@ -743,6 +760,12 @@ function renderDagNodeInputDetail(content, data) {
743
760
  (shell.gates ?? []).join(", ") || "(none)",
744
761
  null,
745
762
  );
763
+ appendMetaRow(
764
+ shellDl,
765
+ "handlers",
766
+ (shell.handlers ?? []).join(", ") || "(none)",
767
+ null,
768
+ );
746
769
  section.appendChild(shellDl);
747
770
  if ((shell.commands ?? []).length > 0) {
748
771
  const list = el("ol", "node-input-command-list");
@@ -799,6 +822,52 @@ function renderDagNodeInputDetail(content, data) {
799
822
  wrap.appendChild(section);
800
823
  }
801
824
 
825
+ if (data.piExtensionRunFacts) {
826
+ const facts = data.piExtensionRunFacts;
827
+ const section = el("section", "node-input-section");
828
+ section.appendChild(el("h4", null, "Pi 扩展运行事实"));
829
+ if (!facts.exists) {
830
+ section.appendChild(
831
+ el(
832
+ "p",
833
+ "muted",
834
+ facts.parseError
835
+ ? `产物 ${facts.artifact} 读取失败(${facts.parseError})。`
836
+ : `产物 ${facts.artifact} 尚未生成(节点未执行或缺失)。`,
837
+ ),
838
+ );
839
+ } else {
840
+ const dl = el("dl", "node-input-summary");
841
+ appendMetaRow(
842
+ dl,
843
+ "resolved",
844
+ facts.resolved
845
+ .map((entry) => `${entry.id} → ${entry.packageName}@${entry.version}`)
846
+ .join(", ") || "(none)",
847
+ null,
848
+ );
849
+ section.appendChild(dl);
850
+ appendCodeList(
851
+ section,
852
+ "requested",
853
+ facts.requested,
854
+ );
855
+ for (const missingEntry of facts.missing ?? []) {
856
+ section.appendChild(
857
+ el(
858
+ "p",
859
+ "node-input-warning",
860
+ `降级:${missingEntry.id} 未加载(${missingEntry.reason || "unknown"})`,
861
+ ),
862
+ );
863
+ }
864
+ if ((facts.missing ?? []).length === 0) {
865
+ section.appendChild(el("p", "muted", "全部声明扩展均已解析加载。"));
866
+ }
867
+ }
868
+ wrap.appendChild(section);
869
+ }
870
+
802
871
  if (data.promptFingerprint?.exists) {
803
872
  const section = el("section", "node-input-section");
804
873
  section.appendChild(el("h4", null, "Assembled prompt 指纹"));
@@ -916,8 +985,304 @@ async function renderDagNodeInput(content, dagRunId, node) {
916
985
  finishInputRender();
917
986
  }
918
987
 
988
+ /**
989
+ * Experimental prompt-override rerun tab. Only reachable when the server
990
+ * injected the mutation token (switch on). Every node shows its frozen
991
+ * subtask_prompt here; only FAILED (ERROR) nodes additionally get the editor +
992
+ * restart flow: preflight (dagRerunPlan) binds the edit into planHash, dagRerun
993
+ * executes it as a Console operation, and on success we jump to the new run.
994
+ */
995
+ async function renderPromptRestart(content, dagRunId, node) {
996
+ const nodeId = node.nodeId;
997
+ clearNode(content);
998
+
999
+ const loading = el("p", "muted", "正在加载冻结提示词…");
1000
+ loading.setAttribute("role", "status");
1001
+ loading.setAttribute("aria-live", "polite");
1002
+ content.appendChild(loading);
1003
+ const input = await fetchDagNodeInput(dagRunId, nodeId);
1004
+ if (
1005
+ !content.isConnected ||
1006
+ content.dataset.dagNodeId !== String(nodeId) ||
1007
+ content.dataset.dagRunId !== String(dagRunId) ||
1008
+ activeInspectorTab() !== "prompt-restart"
1009
+ ) {
1010
+ return;
1011
+ }
1012
+ clearNode(content);
1013
+
1014
+ if (!input.ok || !input.body?.available) {
1015
+ const error = el("p", "node-input-error", "无法读取该节点的冻结提示词。");
1016
+ error.setAttribute("role", "alert");
1017
+ content.appendChild(error);
1018
+ return;
1019
+ }
1020
+ const basePrompt =
1021
+ typeof input.body.prompt?.text === "string" ? input.body.prompt.text : "";
1022
+ if (!basePrompt) {
1023
+ const error = el("p", "node-input-error", "该节点没有冻结提示词。");
1024
+ error.setAttribute("role", "alert");
1025
+ content.appendChild(error);
1026
+ return;
1027
+ }
1028
+
1029
+ if (node.status !== "ERROR") {
1030
+ // Non-failed nodes still show their frozen prompt — read-only.
1031
+ const hint = el(
1032
+ "p",
1033
+ "muted",
1034
+ `当前节点状态为 ${node.status ?? "unknown"};仅失败(ERROR)节点支持编辑提示词并重启,以下为该节点冻结提示词(只读)。`,
1035
+ );
1036
+ hint.setAttribute("role", "status");
1037
+ content.appendChild(hint);
1038
+ const pre = document.createElement("pre");
1039
+ pre.className = "prompt-restart-readonly";
1040
+ pre.setAttribute("data-testid", "prompt-restart-readonly");
1041
+ pre.textContent = basePrompt;
1042
+ content.appendChild(pre);
1043
+ return;
1044
+ }
1045
+
1046
+ if (input.body.prompt?.truncated === true) {
1047
+ const error = el(
1048
+ "p",
1049
+ "node-input-error",
1050
+ "冻结提示词被截断,为避免误替换请改用 CLI(dag rerun --prompt-override)。",
1051
+ );
1052
+ error.setAttribute("role", "alert");
1053
+ content.appendChild(error);
1054
+ return;
1055
+ }
1056
+
1057
+ const box = el("div", "prompt-restart-box");
1058
+ box.appendChild(
1059
+ el(
1060
+ "p",
1061
+ "muted",
1062
+ "编辑该失败节点的提示词并从它重启:父 run spec 保持冻结,将创建带修改提示词的续跑 run(实验能力,仅 ERROR 节点)。",
1063
+ ),
1064
+ );
1065
+
1066
+ const modeRow = el("div", "prompt-restart-mode");
1067
+ modeRow.appendChild(el("span", "muted", "编辑模式"));
1068
+ const modeSelect = document.createElement("select");
1069
+ modeSelect.className = "prompt-restart-mode-select";
1070
+ modeSelect.setAttribute("data-testid", "prompt-restart-mode");
1071
+ const appendOption = document.createElement("option");
1072
+ appendOption.value = "append";
1073
+ appendOption.textContent =
1074
+ "追加指令(推荐):保留原提示词,在末尾追加 operator_addendum 块";
1075
+ const replaceOption = document.createElement("option");
1076
+ replaceOption.value = "replace";
1077
+ replaceOption.textContent = "全文替换(高级):替换整个提示词";
1078
+ modeSelect.appendChild(appendOption);
1079
+ modeSelect.appendChild(replaceOption);
1080
+ modeSelect.value = "append";
1081
+ modeRow.appendChild(modeSelect);
1082
+ box.appendChild(modeRow);
1083
+
1084
+ const baseLabel = el("label", "prompt-restart-field");
1085
+ baseLabel.appendChild(
1086
+ el("span", "muted", `冻结提示词(只读底稿 · ${nodeId})`),
1087
+ );
1088
+ const baseBlock = document.createElement("pre");
1089
+ baseBlock.className = "prompt-restart-readonly";
1090
+ baseBlock.setAttribute("data-testid", "prompt-restart-base");
1091
+ baseBlock.textContent = basePrompt;
1092
+ baseLabel.appendChild(baseBlock);
1093
+ box.appendChild(baseLabel);
1094
+
1095
+ const addendumLabel = el("label", "prompt-restart-field");
1096
+ addendumLabel.appendChild(
1097
+ el(
1098
+ "span",
1099
+ "muted",
1100
+ "追加指令(将包裹为 <operator_addendum> 块附加到原提示词末尾)",
1101
+ ),
1102
+ );
1103
+ const addendumArea = document.createElement("textarea");
1104
+ addendumArea.className = "prompt-restart-textarea";
1105
+ addendumArea.rows = 6;
1106
+ addendumArea.placeholder = "例如:重点检查 X 边界;输出前先自检 Y 约束…";
1107
+ addendumArea.setAttribute("data-testid", "prompt-restart-addendum");
1108
+ addendumLabel.appendChild(addendumArea);
1109
+ box.appendChild(addendumLabel);
1110
+
1111
+ const replaceLabel = el("label", "prompt-restart-field");
1112
+ replaceLabel.appendChild(el("span", "muted", `完整提示词(${nodeId})`));
1113
+ const replaceArea = document.createElement("textarea");
1114
+ replaceArea.className = "prompt-restart-textarea";
1115
+ replaceArea.rows = 16;
1116
+ replaceArea.value = basePrompt;
1117
+ replaceArea.setAttribute("data-testid", "prompt-restart-textarea");
1118
+ replaceLabel.appendChild(replaceArea);
1119
+ box.appendChild(replaceLabel);
1120
+
1121
+ const applyModeVisibility = () => {
1122
+ const append = modeSelect.value === "append";
1123
+ baseLabel.style.display = append ? "" : "none";
1124
+ addendumLabel.style.display = append ? "" : "none";
1125
+ replaceLabel.style.display = append ? "none" : "";
1126
+ };
1127
+ modeSelect.addEventListener("change", applyModeVisibility);
1128
+ applyModeVisibility();
1129
+
1130
+ const reasonLabel = el("label", "prompt-restart-field");
1131
+ reasonLabel.appendChild(el("span", "muted", "重启原因(结构化说明)"));
1132
+ const reasonArea = document.createElement("textarea");
1133
+ reasonArea.className = "prompt-restart-textarea";
1134
+ reasonArea.rows = 3;
1135
+ reasonArea.value = `operator-prompt-override / run=${dagRunId} / node=${nodeId} / 手工编辑失败节点提示词后重启`;
1136
+ reasonArea.setAttribute("data-testid", "prompt-restart-reason");
1137
+ reasonLabel.appendChild(reasonArea);
1138
+ box.appendChild(reasonLabel);
1139
+
1140
+ const row = el("div", "prompt-restart-actions");
1141
+ const submit = document.createElement("button");
1142
+ submit.type = "button";
1143
+ submit.className = "prompt-restart-submit";
1144
+ submit.textContent = "资格预检并重启";
1145
+ submit.setAttribute("data-testid", "prompt-restart-submit");
1146
+ row.appendChild(submit);
1147
+ box.appendChild(row);
1148
+
1149
+ const status = el("p", "prompt-restart-status", "");
1150
+ status.setAttribute("role", "status");
1151
+ status.setAttribute("aria-live", "polite");
1152
+ box.appendChild(status);
1153
+
1154
+ content.appendChild(box);
1155
+
1156
+ const setStatus = (text, isError = false) => {
1157
+ status.textContent = text ?? "";
1158
+ status.classList.toggle("prompt-restart-error", isError);
1159
+ };
1160
+
1161
+ submit.addEventListener("click", () => {
1162
+ void (async () => {
1163
+ const mode = modeSelect.value === "replace" ? "replace" : "append";
1164
+ const prompt =
1165
+ mode === "append" ? addendumArea.value : replaceArea.value;
1166
+ const reason = reasonArea.value.trim();
1167
+ if (!prompt.trim()) {
1168
+ setStatus(
1169
+ mode === "append" ? "追加指令不能为空。" : "提示词不能为空。",
1170
+ true,
1171
+ );
1172
+ return;
1173
+ }
1174
+ if (mode === "replace" && prompt === basePrompt) {
1175
+ setStatus("提示词未修改;如需按原提示词重跑请使用恢复工作台。", true);
1176
+ return;
1177
+ }
1178
+ if (!reason) {
1179
+ setStatus("需要填写重启原因。", true);
1180
+ return;
1181
+ }
1182
+ submit.disabled = true;
1183
+ try {
1184
+ setStatus("正在做重跑资格预检(dagRerunPlan)…");
1185
+ const overrides = [{ nodeId, prompt, mode }];
1186
+ const planRes = await postOperatorAction("dagRerunPlan", {
1187
+ runId: dagRunId,
1188
+ fromNode: nodeId,
1189
+ promptOverrides: overrides,
1190
+ });
1191
+ const plan = planRes.body?.result ?? null;
1192
+ if (!planRes.ok || plan?.eligible !== true || !plan?.planHash) {
1193
+ setStatus(
1194
+ planRes.body?.error?.message ??
1195
+ plan?.blockedReasons?.join(", ") ??
1196
+ "资格预检未通过。",
1197
+ true,
1198
+ );
1199
+ return;
1200
+ }
1201
+
1202
+ const est = plan.estimatedExecution;
1203
+ const estText = est
1204
+ ? `(将执行 ${est.nodeCount ?? "?"} 个节点 / ${est.piCallCount ?? "?"} 次 Pi 调用)`
1205
+ : "";
1206
+ setStatus(`预检通过${estText},正在提交续跑(dagRerun)…`);
1207
+ const clientRequestId = `inspect-prompt-restart-${Date.now()}-${Math.random()
1208
+ .toString(36)
1209
+ .slice(2, 10)}`;
1210
+ const runRes = await postOperatorAction(
1211
+ "dagRerun",
1212
+ {
1213
+ runId: dagRunId,
1214
+ fromNode: nodeId,
1215
+ planHash: plan.planHash,
1216
+ reason,
1217
+ promptOverrides: overrides,
1218
+ },
1219
+ clientRequestId,
1220
+ );
1221
+ const operationId = runRes.body?.operationId;
1222
+ if (!runRes.ok || !operationId) {
1223
+ setStatus(runRes.body?.error?.message ?? "提交续跑失败。", true);
1224
+ return;
1225
+ }
1226
+
1227
+ const op = await pollOperationToTerminal(operationId, (text) =>
1228
+ setStatus(text),
1229
+ );
1230
+ if (op?.state === "succeeded") {
1231
+ const newRunId =
1232
+ op.dagRunId ?? op.result?.newRunId ?? op.runSummary?.runId ?? null;
1233
+ if (newRunId) {
1234
+ setStatus(`重启完成,新 run:${newRunId}`);
1235
+ const open = document.createElement("button");
1236
+ open.type = "button";
1237
+ open.className = "prompt-restart-submit";
1238
+ open.textContent = "打开新 run";
1239
+ open.addEventListener("click", () => {
1240
+ window.location.hash = `#/dag/${encodeURIComponent(String(newRunId))}`;
1241
+ });
1242
+ row.appendChild(open);
1243
+ } else {
1244
+ setStatus("重启完成(未能解析新 run id,请在 DAG 历史中查看)。");
1245
+ }
1246
+ } else if (op) {
1247
+ setStatus(
1248
+ `续跑操作终态:${op.state}${op.errorMessage ? ` — ${op.errorMessage}` : ""}`,
1249
+ true,
1250
+ );
1251
+ } else {
1252
+ setStatus("等待超时:操作仍在运行,可在操作台查看进度。", true);
1253
+ }
1254
+ } catch (err) {
1255
+ setStatus(err instanceof Error ? err.message : String(err), true);
1256
+ } finally {
1257
+ submit.disabled = false;
1258
+ }
1259
+ })();
1260
+ });
1261
+ }
1262
+
1263
+ /** Poll a Console operation until a terminal state; null on poll budget end. */
1264
+ async function pollOperationToTerminal(operationId, onProgress) {
1265
+ const terminal = new Set([
1266
+ "succeeded",
1267
+ "failed",
1268
+ "timed-out",
1269
+ "needs-reconcile",
1270
+ ]);
1271
+ for (let attempt = 0; attempt < 1200; attempt += 1) {
1272
+ const res = await fetchOperatorOperation(operationId);
1273
+ const op = res.body?.operation ?? res.body;
1274
+ const state = op?.state ?? "";
1275
+ if (res.ok && terminal.has(state)) {
1276
+ return op;
1277
+ }
1278
+ onProgress(`续跑执行中…(${state || "queued"})`);
1279
+ await new Promise((resolve) => setTimeout(resolve, 3000));
1280
+ }
1281
+ return null;
1282
+ }
1283
+
919
1284
  function activeInspectorTab() {
920
- return ["input", "timeline", "spec-evidence"].includes(
1285
+ return ["input", "timeline", "spec-evidence", "prompt-restart"].includes(
921
1286
  uiState.dagInspectorTab,
922
1287
  )
923
1288
  ? uiState.dagInspectorTab
@@ -1254,7 +1619,7 @@ function appendOutputBody(content, outputText, errorText, options = null) {
1254
1619
  content.appendChild(el("p", "empty", UI_TEXT.noOutput));
1255
1620
  return;
1256
1621
  }
1257
- if (outputText) content.appendChild(renderMarkdown(outputText));
1622
+ if (outputText) content.appendChild(renderInspectorDocument(outputText));
1258
1623
  // Truncation note only for full/cached execution-output bodies (AC-003);
1259
1624
  // preview path never invents truncated/maxBytes.
1260
1625
  if (options?.truncated === true) {
@@ -1270,7 +1635,7 @@ function appendOutputBody(content, outputText, errorText, options = null) {
1270
1635
  if (errorText) {
1271
1636
  const error = el("section", "markdown-error");
1272
1637
  error.appendChild(el("p", "markdown-error-title", "错误输出"));
1273
- error.appendChild(renderMarkdown(errorText));
1638
+ error.appendChild(renderInspectorDocument(errorText));
1274
1639
  content.appendChild(error);
1275
1640
  }
1276
1641
  }
@@ -1404,6 +1769,228 @@ function repaintOutputTabBody(content, dagRunId, node) {
1404
1769
  restoreInspectorContentScroll(content, dagRunId, node.nodeId, "output");
1405
1770
  }
1406
1771
 
1772
+ function processEmptyMessage(body) {
1773
+ const reason = body?.emptyReason;
1774
+ if (reason === "static-no-process") return UI_TEXT.staticNoProcess;
1775
+ if (reason === "shell-receipt-missing") return UI_TEXT.noShellProcess;
1776
+ if (reason === "shell-preset-pending") return UI_TEXT.noPresetProcess;
1777
+ if (reason === "pi-session") return UI_TEXT.noSessionEvents;
1778
+ return UI_TEXT.noSessionEvents;
1779
+ }
1780
+
1781
+ function formatProcessBlock(text) {
1782
+ if (!text) return "";
1783
+ return prettyJsonBlock(text) ?? text;
1784
+ }
1785
+
1786
+ function appendProcessPre(parent, text) {
1787
+ if (!text) return;
1788
+ const pre = el("pre", "process-timeline-code");
1789
+ pre.textContent = formatProcessBlock(text);
1790
+ parent.appendChild(pre);
1791
+ }
1792
+
1793
+ function renderProcessCommandCard(command) {
1794
+ const failed = command.ok === false;
1795
+ const card = el(
1796
+ "div",
1797
+ `process-timeline-card process-timeline-tool-card${failed ? " is-error" : ""}`,
1798
+ );
1799
+ const details = document.createElement("details");
1800
+ details.className = "process-timeline-details";
1801
+ details.open = failed;
1802
+ const summary = el("summary", "process-timeline-summary");
1803
+ summary.appendChild(
1804
+ el("span", "process-timeline-summary-title", command.command || "command"),
1805
+ );
1806
+ summary.appendChild(
1807
+ el(
1808
+ "span",
1809
+ `process-timeline-summary-status status-${failed ? "failed" : "succeeded"}`,
1810
+ failed ? "失败" : command.reused ? "复用" : "成功",
1811
+ ),
1812
+ );
1813
+ details.appendChild(summary);
1814
+ const body = el("div", "process-timeline-card-body");
1815
+ const meta = [
1816
+ command.exitCode != null ? `exit ${command.exitCode}` : null,
1817
+ command.durationMs != null ? `耗时 ${formatMs(command.durationMs)}` : null,
1818
+ command.failureCategory ? command.failureCategory : null,
1819
+ ].filter(Boolean);
1820
+ if (meta.length > 0) {
1821
+ body.appendChild(el("div", "process-timeline-section-label", meta.join(" · ")));
1822
+ }
1823
+ if (command.stdout) {
1824
+ body.appendChild(el("div", "process-timeline-section-label", "stdout"));
1825
+ appendProcessPre(body, command.stdout);
1826
+ }
1827
+ if (command.stderr) {
1828
+ body.appendChild(el("div", "process-timeline-section-label", "stderr"));
1829
+ appendProcessPre(body, command.stderr);
1830
+ }
1831
+ if (command.truncated) {
1832
+ body.appendChild(el("p", "muted", "内容过长,已截断。"));
1833
+ }
1834
+ details.appendChild(body);
1835
+ card.appendChild(details);
1836
+ return card;
1837
+ }
1838
+
1839
+ function renderProcessStepCard(step) {
1840
+ const failed = step.ok === false;
1841
+ const card = el(
1842
+ "div",
1843
+ `process-timeline-card process-timeline-tool-card${failed ? " is-error" : ""}`,
1844
+ );
1845
+ const details = document.createElement("details");
1846
+ details.className = "process-timeline-details";
1847
+ details.open = true;
1848
+ const summary = el("summary", "process-timeline-summary");
1849
+ summary.appendChild(
1850
+ el("span", "process-timeline-summary-title", step.label || step.id || "step"),
1851
+ );
1852
+ summary.appendChild(
1853
+ el(
1854
+ "span",
1855
+ `process-timeline-summary-status status-${failed ? "failed" : "succeeded"}`,
1856
+ failed ? "失败" : "成功",
1857
+ ),
1858
+ );
1859
+ details.appendChild(summary);
1860
+ const body = el("div", "process-timeline-card-body");
1861
+ if (step.durationMs != null) {
1862
+ body.appendChild(
1863
+ el("div", "process-timeline-section-label", `耗时 ${formatMs(step.durationMs)}`),
1864
+ );
1865
+ }
1866
+ if (step.detail) appendProcessPre(body, step.detail);
1867
+ if (step.truncated) {
1868
+ body.appendChild(el("p", "muted", "内容过长,已截断。"));
1869
+ }
1870
+ details.appendChild(body);
1871
+ card.appendChild(details);
1872
+ return card;
1873
+ }
1874
+
1875
+ function renderNonPiProcess(content, body) {
1876
+ content.classList.add("process-timeline-scroll");
1877
+ if (!body || body.emptyReason || ((body.commands ?? []).length === 0 && (body.steps ?? []).length === 0)) {
1878
+ content.appendChild(el("p", "empty", processEmptyMessage(body)));
1879
+ return;
1880
+ }
1881
+ const heading =
1882
+ body.kind === "shell-preset"
1883
+ ? UI_TEXT.shellPresetHeading
1884
+ : UI_TEXT.shellCommandsHeading;
1885
+ if (body.handler) {
1886
+ content.appendChild(
1887
+ el("p", "process-timeline-note", `${heading} · ${body.handler}`),
1888
+ );
1889
+ }
1890
+ const ul = el("ul", "process-timeline process-timeline-rich");
1891
+ const items = (body.commands ?? []).length > 0 ? body.commands : body.steps ?? [];
1892
+ for (let index = 0; index < items.length; index += 1) {
1893
+ const item = items[index];
1894
+ const failed = item.ok === false;
1895
+ const li = el(
1896
+ "li",
1897
+ `process-timeline-item process-timeline-tool-end${failed ? " is-error" : ""}${index === 0 ? " is-first" : ""}${index === items.length - 1 ? " is-last" : ""}`,
1898
+ );
1899
+ const rail = el("span", "process-timeline-rail");
1900
+ rail.appendChild(el("span", "process-timeline-dot"));
1901
+ li.appendChild(rail);
1902
+ const cardBody = el("div", "process-timeline-body");
1903
+ cardBody.appendChild(
1904
+ body.kind === "shell-preset"
1905
+ ? renderProcessStepCard(item)
1906
+ : renderProcessCommandCard(item),
1907
+ );
1908
+ li.appendChild(cardBody);
1909
+ ul.appendChild(li);
1910
+ }
1911
+ content.appendChild(ul);
1912
+ }
1913
+
1914
+ export async function mergeNodeProcess(dagRunId, node) {
1915
+ if (!dagRunId || !node?.nodeId) return;
1916
+ const nodeId = node.nodeId;
1917
+ const key = effectiveExecutionKey(dagRunId, node);
1918
+ const identity = makeDagNodeProcessIdentity(dagRunId, nodeId, key);
1919
+ uiState.dagNodeProcessIdentity = identity;
1920
+ uiState.dagNodeProcessLoading = true;
1921
+ const result = await fetchDagNodeProcess(dagRunId, nodeId, key);
1922
+ if (uiState.dagNodeProcessIdentity !== identity) return;
1923
+ if (!result.ok || !result.body) {
1924
+ uiState.dagNodeProcessError =
1925
+ result.body?.error || UI_TEXT.processError;
1926
+ uiState.dagNodeProcessData = {
1927
+ schemaVersion: 1,
1928
+ dagRunId,
1929
+ nodeId,
1930
+ kind: "none",
1931
+ executor: node.executor ?? null,
1932
+ handler: null,
1933
+ emptyReason: "not-executed",
1934
+ key: key ?? null,
1935
+ commands: [],
1936
+ steps: [],
1937
+ };
1938
+ uiState.dagNodeProcessLoading = false;
1939
+ return;
1940
+ }
1941
+ uiState.dagNodeProcessError = null;
1942
+ uiState.dagNodeProcessData = result.body;
1943
+ uiState.dagNodeProcessLoading = false;
1944
+ }
1945
+
1946
+ function renderExecutorAwareTimeline(content, dagRunId, node) {
1947
+ content.dataset.dagRunId = String(dagRunId ?? "");
1948
+ content.dataset.nodeId = String(node.nodeId ?? "");
1949
+ const key = effectiveExecutionKey(dagRunId, node);
1950
+ const cached = uiState.dagNodeProcessData;
1951
+ const sameNode =
1952
+ cached &&
1953
+ cached.dagRunId === dagRunId &&
1954
+ cached.nodeId === node.nodeId &&
1955
+ (cached.key ?? null) === (key ?? null);
1956
+ if (sameNode && uiState.dagNodeProcessError) {
1957
+ content.appendChild(el("p", "empty", uiState.dagNodeProcessError));
1958
+ return;
1959
+ }
1960
+ if (sameNode && cached.kind === "pi-session") {
1961
+ renderSessionTimeline(content, node.nodeId, {
1962
+ executionKey: effectiveExecutionKey(dagRunId, node),
1963
+ });
1964
+ return;
1965
+ }
1966
+ if (sameNode && cached.kind !== "pi-session") {
1967
+ renderNonPiProcess(content, cached);
1968
+ return;
1969
+ }
1970
+ if ((node.executor ?? "pi") === "pi") {
1971
+ renderSessionTimeline(content, node.nodeId, {
1972
+ executionKey: effectiveExecutionKey(dagRunId, node),
1973
+ });
1974
+ return;
1975
+ }
1976
+ if (uiState.dagNodeProcessError && sameNode) {
1977
+ content.appendChild(el("p", "empty", uiState.dagNodeProcessError));
1978
+ return;
1979
+ }
1980
+ const loading = el("p", "muted", UI_TEXT.processLoading);
1981
+ loading.setAttribute("role", "status");
1982
+ content.appendChild(loading);
1983
+ void mergeNodeProcess(dagRunId, node).then(() => {
1984
+ if (
1985
+ uiState.dagInspectorTab === "timeline" &&
1986
+ uiState.selectedDagNodeId === node.nodeId &&
1987
+ uiState.currentDagRunId === dagRunId
1988
+ ) {
1989
+ renderDagInspectorForCurrent();
1990
+ }
1991
+ });
1992
+ }
1993
+
1407
1994
  function fillDagInspectorContent(content, dagRunId, node) {
1408
1995
  const previousRenderedNode = content.dataset.dagNodeId ?? "";
1409
1996
  const previousRenderedRun = content.dataset.dagRunId ?? "";
@@ -1450,10 +2037,12 @@ function fillDagInspectorContent(content, dagRunId, node) {
1450
2037
  );
1451
2038
  if (activeTab === "spec-evidence") {
1452
2039
  void renderSpecEvidence(content, dagRunId, node.nodeId);
2040
+ } else if (activeTab === "prompt-restart") {
2041
+ void renderPromptRestart(content, dagRunId, node);
1453
2042
  } else if (activeTab === "timeline") {
1454
2043
  const attemptBuilt = buildAttemptBar(dagRunId, node);
1455
2044
  if (attemptBuilt?.bar) content.appendChild(attemptBuilt.bar);
1456
- renderSessionTimeline(content, node.nodeId);
2045
+ renderExecutorAwareTimeline(content, dagRunId, node);
1457
2046
  } else if (activeTab === "input") {
1458
2047
  void renderDagNodeInput(content, dagRunId, node);
1459
2048
  } else {
@@ -1517,6 +2106,14 @@ function buildDagInspectorHeader(dagRunId, node) {
1517
2106
  ["timeline", "执行过程", "ri-route-line"],
1518
2107
  ["spec-evidence", "规范证据", "ri-book-open-line"],
1519
2108
  ];
2109
+ // Experimental prompt-override rerun: the server injects the SCOPED
2110
+ // inspect mutation token into /inspect HTML only while
2111
+ // HARNESS_DAG_RERUN_PROMPT_OVERRIDE is enabled (and it only authorizes
2112
+ // dagRerunPlan/dagRerun). No token → no tab → the inspector stays
2113
+ // strictly read-only.
2114
+ if (globalThis.__INSPECT_MUTATION_TOKEN__) {
2115
+ tabDefs.push(["prompt-restart", "提示词重启", "ri-restart-line"]);
2116
+ }
1520
2117
  const tabs = el("div", "dag-inspector-tabs");
1521
2118
  tabs.setAttribute("role", "tablist");
1522
2119
  tabs.setAttribute("aria-label", "节点检查器面板");
@@ -1606,7 +2203,14 @@ function dagInspectorSignature(dagRunId, node) {
1606
2203
  uiState.dagNodeExecutionOutputLoading ? "loading" : "idle",
1607
2204
  ]
1608
2205
  : tab === "timeline"
1609
- ? [uiState.sessionEventOffset]
2206
+ ? [
2207
+ uiState.sessionEventOffset,
2208
+ uiState.dagNodeProcessData?.kind ?? "",
2209
+ uiState.dagNodeProcessData?.emptyReason ?? "",
2210
+ (uiState.dagNodeProcessData?.commands ?? []).length,
2211
+ (uiState.dagNodeProcessData?.steps ?? []).length,
2212
+ uiState.dagNodeProcessLoading ? "loading" : "idle",
2213
+ ]
1610
2214
  : [node.status ?? ""];
1611
2215
  // Attempt dimension: selection identity + history shape. Poll refreshes keep
1612
2216
  // the DOM while selection/history are unchanged; switching attempt or a