@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.
Files changed (199) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +57 -53
  3. package/README.md +12 -3
  4. package/dist/application/dag/generate-task-dag.js +28 -58
  5. package/dist/application/evaluation/candidate-hash.js +75 -0
  6. package/dist/application/evaluation/candidate.js +52 -0
  7. package/dist/application/evaluation/replay.js +289 -0
  8. package/dist/application/evaluation/types.js +130 -0
  9. package/dist/cli/command-definitions.js +17 -4
  10. package/dist/cli/program.js +8 -4
  11. package/dist/commands/eval.js +235 -0
  12. package/dist/commands/init.js +131 -24
  13. package/dist/executors/pi-sdk-executor.js +38 -24
  14. package/dist/executors/shell-executor.js +226 -15
  15. package/dist/executors/shell-presets.js +20 -0
  16. package/dist/executors/shell-verification.js +7 -0
  17. package/dist/governance/manifest-types.js +1 -0
  18. package/dist/infrastructure/evaluation/candidate-store.js +439 -0
  19. package/dist/infrastructure/evaluation/store.js +40 -0
  20. package/dist/task/config-types.js +23 -0
  21. package/dist/worker/observe/routes.js +18 -3
  22. package/dist/worker/observe/spec-evidence.js +1 -1
  23. package/dist/worker/observe/static/dom.js +160 -1
  24. package/dist/worker/observe/static/state.js +14 -0
  25. package/dist/worker/observe/static/views/dag-inspector.js +35 -4
  26. package/dist/worker/observe/static/views/dag.js +9 -0
  27. package/dist/worker/observe/static/views/dashboard.js +702 -445
  28. package/dist/worker/observe/static/views/session-timeline.js +15 -1
  29. package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
  30. package/dist/workflows/dag/backend-test-case-manifest.js +503 -0
  31. package/dist/workflows/dag/backend-test-execution-contract.js +353 -0
  32. package/dist/workflows/dag/backend-test-result-contract.js +568 -0
  33. package/dist/workflows/dag/decision-envelope.js +57 -2
  34. package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
  35. package/dist/workflows/dag/frontend-implementation-contract.js +240 -0
  36. package/dist/workflows/dag/frontend-project-capability.js +309 -0
  37. package/dist/workflows/dag/frontend-repair.js +341 -0
  38. package/dist/workflows/dag/frontend-risk.js +161 -0
  39. package/dist/workflows/dag/frontend-verification-trace.js +190 -0
  40. package/dist/workflows/dag/init-hybrid.js +2407 -297
  41. package/dist/workflows/dag/node-execution.js +9 -0
  42. package/dist/workflows/dag/prompt.js +9 -0
  43. package/dist/workflows/dag/repair-artifact.js +43 -3
  44. package/dist/workflows/dag/report.js +35 -1
  45. package/dist/workflows/dag/runner.js +28 -2
  46. package/dist/workflows/dag/skill-instructions.js +4 -2
  47. package/dist/workflows/dag/task-demand-routing.js +383 -0
  48. package/dist/workflows/dag/types.js +71 -13
  49. package/dist/workflows/dag/upstream-artifacts.js +1 -0
  50. package/dist/workflows/dag/validate.js +59 -1
  51. package/docs/README.md +6 -3
  52. package/docs/agent-dag-recovery-playbook.md +5 -3
  53. package/docs/agent-dag-runner.md +3 -3
  54. package/docs/architecture/README.md +3 -3
  55. package/docs/architecture/dag-execution.md +1 -1
  56. package/docs/architecture/evolution.md +13 -13
  57. package/docs/architecture/facts-and-state.md +1 -1
  58. package/docs/architecture/runtime-boundaries.md +7 -7
  59. package/docs/architecture/system-overview.md +3 -3
  60. package/docs/architecture/worker-and-feature.md +3 -3
  61. package/docs/design/README.md +124 -42
  62. package/docs/development-principles.md +4 -4
  63. package/docs/exec-plans/active/README.md +12 -11
  64. package/docs/exec-plans/completed/README.md +33 -0
  65. package/docs/feature-workflow.md +114 -39
  66. package/docs/init-surface.manifest.json +30 -3
  67. package/docs/loop-agent-harness.md +9 -8
  68. package/docs/production-readiness.md +1 -1
  69. package/docs/progress/README.md +23 -1
  70. package/docs/reports/README.md +65 -6
  71. package/docs/skills/vetted-skill-registry.md +2 -0
  72. package/docs/templates/agent-dag.schema.json +29 -1
  73. package/docs/templates/agent-dag.supervised-implementation.json +127 -8
  74. package/docs/templates/backend-test-analysis.schema.json +44 -0
  75. package/docs/templates/backend-test-case-manifest.schema.json +190 -0
  76. package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
  77. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +81 -16
  78. package/docs/templates/backend-test-dag.json +311 -40
  79. package/docs/templates/backend-test-dag.retrospect.prompt.md +44 -30
  80. package/docs/templates/backend-test-dag.review-cases.prompt.md +10 -8
  81. package/docs/templates/backend-test-execution.schema.json +133 -0
  82. package/docs/templates/backend-test-result.schema.json +99 -0
  83. package/docs/templates/branch-merge-report.md +93 -0
  84. package/docs/templates/frontend-design-contract.md +9 -0
  85. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
  86. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
  87. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
  88. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
  89. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
  90. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
  91. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
  92. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
  93. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
  94. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
  95. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
  96. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
  97. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
  98. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
  99. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
  100. package/docs/templates/frontend-eval/metrics.md +138 -0
  101. package/docs/templates/frontend-eval/smoke-targets.md +53 -0
  102. package/docs/templates/frontend-implementation-contract.schema.json +27 -0
  103. package/docs/templates/frontend-task-constraints.md +10 -0
  104. package/docs/templates/frontend-task-requirement.md +9 -0
  105. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
  106. package/docs/templates/frontend-test-dag.json +23 -0
  107. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
  108. package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
  109. package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
  110. package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
  111. package/docs/templates/knowledge-graph-bootstrap-dag.json +1 -1
  112. package/docs/templates/knowledge-sync-dag.json +1 -0
  113. package/docs/verification-matrix.md +4 -1
  114. package/examples/decision-gate-agent-dag.json +4 -4
  115. package/examples/hybrid-loop-agent-dag.json +1 -1
  116. package/package.json +2 -2
  117. package/scripts/kb-bootstrap-init-skeleton.sh +2 -1
  118. package/scripts/kb-graph-incremental-prepare.mjs +19 -5
  119. package/scripts/kb-graph-promote.mjs +12 -1
  120. package/skills/ai-engineering-context/SKILL.md +2 -2
  121. package/skills/analyze-product-dependencies/SKILL.md +67 -0
  122. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
  123. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
  124. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
  125. package/skills/analyze-product-dependencies/references/example.md +76 -0
  126. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
  127. package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
  128. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
  129. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
  130. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
  131. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
  132. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
  133. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
  134. package/skills/analyze-product-requirements/SKILL.md +90 -0
  135. package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
  136. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
  137. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
  138. package/skills/analyze-product-requirements/references/example.md +86 -0
  139. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
  140. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
  141. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
  142. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
  143. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
  144. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
  145. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
  146. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
  147. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
  148. package/skills/browser-tools/SKILL.md +196 -0
  149. package/skills/browser-tools/browser-content.js +103 -0
  150. package/skills/browser-tools/browser-cookies.js +35 -0
  151. package/skills/browser-tools/browser-eval.js +53 -0
  152. package/skills/browser-tools/browser-hn-scraper.js +108 -0
  153. package/skills/browser-tools/browser-nav.js +44 -0
  154. package/skills/browser-tools/browser-pick.js +162 -0
  155. package/skills/browser-tools/browser-screenshot.js +34 -0
  156. package/skills/browser-tools/browser-start.js +86 -0
  157. package/skills/browser-tools/package-lock.json +2556 -0
  158. package/skills/browser-tools/package.json +19 -0
  159. package/skills/frontend-design-review/SKILL.md +6 -1
  160. package/skills/frontend-design-review/references/review-checklist.md +25 -4
  161. package/skills/frontend-implementation/SKILL.md +25 -30
  162. package/skills/frontend-implementation/references/code-standards.md +20 -22
  163. package/skills/frontend-implementation/references/node-contracts.md +17 -53
  164. package/skills/frontend-review/SKILL.md +10 -4
  165. package/skills/frontend-review/references/review-findings.md +8 -3
  166. package/skills/frontend-verification/SKILL.md +22 -9
  167. package/skills/frontend-verification/references/verification-checklist.md +17 -5
  168. package/skills/grill-with-docs/SKILL.md +5 -5
  169. package/skills/grill-with-docs/adr-format.md +3 -3
  170. package/skills/init-capability-evolution/SKILL.md +5 -5
  171. package/skills/loop-agent/SKILL.md +5 -5
  172. package/skills/loop-agent/references/README.md +3 -3
  173. package/skills/loop-agent/references/command-reference.md +98 -24
  174. package/skills/loop-agent/references/docs-converge.md +15 -15
  175. package/skills/loop-agent/references/harness-policy.md +2 -2
  176. package/skills/loop-agent/references/hybrid-dag.md +32 -22
  177. package/skills/loop-agent/references/multi-worktree.md +1 -1
  178. package/skills/loop-agent/references/orchestrator-and-interventions.md +8 -8
  179. package/skills/loop-agent/references/task-workflow.md +1 -1
  180. package/skills/loop-agent/references/verification-and-failure-handling.md +6 -4
  181. package/skills/playwright-cli/SKILL.md +420 -0
  182. package/skills/playwright-cli/references/element-attributes.md +23 -0
  183. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  184. package/skills/playwright-cli/references/request-mocking.md +87 -0
  185. package/skills/playwright-cli/references/running-code.md +241 -0
  186. package/skills/playwright-cli/references/session-management.md +225 -0
  187. package/skills/playwright-cli/references/storage-state.md +275 -0
  188. package/skills/playwright-cli/references/test-generation.md +433 -0
  189. package/skills/playwright-cli/references/tracing.md +139 -0
  190. package/skills/playwright-cli/references/video-recording.md +143 -0
  191. package/skills/playwright-cli-case-generator/SKILL.md +74 -0
  192. package/skills/requesting-code-review/SKILL.md +1 -1
  193. package/skills/systematic-debugging/CREATION-LOG.md +3 -3
  194. package/skills/systematic-debugging/SKILL.md +1 -1
  195. package/skills/systematic-debugging/test-academic.md +1 -1
  196. package/skills/systematic-debugging/test-pressure-1.md +1 -1
  197. package/skills/systematic-debugging/test-pressure-2.md +1 -1
  198. package/skills/systematic-debugging/test-pressure-3.md +1 -1
  199. package/skills/verification-before-completion/SKILL.md +1 -1
@@ -1,6 +1,165 @@
1
1
  /** DOM helpers (Observe UI R5). */
2
2
 
3
- export function clearNode(node) {
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 === "no-evidence"
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
- renderDagInspector(dagRunId, node, false);
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
- renderDagInspector(dagRunId, node, false);
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(