@tea-agent/loop-agent 0.13.0-alpha.0 → 0.13.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.
- package/AGENTS.md +4 -0
- package/CHANGELOG.md +57 -53
- package/README.md +12 -3
- package/dist/application/dag/generate-task-dag.js +28 -58
- package/dist/application/evaluation/candidate-hash.js +75 -0
- package/dist/application/evaluation/candidate.js +52 -0
- package/dist/application/evaluation/replay.js +289 -0
- package/dist/application/evaluation/types.js +130 -0
- package/dist/cli/command-definitions.js +17 -4
- package/dist/cli/program.js +8 -4
- package/dist/commands/eval.js +235 -0
- package/dist/commands/init.js +131 -24
- package/dist/executors/pi-sdk-executor.js +38 -24
- package/dist/executors/shell-executor.js +226 -15
- package/dist/executors/shell-presets.js +20 -0
- package/dist/executors/shell-verification.js +7 -0
- package/dist/governance/manifest-types.js +1 -0
- package/dist/infrastructure/evaluation/candidate-store.js +439 -0
- package/dist/infrastructure/evaluation/store.js +40 -0
- package/dist/task/config-types.js +23 -0
- package/dist/worker/observe/routes.js +18 -3
- package/dist/worker/observe/spec-evidence.js +1 -1
- package/dist/worker/observe/static/dom.js +160 -1
- package/dist/worker/observe/static/state.js +14 -0
- package/dist/worker/observe/static/views/dag-inspector.js +35 -4
- package/dist/worker/observe/static/views/dag.js +9 -0
- package/dist/worker/observe/static/views/dashboard.js +702 -445
- package/dist/worker/observe/static/views/session-timeline.js +15 -1
- package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
- package/dist/workflows/dag/backend-test-case-manifest.js +503 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +353 -0
- package/dist/workflows/dag/backend-test-result-contract.js +568 -0
- package/dist/workflows/dag/decision-envelope.js +57 -2
- package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
- package/dist/workflows/dag/frontend-implementation-contract.js +240 -0
- package/dist/workflows/dag/frontend-project-capability.js +309 -0
- package/dist/workflows/dag/frontend-repair.js +341 -0
- package/dist/workflows/dag/frontend-risk.js +161 -0
- package/dist/workflows/dag/frontend-verification-trace.js +190 -0
- package/dist/workflows/dag/init-hybrid.js +2407 -297
- package/dist/workflows/dag/node-execution.js +9 -0
- package/dist/workflows/dag/prompt.js +9 -0
- package/dist/workflows/dag/repair-artifact.js +43 -3
- package/dist/workflows/dag/report.js +35 -1
- package/dist/workflows/dag/runner.js +28 -2
- package/dist/workflows/dag/skill-instructions.js +4 -2
- package/dist/workflows/dag/task-demand-routing.js +383 -0
- package/dist/workflows/dag/types.js +71 -13
- package/dist/workflows/dag/upstream-artifacts.js +1 -0
- package/dist/workflows/dag/validate.js +59 -1
- package/docs/README.md +6 -3
- package/docs/agent-dag-recovery-playbook.md +5 -3
- package/docs/agent-dag-runner.md +3 -3
- package/docs/architecture/README.md +3 -3
- package/docs/architecture/dag-execution.md +1 -1
- package/docs/architecture/evolution.md +13 -13
- package/docs/architecture/facts-and-state.md +1 -1
- package/docs/architecture/runtime-boundaries.md +7 -7
- package/docs/architecture/system-overview.md +3 -3
- package/docs/architecture/worker-and-feature.md +3 -3
- package/docs/design/README.md +124 -42
- package/docs/development-principles.md +4 -4
- package/docs/exec-plans/active/README.md +12 -11
- package/docs/exec-plans/completed/README.md +33 -0
- package/docs/feature-workflow.md +114 -39
- package/docs/init-surface.manifest.json +30 -3
- package/docs/loop-agent-harness.md +9 -8
- package/docs/production-readiness.md +1 -1
- package/docs/progress/README.md +23 -1
- package/docs/reports/README.md +65 -6
- package/docs/skills/vetted-skill-registry.md +2 -0
- package/docs/templates/agent-dag.schema.json +29 -1
- package/docs/templates/agent-dag.supervised-implementation.json +127 -8
- package/docs/templates/backend-test-analysis.schema.json +44 -0
- package/docs/templates/backend-test-case-manifest.schema.json +190 -0
- package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +81 -16
- package/docs/templates/backend-test-dag.json +311 -40
- package/docs/templates/backend-test-dag.retrospect.prompt.md +44 -30
- package/docs/templates/backend-test-dag.review-cases.prompt.md +10 -8
- package/docs/templates/backend-test-execution.schema.json +133 -0
- package/docs/templates/backend-test-result.schema.json +99 -0
- package/docs/templates/branch-merge-report.md +93 -0
- package/docs/templates/frontend-design-contract.md +9 -0
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
- package/docs/templates/frontend-eval/metrics.md +138 -0
- package/docs/templates/frontend-eval/smoke-targets.md +53 -0
- package/docs/templates/frontend-implementation-contract.schema.json +27 -0
- package/docs/templates/frontend-task-constraints.md +10 -0
- package/docs/templates/frontend-task-requirement.md +9 -0
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.json +23 -0
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
- package/docs/templates/knowledge-graph-bootstrap-dag.json +1 -1
- package/docs/templates/knowledge-sync-dag.json +1 -0
- package/docs/verification-matrix.md +4 -1
- package/examples/decision-gate-agent-dag.json +4 -4
- package/examples/hybrid-loop-agent-dag.json +1 -1
- package/package.json +2 -2
- package/scripts/kb-bootstrap-init-skeleton.sh +2 -1
- package/scripts/kb-graph-incremental-prepare.mjs +19 -5
- package/scripts/kb-graph-promote.mjs +12 -1
- package/skills/ai-engineering-context/SKILL.md +2 -2
- package/skills/analyze-product-dependencies/SKILL.md +67 -0
- package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
- package/skills/analyze-product-dependencies/references/example.md +76 -0
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
- package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
- package/skills/analyze-product-requirements/SKILL.md +90 -0
- package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
- package/skills/analyze-product-requirements/references/example.md +86 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
- package/skills/browser-tools/SKILL.md +196 -0
- package/skills/browser-tools/browser-content.js +103 -0
- package/skills/browser-tools/browser-cookies.js +35 -0
- package/skills/browser-tools/browser-eval.js +53 -0
- package/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/skills/browser-tools/browser-nav.js +44 -0
- package/skills/browser-tools/browser-pick.js +162 -0
- package/skills/browser-tools/browser-screenshot.js +34 -0
- package/skills/browser-tools/browser-start.js +86 -0
- package/skills/browser-tools/package-lock.json +2556 -0
- package/skills/browser-tools/package.json +19 -0
- package/skills/frontend-design-review/SKILL.md +6 -1
- package/skills/frontend-design-review/references/review-checklist.md +25 -4
- package/skills/frontend-implementation/SKILL.md +25 -30
- package/skills/frontend-implementation/references/code-standards.md +20 -22
- package/skills/frontend-implementation/references/node-contracts.md +17 -53
- package/skills/frontend-review/SKILL.md +10 -4
- package/skills/frontend-review/references/review-findings.md +8 -3
- package/skills/frontend-verification/SKILL.md +22 -9
- package/skills/frontend-verification/references/verification-checklist.md +17 -5
- package/skills/grill-with-docs/SKILL.md +5 -5
- package/skills/grill-with-docs/adr-format.md +3 -3
- package/skills/init-capability-evolution/SKILL.md +5 -5
- package/skills/loop-agent/SKILL.md +5 -5
- package/skills/loop-agent/references/README.md +3 -3
- package/skills/loop-agent/references/command-reference.md +98 -24
- package/skills/loop-agent/references/docs-converge.md +15 -15
- package/skills/loop-agent/references/harness-policy.md +2 -2
- package/skills/loop-agent/references/hybrid-dag.md +32 -22
- package/skills/loop-agent/references/multi-worktree.md +1 -1
- package/skills/loop-agent/references/orchestrator-and-interventions.md +8 -8
- package/skills/loop-agent/references/task-workflow.md +1 -1
- package/skills/loop-agent/references/verification-and-failure-handling.md +6 -4
- package/skills/playwright-cli/SKILL.md +420 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +139 -0
- package/skills/playwright-cli/references/video-recording.md +143 -0
- package/skills/playwright-cli-case-generator/SKILL.md +74 -0
- package/skills/requesting-code-review/SKILL.md +1 -1
- package/skills/systematic-debugging/CREATION-LOG.md +3 -3
- package/skills/systematic-debugging/SKILL.md +1 -1
- package/skills/systematic-debugging/test-academic.md +1 -1
- package/skills/systematic-debugging/test-pressure-1.md +1 -1
- package/skills/systematic-debugging/test-pressure-2.md +1 -1
- package/skills/systematic-debugging/test-pressure-3.md +1 -1
- package/skills/verification-before-completion/SKILL.md +1 -1
|
@@ -1,6 +1,165 @@
|
|
|
1
1
|
/** DOM helpers (Observe UI R5). */
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Stable key → node maps, kept per (parent, scope) so reconcileKeyed never
|
|
5
|
+
* depends on dataset support. A WeakMap keyed by parent holds a map of
|
|
6
|
+
* scope → Map<key, node>. A stale parent (e.g. after view teardown) releases
|
|
7
|
+
* its bookkeeping automatically. The default scope "default" is used when a
|
|
8
|
+
* caller reconciles all keyed children of a parent in a single pass; multiple
|
|
9
|
+
* reconcilers that share one parent must pass distinct scope strings so they
|
|
10
|
+
* do not clobber each other's keys.
|
|
11
|
+
*/
|
|
12
|
+
const reconcileMaps = new WeakMap();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Keyed DOM reconciliation: preserve node identity when a stable key recurs
|
|
16
|
+
* across renders; create a fresh node (with fresh closures) when the key is
|
|
17
|
+
* new; remove nodes whose key dropped out of the item set.
|
|
18
|
+
*
|
|
19
|
+
* Callers drive node construction via `create` and content sync via optional
|
|
20
|
+
* `update`. The parent is not bulk-cleared, so headings/headers placed outside
|
|
21
|
+
* this call survive, and scrolling/focus/pointer state on reused nodes is kept.
|
|
22
|
+
*
|
|
23
|
+
* @param {Node} parent
|
|
24
|
+
* @param {Array} items
|
|
25
|
+
* @param {{ getKey: (item: any, index: number) => string,
|
|
26
|
+
* create: (item: any, index: number) => Node,
|
|
27
|
+
* update?: (node: any, item: any, index: number) => void,
|
|
28
|
+
* scope?: string }} opts
|
|
29
|
+
* @returns {Array<Node>} the (possibly reused) nodes in item order
|
|
30
|
+
*/
|
|
31
|
+
export function reconcileKeyed(parent, items, opts) {
|
|
32
|
+
const { getKey, create, update, scope = "default" } = opts;
|
|
33
|
+
let scopes = reconcileMaps.get(parent);
|
|
34
|
+
if (!scopes) {
|
|
35
|
+
scopes = new Map();
|
|
36
|
+
reconcileMaps.set(parent, scopes);
|
|
37
|
+
}
|
|
38
|
+
let map = scopes.get(scope);
|
|
39
|
+
if (!map) {
|
|
40
|
+
map = new Map();
|
|
41
|
+
scopes.set(scope, map);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const nextKeys = new Set();
|
|
45
|
+
const result = [];
|
|
46
|
+
for (let i = 0; i < items.length; i++) {
|
|
47
|
+
const item = items[i];
|
|
48
|
+
const key = String(getKey(item, i));
|
|
49
|
+
nextKeys.add(key);
|
|
50
|
+
let node = map.get(key);
|
|
51
|
+
if (node) {
|
|
52
|
+
if (update) update(node, item, i);
|
|
53
|
+
} else {
|
|
54
|
+
node = create(item, i);
|
|
55
|
+
map.set(key, node);
|
|
56
|
+
// Sync freshly created nodes so callers can split structure (create) from
|
|
57
|
+
// content (update) and still populate the first paint.
|
|
58
|
+
if (update) update(node, item, i);
|
|
59
|
+
}
|
|
60
|
+
result.push(node);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Remove nodes whose key is no longer present in THIS scope.
|
|
64
|
+
for (const [key, node] of map) {
|
|
65
|
+
if (!nextKeys.has(key)) {
|
|
66
|
+
parent.removeChild(node);
|
|
67
|
+
map.delete(key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const resultSet = new Set(result);
|
|
72
|
+
const currentOrder = childNodesOf(parent).filter((node) => resultSet.has(node));
|
|
73
|
+
const orderChanged =
|
|
74
|
+
currentOrder.length !== result.length ||
|
|
75
|
+
currentOrder.some((node, index) => node !== result[index]);
|
|
76
|
+
if (orderChanged) {
|
|
77
|
+
for (const node of result) parent.appendChild(node);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function childNodesOf(node) {
|
|
84
|
+
return Array.from(node.childNodes ?? node.children ?? []);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function nodeKind(node) {
|
|
88
|
+
if (node.nodeType === 3) return "#text";
|
|
89
|
+
return node.nodeName ?? node.tagName ?? "";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function syncElementAttributes(current, next) {
|
|
93
|
+
if (current.getAttributeNames && next.getAttributeNames) {
|
|
94
|
+
const nextNames = new Set(next.getAttributeNames());
|
|
95
|
+
for (const name of current.getAttributeNames()) {
|
|
96
|
+
if (!nextNames.has(name)) current.removeAttribute(name);
|
|
97
|
+
}
|
|
98
|
+
for (const name of nextNames) {
|
|
99
|
+
current.setAttribute(name, next.getAttribute(name));
|
|
100
|
+
}
|
|
101
|
+
} else if (current.attrs && next.attrs) {
|
|
102
|
+
for (const name of Object.keys(current.attrs)) {
|
|
103
|
+
if (!(name in next.attrs)) delete current.attrs[name];
|
|
104
|
+
}
|
|
105
|
+
for (const [name, value] of Object.entries(next.attrs)) {
|
|
106
|
+
current.setAttribute(name, value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const prop of [
|
|
111
|
+
"className",
|
|
112
|
+
"href",
|
|
113
|
+
"title",
|
|
114
|
+
"target",
|
|
115
|
+
"type",
|
|
116
|
+
"value",
|
|
117
|
+
"hidden",
|
|
118
|
+
"tabIndex",
|
|
119
|
+
]) {
|
|
120
|
+
if (prop in next) current[prop] = next[prop];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Recursively sync a structurally compatible node without replacing compatible
|
|
126
|
+
* descendants. Existing event listeners and focusable node identity survive;
|
|
127
|
+
* incompatible tag/node kinds are reported to the caller for replacement.
|
|
128
|
+
*/
|
|
129
|
+
export function syncCompatibleNode(current, next) {
|
|
130
|
+
if (!current || !next || nodeKind(current) !== nodeKind(next)) return false;
|
|
131
|
+
if (current.nodeType === 3 || nodeKind(current) === "#TEXT") {
|
|
132
|
+
current.textContent = next.textContent;
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
syncElementAttributes(current, next);
|
|
137
|
+
const currentChildren = childNodesOf(current);
|
|
138
|
+
const nextChildren = childNodesOf(next);
|
|
139
|
+
if (nextChildren.length === 0) {
|
|
140
|
+
current.textContent = next.textContent;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (let index = 0; index < Math.max(currentChildren.length, nextChildren.length); index++) {
|
|
145
|
+
const currentChild = currentChildren[index];
|
|
146
|
+
const nextChild = nextChildren[index];
|
|
147
|
+
if (!nextChild && currentChild) {
|
|
148
|
+
current.removeChild(currentChild);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (nextChild && !currentChild) {
|
|
152
|
+
current.appendChild(nextChild);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (!syncCompatibleNode(currentChild, nextChild)) {
|
|
156
|
+
current.replaceChild(nextChild, currentChild);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function clearNode(node) {
|
|
4
163
|
while (node.firstChild) node.removeChild(node.firstChild);
|
|
5
164
|
}
|
|
6
165
|
|
|
@@ -26,6 +26,12 @@ export const uiState = {
|
|
|
26
26
|
selectedDagNodeId: null,
|
|
27
27
|
sessionEventOffset: 0,
|
|
28
28
|
sessionEvents: [],
|
|
29
|
+
// Identity token ("<dagRunId>:<nodeId>#<seq>") bumped on every node/DAG
|
|
30
|
+
// switch. In-flight session-event fetches capture this before await and
|
|
31
|
+
// reject themselves when it no longer matches, so a stale response for a
|
|
32
|
+
// previous node can never mutate the current node's event cache.
|
|
33
|
+
sessionEventIdentity: null,
|
|
34
|
+
sessionEventIdentitySeq: 0,
|
|
29
35
|
lastSnapshot: null,
|
|
30
36
|
dagInspectorOpen: false,
|
|
31
37
|
dagInspectorTab: "output",
|
|
@@ -40,6 +46,12 @@ export const uiState = {
|
|
|
40
46
|
/** Per-dagRunId graph viewport restore map (stable Map instance). */
|
|
41
47
|
export const dagGraphViewportStates = new Map();
|
|
42
48
|
|
|
49
|
+
/** Build a fresh session-event identity token and bump the global sequence. */
|
|
50
|
+
export function makeSessionEventIdentity(dagRunId, nodeId) {
|
|
51
|
+
uiState.sessionEventIdentitySeq += 1;
|
|
52
|
+
return `${dagRunId}:${nodeId}#${uiState.sessionEventIdentitySeq}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
export function isPageVisible() {
|
|
44
56
|
return (
|
|
45
57
|
typeof document === "undefined" || document.visibilityState !== "hidden"
|
|
@@ -188,6 +200,8 @@ export function stopTimers() {
|
|
|
188
200
|
uiState.selectedDagNodeId = null;
|
|
189
201
|
uiState.sessionEventOffset = 0;
|
|
190
202
|
uiState.sessionEvents = [];
|
|
203
|
+
uiState.sessionEventIdentity = null;
|
|
204
|
+
uiState.sessionEventIdentitySeq += 1;
|
|
191
205
|
uiState.runEventOffset = 0;
|
|
192
206
|
uiState.runEvents = [];
|
|
193
207
|
}
|
|
@@ -13,6 +13,7 @@ import { fetchJson } from "../api.js";
|
|
|
13
13
|
import { badge, parseMarkdownBlocks, markdownLinkHref } from "../format.js";
|
|
14
14
|
import {
|
|
15
15
|
uiState,
|
|
16
|
+
makeSessionEventIdentity,
|
|
16
17
|
updateDagTimelineViewportState,
|
|
17
18
|
updateDagOutputViewportState,
|
|
18
19
|
computeFollowLatest,
|
|
@@ -23,6 +24,17 @@ import {
|
|
|
23
24
|
renderSessionTimeline,
|
|
24
25
|
} from "./session-timeline.js";
|
|
25
26
|
|
|
27
|
+
// Current inspector selection. Tab handlers reference this mutable slot so a
|
|
28
|
+
// reused inspector shell never keeps stale node closures: every render writes
|
|
29
|
+
// the latest {dagRunId, node} here and tab clicks re-render from the same
|
|
30
|
+
// reference, always resolving the currently selected node identity.
|
|
31
|
+
const inspectorSelection = { dagRunId: null, node: null };
|
|
32
|
+
|
|
33
|
+
function renderDagInspectorForCurrent() {
|
|
34
|
+
if (!inspectorSelection.dagRunId || !inspectorSelection.node) return;
|
|
35
|
+
renderDagInspector(inspectorSelection.dagRunId, inspectorSelection.node, false);
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
export function selectDagNode(dagRunId, nodeId) {
|
|
27
39
|
const nodeChanged = uiState.selectedDagNodeId !== nodeId;
|
|
28
40
|
uiState.selectedDagNodeId = nodeId;
|
|
@@ -30,6 +42,9 @@ export function selectDagNode(dagRunId, nodeId) {
|
|
|
30
42
|
if (nodeChanged) {
|
|
31
43
|
uiState.sessionEventOffset = 0;
|
|
32
44
|
uiState.sessionEvents = [];
|
|
45
|
+
// Bump the session-event identity token so any in-flight response for the
|
|
46
|
+
// previous node is rejected before it can mutate the current node cache.
|
|
47
|
+
uiState.sessionEventIdentity = makeSessionEventIdentity(dagRunId, nodeId);
|
|
33
48
|
}
|
|
34
49
|
void import("./dag.js").then((m) => m.renderDagDetail(dagRunId, false));
|
|
35
50
|
}
|
|
@@ -284,6 +299,7 @@ async function renderSpecEvidence(content, dagRunId, nodeId) {
|
|
|
284
299
|
|
|
285
300
|
const statusLabels = {
|
|
286
301
|
"spec-read": "已读取规范",
|
|
302
|
+
"source-bound": "已绑定任务源",
|
|
287
303
|
"spec-injected": "仅注入 skill",
|
|
288
304
|
"search-only": "仅执行检索",
|
|
289
305
|
"kb-queried": "已观察知识库查询",
|
|
@@ -322,6 +338,14 @@ async function renderSpecEvidence(content, dagRunId, nodeId) {
|
|
|
322
338
|
item.append(icon, document.createTextNode(` ${skill}`));
|
|
323
339
|
return item;
|
|
324
340
|
});
|
|
341
|
+
appendListSection("已绑定任务源", evidence.sourceBinding?.sources, (source) => {
|
|
342
|
+
const item = el("li", null);
|
|
343
|
+
const icon = el("i", "ri-links-line");
|
|
344
|
+
icon.setAttribute("aria-hidden", "true");
|
|
345
|
+
item.append(icon, document.createTextNode(` ${source.kind}: `), el("code", null, source.path), document.createTextNode(` · sha256 ${source.sha256.slice(0, 12)}…`));
|
|
346
|
+
return item;
|
|
347
|
+
});
|
|
348
|
+
appendListSection("显式需求编号", evidence.sourceBinding?.requirementIds, (id) => el("li", null, id));
|
|
325
349
|
appendListSection(
|
|
326
350
|
`已读取规范文件(${evidence.specReads?.length ?? 0})`,
|
|
327
351
|
evidence.specReads,
|
|
@@ -365,14 +389,16 @@ async function renderSpecEvidence(content, dagRunId, nodeId) {
|
|
|
365
389
|
},
|
|
366
390
|
);
|
|
367
391
|
|
|
368
|
-
if (evidence.status === "spec-injected" || evidence.status === "no-evidence") {
|
|
392
|
+
if (evidence.status === "source-bound" || evidence.status === "spec-injected" || evidence.status === "no-evidence") {
|
|
369
393
|
const warning = el("div", "spec-evidence-warning");
|
|
370
394
|
const icon = el("i", "ri-alert-line");
|
|
371
395
|
icon.setAttribute("aria-hidden", "true");
|
|
372
396
|
warning.append(
|
|
373
397
|
icon,
|
|
374
398
|
document.createTextNode(
|
|
375
|
-
evidence.status === "
|
|
399
|
+
evidence.status === "source-bound"
|
|
400
|
+
? "DAG 已绑定权威任务源及内容哈希,但尚未观察到成功的 read 工具调用;绑定证明上下文来源,read 事件才证明节点读取了完整文件。"
|
|
401
|
+
: evidence.status === "no-evidence"
|
|
376
402
|
? "未观察到规范读取。只有 session event 中成功配对的 read 工具调用才算已读取,模型总结或文件名命中不能作为证明。"
|
|
377
403
|
: "规范 skill 已注入,但未观察到规范读取或知识库查询;grep/find/ls 只能证明检索,不能替代 read。",
|
|
378
404
|
),
|
|
@@ -457,7 +483,7 @@ function buildDagInspectorHeader(dagRunId, node) {
|
|
|
457
483
|
button.appendChild(document.createTextNode(` ${label}`));
|
|
458
484
|
button.addEventListener("click", () => {
|
|
459
485
|
uiState.dagInspectorTab = tab;
|
|
460
|
-
|
|
486
|
+
renderDagInspectorForCurrent();
|
|
461
487
|
const next = document.getElementById(`dag-inspector-tab-${tab}`);
|
|
462
488
|
next?.focus();
|
|
463
489
|
});
|
|
@@ -471,7 +497,7 @@ function buildDagInspectorHeader(dagRunId, node) {
|
|
|
471
497
|
const delta = event.key === "ArrowRight" ? 1 : -1;
|
|
472
498
|
const nextTab = order[(idx + delta + order.length) % order.length];
|
|
473
499
|
uiState.dagInspectorTab = nextTab;
|
|
474
|
-
|
|
500
|
+
renderDagInspectorForCurrent();
|
|
475
501
|
document.getElementById(`dag-inspector-tab-${nextTab}`)?.focus();
|
|
476
502
|
});
|
|
477
503
|
tabs.appendChild(button);
|
|
@@ -512,6 +538,11 @@ function dagInspectorSignature(dagRunId, node) {
|
|
|
512
538
|
}
|
|
513
539
|
|
|
514
540
|
export function renderDagInspector(dagRunId, node, initial = true) {
|
|
541
|
+
// Record the current selection before any early-return so tab handlers
|
|
542
|
+
// (which reference the mutable inspectorSelection slot) always re-render
|
|
543
|
+
// the currently selected node, never a stale build-time closure.
|
|
544
|
+
inspectorSelection.dagRunId = dagRunId;
|
|
545
|
+
inspectorSelection.node = node;
|
|
515
546
|
const panel = document.getElementById("dag-inspector");
|
|
516
547
|
if (!panel) return;
|
|
517
548
|
captureDagTimelineViewportState(panel);
|
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
shouldPreserveDiagnosticsOpen,
|
|
61
61
|
renderViewState,
|
|
62
62
|
mountViewState,
|
|
63
|
+
makeSessionEventIdentity,
|
|
63
64
|
} from "../state.js";
|
|
64
65
|
import { uiState, dagGraphViewportStates } from "../state.js";
|
|
65
66
|
import { sortPoolTasks } from "../format-pool.js";
|
|
@@ -104,6 +105,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
|
|
|
104
105
|
uiState.selectedDagNodeId = null;
|
|
105
106
|
uiState.sessionEventOffset = 0;
|
|
106
107
|
uiState.sessionEvents = [];
|
|
108
|
+
uiState.sessionEventIdentity = null;
|
|
107
109
|
uiState.dagInspectorOpen = false;
|
|
108
110
|
uiState.dagInspectorTab = "output";
|
|
109
111
|
uiState.dagGraphViewportState = null;
|
|
@@ -168,6 +170,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
|
|
|
168
170
|
if (initial) {
|
|
169
171
|
uiState.sessionEventOffset = 0;
|
|
170
172
|
uiState.sessionEvents = [];
|
|
173
|
+
uiState.sessionEventIdentity = null;
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
|
|
@@ -336,6 +339,12 @@ export async function renderDagDetail(dagRunId, initial = true) {
|
|
|
336
339
|
nodesEl.scrollLeft = nodesScrollLeft;
|
|
337
340
|
|
|
338
341
|
if (uiState.dagInspectorOpen && uiState.selectedDagNodeId) {
|
|
342
|
+
if (!uiState.sessionEventIdentity) {
|
|
343
|
+
uiState.sessionEventIdentity = makeSessionEventIdentity(
|
|
344
|
+
dagRunId,
|
|
345
|
+
uiState.selectedDagNodeId,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
339
348
|
await mergeSessionEvents(dagRunId, uiState.selectedDagNodeId);
|
|
340
349
|
}
|
|
341
350
|
renderDagInspector(
|