@tea-agent/loop-agent 0.11.0 → 0.13.0-alpha.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 (123) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/README.md +33 -4
  3. package/dist/application/dag/generate-task-dag.js +45 -0
  4. package/dist/application/dag/run-dag.js +10 -0
  5. package/dist/application/dag/validate-dag.js +11 -0
  6. package/dist/cli/command-definitions.js +10 -3
  7. package/dist/commands/init.js +74 -7
  8. package/dist/commands/knowledge.js +129 -31
  9. package/dist/governance/manifest-types.js +3 -0
  10. package/dist/shared/package-metadata.js +135 -0
  11. package/dist/task/config-types.js +6 -1
  12. package/dist/worker/cli.js +99 -2
  13. package/dist/worker/delivery/package.js +3 -3
  14. package/dist/worker/feature/decision-loader.js +37 -6
  15. package/dist/worker/feature/next-action.js +10 -2
  16. package/dist/worker/feature/ready-plan-projection.js +81 -0
  17. package/dist/worker/feature/reducer.js +2 -1
  18. package/dist/worker/feature/review.js +19 -2
  19. package/dist/worker/feature/run.js +27 -2
  20. package/dist/worker/follow-up/approve.js +5 -2
  21. package/dist/worker/follow-up/factory.js +1 -1
  22. package/dist/worker/observability/event-history.js +216 -0
  23. package/dist/worker/observability/read-model.js +552 -118
  24. package/dist/worker/observe/paths.js +17 -0
  25. package/dist/worker/observe/routes.js +310 -23
  26. package/dist/worker/observe/server.js +59 -1
  27. package/dist/worker/observe/spec-evidence.js +281 -0
  28. package/dist/worker/observe/static/api.js +46 -0
  29. package/dist/worker/observe/static/app.js +120 -2598
  30. package/dist/worker/observe/static/constants.js +148 -0
  31. package/dist/worker/observe/static/copy.js +67 -0
  32. package/dist/worker/observe/static/dag-helpers.js +172 -0
  33. package/dist/worker/observe/static/dag-model.js +72 -0
  34. package/dist/worker/observe/static/dom.js +61 -0
  35. package/dist/worker/observe/static/format-pool.js +67 -0
  36. package/dist/worker/observe/static/format.js +292 -0
  37. package/dist/worker/observe/static/index.html +300 -82
  38. package/dist/worker/observe/static/kpi.js +94 -0
  39. package/dist/worker/observe/static/relations.js +133 -0
  40. package/dist/worker/observe/static/router.js +93 -0
  41. package/dist/worker/observe/static/run-processing.js +148 -0
  42. package/dist/worker/observe/static/shell-chrome.js +68 -0
  43. package/dist/worker/observe/static/state.js +253 -0
  44. package/dist/worker/observe/static/styles.css +1731 -495
  45. package/dist/worker/observe/static/views/batch.js +227 -0
  46. package/dist/worker/observe/static/views/dag-graph.js +172 -0
  47. package/dist/worker/observe/static/views/dag-inspector.js +596 -0
  48. package/dist/worker/observe/static/views/dag.js +362 -0
  49. package/dist/worker/observe/static/views/dashboard.js +445 -0
  50. package/dist/worker/observe/static/views/failures.js +143 -0
  51. package/dist/worker/observe/static/views/feature.js +492 -0
  52. package/dist/worker/observe/static/views/pool.js +350 -0
  53. package/dist/worker/observe/static/views/run.js +453 -0
  54. package/dist/worker/observe/static/views/session-timeline.js +205 -0
  55. package/dist/worker/observe/static/views/shell.js +7 -0
  56. package/dist/worker/observe/static/views/task.js +314 -0
  57. package/dist/worker/observe/static/views/timeline.js +163 -0
  58. package/dist/worker/pool/doctor.js +165 -0
  59. package/dist/worker/pool/migrate-state.js +303 -0
  60. package/dist/worker/pool/run-store.js +205 -17
  61. package/dist/worker/pool/types.js +17 -1
  62. package/dist/worker/pool/validation.js +100 -15
  63. package/dist/worker/report/morning-report.js +12 -2
  64. package/dist/worker/runner/run-ready.js +41 -26
  65. package/dist/worker/task-graph/ready-planner.js +136 -0
  66. package/dist/workflows/dag/controller-identity.js +104 -0
  67. package/dist/workflows/dag/convergence/controller.js +16 -8
  68. package/dist/workflows/dag/failure-routing.js +12 -1
  69. package/dist/workflows/dag/init-hybrid.js +1233 -11
  70. package/dist/workflows/dag/node-execution.js +123 -29
  71. package/dist/workflows/dag/repair-artifact.js +91 -0
  72. package/dist/workflows/dag/report.js +50 -0
  73. package/dist/workflows/dag/retry-policy.js +138 -0
  74. package/dist/workflows/dag/runner.js +32 -0
  75. package/dist/workflows/dag/runtime-contract.js +87 -0
  76. package/dist/workflows/dag/skill-snapshot.js +2 -0
  77. package/dist/workflows/dag/types.js +45 -1
  78. package/dist/workflows/dag/validate.js +68 -4
  79. package/docs/README.md +1 -1
  80. package/docs/agent-dag-recovery-playbook.md +9 -0
  81. package/docs/agent-dag-runner.md +26 -1
  82. package/docs/architecture/dag-execution.md +6 -0
  83. package/docs/architecture/evolution.md +7 -5
  84. package/docs/architecture/facts-and-state.md +15 -2
  85. package/docs/architecture/worker-and-feature.md +6 -2
  86. package/docs/decisions/README.md +3 -0
  87. package/docs/design/README.md +12 -3
  88. package/docs/exec-plans/active/README.md +2 -2
  89. package/docs/exec-plans/completed/README.md +12 -0
  90. package/docs/feature-workflow.md +108 -2
  91. package/docs/loop-agent-harness.md +15 -4
  92. package/docs/progress/README.md +22 -0
  93. package/docs/reports/README.md +14 -2
  94. package/docs/templates/agent-dag-report.schema.json +17 -0
  95. package/docs/templates/agent-dag.schema.json +69 -1
  96. package/docs/templates/agent-dag.supervised-implementation.json +8 -2
  97. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +139 -0
  98. package/docs/templates/backend-test-dag.json +288 -0
  99. package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -0
  100. package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -0
  101. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
  102. package/docs/templates/knowledge-sync-dag.json +177 -0
  103. package/docs/templates/knowledge-sync-draft.schema.json +71 -0
  104. package/docs/verification-matrix.md +2 -1
  105. package/package.json +8 -2
  106. package/scripts/kb-bootstrap-init-skeleton.sh +239 -0
  107. package/scripts/kb-graph-incremental-prepare.mjs +372 -0
  108. package/scripts/kb-graph-incremental-prepare.sh +5 -0
  109. package/scripts/kb-graph-materialize.mjs +105 -0
  110. package/scripts/kb-graph-materialize.sh +4 -0
  111. package/scripts/kb-graph-promote.mjs +153 -0
  112. package/scripts/kb-graph-promote.sh +4 -0
  113. package/scripts/kb-query.mjs +554 -0
  114. package/scripts/kb-query.sh +5 -0
  115. package/skills/agent-worker/SKILL.md +3 -1
  116. package/skills/agent-worker/references/agent-worker-operator.md +18 -1
  117. package/skills/frontend-design-review/SKILL.md +26 -24
  118. package/skills/frontend-implementation/SKILL.md +29 -26
  119. package/skills/frontend-implementation/references/node-contracts.md +50 -19
  120. package/skills/frontend-review/SKILL.md +1 -1
  121. package/skills/loop-agent/references/command-reference.md +2 -0
  122. package/skills/loop-agent/references/hybrid-dag.md +22 -3
  123. package/skills/loop-agent/references/verification-and-failure-handling.md +6 -0
@@ -0,0 +1,362 @@
1
+ /** Observe UI view module (R5). */
2
+ import {
3
+ UI_TEXT,
4
+ LIVENESS_LABELS,
5
+ ARTIFACT_LABELS,
6
+ ARTIFACT_KEYS,
7
+ STEP_LABELS,
8
+ STEP_ORDER,
9
+ DAG_INSPECTOR_DEFAULT_WIDTH,
10
+ DAG_INSPECTOR_MIN_WIDTH,
11
+ DAG_INSPECTOR_MAX_WIDTH,
12
+ DAG_INSPECTOR_RIGHT_OFFSET,
13
+ DAG_INSPECTOR_VIEWPORT_MARGIN,
14
+ DAG_INSPECTOR_KEYBOARD_STEP,
15
+ TERMINAL_RUN_STATUSES,
16
+ DAG_EFFECTIVE_STATUS_LABELS,
17
+ STATUS_LABELS,
18
+ } from "../constants.js";
19
+ import { clearNode, el, metaGrid, buildTable } from "../dom.js";
20
+ import {
21
+ badge,
22
+ badgeClass,
23
+ statusLabel,
24
+ formatTs,
25
+ formatMs,
26
+ formatDuration,
27
+ formatAgo,
28
+ dagStatusBadge,
29
+ livenessBadge,
30
+ displayDurationMs,
31
+ derivedDurationMs,
32
+ summaryText,
33
+ truncateText,
34
+ repoBaseName,
35
+ parseMarkdownBlocks,
36
+ markdownLinkHref,
37
+ } from "../format.js";
38
+ import { parseRoute, navigate, buildHashPath } from "../router.js";
39
+ import {
40
+ fetchJson,
41
+ artifactUrl,
42
+ parseArtifactPreviewResponse,
43
+ } from "../api.js";
44
+ import {
45
+ objectRouteLink,
46
+ findFeatureInSnapshot,
47
+ featureLookupState,
48
+ buildObjectRelations,
49
+ renderObjectRelationBar,
50
+ } from "../relations.js";
51
+ import { copyText, renderRecommendedCommand } from "../copy.js";
52
+ import {
53
+ isPageVisible,
54
+ detailPollDelay,
55
+ dashboardPollDelay,
56
+ captureDagGraphViewportState,
57
+ updateDagGraphViewportState,
58
+ updateDagTimelineViewportState,
59
+ shouldReuseDagDetailShell,
60
+ shouldPreserveDiagnosticsOpen,
61
+ renderViewState,
62
+ mountViewState,
63
+ } from "../state.js";
64
+ import { uiState, dagGraphViewportStates } from "../state.js";
65
+ import { sortPoolTasks } from "../format-pool.js";
66
+ import {
67
+ dagMetaVisibility,
68
+ isDagRunActive,
69
+ dashboardVisibleFeatures,
70
+ findDagForRun,
71
+ } from "../dag-model.js";
72
+ import { layoutDag } from "../dag-layout.js";
73
+ import {
74
+ showView,
75
+ setBreadcrumb,
76
+ updateHeaderRefresh,
77
+ mountRelationBar,
78
+ } from "../shell-chrome.js";
79
+ import {
80
+ dagProgress,
81
+ isNodeFailed,
82
+ isNodeFinished,
83
+ isNodeActive,
84
+ renderRankLanes,
85
+ } from "../dag-helpers.js";
86
+ import { findBatchRunIdForTask } from "../run-processing.js";
87
+ import { renderDagGraph } from "./dag-graph.js";
88
+ import {
89
+ closeDagInspector,
90
+ selectDagNode,
91
+ renderDagInspector,
92
+ } from "./dag-inspector.js";
93
+ import { mergeSessionEvents } from "./session-timeline.js";
94
+
95
+ export async function renderDagDetail(dagRunId, initial = true) {
96
+ showView("dag");
97
+ setBreadcrumb([
98
+ { label: UI_TEXT.dashboard, href: "#/" },
99
+ { label: dagRunId },
100
+ ]);
101
+
102
+ if (initial || uiState.currentDagRunId !== dagRunId) {
103
+ uiState.currentDagRunId = dagRunId;
104
+ uiState.selectedDagNodeId = null;
105
+ uiState.sessionEventOffset = 0;
106
+ uiState.sessionEvents = [];
107
+ uiState.dagInspectorOpen = false;
108
+ uiState.dagInspectorTab = "output";
109
+ uiState.dagGraphViewportState = null;
110
+ uiState.dagTimelineViewportState = null;
111
+ }
112
+
113
+ const metaEl = document.getElementById("dag-meta");
114
+ const ranksEl = document.getElementById("dag-ranks");
115
+ const nodesEl = document.getElementById("dag-nodes");
116
+ if (initial && metaEl) mountViewState(metaEl, "loading", "加载 DAG…");
117
+ uiState.dagGraphViewportState = captureDagGraphViewportState(
118
+ uiState.dagGraphViewportState,
119
+ dagRunId,
120
+ ranksEl.querySelector(".dag-graph-viewport"),
121
+ );
122
+ if (uiState.dagGraphViewportState?.dagRunId === dagRunId) {
123
+ dagGraphViewportStates.set(dagRunId, uiState.dagGraphViewportState);
124
+ }
125
+
126
+ const [dag, snapshot] = await Promise.all([
127
+ fetchJson(`/api/dag-runs/${encodeURIComponent(dagRunId)}`),
128
+ fetchJson("/api/snapshot"),
129
+ ]);
130
+ if (snapshot) {
131
+ uiState.lastSnapshot = snapshot;
132
+ updateHeaderRefresh(snapshot.generatedAt);
133
+ }
134
+ if (!dag) {
135
+ clearNode(metaEl);
136
+ mountViewState(metaEl, "error", "找不到该 DAG 运行。");
137
+ mountRelationBar("dag-relations", {}, snapshot);
138
+ closeDagInspector();
139
+ return false;
140
+ }
141
+
142
+ // Only link feature/task when a snapshot object carries this dagRunId (no naming guess).
143
+ const snap = snapshot ?? uiState.lastSnapshot;
144
+ const linkedTask = (snap?.tasks ?? []).find(
145
+ (t) => t.dagRunId === dag.dagRunId,
146
+ );
147
+ mountRelationBar(
148
+ "dag-relations",
149
+ {
150
+ featureId: linkedTask?.featureId,
151
+ taskId: linkedTask?.taskId,
152
+ batchRunId:
153
+ linkedTask?.batchRunId ??
154
+ (linkedTask ? findBatchRunIdForTask(snap, linkedTask) : undefined),
155
+ workerRunId: linkedTask?.workerRunId,
156
+ dagRunId: dag.dagRunId,
157
+ },
158
+ snap,
159
+ );
160
+
161
+ const nodes = dag.nodes ?? [];
162
+ if (
163
+ uiState.selectedDagNodeId &&
164
+ !nodes.some((n) => n.nodeId === uiState.selectedDagNodeId)
165
+ ) {
166
+ uiState.selectedDagNodeId = null;
167
+ uiState.dagInspectorOpen = false;
168
+ if (initial) {
169
+ uiState.sessionEventOffset = 0;
170
+ uiState.sessionEvents = [];
171
+ }
172
+ }
173
+
174
+ const progress = dagProgress(dag);
175
+ const failedCount = nodes.filter((n) => isNodeFailed(n.status)).length;
176
+ const skippedCount = nodes.filter(
177
+ (n) => (n.status ?? "").toLowerCase() === "skipped",
178
+ ).length;
179
+ const succeededCount = nodes.filter(
180
+ (n) => isNodeFinished(n.status) && !isNodeFailed(n.status),
181
+ ).length;
182
+
183
+ // reuse-dag-detail-shell: on a poll refresh (non-initial) we keep the
184
+ // interactive DOM identity stable so active scroll/resize gestures and the
185
+ // diagnostics <details> open state survive the redraw. Initial paints and
186
+ // DAG-run switches rebuild everything from scratch as before.
187
+ const existingGraphWrap =
188
+ ranksEl.querySelector(":scope > .dag-graph-wrap") ?? null;
189
+ const existingViewport =
190
+ existingGraphWrap?.querySelector(":scope > .dag-graph-viewport") ?? null;
191
+ const existingDetails =
192
+ metaEl.querySelector("details.meta-diagnostics") ?? null;
193
+ const reuseShell = shouldReuseDagDetailShell(
194
+ initial,
195
+ Boolean(existingViewport),
196
+ );
197
+ const preserveDetailsOpen = shouldPreserveDiagnosticsOpen(
198
+ initial,
199
+ Boolean(existingDetails?.open),
200
+ );
201
+
202
+ const visibility = dagMetaVisibility(dag);
203
+ const livenessValue =
204
+ LIVENESS_LABELS[dag.liveness] ?? dag.liveness ?? LIVENESS_LABELS.unknown;
205
+ const primaryEntries = [
206
+ ["标题", dag.title || dag.dagRunId],
207
+ ["DAG 运行 ID", dag.dagRunId],
208
+ ["状态", dagStatusBadge(dag.effectiveStatus ?? dag.status)],
209
+ ];
210
+ if (visibility.showExecutorStatus)
211
+ primaryEntries.push(["执行器状态", livenessValue]);
212
+ if (visibility.showConsistencyWarning)
213
+ primaryEntries.push(["状态一致性", "不一致,需要处理"]);
214
+ if (visibility.showResume) primaryEntries.push(["允许恢复", "是"]);
215
+ if (visibility.showReconcile) primaryEntries.push(["允许收口", "是"]);
216
+ primaryEntries.push(
217
+ ["开始时间", formatTs(dag.startedAt)],
218
+ ["结束时间", formatTs(dag.finishedAt)],
219
+ ["总耗时", formatDuration(dag.durationMs)],
220
+ ["进度", `${progress.finished}/${progress.total}`],
221
+ ["成功/失败/跳过", `${succeededCount}/${failedCount}/${skippedCount}`],
222
+ ["DAG 路径", dag.dagPath ?? "—"],
223
+ );
224
+ const primaryGrid = metaGrid(primaryEntries);
225
+ const diagnosticGrid = metaGrid([
226
+ ["当前判定", dagStatusBadge(dag.effectiveStatus ?? dag.status)],
227
+ ["原始记录状态", badge(dag.status)],
228
+ ["执行器状态", livenessValue],
229
+ ["生命周期", dag.lifecycle ?? "—"],
230
+ [
231
+ "状态一致性",
232
+ visibility.showConsistencyWarning ? "不一致,需要处理" : "一致",
233
+ ],
234
+ ["允许恢复", visibility.showResume ? "是" : "否"],
235
+ ["允许收口", visibility.showReconcile ? "是" : "否"],
236
+ ]);
237
+ if (!initial && existingDetails?.parentNode === metaEl) {
238
+ const existingPrimary = metaEl.querySelector(":scope > .meta-grid");
239
+ if (existingPrimary) metaEl.replaceChild(primaryGrid, existingPrimary);
240
+ else metaEl.insertBefore(primaryGrid, existingDetails);
241
+ const existingDiagnosticGrid = existingDetails.querySelector(
242
+ ":scope > .meta-grid",
243
+ );
244
+ if (existingDiagnosticGrid) {
245
+ existingDetails.replaceChild(diagnosticGrid, existingDiagnosticGrid);
246
+ } else {
247
+ existingDetails.appendChild(diagnosticGrid);
248
+ }
249
+ existingDetails.open = preserveDetailsOpen;
250
+ } else {
251
+ clearNode(metaEl);
252
+ metaEl.appendChild(primaryGrid);
253
+ const details = el("details", "meta-diagnostics");
254
+ details.appendChild(el("summary", null, "诊断信息"));
255
+ details.appendChild(diagnosticGrid);
256
+ metaEl.appendChild(details);
257
+ }
258
+
259
+ const graphGestureActive = document.body.classList.contains(
260
+ "is-resizing-dag-graph",
261
+ );
262
+ if (nodes.length === 0) {
263
+ clearNode(ranksEl);
264
+ ranksEl.appendChild(el("h3", null, "DAG 依赖图"));
265
+ ranksEl.appendChild(el("p", "empty", UI_TEXT.noNodes));
266
+ } else if (reuseShell && existingGraphWrap?.parentNode === ranksEl) {
267
+ // reuse-dag-graph-wrap: keep wrap identity across polls when possible.
268
+ if (!graphGestureActive) {
269
+ renderDagGraph(dag, dagRunId, existingGraphWrap);
270
+ }
271
+ } else {
272
+ clearNode(ranksEl);
273
+ ranksEl.appendChild(el("h3", null, "DAG 依赖图"));
274
+ ranksEl.appendChild(renderDagGraph(dag, dagRunId, null));
275
+ ranksEl.appendChild(renderRankLanes(dag));
276
+ }
277
+
278
+ const nodesScrollLeft = nodesEl.scrollLeft;
279
+ clearNode(nodesEl);
280
+ const nodeHeading = el("div", "dag-section-heading");
281
+ nodeHeading.appendChild(el("h3", null, "节点"));
282
+ nodeHeading.appendChild(
283
+ el("span", "dag-section-meta", `共 ${nodes.length} 个节点`),
284
+ );
285
+ nodesEl.appendChild(nodeHeading);
286
+
287
+ if (nodes.length === 0) {
288
+ mountViewState(nodesEl, "empty", UI_TEXT.noNodes);
289
+ } else {
290
+ const sortedNodes = [...nodes].sort((a, b) => {
291
+ const ra = Number(a.rank);
292
+ const rb = Number(b.rank);
293
+ if (!Number.isNaN(ra) && !Number.isNaN(rb) && ra !== rb) return ra - rb;
294
+ if (a.rank !== b.rank) return (a.rank ?? "").localeCompare(b.rank ?? "");
295
+ return a.nodeId.localeCompare(b.nodeId);
296
+ });
297
+
298
+ const rows = sortedNodes.map((node) => {
299
+ const active = isNodeActive(node.status);
300
+ const failed = isNodeFailed(node.status);
301
+ let className = "";
302
+ if (active) className = "dag-active-node";
303
+ if (failed) className = "dag-error-node";
304
+ if (node.nodeId === uiState.selectedDagNodeId) {
305
+ className = className
306
+ ? `${className} dag-node-selected`
307
+ : "dag-node-selected";
308
+ }
309
+ return {
310
+ className,
311
+ onClick: () => selectDagNode(dagRunId, node.nodeId),
312
+ cells: [
313
+ node.nodeId,
314
+ node.rank ?? "—",
315
+ node.executor ?? "—",
316
+ node.model ?? "—",
317
+ node.status ? badge(node.status) : "—",
318
+ formatMs(node.durationMs),
319
+ node.errorPreview ?? node.label ?? "—",
320
+ ],
321
+ };
322
+ });
323
+ const nodeTable = buildTable(
324
+ ["节点 ID", "依赖层", "执行方式", "模型", "状态", "耗时", "备注"],
325
+ rows,
326
+ );
327
+ nodeTable.classList.add("dag-node-table");
328
+ for (const row of nodeTable.tBodies[0]?.rows ?? []) {
329
+ for (const columnIndex of [0, 3, 6]) {
330
+ const cell = row.cells[columnIndex];
331
+ if (cell?.textContent) cell.title = cell.textContent;
332
+ }
333
+ }
334
+ nodesEl.appendChild(nodeTable);
335
+ }
336
+ nodesEl.scrollLeft = nodesScrollLeft;
337
+
338
+ if (uiState.dagInspectorOpen && uiState.selectedDagNodeId) {
339
+ await mergeSessionEvents(dagRunId, uiState.selectedDagNodeId);
340
+ }
341
+ renderDagInspector(
342
+ dagRunId,
343
+ nodes.find((node) => node.nodeId === uiState.selectedDagNodeId),
344
+ initial,
345
+ );
346
+ return isDagRunActive(dag);
347
+ }
348
+
349
+ export function startDagPolling(dagRunId, initial = true) {
350
+ if (uiState.dagPollTimer) clearTimeout(uiState.dagPollTimer);
351
+ const generation = ++uiState.pollingGeneration;
352
+ const poll = async (isInitial) => {
353
+ const active = await renderDagDetail(dagRunId, isInitial);
354
+ if (generation !== uiState.pollingGeneration || !active) return;
355
+ const delay = detailPollDelay("running", isPageVisible());
356
+ uiState.dagPollTimer = setTimeout(() => {
357
+ uiState.dagPollTimer = null;
358
+ void poll(false);
359
+ }, delay);
360
+ };
361
+ void poll(initial);
362
+ }