@tea-agent/loop-agent 0.13.0-beta.0 → 0.14.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 (127) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +56 -305
  3. package/README.md +13 -19
  4. package/dist/commands/init.js +92 -23
  5. package/dist/executors/pi-event-serializer.js +33 -11
  6. package/dist/executors/shell-executor.js +200 -21
  7. package/dist/infrastructure/evaluation/candidate-store.js +5 -1
  8. package/dist/worker/observe/spec-evidence.js +19 -10
  9. package/dist/worker/observe/static/app.js +4 -3
  10. package/dist/worker/observe/static/constants.js +10 -2
  11. package/dist/worker/observe/static/dag-helpers.js +37 -8
  12. package/dist/worker/observe/static/dom.js +159 -0
  13. package/dist/worker/observe/static/format-pool.d.ts +71 -0
  14. package/dist/worker/observe/static/format-pool.js +67 -0
  15. package/dist/worker/observe/static/format.js +27 -2
  16. package/dist/worker/observe/static/index.html +76 -34
  17. package/dist/worker/observe/static/kpi.js +12 -6
  18. package/dist/worker/observe/static/markdown-render.js +124 -0
  19. package/dist/worker/observe/static/shell-chrome.js +8 -2
  20. package/dist/worker/observe/static/state.js +20 -0
  21. package/dist/worker/observe/static/styles.css +662 -60
  22. package/dist/worker/observe/static/views/dag-inspector.js +65 -142
  23. package/dist/worker/observe/static/views/dag.js +9 -0
  24. package/dist/worker/observe/static/views/dashboard.js +512 -269
  25. package/dist/worker/observe/static/views/pool.js +595 -237
  26. package/dist/worker/observe/static/views/session-timeline.js +577 -11
  27. package/dist/workflows/dag/backend-test-case-manifest.js +503 -0
  28. package/dist/workflows/dag/backend-test-execution-contract.js +353 -0
  29. package/dist/workflows/dag/backend-test-result-contract.js +568 -0
  30. package/dist/workflows/dag/decision-envelope.js +57 -2
  31. package/dist/workflows/dag/frontend-implementation-contract.js +240 -0
  32. package/dist/workflows/dag/frontend-project-capability.js +309 -0
  33. package/dist/workflows/dag/frontend-repair.js +341 -0
  34. package/dist/workflows/dag/frontend-risk.js +161 -0
  35. package/dist/workflows/dag/frontend-verification-trace.js +190 -0
  36. package/dist/workflows/dag/init-hybrid.js +1020 -125
  37. package/dist/workflows/dag/repair-artifact.js +43 -3
  38. package/dist/workflows/dag/skill-instructions.js +4 -2
  39. package/dist/workflows/dag/types.js +29 -8
  40. package/docs/README.md +2 -0
  41. package/docs/agent-dag-recovery-playbook.md +3 -3
  42. package/docs/agent-dag-runner.md +3 -3
  43. package/docs/architecture/README.md +3 -3
  44. package/docs/architecture/dag-execution.md +1 -1
  45. package/docs/architecture/evolution.md +13 -13
  46. package/docs/architecture/facts-and-state.md +1 -1
  47. package/docs/architecture/runtime-boundaries.md +7 -7
  48. package/docs/architecture/system-overview.md +3 -3
  49. package/docs/architecture/worker-and-feature.md +3 -3
  50. package/docs/design/README.md +7 -7
  51. package/docs/development-principles.md +4 -4
  52. package/docs/exec-plans/active/README.md +2 -4
  53. package/docs/exec-plans/completed/README.md +29 -6
  54. package/docs/feature-workflow.md +57 -32
  55. package/docs/init-surface.manifest.json +21 -3
  56. package/docs/loop-agent-harness.md +8 -8
  57. package/docs/production-readiness.md +1 -1
  58. package/docs/progress/README.md +20 -3
  59. package/docs/reports/README.md +53 -7
  60. package/docs/templates/agent-dag.supervised-implementation.json +127 -8
  61. package/docs/templates/backend-test-case-manifest.schema.json +190 -0
  62. package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
  63. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +6 -4
  64. package/docs/templates/backend-test-dag.json +269 -21
  65. package/docs/templates/backend-test-dag.retrospect.prompt.md +44 -30
  66. package/docs/templates/backend-test-dag.review-cases.prompt.md +6 -4
  67. package/docs/templates/backend-test-execution.schema.json +133 -0
  68. package/docs/templates/backend-test-result.schema.json +99 -0
  69. package/docs/templates/branch-merge-report.md +93 -0
  70. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
  71. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
  72. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
  73. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
  74. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
  75. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
  76. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
  77. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
  78. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
  79. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
  80. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
  81. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
  82. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
  83. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
  84. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
  85. package/docs/templates/frontend-eval/metrics.md +138 -0
  86. package/docs/templates/frontend-eval/smoke-targets.md +53 -0
  87. package/docs/templates/frontend-implementation-contract.schema.json +27 -0
  88. package/docs/verification-matrix.md +1 -1
  89. package/examples/decision-gate-agent-dag.json +4 -4
  90. package/examples/hybrid-loop-agent-dag.json +1 -1
  91. package/package.json +2 -2
  92. package/skills/ai-engineering-context/SKILL.md +2 -2
  93. package/skills/browser-tools/SKILL.md +196 -0
  94. package/skills/browser-tools/browser-content.js +103 -0
  95. package/skills/browser-tools/browser-cookies.js +35 -0
  96. package/skills/browser-tools/browser-eval.js +53 -0
  97. package/skills/browser-tools/browser-hn-scraper.js +108 -0
  98. package/skills/browser-tools/browser-nav.js +44 -0
  99. package/skills/browser-tools/browser-pick.js +162 -0
  100. package/skills/browser-tools/browser-screenshot.js +34 -0
  101. package/skills/browser-tools/browser-start.js +86 -0
  102. package/skills/browser-tools/package-lock.json +2556 -0
  103. package/skills/browser-tools/package.json +19 -0
  104. package/skills/frontend-implementation/SKILL.md +3 -1
  105. package/skills/frontend-implementation/references/node-contracts.md +17 -66
  106. package/skills/frontend-verification/SKILL.md +1 -1
  107. package/skills/grill-with-docs/SKILL.md +5 -5
  108. package/skills/grill-with-docs/adr-format.md +3 -3
  109. package/skills/init-capability-evolution/SKILL.md +5 -5
  110. package/skills/loop-agent/SKILL.md +5 -5
  111. package/skills/loop-agent/references/README.md +3 -3
  112. package/skills/loop-agent/references/command-reference.md +39 -17
  113. package/skills/loop-agent/references/docs-converge.md +15 -15
  114. package/skills/loop-agent/references/harness-policy.md +2 -2
  115. package/skills/loop-agent/references/hybrid-dag.md +20 -15
  116. package/skills/loop-agent/references/multi-worktree.md +1 -1
  117. package/skills/loop-agent/references/orchestrator-and-interventions.md +8 -8
  118. package/skills/loop-agent/references/task-workflow.md +1 -1
  119. package/skills/loop-agent/references/verification-and-failure-handling.md +6 -4
  120. package/skills/requesting-code-review/SKILL.md +1 -1
  121. package/skills/systematic-debugging/CREATION-LOG.md +3 -3
  122. package/skills/systematic-debugging/SKILL.md +1 -1
  123. package/skills/systematic-debugging/test-academic.md +1 -1
  124. package/skills/systematic-debugging/test-pressure-1.md +1 -1
  125. package/skills/systematic-debugging/test-pressure-2.md +1 -1
  126. package/skills/systematic-debugging/test-pressure-3.md +1 -1
  127. package/skills/verification-before-completion/SKILL.md +1 -1
@@ -1,6 +1,7 @@
1
1
  /** DAG node inspector panel (Observe UI R5). */
2
2
  import {
3
3
  DAG_INSPECTOR_DEFAULT_WIDTH,
4
+ DAG_INSPECTOR_DEFAULT_WIDTH_RATIO,
4
5
  DAG_INSPECTOR_MIN_WIDTH,
5
6
  DAG_INSPECTOR_MAX_WIDTH,
6
7
  DAG_INSPECTOR_RIGHT_OFFSET,
@@ -10,9 +11,11 @@ import {
10
11
  } from "../constants.js";
11
12
  import { clearNode, el } from "../dom.js";
12
13
  import { fetchJson } from "../api.js";
13
- import { badge, parseMarkdownBlocks, markdownLinkHref } from "../format.js";
14
+ import { badge } from "../format.js";
15
+ import { renderMarkdown } from "../markdown-render.js";
14
16
  import {
15
17
  uiState,
18
+ makeSessionEventIdentity,
16
19
  updateDagTimelineViewportState,
17
20
  updateDagOutputViewportState,
18
21
  computeFollowLatest,
@@ -23,6 +26,17 @@ import {
23
26
  renderSessionTimeline,
24
27
  } from "./session-timeline.js";
25
28
 
29
+ // Current inspector selection. Tab handlers reference this mutable slot so a
30
+ // reused inspector shell never keeps stale node closures: every render writes
31
+ // the latest {dagRunId, node} here and tab clicks re-render from the same
32
+ // reference, always resolving the currently selected node identity.
33
+ const inspectorSelection = { dagRunId: null, node: null };
34
+
35
+ function renderDagInspectorForCurrent() {
36
+ if (!inspectorSelection.dagRunId || !inspectorSelection.node) return;
37
+ renderDagInspector(inspectorSelection.dagRunId, inspectorSelection.node, false);
38
+ }
39
+
26
40
  export function selectDagNode(dagRunId, nodeId) {
27
41
  const nodeChanged = uiState.selectedDagNodeId !== nodeId;
28
42
  uiState.selectedDagNodeId = nodeId;
@@ -30,6 +44,9 @@ export function selectDagNode(dagRunId, nodeId) {
30
44
  if (nodeChanged) {
31
45
  uiState.sessionEventOffset = 0;
32
46
  uiState.sessionEvents = [];
47
+ // Bump the session-event identity token so any in-flight response for the
48
+ // previous node is rejected before it can mutate the current node cache.
49
+ uiState.sessionEventIdentity = makeSessionEventIdentity(dagRunId, nodeId);
33
50
  }
34
51
  void import("./dag.js").then((m) => m.renderDagDetail(dagRunId, false));
35
52
  }
@@ -65,6 +82,17 @@ export function dagInspectorMaximumWidth() {
65
82
  );
66
83
  }
67
84
 
85
+ /** Default open width: half the viewport, clamped to min/max. */
86
+ export function dagInspectorDefaultWidth() {
87
+ const half = Math.round(
88
+ window.innerWidth * DAG_INSPECTOR_DEFAULT_WIDTH_RATIO,
89
+ );
90
+ return Math.min(
91
+ dagInspectorMaximumWidth(),
92
+ Math.max(DAG_INSPECTOR_MIN_WIDTH, half || DAG_INSPECTOR_DEFAULT_WIDTH),
93
+ );
94
+ }
95
+
68
96
  export function setDagInspectorWidth(panel, requestedWidth) {
69
97
  const maximum = dagInspectorMaximumWidth();
70
98
  uiState.dagInspectorWidth = Math.min(
@@ -95,7 +123,7 @@ export function createDagInspectorResizeHandle(panel) {
95
123
  panel,
96
124
  requestedWidth ??
97
125
  uiState.dagInspectorWidth ??
98
- DAG_INSPECTOR_DEFAULT_WIDTH,
126
+ dagInspectorDefaultWidth(),
99
127
  );
100
128
  handle.setAttribute("aria-valuemin", String(range.minimum));
101
129
  handle.setAttribute("aria-valuemax", String(range.maximum));
@@ -123,7 +151,7 @@ export function createDagInspectorResizeHandle(panel) {
123
151
  handle.addEventListener("pointercancel", stopResizing);
124
152
  handle.addEventListener("keydown", (event) => {
125
153
  const currentWidth =
126
- uiState.dagInspectorWidth ?? DAG_INSPECTOR_DEFAULT_WIDTH;
154
+ uiState.dagInspectorWidth ?? dagInspectorDefaultWidth();
127
155
  if (event.key === "ArrowLeft") {
128
156
  event.preventDefault();
129
157
  updateRange(currentWidth + DAG_INSPECTOR_KEYBOARD_STEP);
@@ -160,118 +188,6 @@ export function captureDagTimelineViewportState(panel) {
160
188
  }
161
189
  }
162
190
 
163
- export function appendInlineMarkdown(target, text) {
164
- const tokenPattern = /(`[^`]*`|\*\*[^*]+\*\*|\*[^*]+\*|\[[^\]]+\]\([^)]+\))/g;
165
- let cursor = 0;
166
- for (const match of text.matchAll(tokenPattern)) {
167
- const index = match.index ?? 0;
168
- if (index > cursor)
169
- target.appendChild(document.createTextNode(text.slice(cursor, index)));
170
- const token = match[0];
171
- if (token.startsWith("`")) {
172
- target.appendChild(el("code", null, token.slice(1, -1)));
173
- } else if (token.startsWith("**")) {
174
- const strong = document.createElement("strong");
175
- strong.textContent = token.slice(2, -2);
176
- target.appendChild(strong);
177
- } else if (token.startsWith("*")) {
178
- const emphasis = document.createElement("em");
179
- emphasis.textContent = token.slice(1, -1);
180
- target.appendChild(emphasis);
181
- } else {
182
- const link = token.match(/^\[([^\]]+)\]\((.+)\)$/);
183
- const href = markdownLinkHref(link?.[2]);
184
- if (link && href) {
185
- const anchor = document.createElement("a");
186
- anchor.href = href;
187
- anchor.target = "_blank";
188
- anchor.rel = "noopener noreferrer";
189
- anchor.textContent = link[1];
190
- target.appendChild(anchor);
191
- } else {
192
- target.appendChild(document.createTextNode(token));
193
- }
194
- }
195
- cursor = index + token.length;
196
- }
197
- if (cursor < text.length)
198
- target.appendChild(document.createTextNode(text.slice(cursor)));
199
- }
200
-
201
- export function renderMarkdown(content) {
202
- const article = el("article", "markdown-output");
203
- for (const block of parseMarkdownBlocks(content)) {
204
- switch (block.type) {
205
- case "heading": {
206
- const heading = document.createElement(`h${block.level}`);
207
- appendInlineMarkdown(heading, block.text);
208
- article.appendChild(heading);
209
- break;
210
- }
211
- case "paragraph": {
212
- const paragraph = document.createElement("p");
213
- appendInlineMarkdown(paragraph, block.text);
214
- article.appendChild(paragraph);
215
- break;
216
- }
217
- case "quote": {
218
- const quote = document.createElement("blockquote");
219
- appendInlineMarkdown(quote, block.text);
220
- article.appendChild(quote);
221
- break;
222
- }
223
- case "list": {
224
- const list = document.createElement(block.ordered ? "ol" : "ul");
225
- for (const item of block.items) {
226
- const listItem = document.createElement("li");
227
- appendInlineMarkdown(listItem, item);
228
- list.appendChild(listItem);
229
- }
230
- article.appendChild(list);
231
- break;
232
- }
233
- case "code": {
234
- const pre = document.createElement("pre");
235
- const code = document.createElement("code");
236
- if (block.language) code.dataset.language = block.language;
237
- code.textContent = block.text;
238
- pre.appendChild(code);
239
- article.appendChild(pre);
240
- break;
241
- }
242
- case "table": {
243
- const table = document.createElement("table");
244
- const head = document.createElement("thead");
245
- const headRow = document.createElement("tr");
246
- for (const value of block.headers) {
247
- const cell = document.createElement("th");
248
- appendInlineMarkdown(cell, value);
249
- headRow.appendChild(cell);
250
- }
251
- head.appendChild(headRow);
252
- table.appendChild(head);
253
- const body = document.createElement("tbody");
254
- for (const row of block.rows) {
255
- const tableRow = document.createElement("tr");
256
- for (const value of row) {
257
- const cell = document.createElement("td");
258
- appendInlineMarkdown(cell, value);
259
- tableRow.appendChild(cell);
260
- }
261
- body.appendChild(tableRow);
262
- }
263
- table.appendChild(body);
264
- article.appendChild(table);
265
- break;
266
- }
267
- case "rule":
268
- article.appendChild(document.createElement("hr"));
269
- break;
270
- }
271
- }
272
- return article;
273
- }
274
-
275
191
  async function renderSpecEvidence(content, dagRunId, nodeId) {
276
192
  const evidence = await fetchJson(
277
193
  `/api/dag-runs/${encodeURIComponent(dagRunId)}/nodes/${encodeURIComponent(nodeId)}/spec-evidence`,
@@ -392,33 +308,35 @@ async function renderSpecEvidence(content, dagRunId, nodeId) {
392
308
  }
393
309
  }
394
310
 
395
- function fillDagInspectorContent(content, dagRunId, node) {
396
- const activeTab = ["timeline", "spec-evidence"].includes(
397
- uiState.dagInspectorTab,
398
- )
399
- ? uiState.dagInspectorTab
400
- : "output";
401
- content.id = `dag-inspector-panel-${activeTab}`;
402
- content.setAttribute("role", "tabpanel");
403
- content.setAttribute("aria-labelledby", `dag-inspector-tab-${activeTab}`);
404
- content.dataset.dagNodeId = String(node.nodeId ?? "");
405
- if (activeTab === "spec-evidence") {
406
- void renderSpecEvidence(content, dagRunId, node.nodeId);
407
- } else if (activeTab === "timeline") {
408
- renderSessionTimeline(content, node.nodeId);
409
- } else if (!node.outputPreview && !node.errorPreview) {
410
- content.appendChild(el("p", "empty", UI_TEXT.noOutput));
411
- } else {
412
- if (node.outputPreview)
413
- content.appendChild(renderMarkdown(node.outputPreview));
414
- if (node.errorPreview) {
415
- const error = el("section", "markdown-error");
416
- error.appendChild(el("p", "markdown-error-title", "错误输出"));
417
- error.appendChild(renderMarkdown(node.errorPreview));
418
- content.appendChild(error);
311
+ function fillDagInspectorContent(content, dagRunId, node) {
312
+ const activeTab = ["timeline", "spec-evidence"].includes(
313
+ uiState.dagInspectorTab,
314
+ )
315
+ ? uiState.dagInspectorTab
316
+ : "output";
317
+ content.id = `dag-inspector-panel-${activeTab}`;
318
+ content.setAttribute("role", "tabpanel");
319
+ content.setAttribute("aria-labelledby", `dag-inspector-tab-${activeTab}`);
320
+ content.dataset.dagNodeId = String(node.nodeId ?? "");
321
+ // Drop timeline-only classes when switching tabs so output layout stays clean.
322
+ content.className = "dag-inspector-content";
323
+ if (activeTab === "spec-evidence") {
324
+ void renderSpecEvidence(content, dagRunId, node.nodeId);
325
+ } else if (activeTab === "timeline") {
326
+ renderSessionTimeline(content, node.nodeId);
327
+ } else if (!node.outputPreview && !node.errorPreview) {
328
+ content.appendChild(el("p", "empty", UI_TEXT.noOutput));
329
+ } else {
330
+ if (node.outputPreview)
331
+ content.appendChild(renderMarkdown(node.outputPreview));
332
+ if (node.errorPreview) {
333
+ const error = el("section", "markdown-error");
334
+ error.appendChild(el("p", "markdown-error-title", "错误输出"));
335
+ error.appendChild(renderMarkdown(node.errorPreview));
336
+ content.appendChild(error);
337
+ }
419
338
  }
420
339
  }
421
- }
422
340
 
423
341
  function buildDagInspectorHeader(dagRunId, node) {
424
342
  const header = el("div", "dag-inspector-header");
@@ -468,7 +386,7 @@ function buildDagInspectorHeader(dagRunId, node) {
468
386
  button.appendChild(document.createTextNode(` ${label}`));
469
387
  button.addEventListener("click", () => {
470
388
  uiState.dagInspectorTab = tab;
471
- renderDagInspector(dagRunId, node, false);
389
+ renderDagInspectorForCurrent();
472
390
  const next = document.getElementById(`dag-inspector-tab-${tab}`);
473
391
  next?.focus();
474
392
  });
@@ -482,7 +400,7 @@ function buildDagInspectorHeader(dagRunId, node) {
482
400
  const delta = event.key === "ArrowRight" ? 1 : -1;
483
401
  const nextTab = order[(idx + delta + order.length) % order.length];
484
402
  uiState.dagInspectorTab = nextTab;
485
- renderDagInspector(dagRunId, node, false);
403
+ renderDagInspectorForCurrent();
486
404
  document.getElementById(`dag-inspector-tab-${nextTab}`)?.focus();
487
405
  });
488
406
  tabs.appendChild(button);
@@ -523,6 +441,11 @@ function dagInspectorSignature(dagRunId, node) {
523
441
  }
524
442
 
525
443
  export function renderDagInspector(dagRunId, node, initial = true) {
444
+ // Record the current selection before any early-return so tab handlers
445
+ // (which reference the mutable inspectorSelection slot) always re-render
446
+ // the currently selected node, never a stale build-time closure.
447
+ inspectorSelection.dagRunId = dagRunId;
448
+ inspectorSelection.node = node;
526
449
  const panel = document.getElementById("dag-inspector");
527
450
  if (!panel) return;
528
451
  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(