@tea-agent/loop-agent 0.29.3 → 0.31.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 (34) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/executors/dag-pi-executor.js +18 -6
  3. package/dist/worker/observability/dag-execution-trajectory.js +591 -0
  4. package/dist/worker/observability/read-model.js +258 -31
  5. package/dist/worker/observe/dag-node-execution-output.js +180 -0
  6. package/dist/worker/observe/routes.js +53 -6
  7. package/dist/worker/observe/static/dag-edge-routing.js +368 -0
  8. package/dist/worker/observe/static/dag-history-labels.js +95 -0
  9. package/dist/worker/observe/static/dag-layout.d.ts +12 -7
  10. package/dist/worker/observe/static/dag-layout.js +101 -21
  11. package/dist/worker/observe/static/favicon.svg +37 -0
  12. package/dist/worker/observe/static/format.js +31 -1
  13. package/dist/worker/observe/static/index.html +1 -1
  14. package/dist/worker/observe/static/state.js +102 -0
  15. package/dist/worker/observe/static/styles.css +267 -7
  16. package/dist/worker/observe/static/views/dag-graph.js +414 -154
  17. package/dist/worker/observe/static/views/dag-inspector.js +478 -27
  18. package/dist/worker/observe/static/views/dag-trajectory.js +313 -0
  19. package/dist/worker/observe/static/views/dag.js +20 -3
  20. package/dist/workflows/dag/backend-test-writer-completeness.js +398 -26
  21. package/dist/workflows/dag/dynamic-runtime/map.js +10 -4
  22. package/dist/workflows/dag/init-hybrid.js +228 -229
  23. package/dist/workflows/dag/node-execution.js +13 -2
  24. package/dist/workflows/dag/types.js +21 -7
  25. package/docs/architecture/README.md +4 -0
  26. package/docs/architecture/dag-execution.md +1 -1
  27. package/docs/architecture/worker-and-feature.md +1 -1
  28. package/docs/governance/README.md +3 -0
  29. package/docs/operations/README.md +1 -0
  30. package/docs/templates/backend-test-dag.json +664 -460
  31. package/docs/templates/frontend-test-dag.json +1 -1
  32. package/harness.json +2 -2
  33. package/package.json +3 -2
  34. package/scripts/kb-bootstrap-init-skeleton.sh +1 -1
@@ -0,0 +1,313 @@
1
+ /**
2
+ * DAG 执行轨迹 storyboard(Observe UI R5 / P1)。
3
+ *
4
+ * 将 detail 响应内嵌的有界 executionTrajectory 渲染为自上而下的 lane:
5
+ * 「初始执行」「修复轮次 N-1」「最终收敛」(仅内容 lane)。raw pass=1(scope
6
+ * base 或 convergence-pass)均显示「初始执行」,raw pass>=2 显示「修复轮次
7
+ * N-1」;raw pass 编号仅保留在 occurrence 卡 hover/title 诊断(AC-001/AC-002)。
8
+ * lane 内按真实
9
+ * sequence 左→右;occurrence 卡含节点名/状态/耗时/语义 verdict;多 attempt
10
+ * 显示中文紧凑尝试条;lane 间短箭头加中文原因;不画跨 lane 回环(P2 路由
11
+ * 算法不在 P1 范围)。布局异常降级为纵向事件列表,不空白页。
12
+ */
13
+ import { el } from "../dom.js";
14
+ import { statusLabel, formatMs } from "../format.js";
15
+ import {
16
+ attemptFailureLabel,
17
+ semanticVerdictLabel,
18
+ transitionReasonLabel,
19
+ } from "../dag-history-labels.js";
20
+ import { setDagGraphViewMode } from "../state.js";
21
+ import { selectDagExecution } from "./dag-inspector.js";
22
+
23
+ export const TRAJECTORY_EMPTY_TEXT = "本次运行没有可展开的执行轨迹";
24
+ export const TRAJECTORY_UNAVAILABLE_TEXT = "执行历史暂不可用";
25
+ export const TRAJECTORY_PARTIAL_TEXT = "历史证据不完整";
26
+
27
+ const SUCCESS_STATUSES = new Set([
28
+ "finished",
29
+ "completed",
30
+ "succeeded",
31
+ "done",
32
+ "success",
33
+ ]);
34
+ const FAILURE_STATUSES = new Set([
35
+ "error",
36
+ "failed",
37
+ "partial_failed",
38
+ "partial-failed",
39
+ "interrupted",
40
+ "failure",
41
+ "superseded",
42
+ ]);
43
+ const RUNNING_STATUSES = new Set(["running", "started", "paused", "queued"]);
44
+
45
+ function occurrenceStatusBadge(status) {
46
+ const normalized = String(status ?? "").toLowerCase();
47
+ if (SUCCESS_STATUSES.has(normalized)) {
48
+ return { cls: "succeeded", label: "成功" };
49
+ }
50
+ if (FAILURE_STATUSES.has(normalized)) {
51
+ return { cls: "failed", label: "失败" };
52
+ }
53
+ if (RUNNING_STATUSES.has(normalized)) {
54
+ return { cls: "pending", label: "运行中" };
55
+ }
56
+ return { cls: "pending", label: statusLabel(status) ?? status ?? "未知" };
57
+ }
58
+
59
+ /** Lane grouping key from an occurrence (pre-sorted by the projection). */
60
+ function laneKeyOf(occurrence) {
61
+ if (occurrence.scope === "final") return "final";
62
+ if (occurrence.scope === "base") return "base";
63
+ return `pass-${occurrence.pass ?? 0}`;
64
+ }
65
+
66
+ function laneTitle(laneKey, occurrence) {
67
+ if (laneKey === "base") return "初始执行";
68
+ if (laneKey === "final") return "最终收敛";
69
+ // raw pass=1 的 convergence-pass lane 也显示「初始执行」;raw pass>=2
70
+ // 映射为人类修复轮次 N-1(AC-001/AC-002),主文案不出现 raw 轮次直译。
71
+ const pass = occurrence.pass ?? 0;
72
+ if (pass <= 1) return "初始执行";
73
+ return `修复轮次 ${pass - 1}`;
74
+ }
75
+
76
+ /** 中文紧凑尝试条:首次尝试 / 第 N 次尝试(AC-002,主 UI 不出现裸 A1/A2)。 */
77
+ function attemptStripLabel(occurrence) {
78
+ const attempts = occurrence.attempts;
79
+ if (!Array.isArray(attempts) || attempts.length <= 1) return null;
80
+ return attempts
81
+ .map((attempt) => {
82
+ const name =
83
+ attempt.attempt === 1 ? "首次尝试" : `第 ${attempt.attempt} 次尝试`;
84
+ // 失败 attempt 显示表一中文原因(AC-001),未知/缺失分类
85
+ // fail-closed 为「其他失败原因」;成功 attempt 保持「成功」。
86
+ const outcome =
87
+ attempt.status === "success"
88
+ ? "成功"
89
+ : attemptFailureLabel(attempt.failureCategory);
90
+ return `${name} ${outcome}`;
91
+ })
92
+ .join(" → ");
93
+ }
94
+
95
+ function buildOccurrenceCard(dagRunId, occurrence) {
96
+ const badge = occurrenceStatusBadge(occurrence.status);
97
+ const recovered =
98
+ occurrence.recovered === true ||
99
+ occurrence.supersededBy?.length > 0 ||
100
+ false;
101
+ const card = document.createElement("button");
102
+ card.type = "button";
103
+ const extraClasses = [
104
+ recovered ? "is-recovered" : "",
105
+ occurrence.availability === "partial" ? "is-partial" : "",
106
+ ]
107
+ .filter(Boolean)
108
+ .join(" ");
109
+ card.className = extraClasses
110
+ ? `dag-trajectory-occurrence ${extraClasses}`
111
+ : "dag-trajectory-occurrence";
112
+ // 原始 pass/attempt 编号仅出现在 hover/title 诊断中,不作主标题(AC-003)。
113
+ const diagnostic = [
114
+ occurrence.executionId,
115
+ occurrence.status,
116
+ ...(occurrence.pass ? [`pass ${occurrence.pass}`] : []),
117
+ ...(occurrence.failureCategory
118
+ ? [`failureCategory ${occurrence.failureCategory}`]
119
+ : []),
120
+ ].join("\n");
121
+ card.setAttribute("title", diagnostic);
122
+ // 语义 verdict 主文案与 aria-label 均显示表二中文(AC-002)。
123
+ const verdictLabel = semanticVerdictLabel(occurrence.semanticVerdict);
124
+ card.setAttribute(
125
+ "aria-label",
126
+ `${occurrence.nodeId},${badge.label}${occurrence.semanticVerdict ? `,语义 ${verdictLabel}` : ""}${recovered ? ",历史失败 · 已恢复/已覆盖" : ""}`,
127
+ );
128
+
129
+ const nodeLine = el("span", "dag-trajectory-occurrence-node", occurrence.nodeId);
130
+ card.appendChild(nodeLine);
131
+ const statusBadge = el(
132
+ "span",
133
+ `badge badge-${badge.cls} dag-trajectory-occurrence-status`,
134
+ badge.label,
135
+ );
136
+ card.appendChild(statusBadge);
137
+ if (occurrence.durationMs !== undefined) {
138
+ card.appendChild(
139
+ el(
140
+ "span",
141
+ "dag-trajectory-occurrence-meta",
142
+ formatMs(occurrence.durationMs),
143
+ ),
144
+ );
145
+ }
146
+ if (occurrence.semanticVerdict) {
147
+ card.appendChild(
148
+ el("span", "dag-trajectory-occurrence-verdict", verdictLabel),
149
+ );
150
+ }
151
+ const strip = attemptStripLabel(occurrence);
152
+ if (strip) {
153
+ card.appendChild(el("span", "dag-trajectory-attempt-strip", strip));
154
+ }
155
+ if (recovered) {
156
+ const label =
157
+ occurrence.supersededBy?.length > 0
158
+ ? "历史失败 · 已覆盖"
159
+ : "历史失败 · 已恢复";
160
+ card.appendChild(el("span", "dag-trajectory-recovered", label));
161
+ }
162
+ if (occurrence.availability === "partial") {
163
+ card.appendChild(
164
+ el("span", "dag-trajectory-partial", TRAJECTORY_PARTIAL_TEXT),
165
+ );
166
+ }
167
+ card.addEventListener("click", () => {
168
+ // @pass-<n> 映射为 Inspector 既有 pass-<n> key;@base/@final 默认最新。
169
+ selectDagExecution(
170
+ dagRunId,
171
+ occurrence.nodeId,
172
+ occurrence.pass ? `pass-${occurrence.pass}` : null,
173
+ );
174
+ });
175
+ return card;
176
+ }
177
+
178
+ function buildEmptyState(dagRunId, rerender) {
179
+ const root = el("div", "dag-trajectory-empty");
180
+ root.appendChild(el("p", "dag-trajectory-empty-text", TRAJECTORY_EMPTY_TEXT));
181
+ const back = document.createElement("button");
182
+ back.type = "button";
183
+ back.className = "dag-trajectory-back";
184
+ back.textContent = "返回逻辑拓扑";
185
+ back.addEventListener("click", () => {
186
+ setDagGraphViewMode(dagRunId, "topology");
187
+ rerender();
188
+ });
189
+ root.appendChild(back);
190
+ return root;
191
+ }
192
+
193
+ /**
194
+ * 渲染执行轨迹视图到图区域 viewport。`rerender` 由 dag-graph 注入,用于
195
+ * 空态「返回逻辑拓扑」就地切回拓扑(避免模块环)。
196
+ */
197
+ export function renderTrajectoryStoryboard(dag, dagRunId, viewport, rerender) {
198
+ const trajectory = dag.executionTrajectory;
199
+ const fallback = (occurrences) => {
200
+ const list = el("ul", "dag-trajectory-fallback");
201
+ const items = Array.isArray(occurrences)
202
+ ? occurrences
203
+ : trajectory?.occurrences ?? [];
204
+ if (items.length === 0) {
205
+ list.appendChild(
206
+ el("li", "dag-trajectory-fallback-item", TRAJECTORY_UNAVAILABLE_TEXT),
207
+ );
208
+ } else {
209
+ let rendered = 0;
210
+ for (const occurrence of items) {
211
+ if (!occurrence || typeof occurrence !== "object") continue;
212
+ // 纵向降级列表同样经表三/表二映射拼接,不出现裸英文 token(AC-002)。
213
+ const reason =
214
+ transitionReasonLabel(occurrence.nextReason) ||
215
+ semanticVerdictLabel(occurrence.semanticVerdict) ||
216
+ "";
217
+ list.appendChild(
218
+ el(
219
+ "li",
220
+ "dag-trajectory-fallback-item",
221
+ `${occurrence.nodeId} · ${statusLabel(occurrence.status)}${reason ? ` · ${reason}` : ""}`,
222
+ ),
223
+ );
224
+ rendered += 1;
225
+ }
226
+ if (rendered === 0) {
227
+ list.appendChild(
228
+ el(
229
+ "li",
230
+ "dag-trajectory-fallback-item",
231
+ TRAJECTORY_UNAVAILABLE_TEXT,
232
+ ),
233
+ );
234
+ }
235
+ }
236
+ viewport.appendChild(list);
237
+ };
238
+ try {
239
+ if (!trajectory || !Array.isArray(trajectory.occurrences)) {
240
+ // 无 executionTrajectory 或空轨迹:空态 + 返回逻辑拓扑入口。
241
+ viewport.appendChild(buildEmptyState(dagRunId, rerender));
242
+ return;
243
+ }
244
+ const root = el("div", "dag-trajectory");
245
+ if (trajectory.projectionError === true) {
246
+ root.appendChild(
247
+ el("div", "dag-trajectory-banner is-error", TRAJECTORY_UNAVAILABLE_TEXT),
248
+ );
249
+ }
250
+ if (Array.isArray(trajectory.warnings) && trajectory.warnings.length > 0) {
251
+ root.appendChild(
252
+ el(
253
+ "div",
254
+ "dag-trajectory-banner",
255
+ `${TRAJECTORY_PARTIAL_TEXT}:${trajectory.warnings.join(";")}`,
256
+ ),
257
+ );
258
+ }
259
+ if (trajectory.occurrences.length === 0) {
260
+ root.appendChild(buildEmptyState(dagRunId, rerender));
261
+ viewport.appendChild(root);
262
+ return;
263
+ }
264
+ // 按投影排序连续分组为 lane(仅内容 lane),lane 间插入带中文原因的短箭头。
265
+ const lanes = [];
266
+ let currentLaneKey = null;
267
+ for (const occurrence of trajectory.occurrences) {
268
+ const laneKey = laneKeyOf(occurrence);
269
+ if (laneKey !== currentLaneKey) {
270
+ currentLaneKey = laneKey;
271
+ lanes.push({
272
+ laneKey,
273
+ title: laneTitle(laneKey, occurrence),
274
+ occurrences: [],
275
+ });
276
+ }
277
+ lanes[lanes.length - 1].occurrences.push(occurrence);
278
+ }
279
+ for (const [index, lane] of lanes.entries()) {
280
+ const laneRoot = el("div", "dag-trajectory-lane");
281
+ const head = el("div", "dag-trajectory-lane-head");
282
+ head.appendChild(el("span", "dag-trajectory-lane-title", lane.title));
283
+ head.appendChild(
284
+ el("span", "dag-trajectory-lane-meta", `${lane.occurrences.length} 次执行`),
285
+ );
286
+ laneRoot.appendChild(head);
287
+ const laneCards = el("div", "dag-trajectory-lane-cards");
288
+ for (const occurrence of lane.occurrences) {
289
+ laneCards.appendChild(buildOccurrenceCard(dagRunId, occurrence));
290
+ }
291
+ laneRoot.appendChild(laneCards);
292
+ root.appendChild(laneRoot);
293
+ if (index < lanes.length - 1) {
294
+ const lastOccurrence = lane.occurrences[lane.occurrences.length - 1];
295
+ const reason = transitionReasonLabel(lastOccurrence?.nextReason);
296
+ if (reason) {
297
+ const transition = el("div", "dag-trajectory-transition");
298
+ transition.appendChild(
299
+ el("span", "dag-trajectory-transition-arrow", "↓"),
300
+ );
301
+ transition.appendChild(
302
+ el("span", "dag-trajectory-transition-reason", reason),
303
+ );
304
+ root.appendChild(transition);
305
+ }
306
+ }
307
+ }
308
+ viewport.appendChild(root);
309
+ } catch {
310
+ // 布局/渲染异常降级:纵向事件列表,不空白页(AC-006)。
311
+ fallback(null);
312
+ }
313
+ }
@@ -62,7 +62,7 @@ import {
62
62
  mountViewState,
63
63
  makeSessionEventIdentity,
64
64
  } from "../state.js";
65
- import { uiState, dagGraphViewportStates } from "../state.js";
65
+ import { uiState, dagGraphViewportStates, dagGraphViewModes } from "../state.js";
66
66
  import { sortPoolTasks } from "../format-pool.js";
67
67
  import {
68
68
  dagMetaVisibility,
@@ -108,9 +108,12 @@ export async function renderDagDetail(dagRunId, initial = true) {
108
108
  uiState.sessionEventIdentity = null;
109
109
  uiState.dagInspectorOpen = false;
110
110
  uiState.dagInspectorTab = "output";
111
+ uiState.dagInspectorAttempt = null;
111
112
  uiState.dagGraphViewportState = null;
112
113
  uiState.dagTimelineViewportState = null;
113
114
  }
115
+ // AC-001:同会话内重新进入同一 dagRunId 保持当前图视图模式(不切回逻辑拓扑)。
116
+ uiState.dagGraphViewMode = dagGraphViewModes.get(dagRunId) ?? "topology";
114
117
 
115
118
  const metaEl = document.getElementById("dag-meta");
116
119
  const ranksEl = document.getElementById("dag-ranks");
@@ -167,6 +170,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
167
170
  ) {
168
171
  uiState.selectedDagNodeId = null;
169
172
  uiState.dagInspectorOpen = false;
173
+ uiState.dagInspectorAttempt = null;
170
174
  if (initial) {
171
175
  uiState.sessionEventOffset = 0;
172
176
  uiState.sessionEvents = [];
@@ -325,17 +329,30 @@ export async function renderDagDetail(dagRunId, initial = true) {
325
329
  node.model ?? "—",
326
330
  node.status ? badge(node.status) : "—",
327
331
  formatMs(node.durationMs),
332
+ (typeof node.executionSummary?.historyLabel === "string" &&
333
+ node.executionSummary.historyLabel.trim()) ||
334
+ "—",
328
335
  node.errorPreview ?? node.label ?? "—",
329
336
  ],
330
337
  };
331
338
  });
332
339
  const nodeTable = buildTable(
333
- ["序号", "节点 ID", "依赖层", "执行方式", "模型", "状态", "耗时", "备注"],
340
+ [
341
+ "序号",
342
+ "节点 ID",
343
+ "依赖层",
344
+ "执行方式",
345
+ "模型",
346
+ "状态",
347
+ "耗时",
348
+ "执行历史",
349
+ "备注",
350
+ ],
334
351
  rows,
335
352
  );
336
353
  nodeTable.classList.add("dag-node-table");
337
354
  for (const row of nodeTable.tBodies[0]?.rows ?? []) {
338
- for (const columnIndex of [1, 4, 7]) {
355
+ for (const columnIndex of [1, 4, 8]) {
339
356
  const cell = row.cells[columnIndex];
340
357
  if (cell?.textContent) cell.title = cell.textContent;
341
358
  }