@tea-agent/loop-agent 0.26.0 → 0.26.2
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.
- package/CHANGELOG.md +1056 -1020
- package/README.md +8 -3
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/generate-task-dag.js +33 -0
- package/dist/cli/command-definitions.js +25 -10
- package/dist/cli/help.js +4 -3
- package/dist/cli/program.js +43 -17
- package/dist/commands/cursor-prompt.js +6 -6
- package/dist/commands/import-prd.js +7 -2
- package/dist/commands/init.js +7 -5
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/commands/task-source-prepare.js +474 -0
- package/dist/executors/dag-pi-executor.js +40 -5
- package/dist/executors/shell-executor.js +111 -0
- package/dist/executors/shell-presets.js +12 -4
- package/dist/executors/shell-write-guard.js +161 -25
- package/dist/sidecars/cursor-prompt/executor.js +1 -1
- package/dist/task/config-types.js +6 -0
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/project.js +8 -0
- package/dist/task/contract/schema.js +1 -0
- package/dist/task/frontend-preflight.js +131 -0
- package/dist/task/runtime.js +2 -4
- package/dist/task/source-prepare/build-draft.js +224 -0
- package/dist/task/source-prepare/completeness.js +195 -0
- package/dist/task/source-prepare/index.js +7 -0
- package/dist/task/source-prepare/parse-intent.js +373 -0
- package/dist/task/source-prepare/path-policy.js +197 -0
- package/dist/task/source-prepare/prepare.js +506 -0
- package/dist/task/source-prepare/reference-integrity.js +274 -0
- package/dist/task/source-prepare/types.js +7 -0
- package/dist/worker/observability/read-model.js +134 -0
- package/dist/worker/observe/static/copy.js +67 -67
- package/dist/worker/observe/static/dag-layout.d.ts +31 -31
- package/dist/worker/observe/static/dag-layout.js +83 -83
- package/dist/worker/observe/static/dom.js +220 -220
- package/dist/worker/observe/static/relations.js +133 -133
- package/dist/worker/observe/static/router.js +93 -93
- package/dist/worker/observe/static/run-processing.js +148 -148
- package/dist/worker/observe/static/state.js +61 -0
- package/dist/worker/observe/static/styles.css +8 -0
- package/dist/worker/observe/static/views/batch.js +227 -227
- package/dist/worker/observe/static/views/dag-graph.js +248 -172
- package/dist/worker/observe/static/views/dag-inspector.js +374 -157
- package/dist/worker/observe/static/views/dag.js +4 -11
- package/dist/worker/observe/static/views/failures.js +143 -143
- package/dist/worker/observe/static/views/feature.js +492 -492
- package/dist/worker/observe/static/views/run.js +453 -453
- package/dist/worker/observe/static/views/shell.js +7 -7
- package/dist/worker/observe/static/views/timeline.js +163 -163
- package/dist/workflows/dag/backend-test-pytest-collection.js +277 -0
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/convergence/controller.js +110 -21
- package/dist/workflows/dag/frontend-implementation-contract.js +218 -17
- package/dist/workflows/dag/frontend-review-context.js +7 -1
- package/dist/workflows/dag/frontend-verification-trace.js +14 -3
- package/dist/workflows/dag/frontend-worktree-diff.js +14 -3
- package/dist/workflows/dag/init-hybrid.js +96 -34
- package/dist/workflows/dag/output-protocol.js +180 -7
- package/dist/workflows/dag/runner.js +141 -52
- package/dist/workflows/dag/types.js +4 -0
- package/dist/workflows/dag/validate.js +3 -2
- package/docs/skills/README.md +7 -7
- package/docs/templates/adr.md +60 -60
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
- package/docs/templates/agent-dag-report.schema.json +473 -473
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/backend-test-dag.json +100 -8
- package/docs/templates/backend-test-result.schema.json +99 -99
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/frontend-design-contract.md +42 -42
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
- package/docs/templates/frontend-eval/metrics.md +138 -138
- package/docs/templates/frontend-eval/smoke-targets.md +53 -53
- package/docs/templates/frontend-task-constraints.md +35 -35
- package/docs/templates/frontend-task-requirement.md +70 -70
- package/docs/templates/init-evolution-review.md +35 -35
- package/docs/templates/init-managed-agents.md +10 -5
- package/docs/templates/interactive-ui-round2-experiment.md +66 -66
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
- package/docs/templates/knowledge-sync-dag.json +178 -178
- package/docs/templates/knowledge-sync-draft.schema.json +71 -71
- package/docs/templates/product-line/closeout.yaml +9 -9
- package/docs/templates/product-line/design.md +13 -13
- package/docs/templates/product-line/links.md +10 -10
- package/docs/templates/product-line/requirement.md +17 -17
- package/docs/templates/product-line/test-plan.md +7 -7
- package/docs/templates/production-readiness-checklist.md +57 -57
- package/docs/templates/project-start-checklist.md +9 -9
- package/docs/templates/qa-report.md +48 -48
- package/docs/templates/sprint-contract.md +29 -29
- package/docs/templates/worker-dogfood-evidence.md +80 -80
- package/docs/templates/worker-dogfood-setup.md +68 -68
- package/package.json +1 -1
- package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
- package/scripts/kb-graph-incremental-prepare.mjs +386 -386
- package/scripts/kb-graph-materialize.mjs +105 -105
- package/scripts/kb-graph-promote.mjs +164 -164
- package/scripts/kb-query.mjs +554 -554
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/analyze-product-dependencies/SKILL.md +67 -67
- package/skills/analyze-product-dependencies/agents/openai.yaml +4 -4
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -30
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -28
- package/skills/analyze-product-dependencies/references/example.md +76 -76
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -35
- package/skills/analyze-product-dependencies/references/input-contract.md +11 -11
- package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -61
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -267
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -101
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -142
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -76
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -146
- package/skills/analyze-product-requirements/SKILL.md +90 -90
- package/skills/analyze-product-requirements/agents/openai.yaml +4 -4
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -91
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -56
- package/skills/analyze-product-requirements/references/example.md +86 -86
- package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -66
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -32
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -33
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -35
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -193
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -69
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -97
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -98
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -156
- package/skills/browser-tools/browser-content.js +103 -103
- package/skills/browser-tools/browser-cookies.js +35 -35
- package/skills/browser-tools/browser-eval.js +53 -53
- package/skills/browser-tools/browser-hn-scraper.js +108 -108
- package/skills/browser-tools/browser-nav.js +44 -44
- package/skills/browser-tools/browser-pick.js +162 -162
- package/skills/browser-tools/browser-screenshot.js +34 -34
- package/skills/browser-tools/browser-start.js +86 -86
- package/skills/browser-tools/package-lock.json +2556 -2556
- package/skills/browser-tools/package.json +19 -19
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/grill-me/SKILL.md +10 -10
- package/skills/loop-agent/SKILL.md +5 -2
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +17 -15
- package/skills/loop-agent/references/docs-converge.md +126 -126
- package/skills/loop-agent/references/harness-policy.md +3 -4
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/long-running-loop.md +57 -57
- package/skills/loop-agent/references/one-shot-runs.md +85 -85
- package/skills/loop-agent/references/pi-prompt.md +23 -23
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
- package/skills/loop-agent/references/post-implementation-and-patterns.md +1 -1
- package/skills/loop-agent/references/source-and-plan-practice.md +3 -2
- package/skills/loop-agent/references/task-workflow.md +7 -5
- package/skills/playwright-cli/SKILL.md +420 -420
- package/skills/playwright-cli/references/element-attributes.md +23 -23
- package/skills/playwright-cli/references/playwright-tests.md +39 -39
- package/skills/playwright-cli/references/request-mocking.md +87 -87
- package/skills/playwright-cli/references/running-code.md +241 -241
- package/skills/playwright-cli/references/session-management.md +225 -225
- package/skills/playwright-cli/references/storage-state.md +275 -275
- package/skills/playwright-cli/references/test-generation.md +433 -433
- package/skills/playwright-cli/references/tracing.md +139 -139
- package/skills/playwright-cli/references/video-recording.md +143 -143
- package/skills/requesting-code-review/SKILL.md +101 -101
- package/skills/requesting-code-review/code-reviewer.md +168 -168
- package/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/systematic-debugging/find-polluter.sh +63 -63
- package/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/systematic-debugging/test-academic.md +14 -14
- package/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/using-git-worktrees/SKILL.md +215 -215
- package/skills/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -19
|
@@ -23,6 +23,9 @@ import {
|
|
|
23
23
|
updateDagOutputViewportState,
|
|
24
24
|
computeFollowLatest,
|
|
25
25
|
isInteractiveGestureActive,
|
|
26
|
+
inspectorViewportRegionKey,
|
|
27
|
+
saveDagInspectorViewportState,
|
|
28
|
+
getDagInspectorViewportState,
|
|
26
29
|
} from "../state.js";
|
|
27
30
|
import {
|
|
28
31
|
formatSessionEventTime,
|
|
@@ -37,7 +40,11 @@ const inspectorSelection = { dagRunId: null, node: null };
|
|
|
37
40
|
|
|
38
41
|
function renderDagInspectorForCurrent() {
|
|
39
42
|
if (!inspectorSelection.dagRunId || !inspectorSelection.node) return;
|
|
40
|
-
renderDagInspector(
|
|
43
|
+
renderDagInspector(
|
|
44
|
+
inspectorSelection.dagRunId,
|
|
45
|
+
inspectorSelection.node,
|
|
46
|
+
false,
|
|
47
|
+
);
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
export function selectDagNode(dagRunId, nodeId) {
|
|
@@ -132,9 +139,7 @@ export function createDagInspectorResizeHandle(panel) {
|
|
|
132
139
|
const updateRange = (requestedWidth) => {
|
|
133
140
|
const range = setDagInspectorWidth(
|
|
134
141
|
panel,
|
|
135
|
-
requestedWidth ??
|
|
136
|
-
uiState.dagInspectorWidth ??
|
|
137
|
-
dagInspectorDefaultWidth(),
|
|
142
|
+
requestedWidth ?? uiState.dagInspectorWidth ?? dagInspectorDefaultWidth(),
|
|
138
143
|
);
|
|
139
144
|
handle.setAttribute("aria-valuemin", String(range.minimum));
|
|
140
145
|
handle.setAttribute("aria-valuemax", String(range.maximum));
|
|
@@ -200,16 +205,27 @@ export function captureDagTimelineViewportState(panel) {
|
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
203
|
-
const evidence =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
208
|
+
const evidence =
|
|
209
|
+
cachedEvidence ??
|
|
210
|
+
(await fetchJson(
|
|
211
|
+
`/api/dag-runs/${encodeURIComponent(dagRunId)}/nodes/${encodeURIComponent(nodeId)}/spec-evidence`,
|
|
212
|
+
));
|
|
213
|
+
if (
|
|
214
|
+
!content.isConnected ||
|
|
215
|
+
content.dataset.dagNodeId !== String(nodeId) ||
|
|
216
|
+
content.dataset.dagRunId !== String(dagRunId) ||
|
|
217
|
+
uiState.dagInspectorTab !== "spec-evidence"
|
|
218
|
+
) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
207
221
|
if (uiState.specEvidenceDetail) {
|
|
208
222
|
renderSpecEvidenceDetail(content, dagRunId, nodeId);
|
|
223
|
+
restoreInspectorContentScroll(content, dagRunId, nodeId, "spec-evidence");
|
|
209
224
|
return;
|
|
210
225
|
}
|
|
211
226
|
if (!evidence) {
|
|
212
227
|
content.appendChild(el("p", "empty", "无法加载规范证据。"));
|
|
228
|
+
restoreInspectorContentScroll(content, dagRunId, nodeId, "spec-evidence");
|
|
213
229
|
return;
|
|
214
230
|
}
|
|
215
231
|
uiState.specEvidenceListEvidence = evidence;
|
|
@@ -238,7 +254,12 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
238
254
|
}
|
|
239
255
|
content.appendChild(statusSection);
|
|
240
256
|
|
|
241
|
-
const appendListSection = (
|
|
257
|
+
const appendListSection = (
|
|
258
|
+
title,
|
|
259
|
+
entries,
|
|
260
|
+
renderEntry,
|
|
261
|
+
emptyMessage = "",
|
|
262
|
+
) => {
|
|
242
263
|
if (!entries?.length && !emptyMessage) return;
|
|
243
264
|
const section = el("div", "spec-evidence-section");
|
|
244
265
|
section.appendChild(el("h4", null, title));
|
|
@@ -253,36 +274,55 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
253
274
|
content.appendChild(section);
|
|
254
275
|
};
|
|
255
276
|
|
|
256
|
-
appendListSection(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
277
|
+
appendListSection(
|
|
278
|
+
"注入的 Skill",
|
|
279
|
+
evidence.skillInjection?.skills,
|
|
280
|
+
(skill) => {
|
|
281
|
+
const item = el("li", null);
|
|
282
|
+
const icon = el("i", "ri-bookmark-line");
|
|
283
|
+
icon.setAttribute("aria-hidden", "true");
|
|
284
|
+
item.append(icon, document.createTextNode(` ${skill}`));
|
|
285
|
+
return item;
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
appendListSection(
|
|
289
|
+
"已绑定任务源",
|
|
290
|
+
evidence.sourceBinding?.sources,
|
|
291
|
+
(source) => {
|
|
292
|
+
const item = el("li", null);
|
|
293
|
+
const button = document.createElement("button");
|
|
294
|
+
button.type = "button";
|
|
295
|
+
button.className = "spec-evidence-file-btn";
|
|
296
|
+
button.setAttribute("data-source", "binding");
|
|
297
|
+
button.setAttribute("data-path", source.path);
|
|
298
|
+
button.id = `spec-evidence-btn-binding-${source.path}`;
|
|
299
|
+
const icon = el("i", "ri-links-line");
|
|
300
|
+
icon.setAttribute("aria-hidden", "true");
|
|
301
|
+
button.append(
|
|
302
|
+
icon,
|
|
303
|
+
document.createTextNode(` ${source.kind}: `),
|
|
304
|
+
el("code", null, source.path),
|
|
305
|
+
document.createTextNode(` · sha256 ${source.sha256.slice(0, 12)}…`),
|
|
306
|
+
);
|
|
307
|
+
button.addEventListener("click", () =>
|
|
308
|
+
onSpecEvidenceFileClick(
|
|
309
|
+
content,
|
|
310
|
+
dagRunId,
|
|
311
|
+
nodeId,
|
|
312
|
+
"binding",
|
|
313
|
+
source.path,
|
|
314
|
+
button,
|
|
315
|
+
),
|
|
316
|
+
);
|
|
317
|
+
item.appendChild(button);
|
|
318
|
+
return item;
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
appendListSection(
|
|
322
|
+
"显式需求编号",
|
|
323
|
+
evidence.sourceBinding?.requirementIds,
|
|
324
|
+
(id) => el("li", null, id),
|
|
325
|
+
);
|
|
286
326
|
const openspecReadPaths = new Set(
|
|
287
327
|
(evidence.openspecReads ?? []).map((entry) => entry.path),
|
|
288
328
|
);
|
|
@@ -316,7 +356,14 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
316
356
|
);
|
|
317
357
|
}
|
|
318
358
|
button.addEventListener("click", () =>
|
|
319
|
-
onSpecEvidenceFileClick(
|
|
359
|
+
onSpecEvidenceFileClick(
|
|
360
|
+
content,
|
|
361
|
+
dagRunId,
|
|
362
|
+
nodeId,
|
|
363
|
+
"read",
|
|
364
|
+
read.path,
|
|
365
|
+
button,
|
|
366
|
+
),
|
|
320
367
|
);
|
|
321
368
|
item.appendChild(button);
|
|
322
369
|
return item;
|
|
@@ -329,7 +376,10 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
329
376
|
const item = el("li", null);
|
|
330
377
|
const icon = el("i", "ri-search-line");
|
|
331
378
|
icon.setAttribute("aria-hidden", "true");
|
|
332
|
-
item.append(
|
|
379
|
+
item.append(
|
|
380
|
+
icon,
|
|
381
|
+
document.createTextNode(` ${search.tool}: ${search.query}`),
|
|
382
|
+
);
|
|
333
383
|
return item;
|
|
334
384
|
},
|
|
335
385
|
);
|
|
@@ -374,7 +424,14 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
374
424
|
);
|
|
375
425
|
}
|
|
376
426
|
button.addEventListener("click", () =>
|
|
377
|
-
onSpecEvidenceFileClick(
|
|
427
|
+
onSpecEvidenceFileClick(
|
|
428
|
+
content,
|
|
429
|
+
dagRunId,
|
|
430
|
+
nodeId,
|
|
431
|
+
"read",
|
|
432
|
+
read.path,
|
|
433
|
+
button,
|
|
434
|
+
),
|
|
378
435
|
);
|
|
379
436
|
item.appendChild(button);
|
|
380
437
|
return item;
|
|
@@ -391,13 +448,20 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
391
448
|
const item = el("li", null);
|
|
392
449
|
const icon = el("i", "ri-search-line");
|
|
393
450
|
icon.setAttribute("aria-hidden", "true");
|
|
394
|
-
item.append(
|
|
451
|
+
item.append(
|
|
452
|
+
icon,
|
|
453
|
+
document.createTextNode(` ${search.tool}: ${search.query}`),
|
|
454
|
+
);
|
|
395
455
|
return item;
|
|
396
456
|
},
|
|
397
457
|
"unavailable:未观察到 openspec 检索。",
|
|
398
458
|
);
|
|
399
459
|
|
|
400
|
-
if (
|
|
460
|
+
if (
|
|
461
|
+
evidence.status === "source-bound" ||
|
|
462
|
+
evidence.status === "spec-injected" ||
|
|
463
|
+
evidence.status === "no-evidence"
|
|
464
|
+
) {
|
|
401
465
|
const warning = el("div", "spec-evidence-warning");
|
|
402
466
|
const icon = el("i", "ri-alert-line");
|
|
403
467
|
icon.setAttribute("aria-hidden", "true");
|
|
@@ -407,12 +471,13 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
407
471
|
evidence.status === "source-bound"
|
|
408
472
|
? "DAG 已绑定权威任务源及内容哈希,但尚未观察到成功的 read 工具调用;绑定证明上下文来源,read 事件才证明节点读取了完整文件。"
|
|
409
473
|
: evidence.status === "no-evidence"
|
|
410
|
-
|
|
411
|
-
|
|
474
|
+
? "未观察到规范读取。只有 session event 中成功配对的 read 工具调用才算已读取,模型总结或文件名命中不能作为证明。"
|
|
475
|
+
: "规范 skill 已注入,但未观察到规范读取或知识库查询;grep/find/ls 只能证明检索,不能替代 read。",
|
|
412
476
|
),
|
|
413
477
|
);
|
|
414
478
|
content.appendChild(warning);
|
|
415
479
|
}
|
|
480
|
+
restoreInspectorContentScroll(content, dagRunId, nodeId, "spec-evidence");
|
|
416
481
|
}
|
|
417
482
|
|
|
418
483
|
/**
|
|
@@ -420,14 +485,26 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
420
485
|
* view in the same spec-evidence panel (no modal), records the trigger button
|
|
421
486
|
* so the back action can restore keyboard focus (AC-005).
|
|
422
487
|
*/
|
|
423
|
-
function onSpecEvidenceFileClick(
|
|
488
|
+
function onSpecEvidenceFileClick(
|
|
489
|
+
content,
|
|
490
|
+
dagRunId,
|
|
491
|
+
nodeId,
|
|
492
|
+
source,
|
|
493
|
+
relPath,
|
|
494
|
+
button,
|
|
495
|
+
) {
|
|
424
496
|
openSpecEvidenceDetail(source, relPath, button);
|
|
425
497
|
clearNode(content);
|
|
426
498
|
const detail = uiState.specEvidenceDetail;
|
|
427
499
|
renderSpecEvidenceDetail(content, dagRunId, nodeId);
|
|
428
500
|
void (async () => {
|
|
429
501
|
try {
|
|
430
|
-
const data = await fetchSpecEvidenceFile(
|
|
502
|
+
const data = await fetchSpecEvidenceFile(
|
|
503
|
+
dagRunId,
|
|
504
|
+
nodeId,
|
|
505
|
+
source,
|
|
506
|
+
relPath,
|
|
507
|
+
);
|
|
431
508
|
if (
|
|
432
509
|
!content.isConnected ||
|
|
433
510
|
content.dataset.dagNodeId !== String(nodeId) ||
|
|
@@ -464,7 +541,12 @@ function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
|
|
|
464
541
|
const detail = uiState.specEvidenceDetail;
|
|
465
542
|
clearNode(content);
|
|
466
543
|
if (!detail) {
|
|
467
|
-
renderSpecEvidence(
|
|
544
|
+
renderSpecEvidence(
|
|
545
|
+
content,
|
|
546
|
+
dagRunId,
|
|
547
|
+
nodeId,
|
|
548
|
+
uiState.specEvidenceListEvidence,
|
|
549
|
+
);
|
|
468
550
|
return;
|
|
469
551
|
}
|
|
470
552
|
|
|
@@ -481,7 +563,12 @@ function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
|
|
|
481
563
|
clearNode(content);
|
|
482
564
|
// Restore the list synchronously from the cached evidence so the user
|
|
483
565
|
// sees no flicker and the trigger button is immediately focusable.
|
|
484
|
-
renderSpecEvidence(
|
|
566
|
+
renderSpecEvidence(
|
|
567
|
+
content,
|
|
568
|
+
dagRunId,
|
|
569
|
+
nodeId,
|
|
570
|
+
uiState.specEvidenceListEvidence,
|
|
571
|
+
);
|
|
485
572
|
if (closed?.triggerId) {
|
|
486
573
|
const trigger = document.getElementById(closed.triggerId);
|
|
487
574
|
trigger?.focus();
|
|
@@ -516,7 +603,11 @@ function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
|
|
|
516
603
|
loading.setAttribute("aria-live", "polite");
|
|
517
604
|
wrapper.appendChild(loading);
|
|
518
605
|
} else if (detail.error) {
|
|
519
|
-
const error = el(
|
|
606
|
+
const error = el(
|
|
607
|
+
"p",
|
|
608
|
+
"spec-evidence-detail-error",
|
|
609
|
+
`加载失败:${detail.error}`,
|
|
610
|
+
);
|
|
520
611
|
error.setAttribute("role", "alert");
|
|
521
612
|
wrapper.appendChild(error);
|
|
522
613
|
} else if (detail.data) {
|
|
@@ -533,6 +624,7 @@ function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
|
|
|
533
624
|
}
|
|
534
625
|
}
|
|
535
626
|
content.appendChild(wrapper);
|
|
627
|
+
restoreInspectorContentScroll(content, dagRunId, nodeId, "spec-evidence");
|
|
536
628
|
}
|
|
537
629
|
|
|
538
630
|
function sourceLabel(source) {
|
|
@@ -573,9 +665,7 @@ function appendCodeList(parent, title, values) {
|
|
|
573
665
|
|
|
574
666
|
function renderDagNodeInputDetail(content, data) {
|
|
575
667
|
const wrap = el("div", "node-input-root");
|
|
576
|
-
wrap.appendChild(
|
|
577
|
-
el("p", "node-input-semantics", UI_TEXT.inputFrozenLabel),
|
|
578
|
-
);
|
|
668
|
+
wrap.appendChild(el("p", "node-input-semantics", UI_TEXT.inputFrozenLabel));
|
|
579
669
|
|
|
580
670
|
if (data.summary) {
|
|
581
671
|
const section = el("section", "node-input-section");
|
|
@@ -747,17 +837,10 @@ function renderDagNodeInputDetail(content, data) {
|
|
|
747
837
|
data.promptFingerprint.sha256Prefix ?? "(parse failed)",
|
|
748
838
|
null,
|
|
749
839
|
);
|
|
750
|
-
appendMetaRow(
|
|
751
|
-
dl,
|
|
752
|
-
"lengthChars",
|
|
753
|
-
data.promptFingerprint.lengthChars,
|
|
754
|
-
null,
|
|
755
|
-
);
|
|
840
|
+
appendMetaRow(dl, "lengthChars", data.promptFingerprint.lengthChars, null);
|
|
756
841
|
appendMetaRow(dl, "artifact", data.promptFingerprint.artifact, null);
|
|
757
842
|
section.appendChild(dl);
|
|
758
|
-
section.appendChild(
|
|
759
|
-
el("p", "muted", UI_TEXT.inputFingerprintNote),
|
|
760
|
-
);
|
|
843
|
+
section.appendChild(el("p", "muted", UI_TEXT.inputFingerprintNote));
|
|
761
844
|
wrap.appendChild(section);
|
|
762
845
|
}
|
|
763
846
|
|
|
@@ -793,6 +876,10 @@ async function renderDagNodeInput(content, dagRunId, node) {
|
|
|
793
876
|
uiState.dagNodeInputLoading = true;
|
|
794
877
|
uiState.dagNodeInputError = null;
|
|
795
878
|
|
|
879
|
+
// Preserve prior intent scroll across the short loading shell (AC-004).
|
|
880
|
+
saveInspectorContentScroll(content, dagRunId, nodeId, "input", {
|
|
881
|
+
preserveIntent: true,
|
|
882
|
+
});
|
|
796
883
|
clearNode(content);
|
|
797
884
|
const loading = el("p", "muted", UI_TEXT.inputLoading);
|
|
798
885
|
loading.setAttribute("role", "status");
|
|
@@ -812,8 +899,11 @@ async function renderDagNodeInput(content, dagRunId, node) {
|
|
|
812
899
|
return;
|
|
813
900
|
}
|
|
814
901
|
|
|
815
|
-
uiState.dagNodeInputLoading = false;
|
|
816
902
|
clearNode(content);
|
|
903
|
+
const finishInputRender = () => {
|
|
904
|
+
restoreInspectorContentScroll(content, dagRunId, nodeId, "input");
|
|
905
|
+
uiState.dagNodeInputLoading = false;
|
|
906
|
+
};
|
|
817
907
|
|
|
818
908
|
if (!result.ok) {
|
|
819
909
|
const message =
|
|
@@ -826,6 +916,7 @@ async function renderDagNodeInput(content, dagRunId, node) {
|
|
|
826
916
|
const error = el("p", "node-input-error", message);
|
|
827
917
|
error.setAttribute("role", "alert");
|
|
828
918
|
content.appendChild(error);
|
|
919
|
+
finishInputRender();
|
|
829
920
|
return;
|
|
830
921
|
}
|
|
831
922
|
|
|
@@ -845,57 +936,198 @@ async function renderDagNodeInput(content, dagRunId, node) {
|
|
|
845
936
|
content.appendChild(el("p", "node-input-warning", String(warning)));
|
|
846
937
|
}
|
|
847
938
|
}
|
|
939
|
+
finishInputRender();
|
|
848
940
|
return;
|
|
849
941
|
}
|
|
850
942
|
|
|
851
943
|
renderDagNodeInputDetail(content, data);
|
|
944
|
+
finishInputRender();
|
|
852
945
|
}
|
|
853
946
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
947
|
+
function activeInspectorTab() {
|
|
948
|
+
return ["input", "timeline", "spec-evidence"].includes(
|
|
949
|
+
uiState.dagInspectorTab,
|
|
950
|
+
)
|
|
951
|
+
? uiState.dagInspectorTab
|
|
952
|
+
: "output";
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function saveInspectorContentScroll(
|
|
956
|
+
content,
|
|
957
|
+
dagRunId,
|
|
958
|
+
nodeId,
|
|
959
|
+
tab,
|
|
960
|
+
options = {},
|
|
961
|
+
) {
|
|
962
|
+
if (!content) return null;
|
|
963
|
+
const regionKey = inspectorViewportRegionKey(dagRunId, nodeId, tab);
|
|
964
|
+
const saved = saveDagInspectorViewportState(
|
|
965
|
+
regionKey,
|
|
966
|
+
content.scrollLeft,
|
|
967
|
+
content.scrollTop,
|
|
968
|
+
options,
|
|
969
|
+
);
|
|
970
|
+
if (tab === "input") {
|
|
971
|
+
uiState.dagNodeInputViewportState = updateDagOutputViewportState(
|
|
972
|
+
uiState.dagNodeInputViewportState,
|
|
973
|
+
regionKey,
|
|
974
|
+
saved?.scrollLeft ?? 0,
|
|
975
|
+
saved?.scrollTop ?? 0,
|
|
976
|
+
false,
|
|
977
|
+
);
|
|
978
|
+
} else if (tab === "output") {
|
|
979
|
+
const followLatest = computeFollowLatest(
|
|
980
|
+
content.scrollTop,
|
|
981
|
+
content.scrollHeight,
|
|
982
|
+
content.clientHeight,
|
|
983
|
+
);
|
|
984
|
+
uiState.dagNodeOutputViewportState = updateDagOutputViewportState(
|
|
985
|
+
uiState.dagNodeOutputViewportState,
|
|
986
|
+
regionKey,
|
|
987
|
+
content.scrollLeft,
|
|
988
|
+
content.scrollTop,
|
|
989
|
+
followLatest,
|
|
990
|
+
);
|
|
991
|
+
} else if (tab === "spec-evidence") {
|
|
992
|
+
uiState.dagNodeSpecEvidenceViewportState = saved;
|
|
993
|
+
}
|
|
994
|
+
return saved;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function restoreInspectorContentScroll(content, dagRunId, nodeId, tab) {
|
|
998
|
+
if (!content) return;
|
|
999
|
+
const regionKey = inspectorViewportRegionKey(dagRunId, nodeId, tab);
|
|
1000
|
+
const apply = () => {
|
|
1001
|
+
if (
|
|
1002
|
+
!content.isConnected ||
|
|
1003
|
+
content.dataset.dagNodeId !== String(nodeId ?? "") ||
|
|
1004
|
+
content.dataset.dagRunId !== String(dagRunId ?? "") ||
|
|
1005
|
+
activeInspectorTab() !== tab
|
|
1006
|
+
) {
|
|
1007
|
+
return;
|
|
867
1008
|
}
|
|
868
|
-
|
|
869
|
-
uiState.
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
content.setAttribute("role", "tabpanel");
|
|
875
|
-
content.setAttribute("aria-labelledby", `dag-inspector-tab-${activeTab}`);
|
|
876
|
-
content.dataset.dagNodeId = String(node.nodeId ?? "");
|
|
877
|
-
content.dataset.dagRunId = String(dagRunId ?? "");
|
|
878
|
-
// Drop timeline-only classes when switching tabs so output layout stays clean.
|
|
879
|
-
content.className = "dag-inspector-content";
|
|
880
|
-
if (activeTab === "spec-evidence") {
|
|
881
|
-
void renderSpecEvidence(content, dagRunId, node.nodeId);
|
|
882
|
-
} else if (activeTab === "timeline") {
|
|
883
|
-
renderSessionTimeline(content, node.nodeId);
|
|
884
|
-
} else if (activeTab === "input") {
|
|
885
|
-
void renderDagNodeInput(content, dagRunId, node);
|
|
886
|
-
} else if (!node.outputPreview && !node.errorPreview) {
|
|
887
|
-
content.appendChild(el("p", "empty", UI_TEXT.noOutput));
|
|
888
|
-
} else {
|
|
889
|
-
if (node.outputPreview)
|
|
890
|
-
content.appendChild(renderMarkdown(node.outputPreview));
|
|
891
|
-
if (node.errorPreview) {
|
|
892
|
-
const error = el("section", "markdown-error");
|
|
893
|
-
error.appendChild(el("p", "markdown-error-title", "错误输出"));
|
|
894
|
-
error.appendChild(renderMarkdown(node.errorPreview));
|
|
895
|
-
content.appendChild(error);
|
|
1009
|
+
if (tab === "output") {
|
|
1010
|
+
const saved = uiState.dagNodeOutputViewportState;
|
|
1011
|
+
if (saved?.regionKey === regionKey && saved.followLatest) {
|
|
1012
|
+
content.scrollTop = content.scrollHeight;
|
|
1013
|
+
content.scrollLeft = saved.scrollLeft ?? 0;
|
|
1014
|
+
return;
|
|
896
1015
|
}
|
|
897
1016
|
}
|
|
1017
|
+
const saved =
|
|
1018
|
+
getDagInspectorViewportState(regionKey) ??
|
|
1019
|
+
(tab === "input" &&
|
|
1020
|
+
uiState.dagNodeInputViewportState?.regionKey === regionKey
|
|
1021
|
+
? uiState.dagNodeInputViewportState
|
|
1022
|
+
: null) ??
|
|
1023
|
+
(tab === "output" &&
|
|
1024
|
+
uiState.dagNodeOutputViewportState?.regionKey === regionKey
|
|
1025
|
+
? uiState.dagNodeOutputViewportState
|
|
1026
|
+
: null) ??
|
|
1027
|
+
(tab === "spec-evidence" &&
|
|
1028
|
+
uiState.dagNodeSpecEvidenceViewportState?.regionKey === regionKey
|
|
1029
|
+
? uiState.dagNodeSpecEvidenceViewportState
|
|
1030
|
+
: null);
|
|
1031
|
+
content.scrollLeft = saved?.scrollLeft ?? 0;
|
|
1032
|
+
content.scrollTop = saved?.scrollTop ?? 0;
|
|
1033
|
+
};
|
|
1034
|
+
apply();
|
|
1035
|
+
if (typeof requestAnimationFrame === "function") {
|
|
1036
|
+
requestAnimationFrame(apply);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function ensureInspectorContentScrollCapture(content, dagRunId, nodeId, tab) {
|
|
1041
|
+
if (!content || content.dataset.scrollCaptureBound === "1") return;
|
|
1042
|
+
content.dataset.scrollCaptureBound = "1";
|
|
1043
|
+
content.addEventListener(
|
|
1044
|
+
"scroll",
|
|
1045
|
+
() => {
|
|
1046
|
+
const runId = content.dataset.dagRunId;
|
|
1047
|
+
const nId = content.dataset.dagNodeId;
|
|
1048
|
+
const active = activeInspectorTab();
|
|
1049
|
+
if (!runId || !nId || !active) return;
|
|
1050
|
+
saveInspectorContentScroll(content, runId, nId, active, {
|
|
1051
|
+
// Clearing a tall input pane for its loading shell can emit a native
|
|
1052
|
+
// scroll-to-zero event. Preserve the user's prior intent until the
|
|
1053
|
+
// asynchronous content has been restored.
|
|
1054
|
+
preserveIntent:
|
|
1055
|
+
active === "input" && Boolean(uiState.dagNodeInputLoading),
|
|
1056
|
+
});
|
|
1057
|
+
},
|
|
1058
|
+
{ passive: true },
|
|
1059
|
+
);
|
|
1060
|
+
// Touch current context so callers always have a key even before first scroll.
|
|
1061
|
+
saveInspectorContentScroll(content, dagRunId, nodeId, tab, {
|
|
1062
|
+
preserveIntent: true,
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function fillDagInspectorContent(content, dagRunId, node) {
|
|
1067
|
+
const previousRenderedNode = content.dataset.dagNodeId ?? "";
|
|
1068
|
+
const previousRenderedRun = content.dataset.dagRunId ?? "";
|
|
1069
|
+
const previousTab = content.dataset.inspectorTab ?? "";
|
|
1070
|
+
const nodeChanged =
|
|
1071
|
+
previousRenderedNode !== String(node.nodeId ?? "") ||
|
|
1072
|
+
previousRenderedRun !== String(dagRunId ?? "");
|
|
1073
|
+
if (nodeChanged) {
|
|
1074
|
+
// Drop any open spec-evidence preview + cached list so a stale A detail
|
|
1075
|
+
// cannot survive into node B (AC-007 regression guard).
|
|
1076
|
+
uiState.specEvidenceDetail = null;
|
|
1077
|
+
uiState.specEvidenceListEvidence = null;
|
|
1078
|
+
uiState.dagNodeInputData = null;
|
|
1079
|
+
uiState.dagNodeInputError = null;
|
|
1080
|
+
}
|
|
1081
|
+
const activeTab = activeInspectorTab();
|
|
1082
|
+
// Capture outgoing context scroll before datasets flip (AC-005 isolation).
|
|
1083
|
+
if (
|
|
1084
|
+
previousRenderedNode &&
|
|
1085
|
+
previousRenderedRun &&
|
|
1086
|
+
(previousTab || activeTab)
|
|
1087
|
+
) {
|
|
1088
|
+
saveInspectorContentScroll(
|
|
1089
|
+
content,
|
|
1090
|
+
previousRenderedRun,
|
|
1091
|
+
previousRenderedNode,
|
|
1092
|
+
previousTab || activeTab,
|
|
1093
|
+
{ preserveIntent: true },
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
content.id = `dag-inspector-panel-${activeTab}`;
|
|
1097
|
+
content.setAttribute("role", "tabpanel");
|
|
1098
|
+
content.setAttribute("aria-labelledby", `dag-inspector-tab-${activeTab}`);
|
|
1099
|
+
content.dataset.dagNodeId = String(node.nodeId ?? "");
|
|
1100
|
+
content.dataset.dagRunId = String(dagRunId ?? "");
|
|
1101
|
+
content.dataset.inspectorTab = activeTab;
|
|
1102
|
+
// Drop timeline-only classes when switching tabs so output layout stays clean.
|
|
1103
|
+
content.className = "dag-inspector-content";
|
|
1104
|
+
ensureInspectorContentScrollCapture(
|
|
1105
|
+
content,
|
|
1106
|
+
dagRunId,
|
|
1107
|
+
node.nodeId,
|
|
1108
|
+
activeTab,
|
|
1109
|
+
);
|
|
1110
|
+
if (activeTab === "spec-evidence") {
|
|
1111
|
+
void renderSpecEvidence(content, dagRunId, node.nodeId);
|
|
1112
|
+
} else if (activeTab === "timeline") {
|
|
1113
|
+
renderSessionTimeline(content, node.nodeId);
|
|
1114
|
+
} else if (activeTab === "input") {
|
|
1115
|
+
void renderDagNodeInput(content, dagRunId, node);
|
|
1116
|
+
} else if (!node.outputPreview && !node.errorPreview) {
|
|
1117
|
+
content.appendChild(el("p", "empty", UI_TEXT.noOutput));
|
|
1118
|
+
restoreInspectorContentScroll(content, dagRunId, node.nodeId, "output");
|
|
1119
|
+
} else {
|
|
1120
|
+
if (node.outputPreview)
|
|
1121
|
+
content.appendChild(renderMarkdown(node.outputPreview));
|
|
1122
|
+
if (node.errorPreview) {
|
|
1123
|
+
const error = el("section", "markdown-error");
|
|
1124
|
+
error.appendChild(el("p", "markdown-error-title", "错误输出"));
|
|
1125
|
+
error.appendChild(renderMarkdown(node.errorPreview));
|
|
1126
|
+
content.appendChild(error);
|
|
1127
|
+
}
|
|
1128
|
+
restoreInspectorContentScroll(content, dagRunId, node.nodeId, "output");
|
|
898
1129
|
}
|
|
1130
|
+
}
|
|
899
1131
|
|
|
900
1132
|
function buildDagInspectorHeader(dagRunId, node) {
|
|
901
1133
|
const header = el("div", "dag-inspector-header");
|
|
@@ -990,25 +1222,18 @@ function syncDagInspectorHeader(panel, dagRunId, node) {
|
|
|
990
1222
|
}
|
|
991
1223
|
|
|
992
1224
|
function dagInspectorSignature(dagRunId, node) {
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
uiState.dagNodeInputData?.promptFingerprint?.sha256Prefix ?? "",
|
|
1001
|
-
node.status ?? "",
|
|
1002
|
-
]
|
|
1003
|
-
: null;
|
|
1225
|
+
const tab = activeInspectorTab();
|
|
1226
|
+
const contentRevision =
|
|
1227
|
+
tab === "output"
|
|
1228
|
+
? [node.outputPreview ?? "", node.errorPreview ?? ""]
|
|
1229
|
+
: tab === "timeline"
|
|
1230
|
+
? [uiState.sessionEventOffset]
|
|
1231
|
+
: [node.status ?? ""];
|
|
1004
1232
|
return JSON.stringify([
|
|
1005
1233
|
dagRunId,
|
|
1006
1234
|
node.nodeId,
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
node.errorPreview ?? "",
|
|
1010
|
-
uiState.dagInspectorTab === "timeline" ? uiState.sessionEventOffset : null,
|
|
1011
|
-
inputRevision,
|
|
1235
|
+
tab,
|
|
1236
|
+
contentRevision,
|
|
1012
1237
|
]);
|
|
1013
1238
|
}
|
|
1014
1239
|
|
|
@@ -1042,36 +1267,23 @@ export function renderDagInspector(dagRunId, node, initial = true) {
|
|
|
1042
1267
|
panel.classList.add("is-open");
|
|
1043
1268
|
panel.setAttribute("aria-hidden", "false");
|
|
1044
1269
|
syncDagInspectorHeader(panel, dagRunId, node);
|
|
1270
|
+
// AC-003: signature unchanged → keep content DOM; header already synced.
|
|
1045
1271
|
if (panel.dataset.contentSignature === nextContentSignature) return;
|
|
1046
1272
|
const previousNode = existingContent.dataset.dagNodeId ?? "";
|
|
1047
1273
|
const previousRun = existingContent.dataset.dagRunId ?? "";
|
|
1274
|
+
const previousTab =
|
|
1275
|
+
existingContent.dataset.inspectorTab ?? activeInspectorTab();
|
|
1048
1276
|
const nodeChanged =
|
|
1049
1277
|
previousNode !== String(node.nodeId ?? "") ||
|
|
1050
1278
|
previousRun !== String(dagRunId ?? "");
|
|
1051
|
-
const activeTab =
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
: nodeChanged ||
|
|
1060
|
-
computeFollowLatest(
|
|
1061
|
-
existingContent.scrollTop,
|
|
1062
|
-
existingContent.scrollHeight,
|
|
1063
|
-
existingContent.clientHeight,
|
|
1064
|
-
);
|
|
1065
|
-
const regionKey = `dag:${dagRunId}:${node.nodeId}:${isInputTab ? "input" : "output"}`;
|
|
1066
|
-
const viewportUpdater = isInputTab
|
|
1067
|
-
? "dagNodeInputViewportState"
|
|
1068
|
-
: "dagNodeOutputViewportState";
|
|
1069
|
-
uiState[viewportUpdater] = updateDagOutputViewportState(
|
|
1070
|
-
uiState[viewportUpdater],
|
|
1071
|
-
regionKey,
|
|
1072
|
-
existingContent.scrollLeft,
|
|
1073
|
-
existingContent.scrollTop,
|
|
1074
|
-
followLatest,
|
|
1279
|
+
const activeTab = activeInspectorTab();
|
|
1280
|
+
// Capture scroll for the outgoing context before rebuild.
|
|
1281
|
+
saveInspectorContentScroll(
|
|
1282
|
+
existingContent,
|
|
1283
|
+
previousRun || dagRunId,
|
|
1284
|
+
previousNode || node.nodeId,
|
|
1285
|
+
previousTab,
|
|
1286
|
+
{ preserveIntent: true },
|
|
1075
1287
|
);
|
|
1076
1288
|
const nestedViewportState = [
|
|
1077
1289
|
...existingContent.querySelectorAll("pre"),
|
|
@@ -1081,19 +1293,24 @@ export function renderDagInspector(dagRunId, node, initial = true) {
|
|
|
1081
1293
|
}));
|
|
1082
1294
|
clearNode(existingContent);
|
|
1083
1295
|
fillDagInspectorContent(existingContent, dagRunId, node);
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1296
|
+
// Async tabs restore themselves after fetch; sync tabs restore here.
|
|
1297
|
+
if (activeTab === "output" || activeTab === "timeline") {
|
|
1298
|
+
restoreInspectorContentScroll(
|
|
1299
|
+
existingContent,
|
|
1300
|
+
dagRunId,
|
|
1301
|
+
node.nodeId,
|
|
1302
|
+
activeTab,
|
|
1303
|
+
);
|
|
1089
1304
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1305
|
+
if (!nodeChanged && previousTab === activeTab) {
|
|
1306
|
+
for (const [index, pre] of [
|
|
1307
|
+
...existingContent.querySelectorAll("pre"),
|
|
1308
|
+
].entries()) {
|
|
1309
|
+
const previous = nestedViewportState[index];
|
|
1310
|
+
if (!previous) continue;
|
|
1311
|
+
pre.scrollLeft = previous.scrollLeft;
|
|
1312
|
+
pre.scrollTop = previous.scrollTop;
|
|
1313
|
+
}
|
|
1097
1314
|
}
|
|
1098
1315
|
panel.dataset.contentSignature = nextContentSignature;
|
|
1099
1316
|
return;
|