@tea-agent/loop-agent 0.43.0-next.1 → 0.43.0-next.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 (199) hide show
  1. package/CHANGELOG.md +129 -9
  2. package/dist/application/dag/run-dag.js +5 -0
  3. package/dist/application/evaluation/budget.js +19 -1
  4. package/dist/application/task-lifecycle/advance.js +17 -4
  5. package/dist/application/task-lifecycle/observe.js +43 -29
  6. package/dist/application/task-lifecycle/plan-transitions.js +5 -4
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/executors/dag-pi-executor.js +1214 -584
  9. package/dist/executors/pi-executor.js +22 -1
  10. package/dist/executors/pi-sdk-executor.js +140 -39
  11. package/dist/executors/shell-executor.js +5 -2
  12. package/dist/shared/frontend-execution-policy.js +23 -0
  13. package/dist/task/config-types.js +4 -0
  14. package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
  15. package/dist/task/source-prepare/ledger-review.js +6 -9
  16. package/dist/task/source-prepare/semantic-intake.js +16 -26
  17. package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
  18. package/dist/task/source-references.js +48 -23
  19. package/dist/worker/console/chat/assistant-content.js +23 -2
  20. package/dist/worker/console/chat/browser-policy.js +143 -0
  21. package/dist/worker/console/chat/browser-routes.js +148 -0
  22. package/dist/worker/console/chat/explore-tools.js +13 -0
  23. package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
  24. package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
  25. package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +78 -0
  26. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +86 -0
  27. package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
  28. package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
  29. package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
  30. package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
  31. package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
  32. package/dist/worker/console/chat/pi-runtime.js +213 -1181
  33. package/dist/worker/console/chat/routes.js +212 -71
  34. package/dist/worker/console/chat/scm-routes.js +217 -0
  35. package/dist/worker/console/chat/scm-service.js +283 -0
  36. package/dist/worker/console/chat/scm-tools.js +111 -0
  37. package/dist/worker/console/chat/sdd-data-alignment.js +65 -9
  38. package/dist/worker/console/chat/session-catalog.js +32 -0
  39. package/dist/worker/console/chat/session-mode-view.js +48 -0
  40. package/dist/worker/console/chat/session-mode.js +218 -0
  41. package/dist/worker/console/chat/session-store.js +22 -6
  42. package/dist/worker/console/chat/shortcuts.js +6 -0
  43. package/dist/worker/console/chat/terminal-routes.js +216 -0
  44. package/dist/worker/console/chat/terminal-sessions.js +348 -0
  45. package/dist/worker/console/chat/terminal-tools.js +211 -0
  46. package/dist/worker/console/chat/tool-preview.js +11 -0
  47. package/dist/worker/console/chat/tools.js +2 -0
  48. package/dist/worker/console/server.js +2 -28
  49. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B0Q0nClf.js → abnfDiagram-N423BO3Z-D_fNbWc_.js} +1 -1
  50. package/dist/worker/console/static/assets/{arc-DCPjC19G.js → arc-_HuqLW6m.js} +1 -1
  51. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP5n9jmG.js → architectureDiagram-T3A2C74G-Djy2qY1m.js} +1 -1
  52. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CmaWi_Wg.js → blockDiagram-VBNYF7ZC-bB-1TSyY.js} +1 -1
  53. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ct5tcMfZ.js → c4Diagram-5PPSVZJV-C5Hmghrz.js} +1 -1
  54. package/dist/worker/console/static/assets/channel-C0A6UEIa.js +1 -0
  55. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C0osm1Zf.js → chunk-2GRJ4B5K-Duyl39oy.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDWviED7.js → chunk-2Q5K7J3B-x6mdnSyf.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-mtSk1-j7.js → chunk-5RXB4S5H-WlBuFj7W.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D5J9PHC7.js → chunk-5VM5RSS4-CTF429HX.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Begg4WAa.js → chunk-6Q2QTUOP-Dwd6r-SF.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-sF1AEy7T.js → chunk-GF5L2VYU-C-Br82D1.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BqbsWXZb.js → chunk-JWPE2WC7-CuGp0DeE.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BKJrPcJ6.js → chunk-KBJHAD2P-CJhFX6Lh.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CcwNMRho.js → chunk-RYQCIY6F-DpGOa_jT.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BEJi3iIs.js → chunk-XXDRQBXY-DXJaTynd.js} +1 -1
  65. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +1 -0
  66. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +1 -0
  67. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-DRLgTvVu.js → cose-bilkent-JH36ORCC-CRBZWjE4.js} +1 -1
  68. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BoAVG3cs.js → cynefin-VYW2F7L2-BdXcxtlb.js} +1 -1
  69. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bzol06hR.js → cynefinDiagram-MW4NZA55-07UKsOts.js} +1 -1
  70. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UmliJM77.js → dagre-VZM6K2ZE-D38p9__S.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BWLcJkFo.js → diagram-7IWD3JNH-nSiBNNjH.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CgApOm-O.js → diagram-B4RE2ZJO-BFOTot5D.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bh3RgTLs.js → diagram-LBJQPF4R-DypKaZ6k.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Dsh-D5nE.js → diagram-Q27KOJAE-B6yoOQeW.js} +1 -1
  75. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dkbbpcpb.js → diagram-UB23O5K3-BHYS6viX.js} +1 -1
  76. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-3EBaA3t3.js → ebnfDiagram-BXEA7PRR-D2VD5Snj.js} +1 -1
  77. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bq2Dc5ok.js → erDiagram-JOGREHBK-E98mWxN1.js} +1 -1
  78. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-De7y55ha.js → flowDiagram-UKHOOZJN-CT1zxhqP.js} +1 -1
  79. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-fLiDbQRh.js → ganttDiagram-PKOTCBZU-6GcngNcy.js} +1 -1
  80. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Du617mp1.js → gitGraphDiagram-DS77QQ5N-DCL6NvKe.js} +1 -1
  81. package/dist/worker/console/static/assets/index-24jp3aBh.js +468 -0
  82. package/dist/worker/console/static/assets/index-CY6y6I8M.css +1 -0
  83. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BwmlF-XP.js → infoDiagram-6WML65LV-C0627e-9.js} +1 -1
  84. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-zsGtS59u.js → ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js} +1 -1
  85. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DTTPTe3f.js → journeyDiagram-NVQOT4AX-DPDPv9HL.js} +1 -1
  86. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BkXg9aP-.js → kanban-definition-27J2QSJJ-D3OHdWiO.js} +1 -1
  87. package/dist/worker/console/static/assets/{linear-7U2ue5IE.js → linear-DI88zD7n.js} +1 -1
  88. package/dist/worker/console/static/assets/{mermaid.core-BUuGHmWO.js → mermaid.core-p2l8IrWY.js} +5 -5
  89. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-7q6fX0Sv.js → mindmap-definition-FAOFIHXS-B1bAX-oS.js} +1 -1
  90. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkQPJN55.js → pegDiagram-VL7TDLO6-DwOnWxdD.js} +1 -1
  91. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CCal_tfX.js → pieDiagram-7S7Q4E2Y-xRkTXnJC.js} +1 -1
  92. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js → quadrantDiagram-CIZ2JOQS-_9UC5Zio.js} +1 -1
  93. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CJmww7D-.js → railroadDiagram-AXF67PYL-CgEUirWJ.js} +1 -1
  94. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D6ldWJEv.js → requirementDiagram-LRYGKXZP-Czbpwiyz.js} +1 -1
  95. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-VGh33I9e.js → sankeyDiagram-W5VNT64P-_j7_KiqY.js} +1 -1
  96. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CCkDrIjU.js → sequenceDiagram-SI44F4Z6-BNJa6Hyi.js} +1 -1
  97. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-NC8f5Otb.js → sizeCapture-X5ZJPWSS-DedpKtOc.js} +1 -1
  98. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-_92ezZdF.js → stateDiagram-OKZ733FA-BBvthr7c.js} +1 -1
  99. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +1 -0
  100. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BdUCGtbP.js → swimlanes-SLNWSIFB-Ds7umgEM.js} +2 -2
  101. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +8 -0
  102. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CdD1H8ia.js → timeline-definition-Z64GVDOM-CKa-p1nF.js} +1 -1
  103. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Dkdk3oFo.js → vennDiagram-T6HMQDX7-LoUNR56o.js} +1 -1
  104. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CKu2uPPY.js → wardleyDiagram-T6FBY63Y-Bqbl6ym7.js} +1 -1
  105. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-ClqAnuG8.js → xychartDiagram-ELKLHX3M-B8dUg4lf.js} +1 -1
  106. package/dist/worker/console/static/index.html +2 -2
  107. package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +10 -0
  108. package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
  109. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +122 -10
  110. package/dist/worker/console/static-src/operator-chat/compaction-message.js +7 -0
  111. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +27 -15
  112. package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
  113. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
  114. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +10 -0
  115. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
  116. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +44 -24
  117. package/dist/worker/console/static-src/operator-chat/useChatStream.js +18 -2
  118. package/dist/worker/console/static-src/operator-chat/useChatThread.js +41 -8
  119. package/dist/worker/console/static-src/operator-chat/useComposer.js +26 -11
  120. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
  121. package/dist/worker/console/workspace-context.js +15 -1
  122. package/dist/worker/observe/node-transparency.js +81 -72
  123. package/dist/worker/observe/routes.js +20 -1
  124. package/dist/worker/observe/static/dag-inspector-humanize.js +3 -0
  125. package/dist/worker/observe/static/dom.js +20 -1
  126. package/dist/worker/observe/static/format-pool.d.ts +2 -0
  127. package/dist/worker/observe/static/format-pool.js +6 -0
  128. package/dist/worker/observe/static/format.js +7 -0
  129. package/dist/worker/observe/static/inspect-workspace.js +34 -7
  130. package/dist/worker/observe/static/inspector-submission.js +32 -0
  131. package/dist/worker/observe/static/kpi.js +1 -0
  132. package/dist/worker/observe/static/relations.js +2 -0
  133. package/dist/worker/observe/static/router.js +13 -0
  134. package/dist/worker/observe/static/run-processing.js +2 -0
  135. package/dist/worker/observe/static/shell-chrome.js +36 -3
  136. package/dist/worker/observe/static/state.js +35 -2
  137. package/dist/worker/observe/static/styles.css +263 -39
  138. package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
  139. package/dist/worker/observe/static/task-failure-labels.js +67 -0
  140. package/dist/worker/observe/static/task-history.js +12 -0
  141. package/dist/worker/observe/static/views/batch.js +6 -13
  142. package/dist/worker/observe/static/views/dag-graph.js +50 -3
  143. package/dist/worker/observe/static/views/dag-inspector.js +742 -268
  144. package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
  145. package/dist/worker/observe/static/views/dag.d.ts +6 -0
  146. package/dist/worker/observe/static/views/dag.js +49 -11
  147. package/dist/worker/observe/static/views/dags.js +2 -0
  148. package/dist/worker/observe/static/views/dashboard.js +21 -12
  149. package/dist/worker/observe/static/views/failures.js +21 -11
  150. package/dist/worker/observe/static/views/feature.js +11 -29
  151. package/dist/worker/observe/static/views/pool.js +37 -28
  152. package/dist/worker/observe/static/views/run.js +48 -5
  153. package/dist/worker/observe/static/views/session-timeline.js +189 -240
  154. package/dist/worker/observe/static/views/task.js +81 -62
  155. package/dist/workflows/dag/budget-enforcement.js +53 -3
  156. package/dist/workflows/dag/frontend-capacity.js +9 -0
  157. package/dist/workflows/dag/frontend-design-policy.js +2 -1
  158. package/dist/workflows/dag/frontend-durable-tools.js +193 -0
  159. package/dist/workflows/dag/frontend-execution-groups.js +24 -0
  160. package/dist/workflows/dag/frontend-implementation-contract.js +13 -0
  161. package/dist/workflows/dag/frontend-input-projection.js +76 -0
  162. package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
  163. package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
  164. package/dist/workflows/dag/frontend-plan-render.js +10 -3
  165. package/dist/workflows/dag/frontend-recovery-controller.js +7 -7
  166. package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
  167. package/dist/workflows/dag/frontend-recovery-run.js +4 -0
  168. package/dist/workflows/dag/frontend-review-scopes.js +139 -0
  169. package/dist/workflows/dag/frontend-session-budget.js +249 -0
  170. package/dist/workflows/dag/frontend-shadow-dual-write.js +20 -2
  171. package/dist/workflows/dag/frontend-shape-facts.js +12 -2
  172. package/dist/workflows/dag/frontend-shape.js +30 -1
  173. package/dist/workflows/dag/frontend-test-execution-evidence.js +3 -2
  174. package/dist/workflows/dag/frontend-typed-event-store.js +11 -0
  175. package/dist/workflows/dag/init-hybrid.js +16 -10
  176. package/dist/workflows/dag/node-execution.js +38 -155
  177. package/dist/workflows/dag/prompt.js +4 -0
  178. package/dist/workflows/dag/rerun-plan.js +7 -1
  179. package/dist/workflows/dag/runner.js +26 -1
  180. package/dist/workflows/dag/types.js +6 -0
  181. package/docs/operations/README.md +1 -0
  182. package/docs/templates/frontend-design-contract.md +4 -4
  183. package/docs/templates/frontend-implementation-contract.schema.json +34 -2
  184. package/docs/templates/frontend-implementation-dag.json +5 -5
  185. package/package.json +6 -3
  186. package/skills/frontend-contract/SKILL.md +2 -1
  187. package/skills/frontend-contract/references/contract-protocol.md +19 -3
  188. package/skills/frontend-design-review/SKILL.md +12 -11
  189. package/skills/frontend-plan/SKILL.md +9 -2
  190. package/skills/frontend-plan/references/decision-contract.md +18 -5
  191. package/skills/frontend-review/SKILL.md +10 -11
  192. package/skills/frontend-scout/references/scout-evidence.md +4 -0
  193. package/dist/worker/console/static/assets/channel-DAS07MdS.js +0 -1
  194. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +0 -1
  195. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +0 -1
  196. package/dist/worker/console/static/assets/index-C0O48S_P.js +0 -449
  197. package/dist/worker/console/static/assets/index-CzKf4U8P.css +0 -1
  198. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +0 -1
  199. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +0 -8
@@ -183,6 +183,13 @@ body[data-shell="observe-console"] .process-timeline-time,
183
183
  color: var(--muted);
184
184
  font-size: 12px;
185
185
  }
186
+ /* 末级(当前页)窄屏截断,避免长标题/裸 ID 撑换行。 */
187
+ .breadcrumb .breadcrumb-current {
188
+ overflow: hidden;
189
+ min-width: 0;
190
+ text-overflow: ellipsis;
191
+ white-space: nowrap;
192
+ }
186
193
 
187
194
  .breadcrumb[hidden] {
188
195
  display: none;
@@ -274,9 +281,34 @@ body[data-shell="observe-console"] .process-timeline-time,
274
281
 
275
282
  .view-heading {
276
283
  display: grid;
277
- gap: 4px;
284
+ grid-template-columns: auto minmax(0, 1fr) auto;
285
+ align-items: center;
286
+ gap: 4px 10px;
278
287
  margin-bottom: var(--space-6);
279
288
  }
289
+ /* 标题行:kicker + 标题 + 刷新按钮同行(按钮靠右);说明文字独占下一行。
290
+ 用显式 grid 而非 flex-wrap:flex-basis 百分比在部分嵌套下不触发换行。 */
291
+ .view-heading .view-kicker {
292
+ grid-row: 1;
293
+ grid-column: 1;
294
+ }
295
+ /* 无 kicker 的页面标题从第一列开始,避免空列 + 列间距造成缩进。 */
296
+ .view-heading:not(:has(.view-kicker)) > h2 {
297
+ grid-column: 1 / 3;
298
+ }
299
+ .view-heading > h2 {
300
+ grid-row: 1;
301
+ grid-column: 2;
302
+ margin: 0;
303
+ }
304
+ .view-heading .view-manual-refresh {
305
+ grid-row: 1;
306
+ grid-column: 3;
307
+ }
308
+ .view-heading > p:last-of-type {
309
+ grid-row: 2;
310
+ grid-column: 1 / -1;
311
+ }
280
312
 
281
313
  .view-kicker {
282
314
  display: inline-flex;
@@ -301,7 +333,7 @@ body[data-shell="observe-console"] .process-timeline-time,
301
333
  letter-spacing: -0.02em;
302
334
  }
303
335
 
304
- .view-heading > p:last-child {
336
+ .view-heading > p:last-of-type {
305
337
  max-width: 66ch;
306
338
  margin: 0;
307
339
  color: var(--body);
@@ -1249,6 +1281,9 @@ body.is-resizing-dag-graph {
1249
1281
  user-select: none;
1250
1282
  }
1251
1283
  .dag-inspector-header {
1284
+ /* Keep the attempt menu above the content's sticky timeline toolbar. */
1285
+ position: relative;
1286
+ z-index: 6;
1252
1287
  display: grid;
1253
1288
  grid-template-columns: minmax(0, 1fr) auto;
1254
1289
  gap: var(--space-3);
@@ -1321,6 +1356,26 @@ body.is-resizing-dag-graph {
1321
1356
  overscroll-behavior-x: contain;
1322
1357
  scrollbar-width: thin;
1323
1358
  }
1359
+ /* 溢出渐隐:边缘阴影提示还有更多 Tab(类名由 overflow 测量驱动)。 */
1360
+ .dag-inspector-tabs.is-overflow-start,
1361
+ .dag-inspector-tabs.is-overflow-end {
1362
+ background-repeat: no-repeat;
1363
+ background-size: 22px 100%;
1364
+ }
1365
+ .dag-inspector-tabs.is-overflow-start {
1366
+ background-image: linear-gradient(90deg, rgb(38 37 30 / 12%), rgb(38 37 30 / 0));
1367
+ background-position: left center;
1368
+ }
1369
+ .dag-inspector-tabs.is-overflow-end {
1370
+ background-image: linear-gradient(270deg, rgb(38 37 30 / 12%), rgb(38 37 30 / 0));
1371
+ background-position: right center;
1372
+ }
1373
+ .dag-inspector-tabs.is-overflow-start.is-overflow-end {
1374
+ background-image:
1375
+ linear-gradient(90deg, rgb(38 37 30 / 12%), rgb(38 37 30 / 0)),
1376
+ linear-gradient(270deg, rgb(38 37 30 / 12%), rgb(38 37 30 / 0));
1377
+ background-position: left center, right center;
1378
+ }
1324
1379
  .dag-inspector-tab {
1325
1380
  flex: 0 0 auto;
1326
1381
  min-height: 30px;
@@ -1406,6 +1461,25 @@ body.is-resizing-dag-graph {
1406
1461
  font-weight: 800;
1407
1462
  letter-spacing: 0.12em;
1408
1463
  }
1464
+ .inspector-page-eyebrow-row {
1465
+ display: flex;
1466
+ align-items: center;
1467
+ justify-content: space-between;
1468
+ gap: 8px;
1469
+ }
1470
+ .inspector-page-eyebrow-row .observe-icon-button {
1471
+ flex: 0 0 24px;
1472
+ width: 24px;
1473
+ min-width: 24px;
1474
+ height: 24px;
1475
+ min-height: 24px;
1476
+ font-size: 14px;
1477
+ }
1478
+ /* 收起态只保留 eyebrow 行,点击可再展开(会话级记忆)。 */
1479
+ .inspector-page-lead.is-collapsed h4,
1480
+ .inspector-page-lead.is-collapsed p {
1481
+ display: none;
1482
+ }
1409
1483
  .dag-inspector-attempt-bar {
1410
1484
  position: sticky;
1411
1485
  top: calc(-1 * var(--space-4));
@@ -2029,13 +2103,16 @@ body.is-resizing-dag-graph {
2029
2103
  margin: 0 2px;
2030
2104
  pointer-events: none;
2031
2105
  }
2106
+ /* display:flex 会盖过 UA 的 [hidden],贴底/无溢出隐藏 dock 需显式声明。 */
2107
+ .process-timeline-autoscroll-dock[hidden] {
2108
+ display: none;
2109
+ }
2032
2110
  .process-timeline-autoscroll {
2033
2111
  position: relative;
2034
2112
  pointer-events: auto;
2035
2113
  display: inline-flex;
2036
2114
  align-items: center;
2037
2115
  justify-content: center;
2038
- gap: 7px;
2039
2116
  width: 36px;
2040
2117
  height: 36px;
2041
2118
  padding: 0;
@@ -2044,7 +2121,9 @@ body.is-resizing-dag-graph {
2044
2121
  background: color-mix(in srgb, var(--surface) 94%, var(--canvas));
2045
2122
  color: var(--body);
2046
2123
  box-shadow: 0 8px 24px rgb(38 37 30 / 14%);
2047
- font-size: 14px;
2124
+ font-size: 11px;
2125
+ font-weight: 650;
2126
+ white-space: nowrap;
2048
2127
  cursor: pointer;
2049
2128
  backdrop-filter: blur(10px);
2050
2129
  }
@@ -2080,25 +2159,22 @@ body.is-resizing-dag-graph {
2080
2159
  outline: none;
2081
2160
  box-shadow: 0 8px 24px rgb(38 37 30 / 16%), 0 0 0 3px var(--orange-soft);
2082
2161
  }
2083
- .process-timeline-autoscroll.is-active {
2084
- border-color: var(--ink);
2085
- background: var(--ink);
2086
- color: #fff;
2087
- }
2088
- .process-timeline-autoscroll.is-active:hover,
2089
- .process-timeline-autoscroll.is-active:focus-visible {
2162
+ .process-timeline-autoscroll.has-unread {
2090
2163
  border-color: var(--orange);
2091
- background: var(--ink);
2092
- color: #fff;
2164
+ color: var(--orange-active);
2093
2165
  }
2094
2166
  .process-timeline-autoscroll-signal {
2167
+ display: none;
2168
+ position: absolute;
2169
+ top: 5px;
2170
+ right: 5px;
2095
2171
  width: 5px;
2096
2172
  height: 5px;
2097
2173
  border-radius: 50%;
2098
- background: var(--muted-soft);
2099
- }
2100
- .process-timeline-autoscroll.is-active .process-timeline-autoscroll-signal {
2101
2174
  background: var(--orange);
2175
+ }
2176
+ .process-timeline-autoscroll.has-unread .process-timeline-autoscroll-signal {
2177
+ display: block;
2102
2178
  box-shadow: 0 0 0 4px rgb(245 78 0 / 16%);
2103
2179
  animation: process-timeline-live-pulse 1.8s ease-out infinite;
2104
2180
  }
@@ -2107,7 +2183,7 @@ body.is-resizing-dag-graph {
2107
2183
  75% { box-shadow: 0 0 0 7px rgb(245 78 0 / 0%); }
2108
2184
  }
2109
2185
  @media (prefers-reduced-motion: reduce) {
2110
- .process-timeline-autoscroll.is-active .process-timeline-autoscroll-signal {
2186
+ .process-timeline-autoscroll.has-unread .process-timeline-autoscroll-signal {
2111
2187
  animation: none;
2112
2188
  }
2113
2189
  .process-timeline-autoscroll::after {
@@ -2119,28 +2195,6 @@ body.is-resizing-dag-graph {
2119
2195
  flex-wrap: wrap;
2120
2196
  gap: 4px;
2121
2197
  }
2122
- .process-timeline-toggle {
2123
- min-height: 28px;
2124
- padding: 0 10px;
2125
- border: 1px solid var(--hairline-strong);
2126
- border-radius: var(--radius-sm);
2127
- background: var(--surface);
2128
- color: var(--body);
2129
- font-size: 11px;
2130
- font-weight: 600;
2131
- cursor: pointer;
2132
- }
2133
- .process-timeline-toggle:hover,
2134
- .process-timeline-toggle:focus-visible {
2135
- border-color: var(--orange);
2136
- color: var(--orange-active);
2137
- outline: none;
2138
- }
2139
- .process-timeline-toggle[aria-pressed="true"] {
2140
- border-color: color-mix(in srgb, var(--orange) 55%, var(--hairline));
2141
- background: #fff5ed;
2142
- color: var(--orange-active);
2143
- }
2144
2198
  .process-timeline-body {
2145
2199
  min-width: 0;
2146
2200
  padding: 4px 0 8px var(--space-2);
@@ -2995,6 +3049,19 @@ body.is-resizing-dag-graph {
2995
3049
  white-space: pre-wrap;
2996
3050
  overflow-wrap: anywhere;
2997
3051
  }
3052
+ .run-output-shell {
3053
+ position: relative;
3054
+ }
3055
+ .run-output-follow-dock {
3056
+ position: absolute;
3057
+ right: 10px;
3058
+ bottom: 10px;
3059
+ z-index: 3;
3060
+ pointer-events: none;
3061
+ }
3062
+ .run-output-follow-dock[hidden] {
3063
+ display: none;
3064
+ }
2998
3065
 
2999
3066
  .artifact-list {
3000
3067
  display: grid;
@@ -5199,3 +5266,160 @@ button.copy-command:focus-visible {
5199
5266
  .prompt-restart-status.prompt-restart-error {
5200
5267
  color: var(--red);
5201
5268
  }
5269
+
5270
+ /* Reader controls stay within reach while the body scrolls. */
5271
+ .dag-inspector.is-expanded { width: calc(100vw - 48px); }
5272
+ .inspect-workspace-root .dag-inspector.is-expanded { width: calc(100% - 32px); }
5273
+ .dag-inspector-header .dag-inspector-attempt-bar { position: relative; top: auto; grid-column: 1 / -1; margin: 0; padding: 8px 0 0; border-bottom: 0; border-top: 1px solid var(--hairline); }
5274
+ .process-timeline-toolbar { position: sticky; top: -16px; z-index: 5; padding: 8px 0; background: var(--surface); border-bottom: 1px solid var(--hairline); }
5275
+ .process-timeline-search { flex: 1 1 160px; min-width: 100px; padding: 6px 8px; border: 1px solid var(--hairline-strong); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); }
5276
+ .process-timeline-item.is-search-match { background: color-mix(in srgb, var(--amber) 12%, var(--surface)); border-radius: var(--radius-sm); }
5277
+ .dag-inspector.is-expanded .inspector-page-lead { max-width: none; }
5278
+ .dag-inspector-content .process-timeline-code, .dag-inspector-content .process-timeline-thinking-body { max-height: none; overflow-y: visible; }
5279
+ @media (max-width: 700px) {
5280
+ .dag-inspector.is-expanded { width: calc(100vw - 24px); }
5281
+ .dag-inspector-controls { flex-wrap: wrap; }
5282
+ .dag-inspector-header { padding: 12px; }
5283
+ .dag-inspector-purpose, .dag-inspector-attempt-label { display: none; }
5284
+ .dag-inspector-attempt-preview { font-size: 11px; }
5285
+ }
5286
+
5287
+ .dag-action-intents, .node-relation-links { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
5288
+ .node-relation-links h5 { width:100%; margin:8px 0 0; }
5289
+ .dag-action-intents [aria-pressed="true"] { border-color:var(--orange); color:var(--orange-active); }
5290
+ .dag-action-card[hidden] { display:none; }
5291
+ .context-component { padding:10px 0; border-top:1px solid var(--hairline); }
5292
+ .context-component > summary { cursor:pointer; overflow-wrap:anywhere; }
5293
+ .context-component-body { margin-top:12px; }
5294
+ .context-record-heading { margin:0; color:var(--ink); font-weight:650; }
5295
+ .context-record-meta { margin:0; color:var(--muted); font-size:12px; line-height:1.6; overflow-wrap:anywhere; }
5296
+ .context-component > summary { display:list-item; color:var(--ink); }
5297
+ .context-component-name { font-weight:600; }
5298
+ .context-component-meta { display:block; margin:4px 0 0 16px; color:var(--muted); font-size:11px; font-weight:400; }
5299
+ .context-source-details { margin-top:12px; color:var(--muted); font-size:12px; }
5300
+ .context-source-details > summary { cursor:pointer; }
5301
+ .context-source-details .node-transparency-summary { margin-top:12px; }
5302
+ .inspector-operation { padding:8px 12px; border:1px solid var(--hairline); border-radius:var(--radius-sm); margin-bottom:8px; overflow-wrap:anywhere; }
5303
+ .dag-graph-toolbar { flex-wrap:wrap; gap:8px; }
5304
+ .dag-graph-search, .relation-task-picker { padding:6px 8px; border:1px solid var(--hairline); border-radius:var(--radius-sm); background:var(--surface); max-width:100%; }
5305
+ #view-dashboard { display:flex; flex-direction:column; }
5306
+ #view-dashboard[hidden] { display:none; }
5307
+ #view-dashboard .dashboard-workspace { order:-2; }
5308
+ #dashboard-kpi { padding-top:8px; padding-bottom:8px; }
5309
+ #dashboard-kpi .kpi-card { padding-top:12px; padding-bottom:12px; }
5310
+ #dashboard-kpi .kpi-value { font-size:24px; }
5311
+
5312
+ .attempt-comparison-toolbar { display:flex; gap:12px; padding-bottom:12px; }
5313
+ .attempt-comparison-columns { display:grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap:16px; }
5314
+ .attempt-comparison-columns > p { grid-column:1/-1; }
5315
+ .attempt-comparison-column { min-width:0; overflow-wrap:anywhere; }
5316
+ @media (max-width:700px) { .attempt-comparison-columns { grid-template-columns:minmax(0,1fr); } }
5317
+
5318
+ /* Compact controls share the existing palette and keep names available on focus. */
5319
+ .observe-icon-button,
5320
+ .dag-graph-toolbar .observe-icon-button {
5321
+ display: inline-grid;
5322
+ place-items: center;
5323
+ flex: 0 0 32px;
5324
+ width: 32px;
5325
+ min-width: 32px;
5326
+ height: 32px;
5327
+ min-height: 32px;
5328
+ padding: 0;
5329
+ border: 1px solid transparent;
5330
+ border-radius: 7px;
5331
+ background: transparent;
5332
+ color: var(--body);
5333
+ font-size: 17px;
5334
+ line-height: 1;
5335
+ box-shadow: none;
5336
+ cursor: pointer;
5337
+ }
5338
+ .observe-icon-button:hover:not(:disabled),
5339
+ .observe-icon-button[aria-pressed="true"] {
5340
+ background: var(--surface-muted);
5341
+ color: var(--ink);
5342
+ border-color: var(--hairline);
5343
+ transform: none;
5344
+ }
5345
+ .observe-icon-button:focus-visible,
5346
+ .observe-select:focus-visible {
5347
+ outline: 2px solid var(--orange);
5348
+ outline-offset: 2px;
5349
+ }
5350
+ .observe-icon-button:disabled { opacity: .35; cursor: default; }
5351
+ .observe-select {
5352
+ min-height: 32px;
5353
+ max-width: 100%;
5354
+ padding: 4px 24px 4px 10px;
5355
+ border: 1px solid var(--hairline);
5356
+ border-radius: 7px;
5357
+ background: var(--surface);
5358
+ color: var(--ink);
5359
+ font: inherit;
5360
+ font-size: 12px;
5361
+ cursor: pointer;
5362
+ }
5363
+ .dag-inspector-header { gap: 10px; padding: 12px 16px; }
5364
+ .dag-inspector-title { gap: 3px; }
5365
+ .dag-inspector-purpose { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5366
+ .dag-inspector-controls { gap: 3px; }
5367
+ .dag-inspector-header .dag-inspector-attempt-bar { gap: 4px; padding-top: 8px; }
5368
+ .dag-inspector-attempt-label { display: none; }
5369
+ .dag-inspector-attempt-preview { flex: 1; gap: 8px; min-width: 0; }
5370
+ .dag-inspector-attempt-category { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
5371
+ .process-timeline-toolbar { gap: 4px; align-items: center; flex-shrink: 0; }
5372
+ .process-timeline-filter-group { flex: 0 0 auto; max-width: 135px; }
5373
+ .process-timeline-search { margin: 0 4px; height: 32px; flex: 1 1 120px; }
5374
+ .process-timeline-toolbar > [role="status"] { font-size: 11px; white-space: nowrap; }
5375
+ .dag-graph-toolbar { gap: 4px; }
5376
+ .dag-graph-toolbar-left { margin-right: auto; }
5377
+ .dag-graph-search { height: 32px; width: 160px; margin-left: 8px; }
5378
+ @media (max-width: 700px) {
5379
+ .dag-inspector-header { padding: 10px 12px; }
5380
+ .dag-inspector-controls { flex-wrap: nowrap; }
5381
+ .dag-inspector-status { padding: 2px 6px; }
5382
+ .dag-inspector-attempt-preview { flex-basis: 100%; padding-left: 36px; }
5383
+ .process-timeline-search { flex-basis: 96px; min-width: 76px; }
5384
+ .dag-graph-toolbar-left { flex: 1 1 100%; }
5385
+ .dag-graph-search { flex: 1; min-width: 96px; }
5386
+ }
5387
+ .observe-icon-button { position: relative; }
5388
+ .observe-icon-button::after {
5389
+ content: attr(data-tooltip);
5390
+ position: absolute;
5391
+ right: 0;
5392
+ top: calc(100% + 6px);
5393
+ z-index: 30;
5394
+ padding: 6px 8px;
5395
+ border-radius: 5px;
5396
+ background: var(--ink);
5397
+ color: var(--surface);
5398
+ font: 11px/1.4 system-ui, sans-serif;
5399
+ white-space: nowrap;
5400
+ opacity: 0;
5401
+ pointer-events: none;
5402
+ }
5403
+ .observe-icon-button:focus-visible::after { opacity: 1; }
5404
+ .risk-dag-items { display: grid; gap: 6px; padding-top: 8px; }
5405
+ /* The hosted Console also has .panel styles; keep the reader shell flush. */
5406
+ #dag-inspector { padding: 0; gap: 0; }
5407
+ .dag-inspector-header .dag-inspector-attempt-bar { flex-wrap: wrap; gap: 8px; background: transparent; }
5408
+ .dag-inspector-header .dag-inspector-attempt-select { min-width: 112px; flex: 0 0 126px; }
5409
+ .dag-inspector-header .dag-inspector-attempt-preview { flex: 1 1 100px; flex-wrap: wrap; padding: 0; }
5410
+ .dag-inspector-header .dag-inspector-attempt-category { display: inline; white-space: normal; overflow-wrap: anywhere; }
5411
+ .dag-inspector-header .dag-inspector-attempt-duration { display: none; }
5412
+ @container (max-width: 560px) {
5413
+ .process-timeline-toolbar { display: grid; grid-template-columns: minmax(80px, 1fr) 32px 32px 32px; gap: 4px; }
5414
+ .process-timeline-filter-group { grid-row: 1; grid-column: 1 / 3; width: fit-content; }
5415
+ .process-timeline-errors { grid-row: 1; grid-column: 3; }
5416
+ .process-timeline-protocol-toggle { grid-row: 1; grid-column: 4; }
5417
+ .process-timeline-search { grid-row: 2; grid-column: 1 / 3; width: 100%; margin: 0; }
5418
+ .process-search-previous { grid-row: 2; grid-column: 3; }
5419
+ .process-search-next { grid-row: 2; grid-column: 4; }
5420
+ .process-timeline-toolbar > [role="status"] { grid-row: 3; grid-column: 1 / -1; }
5421
+ .process-timeline-toolbar > [role="status"]:empty { display: none; }
5422
+ }
5423
+
5424
+ .process-search-highlight { background: #ffe082; color: #382800; border-radius: 2px; }
5425
+ .process-timeline-item.is-search-current { outline: 2px solid var(--amber); outline-offset: -2px; }
@@ -0,0 +1,4 @@
1
+ export const TASK_FAILURE_UNKNOWN_LABEL: string;
2
+
3
+ export function taskFailureCategoryLabel(category: unknown): string;
4
+ export function followUpLabel(action: unknown): string;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * 异常 Task 页业务文案映射(Observe UI,2026-09-06-observe-reading-and-polish)。
3
+ *
4
+ * 表驱动映射:Task 级 failureCategory(ProductLineFailureCategory)与
5
+ * 建议跟进(FOLLOW_UP_BY_CATEGORY / DAG_RECOVERY_ACTIONS)仅保留 raw token
6
+ * 于 title 等诊断属性,主文案一律中文业务术语。未知或缺失分类 fail-closed
7
+ * 原文透传(异常页是排障入口,未收录 token 比误译更有诊断价值)。
8
+ */
9
+
10
+ /** 表一:Task 失败分类(src/worker/pool/types.ts ProductLineFailureCategory)→ 中文。 */
11
+ const TASK_FAILURE_CATEGORY_LABELS = {
12
+ SpecUnclear: "需求不明确",
13
+ ContractMismatch: "契约不一致",
14
+ ProductBug: "产品缺陷",
15
+ TestBug: "测试缺陷",
16
+ EnvFailure: "环境问题",
17
+ FlakyTest: "测试不稳定",
18
+ RiskyChange: "变更风险",
19
+ DependencyFailure: "依赖阻塞",
20
+ NeedsHuman: "需要人工",
21
+ Unknown: "未分类",
22
+ // run 级补充(read-model:state.json controller-interrupted)。
23
+ "controller-interrupted": "控制器中断",
24
+ };
25
+
26
+ /**
27
+ * 表二:建议跟进/恢复动作 → 中文。并集覆盖
28
+ * src/worker/pool/failure-routing.ts FOLLOW_UP_BY_CATEGORY 与
29
+ * src/workflows/dag/recovery-recommendation.ts DAG_RECOVERY_ACTIONS。
30
+ */
31
+ const FOLLOW_UP_LABELS = {
32
+ "spec-clarification": "澄清需求",
33
+ "architecture-contract-fix": "修订契约",
34
+ "dev-fix": "开发修复",
35
+ "qa-fix-test": "修复测试",
36
+ "env-fix": "修复环境",
37
+ "flaky-test-analysis": "分析不稳定测试",
38
+ "human-review": "人工审查",
39
+ "manual-review": "人工审查",
40
+ "unblock-dependency": "解除依赖阻塞",
41
+ "human-triage": "人工分诊",
42
+ none: "无需处理",
43
+ monitor: "持续观察",
44
+ "retry-node": "重试节点",
45
+ "rerun-after-fix": "修复后重跑",
46
+ "resume-or-reject": "恢复或拒绝",
47
+ "inspect-upstream": "检查上游",
48
+ };
49
+
50
+ export const TASK_FAILURE_UNKNOWN_LABEL = "未分类";
51
+
52
+ /**
53
+ * Task 失败分类 → 中文;未知英文 token 原样透传(title 仍可标注 raw),
54
+ * 空值返回空串由调用方决定占位。
55
+ */
56
+ export function taskFailureCategoryLabel(category) {
57
+ const raw = String(category ?? "").trim();
58
+ if (!raw) return "";
59
+ return TASK_FAILURE_CATEGORY_LABELS[raw] ?? raw;
60
+ }
61
+
62
+ /** 建议跟进/恢复动作 → 中文;未知 token 原样透传,空值返回空串。 */
63
+ export function followUpLabel(action) {
64
+ const raw = String(action ?? "").trim();
65
+ if (!raw) return "";
66
+ return FOLLOW_UP_LABELS[raw] ?? raw;
67
+ }
@@ -0,0 +1,12 @@
1
+ /** Merge poll/older pages without dropping the pages the reader loaded. */
2
+ export function mergeTaskHistoryPage(state, page, older = false) {
3
+ const existing = new Map(state.runs.map((run) => [run.workerRunId, run]));
4
+ for (const run of page.runs ?? []) existing.set(run.workerRunId, run);
5
+ const order = older ? [...state.runs, ...(page.runs ?? [])] : [...(page.runs ?? []), ...state.runs];
6
+ const seen = new Set();
7
+ state.runs = order.filter((run) => { if (seen.has(run.workerRunId)) return false; seen.add(run.workerRunId); return true; }).map((run) => existing.get(run.workerRunId));
8
+ if (older || !state.loadedOlder) state.nextBefore = page.nextBefore ?? null;
9
+ if (older) state.loadedOlder = true;
10
+ state.error = null;
11
+ return state;
12
+ }
@@ -1,3 +1,4 @@
1
+ import { bindManualViewRefresh, mountTaskRelationPicker } from "../shell-chrome.js";
1
2
  /** Observe UI view module (R5). */
2
3
  import {
3
4
  UI_TEXT,
@@ -19,6 +20,7 @@ import {
19
20
  import { clearNode, el, metaGrid, buildTable } from "../dom.js";
20
21
  import {
21
22
  badge,
23
+ batchStatusBadge,
22
24
  badgeClass,
23
25
  statusLabel,
24
26
  formatTs,
@@ -71,6 +73,7 @@ import { mountBoundedEventTimeline } from "./timeline.js";
71
73
 
72
74
  export async function renderBatch(batchRunId) {
73
75
  showView("batch");
76
+ bindManualViewRefresh("batch", () => renderBatch(batchRunId));
74
77
  setBreadcrumb([
75
78
  { label: UI_TEXT.dashboard, href: "#/" },
76
79
  { label: batchRunId },
@@ -88,7 +91,7 @@ export async function renderBatch(batchRunId) {
88
91
  ]);
89
92
  if (snapshot) {
90
93
  uiState.lastSnapshot = snapshot;
91
- updateHeaderRefresh(snapshot.generatedAt);
94
+ updateHeaderRefresh(snapshot.generatedAt, { mode: "manual" });
92
95
  }
93
96
  if (!batch) {
94
97
  clearNode(metaEl);
@@ -100,17 +103,7 @@ export async function renderBatch(batchRunId) {
100
103
  return;
101
104
  }
102
105
 
103
- mountRelationBar(
104
- "batch-relations",
105
- {
106
- featureId: batch.featureId,
107
- batchRunId: batch.batchRunId,
108
- taskId: batch.tasks?.[0]?.taskId,
109
- workerRunId: batch.tasks?.[0]?.workerRunId,
110
- dagRunId: batch.tasks?.[0]?.dagRunId,
111
- },
112
- snapshot,
113
- );
106
+ mountTaskRelationPicker("batch-relations", { featureId: batch.featureId, batchRunId: batch.batchRunId }, (batch.tasks ?? []).map((task) => ({ ...task, featureId: task.featureId ?? batch.featureId })), snapshot);
114
107
 
115
108
  const totalDerived = derivedDurationMs(batch.startedAt, batch.finishedAt);
116
109
  clearNode(metaEl);
@@ -123,7 +116,7 @@ export async function renderBatch(batchRunId) {
123
116
  ? objectRouteLink("feature", batch.featureId, snapshot)
124
117
  : el("span", "muted", "—"),
125
118
  ],
126
- ["状态", badge(batch.status)],
119
+ ["状态", batchStatusBadge(batch)],
127
120
  ["开始时间", formatTs(batch.startedAt)],
128
121
  ["结束时间", formatTs(batch.finishedAt)],
129
122
  ["总耗时", formatDuration(totalDerived)],
@@ -6,7 +6,7 @@ import {
6
6
  edgeCollidesNonEndpointNodes,
7
7
  routeControlEdge,
8
8
  } from "../dag-edge-routing.js";
9
- import { el } from "../dom.js";
9
+ import { el, iconButton } from "../dom.js";
10
10
  import {
11
11
  statusLabel,
12
12
  statusSymbol,
@@ -23,7 +23,7 @@ import {
23
23
  currentDagGraphViewMode,
24
24
  } from "../state.js";
25
25
  import { describeDagNodePurpose } from "../dag-node-purpose.js";
26
- import { selectDagNode } from "./dag-inspector.js";
26
+ import { selectDagNode, selectDagExecution } from "./dag-inspector.js";
27
27
  import { renderTrajectoryStoryboard } from "./dag-trajectory.js";
28
28
 
29
29
  export function svgEl(tag, attrs = {}) {
@@ -138,6 +138,10 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
138
138
  controlLaneCount,
139
139
  });
140
140
  const wrap = existingWrap ?? el("div", "dag-graph-wrap");
141
+ const active = document.activeElement;
142
+ const focusedNodeId = existingWrap?.contains?.(active) ? active?.getAttribute?.("data-node-id") : null;
143
+ const focusedAction = existingWrap?.contains?.(active) ? active?.dataset?.graphAction : null;
144
+ const previousSearch = existingWrap?.querySelector(".dag-graph-search")?.value ?? "";
141
145
  // Preserve .dag-graph-viewport DOM identity across poll refreshes so the
142
146
  // user's active scroll/pointer target is not destroyed mid-gesture.
143
147
  let viewport =
@@ -169,7 +173,7 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
169
173
  }
170
174
  toolbarLeft.appendChild(el("span", "dag-graph-summary", summaryText));
171
175
  toolbar.appendChild(toolbarLeft);
172
- const reset = el("button", "dag-graph-reset", "重置视图");
176
+ const reset = iconButton("重置视图", "ri-restart-line", "dag-graph-reset");
173
177
  toolbar.appendChild(reset);
174
178
  const appendBeforeViewport = (child) => {
175
179
  if (viewport?.parentNode === wrap) wrap.insertBefore(child, viewport);
@@ -339,6 +343,7 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
339
343
  const purpose = describeDagNodePurpose(node.nodeId, node);
340
344
  const group = svgEl("g", {
341
345
  transform: `translate(${position.x} ${position.y})`,
346
+ "data-node-id": node.nodeId,
342
347
  class: `dag-graph-node status-${status}${node.nodeId === uiState.selectedDagNodeId ? " selected" : ""}${uiState.selectedDagNodeId && !related.has(node.nodeId) ? " dag-dimmed" : ""}`,
343
348
  tabindex: 0,
344
349
  role: "button",
@@ -402,6 +407,9 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
402
407
  });
403
408
  svg.appendChild(group);
404
409
  }
410
+ const scale = Number(viewport.dataset.scale) || 1;
411
+ svg.style.width = `${Number(svg.getAttribute("width")) * scale}px`;
412
+ svg.style.height = `${Number(svg.getAttribute("height")) * scale}px`;
405
413
  viewport.appendChild(svg);
406
414
  }
407
415
  if (viewport.parentNode !== wrap) wrap.appendChild(viewport);
@@ -476,9 +484,48 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
476
484
  }
477
485
  }
478
486
  }
487
+ const findNode = (id) => {
488
+ const matches = [...viewport.querySelectorAll("[data-node-id]")].filter((element) => element.getAttribute("data-node-id") === id);
489
+ const key = uiState.dagInspectorAttempt?.nodeId === id ? uiState.dagInspectorAttempt.key : null;
490
+ return matches.find((element) => key && element.getAttribute("data-execution-key") === key) ?? matches.at(-1);
491
+ };
492
+ const center = (id, executionId = null) => {
493
+ const target = executionId ? [...viewport.querySelectorAll("[data-execution-id]")].find((item) => item.getAttribute("data-execution-id") === executionId) : findNode(id);
494
+ if (!target) return;
495
+ const rect = target.getBoundingClientRect(), container = viewport.getBoundingClientRect();
496
+ viewport.scrollTo({ left: viewport.scrollLeft + rect.left - container.left - (container.width - rect.width) / 2, top: viewport.scrollTop + rect.top - container.top - (container.height - rect.height) / 2, behavior: "smooth" });
497
+ };
498
+ const addAction = (id, label, action) => { const icons = { selected: "ri-focus-3-line", running: "ri-play-circle-line", failed: "ri-error-warning-line", fit: "ri-fullscreen-line" }; const button = iconButton(label, icons[id], "dag-graph-reset"); button.type = "button"; button.dataset.graphAction = id; button.addEventListener("click", action); toolbar.appendChild(button); return button; };
499
+ addAction("selected", "定位选中", () => center(uiState.selectedDagNodeId)).disabled = !findNode(uiState.selectedDagNodeId);
500
+ addAction("running", "定位运行中", () => { const node = nodes.find((item) => String(item.status).toLowerCase() === "running"); if (node) center(node.nodeId); }).disabled = !nodes.some((item) => String(item.status).toLowerCase() === "running");
501
+ const failedNodes = (mode === "trajectory" ? dag.executionTrajectory?.occurrences ?? [] : nodes).filter((item) => ["error", "failed"].includes(String(item.status).toLowerCase()));
502
+ const failed = addAction("failed", `下个失败 (${failedNodes.length})`, () => {
503
+ if (!failedNodes.length) return;
504
+ const index = failedNodes.findIndex((item) => item.nodeId === uiState.selectedDagNodeId && (mode !== "trajectory" || (item.pass ? `pass-${item.pass}` : null) === (uiState.dagInspectorAttempt?.key ?? null)));
505
+ const node = failedNodes[(index + 1) % failedNodes.length];
506
+ if (mode === "trajectory") selectDagExecution(dagRunId, node.nodeId, node.pass ? `pass-${node.pass}` : null);
507
+ else selectDagNode(dagRunId, node.nodeId);
508
+ requestAnimationFrame(() => center(node.nodeId, node.executionId));
509
+ });
510
+ failed.disabled = !failedNodes.some((node) => findNode(node.nodeId));
511
+ const fit = addAction("fit", "适应画布", () => {
512
+ const svg = viewport.querySelector("svg.dag-graph"); if (!svg) return;
513
+ const width = Number(svg.getAttribute("width")), height = Number(svg.getAttribute("height"));
514
+ const scale = Math.min(1, (viewport.clientWidth - 24) / width, (viewport.clientHeight - 24) / height);
515
+ if (!(scale > 0)) return;
516
+ viewport.dataset.scale = String(scale); svg.style.width = `${width * scale}px`; svg.style.height = `${height * scale}px`; viewport.scrollTo({ left: 0, top: 0 });
517
+ });
518
+ fit.disabled = mode !== "topology";
519
+ const search = el("input", "dag-graph-search"); search.type = "search"; search.placeholder = "查找节点…"; search.value = previousSearch; search.setAttribute("aria-label", "查找流程节点"); search.dataset.graphAction = "search";
520
+ search.addEventListener("keydown", (event) => { if (event.key !== "Enter" || event.isComposing) return; event.preventDefault(); const matches = nodes.filter((item) => `${item.nodeId} ${describeDagNodePurpose(item.nodeId, item)}`.toLocaleLowerCase().includes(search.value.trim().toLocaleLowerCase())); if (!matches.length) return; const index = matches.findIndex((item) => item.nodeId === uiState.selectedDagNodeId); const node = matches[(index + 1) % matches.length]; center(node.nodeId); selectDagNode(dagRunId, node.nodeId); }); toolbar.appendChild(search);
521
+ if (focusedNodeId) findNode(focusedNodeId)?.focus?.({ preventScroll: true });
522
+ else if (focusedAction) [...toolbar.querySelectorAll("[data-graph-action]")].find((item) => item.dataset.graphAction === focusedAction)?.focus?.({ preventScroll: true });
479
523
  // Toolbar is rebuilt each paint; bind reset to the current viewport/dagRunId.
480
524
  // 重置视图只重置当前模式的 viewport:不切换模式、不重置另一模式、不关闭 Inspector。
481
525
  reset.addEventListener("click", () => {
526
+ viewport.dataset.scale = "1";
527
+ const svg = viewport.querySelector("svg.dag-graph");
528
+ if (svg) { svg.style.width = `${svg.getAttribute("width")}px`; svg.style.height = `${svg.getAttribute("height")}px`; }
482
529
  if (mode === "trajectory") {
483
530
  uiState.dagTrajectoryViewportState = updateDagTrajectoryViewportState(
484
531
  uiState.dagTrajectoryViewportState,