@tea-agent/loop-agent 0.42.0-next.6 → 0.42.0-next.8

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 (115) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +20 -0
  3. package/dist/build-stamp.json +3 -3
  4. package/dist/executors/dag-pi-executor.js +74 -13
  5. package/dist/executors/shell-executor.js +3 -1
  6. package/dist/task/source-prepare/fragment-inventory.js +49 -16
  7. package/dist/task/source-prepare/prepare.js +37 -20
  8. package/dist/worker/console/chat/model-resolver.js +7 -4
  9. package/dist/worker/console/chat/pi-runtime.js +60 -5
  10. package/dist/worker/console/chat/routes.js +69 -46
  11. package/dist/worker/console/chat/sdd-data-alignment.js +6 -3
  12. package/dist/worker/console/chat/session-store.js +16 -0
  13. package/dist/worker/console/pi-readiness.js +3 -2
  14. package/dist/worker/console/server.js +19 -1
  15. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BJpsGgAv.js → abnfDiagram-N423BO3Z-C6n9_m0P.js} +1 -1
  16. package/dist/worker/console/static/assets/{arc-DjMFPsEd.js → arc-DQh-IfZ1.js} +1 -1
  17. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DqdZlr1O.js → architectureDiagram-T3A2C74G-54NnrwUC.js} +1 -1
  18. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BmZnfFEO.js → blockDiagram-VBNYF7ZC-pivRALGK.js} +1 -1
  19. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-3cKberpL.js → c4Diagram-5PPSVZJV-BR7OV2NJ.js} +1 -1
  20. package/dist/worker/console/static/assets/channel-DsZxrgqe.js +1 -0
  21. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-q-0yjQ1h.js → chunk-2GRJ4B5K-B1Aq6BcK.js} +1 -1
  22. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DnONBaDW.js → chunk-2Q5K7J3B-CbdU0rjo.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-v3a6PCOS.js → chunk-5RXB4S5H-Dqi2GJeD.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D1kLrlU_.js → chunk-5VM5RSS4-BYn1Hu4R.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BPelzihi.js → chunk-6Q2QTUOP-DRLjDL0k.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-DauNE69H.js → chunk-GF5L2VYU-CY9Xx2jV.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-RIg5_bFJ.js → chunk-JWPE2WC7-BNCZs7_z.js} +1 -1
  28. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BuILuoxs.js → chunk-KBJHAD2P-DZ8AStLL.js} +1 -1
  29. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-VHORV5oW.js → chunk-RYQCIY6F-DsxjYrzz.js} +1 -1
  30. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BJYr4xHC.js → chunk-XXDRQBXY-Ddx1KC1l.js} +1 -1
  31. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +1 -0
  32. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +1 -0
  33. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BS6nuqQn.js → cose-bilkent-JH36ORCC-W9TveCnK.js} +1 -1
  34. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BBhF0NAT.js → cynefin-VYW2F7L2-l9j_ztZH.js} +1 -1
  35. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D1135Ifg.js → cynefinDiagram-MW4NZA55-BMJMKi4G.js} +1 -1
  36. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-vAOYPUWJ.js → dagre-VZM6K2ZE-Bb4mG9pH.js} +1 -1
  37. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DuaSPkfa.js → diagram-7IWD3JNH-BMgL_Qv0.js} +1 -1
  38. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-mMvXvGU6.js → diagram-B4RE2ZJO-Bvo2T4OQ.js} +1 -1
  39. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDIEJtlL.js → diagram-LBJQPF4R-_5kWRN9c.js} +1 -1
  40. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Bi_6JvCW.js → diagram-Q27KOJAE-DqdMrltM.js} +1 -1
  41. package/dist/worker/console/static/assets/{diagram-UB23O5K3-DAqj57yN.js → diagram-UB23O5K3-CDDYsNkp.js} +1 -1
  42. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DfWaxNEo.js → ebnfDiagram-BXEA7PRR-c4nfnZUV.js} +1 -1
  43. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bt7QqkNG.js → erDiagram-JOGREHBK-DnkUcNMs.js} +1 -1
  44. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CRRhQwT2.js → flowDiagram-UKHOOZJN-Dz0KGLZE.js} +1 -1
  45. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DAV1O5B6.js → ganttDiagram-PKOTCBZU-Cj1t1uka.js} +1 -1
  46. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-0X2Qo4Lp.js → gitGraphDiagram-DS77QQ5N-tp2FrBHd.js} +1 -1
  47. package/dist/worker/console/static/assets/index-D9Sc0f0y.js +449 -0
  48. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C5W5eILC.js → infoDiagram-6WML65LV-DQwJS8DH.js} +1 -1
  49. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-5SfqU-Tb.js → ishikawaDiagram-WSZJBQD7-eimCQWD4.js} +1 -1
  50. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CKDRcaKf.js → journeyDiagram-NVQOT4AX-Dd9Gbwgv.js} +1 -1
  51. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Cc7_TuSb.js → kanban-definition-27J2QSJJ-Qve3cyft.js} +1 -1
  52. package/dist/worker/console/static/assets/{linear-BcQYYmeT.js → linear-UXKSe36Z.js} +1 -1
  53. package/dist/worker/console/static/assets/{mermaid.core-CwLZPtQo.js → mermaid.core-CWhj4JXN.js} +5 -5
  54. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D29M025z.js → mindmap-definition-FAOFIHXS-yTcwf4SJ.js} +1 -1
  55. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CV-ZAWYe.js → pegDiagram-VL7TDLO6-_7qToaZP.js} +1 -1
  56. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-C6iPS_o5.js → pieDiagram-7S7Q4E2Y-CXmrKmDm.js} +1 -1
  57. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-BFCD1sTg.js → quadrantDiagram-CIZ2JOQS-hMmrJyaS.js} +1 -1
  58. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CztNj4O9.js → railroadDiagram-AXF67PYL-BXw8tCBe.js} +1 -1
  59. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BWIELSNd.js → requirementDiagram-LRYGKXZP-BmjgnLZl.js} +1 -1
  60. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CPtxrjJQ.js → sankeyDiagram-W5VNT64P-Bj77SPpN.js} +1 -1
  61. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BEWVSFsu.js → sequenceDiagram-SI44F4Z6-B2FDVysL.js} +1 -1
  62. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DyhzoX_J.js → sizeCapture-X5ZJPWSS-UChNY9ZZ.js} +1 -1
  63. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-xLox0nu7.js → stateDiagram-OKZ733FA-wbAEkbd7.js} +1 -1
  64. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +1 -0
  65. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CLC_ow9A.js → swimlanes-SLNWSIFB-DCEkU8HR.js} +2 -2
  66. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +8 -0
  67. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DmJZ4uyY.js → timeline-definition-Z64GVDOM-BGp7H06U.js} +1 -1
  68. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DD7GGi9Z.js → vennDiagram-T6HMQDX7-DsCIQuWR.js} +1 -1
  69. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Bi8PQoGt.js → wardleyDiagram-T6FBY63Y-cRZLP4Xe.js} +1 -1
  70. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-uKdAY8Dp.js → xychartDiagram-ELKLHX3M-CP0xLR9Y.js} +1 -1
  71. package/dist/worker/console/static/index.html +1 -1
  72. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
  73. package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
  74. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
  75. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +31 -2
  76. package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -3
  77. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
  78. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
  79. package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
  80. package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
  81. package/dist/worker/observe/static/dag-helpers.js +4 -2
  82. package/dist/worker/observe/static/inspect-workspace.js +23 -0
  83. package/dist/worker/observe/static/kpi.js +2 -2
  84. package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
  85. package/dist/worker/observe/static/operator-chrome.js +37 -23
  86. package/dist/worker/observe/static/relations.js +2 -2
  87. package/dist/worker/observe/static/router.d.ts +12 -1
  88. package/dist/worker/observe/static/router.js +48 -6
  89. package/dist/worker/observe/static/run-processing.js +4 -2
  90. package/dist/worker/observe/static/shell-chrome.js +2 -2
  91. package/dist/worker/observe/static/state.js +5 -0
  92. package/dist/worker/observe/static/styles.css +95 -0
  93. package/dist/worker/observe/static/views/dag-inspector.js +14 -7
  94. package/dist/worker/observe/static/views/dag.js +10 -2
  95. package/dist/worker/observe/static/views/dags.js +3 -1
  96. package/dist/worker/observe/static/views/dashboard.js +14 -6
  97. package/dist/worker/observe/static/views/pool.js +7 -2
  98. package/dist/worker/observe/static/views/run.js +12 -3
  99. package/dist/worker/observe/static/views/session-timeline.js +69 -10
  100. package/dist/worker/observe/static/views/task.js +7 -2
  101. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +40 -9
  102. package/dist/workflows/dag/backend-test-scenario-param.js +126 -19
  103. package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
  104. package/dist/workflows/dag/backend-test-writer-completeness.js +7 -3
  105. package/dist/workflows/dag/frontend-shadow-dual-write.js +33 -6
  106. package/dist/workflows/dag/init-hybrid.js +175 -27
  107. package/dist/workflows/dag/node-execution.js +1 -1
  108. package/dist/workflows/dag/types.js +1 -1
  109. package/package.json +3 -2
  110. package/dist/worker/console/static/assets/channel-DWYwciiv.js +0 -1
  111. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D6jOf_l4.js +0 -1
  112. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D6jOf_l4.js +0 -1
  113. package/dist/worker/console/static/assets/index-CVdddQsA.js +0 -451
  114. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DS5ZRvJG.js +0 -1
  115. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Dvpxn-bf.js +0 -8
@@ -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) => {
@@ -7,12 +7,72 @@ import { fetchJson } from "../api.js";
7
7
  import {
8
8
  uiState,
9
9
  updateDagTimelineViewportState,
10
- computeFollowLatest,
11
10
  } from "../state.js";
12
11
 
13
12
  /** Default visible lines for tool result preview before expand. */
14
13
  export const TOOL_RESULT_PREVIEW_LINES = 20;
15
14
 
15
+ function isTimelineAutoScrollEnabled() {
16
+ return uiState.dagTimelineAutoScrollEnabled !== false;
17
+ }
18
+
19
+ function scrollTimelineToLatest(content) {
20
+ content.scrollLeft = 0;
21
+ content.scrollTop = content.scrollHeight;
22
+ uiState.dagTimelineViewportState = updateDagTimelineViewportState(
23
+ uiState.dagTimelineViewportState,
24
+ uiState.currentDagRunId,
25
+ content.dataset.nodeId,
26
+ content.scrollLeft,
27
+ content.scrollTop,
28
+ true,
29
+ );
30
+ }
31
+
32
+ /** Floating, explicit live-follow control for the execution-record viewport. */
33
+ export function createTimelineAutoScrollControl(content) {
34
+ const dock = el("div", "process-timeline-autoscroll-dock");
35
+ const button = document.createElement("button");
36
+ button.type = "button";
37
+ const signal = el("span", "process-timeline-autoscroll-signal");
38
+ signal.setAttribute("aria-hidden", "true");
39
+ const icon = el("i", "ri-arrow-down-line");
40
+ icon.setAttribute("aria-hidden", "true");
41
+ button.appendChild(signal);
42
+ button.appendChild(icon);
43
+
44
+ const sync = () => {
45
+ const enabled = isTimelineAutoScrollEnabled();
46
+ const actionLabel = enabled ? "关闭自动滚动" : "自动滚动";
47
+ button.className = `process-timeline-autoscroll${enabled ? " is-active" : ""}`;
48
+ button.setAttribute("aria-pressed", String(enabled));
49
+ button.setAttribute("aria-label", actionLabel);
50
+ button.setAttribute("data-tooltip", actionLabel);
51
+ icon.className = enabled ? "ri-arrow-down-line" : "ri-pause-mini-fill";
52
+ };
53
+
54
+ button.addEventListener("click", () => {
55
+ const enabled = !isTimelineAutoScrollEnabled();
56
+ uiState.dagTimelineAutoScrollEnabled = enabled;
57
+ if (enabled) scrollTimelineToLatest(content);
58
+ else {
59
+ uiState.dagTimelineViewportState = updateDagTimelineViewportState(
60
+ uiState.dagTimelineViewportState,
61
+ uiState.currentDagRunId,
62
+ content.dataset.nodeId,
63
+ content.scrollLeft,
64
+ content.scrollTop,
65
+ false,
66
+ );
67
+ }
68
+ sync();
69
+ });
70
+
71
+ sync();
72
+ dock.appendChild(button);
73
+ return dock;
74
+ }
75
+
16
76
  export function normalizeSessionEventFields(event) {
17
77
  if (!event || typeof event !== "object") return {};
18
78
  const args = event.args ?? event.input ?? event.toolInput;
@@ -951,6 +1011,7 @@ export function renderSessionTimeline(content, nodeId, options = {}) {
951
1011
  });
952
1012
  toolbar.appendChild(exportBtn);
953
1013
  content.appendChild(toolbar);
1014
+ content.appendChild(createTimelineAutoScrollControl(content));
954
1015
 
955
1016
  const timelineItems = filterExecutionRecordItems(
956
1017
  buildRichTimelineItems(visible.events, { showProtocol }),
@@ -1030,23 +1091,18 @@ export function renderSessionTimeline(content, nodeId, options = {}) {
1030
1091
  content.addEventListener(
1031
1092
  "scroll",
1032
1093
  () => {
1033
- const followLatest = computeFollowLatest(
1034
- content.scrollTop,
1035
- content.scrollHeight,
1036
- content.clientHeight,
1037
- );
1038
1094
  uiState.dagTimelineViewportState = updateDagTimelineViewportState(
1039
1095
  uiState.dagTimelineViewportState,
1040
1096
  uiState.currentDagRunId,
1041
1097
  nodeId,
1042
1098
  content.scrollLeft,
1043
1099
  content.scrollTop,
1044
- followLatest,
1100
+ isTimelineAutoScrollEnabled(),
1045
1101
  );
1046
1102
  },
1047
1103
  { passive: true },
1048
1104
  );
1049
- if (timelineState?.followLatest ?? true) {
1105
+ if (isTimelineAutoScrollEnabled()) {
1050
1106
  content.scrollLeft = timelineState?.scrollLeft ?? 0;
1051
1107
  content.scrollTop = content.scrollHeight;
1052
1108
  } else {
@@ -1058,6 +1114,7 @@ export function renderSessionTimeline(content, nodeId, options = {}) {
1058
1114
  function rerenderSessionTimeline(content, nodeId, options) {
1059
1115
  const scrollTop = content.scrollTop;
1060
1116
  const scrollLeft = content.scrollLeft;
1117
+ const autoScrollEnabled = isTimelineAutoScrollEnabled();
1061
1118
  const preserved = [...content.children].filter((child) =>
1062
1119
  String(child.className ?? "")
1063
1120
  .split(/\s+/)
@@ -1066,6 +1123,8 @@ function rerenderSessionTimeline(content, nodeId, options) {
1066
1123
  while (content.firstChild) content.removeChild(content.firstChild);
1067
1124
  for (const child of preserved) content.appendChild(child);
1068
1125
  renderSessionTimeline(content, nodeId, options);
1069
- content.scrollTop = scrollTop;
1070
- content.scrollLeft = scrollLeft;
1126
+ if (!autoScrollEnabled) {
1127
+ content.scrollTop = scrollTop;
1128
+ content.scrollLeft = scrollLeft;
1129
+ }
1071
1130
  }
@@ -36,7 +36,12 @@ import {
36
36
  markdownLinkHref,
37
37
  formatLedgerSummary,
38
38
  } from "../format.js";
39
- import { parseRoute, navigate, buildHashPath } from "../router.js";
39
+ import {
40
+ parseRoute,
41
+ navigate,
42
+ buildHashPath,
43
+ inspectPathToCanonicalHref,
44
+ } from "../router.js";
40
45
  import {
41
46
  fetchJson,
42
47
  fetchJsonResult,
@@ -133,7 +138,7 @@ export async function renderTaskDetail(featureId, taskId) {
133
138
  if (!c?.featureId || !c?.taskId) continue;
134
139
  const path = `/feature/${encodeURIComponent(c.featureId)}/task/${encodeURIComponent(c.taskId)}`;
135
140
  const a = el("a", "object-link", `${c.featureId}::${c.taskId}`);
136
- a.href = `#${path}`;
141
+ a.href = inspectPathToCanonicalHref(path);
137
142
  a.addEventListener("click", (e) => {
138
143
  e.preventDefault();
139
144
  navigate(path);
@@ -307,14 +307,18 @@ export function classifyBackendTestCorrespondenceRepair(facts, layout) {
307
307
  const markdownReasons = [];
308
308
  const pytestReasons = [];
309
309
  const counts = bindingCounts(entry);
310
- if (entry.testPoints.some((point) => (counts.get(point) ?? 0) !== 1) ||
311
- [...counts.keys()].some((point) => !entry.testPoints.includes(point))) {
310
+ if (entry.cardinality !== "0:1" &&
311
+ entry.testPoints.some((point) => (counts.get(point) ?? 0) !== 1)) {
312
+ // Only ### 测试点 inventory must be bound exactly once. Extra
313
+ // automation-mapping TPs (typical: 横切 listed only under 跨切证据)
314
+ // are pytest-owned docstring/repair defects, not Markdown BLOCKED.
312
315
  markdownReasons.push("markdown-test-point-binding-invalid");
313
316
  }
314
317
  if (entry.declaredScript && entry.declaredScript !== entry.expectedScript) {
315
318
  markdownReasons.push("markdown-script-declaration-invalid");
316
319
  }
317
- if (entry.cardinality === "1:0")
320
+ const evidenceOnly = isEvidenceOnlyCorrespondenceEntry(entry);
321
+ if (!evidenceOnly && entry.cardinality === "1:0")
318
322
  pytestReasons.push("missing-pytest-symbol");
319
323
  if (entry.cardinality === "1:N")
320
324
  pytestReasons.push("multiple-pytest-symbols");
@@ -325,12 +329,15 @@ export function classifyBackendTestCorrespondenceRepair(facts, layout) {
325
329
  if (entry.declaredPrimarySymbol && entry.pytestSymbols.some((symbol) => symbol !== entry.declaredPrimarySymbol)) {
326
330
  pytestReasons.push("pytest-primary-symbol-mismatch");
327
331
  }
328
- if (entry.variantTestPoints.some((point) => !entry.parameterIds.includes(point)))
332
+ if (!evidenceOnly && entry.variantTestPoints.some((point) => !entry.parameterIds.includes(point))) {
329
333
  pytestReasons.push("variant-parameter-missing");
330
- if (entry.assertionTestPoints.some((point) => !entry.mappedTestPoints.includes(point)))
334
+ }
335
+ if (!evidenceOnly && entry.assertionTestPoints.some((point) => !entry.mappedTestPoints.includes(point))) {
331
336
  pytestReasons.push("assertion-binding-missing");
332
- if (entry.crossCuttingTestPoints.some((point) => !entry.mappedTestPoints.includes(point)))
337
+ }
338
+ if (!evidenceOnly && entry.crossCuttingTestPoints.some((point) => !entry.mappedTestPoints.includes(point))) {
333
339
  pytestReasons.push("cross-cutting-binding-missing");
340
+ }
334
341
  if (entry.parameterIds.some((point) => !entry.testPoints.includes(point)))
335
342
  pytestReasons.push("pytest-test-point-binding-extra");
336
343
  if (entry.payloadAssessment.status === "UNSAFE")
@@ -339,7 +346,7 @@ export function classifyBackendTestCorrespondenceRepair(facts, layout) {
339
346
  !["TEST_POINT_BINDING_DUPLICATE", "PAYLOAD_CONTRACT_UNAVAILABLE"].includes(entry.status)) {
340
347
  pytestReasons.push(`correspondence-${entry.status.toLowerCase().replaceAll("_", "-")}`);
341
348
  }
342
- if (entry.status === "PAYLOAD_CONTRACT_UNAVAILABLE") {
349
+ if (entry.status === "PAYLOAD_CONTRACT_UNAVAILABLE" && entry.cardinality !== "0:1") {
343
350
  markdownReasons.push("markdown-payload-contract-unavailable");
344
351
  }
345
352
  const paths = orderedUnique([entry.expectedScript, ...entry.actualScripts].filter(safePytestPath));
@@ -796,6 +803,16 @@ function isEvidenceOnlyMarkdownCase(testCase) {
796
803
  !hasVariant &&
797
804
  hasNonVariant);
798
805
  }
806
+ function isEvidenceOnlyCorrespondenceEntry(entry) {
807
+ return (entry.status === "EXACT_1_TO_1" &&
808
+ entry.cardinality === "1:0" &&
809
+ !entry.declaredScript &&
810
+ !entry.declaredPrimarySymbol &&
811
+ entry.variantTestPoints.length === 0 &&
812
+ (entry.assertionTestPoints.length > 0 || entry.crossCuttingTestPoints.length > 0) &&
813
+ entry.pytestSymbols.length === 0 &&
814
+ entry.actualScripts.length === 0);
815
+ }
799
816
  function payloadContract(body) {
800
817
  const contractValue = automationLabelBody(body, ["Payload Contract", "载荷合同"]);
801
818
  const requiredValue = automationLabelBody(body, ["Payload Required Paths", "载荷必需路径"]);
@@ -804,7 +821,7 @@ function payloadContract(body) {
804
821
  const tokens = (value) => orderedUnique(value
805
822
  .split(/[,;,;]/)
806
823
  .map((item) => item.trim())
807
- .filter((item) => !/^(?:none|无|not-applicable|n\/a)$/i.test(item))
824
+ .filter((item) => !/^(?:none|无|not-applicable|n\/a|true|false)$/i.test(item))
808
825
  .filter((item) => /^[A-Za-z_][A-Za-z0-9_.]*$/.test(item)));
809
826
  const enumValues = {};
810
827
  for (const assignment of enumValue.split(/[,;,;]/).map((item) => item.trim()).filter(Boolean)) {
@@ -1705,6 +1722,20 @@ def is_direct_transport_call(node):
1705
1722
  if isinstance(current,ast.Name): receiver_tokens.append(current.id.lower())
1706
1723
  return any(token in {'requests','httpx','client','session','api_client','http_client'} or token.endswith('_client') for token in receiver_tokens)
1707
1724
 
1725
+ HTTP_METHODS={'GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'}
1726
+ HTTP_FIXTURE_NAMES={'http','http_client','client','api_client','session'}
1727
+
1728
+ def is_fixture_http_call(node):
1729
+ # R12: http("POST", path, json_body={"title": title}) is a Name-call fixture, not client.post.
1730
+ # R15: http_client(operation, path, json_body={...}) still transports when operation is a parametrized method.
1731
+ if not isinstance(node,ast.Call) or not isinstance(node.func,ast.Name) or not node.args: return False
1732
+ first=node.args[0]
1733
+ if isinstance(first,ast.Constant) and isinstance(first.value,str) and first.value.upper() in HTTP_METHODS:
1734
+ return True
1735
+ func=node.func.id.lower()
1736
+ httpish=func in HTTP_FIXTURE_NAMES or func.endswith('_client')
1737
+ return httpish and isinstance(first,ast.Name)
1738
+
1708
1739
  def payload_nodes(fn):
1709
1740
  result=[]
1710
1741
  assigned={}
@@ -1727,7 +1758,7 @@ def payload_nodes(fn):
1727
1758
  # Observe local payload/body variables only when they flow into a direct
1728
1759
  # HTTP transport call. Arbitrary setup variables are not target requests.
1729
1760
  for node in ast.walk(fn):
1730
- if not is_direct_transport_call(node): continue
1761
+ if not (is_direct_transport_call(node) or is_fixture_http_call(node)): continue
1731
1762
  for keyword in node.keywords:
1732
1763
  if keyword.arg in PAYLOAD_NAMES: result.extend(payload_expr_nodes(keyword.value,assigned,parameters))
1733
1764
  return result