@tea-agent/loop-agent 0.10.0 → 0.12.0

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 (207) hide show
  1. package/AGENTS.md +10 -2
  2. package/CHANGELOG.md +91 -24
  3. package/README.md +84 -12
  4. package/dist/application/dag/args.js +1 -12
  5. package/dist/application/dag/generate-task-dag.js +38 -2
  6. package/dist/application/dag/run-dag.js +11 -27
  7. package/dist/application/dag/validate-dag.js +13 -2
  8. package/dist/application/loop/run-action.js +0 -4
  9. package/dist/cli/command-definitions.js +44 -16
  10. package/dist/cli/program.js +40 -23
  11. package/dist/cli/update/notifier.js +117 -0
  12. package/dist/cli/update/npm-client.js +151 -0
  13. package/dist/cli/update/policy.js +58 -0
  14. package/dist/cli/update/state.js +68 -0
  15. package/dist/cli.js +33 -0
  16. package/dist/commands/cursor-prompt.js +42 -82
  17. package/dist/commands/dag-approve.js +36 -0
  18. package/dist/commands/delegate.js +75 -77
  19. package/dist/commands/doctor.js +0 -18
  20. package/dist/commands/init.js +547 -95
  21. package/dist/commands/instructions.js +7 -10
  22. package/dist/commands/loop.js +4 -20
  23. package/dist/commands/plan.js +50 -0
  24. package/dist/executors/config-core.js +0 -51
  25. package/dist/executors/dag-pi-executor.js +1 -1
  26. package/dist/executors/dag.js +0 -1
  27. package/dist/executors/index.js +0 -2
  28. package/dist/executors/model-routing.js +9 -9
  29. package/dist/executors/shell-executor.js +1 -1
  30. package/dist/governance/checks.js +6 -3
  31. package/dist/governance/exec-plans.js +545 -0
  32. package/dist/governance/manifest-types.js +24 -2
  33. package/dist/infrastructure/harness/loop-action-store.js +0 -3
  34. package/dist/records/harvest.js +2 -23
  35. package/dist/records/one-shot-runs.js +1 -1
  36. package/dist/shared/artifacts-core.js +24 -5
  37. package/dist/shared/output-truncation.js +37 -0
  38. package/dist/shared/package-metadata.js +488 -0
  39. package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
  40. package/dist/sidecars/cursor-prompt/index.js +3 -0
  41. package/dist/sidecars/cursor-prompt/stream.js +121 -0
  42. package/dist/task/config-types.js +29 -12
  43. package/dist/task/delegate.js +9 -21
  44. package/dist/task/runtime.js +1 -2
  45. package/dist/worker/cli.js +32 -3
  46. package/dist/worker/delivery/final-verification.js +47 -11
  47. package/dist/worker/delivery/package.js +63 -10
  48. package/dist/worker/feature/run.js +60 -8
  49. package/dist/worker/loop-agent/loop-agent-client.js +329 -126
  50. package/dist/worker/observability/event-history.js +216 -0
  51. package/dist/worker/observability/read-model.js +338 -83
  52. package/dist/worker/observe/paths.js +17 -0
  53. package/dist/worker/observe/routes.js +165 -21
  54. package/dist/worker/observe/server.js +59 -1
  55. package/dist/worker/observe/static/api.js +27 -0
  56. package/dist/worker/observe/static/app.js +120 -2317
  57. package/dist/worker/observe/static/constants.js +148 -0
  58. package/dist/worker/observe/static/copy.js +67 -0
  59. package/dist/worker/observe/static/dag-helpers.js +172 -0
  60. package/dist/worker/observe/static/dag-model.js +72 -0
  61. package/dist/worker/observe/static/dom.js +61 -0
  62. package/dist/worker/observe/static/format-pool.js +67 -0
  63. package/dist/worker/observe/static/format.js +292 -0
  64. package/dist/worker/observe/static/index.html +300 -82
  65. package/dist/worker/observe/static/kpi.js +94 -0
  66. package/dist/worker/observe/static/relations.js +128 -0
  67. package/dist/worker/observe/static/router.js +85 -0
  68. package/dist/worker/observe/static/run-processing.js +148 -0
  69. package/dist/worker/observe/static/shell-chrome.js +68 -0
  70. package/dist/worker/observe/static/state.js +253 -0
  71. package/dist/worker/observe/static/styles.css +1720 -495
  72. package/dist/worker/observe/static/views/batch.js +226 -0
  73. package/dist/worker/observe/static/views/dag-graph.js +172 -0
  74. package/dist/worker/observe/static/views/dag-inspector.js +477 -0
  75. package/dist/worker/observe/static/views/dag.js +362 -0
  76. package/dist/worker/observe/static/views/dashboard.js +442 -0
  77. package/dist/worker/observe/static/views/failures.js +143 -0
  78. package/dist/worker/observe/static/views/feature.js +453 -0
  79. package/dist/worker/observe/static/views/pool.js +347 -0
  80. package/dist/worker/observe/static/views/run.js +453 -0
  81. package/dist/worker/observe/static/views/session-timeline.js +205 -0
  82. package/dist/worker/observe/static/views/shell.js +7 -0
  83. package/dist/worker/observe/static/views/task.js +260 -0
  84. package/dist/worker/observe/static/views/timeline.js +163 -0
  85. package/dist/worker/preflight.js +49 -1
  86. package/dist/worker/run-task/run-task.js +22 -12
  87. package/dist/worker/runner/run-ready.js +76 -12
  88. package/dist/worker/task-spec/schema.js +0 -1
  89. package/dist/workflows/dag/controller-identity.js +104 -0
  90. package/dist/workflows/dag/convergence/controller.js +1 -1
  91. package/dist/workflows/dag/executor-registry.js +0 -2
  92. package/dist/workflows/dag/init-hybrid.js +797 -27
  93. package/dist/workflows/dag/node-execution.js +183 -35
  94. package/dist/workflows/dag/repair-artifact.js +91 -0
  95. package/dist/workflows/dag/report.js +50 -0
  96. package/dist/workflows/dag/retry-policy.js +138 -0
  97. package/dist/workflows/dag/runner.js +77 -17
  98. package/dist/workflows/dag/runtime-contract.js +87 -0
  99. package/dist/workflows/dag/scheduler.js +7 -2
  100. package/dist/workflows/dag/sdd-embedded.js +128 -0
  101. package/dist/workflows/dag/skill-instructions.js +5 -4
  102. package/dist/workflows/dag/skill-snapshot.js +529 -0
  103. package/dist/workflows/dag/types.js +86 -10
  104. package/dist/workflows/dag/validate.js +73 -12
  105. package/dist/workflows/loop/actions/dag-action.js +0 -2
  106. package/dist/workflows/loop/actions/shared.js +1 -1
  107. package/dist/workflows/loop/actions.js +14 -31
  108. package/dist/workflows/loop/benchmark.js +1 -1
  109. package/dist/workflows/loop/index.js +1 -1
  110. package/dist/workflows/loop/policy/auto-policy.js +22 -14
  111. package/dist/workflows/loop/policy/path-patterns.js +13 -0
  112. package/docs/README.md +36 -33
  113. package/docs/agent-dag-recovery-playbook.md +1 -1
  114. package/docs/agent-dag-runner.md +28 -3
  115. package/docs/architecture/README.md +26 -0
  116. package/docs/architecture/dag-execution.md +140 -0
  117. package/docs/architecture/evolution.md +53 -0
  118. package/docs/architecture/facts-and-state.md +58 -0
  119. package/docs/architecture/runtime-boundaries.md +45 -17
  120. package/docs/architecture/system-overview.md +93 -0
  121. package/docs/architecture/worker-and-feature.md +81 -0
  122. package/docs/cursor-prompt-sidecar.md +36 -0
  123. package/docs/decisions/README.md +13 -1
  124. package/docs/design/README.md +43 -21
  125. package/docs/development-principles.md +2 -2
  126. package/docs/exec-plans/active/README.md +1 -3
  127. package/docs/exec-plans/completed/README.md +23 -0
  128. package/docs/feature-workflow.md +78 -4
  129. package/docs/harness-methodology-debugging.md +1 -1
  130. package/docs/harness-methodology-tdd.md +3 -3
  131. package/docs/init-surface.manifest.json +60 -25
  132. package/docs/loop-agent-harness.md +28 -4
  133. package/docs/progress/README.md +50 -1
  134. package/docs/reports/README.md +90 -18
  135. package/docs/skills/README.md +2 -1
  136. package/docs/skills/vetted-skill-registry.md +2 -1
  137. package/docs/templates/agent-dag-report.schema.json +23 -6
  138. package/docs/templates/agent-dag.base.json +0 -5
  139. package/docs/templates/agent-dag.final-verification.json +0 -5
  140. package/docs/templates/agent-dag.schema.json +70 -3
  141. package/docs/templates/agent-dag.supervised-implementation.json +9 -8
  142. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +139 -0
  143. package/docs/templates/backend-test-dag.json +276 -0
  144. package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -0
  145. package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -0
  146. package/docs/templates/frontend-design-contract.md +33 -0
  147. package/docs/templates/frontend-task-constraints.md +25 -0
  148. package/docs/templates/frontend-task-requirement.md +61 -0
  149. package/docs/templates/harness.schema.json +10 -12
  150. package/docs/templates/hybrid-dag.json +1 -6
  151. package/docs/templates/interactive-ui-round2-experiment.md +1 -1
  152. package/docs/templates/product-line/task.yaml +0 -1
  153. package/docs/templates/project-start-checklist.md +2 -2
  154. package/docs/templates/worker-dogfood-evidence.md +28 -0
  155. package/docs/templates/worker-dogfood-setup.md +20 -0
  156. package/docs/verification-matrix.md +10 -0
  157. package/examples/decision-gate-agent-dag.json +87 -33
  158. package/examples/example-dag.json +0 -5
  159. package/examples/hybrid-loop-agent-dag.json +0 -5
  160. package/harness.json +7 -15
  161. package/package.json +22 -46
  162. package/scripts/check-product-line-docs.sh +10 -7
  163. package/skills/agent-worker/SKILL.md +37 -0
  164. package/skills/agent-worker/references/agent-worker-operator.md +43 -0
  165. package/skills/frontend-design-review/SKILL.md +59 -0
  166. package/skills/frontend-design-review/references/review-checklist.md +37 -0
  167. package/skills/frontend-implementation/SKILL.md +51 -0
  168. package/skills/frontend-implementation/references/code-standards.md +34 -0
  169. package/skills/frontend-implementation/references/design-spec.md +46 -0
  170. package/skills/frontend-implementation/references/node-contracts.md +32 -0
  171. package/skills/frontend-review/SKILL.md +53 -0
  172. package/skills/frontend-review/references/review-findings.md +42 -0
  173. package/skills/frontend-verification/SKILL.md +40 -0
  174. package/skills/frontend-verification/references/verification-checklist.md +56 -0
  175. package/skills/grill-me/SKILL.md +10 -0
  176. package/skills/grill-with-docs/SKILL.md +88 -0
  177. package/skills/grill-with-docs/adr-format.md +47 -0
  178. package/skills/grill-with-docs/context-format.md +60 -0
  179. package/skills/loop-agent/SKILL.md +11 -9
  180. package/skills/loop-agent/references/command-reference.md +14 -15
  181. package/skills/loop-agent/references/docs-converge.md +126 -0
  182. package/skills/loop-agent/references/harness-policy.md +7 -7
  183. package/skills/loop-agent/references/hybrid-dag.md +36 -20
  184. package/skills/loop-agent/references/long-running-loop.md +4 -6
  185. package/skills/loop-agent/references/multi-worktree.md +6 -6
  186. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  187. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
  188. package/skills/loop-agent/references/task-workflow.md +1 -1
  189. package/skills/loop-agent/references/verification-and-failure-handling.md +6 -0
  190. package/skills/using-git-worktrees/SKILL.md +215 -0
  191. package/dist/commands/cursor-worker.js +0 -43
  192. package/dist/cursor-worker-entry.js +0 -8
  193. package/dist/executors/cursor-artifacts.js +0 -33
  194. package/dist/executors/cursor-execution-log.js +0 -81
  195. package/dist/executors/cursor-executor-artifacts.js +0 -134
  196. package/dist/executors/cursor-run.js +0 -115
  197. package/dist/executors/cursor-tool.js +0 -94
  198. package/dist/executors/cursor-worker-client.js +0 -223
  199. package/dist/executors/cursor-worker-protocol.js +0 -18
  200. package/dist/executors/cursor-worker-server.js +0 -54
  201. package/dist/executors/cursor-worker.js +0 -3
  202. package/dist/executors/cursor.js +0 -6
  203. package/dist/executors/dag-cursor-executor.js +0 -87
  204. package/dist/workflows/loop/actions/cursor-fix.js +0 -191
  205. package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
  206. package/docs/cursor-executor-usage.md +0 -25
  207. package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
@@ -0,0 +1,477 @@
1
+ /** DAG node inspector panel (Observe UI R5). */
2
+ import {
3
+ DAG_INSPECTOR_DEFAULT_WIDTH,
4
+ DAG_INSPECTOR_MIN_WIDTH,
5
+ DAG_INSPECTOR_MAX_WIDTH,
6
+ DAG_INSPECTOR_RIGHT_OFFSET,
7
+ DAG_INSPECTOR_VIEWPORT_MARGIN,
8
+ DAG_INSPECTOR_KEYBOARD_STEP,
9
+ UI_TEXT,
10
+ } from "../constants.js";
11
+ import { clearNode, el } from "../dom.js";
12
+ import { badge, parseMarkdownBlocks, markdownLinkHref } from "../format.js";
13
+ import {
14
+ uiState,
15
+ updateDagTimelineViewportState,
16
+ updateDagOutputViewportState,
17
+ computeFollowLatest,
18
+ isInteractiveGestureActive,
19
+ } from "../state.js";
20
+ import { renderSessionTimeline } from "./session-timeline.js";
21
+
22
+ export function selectDagNode(dagRunId, nodeId) {
23
+ const nodeChanged = uiState.selectedDagNodeId !== nodeId;
24
+ uiState.selectedDagNodeId = nodeId;
25
+ uiState.dagInspectorOpen = true;
26
+ if (nodeChanged) {
27
+ uiState.sessionEventOffset = 0;
28
+ uiState.sessionEvents = [];
29
+ }
30
+ void import("./dag.js").then((m) => m.renderDagDetail(dagRunId, false));
31
+ }
32
+
33
+ export function closeDagInspector() {
34
+ uiState.dagInspectorOpen = false;
35
+ const panel = document.getElementById("dag-inspector");
36
+ if (panel) {
37
+ panel.classList.remove("is-open");
38
+ panel.setAttribute("aria-hidden", "true");
39
+ }
40
+ }
41
+
42
+ export function closeDagInspectorOnOutsideClick(event) {
43
+ if (!uiState.dagInspectorOpen || !(event.target instanceof Element)) return;
44
+ if (
45
+ event.target.closest(
46
+ "#dag-inspector, .dag-node-table tbody tr, button, a, input, select, textarea, [role='button'], [role='tab']",
47
+ )
48
+ ) {
49
+ return;
50
+ }
51
+ closeDagInspector();
52
+ }
53
+
54
+ export function dagInspectorMaximumWidth() {
55
+ return Math.max(
56
+ DAG_INSPECTOR_MIN_WIDTH,
57
+ Math.min(
58
+ DAG_INSPECTOR_MAX_WIDTH,
59
+ window.innerWidth - DAG_INSPECTOR_VIEWPORT_MARGIN,
60
+ ),
61
+ );
62
+ }
63
+
64
+ export function setDagInspectorWidth(panel, requestedWidth) {
65
+ const maximum = dagInspectorMaximumWidth();
66
+ uiState.dagInspectorWidth = Math.min(
67
+ maximum,
68
+ Math.max(DAG_INSPECTOR_MIN_WIDTH, Math.round(requestedWidth)),
69
+ );
70
+ panel.style.setProperty(
71
+ "--dag-inspector-width",
72
+ `${uiState.dagInspectorWidth}px`,
73
+ );
74
+ return {
75
+ minimum: DAG_INSPECTOR_MIN_WIDTH,
76
+ maximum,
77
+ value: uiState.dagInspectorWidth,
78
+ };
79
+ }
80
+
81
+ export function createDagInspectorResizeHandle(panel) {
82
+ const handle = el("div", "dag-inspector-resize-handle");
83
+ handle.tabIndex = 0;
84
+ handle.setAttribute("role", "separator");
85
+ handle.setAttribute("aria-orientation", "vertical");
86
+ handle.setAttribute("aria-label", "调整节点检查器宽度");
87
+ handle.title = "拖动调整节点检查器宽度";
88
+
89
+ const updateRange = (requestedWidth) => {
90
+ const range = setDagInspectorWidth(
91
+ panel,
92
+ requestedWidth ??
93
+ uiState.dagInspectorWidth ??
94
+ DAG_INSPECTOR_DEFAULT_WIDTH,
95
+ );
96
+ handle.setAttribute("aria-valuemin", String(range.minimum));
97
+ handle.setAttribute("aria-valuemax", String(range.maximum));
98
+ handle.setAttribute("aria-valuenow", String(range.value));
99
+ };
100
+
101
+ updateRange();
102
+ handle.addEventListener("pointerdown", (event) => {
103
+ if (event.button !== 0) return;
104
+ event.preventDefault();
105
+ handle.setPointerCapture(event.pointerId);
106
+ document.body.classList.add("is-resizing-dag-inspector");
107
+ });
108
+ handle.addEventListener("pointermove", (event) => {
109
+ if (!handle.hasPointerCapture(event.pointerId)) return;
110
+ updateRange(window.innerWidth - event.clientX - DAG_INSPECTOR_RIGHT_OFFSET);
111
+ });
112
+ const stopResizing = (event) => {
113
+ if (handle.hasPointerCapture(event.pointerId)) {
114
+ handle.releasePointerCapture(event.pointerId);
115
+ }
116
+ document.body.classList.remove("is-resizing-dag-inspector");
117
+ };
118
+ handle.addEventListener("pointerup", stopResizing);
119
+ handle.addEventListener("pointercancel", stopResizing);
120
+ handle.addEventListener("keydown", (event) => {
121
+ const currentWidth =
122
+ uiState.dagInspectorWidth ?? DAG_INSPECTOR_DEFAULT_WIDTH;
123
+ if (event.key === "ArrowLeft") {
124
+ event.preventDefault();
125
+ updateRange(currentWidth + DAG_INSPECTOR_KEYBOARD_STEP);
126
+ } else if (event.key === "ArrowRight") {
127
+ event.preventDefault();
128
+ updateRange(currentWidth - DAG_INSPECTOR_KEYBOARD_STEP);
129
+ } else if (event.key === "Home") {
130
+ event.preventDefault();
131
+ updateRange(DAG_INSPECTOR_MIN_WIDTH);
132
+ } else if (event.key === "End") {
133
+ event.preventDefault();
134
+ updateRange(dagInspectorMaximumWidth());
135
+ }
136
+ });
137
+ return handle;
138
+ }
139
+
140
+ export function captureDagTimelineViewportState(panel) {
141
+ const scrollEl = panel.querySelector(".process-timeline-scroll");
142
+ if (scrollEl?.dataset.dagRunId && scrollEl.dataset.nodeId) {
143
+ const followLatest = computeFollowLatest(
144
+ scrollEl.scrollTop,
145
+ scrollEl.scrollHeight,
146
+ scrollEl.clientHeight,
147
+ );
148
+ uiState.dagTimelineViewportState = updateDagTimelineViewportState(
149
+ uiState.dagTimelineViewportState,
150
+ scrollEl.dataset.dagRunId,
151
+ scrollEl.dataset.nodeId,
152
+ scrollEl.scrollLeft,
153
+ scrollEl.scrollTop,
154
+ followLatest,
155
+ );
156
+ }
157
+ }
158
+
159
+ export function appendInlineMarkdown(target, text) {
160
+ const tokenPattern = /(`[^`]*`|\*\*[^*]+\*\*|\*[^*]+\*|\[[^\]]+\]\([^)]+\))/g;
161
+ let cursor = 0;
162
+ for (const match of text.matchAll(tokenPattern)) {
163
+ const index = match.index ?? 0;
164
+ if (index > cursor)
165
+ target.appendChild(document.createTextNode(text.slice(cursor, index)));
166
+ const token = match[0];
167
+ if (token.startsWith("`")) {
168
+ target.appendChild(el("code", null, token.slice(1, -1)));
169
+ } else if (token.startsWith("**")) {
170
+ const strong = document.createElement("strong");
171
+ strong.textContent = token.slice(2, -2);
172
+ target.appendChild(strong);
173
+ } else if (token.startsWith("*")) {
174
+ const emphasis = document.createElement("em");
175
+ emphasis.textContent = token.slice(1, -1);
176
+ target.appendChild(emphasis);
177
+ } else {
178
+ const link = token.match(/^\[([^\]]+)\]\((.+)\)$/);
179
+ const href = markdownLinkHref(link?.[2]);
180
+ if (link && href) {
181
+ const anchor = document.createElement("a");
182
+ anchor.href = href;
183
+ anchor.target = "_blank";
184
+ anchor.rel = "noopener noreferrer";
185
+ anchor.textContent = link[1];
186
+ target.appendChild(anchor);
187
+ } else {
188
+ target.appendChild(document.createTextNode(token));
189
+ }
190
+ }
191
+ cursor = index + token.length;
192
+ }
193
+ if (cursor < text.length)
194
+ target.appendChild(document.createTextNode(text.slice(cursor)));
195
+ }
196
+
197
+ export function renderMarkdown(content) {
198
+ const article = el("article", "markdown-output");
199
+ for (const block of parseMarkdownBlocks(content)) {
200
+ switch (block.type) {
201
+ case "heading": {
202
+ const heading = document.createElement(`h${block.level}`);
203
+ appendInlineMarkdown(heading, block.text);
204
+ article.appendChild(heading);
205
+ break;
206
+ }
207
+ case "paragraph": {
208
+ const paragraph = document.createElement("p");
209
+ appendInlineMarkdown(paragraph, block.text);
210
+ article.appendChild(paragraph);
211
+ break;
212
+ }
213
+ case "quote": {
214
+ const quote = document.createElement("blockquote");
215
+ appendInlineMarkdown(quote, block.text);
216
+ article.appendChild(quote);
217
+ break;
218
+ }
219
+ case "list": {
220
+ const list = document.createElement(block.ordered ? "ol" : "ul");
221
+ for (const item of block.items) {
222
+ const listItem = document.createElement("li");
223
+ appendInlineMarkdown(listItem, item);
224
+ list.appendChild(listItem);
225
+ }
226
+ article.appendChild(list);
227
+ break;
228
+ }
229
+ case "code": {
230
+ const pre = document.createElement("pre");
231
+ const code = document.createElement("code");
232
+ if (block.language) code.dataset.language = block.language;
233
+ code.textContent = block.text;
234
+ pre.appendChild(code);
235
+ article.appendChild(pre);
236
+ break;
237
+ }
238
+ case "table": {
239
+ const table = document.createElement("table");
240
+ const head = document.createElement("thead");
241
+ const headRow = document.createElement("tr");
242
+ for (const value of block.headers) {
243
+ const cell = document.createElement("th");
244
+ appendInlineMarkdown(cell, value);
245
+ headRow.appendChild(cell);
246
+ }
247
+ head.appendChild(headRow);
248
+ table.appendChild(head);
249
+ const body = document.createElement("tbody");
250
+ for (const row of block.rows) {
251
+ const tableRow = document.createElement("tr");
252
+ for (const value of row) {
253
+ const cell = document.createElement("td");
254
+ appendInlineMarkdown(cell, value);
255
+ tableRow.appendChild(cell);
256
+ }
257
+ body.appendChild(tableRow);
258
+ }
259
+ table.appendChild(body);
260
+ article.appendChild(table);
261
+ break;
262
+ }
263
+ case "rule":
264
+ article.appendChild(document.createElement("hr"));
265
+ break;
266
+ }
267
+ }
268
+ return article;
269
+ }
270
+
271
+ function fillDagInspectorContent(content, dagRunId, node) {
272
+ const activeTab =
273
+ uiState.dagInspectorTab === "timeline" ? "timeline" : "output";
274
+ content.id = `dag-inspector-panel-${activeTab}`;
275
+ content.setAttribute("role", "tabpanel");
276
+ content.setAttribute("aria-labelledby", `dag-inspector-tab-${activeTab}`);
277
+ content.dataset.dagNodeId = String(node.nodeId ?? "");
278
+ if (activeTab === "timeline") {
279
+ renderSessionTimeline(content, node.nodeId);
280
+ } else if (!node.outputPreview && !node.errorPreview) {
281
+ content.appendChild(el("p", "empty", UI_TEXT.noOutput));
282
+ } else {
283
+ if (node.outputPreview)
284
+ content.appendChild(renderMarkdown(node.outputPreview));
285
+ if (node.errorPreview) {
286
+ const error = el("section", "markdown-error");
287
+ error.appendChild(el("p", "markdown-error-title", "错误输出"));
288
+ error.appendChild(renderMarkdown(node.errorPreview));
289
+ content.appendChild(error);
290
+ }
291
+ }
292
+ }
293
+
294
+ function buildDagInspectorHeader(dagRunId, node) {
295
+ const header = el("div", "dag-inspector-header");
296
+ header.dataset.dagRunId = String(dagRunId ?? "");
297
+ header.dataset.dagNodeId = String(node.nodeId ?? "");
298
+ const title = el("div", "dag-inspector-title");
299
+ title.appendChild(el("h3", null, "节点检查器"));
300
+ title.appendChild(el("div", "dag-inspector-node", node.nodeId));
301
+ header.appendChild(title);
302
+ const controls = el("div", "dag-inspector-controls");
303
+ const status = badge(node.status ?? "unknown");
304
+ status.classList.add("dag-inspector-status");
305
+ controls.appendChild(status);
306
+ const close = document.createElement("button");
307
+ close.type = "button";
308
+ close.className = "dag-inspector-close";
309
+ close.setAttribute("aria-label", "关闭节点检查器");
310
+ const closeIcon = el("i", "ri-close-line");
311
+ closeIcon.setAttribute("aria-hidden", "true");
312
+ close.appendChild(closeIcon);
313
+ close.addEventListener("click", closeDagInspector);
314
+ controls.appendChild(close);
315
+ header.appendChild(controls);
316
+
317
+ const tabDefs = [
318
+ ["output", "节点输出", "ri-file-text-line"],
319
+ ["timeline", "执行过程", "ri-route-line"],
320
+ ];
321
+ const tabs = el("div", "dag-inspector-tabs");
322
+ tabs.setAttribute("role", "tablist");
323
+ tabs.setAttribute("aria-label", "节点检查器面板");
324
+ for (const [tab, label, icon] of tabDefs) {
325
+ const selected = uiState.dagInspectorTab === tab;
326
+ const button = document.createElement("button");
327
+ button.type = "button";
328
+ button.id = `dag-inspector-tab-${tab}`;
329
+ button.className = "dag-inspector-tab";
330
+ button.setAttribute("role", "tab");
331
+ button.setAttribute("aria-controls", `dag-inspector-panel-${tab}`);
332
+ button.setAttribute("aria-selected", String(selected));
333
+ button.tabIndex = selected ? 0 : -1;
334
+ button.dataset.tab = tab;
335
+ const tabIcon = el("i", icon);
336
+ tabIcon.setAttribute("aria-hidden", "true");
337
+ button.appendChild(tabIcon);
338
+ button.appendChild(document.createTextNode(` ${label}`));
339
+ button.addEventListener("click", () => {
340
+ uiState.dagInspectorTab = tab;
341
+ renderDagInspector(dagRunId, node, false);
342
+ const next = document.getElementById(`dag-inspector-tab-${tab}`);
343
+ next?.focus();
344
+ });
345
+ button.addEventListener("keydown", (event) => {
346
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
347
+ event.preventDefault();
348
+ event.stopPropagation();
349
+ const order = tabDefs.map(([id]) => id);
350
+ const idx = order.indexOf(tab);
351
+ if (idx < 0) return;
352
+ const delta = event.key === "ArrowRight" ? 1 : -1;
353
+ const nextTab = order[(idx + delta + order.length) % order.length];
354
+ uiState.dagInspectorTab = nextTab;
355
+ renderDagInspector(dagRunId, node, false);
356
+ document.getElementById(`dag-inspector-tab-${nextTab}`)?.focus();
357
+ });
358
+ tabs.appendChild(button);
359
+ }
360
+ header.appendChild(tabs);
361
+ return header;
362
+ }
363
+
364
+ function syncDagInspectorHeader(panel, dagRunId, node) {
365
+ const header = panel.querySelector(".dag-inspector-header");
366
+ if (!header) return;
367
+ header.dataset.dagRunId = String(dagRunId ?? "");
368
+ header.dataset.dagNodeId = String(node.nodeId ?? "");
369
+ const nodeLabel = header.querySelector(".dag-inspector-node");
370
+ if (nodeLabel) nodeLabel.textContent = node.nodeId;
371
+ const status = header.querySelector(".dag-inspector-status");
372
+ if (status) {
373
+ const next = badge(node.status ?? "unknown");
374
+ next.classList.add("dag-inspector-status");
375
+ status.replaceWith(next);
376
+ }
377
+ for (const button of header.querySelectorAll(".dag-inspector-tab")) {
378
+ const selected = button.dataset.tab === uiState.dagInspectorTab;
379
+ button.setAttribute("aria-selected", String(selected));
380
+ button.tabIndex = selected ? 0 : -1;
381
+ }
382
+ }
383
+
384
+ function dagInspectorSignature(dagRunId, node) {
385
+ return JSON.stringify([
386
+ dagRunId,
387
+ node.nodeId,
388
+ uiState.dagInspectorTab,
389
+ node.outputPreview ?? "",
390
+ node.errorPreview ?? "",
391
+ uiState.dagInspectorTab === "timeline" ? uiState.sessionEventOffset : null,
392
+ ]);
393
+ }
394
+
395
+ export function renderDagInspector(dagRunId, node, initial = true) {
396
+ const panel = document.getElementById("dag-inspector");
397
+ if (!panel) return;
398
+ captureDagTimelineViewportState(panel);
399
+ if (!uiState.dagInspectorOpen || !node) {
400
+ panel.classList.remove("is-open");
401
+ panel.setAttribute("aria-hidden", "true");
402
+ return;
403
+ }
404
+
405
+ // Defer tearing down the inspector shell while the user is actively
406
+ // dragging the resize handle (or the graph) so the pointer-capture target
407
+ // is not destroyed mid-gesture; the next poll refreshes the content.
408
+ if (isInteractiveGestureActive(document.body.classList)) {
409
+ return;
410
+ }
411
+
412
+ const existingContent = panel.querySelector(".dag-inspector-content") ?? null;
413
+ const reuseShell = !initial && Boolean(existingContent);
414
+ const nextContentSignature = dagInspectorSignature(dagRunId, node);
415
+
416
+ if (reuseShell && existingContent) {
417
+ panel.classList.add("is-open");
418
+ panel.setAttribute("aria-hidden", "false");
419
+ syncDagInspectorHeader(panel, dagRunId, node);
420
+ if (panel.dataset.contentSignature === nextContentSignature) return;
421
+ const previousNode = existingContent.dataset.dagNodeId ?? "";
422
+ const nodeChanged = previousNode !== String(node.nodeId ?? "");
423
+ const followLatest =
424
+ nodeChanged ||
425
+ computeFollowLatest(
426
+ existingContent.scrollTop,
427
+ existingContent.scrollHeight,
428
+ existingContent.clientHeight,
429
+ );
430
+ const regionKey = `dag:${dagRunId}:${node.nodeId}:output`;
431
+ uiState.dagNodeOutputViewportState = updateDagOutputViewportState(
432
+ uiState.dagNodeOutputViewportState,
433
+ regionKey,
434
+ existingContent.scrollLeft,
435
+ existingContent.scrollTop,
436
+ followLatest,
437
+ );
438
+ const nestedViewportState = [
439
+ ...existingContent.querySelectorAll("pre"),
440
+ ].map((pre) => ({
441
+ scrollLeft: pre.scrollLeft,
442
+ scrollTop: pre.scrollTop,
443
+ }));
444
+ clearNode(existingContent);
445
+ fillDagInspectorContent(existingContent, dagRunId, node);
446
+ if (followLatest) {
447
+ existingContent.scrollTop = existingContent.scrollHeight;
448
+ } else {
449
+ existingContent.scrollTop = uiState.dagNodeOutputViewportState.scrollTop;
450
+ }
451
+ for (const [index, pre] of [
452
+ ...existingContent.querySelectorAll("pre"),
453
+ ].entries()) {
454
+ const previous = nestedViewportState[index];
455
+ if (!previous) continue;
456
+ pre.scrollLeft = previous.scrollLeft;
457
+ pre.scrollTop = previous.scrollTop;
458
+ }
459
+ panel.dataset.contentSignature = nextContentSignature;
460
+ return;
461
+ }
462
+
463
+ clearNode(panel);
464
+ panel.classList.add("is-open");
465
+ panel.setAttribute("aria-hidden", "false");
466
+ panel.appendChild(createDagInspectorResizeHandle(panel));
467
+ panel.appendChild(buildDagInspectorHeader(dagRunId, node));
468
+
469
+ const content = el("div", "dag-inspector-content");
470
+ fillDagInspectorContent(content, dagRunId, node);
471
+ panel.appendChild(content);
472
+ panel.dataset.contentSignature = nextContentSignature;
473
+ }
474
+
475
+ /**
476
+ * Bounded event history UI. Uses opaque `cursor`/`nextCursor` (not SSE/run `after` offsets).
477
+ */