@tea-agent/loop-agent 0.42.0-next.9 → 0.42.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 (236) hide show
  1. package/CHANGELOG.md +111 -45
  2. package/dist/application/dag/run-dag.js +8 -2
  3. package/dist/application/evaluation/budget.js +19 -1
  4. package/dist/application/task-lifecycle/advance.js +26 -8
  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/cli/program.js +1 -1
  9. package/dist/commands/dag-rerun-task.js +2 -0
  10. package/dist/commands/task-source-prepare.js +3 -1
  11. package/dist/executors/dag-pi-executor.js +2584 -600
  12. package/dist/executors/pi-executor.js +22 -1
  13. package/dist/executors/pi-extension-resolver.js +14 -2
  14. package/dist/executors/pi-sdk-executor.js +140 -39
  15. package/dist/executors/shell-executor.js +135 -55
  16. package/dist/shared/dag-failure-category.js +6 -0
  17. package/dist/shared/frontend-execution-policy.js +23 -0
  18. package/dist/task/config-types.js +4 -0
  19. package/dist/task/contract/apply.js +36 -2
  20. package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
  21. package/dist/task/source-prepare/ledger-review.js +6 -9
  22. package/dist/task/source-prepare/parse-intent.js +7 -0
  23. package/dist/task/source-prepare/semantic-intake.js +16 -26
  24. package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
  25. package/dist/task/source-references.js +48 -23
  26. package/dist/worker/console/chat/assistant-content.js +23 -2
  27. package/dist/worker/console/chat/browser-policy.js +143 -0
  28. package/dist/worker/console/chat/browser-routes.js +148 -0
  29. package/dist/worker/console/chat/chat-event-store.js +4 -2
  30. package/dist/worker/console/chat/explore-tools.js +13 -0
  31. package/dist/worker/console/chat/pi-runtime.js +173 -94
  32. package/dist/worker/console/chat/resource-loader.js +4 -1
  33. package/dist/worker/console/chat/routes.js +208 -66
  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 +78 -11
  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 +27 -7
  42. package/dist/worker/console/chat/shortcuts.js +6 -0
  43. package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
  44. package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
  45. package/dist/worker/console/chat/subagents/index.js +5 -0
  46. package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
  47. package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
  48. package/dist/worker/console/chat/subagents/types.js +13 -0
  49. package/dist/worker/console/chat/terminal-routes.js +216 -0
  50. package/dist/worker/console/chat/terminal-sessions.js +284 -0
  51. package/dist/worker/console/chat/terminal-tools.js +199 -0
  52. package/dist/worker/console/chat/tool-preview.js +21 -0
  53. package/dist/worker/console/chat/tools.js +13 -1
  54. package/dist/worker/console/chat/turn-process.js +1 -0
  55. package/dist/worker/console/interview/tools.js +1 -0
  56. package/dist/worker/console/server.js +2 -28
  57. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
  58. package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-VJWsxWhB.js} +1 -1
  59. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
  60. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
  61. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
  62. package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
  63. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
  73. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
  74. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
  75. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
  76. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
  77. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
  78. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
  80. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
  81. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
  82. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
  83. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
  84. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
  85. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
  86. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
  87. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
  88. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
  89. package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
  90. package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
  91. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
  92. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
  93. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
  94. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
  95. package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-G1JR40SX.js} +1 -1
  96. package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-CCgPF0oA.js} +5 -5
  97. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
  98. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
  99. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
  100. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
  101. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
  102. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
  103. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
  104. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
  105. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
  106. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
  107. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
  108. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
  109. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
  110. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
  111. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
  112. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
  113. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
  114. package/dist/worker/console/static/index.html +2 -2
  115. package/dist/worker/console/static-src/chat-view-types.js +1 -1
  116. package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
  117. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +142 -7
  118. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +44 -0
  119. package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
  120. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
  121. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
  122. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
  123. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
  124. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +33 -18
  125. package/dist/worker/console/static-src/operator-chat/useChatStream.js +48 -16
  126. package/dist/worker/console/static-src/operator-chat/useChatThread.js +170 -7
  127. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
  128. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
  129. package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
  130. package/dist/worker/console/workspace-context.js +15 -1
  131. package/dist/worker/observe/node-transparency.js +81 -72
  132. package/dist/worker/observe/routes.js +20 -1
  133. package/dist/worker/observe/static/constants.js +22 -22
  134. package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
  135. package/dist/worker/observe/static/dag-history-labels.js +1 -0
  136. package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
  137. package/dist/worker/observe/static/dag-inspector-humanize.js +342 -0
  138. package/dist/worker/observe/static/dag-node-purpose.js +10 -10
  139. package/dist/worker/observe/static/dom.js +20 -1
  140. package/dist/worker/observe/static/format-pool.d.ts +2 -0
  141. package/dist/worker/observe/static/format-pool.js +6 -0
  142. package/dist/worker/observe/static/format.js +7 -0
  143. package/dist/worker/observe/static/index.html +4 -4
  144. package/dist/worker/observe/static/inspect-workspace.js +34 -7
  145. package/dist/worker/observe/static/inspector-submission.js +32 -0
  146. package/dist/worker/observe/static/kpi.js +1 -0
  147. package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
  148. package/dist/worker/observe/static/relations.js +7 -5
  149. package/dist/worker/observe/static/router.js +13 -0
  150. package/dist/worker/observe/static/run-processing.js +2 -0
  151. package/dist/worker/observe/static/shell-chrome.js +36 -3
  152. package/dist/worker/observe/static/state.js +35 -2
  153. package/dist/worker/observe/static/styles.css +431 -39
  154. package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
  155. package/dist/worker/observe/static/task-failure-labels.js +67 -0
  156. package/dist/worker/observe/static/task-history.js +12 -0
  157. package/dist/worker/observe/static/views/batch.js +6 -13
  158. package/dist/worker/observe/static/views/dag-graph.js +51 -4
  159. package/dist/worker/observe/static/views/dag-inspector.js +1069 -426
  160. package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
  161. package/dist/worker/observe/static/views/dag.d.ts +6 -0
  162. package/dist/worker/observe/static/views/dag.js +64 -22
  163. package/dist/worker/observe/static/views/dags.js +2 -0
  164. package/dist/worker/observe/static/views/dashboard.js +21 -12
  165. package/dist/worker/observe/static/views/failures.js +21 -11
  166. package/dist/worker/observe/static/views/feature.js +11 -29
  167. package/dist/worker/observe/static/views/pool.js +37 -28
  168. package/dist/worker/observe/static/views/run.js +48 -5
  169. package/dist/worker/observe/static/views/session-timeline.js +194 -243
  170. package/dist/worker/observe/static/views/task.js +81 -62
  171. package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
  172. package/dist/workflows/dag/budget-enforcement.js +53 -3
  173. package/dist/workflows/dag/dag-retry-schema.js +3 -0
  174. package/dist/workflows/dag/frontend-capacity.js +9 -0
  175. package/dist/workflows/dag/frontend-contract-facts.js +130 -0
  176. package/dist/workflows/dag/frontend-design-policy.js +103 -17
  177. package/dist/workflows/dag/frontend-durable-tools.js +193 -0
  178. package/dist/workflows/dag/frontend-execution-groups.js +24 -0
  179. package/dist/workflows/dag/frontend-implementation-contract.js +423 -41
  180. package/dist/workflows/dag/frontend-input-projection.js +76 -0
  181. package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
  182. package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
  183. package/dist/workflows/dag/frontend-plan-render.js +21 -3
  184. package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
  185. package/dist/workflows/dag/frontend-recovery-controller.js +32 -8
  186. package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
  187. package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
  188. package/dist/workflows/dag/frontend-recovery-run.js +146 -16
  189. package/dist/workflows/dag/frontend-review-scopes.js +139 -0
  190. package/dist/workflows/dag/frontend-risk.js +2 -0
  191. package/dist/workflows/dag/frontend-session-budget.js +249 -0
  192. package/dist/workflows/dag/frontend-shadow-dual-write.js +37 -3
  193. package/dist/workflows/dag/frontend-shape-facts.js +12 -2
  194. package/dist/workflows/dag/frontend-shape.js +46 -7
  195. package/dist/workflows/dag/frontend-test-execution-evidence.js +49 -0
  196. package/dist/workflows/dag/frontend-typed-event-store.js +14 -0
  197. package/dist/workflows/dag/frontend-verification-trace.js +38 -4
  198. package/dist/workflows/dag/frontend-writer-admission.js +2 -2
  199. package/dist/workflows/dag/init-hybrid.js +70 -35
  200. package/dist/workflows/dag/node-execution.js +225 -226
  201. package/dist/workflows/dag/prompt.js +4 -0
  202. package/dist/workflows/dag/recovery-lease.js +106 -16
  203. package/dist/workflows/dag/rerun-feedback.js +1 -0
  204. package/dist/workflows/dag/rerun-plan.js +96 -4
  205. package/dist/workflows/dag/rerun-run.js +28 -6
  206. package/dist/workflows/dag/rerun-task.js +221 -18
  207. package/dist/workflows/dag/retry-policy.js +27 -10
  208. package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
  209. package/dist/workflows/dag/runner.js +246 -19
  210. package/dist/workflows/dag/structured-output-repair.js +4 -1
  211. package/dist/workflows/dag/types.js +10 -3
  212. package/dist/workflows/dag/validate.js +10 -8
  213. package/dist/workflows/dag/workspace-checkpoint.js +66 -0
  214. package/docs/operations/README.md +2 -0
  215. package/docs/templates/agent-dag.schema.json +2 -2
  216. package/docs/templates/backend-test-dag.json +6 -4
  217. package/docs/templates/frontend-design-contract.md +4 -4
  218. package/docs/templates/frontend-implementation-contract.schema.json +68 -4
  219. package/docs/templates/frontend-implementation-dag.json +5 -5
  220. package/harness.json +1 -1
  221. package/package.json +8 -6
  222. package/skills/frontend-contract/SKILL.md +2 -1
  223. package/skills/frontend-contract/references/contract-protocol.md +19 -3
  224. package/skills/frontend-design-review/SKILL.md +12 -11
  225. package/skills/frontend-plan/SKILL.md +22 -2
  226. package/skills/frontend-plan/references/decision-contract.md +114 -5
  227. package/skills/frontend-plan/references/design-decisions.md +32 -0
  228. package/skills/frontend-review/SKILL.md +10 -11
  229. package/skills/frontend-scout/references/scout-evidence.md +4 -0
  230. package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
  231. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
  232. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
  233. package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
  234. package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
  235. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
  236. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
@@ -100,6 +100,9 @@ function buildOccurrenceCard(dagRunId, occurrence) {
100
100
  false;
101
101
  const card = document.createElement("button");
102
102
  card.type = "button";
103
+ card.setAttribute("data-node-id", occurrence.nodeId);
104
+ card.setAttribute("data-execution-id", occurrence.executionId);
105
+ card.setAttribute("data-execution-key", occurrence.pass ? `pass-${occurrence.pass}` : "");
103
106
  const extraClasses = [
104
107
  recovered ? "is-recovered" : "",
105
108
  occurrence.availability === "partial" ? "is-partial" : "",
@@ -11,3 +11,9 @@ export function resolveNodeRemark(node: {
11
11
  nodeId: string;
12
12
  rank?: string | number | null;
13
13
  } | null | undefined): DagNodeRemark;
14
+
15
+ /** 面包屑次级:可读标题优先(截断),缺失/失败回退 run ID。 */
16
+ export function dagBreadcrumbLabel(
17
+ dag: { title?: string | null } | null | undefined,
18
+ dagRunId: string,
19
+ ): string;
@@ -43,6 +43,8 @@ import {
43
43
  } from "../router.js";
44
44
  import {
45
45
  fetchJson,
46
+ fetchJsonResult,
47
+ summarizeFetchError,
46
48
  artifactUrl,
47
49
  parseArtifactPreviewResponse,
48
50
  } from "../api.js";
@@ -67,7 +69,7 @@ import {
67
69
  mountViewState,
68
70
  makeSessionEventIdentity,
69
71
  } from "../state.js";
70
- import { uiState, dagGraphViewportStates, dagGraphViewModes } from "../state.js";
72
+ import { uiState, dagGraphViewportStates, dagGraphViewModes, inspectRouteStates } from "../state.js";
71
73
  import { sortPoolTasks } from "../format-pool.js";
72
74
  import {
73
75
  dagMetaVisibility,
@@ -76,11 +78,13 @@ import {
76
78
  findDagForRun,
77
79
  } from "../dag-model.js";
78
80
  import { describeDagNodePurpose } from "../dag-node-purpose.js";
81
+ import { inspectorValueLabel } from "../dag-inspector-humanize.js";
79
82
  import { layoutDag } from "../dag-layout.js";
80
83
  import {
81
84
  showView,
82
85
  setBreadcrumb,
83
86
  updateHeaderRefresh,
87
+ bindManualViewRefresh,
84
88
  mountRelationBar,
85
89
  } from "../shell-chrome.js";
86
90
  import {
@@ -197,7 +201,9 @@ export function buildDagNodeTable(dagRunId, nodes) {
197
201
  index + 1,
198
202
  node.nodeId,
199
203
  node.rank ?? "—",
200
- node.executor ?? "—",
204
+ node.executor
205
+ ? inspectorValueLabel("executor", node.executor)
206
+ : "—",
201
207
  node.model ?? "—",
202
208
  node.status ? badge(node.status) : "—",
203
209
  formatMs(node.durationMs),
@@ -211,10 +217,10 @@ export function buildDagNodeTable(dagRunId, nodes) {
211
217
  const nodeTable = buildTable(
212
218
  [
213
219
  "序号",
214
- "节点 ID",
215
- "依赖层",
220
+ "步骤编号",
221
+ "执行层级",
216
222
  "执行方式",
217
- "模型",
223
+ "实际模型",
218
224
  "状态",
219
225
  "耗时",
220
226
  "执行历史",
@@ -235,12 +241,20 @@ export function buildDagNodeTable(dagRunId, nodes) {
235
241
  return nodeTable;
236
242
  }
237
243
 
244
+ /** 面包屑次级:可读标题优先(截断),缺失/失败回退 run ID。 */
245
+ export function dagBreadcrumbLabel(dag, dagRunId) {
246
+ const title = typeof dag?.title === "string" ? dag.title.trim() : "";
247
+ return title ? truncateText(title, 48) : dagRunId;
248
+ }
249
+
238
250
  export async function renderDagDetail(dagRunId, initial = true) {
239
251
  showView("dag");
240
- setBreadcrumb([
252
+ const breadcrumbParts = (label) => [
241
253
  { label: UI_TEXT.dashboard, href: "#/" },
242
- { label: dagRunId },
243
- ]);
254
+ ...(inspectRouteStates.get("return-list") ? [{ label: "返回结果列表", href: inspectRouteStates.get("return-list") }] : []),
255
+ { label },
256
+ ];
257
+ setBreadcrumb(breadcrumbParts(dagRunId));
244
258
 
245
259
  if (initial || uiState.currentDagRunId !== dagRunId) {
246
260
  uiState.currentDagRunId = dagRunId;
@@ -249,7 +263,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
249
263
  uiState.sessionEvents = [];
250
264
  uiState.sessionEventIdentity = null;
251
265
  uiState.dagInspectorOpen = false;
252
- uiState.dagInspectorTab = "output";
266
+ uiState.dagInspectorTab = null;
253
267
  uiState.dagInspectorAttempt = null;
254
268
  uiState.dagGraphViewportState = null;
255
269
  uiState.dagTimelineViewportState = null;
@@ -261,7 +275,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
261
275
  const metaEl = document.getElementById("dag-meta");
262
276
  const ranksEl = document.getElementById("dag-ranks");
263
277
  const nodesEl = document.getElementById("dag-nodes");
264
- if (initial && metaEl) mountViewState(metaEl, "loading", "加载 DAG…");
278
+ if (initial && metaEl) mountViewState(metaEl, "loading", "正在加载运行流程…");
265
279
  uiState.dagGraphViewportState = captureDagGraphViewportState(
266
280
  uiState.dagGraphViewportState,
267
281
  dagRunId,
@@ -271,17 +285,34 @@ export async function renderDagDetail(dagRunId, initial = true) {
271
285
  dagGraphViewportStates.set(dagRunId, uiState.dagGraphViewportState);
272
286
  }
273
287
 
274
- const [dag, snapshot] = await Promise.all([
275
- fetchJson(`/api/dag-runs/${encodeURIComponent(dagRunId)}`),
288
+ const requestGeneration = uiState.pollingGeneration;
289
+ const [dagResult, snapshot] = await Promise.all([
290
+ fetchJsonResult(`/api/dag-runs/${encodeURIComponent(dagRunId)}`),
276
291
  fetchJson("/api/snapshot"),
277
292
  ]);
293
+ if (requestGeneration !== uiState.pollingGeneration || uiState.currentDagRunId !== dagRunId) return false;
294
+ if (!dagResult.ok && dagResult.status !== 404) {
295
+ let error = metaEl.querySelector(".dag-refresh-error");
296
+ if (!error) { error = el("div", "dag-refresh-error"); metaEl.appendChild(error); }
297
+ clearNode(error);
298
+ error.appendChild(el("span", "node-input-error", `更新失败,保留上次内容。${summarizeFetchError(dagResult)}`));
299
+ const retry = el("button", "btn btn-secondary", "重试");
300
+ retry.type = "button";
301
+ retry.addEventListener("click", () => { void renderDagDetail(dagRunId, false); });
302
+ error.appendChild(retry);
303
+ return true;
304
+ }
305
+ metaEl.querySelector(".dag-refresh-error")?.remove();
306
+ const dag = dagResult.ok ? dagResult.body : null;
307
+ if (dag) setBreadcrumb(breadcrumbParts(dagBreadcrumbLabel(dag, dagRunId)));
278
308
  if (snapshot) {
279
309
  uiState.lastSnapshot = snapshot;
280
- updateHeaderRefresh(snapshot.generatedAt);
310
+ updateHeaderRefresh(snapshot.generatedAt, { mode: dag && isDagRunActive(dag) ? "auto" : "ended" });
311
+ bindManualViewRefresh("dag", () => renderDagDetail(dagRunId, false));
281
312
  }
282
313
  if (!dag) {
283
314
  clearNode(metaEl);
284
- mountViewState(metaEl, "error", "找不到该 DAG 运行。");
315
+ mountViewState(metaEl, "error", "找不到这次运行。运行记录可能已被清理。");
285
316
  mountRelationBar("dag-relations", {}, snapshot);
286
317
  closeDagInspector();
287
318
  return false;
@@ -307,6 +338,16 @@ export async function renderDagDetail(dagRunId, initial = true) {
307
338
  );
308
339
 
309
340
  const nodes = dag.nodes ?? [];
341
+ if (initial) {
342
+ const routeState = parseRoute().inspector ?? inspectRouteStates.get(`dag:${dagRunId}`);
343
+ if (routeState?.nodeId && routeState.open !== false && nodes.some((node) => node.nodeId === routeState.nodeId)) {
344
+ uiState.selectedDagNodeId = routeState.nodeId;
345
+ uiState.dagInspectorOpen = true;
346
+ uiState.dagInspectorTab = ["overview", "input", "context", "timeline", "compare", "output", "lineage", "spec-evidence", "prompt-restart"].includes(routeState.tab) ? routeState.tab : null;
347
+ uiState.dagInspectorAttempt = { dagRunId, nodeId: routeState.nodeId, key: routeState.executionKey };
348
+ uiState.sessionEventIdentity = makeSessionEventIdentity(dagRunId, routeState.nodeId);
349
+ }
350
+ }
310
351
  if (
311
352
  uiState.selectedDagNodeId &&
312
353
  !nodes.some((n) => n.nodeId === uiState.selectedDagNodeId)
@@ -327,7 +368,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
327
368
  (n) => (n.status ?? "").toLowerCase() === "skipped",
328
369
  ).length;
329
370
  const succeededCount = nodes.filter(
330
- (n) => isNodeFinished(n.status) && !isNodeFailed(n.status),
371
+ (n) => isNodeFinished(n.status) && !isNodeFailed(n.status) && String(n.status ?? "").toLowerCase() !== "skipped",
331
372
  ).length;
332
373
 
333
374
  // reuse-dag-detail-shell: on a poll refresh (non-initial) we keep the
@@ -354,7 +395,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
354
395
  LIVENESS_LABELS[dag.liveness] ?? dag.liveness ?? LIVENESS_LABELS.unknown;
355
396
  const primaryEntries = [
356
397
  ["标题", dag.title || dag.dagRunId],
357
- ["DAG 运行 ID", dag.dagRunId],
398
+ ["运行编号", dag.dagRunId],
358
399
  ["状态", dagStatusBadge(dag.effectiveStatus ?? dag.status)],
359
400
  ];
360
401
  if (visibility.showExecutorStatus)
@@ -381,9 +422,8 @@ export async function renderDagDetail(dagRunId, initial = true) {
381
422
  ["开始时间", formatTs(dag.startedAt)],
382
423
  ["结束时间", formatTs(dag.finishedAt)],
383
424
  ["总耗时", formatDuration(dag.durationMs)],
384
- ["进度", `${progress.finished}/${progress.total}`],
425
+ ["已结束", `${progress.finished}/${progress.total}`],
385
426
  ["成功/失败/跳过", `${succeededCount}/${failedCount}/${skippedCount}`],
386
- ["DAG 路径", dag.dagPath ?? "—"],
387
427
  );
388
428
  const lintSummary = frontendLintSummary(dag.frontendLint);
389
429
  if (lintSummary) {
@@ -401,6 +441,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
401
441
  ],
402
442
  ["允许恢复", visibility.showResume ? "是" : "否"],
403
443
  ["允许收口", visibility.showReconcile ? "是" : "否"],
444
+ ["记录保存位置", dag.dagPath ?? "—"],
404
445
  ]);
405
446
  if (!initial && existingDetails?.parentNode === metaEl) {
406
447
  const existingPrimary = metaEl.querySelector(":scope > .meta-grid");
@@ -429,7 +470,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
429
470
  );
430
471
  if (nodes.length === 0) {
431
472
  clearNode(ranksEl);
432
- ranksEl.appendChild(el("h3", null, "DAG 依赖图"));
473
+ ranksEl.appendChild(el("h3", null, "执行步骤关系"));
433
474
  ranksEl.appendChild(el("p", "empty", UI_TEXT.noNodes));
434
475
  } else if (reuseShell && existingGraphWrap?.parentNode === ranksEl) {
435
476
  // reuse-dag-graph-wrap + viewport identity: keep wrap/viewport alive.
@@ -440,16 +481,16 @@ export async function renderDagDetail(dagRunId, initial = true) {
440
481
  }
441
482
  } else {
442
483
  clearNode(ranksEl);
443
- ranksEl.appendChild(el("h3", null, "DAG 依赖图"));
484
+ ranksEl.appendChild(el("h3", null, "执行步骤关系"));
444
485
  ranksEl.appendChild(renderDagGraph(dag, dagRunId, null));
445
486
  }
446
487
 
447
488
  const nodesScrollLeft = nodesEl.scrollLeft;
448
489
  clearNode(nodesEl);
449
490
  const nodeHeading = el("div", "dag-section-heading");
450
- nodeHeading.appendChild(el("h3", null, "节点"));
491
+ nodeHeading.appendChild(el("h3", null, "执行步骤"));
451
492
  nodeHeading.appendChild(
452
- el("span", "dag-section-meta", `共 ${nodes.length} 个节点`),
493
+ el("span", "dag-section-meta", `共 ${nodes.length} 个步骤`),
453
494
  );
454
495
  nodesEl.appendChild(nodeHeading);
455
496
 
@@ -479,6 +520,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
479
520
  dagRunId,
480
521
  nodes.find((node) => node.nodeId === uiState.selectedDagNodeId),
481
522
  initial,
523
+ dag,
482
524
  );
483
525
  return isDagRunActive(dag);
484
526
  }
@@ -1,3 +1,4 @@
1
+ import { isPlainNavigationClick, isRowNavigationClick } from "../router.js";
1
2
  /** Observe UI — formal DAG run history page (independent #/dags). */
2
3
  import { UI_TEXT } from "../constants.js";
3
4
  import { el, clearNode } from "../dom.js";
@@ -331,6 +332,7 @@ function buildRow(run) {
331
332
  `/dag/${encodeURIComponent(run.dagRunId)}`,
332
333
  );
333
334
  nameLink.addEventListener("click", (event) => {
335
+ if (!isPlainNavigationClick(event)) return;
334
336
  event.preventDefault();
335
337
  event.stopPropagation();
336
338
  navigate(`/dag/${encodeURIComponent(run.dagRunId)}`);
@@ -1,3 +1,4 @@
1
+ import { isRowNavigationClick } from "../router.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, reconcileKeyed, syncCompatibleNode } from "../dom.js";
20
21
  import {
21
22
  badge,
23
+ batchStatusBadge,
22
24
  badgeClass,
23
25
  statusLabel,
24
26
  formatTs,
@@ -287,16 +289,12 @@ export async function renderDashboard(scrollTo) {
287
289
  const dagStaleRuns = dagHealth?.staleRuns ?? 0;
288
290
  const dagInterruptedRuns = dagHealth?.interruptedRuns ?? 0;
289
291
  const dagInconsistentRuns = dagHealth?.inconsistentRuns ?? 0;
290
- const dagAttentionRuns =
291
- dagHealth?.attentionRuns ??
292
- dagRuns.filter(
293
- (dag) =>
294
- (dag.lifecycle ?? "").toLowerCase() !== "completed" &&
295
- ((dag.lifecycle ?? "").toLowerCase() === "paused" ||
296
- ["stale", "orphaned"].includes((dag.liveness ?? "").toLowerCase()) ||
297
- ["interrupted", "remote-unknown"].includes(dag.effectiveStatus) ||
298
- dag.stateConsistent === false),
299
- ).length;
292
+ const attentionDags = dagRuns.filter((dag) =>
293
+ ["paused", "failed", "error", "interrupted"].includes(String(dag.lifecycle ?? "").toLowerCase()) ||
294
+ ["failed", "error", "interrupted", "remote-unknown"].includes(String(dag.effectiveStatus ?? dag.status ?? "").toLowerCase()) ||
295
+ ["stale", "orphaned"].includes(String(dag.liveness ?? "").toLowerCase()) || dag.stateConsistent === false,
296
+ );
297
+ const dagAttentionRuns = attentionDags.length;
300
298
 
301
299
  const quietCount = snapshot.health?.quietCount ?? 0;
302
300
  const staleCount = snapshot.health?.staleCount ?? 0;
@@ -504,6 +502,7 @@ export async function renderDashboard(scrollTo) {
504
502
  dagBlock.appendChild(el("div", "risk-block-label", "需处理 DAG"));
505
503
  dagBlock.appendChild(el("div", "risk-total", String(block.total)));
506
504
  dagBlock.appendChild(el("p", "risk-summary", block.summary));
505
+ const details = el("details", "risk-dag-list"); details.appendChild(el("summary", "risk-link", "查看需处理运行")); details.appendChild(el("div", "risk-dag-items")); dagBlock.appendChild(details);
507
506
  return dagBlock;
508
507
  },
509
508
  update: (dagBlock, block) => {
@@ -511,6 +510,15 @@ export async function renderDashboard(scrollTo) {
511
510
  if (total) total.textContent = String(block.total);
512
511
  const summary = dagBlock.querySelector(".risk-summary");
513
512
  if (summary) summary.textContent = block.summary;
513
+ const list = dagBlock.querySelector(".risk-dag-items");
514
+ if (list) {
515
+ clearNode(list); for (const dag of attentionDags) {
516
+ const link = el("a", "object-link", dag.title || dag.dagRunId);
517
+ link.href = inspectPathToCanonicalHref(`/dag/${encodeURIComponent(dag.dagRunId)}`);
518
+ list.appendChild(link);
519
+ }
520
+ }
521
+
514
522
  },
515
523
  },
516
524
  );
@@ -634,7 +642,8 @@ export async function renderDashboard(scrollTo) {
634
642
  create: (batch) => {
635
643
  const tr = document.createElement("tr");
636
644
  tr.classList.add("clickable");
637
- tr.addEventListener("click", () =>
645
+ tr.addEventListener("click", (event) =>
646
+ isRowNavigationClick(event) &&
638
647
  navigate(`/batch/${encodeURIComponent(batch.batchRunId)}`),
639
648
  );
640
649
  return tr;
@@ -643,7 +652,7 @@ export async function renderDashboard(scrollTo) {
643
652
  const cells = [
644
653
  batch.batchRunId,
645
654
  batch.featureId ?? "—",
646
- badge(batch.status),
655
+ batchStatusBadge(batch),
647
656
  formatTs(batch.startedAt),
648
657
  summaryText(batch.summary),
649
658
  ];
@@ -1,3 +1,5 @@
1
+ import { bindManualViewRefresh, mountTaskRelationPicker } from "../shell-chrome.js";
2
+ import { sortAttentionTasks } from "../format-pool.js";
1
3
  /** Observe UI view module (R5). */
2
4
  import {
3
5
  UI_TEXT,
@@ -66,9 +68,23 @@ import {
66
68
  import { layoutDag } from "../dag-layout.js";
67
69
  import { showView, setBreadcrumb, updateHeaderRefresh } from "../shell-chrome.js";
68
70
  import { artifactLink } from "../kpi.js";
71
+ import { taskFailureCategoryLabel, followUpLabel } from "../task-failure-labels.js";
72
+
73
+ /** 异常页枚举单元格:中文主文案,raw token 保留在 title 供排障(未知原文透传)。 */
74
+ function attentionTokenCell(raw, kind) {
75
+ const value = String(raw ?? "").trim();
76
+ if (!value) return "—";
77
+ const kindName = kind === "category" ? "分类" : "跟进";
78
+ const mapped = kind === "category" ? taskFailureCategoryLabel(value) : followUpLabel(value);
79
+ const cell = el("span", "attention-token");
80
+ cell.textContent = mapped;
81
+ cell.setAttribute("title", mapped === value ? `未收录的${kindName} token:${value}` : `${kindName} token:${value}`);
82
+ return cell;
83
+ }
69
84
 
70
85
  export async function renderFailures() {
71
86
  showView("failures");
87
+ bindManualViewRefresh("failures", () => renderFailures());
72
88
  setBreadcrumb([
73
89
  { label: UI_TEXT.dashboard, href: "#/" },
74
90
  { label: UI_TEXT.failures },
@@ -89,15 +105,9 @@ export async function renderFailures() {
89
105
  return;
90
106
  }
91
107
  uiState.lastSnapshot = snapshot;
92
- updateHeaderRefresh(snapshot.generatedAt);
108
+ updateHeaderRefresh(snapshot.generatedAt, { mode: "manual" });
93
109
 
94
- const tasks = (snapshot.tasks ?? []).filter(
95
- (task) =>
96
- task.status === "failed" ||
97
- task.status === "blocked" ||
98
- task.status === "stale" ||
99
- task.liveness === "stale",
100
- );
110
+ const tasks = sortAttentionTasks(snapshot.tasks ?? []);
101
111
 
102
112
  if (tasks.length === 0) {
103
113
  clearNode(listEl);
@@ -122,11 +132,11 @@ export async function renderFailures() {
122
132
  ? () => navigate(`/run/${encodeURIComponent(task.workerRunId)}`)
123
133
  : undefined,
124
134
  cells: [
125
- task.taskId ?? "—",
135
+ objectRouteLink("task", task.taskId, snapshot, task.featureId),
126
136
  task.featureId ?? "—",
127
137
  badge(task.status),
128
- task.failureCategory ?? "—",
129
- task.recommendedFollowUp ?? "—",
138
+ attentionTokenCell(task.failureCategory, "category"),
139
+ attentionTokenCell(task.recommendedFollowUp, "followUp"),
130
140
  (() => {
131
141
  const links = el("div", "artifact-links");
132
142
  const r = refs(task);
@@ -1,3 +1,5 @@
1
+ import { renderInlineArtifactList } from "./run.js";
2
+ import { bindManualViewRefresh, mountTaskRelationPicker } from "../shell-chrome.js";
1
3
  /** Observe UI view module (R5). */
2
4
  import {
3
5
  UI_TEXT,
@@ -79,6 +81,7 @@ import { artifactLink } from "../kpi.js";
79
81
 
80
82
  export async function renderFeatureDetail(featureId) {
81
83
  showView("feature");
84
+ bindManualViewRefresh("feature", () => renderFeatureDetail(featureId));
82
85
  setBreadcrumb([
83
86
  { label: UI_TEXT.dashboard, href: "#/" },
84
87
  { label: featureId || "Feature" },
@@ -124,7 +127,7 @@ export async function renderFeatureDetail(featureId) {
124
127
  return false;
125
128
  }
126
129
  uiState.lastSnapshot = snapshot;
127
- updateHeaderRefresh(snapshot.generatedAt);
130
+ updateHeaderRefresh(snapshot.generatedAt, { mode: "manual" });
128
131
 
129
132
  const feature = lookup.kind === "found" ? lookup.feature : undefined;
130
133
  if (!feature) {
@@ -143,31 +146,8 @@ export async function renderFeatureDetail(featureId) {
143
146
  return false;
144
147
  }
145
148
 
146
- // Relations: only proven snapshot objects (feature itself + linked tasks if present).
147
- const linkedTask = (feature.tasks ?? [])
148
- .map((t) => t.taskId)
149
- .find((id) => objectExistsInSnapshot("task", id, snapshot));
150
- const poolTask = linkedTask
151
- ? (snapshot?.tasks ?? []).find((t) => t.taskId === linkedTask)
152
- : undefined;
153
- const batchRunId =
154
- poolTask?.batchRunId ??
155
- (poolTask ? findBatchRunIdForTask(snapshot, poolTask) : null);
156
- const dag =
157
- poolTask != null
158
- ? findDagForRun(snapshot, poolTask.workerRunId, poolTask)
159
- : undefined;
160
- mountRelationBar(
161
- "feature-relations",
162
- {
163
- featureId: feature.featureId,
164
- taskId: linkedTask,
165
- batchRunId: batchRunId ?? undefined,
166
- workerRunId: poolTask?.workerRunId,
167
- dagRunId: dag?.dagRunId ?? poolTask?.dagRunId,
168
- },
169
- snapshot,
170
- );
149
+ const linkedTasks = (feature.tasks ?? []).map((task) => ({ ...task, ...(snapshot.tasks ?? []).find((entry) => entry.taskId === task.taskId && entry.featureId === feature.featureId), featureId: feature.featureId }));
150
+ mountTaskRelationPicker("feature-relations", { featureId: feature.featureId }, linkedTasks, snapshot);
171
151
 
172
152
  try {
173
153
  metaEl.appendChild(el("h3", "section-heading", "状态与摘要"));
@@ -205,9 +185,11 @@ export async function renderFeatureDetail(featureId) {
205
185
  ac.required ? "是" : "否",
206
186
  ac.status ?? "—",
207
187
  (ac.blockedBy ?? []).join(", ") || "—",
208
- (ac.evidence ?? []).length
209
- ? String((ac.evidence ?? []).length)
210
- : "缺失",
188
+ (() => {
189
+ if (!(ac.evidence ?? []).length) return "缺失";
190
+ const details = el("details", "ac-evidence"); details.appendChild(el("summary", null, `${ac.evidence.length} 份证据`));
191
+ let loaded = false; details.addEventListener("toggle", () => { if (details.open && !loaded) { loaded = true; const body = el("div"); details.appendChild(body); renderInlineArtifactList(body, ac.evidence.map((path, index) => [`证据 ${index + 1}`, path])); } }); return details;
192
+ })(),
211
193
  ],
212
194
  })),
213
195
  ),
@@ -1,3 +1,4 @@
1
+ import { isPlainNavigationClick, isRowNavigationClick } from "../router.js";
1
2
  /** Observe UI view module — Task Pool ops surface (R5 + ops refresh). */
2
3
  import { UI_TEXT, POOL_POLL_MS } from "../constants.js";
3
4
  import { clearNode, el, reconcileKeyed } from "../dom.js";
@@ -26,6 +27,7 @@ import { uiState } from "../state.js";
26
27
  import {
27
28
  sortPoolTasks,
28
29
  matchesPoolStatusFilter,
30
+ matchesPoolTaskFilter,
29
31
  poolStatusCounts,
30
32
  sortAttentionTasks,
31
33
  collectPlannerReadyTasks,
@@ -125,6 +127,7 @@ function linkTo(path, label, className) {
125
127
  const a = el("a", className || "object-link", label);
126
128
  a.href = inspectPathToCanonicalHref(path);
127
129
  a.addEventListener("click", (e) => {
130
+ if (!isPlainNavigationClick(e)) return;
128
131
  e.preventDefault();
129
132
  e.stopPropagation();
130
133
  navigate(path);
@@ -264,7 +267,7 @@ export async function renderPool(options = {}) {
264
267
 
265
268
  if (updatedAtEl) {
266
269
  updatedAtEl.textContent = snapshot.generatedAt
267
- ? `投影更新:${formatTs(snapshot.generatedAt)}(advisory)`
270
+ ? `数据更新于 ${formatTs(snapshot.generatedAt)} · 仅提示`
268
271
  : "";
269
272
  }
270
273
 
@@ -302,21 +305,12 @@ export async function renderPool(options = {}) {
302
305
  const routeFilters = parseRoute().filters ?? {};
303
306
  const statusFilter = (routeFilters.status ?? "").toLowerCase();
304
307
  const qFilter = (routeFilters.q ?? "").trim().toLowerCase();
305
- const filteredTasks = allTasks.filter((task) => {
306
- if (statusFilter && !matchesPoolStatusFilter(task.status, statusFilter)) {
307
- return false;
308
- }
309
- if (qFilter) {
310
- const hay =
311
- `${task.taskId ?? ""} ${task.featureId ?? ""} ${task.workerRunId ?? ""} ${task.failureCategory ?? ""} ${task.recommendedFollowUp ?? ""}`.toLowerCase();
312
- if (!hay.includes(qFilter)) return false;
313
- }
314
- return true;
315
- });
308
+ const filteredTasks = allTasks.filter((task) => matchesPoolTaskFilter(task, routeFilters));
316
309
  const poolPresent = snapshot.taskPool?.present === true;
317
310
  const projectionFailed = Boolean(snapshot.projectionError);
318
- const readyItems = collectPlannerReadyTasks(snapshot.features);
319
- const attentionTasks = sortAttentionTasks(allTasks);
311
+ const taskById = new Map(allTasks.map((task) => [taskIdentityKey(task.featureId, task.taskId), task]));
312
+ const readyItems = collectPlannerReadyTasks(snapshot.features).filter((item) => matchesPoolTaskFilter({ ...item, status: "pending", ...taskById.get(taskIdentityKey(item.featureId, item.taskId)) }, routeFilters));
313
+ const attentionTasks = sortAttentionTasks(filteredTasks);
320
314
  const counts = poolStatusCounts(allTasks);
321
315
  const health = snapshot.health ?? {};
322
316
 
@@ -352,7 +346,7 @@ export async function renderPool(options = {}) {
352
346
  qLabel.appendChild(document.createTextNode("搜索"));
353
347
  const qInput = document.createElement("input");
354
348
  qInput.type = "search";
355
- qInput.placeholder = "Task / Feature / run…";
349
+ qInput.placeholder = "名称 / Task / Feature / run…";
356
350
  qInput.setAttribute("aria-label", "搜索 Task");
357
351
  qInput.dataset.role = "q";
358
352
  qLabel.appendChild(qInput);
@@ -366,14 +360,20 @@ export async function renderPool(options = {}) {
366
360
  }),
367
361
  );
368
362
  });
369
- qInput.addEventListener("change", () => {
370
- navigate(
371
- buildHashPath("/pool", {
372
- status: statusSelect.value || undefined,
373
- q: qInput.value.trim() || undefined,
374
- }),
375
- );
376
- });
363
+ let searchTimer;
364
+ const applySearch = () => {
365
+ clearTimeout(searchTimer);
366
+ searchTimer = setTimeout(() => {
367
+ if (parseRoute().view !== "pool" || !qInput.isConnected) return;
368
+ navigate(buildHashPath("/pool", { status: statusSelect.value, q: qInput.value.trim() }));
369
+ }, 220);
370
+ };
371
+ qInput.addEventListener("input", (event) => { if (!event.isComposing) applySearch(); });
372
+ qInput.addEventListener("compositionend", applySearch);
373
+ const clear = el("button", "btn btn-secondary", "清空筛选");
374
+ clear.type = "button";
375
+ clear.addEventListener("click", () => { clearTimeout(searchTimer); qInput.value = ""; statusSelect.value = ""; navigate("/pool"); });
376
+ wrap.appendChild(clear);
377
377
  wrap.appendChild(statusLabelEl);
378
378
  wrap.appendChild(qLabel);
379
379
  wrap.appendChild(count);
@@ -469,22 +469,31 @@ export async function renderPool(options = {}) {
469
469
  health.timeoutRiskCount ?? 0,
470
470
  (health.timeoutRiskCount ?? 0) > 0 ? "kpi-risk" : "",
471
471
  ],
472
- ["合计", allTasks.length, ""],
472
+ // 口径与 Ready 不同:Ready 来自 Feature 规划项,此处只数已建 Task。
473
+ [
474
+ "已建 Task",
475
+ allTasks.length,
476
+ "",
477
+ "当前已创建的 Task 数;上方 Ready 是 Feature 决策产生的规划项,尚未建 Task,不计入本数。",
478
+ ],
473
479
  ];
474
480
  reconcileKeyed(cards, entries, {
475
481
  getKey: ([label]) => label,
476
- create: ([label, value, extra]) => {
482
+ create: ([label, value, extra, title]) => {
477
483
  const card = el("div", `kpi-card${extra ? ` ${extra}` : ""}`);
478
484
  card.appendChild(el("div", "kpi-value", String(value)));
479
485
  card.appendChild(el("div", "kpi-label", label));
486
+ if (title) card.setAttribute("title", title);
480
487
  return card;
481
488
  },
482
- update: (card, [label, value, extra]) => {
489
+ update: (card, [label, value, extra, title]) => {
483
490
  card.className = `kpi-card${extra ? ` ${extra}` : ""}`;
484
491
  const valueEl = card.querySelector(".kpi-value");
485
492
  const labelEl = card.querySelector(".kpi-label");
486
493
  if (valueEl) valueEl.textContent = String(value);
487
494
  if (labelEl) labelEl.textContent = label;
495
+ if (title) card.setAttribute("title", title);
496
+ else card.removeAttribute("title");
488
497
  },
489
498
  });
490
499
  },
@@ -621,7 +630,7 @@ export async function renderPool(options = {}) {
621
630
  create: (task) => {
622
631
  const tr = document.createElement("tr");
623
632
  tr.classList.add("clickable");
624
- tr.addEventListener("click", () => navigate(taskPath(task)));
633
+ tr.addEventListener("click", (event) => { if (isRowNavigationClick(event)) navigate(taskPath(task)); });
625
634
  for (let i = 0; i < 8; i++) tr.appendChild(document.createElement("td"));
626
635
  return tr;
627
636
  },
@@ -703,7 +712,7 @@ export async function renderPool(options = {}) {
703
712
  export function startPoolView() {
704
713
  stopPoolAutoPolling();
705
714
  syncPoolToolbar();
706
- void renderPool({ reason: "route" }).then(() => {
715
+ return renderPool({ reason: "route" }).then(() => {
707
716
  if (poolAutoRefreshEnabled()) startPoolAutoPolling();
708
717
  });
709
718
  }