@tea-agent/loop-agent 0.42.0-next.1 → 0.42.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 (238) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +319 -1
  3. package/dist/application/dag/generate-task-dag.js +75 -19
  4. package/dist/application/dag/run-dag.js +41 -0
  5. package/dist/application/task-lifecycle/advance.js +24 -5
  6. package/dist/application/task-lifecycle/observe.js +171 -17
  7. package/dist/application/task-lifecycle/plan-transitions.js +42 -7
  8. package/dist/application/task-lifecycle/recommendations.js +13 -2
  9. package/dist/build-stamp.json +3 -3
  10. package/dist/cli/command-definitions.js +7 -0
  11. package/dist/cli/program.js +6 -1
  12. package/dist/commands/client-recovery.js +3 -0
  13. package/dist/commands/dag-follow-up.js +138 -0
  14. package/dist/commands/dag-rerun-task.js +2 -0
  15. package/dist/commands/init.js +27 -1
  16. package/dist/commands/task-advance.js +19 -0
  17. package/dist/executors/dag-pi-executor.js +3976 -76
  18. package/dist/executors/pi-executor.js +15 -4
  19. package/dist/executors/pi-read-budget-policy.js +239 -0
  20. package/dist/executors/shell-executor.js +978 -188
  21. package/dist/executors/shell-write-guard.js +7 -0
  22. package/dist/infrastructure/console/operation-store.js +226 -6
  23. package/dist/shared/dag-failure-category.js +12 -0
  24. package/dist/shared/openspec-spec.js +70 -4
  25. package/dist/shared/operator/capabilities.js +7 -0
  26. package/dist/task/config-types.js +105 -5
  27. package/dist/task/contract/adopt.js +4 -0
  28. package/dist/task/contract/import-revision.js +4 -0
  29. package/dist/task/contract/project.js +3 -0
  30. package/dist/task/contract/schema.js +2 -1
  31. package/dist/task/frontend-project-capability.js +203 -20
  32. package/dist/task/runtime.js +5 -2
  33. package/dist/task/source-prepare/build-draft.js +3 -3
  34. package/dist/task/source-prepare/fragment-inventory.js +64 -25
  35. package/dist/task/source-prepare/prepare.js +126 -21
  36. package/dist/task/source-prepare/semantic-intake.js +6 -2
  37. package/dist/task/source-references.js +22 -1
  38. package/dist/worker/console/chat/chat-ui-policy.js +4 -2
  39. package/dist/worker/console/chat/model-resolver.js +7 -4
  40. package/dist/worker/console/chat/pi-runtime.js +258 -11
  41. package/dist/worker/console/chat/repo-browser.js +10 -3
  42. package/dist/worker/console/chat/routes.js +165 -71
  43. package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
  44. package/dist/worker/console/chat/session-catalog.js +2 -0
  45. package/dist/worker/console/chat/session-store.js +159 -70
  46. package/dist/worker/console/chat/shortcuts.js +19 -3
  47. package/dist/worker/console/chat/turn-process.js +111 -56
  48. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  49. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  50. package/dist/worker/console/index.js +3 -0
  51. package/dist/worker/console/operator-actions.js +169 -7
  52. package/dist/worker/console/pi-readiness.js +3 -2
  53. package/dist/worker/console/server.js +23 -1
  54. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
  55. package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-COptKq2S.js} +1 -1
  56. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
  57. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
  58. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
  59. package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
  60. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
  70. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
  71. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
  72. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
  73. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
  74. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
  75. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
  76. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
  77. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
  78. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
  80. package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
  81. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
  82. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
  83. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
  84. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
  85. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
  86. package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
  87. package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
  88. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
  89. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
  90. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
  91. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
  92. package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-D59YJ9kB.js} +1 -1
  93. package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-Bl13LpNa.js} +5 -5
  94. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
  95. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
  96. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
  97. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
  98. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
  99. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
  100. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
  101. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
  102. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
  103. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
  104. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
  105. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
  106. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
  107. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
  108. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
  109. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
  110. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
  111. package/dist/worker/console/static/index.html +2 -2
  112. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  113. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
  114. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
  115. package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
  116. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
  117. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
  118. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
  119. package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
  120. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
  121. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
  122. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
  123. package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
  124. package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
  125. package/dist/worker/console/workspace-context.js +115 -1
  126. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  127. package/dist/worker/observe/routes.js +4 -0
  128. package/dist/worker/observe/static/dag-helpers.js +4 -2
  129. package/dist/worker/observe/static/dag-node-purpose.js +5 -0
  130. package/dist/worker/observe/static/inspect-workspace.js +23 -0
  131. package/dist/worker/observe/static/kpi.js +2 -2
  132. package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
  133. package/dist/worker/observe/static/operator-chrome.js +37 -23
  134. package/dist/worker/observe/static/relations.js +2 -2
  135. package/dist/worker/observe/static/router.d.ts +12 -1
  136. package/dist/worker/observe/static/router.js +48 -6
  137. package/dist/worker/observe/static/run-processing.js +4 -2
  138. package/dist/worker/observe/static/shell-chrome.js +2 -2
  139. package/dist/worker/observe/static/state.js +5 -0
  140. package/dist/worker/observe/static/styles.css +95 -0
  141. package/dist/worker/observe/static/views/dag-inspector.js +14 -7
  142. package/dist/worker/observe/static/views/dag.js +10 -2
  143. package/dist/worker/observe/static/views/dags.js +3 -1
  144. package/dist/worker/observe/static/views/dashboard.js +14 -6
  145. package/dist/worker/observe/static/views/pool.js +7 -2
  146. package/dist/worker/observe/static/views/run.js +12 -3
  147. package/dist/worker/observe/static/views/session-timeline.js +69 -10
  148. package/dist/worker/observe/static/views/task.js +7 -2
  149. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
  150. package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
  151. package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
  152. package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
  153. package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
  154. package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
  155. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  156. package/dist/workflows/dag/dag-retry-schema.js +138 -0
  157. package/dist/workflows/dag/frontend-closeout.js +221 -0
  158. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  159. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  160. package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
  161. package/dist/workflows/dag/frontend-plan-render.js +2 -1
  162. package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
  163. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  164. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  165. package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
  166. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  167. package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
  168. package/dist/workflows/dag/frontend-recovery-run.js +166 -34
  169. package/dist/workflows/dag/frontend-repair.js +1 -432
  170. package/dist/workflows/dag/frontend-review-context.js +261 -15
  171. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  172. package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
  173. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  174. package/dist/workflows/dag/frontend-shape-facts.js +419 -0
  175. package/dist/workflows/dag/frontend-shape.js +435 -0
  176. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  177. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  178. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  179. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  180. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  181. package/dist/workflows/dag/frontend-verification-trace.js +252 -25
  182. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  183. package/dist/workflows/dag/frontend-writer-admission.js +319 -0
  184. package/dist/workflows/dag/frontend-writer-status.js +256 -0
  185. package/dist/workflows/dag/init-hybrid.js +1028 -562
  186. package/dist/workflows/dag/interrupt-request.js +7 -0
  187. package/dist/workflows/dag/node-execution.js +828 -4
  188. package/dist/workflows/dag/prompt.js +70 -3
  189. package/dist/workflows/dag/recovery-lease.js +170 -0
  190. package/dist/workflows/dag/report.js +37 -1
  191. package/dist/workflows/dag/rerun-feedback.js +315 -1
  192. package/dist/workflows/dag/rerun-plan.js +17 -6
  193. package/dist/workflows/dag/rerun-run.js +44 -6
  194. package/dist/workflows/dag/rerun-task.js +251 -13
  195. package/dist/workflows/dag/retry-policy.js +219 -104
  196. package/dist/workflows/dag/runner.js +580 -124
  197. package/dist/workflows/dag/scheduler.js +133 -20
  198. package/dist/workflows/dag/types.js +244 -16
  199. package/dist/workflows/dag/validate.js +28 -12
  200. package/docs/examples/README.md +5 -0
  201. package/docs/init-surface.manifest.json +30 -12
  202. package/docs/skills/vetted-skill-registry.md +4 -2
  203. package/docs/templates/README.md +2 -0
  204. package/docs/templates/agent-dag-report.schema.json +8 -2
  205. package/docs/templates/agent-dag.schema.json +1 -1
  206. package/docs/templates/backend-test-dag.json +23 -20
  207. package/docs/templates/frontend-implementation-contract.schema.json +4 -1
  208. package/docs/templates/frontend-implementation-dag.json +89 -0
  209. package/docs/templates/spec-registry.schema.json +45 -0
  210. package/harness.json +1 -1
  211. package/package.json +4 -3
  212. package/skills/frontend-bounded-implement/SKILL.md +15 -14
  213. package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
  214. package/skills/frontend-contract/SKILL.md +23 -0
  215. package/skills/frontend-contract/references/contract-protocol.md +34 -0
  216. package/skills/frontend-design-review/SKILL.md +22 -41
  217. package/skills/frontend-plan/SKILL.md +26 -0
  218. package/skills/frontend-plan/references/decision-contract.md +37 -0
  219. package/skills/frontend-plan/references/design-decisions.md +17 -0
  220. package/skills/frontend-review/SKILL.md +20 -15
  221. package/skills/frontend-review/references/review-findings.md +6 -7
  222. package/skills/frontend-scout/SKILL.md +25 -0
  223. package/skills/frontend-scout/references/design-evidence.md +16 -0
  224. package/skills/frontend-scout/references/scout-evidence.md +23 -0
  225. package/skills/frontend-verification/SKILL.md +1 -1
  226. package/skills/loop-agent/references/command-reference.md +1 -0
  227. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  228. package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
  229. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
  230. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
  231. package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
  232. package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
  233. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
  234. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
  235. package/skills/frontend-implementation/SKILL.md +0 -52
  236. package/skills/frontend-implementation/references/code-standards.md +0 -33
  237. package/skills/frontend-implementation/references/design-spec.md +0 -56
  238. package/skills/frontend-implementation/references/node-contracts.md +0 -31
@@ -11,6 +11,8 @@
11
11
  * mountOperatorChrome 仅在浏览器环境调用。
12
12
  */
13
13
 
14
+ import { withWorkspaceQuery } from "./router.js";
15
+
14
16
  export const OPERATE_TABS = Object.freeze([
15
17
  { id: "tasks", label: "任务", href: "/#/operate/tasks" },
16
18
  { id: "recovery", label: "恢复", href: "/#/operate/recovery" },
@@ -52,16 +54,7 @@ export function parseInspectWorkspaceId(hashValue) {
52
54
  return id || null;
53
55
  }
54
56
 
55
- export function withWorkspaceQuery(href, workspaceId) {
56
- const raw = String(href ?? "");
57
- if (!workspaceId) return raw;
58
- const qIndex = raw.indexOf("?");
59
- if (qIndex < 0) return `${raw}?workspaceId=${encodeURIComponent(workspaceId)}`;
60
- const params = new URLSearchParams(raw.slice(qIndex + 1));
61
- if (params.get("workspaceId") === workspaceId) return raw;
62
- params.set("workspaceId", workspaceId);
63
- return `${raw.slice(0, qIndex)}?${params.toString()}`;
64
- }
57
+ export { withWorkspaceQuery };
65
58
 
66
59
  function splitHash(hashValue) {
67
60
  const raw = String(hashValue ?? "").replace(/^#/, "");
@@ -129,13 +122,17 @@ export function parseInspectContext(hashValue) {
129
122
  if (from !== "recovery") return null;
130
123
  const returnParams = new URLSearchParams();
131
124
  if (objectView.view === "dag") returnParams.set("dagRunId", objectView.id);
125
+ const workspaceId = parseInspectWorkspaceId(hashValue);
132
126
  const suffix = returnParams.toString();
133
127
  return {
134
128
  view: objectView.view,
135
129
  id: objectView.id,
136
130
  from,
137
131
  kindLabel: CONTEXT_KIND_LABELS[objectView.view] ?? objectView.view,
138
- returnHref: `/#/operate/recovery${suffix ? `?${suffix}` : ""}`,
132
+ returnHref: withWorkspaceQuery(
133
+ `/#/operate/recovery${suffix ? `?${suffix}` : ""}`,
134
+ workspaceId,
135
+ ),
139
136
  returnLabel: "返回 恢复",
140
137
  };
141
138
  }
@@ -143,21 +140,31 @@ export function parseInspectContext(hashValue) {
143
140
  /** 从 Inspect 切回操作侧:持有 DAG 选中时不丢对象(换动作不换对象)。 */
144
141
  export function resolveOperateHrefFromInspectHash(hashValue) {
145
142
  const objectView = parseInspectObjectView(hashValue);
143
+ const workspaceId = parseInspectWorkspaceId(hashValue);
146
144
  if (objectView?.view === "dag") {
147
- return `/#/operate/recovery?dagRunId=${encodeURIComponent(objectView.id)}`;
145
+ return withWorkspaceQuery(
146
+ `/#/operate/recovery?dagRunId=${encodeURIComponent(objectView.id)}`,
147
+ workspaceId,
148
+ );
148
149
  }
149
- return "/#/operate/tasks";
150
+ return withWorkspaceQuery("/#/operate/tasks", workspaceId);
150
151
  }
151
152
 
152
153
  /** Inspect DAG 详情 → Operate Recovery 协作中止深链(只改导航,不发 mutation)。 */
153
- export function buildOperateInterruptHref(dagRunId) {
154
- return `/#/operate/recovery?dagRunId=${encodeURIComponent(dagRunId)}&intent=interrupt`;
154
+ export function buildOperateInterruptHref(dagRunId, options = {}) {
155
+ return withWorkspaceQuery(
156
+ `/#/operate/recovery?dagRunId=${encodeURIComponent(dagRunId)}&intent=interrupt`,
157
+ options.workspaceId,
158
+ );
155
159
  }
156
160
 
157
161
  /** 操作侧 → Inspect 的 canonical 深链(可携带 from 以激活上下文 chip)。 */
158
162
  export function buildInspectDagHref(dagRunId, options = {}) {
159
163
  const base = `/#/inspect/dag/${encodeURIComponent(dagRunId)}`;
160
- return options.from === "recovery" ? `${base}?from=recovery` : base;
164
+ return withWorkspaceQuery(
165
+ options.from === "recovery" ? `${base}?from=recovery` : base,
166
+ options.workspaceId,
167
+ );
161
168
  }
162
169
 
163
170
  /** 清除跨模式来源标记,但保留对象页上的其它查询参数与当前历史位置。 */
@@ -218,14 +225,19 @@ export function buildOperatorChromeModel(config) {
218
225
  : []
219
226
  ).map((tab) => ({
220
227
  ...tab,
228
+ href: withWorkspaceQuery(
229
+ tab.href,
230
+ config.workspaceSwitcher?.activeId ?? config.workspaceId,
231
+ ),
221
232
  active:
222
- tab.href ===
233
+ tab.href.split("?")[0] ===
223
234
  (String(config.activeTabHref ?? "").split("?")[0] || null),
224
235
  }));
225
236
  const state = config.readinessState ?? "unknown";
226
237
  const operateHref = config.operateHref ?? "/#/operate/tasks";
227
238
  const inspectHref = config.inspectHref ?? "/#/inspect";
228
- const activeWorkspaceId = config.workspaceSwitcher?.activeId ?? null;
239
+ const activeWorkspaceId =
240
+ config.workspaceSwitcher?.activeId ?? config.workspaceId ?? null;
229
241
  return {
230
242
  mode,
231
243
  homeHref: withWorkspaceQuery(
@@ -245,13 +257,13 @@ export function buildOperatorChromeModel(config) {
245
257
  {
246
258
  id: "operate",
247
259
  label: "操作",
248
- href: operateHref,
260
+ href: withWorkspaceQuery(operateHref, activeWorkspaceId),
249
261
  active: mode === "operate",
250
262
  },
251
263
  {
252
264
  id: "inspect",
253
265
  label: "观测",
254
- href: inspectHref,
266
+ href: withWorkspaceQuery(inspectHref, activeWorkspaceId),
255
267
  active: mode === "inspect",
256
268
  },
257
269
  ],
@@ -261,9 +273,10 @@ export function buildOperatorChromeModel(config) {
261
273
  pi: {
262
274
  state,
263
275
  label: READINESS_LABELS[state] ?? state,
264
- href:
265
- config.readinessHref ??
266
- (mode === "inspect" ? "/#/operate/models" : "/#/operate/models"),
276
+ href: withWorkspaceQuery(
277
+ config.readinessHref ?? "/#/operate/models",
278
+ activeWorkspaceId,
279
+ ),
267
280
  title: config.readinessTitle ?? "",
268
281
  },
269
282
  workspaceTabsVisible: tabs.length > 0,
@@ -424,6 +437,7 @@ export function mountOperatorChrome(root, config = {}) {
424
437
  const base = { ...currentConfig };
425
438
  if (base.liveLocation) {
426
439
  const hash = window.location.hash;
440
+ base.workspaceId = parseInspectWorkspaceId(hash);
427
441
  base.activeTabHref = activeInspectTabHref(hash);
428
442
  base.operateHref = resolveOperateHrefFromInspectHash(hash);
429
443
  base.context = parseInspectContext(hash);
@@ -1,6 +1,6 @@
1
1
  /** Object relation navigation (Observe UI R5). */
2
2
  import { el } from "./dom.js";
3
- import { navigate } from "./router.js";
3
+ import { inspectPathToCanonicalHref, navigate } from "./router.js";
4
4
 
5
5
  export function objectExistsInSnapshot(kind, id, snapshot, featureId) {
6
6
  if (!id) return false;
@@ -48,7 +48,7 @@ export function objectRouteLink(kind, id, snapshot, featureId) {
48
48
  if (!exists) return el("span", "muted", id);
49
49
  const path = objectRoutePath(kind, id, featureId);
50
50
  const a = el("a", "object-link", id);
51
- a.href = `#${path}`;
51
+ a.href = inspectPathToCanonicalHref(path);
52
52
  a.addEventListener("click", (e) => {
53
53
  e.preventDefault();
54
54
  e.stopPropagation();
@@ -18,7 +18,18 @@ export function parseRoute(): InspectHashRoute;
18
18
  export function setNavigateHandler(
19
19
  handler: ((href: string) => void) | null,
20
20
  ): void;
21
- export function inspectPathToCanonicalHref(path: string): string;
21
+ export function workspaceIdFromLocation(locationLike?: {
22
+ hash?: string;
23
+ search?: string;
24
+ } | null): string | null;
25
+ export function withWorkspaceQuery(
26
+ href: string,
27
+ workspaceId?: string | null,
28
+ ): string;
29
+ export function inspectPathToCanonicalHref(
30
+ path: string,
31
+ workspaceId?: string | null,
32
+ ): string;
22
33
  export function navigate(path: string): void;
23
34
 
24
35
  export const DAG_HISTORY_DEFAULT_PAGE: number;
@@ -70,15 +70,57 @@ export function setNavigateHandler(handler) {
70
70
  navigateHandler = typeof handler === "function" ? handler : null;
71
71
  }
72
72
 
73
- export function inspectPathToCanonicalHref(path) {
73
+ /** Read the selected workspace from either Console's canonical hash or the
74
+ * standalone Inspect document query. The latter remains a compatibility
75
+ * entrypoint, so it must not manufacture a scope when none was supplied. */
76
+ export function workspaceIdFromLocation(locationLike = typeof location !== "undefined" ? location : null) {
77
+ if (!locationLike) return null;
78
+ const hash = String(locationLike.hash ?? "").replace(/^#/, "");
79
+ const hashQuery = hash.includes("?") ? hash.slice(hash.indexOf("?") + 1) : "";
80
+ const fromHash = new URLSearchParams(hashQuery).get("workspaceId")?.trim();
81
+ if (fromHash) return fromHash;
82
+ return new URLSearchParams(String(locationLike.search ?? ""))
83
+ .get("workspaceId")
84
+ ?.trim() || null;
85
+ }
86
+
87
+ /** Add the current workspace to a Console hash href without changing the
88
+ * destination's own query. This is deliberately usable for Inspect, Operate,
89
+ * and Chat; Console canonicalization still strips scope from Chat itself. */
90
+ export function withWorkspaceQuery(href, workspaceId = workspaceIdFromLocation()) {
91
+ const raw = String(href ?? "").trim();
92
+ const id = workspaceId?.trim();
93
+ if (!raw || !id) return raw;
94
+ const normalized = raw.startsWith("#") ? `/${raw}` : raw;
95
+ const qIndex = normalized.indexOf("?");
96
+ const base = qIndex >= 0 ? normalized.slice(0, qIndex) : normalized;
97
+ const query = new URLSearchParams(qIndex >= 0 ? normalized.slice(qIndex + 1) : "");
98
+ query.set("workspaceId", id);
99
+ return `${base}?${query.toString()}`;
100
+ }
101
+
102
+ export function inspectPathToCanonicalHref(path, workspaceId = workspaceIdFromLocation()) {
74
103
  const raw = String(path ?? "");
75
- if (raw.startsWith("/#/inspect")) return raw;
76
- if (raw.startsWith("#/inspect")) return `/${raw}`;
104
+ if (raw.startsWith("/#/inspect")) return withWorkspaceQuery(raw, workspaceId);
105
+ if (raw.startsWith("#/inspect")) return withWorkspaceQuery(`/${raw}`, workspaceId);
106
+ // Inspect owns only its own paths. Passing an Operate/Chat deep link through
107
+ // the old converter produced /#/inspect/#/operate/... and was the direct
108
+ // cause of the live-DAG interrupt link landing on the wrong page.
109
+ if (raw.startsWith("/#/operate") || raw.startsWith("/#/chat")) {
110
+ return withWorkspaceQuery(raw, workspaceId);
111
+ }
112
+ if (raw.startsWith("#/operate") || raw.startsWith("#/chat")) {
113
+ return withWorkspaceQuery(`/${raw}`, workspaceId);
114
+ }
77
115
  const stripped = raw.replace(/^#/, "");
78
116
  const withSlash = stripped.startsWith("/") ? stripped : `/${stripped}`;
79
- if (withSlash === "/inspect" || withSlash === "/") return "/#/inspect";
80
- if (withSlash.startsWith("/inspect/")) return `/#${withSlash}`;
81
- return `/#/inspect${withSlash}`;
117
+ if (withSlash === "/inspect" || withSlash === "/") {
118
+ return withWorkspaceQuery("/#/inspect", workspaceId);
119
+ }
120
+ if (withSlash.startsWith("/inspect/")) {
121
+ return withWorkspaceQuery(`/#${withSlash}`, workspaceId);
122
+ }
123
+ return withWorkspaceQuery(`/#/inspect${withSlash}`, workspaceId);
82
124
  }
83
125
 
84
126
  export function navigate(path) {
@@ -6,7 +6,7 @@ import {
6
6
  ARTIFACT_KEYS,
7
7
  } from "./constants.js";
8
8
  import { badge } from "./format.js";
9
- import { navigate } from "./router.js";
9
+ import { inspectPathToCanonicalHref, navigate } from "./router.js";
10
10
 
11
11
  export function stepDisplayLabel(label) {
12
12
  return STEP_LABELS[label] ?? label;
@@ -127,7 +127,9 @@ export function taskRunHistoryRow(run) {
127
127
  let runLink = run.workerRunId;
128
128
  if (run.workerRunId) {
129
129
  const a = document.createElement("a");
130
- a.href = `#/run/${encodeURIComponent(run.workerRunId)}`;
130
+ a.href = inspectPathToCanonicalHref(
131
+ `/run/${encodeURIComponent(run.workerRunId)}`,
132
+ );
131
133
  a.textContent = run.workerRunId;
132
134
  a.addEventListener("click", (e) => {
133
135
  e.preventDefault();
@@ -2,7 +2,7 @@
2
2
  import { UI_TEXT } from "./constants.js";
3
3
  import { clearNode, el } from "./dom.js";
4
4
  import { formatAgo } from "./format.js";
5
- import { navigate } from "./router.js";
5
+ import { inspectPathToCanonicalHref, navigate } from "./router.js";
6
6
  import { buildObjectRelations, renderObjectRelationBar } from "./relations.js";
7
7
  import { closeDagInspector } from "./views/dag-inspector.js";
8
8
 
@@ -23,7 +23,7 @@ export function setBreadcrumb(parts) {
23
23
  if (i > 0) nav.appendChild(document.createTextNode(" / "));
24
24
  if (part.href) {
25
25
  const a = document.createElement("a");
26
- a.href = part.href;
26
+ a.href = inspectPathToCanonicalHref(part.href);
27
27
  a.textContent = part.label;
28
28
  a.addEventListener("click", (e) => {
29
29
  e.preventDefault();
@@ -105,6 +105,11 @@ export const uiState = {
105
105
  dagGraphViewportState: null,
106
106
  dagTrajectoryViewportState: null,
107
107
  dagTimelineViewportState: null,
108
+ /**
109
+ * Explicit「执行记录」auto-scroll preference for the selected node.
110
+ * Defaults on for every newly selected node and survives polling/tab switches.
111
+ */
112
+ dagTimelineAutoScrollEnabled: true,
108
113
  dagNodeOutputViewportState: null,
109
114
  dagNodeInputViewportState: null,
110
115
  /** Spec-evidence inspector content scroll (single active region snapshot). */
@@ -1968,6 +1968,101 @@ body.is-resizing-dag-graph {
1968
1968
  gap: 8px;
1969
1969
  margin-bottom: var(--space-2);
1970
1970
  }
1971
+ .process-timeline-autoscroll-dock {
1972
+ position: sticky;
1973
+ top: calc(100% - 42px);
1974
+ z-index: 4;
1975
+ display: flex;
1976
+ justify-content: flex-end;
1977
+ height: 0;
1978
+ margin: 0 2px;
1979
+ pointer-events: none;
1980
+ }
1981
+ .process-timeline-autoscroll {
1982
+ position: relative;
1983
+ pointer-events: auto;
1984
+ display: inline-flex;
1985
+ align-items: center;
1986
+ justify-content: center;
1987
+ gap: 7px;
1988
+ width: 36px;
1989
+ height: 36px;
1990
+ padding: 0;
1991
+ border: 1px solid var(--hairline-strong);
1992
+ border-radius: 50%;
1993
+ background: color-mix(in srgb, var(--surface) 94%, var(--canvas));
1994
+ color: var(--body);
1995
+ box-shadow: 0 8px 24px rgb(38 37 30 / 14%);
1996
+ font-size: 14px;
1997
+ cursor: pointer;
1998
+ backdrop-filter: blur(10px);
1999
+ }
2000
+ .process-timeline-autoscroll::after {
2001
+ position: absolute;
2002
+ right: 0;
2003
+ bottom: calc(100% + 8px);
2004
+ padding: 6px 8px;
2005
+ border: 1px solid var(--hairline-strong);
2006
+ border-radius: var(--radius-sm);
2007
+ background: var(--ink);
2008
+ color: #fff;
2009
+ box-shadow: 0 6px 16px rgb(38 37 30 / 14%);
2010
+ content: attr(data-tooltip);
2011
+ font-size: 11px;
2012
+ font-weight: 650;
2013
+ line-height: 1;
2014
+ white-space: nowrap;
2015
+ opacity: 0;
2016
+ transform: translateY(3px);
2017
+ pointer-events: none;
2018
+ transition: opacity 120ms ease, transform 120ms ease;
2019
+ }
2020
+ .process-timeline-autoscroll:hover::after,
2021
+ .process-timeline-autoscroll:focus-visible::after {
2022
+ opacity: 1;
2023
+ transform: translateY(0);
2024
+ }
2025
+ .process-timeline-autoscroll:hover,
2026
+ .process-timeline-autoscroll:focus-visible {
2027
+ border-color: var(--orange);
2028
+ color: var(--orange-active);
2029
+ outline: none;
2030
+ box-shadow: 0 8px 24px rgb(38 37 30 / 16%), 0 0 0 3px var(--orange-soft);
2031
+ }
2032
+ .process-timeline-autoscroll.is-active {
2033
+ border-color: var(--ink);
2034
+ background: var(--ink);
2035
+ color: #fff;
2036
+ }
2037
+ .process-timeline-autoscroll.is-active:hover,
2038
+ .process-timeline-autoscroll.is-active:focus-visible {
2039
+ border-color: var(--orange);
2040
+ background: var(--ink);
2041
+ color: #fff;
2042
+ }
2043
+ .process-timeline-autoscroll-signal {
2044
+ width: 5px;
2045
+ height: 5px;
2046
+ border-radius: 50%;
2047
+ background: var(--muted-soft);
2048
+ }
2049
+ .process-timeline-autoscroll.is-active .process-timeline-autoscroll-signal {
2050
+ background: var(--orange);
2051
+ box-shadow: 0 0 0 4px rgb(245 78 0 / 16%);
2052
+ animation: process-timeline-live-pulse 1.8s ease-out infinite;
2053
+ }
2054
+ @keyframes process-timeline-live-pulse {
2055
+ 0%, 55%, 100% { box-shadow: 0 0 0 4px rgb(245 78 0 / 16%); }
2056
+ 75% { box-shadow: 0 0 0 7px rgb(245 78 0 / 0%); }
2057
+ }
2058
+ @media (prefers-reduced-motion: reduce) {
2059
+ .process-timeline-autoscroll.is-active .process-timeline-autoscroll-signal {
2060
+ animation: none;
2061
+ }
2062
+ .process-timeline-autoscroll::after {
2063
+ transition: none;
2064
+ }
2065
+ }
1971
2066
  .process-timeline-filter-group {
1972
2067
  display: inline-flex;
1973
2068
  flex-wrap: wrap;
@@ -421,6 +421,10 @@ function renderDagInspectorForCurrent() {
421
421
  function resetInspectorNodeSelection(dagRunId, nodeId) {
422
422
  uiState.selectedDagNodeId = nodeId;
423
423
  uiState.dagInspectorOpen = true;
424
+ // Each node opens in live-follow mode by default. The explicit floating
425
+ // control can pause it without losing the reader's current position.
426
+ uiState.dagTimelineAutoScrollEnabled = true;
427
+ uiState.dagTimelineViewportState = null;
424
428
  // Reset the per-attempt selection so an old node's attempt cannot leak
425
429
  // into the newly selected node (always defaults back to the latest).
426
430
  uiState.dagInspectorAttempt = null;
@@ -594,18 +598,13 @@ export function createDagInspectorResizeHandle(panel) {
594
598
  export function captureDagTimelineViewportState(panel) {
595
599
  const scrollEl = panel.querySelector(".process-timeline-scroll");
596
600
  if (scrollEl?.dataset.dagRunId && scrollEl.dataset.nodeId) {
597
- const followLatest = computeFollowLatest(
598
- scrollEl.scrollTop,
599
- scrollEl.scrollHeight,
600
- scrollEl.clientHeight,
601
- );
602
601
  uiState.dagTimelineViewportState = updateDagTimelineViewportState(
603
602
  uiState.dagTimelineViewportState,
604
603
  scrollEl.dataset.dagRunId,
605
604
  scrollEl.dataset.nodeId,
606
605
  scrollEl.scrollLeft,
607
606
  scrollEl.scrollTop,
608
- followLatest,
607
+ uiState.dagTimelineAutoScrollEnabled !== false,
609
608
  );
610
609
  }
611
610
  }
@@ -2111,7 +2110,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
2111
2110
  ? "打开新运行"
2112
2111
  : "打开失败的新运行";
2113
2112
  open.addEventListener("click", () => {
2114
- window.location.hash = `#/dag/${encodeURIComponent(childRunId)}`;
2113
+ navigate(`/dag/${encodeURIComponent(childRunId)}`);
2115
2114
  });
2116
2115
  planHost.appendChild(open);
2117
2116
  }
@@ -2919,6 +2918,11 @@ function restoreInspectorContentScroll(content, dagRunId, nodeId, tab) {
2919
2918
  ) {
2920
2919
  return;
2921
2920
  }
2921
+ if (tab === "timeline" && uiState.dagTimelineAutoScrollEnabled !== false) {
2922
+ content.scrollLeft = uiState.dagTimelineViewportState?.scrollLeft ?? 0;
2923
+ content.scrollTop = content.scrollHeight;
2924
+ return;
2925
+ }
2922
2926
  if (tab === "output") {
2923
2927
  const saved = uiState.dagNodeOutputViewportState;
2924
2928
  if (saved?.regionKey === regionKey && saved.followLatest) {
@@ -3673,6 +3677,9 @@ function fillDagInspectorContent(content, dagRunId, node) {
3673
3677
  const attemptBuilt = buildAttemptBar(dagRunId, node);
3674
3678
  if (attemptBuilt?.bar) content.appendChild(attemptBuilt.bar);
3675
3679
  renderExecutorAwareTimeline(content, dagRunId, node);
3680
+ // Initial content is rendered before it is attached. Restore again on the
3681
+ // connected animation frame so live-follow sees the final scroll height.
3682
+ restoreInspectorContentScroll(content, dagRunId, node.nodeId, "timeline");
3676
3683
  } else if (activeTab === "input") {
3677
3684
  void renderDagNodeInput(content, dagRunId, node);
3678
3685
  } else {
@@ -35,7 +35,12 @@ import {
35
35
  parseMarkdownBlocks,
36
36
  markdownLinkHref,
37
37
  } from "../format.js";
38
- import { parseRoute, navigate, buildHashPath } from "../router.js";
38
+ import {
39
+ parseRoute,
40
+ navigate,
41
+ buildHashPath,
42
+ withWorkspaceQuery,
43
+ } from "../router.js";
39
44
  import {
40
45
  fetchJson,
41
46
  artifactUrl,
@@ -248,6 +253,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
248
253
  uiState.dagInspectorAttempt = null;
249
254
  uiState.dagGraphViewportState = null;
250
255
  uiState.dagTimelineViewportState = null;
256
+ uiState.dagTimelineAutoScrollEnabled = true;
251
257
  }
252
258
  // AC-001:同会话内重新进入同一 dagRunId 保持当前图视图模式(不切回逻辑拓扑)。
253
259
  uiState.dagGraphViewMode = dagGraphViewModes.get(dagRunId) ?? "topology";
@@ -364,7 +370,9 @@ export async function renderDagDetail(dagRunId, initial = true) {
364
370
  "page-action-btn",
365
371
  "在操作台中止此运行 →",
366
372
  );
367
- interruptLink.href = `/#/operate/recovery?dagRunId=${encodeURIComponent(dag.dagRunId)}&intent=interrupt`;
373
+ interruptLink.href = withWorkspaceQuery(
374
+ `/#/operate/recovery?dagRunId=${encodeURIComponent(dag.dagRunId)}&intent=interrupt`,
375
+ );
368
376
  interruptLink.setAttribute("data-testid", "inspect-dag-interrupt-link");
369
377
  interruptLink.title = "只跳转到操作台 Recovery,不会在观测页执行中止";
370
378
  primaryEntries.push(["协作中止", interruptLink]);
@@ -327,7 +327,9 @@ function buildRow(run) {
327
327
 
328
328
  const nameTd = document.createElement("td");
329
329
  const nameLink = el("a", "dags-name-link", run.title || run.dagRunId);
330
- nameLink.href = `#/dag/${encodeURIComponent(run.dagRunId)}`;
330
+ nameLink.href = inspectPathToCanonicalHref(
331
+ `/dag/${encodeURIComponent(run.dagRunId)}`,
332
+ );
331
333
  nameLink.addEventListener("click", (event) => {
332
334
  event.preventDefault();
333
335
  event.stopPropagation();
@@ -35,7 +35,13 @@ import {
35
35
  parseMarkdownBlocks,
36
36
  markdownLinkHref,
37
37
  } from "../format.js";
38
- import { parseRoute, navigate, buildHashPath } from "../router.js";
38
+ import {
39
+ parseRoute,
40
+ navigate,
41
+ buildHashPath,
42
+ inspectPathToCanonicalHref,
43
+ withWorkspaceQuery,
44
+ } from "../router.js";
39
45
  import { fetchJson, artifactUrl, parseArtifactPreviewResponse } from "../api.js";
40
46
  import {
41
47
  objectRouteLink,
@@ -118,14 +124,14 @@ async function renderNightStrip() {
118
124
 
119
125
  const actions = el("div", "night-strip-actions");
120
126
  const detail = el("a", "page-action-btn", "夜间详情");
121
- detail.href = "/#/inspect/night";
127
+ detail.href = inspectPathToCanonicalHref("/night");
122
128
  detail.addEventListener("click", (e) => {
123
129
  e.preventDefault();
124
130
  navigate("/night");
125
131
  });
126
132
  actions.appendChild(detail);
127
133
  const manage = el("a", "page-action-btn night-strip-manage", "晨审处置 →");
128
- manage.href = "/#/operate/night";
134
+ manage.href = withWorkspaceQuery("/#/operate/night");
129
135
  manage.title = "夜间计划的管理与晨审处置在操作台完成(Inspect 保持只读)";
130
136
  actions.appendChild(manage);
131
137
  strip.appendChild(actions);
@@ -528,7 +534,7 @@ export async function renderDashboard(scrollTo) {
528
534
  workerBlock.appendChild(el("div", "risk-total", String(block.total)));
529
535
  workerBlock.appendChild(el("p", "risk-summary", block.summary));
530
536
  const link = el("a", "risk-link", "查看异常 Task →");
531
- link.href = "#/failures";
537
+ link.href = inspectPathToCanonicalHref("/failures");
532
538
  link.addEventListener("click", (event) => {
533
539
  event.preventDefault();
534
540
  navigate("/failures");
@@ -556,7 +562,7 @@ export async function renderDashboard(scrollTo) {
556
562
  const head = el("div", "section-heading-row");
557
563
  head.appendChild(el("h3", "section-heading", UI_TEXT.recentDags));
558
564
  const allLink = el("a", "section-heading-action", UI_TEXT.viewAllDags);
559
- allLink.href = "#/dags?page=1&pageSize=20";
565
+ allLink.href = inspectPathToCanonicalHref("/dags?page=1&pageSize=20");
560
566
  allLink.setAttribute("aria-label", "查看全部 DAG 运行历史");
561
567
  allLink.addEventListener("click", (event) => {
562
568
  event.preventDefault();
@@ -686,7 +692,9 @@ function syncTableRow(row, cells) {
686
692
  function buildDagTimelineItem(dag) {
687
693
  const progress = dagProgress(dag);
688
694
  const item = el("a", "dag-run-timeline-item");
689
- item.href = `#/dag/${encodeURIComponent(dag.dagRunId)}`;
695
+ item.href = inspectPathToCanonicalHref(
696
+ `/dag/${encodeURIComponent(dag.dagRunId)}`,
697
+ );
690
698
  item.title = dag.title || dag.dagRunId;
691
699
  item.addEventListener("click", (event) => {
692
700
  event.preventDefault();
@@ -8,7 +8,12 @@ import {
8
8
  livenessBadge,
9
9
  truncateText,
10
10
  } from "../format.js";
11
- import { parseRoute, navigate, buildHashPath } from "../router.js";
11
+ import {
12
+ parseRoute,
13
+ navigate,
14
+ buildHashPath,
15
+ inspectPathToCanonicalHref,
16
+ } from "../router.js";
12
17
  import { fetchJson } from "../api.js";
13
18
  import { objectRouteLink } from "../relations.js";
14
19
  import {
@@ -118,7 +123,7 @@ function taskPath(task) {
118
123
 
119
124
  function linkTo(path, label, className) {
120
125
  const a = el("a", className || "object-link", label);
121
- a.href = `#${path}`;
126
+ a.href = inspectPathToCanonicalHref(path);
122
127
  a.addEventListener("click", (e) => {
123
128
  e.preventDefault();
124
129
  e.stopPropagation();
@@ -35,7 +35,12 @@ import {
35
35
  parseMarkdownBlocks,
36
36
  markdownLinkHref,
37
37
  } from "../format.js";
38
- import { parseRoute, navigate, buildHashPath } from "../router.js";
38
+ import {
39
+ parseRoute,
40
+ navigate,
41
+ buildHashPath,
42
+ inspectPathToCanonicalHref,
43
+ } from "../router.js";
39
44
  import {
40
45
  fetchJson,
41
46
  artifactUrl,
@@ -176,7 +181,9 @@ export function renderRunDagProgress(task, snapshot) {
176
181
  "详情",
177
182
  (() => {
178
183
  const a = el("a", "link", "查看 DAG 详情 →");
179
- a.href = `#/dag/${encodeURIComponent(dag.dagRunId)}`;
184
+ a.href = inspectPathToCanonicalHref(
185
+ `/dag/${encodeURIComponent(dag.dagRunId)}`,
186
+ );
180
187
  a.addEventListener("click", (e) => {
181
188
  e.preventDefault();
182
189
  navigate(`/dag/${encodeURIComponent(dag.dagRunId)}`);
@@ -295,7 +302,9 @@ export function renderRunPanels(task, events, snapshot) {
295
302
  li.appendChild(marker);
296
303
  if (step.label === "run-dag" && dag?.dagRunId) {
297
304
  const link = document.createElement("a");
298
- link.href = `#/dag/${encodeURIComponent(dag.dagRunId)}`;
305
+ link.href = inspectPathToCanonicalHref(
306
+ `/dag/${encodeURIComponent(dag.dagRunId)}`,
307
+ );
299
308
  link.className = "step-link";
300
309
  link.textContent = "DAG →";
301
310
  link.addEventListener("click", (e) => {