@tea-agent/loop-agent 0.41.1-beta.0 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/CHANGELOG.md +47 -267
  2. package/dist/application/dag/generate-task-dag.js +19 -75
  3. package/dist/application/task-lifecycle/advance.js +5 -24
  4. package/dist/application/task-lifecycle/observe.js +17 -171
  5. package/dist/application/task-lifecycle/plan-transitions.js +7 -42
  6. package/dist/application/task-lifecycle/recommendations.js +2 -13
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +0 -7
  9. package/dist/cli/program.js +0 -5
  10. package/dist/commands/client-recovery.js +0 -3
  11. package/dist/commands/init.js +1 -27
  12. package/dist/commands/task-advance.js +0 -19
  13. package/dist/executors/dag-pi-executor.js +231 -3784
  14. package/dist/executors/pi-executor.js +4 -15
  15. package/dist/executors/shell-executor.js +217 -945
  16. package/dist/executors/shell-write-guard.js +0 -7
  17. package/dist/shared/dag-failure-category.js +0 -12
  18. package/dist/shared/openspec-spec.js +4 -70
  19. package/dist/shared/operator/capabilities.js +0 -7
  20. package/dist/task/config-types.js +7 -34
  21. package/dist/task/contract/adopt.js +0 -4
  22. package/dist/task/contract/import-revision.js +0 -4
  23. package/dist/task/contract/project.js +0 -3
  24. package/dist/task/contract/schema.js +1 -2
  25. package/dist/task/frontend-project-capability.js +20 -203
  26. package/dist/task/runtime.js +2 -5
  27. package/dist/task/source-prepare/build-draft.js +3 -3
  28. package/dist/task/source-prepare/fragment-inventory.js +9 -15
  29. package/dist/task/source-prepare/prepare.js +1 -89
  30. package/dist/task/source-prepare/semantic-intake.js +2 -6
  31. package/dist/task/source-references.js +1 -22
  32. package/dist/worker/console/chat/chat-ui-policy.js +4 -2
  33. package/dist/worker/console/chat/pi-runtime.js +176 -9
  34. package/dist/worker/console/chat/repo-browser.js +10 -3
  35. package/dist/worker/console/chat/routes.js +42 -22
  36. package/dist/worker/console/chat/semantic-activity.js +12 -11
  37. package/dist/worker/console/chat/session-stats.js +96 -0
  38. package/dist/worker/console/chat/session-store.js +117 -70
  39. package/dist/worker/console/chat/shortcuts.js +1 -1
  40. package/dist/worker/console/chat/turn-process.js +139 -60
  41. package/dist/worker/console/index.js +0 -3
  42. package/dist/worker/console/operation-store.js +6 -226
  43. package/dist/worker/console/operator-actions.js +7 -169
  44. package/dist/worker/console/pi-plugins.js +3 -1
  45. package/dist/worker/console/server.js +0 -3
  46. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-D7oU983t.js} +1 -1
  47. package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-BrKSRdFS.js} +1 -1
  48. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-CiXV7L56.js} +1 -1
  49. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-D_Hs_gIG.js} +1 -1
  50. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-DbM-Lqqh.js} +1 -1
  51. package/dist/worker/console/static/assets/channel-D1xajGpe.js +1 -0
  52. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-D1M8uqJw.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-4D3RFHT6.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CdwpY_n9.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-BLeLZ9PG.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-CeRNEMvc.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-CjkHP8Vy.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-B54ZUQY-.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-4bKaMVmx.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-Cyjfznu_.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CDN-zGFV.js} +1 -1
  62. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BmuGTfRr.js +1 -0
  63. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BmuGTfRr.js +1 -0
  64. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-HxJmz411.js} +1 -1
  65. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-DtSt5K2w.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-VtkSpRmG.js} +1 -1
  67. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-Bj4DT4du.js} +1 -1
  68. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-Bpuk6BmP.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-BHYGv_yO.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-DEx3WwnT.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-Cj0dS5e8.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-B97h8l21.js} +1 -1
  73. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-DMSFAhBd.js} +1 -1
  74. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-DhVOCbt6.js} +1 -1
  75. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-GlakITWL.js} +1 -1
  76. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-VxPpu9eQ.js} +1 -1
  77. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bs_e5DRT.js} +1 -1
  78. package/dist/worker/console/static/assets/index-BLzcAykx.js +451 -0
  79. package/dist/worker/console/static/assets/index-Cs9JUSPs.css +1 -0
  80. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-BXYuU4Uw.js} +1 -1
  81. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-C34erCbU.js} +1 -1
  82. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-DZCAiOU3.js} +1 -1
  83. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-C9gaTdUm.js} +1 -1
  84. package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CvI0Z17U.js} +1 -1
  85. package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-DMMSIx7j.js} +5 -5
  86. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-BKKP0EO6.js} +1 -1
  87. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-C3LiFbl5.js} +1 -1
  88. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-Bb3Om2Dz.js} +1 -1
  89. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-DNthMjzc.js} +1 -1
  90. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-miFsr4Tv.js} +1 -1
  91. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-yXUncmn6.js} +1 -1
  92. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-DLeWKLak.js} +1 -1
  93. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-CI3he1R3.js} +1 -1
  94. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-B7jfDkS5.js} +1 -1
  95. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-BmrZod7J.js} +1 -1
  96. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-D9ykKEWP.js +1 -0
  97. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-CDOlHVSY.js} +2 -2
  98. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BvPOXALT.js +8 -0
  99. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-Cz-XX1gy.js} +1 -1
  100. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-caV4cI8C.js} +1 -1
  101. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-dImPlnIO.js} +1 -1
  102. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CtRYVJcn.js} +1 -1
  103. package/dist/worker/console/static/index.html +2 -2
  104. package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
  105. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
  106. package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
  107. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
  108. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
  109. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
  110. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  111. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
  112. package/dist/worker/console/workspace-context.js +1 -111
  113. package/dist/worker/observability/read-model.js +4 -0
  114. package/dist/worker/observe/static/dag-node-purpose.js +0 -5
  115. package/dist/worker/observe/static/views/dag-inspector.js +2 -7
  116. package/dist/worker/observe/static/views/session-timeline.js +46 -15
  117. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  118. package/dist/workflows/dag/contract-validator-registrations.js +2 -1
  119. package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
  120. package/dist/workflows/dag/frontend-plan-render.js +1 -2
  121. package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
  122. package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
  123. package/dist/workflows/dag/frontend-recovery-run.js +20 -33
  124. package/dist/workflows/dag/frontend-repair.js +432 -1
  125. package/dist/workflows/dag/frontend-review-context.js +15 -261
  126. package/dist/workflows/dag/frontend-verification-trace.js +24 -249
  127. package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
  128. package/dist/workflows/dag/init-hybrid.js +494 -685
  129. package/dist/workflows/dag/interrupt-request.js +0 -7
  130. package/dist/workflows/dag/node-execution.js +4 -732
  131. package/dist/workflows/dag/prompt.js +1 -34
  132. package/dist/workflows/dag/report.js +1 -37
  133. package/dist/workflows/dag/rerun-feedback.js +1 -256
  134. package/dist/workflows/dag/rerun-plan.js +2 -10
  135. package/dist/workflows/dag/rerun-run.js +0 -23
  136. package/dist/workflows/dag/rerun-task.js +0 -29
  137. package/dist/workflows/dag/retry-policy.js +104 -214
  138. package/dist/workflows/dag/runner.js +122 -429
  139. package/dist/workflows/dag/scheduler.js +16 -114
  140. package/dist/workflows/dag/types.js +12 -239
  141. package/dist/workflows/dag/validate.js +12 -28
  142. package/docs/init-surface.manifest.json +12 -30
  143. package/docs/skills/vetted-skill-registry.md +2 -4
  144. package/docs/templates/README.md +0 -2
  145. package/docs/templates/agent-dag-report.schema.json +2 -8
  146. package/docs/templates/agent-dag.schema.json +1 -1
  147. package/docs/templates/frontend-implementation-contract.schema.json +1 -4
  148. package/harness.json +3 -3
  149. package/package.json +1 -1
  150. package/skills/frontend-bounded-implement/SKILL.md +14 -15
  151. package/skills/frontend-design-review/SKILL.md +41 -22
  152. package/skills/frontend-implementation/SKILL.md +52 -0
  153. package/skills/frontend-implementation/references/code-standards.md +33 -0
  154. package/skills/frontend-implementation/references/design-spec.md +56 -0
  155. package/skills/frontend-implementation/references/node-contracts.md +31 -0
  156. package/skills/frontend-review/SKILL.md +15 -20
  157. package/skills/frontend-review/references/review-findings.md +7 -6
  158. package/skills/frontend-verification/SKILL.md +1 -1
  159. package/skills/loop-agent/references/command-reference.md +0 -1
  160. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  161. package/dist/commands/dag-follow-up.js +0 -138
  162. package/dist/executors/pi-read-budget-policy.js +0 -239
  163. package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
  164. package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
  165. package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
  166. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
  167. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
  168. package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
  169. package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
  170. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
  171. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
  172. package/dist/worker/console/static-src/active-run-badge.js +0 -17
  173. package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
  174. package/dist/workflows/dag/dag-retry-schema.js +0 -138
  175. package/dist/workflows/dag/frontend-closeout.js +0 -221
  176. package/dist/workflows/dag/frontend-design-policy.js +0 -400
  177. package/dist/workflows/dag/frontend-human-decision.js +0 -182
  178. package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
  179. package/dist/workflows/dag/frontend-read-budgets.js +0 -12
  180. package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
  181. package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
  182. package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
  183. package/dist/workflows/dag/frontend-review-findings.js +0 -270
  184. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
  185. package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
  186. package/dist/workflows/dag/frontend-shape-facts.js +0 -409
  187. package/dist/workflows/dag/frontend-shape.js +0 -427
  188. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
  189. package/dist/workflows/dag/frontend-split-application-service.js +0 -203
  190. package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
  191. package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
  192. package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
  193. package/dist/workflows/dag/frontend-writer-admission.js +0 -285
  194. package/dist/workflows/dag/frontend-writer-status.js +0 -256
  195. package/dist/workflows/dag/recovery-lease.js +0 -80
  196. package/docs/templates/frontend-implementation-dag.json +0 -89
  197. package/docs/templates/spec-registry.schema.json +0 -45
  198. package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
  199. package/skills/frontend-contract/SKILL.md +0 -23
  200. package/skills/frontend-contract/references/contract-protocol.md +0 -34
  201. package/skills/frontend-plan/SKILL.md +0 -26
  202. package/skills/frontend-plan/references/decision-contract.md +0 -37
  203. package/skills/frontend-plan/references/design-decisions.md +0 -17
  204. package/skills/frontend-scout/SKILL.md +0 -25
  205. package/skills/frontend-scout/references/design-evidence.md +0 -16
  206. package/skills/frontend-scout/references/scout-evidence.md +0 -23
@@ -35,8 +35,25 @@ function toProgressNode(node) {
35
35
  startedAt: typeof node.startedAt === "string" ? node.startedAt : undefined,
36
36
  durationMs: typeof node.durationMs === "number" ? node.durationMs : undefined,
37
37
  errorPreview: typeof node.errorPreview === "string" ? node.errorPreview : undefined,
38
+ lastMeaningfulProgressAt: typeof node.lastMeaningfulProgressAt === "string"
39
+ ? node.lastMeaningfulProgressAt
40
+ : undefined,
41
+ livenessStatus: typeof node.livenessStatus === "string" ? node.livenessStatus : undefined,
38
42
  };
39
43
  }
44
+ function latestIso(values) {
45
+ let latest = null;
46
+ for (const iso of values) {
47
+ if (!iso)
48
+ continue;
49
+ const time = Date.parse(iso);
50
+ if (!Number.isFinite(time))
51
+ continue;
52
+ if (!latest || time > latest.time)
53
+ latest = { iso, time };
54
+ }
55
+ return latest?.iso ?? null;
56
+ }
40
57
  function elapsedSince(iso, now) {
41
58
  if (!iso)
42
59
  return null;
@@ -58,6 +75,8 @@ export function summarizeRunProgress(dag, now = Date.now()) {
58
75
  stalled: false,
59
76
  currentNodeElapsedMs: null,
60
77
  runElapsedMs: null,
78
+ lastMeaningfulProgressAt: null,
79
+ observedAtMs: now,
61
80
  };
62
81
  }
63
82
  const rawNodes = Array.isArray(dag.nodes) ? dag.nodes : [];
@@ -86,6 +105,8 @@ export function summarizeRunProgress(dag, now = Date.now()) {
86
105
  runElapsedMs: terminal
87
106
  ? (runDurationMs ?? elapsedSince(runStartedAt, now))
88
107
  : elapsedSince(runStartedAt, now),
108
+ lastMeaningfulProgressAt: latestIso(nodes.map((node) => node.lastMeaningfulProgressAt)),
109
+ observedAtMs: now,
89
110
  };
90
111
  }
91
112
  const TERMINAL_RUN_PROGRESS_STATUSES = new Set([
@@ -108,3 +129,88 @@ export function formatDurationMs(ms) {
108
129
  return `${minutes} 分 ${seconds} 秒`;
109
130
  return `${seconds} 秒`;
110
131
  }
132
+ function friendlyRunPhase(node) {
133
+ if (!node)
134
+ return "正在等待下一阶段";
135
+ const searchable = `${node.nodeId} ${node.label}`.toLowerCase();
136
+ if (/contract|boundary|intake/.test(searchable))
137
+ return "正在确认执行边界";
138
+ if (/scout|explore|research/.test(searchable))
139
+ return "正在梳理影响范围";
140
+ if (/plan|design/.test(searchable))
141
+ return "正在规划实施步骤";
142
+ if (/implement|repair|fix|write/.test(searchable))
143
+ return "正在实现本次修改";
144
+ if (/verify|test|lint|review|check/.test(searchable))
145
+ return "正在验证本次修改";
146
+ if (/closeout|handoff|report/.test(searchable))
147
+ return "正在整理执行结果";
148
+ if (node.label && node.label !== node.nodeId)
149
+ return `正在进行${node.label}`;
150
+ return "正在推进当前阶段";
151
+ }
152
+ function formatMeaningfulAge(iso, now) {
153
+ if (!iso)
154
+ return "等待首个执行进展";
155
+ const time = Date.parse(iso);
156
+ if (!Number.isFinite(time))
157
+ return "等待首个执行进展";
158
+ const seconds = Math.max(0, Math.floor((now - time) / 1000));
159
+ if (seconds < 60)
160
+ return `最近进展 ${seconds} 秒前`;
161
+ const minutes = Math.floor(seconds / 60);
162
+ if (minutes < 60)
163
+ return `最近进展 ${minutes} 分钟前`;
164
+ return `最近进展 ${Math.floor(minutes / 60)} 小时前`;
165
+ }
166
+ /** Human-facing copy shared by the task panel and Operator Chat progress row. */
167
+ export function presentRunProgress(summary, now = Date.now()) {
168
+ if (!summary || !summary.effectiveStatus) {
169
+ return {
170
+ kind: "pending",
171
+ title: "DAG 正在启动",
172
+ detail: "正在等待首个执行进展",
173
+ };
174
+ }
175
+ const status = summary.effectiveStatus;
176
+ const liveElapsed = summary.runElapsedMs == null
177
+ ? null
178
+ : summary.runElapsedMs +
179
+ (summary.terminal ? 0 : Math.max(0, now - summary.observedAtMs));
180
+ const elapsed = formatDurationMs(liveElapsed);
181
+ const progressDetail = `${friendlyRunPhase(summary.running)} · 已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`;
182
+ if (status === "running") {
183
+ return { kind: "progress", title: `DAG 执行中 · 已运行 ${elapsed}`, detail: progressDetail };
184
+ }
185
+ if (status === "running-quiet") {
186
+ return { kind: "quiet", title: `DAG 仍在执行 · 已运行 ${elapsed}`, detail: progressDetail };
187
+ }
188
+ if (status === "running-suspected-stall") {
189
+ return { kind: "warning", title: `执行可能停滞 · 已运行 ${elapsed}`, detail: progressDetail };
190
+ }
191
+ if (["needs-attention", "paused", "interrupted"].includes(status)) {
192
+ return { kind: "warning", title: "DAG 需要处理", detail: progressDetail };
193
+ }
194
+ if (status === "finished") {
195
+ return {
196
+ kind: "success",
197
+ title: `DAG 已完成 · 总用时 ${elapsed}`,
198
+ detail: `已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`,
199
+ };
200
+ }
201
+ if (status === "partial_failed" || status === "failed") {
202
+ return {
203
+ kind: "error",
204
+ title: `DAG 未完成 · 总用时 ${elapsed}`,
205
+ detail: `已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`,
206
+ };
207
+ }
208
+ if (status === "pending") {
209
+ return { kind: "pending", title: "DAG 正在启动", detail: progressDetail };
210
+ }
211
+ return {
212
+ kind: "warning",
213
+ title: `DAG 已结束 · 总用时 ${elapsed}`,
214
+ detail: `已完成 ${summary.finished}/${summary.total} 步`,
215
+ };
216
+ }
@@ -19,10 +19,9 @@
19
19
  */
20
20
  import { randomBytes } from "node:crypto";
21
21
  import { existsSync } from "node:fs";
22
- import { rm } from "node:fs/promises";
23
22
  import path from "node:path";
24
23
  import { LoopAgentClient } from "../loop-agent/loop-agent-client.js";
25
- import { openConsoleAppData, sha256Json, writeSecureJson, } from "./app-data.js";
24
+ import { openConsoleAppData } from "./app-data.js";
26
25
  import { DagConfirmationStore } from "./dag-confirmation.js";
27
26
  import { DagExecutionReceiptStore } from "./dag-execution-receipt.js";
28
27
  import { DraftStore } from "./draft-store.js";
@@ -39,9 +38,6 @@ import { GoalRoundDriver } from "./chat/goal-round-driver.js";
39
38
  import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
40
39
  import { createOperationEventStore, } from "./operation-sse.js";
41
40
  import { OperationStore } from "./operation-store.js";
42
- import { createFrontendSplitOperationAdapter, } from "./frontend-split-operation-adapter.js";
43
- import { createFrontendHumanDecisionAdapter } from "./frontend-human-decision-adapter.js";
44
- import { FrontendSplitTaskMaterializer, } from "../materialize/frontend-split-task-materializer.js";
45
41
  import { probePiReadiness, } from "./pi-readiness.js";
46
42
  import { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
47
43
  import { NightAuxTicker } from "./night-aux-ticker.js";
@@ -77,106 +73,6 @@ export async function createWorkspaceContext(record, deps) {
77
73
  const events = createOperationEventStore({
78
74
  persistenceDir: path.join(appData.operations, "events"),
79
75
  });
80
- // Frontend split runtime is repo-scoped: materialization, durable schedule
81
- // intents, and aggregate closeout must use this workspace's app-data and
82
- // OperationStore rather than the process startup workspace.
83
- const splitScheduleDir = path.join(appData.root, "frontend-split-schedule");
84
- const splitAuditDir = path.join(appData.root, "frontend-split-audit");
85
- const splitAggregateDir = path.join(appData.root, "frontend-split-aggregate");
86
- const splitMaterializationBackend = {
87
- async ensureTask({ taskId }) {
88
- return { taskId, complete: true };
89
- },
90
- async ensureOperation({ taskId, operationId, payloadDigest, request }) {
91
- const action = request.kind === "child"
92
- ? "frontendSplitChild"
93
- : "frontendSplitIntegration";
94
- const { operation } = await operations.ensureSplitMaterializedOperation({
95
- operationId,
96
- taskId,
97
- action,
98
- actionParams: request,
99
- payloadHash: payloadDigest,
100
- });
101
- return {
102
- taskId: operation.taskId ?? taskId,
103
- operationId: operation.operationId,
104
- complete: true,
105
- };
106
- },
107
- };
108
- const materializer = new FrontendSplitTaskMaterializer(splitMaterializationBackend);
109
- const schedule = async (input) => {
110
- const fact = {
111
- schemaVersion: 1,
112
- kind: "frontend-split-schedule-intent",
113
- operationId: input.operationId,
114
- generation: input.generation,
115
- bindingDigest: input.bindingDigest,
116
- recordedAt: new Date().toISOString(),
117
- };
118
- const name = `${sha256Json({
119
- operationId: input.operationId,
120
- generation: input.generation,
121
- bindingDigest: input.bindingDigest,
122
- })}.json`;
123
- await writeSecureJson(path.join(splitScheduleDir, name), fact);
124
- };
125
- const auditStale = async (input) => {
126
- const fact = {
127
- schemaVersion: 1,
128
- kind: "frontend-split-stale-audit",
129
- operationId: input.operationId,
130
- reason: input.reason,
131
- fact: input.fact,
132
- recordedAt: new Date().toISOString(),
133
- };
134
- await writeSecureJson(path.join(splitAuditDir, `${sha256Json(fact)}.json`), fact);
135
- };
136
- const prepareAggregate = async ({ operationId, aggregate, }) => {
137
- const before = await operations.get(operationId);
138
- if (!before) {
139
- throw new Error(`cannot prepare aggregate: parent operation not found: ${operationId}`);
140
- }
141
- const aggregateFile = path.join(splitAggregateDir, `${aggregate.digest}.json`);
142
- let parentRecordPath = "";
143
- let wroteAggregate = false;
144
- let wroteParent = false;
145
- return {
146
- committedRef: {
147
- ref: aggregateFile,
148
- digest: aggregate.digest,
149
- commitState: "committed",
150
- },
151
- async commit(input) {
152
- parentRecordPath = input.parentRecordPath;
153
- await writeSecureJson(aggregateFile, {
154
- schemaVersion: 1,
155
- kind: "frontend-split-aggregate-closeout",
156
- operationId,
157
- aggregate,
158
- });
159
- wroteAggregate = true;
160
- await writeSecureJson(input.parentRecordPath, input.parentRecord);
161
- wroteParent = true;
162
- },
163
- async rollback() {
164
- if (wroteAggregate)
165
- await rm(aggregateFile, { force: true });
166
- if (wroteParent && parentRecordPath) {
167
- await writeSecureJson(parentRecordPath, before);
168
- }
169
- },
170
- };
171
- };
172
- const splitOrchestrator = createFrontendSplitOperationAdapter({
173
- store: operations,
174
- materializer,
175
- schedule,
176
- auditStale,
177
- prepareAggregate,
178
- });
179
- const humanDecisionAdapter = createFrontendHumanDecisionAdapter(operations);
180
76
  const drafts = new DraftStore(appData);
181
77
  const confirmations = new DagConfirmationStore(appData, deps.bootToken.confirmationToken);
182
78
  const dagExecutionReceipts = new DagExecutionReceiptStore(appData);
@@ -201,9 +97,6 @@ export async function createWorkspaceContext(record, deps) {
201
97
  client,
202
98
  operations,
203
99
  events,
204
- splitOrchestrator,
205
- humanDecisionAdapter,
206
- materializer,
207
100
  drafts,
208
101
  confirmations,
209
102
  dagExecutionReceipts,
@@ -299,9 +192,6 @@ export async function createWorkspaceContext(record, deps) {
299
192
  client,
300
193
  operations,
301
194
  events,
302
- splitOrchestrator,
303
- humanDecisionAdapter,
304
- materializer,
305
195
  drafts,
306
196
  confirmations,
307
197
  dagExecutionReceipts,
@@ -2362,6 +2362,8 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
2362
2362
  durationMs: readNumber(node, "durationMs"),
2363
2363
  startedAt: readString(node, "startedAt"),
2364
2364
  finishedAt: readString(node, "finishedAt"),
2365
+ lastMeaningfulProgressAt: readString(node, "lastMeaningfulProgressAt"),
2366
+ livenessStatus: readString(node, "livenessStatus"),
2365
2367
  ...(outputPreview ? { outputPreview } : {}),
2366
2368
  ...(errorPreview ? { errorPreview } : {}),
2367
2369
  ...(attempts && attempts.length > 0
@@ -2407,6 +2409,8 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
2407
2409
  durationMs: readNumber(node, "durationMs"),
2408
2410
  startedAt: readString(node, "startedAt"),
2409
2411
  finishedAt: readString(node, "finishedAt"),
2412
+ lastMeaningfulProgressAt: readString(node, "lastMeaningfulProgressAt"),
2413
+ livenessStatus: readString(node, "livenessStatus"),
2410
2414
  ...(outputPreview ? { outputPreview } : {}),
2411
2415
  ...(errorPreview ? { errorPreview } : {}),
2412
2416
  ...(attempts && attempts.length > 0
@@ -39,25 +39,20 @@ const NODE_PURPOSE_BY_ID = {
39
39
  "closeout-summary-pi": "把验证与交付事实写成简短交接摘要。",
40
40
 
41
41
  "frontend-contract-pi": "把前端需求整理成可追踪契约:页面、状态、验收与边界。",
42
- "frontend-contract-import-shell": "导入并固化前端实现契约,供后续设计、写入与验证节点使用。",
43
42
  "frontend-scout-pi": "只读侦察前端结构、组件与接口用法,不改文件。",
44
43
  "frontend-plan-pi": "选定实现与 Mock/API 策略,输出可执行的前端实现契约补丁。",
45
- "frontend-design-policy-shell": "确定前端设计与 Mock/API 策略,冻结后续实现必须遵守的规则。",
46
44
  "frontend-design-review-pi": "审查前端计划是否可写:通过则沿用原计划,否则要求修订。",
47
45
  "frontend-plan-revision-pi": "按设计审查意见修订前端契约,不扩大范围。",
48
46
  "frontend-final-design-review-pi": "复核修订后的前端计划,只有通过才能授权写入。",
49
47
  "frontend-prewrite-gate-shell": "写入前门禁:核对契约、覆盖与 Mock 策略,通过才允许改前端。",
50
- "frontend-writer-admission-shell": "核对契约、设计策略与 writeSet,满足准入条件后才允许 writer 写入。",
51
48
  "frontend-lint-baseline-shell": "记录写入前的 lint 基线,供实现后对比,不阻断写入。",
52
49
  "frontend-implement-pi": "按已批准契约改前端代码、状态与交互,留在 writeSet 内。",
53
50
  "frontend-verify-assess-shell": "跑静态/行为/Mock 验证并评估能否有界修复。",
54
- "frontend-verify-shell": "执行冻结的前端验证命令,产出可复查的通过或失败证据。",
55
51
  "frontend-repair-pi": "只修评估为可修复的前端失败,不重开需求。",
56
52
  "frontend-reverify-shell": "修复后用同一套冻结命令复验。",
57
53
  "frontend-review-context-shell": "打包真实 diff 与有效验证证据,供审查节点使用。",
58
54
  "frontend-review-pi": "对照契约审查前端实现与证据,给出通过或需修订。",
59
55
  "frontend-review-gate-shell": "前端审查门禁:结论为通过才进入收口。",
60
- "frontend-closeout-shell": "汇总实现、验证与遗留事项,形成确定性的前端收口事实。",
61
56
  "frontend-closeout-pi": "汇总前端交付状态(含真实联调是否仍待完成)。",
62
57
  "frontend-mock-assess-pi": "评估项目是否具备授权的 Mock 验证能力。",
63
58
  "frontend-mock-contract-gate-shell": "Mock 契约门禁:策略与验证命令不一致则阻断。",
@@ -49,6 +49,7 @@ import {
49
49
  formatSessionEventTime,
50
50
  renderSessionTimeline,
51
51
  buildPageStyledDoc,
52
+ fetchPageStylesheetText,
52
53
  } from "./session-timeline.js";
53
54
  import { createCustomSelect } from "../custom-select.js";
54
55
 
@@ -2366,13 +2367,7 @@ function appendProcessExportButton(content, body, meta) {
2366
2367
  const btn = el("button", "btn btn-secondary", "导出 HTML");
2367
2368
  btn.type = "button";
2368
2369
  btn.addEventListener("click", async () => {
2369
- let cssText = "";
2370
- try {
2371
- const res = await fetch("/inspect/styles.css", { cache: "no-cache" });
2372
- if (res.ok) cssText = await res.text();
2373
- } catch {
2374
- /* 忽略:导出仍会用内联兜底样式 */
2375
- }
2370
+ const cssText = await fetchPageStylesheetText();
2376
2371
  // 复用页面真实渲染 DOM,保证导出排版与页面一致。
2377
2372
  const clone = content.cloneNode(true);
2378
2373
  const bar = clone.querySelector(".process-timeline-export-bar");
@@ -51,6 +51,27 @@ function downloadTextFile(filename, text) {
51
51
  URL.revokeObjectURL(url);
52
52
  }
53
53
 
54
+ /** 抓取页面真实加载的样式表原文(导出内联用)。
55
+ * 不猜路径:直接取当前文档 <link rel="stylesheet"> 的 href(相对当前页面解析),
56
+ * 反代/子路径部署下也和页面渲染用的是同一份 CSS;fetch 失败时返回 "",
57
+ * 调用方回退 EXPORT_FALLBACK_CSS。 */
58
+ export async function fetchPageStylesheetText() {
59
+ try {
60
+ const links = [...document.querySelectorAll('link[rel="stylesheet"]')];
61
+ const texts = [];
62
+ for (const link of links) {
63
+ const href = link.getAttribute("href");
64
+ if (!href) continue;
65
+ const url = new URL(href, document.baseURI).href;
66
+ const res = await fetch(url, { cache: "no-cache" });
67
+ if (res.ok) texts.push(await res.text());
68
+ }
69
+ return texts.join("\n");
70
+ } catch {
71
+ return "";
72
+ }
73
+ }
74
+
54
75
  /** 兜底样式:仅在无法抓取页面 /inspect/styles.css 时使用(与页面 process-timeline 排版对齐)。 */
55
76
  const EXPORT_FALLBACK_CSS = `
56
77
  :root{--canvas:#f7f7f4;--surface:#ffffff;--canvas-soft:#fafaf7;--surface-muted:#f0efe9;--ink:#26251e;--body:#5a5852;--muted:#807d72;--muted-soft:#a09c92;--hairline:#e6e5e0;--hairline-strong:#cfcdc4;--orange:#f54e00;--orange-active:#d04200;--blue:#3f6ea4;--green:#1f8a65;--amber:#a66a00;--red:#c72d53;--lavender:#e4daf1;--radius-sm:6px;--space-2:8px;--space-3:12px;--space-5:20px}
@@ -103,19 +124,31 @@ body{margin:0;font-family:-apple-system,"Segoe UI",Roboto,"PingFang SC","Microso
103
124
  .markdown-output p,.markdown-output blockquote,.markdown-output ul,.markdown-output ol{margin:0;overflow-wrap:anywhere}
104
125
  .markdown-output ul,.markdown-output ol{padding-left:22px}
105
126
  .markdown-output blockquote{padding-left:12px}
127
+ .btn{display:inline-flex;align-items:center;gap:6px;min-height:28px;padding:0 12px;border:1px solid var(--hairline-strong);border-radius:var(--radius-sm);background:var(--surface);color:var(--body);font-size:12px;font-weight:600;cursor:pointer;user-select:none}
128
+ .btn-secondary{background:var(--surface-muted)}
129
+ .btn:hover{border-color:var(--orange);color:var(--orange-active)}
106
130
  `;
107
131
 
108
132
  const EXPORT_SHELL_CSS = `
109
- body{margin:0;font-family:-apple-system,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;background:var(--canvas)}
110
- .export-shell{max-width:1000px;margin:0 auto;padding:24px 28px;background:var(--surface);min-height:100vh;box-shadow:0 0 0 1px var(--hairline)}
111
- .export-head h1{font-size:15px;color:var(--ink);margin:0;font-weight:700}
112
- .export-sub{color:var(--muted);font-size:12px;margin:4px 0 16px}
133
+ /* 导出文档变量基础:页面样式把这些变量定义在 body[data-shell]/.inspect-workspace-root
134
+ * 下,独立导出的 HTML 既无该属性也无该包裹,变量会整体失效——深色系统的画布
135
+ * 兜底为黑底,叠加黑字后正文不可读。导出文档自带变量定义并强制 light 配色。 */
136
+ :root{color-scheme:light}
137
+ body{margin:0;font-family:-apple-system,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;background:var(--canvas,#f7f7f4)}
138
+ .export-shell{max-width:1000px;margin:0 auto;padding:24px 28px;background:var(--surface,#ffffff);min-height:100vh;box-shadow:0 0 0 1px var(--hairline,#e6e5e0)}
139
+ .export-head h1{font-size:15px;color:var(--ink,#26251e);margin:0;font-weight:700}
140
+ .export-sub{color:var(--muted,#807d72);font-size:12px;margin:4px 0 16px}
113
141
  .export-controls{display:flex;justify-content:flex-start;margin:0 0 12px}
114
- .export-toggle{min-height:28px;padding:0 12px;border:1px solid var(--hairline-strong);border-radius:var(--radius-sm);background:var(--surface);color:var(--body);font-size:11px;font-weight:600;cursor:pointer;user-select:none}
115
- .export-toggle:hover,.export-toggle:focus-visible{border-color:var(--orange);color:var(--orange-active);outline:none}
142
+ .export-toggle{min-height:28px;padding:0 12px;border:1px solid var(--hairline-strong,#cfcdc4);border-radius:var(--radius-sm,6px);background:var(--surface,#ffffff);color:var(--body,#5a5852);font-size:11px;font-weight:600;cursor:pointer;user-select:none}
143
+ .export-toggle:hover,.export-toggle:focus-visible{border-color:var(--orange,#f54e00);color:var(--orange-active,#d04200);outline:none}
116
144
  body.hide-protocol .process-timeline-item.process-timeline-protocol{display:none}
117
145
  `;
118
146
 
147
+ /** 导出文档自带的变量基础:与页面 styles.css 的 light 主题保持一致。
148
+ * 即使 fetch 到了页面 CSS,其变量也挂在 body[data-shell=…]/.inspect-workspace-root
149
+ * 选择器下而不生效,必须在导出文档内重新定义。 */
150
+ const EXPORT_ROOT_VARS_CSS = `:root{--canvas:#f7f7f4;--surface:#ffffff;--canvas-soft:#fafaf7;--surface-muted:#f0efe9;--surface-ink:#26251e;--ink:#26251e;--body:#5a5852;--muted:#807d72;--muted-soft:#a09c92;--hairline:#e6e5e0;--hairline-strong:#cfcdc4;--orange:#f54e00;--orange-active:#d04200;--blue:#3f6ea4;--green:#1f8a65;--amber:#a66a00;--red:#c72d53;--lavender:#e4daf1;--mint:#d8ead5;--peach:#f1d8cb;--gold:#d8b166;--radius-sm:6px;--radius-md:8px;--radius-lg:12px;--space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:20px;--space-6:24px;--space-8:32px;--space-10:40px;--space-12:48px}`;
151
+
119
152
  /**
120
153
  * 把页面渲染出的时间线 innerHTML 封装成自包含 HTML。
121
154
  * @param {string} bodyHTML 页面渲染的 .process-timeline 等真实 DOM 序列化结果
@@ -126,14 +159,18 @@ export function buildPageStyledDoc(bodyHTML, meta = {}, cssText = "") {
126
159
  const now = new Date().toLocaleString();
127
160
  const title = meta.nodeId ? `节点执行过程 · ${meta.nodeId}` : "节点执行过程";
128
161
  const style = cssText.trim()
129
- ? `${cssText}\n\n/* 导出外壳 */\n${EXPORT_SHELL_CSS}`
162
+ ? `${EXPORT_ROOT_VARS_CSS}\n${cssText}\n\n/* 导出外壳 */\n${EXPORT_SHELL_CSS}`
130
163
  : `${EXPORT_FALLBACK_CSS}\n${EXPORT_SHELL_CSS}`;
131
164
  // 有协议行时,在左上角提供一个「展开/隐藏原始协议事件」按钮(与页面一致),默认隐藏。
132
165
  const hasProtocol = bodyHTML.includes("process-timeline-protocol");
133
166
  const controls = hasProtocol
134
167
  ? `<div class="export-controls"><button type="button" id="exportToggleProtocol" class="export-toggle">展开原始协议事件</button></div><script>(function(){var b=document.body,t=document.getElementById('exportToggleProtocol'),on=false;function apply(){b.classList.toggle('hide-protocol',!on);t.textContent=on?'隐藏原始协议事件':'展开原始协议事件';}if(t){t.addEventListener('click',function(){on=!on;apply();});apply();}})();</script>`
135
168
  : "";
136
- return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(title)}</title><style>${style}</style></head><body><div class="export-shell"><div class="export-head"><h1>${escapeHtml(title)}</h1></div><p class="export-sub">DAG ${escapeHtml(meta.dagRunId ?? "")} · 导出于 ${escapeHtml(now)}</p>${controls}<div class="export-timeline">${bodyHTML}</div></div></body></html>`;
169
+ // body 带 data-shell="observe-console":内联的页面 styles.css 把全部设计令牌
170
+ // (--canvas/--surface/--hairline-strong …) 与部分规则 (body[data-shell] …) 定义在
171
+ // 该作用域下,导出文档带上属性后令牌与前缀规则原生命中,与页面渲染一致。
172
+ // EXPORT_ROOT_VARS_CSS 仍保留:fetch 页面 CSS 失败的兜底路径令牌不缺。
173
+ return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(title)}</title><style>${style}</style></head><body data-shell="observe-console"><div class="export-shell"><div class="export-head"><h1>${escapeHtml(title)}</h1></div><p class="export-sub">DAG ${escapeHtml(meta.dagRunId ?? "")} · 导出于 ${escapeHtml(now)}</p>${controls}<div class="export-timeline">${bodyHTML}</div></div></body></html>`;
137
174
  }
138
175
 
139
176
  export function sessionEventResult(event) {
@@ -851,13 +888,7 @@ export function renderSessionTimeline(content, nodeId, options = {}) {
851
888
  const exportBtn = el("button", "btn btn-secondary", "导出 HTML");
852
889
  exportBtn.type = "button";
853
890
  exportBtn.addEventListener("click", async () => {
854
- let cssText = "";
855
- try {
856
- const res = await fetch("/inspect/styles.css", { cache: "no-cache" });
857
- if (res.ok) cssText = await res.text();
858
- } catch {
859
- /* 忽略:导出仍会用内联兜底样式 */
860
- }
891
+ const cssText = await fetchPageStylesheetText();
861
892
  // 复用页面真实渲染(含原始协议事件),保证导出的排版与页面一致。
862
893
  const prevShow = uiState.sessionTimelineShowProtocol;
863
894
  uiState.sessionTimelineShowProtocol = true;
@@ -934,7 +934,7 @@ export async function materializeBackendTestResultFromPytestHtml(input) {
934
934
  outcome = "command-error";
935
935
  }
936
936
  const commandSummary = input.commandSummary ??
937
- `PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python -m pytest -v -p no:cacheprovider --html=${htmlRelativePath} --self-contained-html`;
937
+ `PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python reports/backend-test-pytest-launcher.py reports/backend-test-pytest-targets.txt ${htmlRelativePath} -> pytest.main(-v -p no:cacheprovider --self-contained-html)`;
938
938
  assertNoSecrets("commandSummary", commandSummary);
939
939
  const result = backendTestResultContractSchema.parse({
940
940
  schemaVersion: 1,
@@ -3,7 +3,8 @@
3
3
  * its schemaId in the contract output registry. Imported once by the node
4
4
  * executor; keeps per-contract imports out of the executor.
5
5
  */
6
- import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput, } from "./frontend-implementation-contract.js";
6
+ import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput, validateFrontendRevisionPatchNodeOutput, } from "./frontend-implementation-contract.js";
7
7
  import { registerStructuredContractValidator } from "./contract-output-registry.js";
8
8
  registerStructuredContractValidator("frontend-implementation-contract-v1", validateFrontendContractNodeOutput);
9
9
  registerStructuredContractValidator("frontend-implementation-contract-plan-patch-v1", validateFrontendPlanPatchNodeOutput);
10
+ registerStructuredContractValidator("frontend-implementation-contract-revision-patch-v1", validateFrontendRevisionPatchNodeOutput);