@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,281 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { isSafeObservabilityIdentifier } from "../observability/event-store.js";
5
+ const DAG_RUN_LIFECYCLE_DIRS = ["active", "completed", "paused"];
6
+ /**
7
+ * Known knowledge-base connector tool names.
8
+ */
9
+ const KB_CONNECTOR_TOOLS = new Set([
10
+ "knowledge-base-query",
11
+ "kb_query",
12
+ "kb-search",
13
+ "knowledge_base_search",
14
+ "rag_search",
15
+ "vector_search",
16
+ "mcp__knowledge",
17
+ "mcp__kb",
18
+ ]);
19
+ /**
20
+ * Pattern for detecting spec-related files:
21
+ * - openSpec/** files
22
+ * - *.spec.md / *.spec.ts / *.spec.tsx
23
+ * - project-specs/**
24
+ * - design-spec.md, code-standards.md, review-checklist.md, etc.
25
+ * - SKILL.md files
26
+ * - docs/** specification files
27
+ */
28
+ const SPEC_FILE_PATTERNS = [
29
+ /openspec\//i,
30
+ /\/openSpec\//i,
31
+ /\/project-specs\//i,
32
+ /\/spec\//i,
33
+ /\.spec\.(md|tsx?|jsx?)$/i,
34
+ /\bdesign-spec\b/i,
35
+ /\bcode-standards\b/i,
36
+ /\breview-checklist\b/i,
37
+ /\bverification-checklist\b/i,
38
+ /\bnode-contracts\b/i,
39
+ /\breview-findings\b/i,
40
+ /\bSKILL\.md$/i,
41
+ /\bskills\//i,
42
+ /\bdocs\/design\//i,
43
+ /\bdocs\/templates\//i,
44
+ ];
45
+ function isSpecFilePath(filePath) {
46
+ return SPEC_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
47
+ }
48
+ function isKnowledgeBaseTool(toolName) {
49
+ return KB_CONNECTOR_TOOLS.has(toolName);
50
+ }
51
+ function resolveSessionEventsPath(repoRoot, dagRunId, nodeId) {
52
+ if (!isSafeObservabilityIdentifier(dagRunId) || !isSafeObservabilityIdentifier(nodeId)) {
53
+ return null;
54
+ }
55
+ const dagRunsRoot = path.resolve(repoRoot, ".harness", "dag-runs");
56
+ const candidates = [];
57
+ for (const lifecycle of DAG_RUN_LIFECYCLE_DIRS) {
58
+ candidates.push(path.join(dagRunsRoot, lifecycle, dagRunId, nodeId, "session-events.jsonl"));
59
+ }
60
+ for (const candidate of candidates) {
61
+ const resolved = path.resolve(candidate);
62
+ if (!isPathInside(dagRunsRoot, resolved))
63
+ continue;
64
+ if (!isExpectedSessionEventsRelativePath(dagRunsRoot, resolved, dagRunId, nodeId)) {
65
+ continue;
66
+ }
67
+ if (existsSync(resolved))
68
+ return resolved;
69
+ }
70
+ return null;
71
+ }
72
+ function isExpectedSessionEventsRelativePath(dagRunsRoot, resolvedPath, dagRunId, nodeId) {
73
+ const relative = path.relative(dagRunsRoot, resolvedPath);
74
+ const parts = relative.split(path.sep).filter(Boolean);
75
+ return (parts.length === 4 &&
76
+ DAG_RUN_LIFECYCLE_DIRS.includes(parts[0]) &&
77
+ parts[1] === dagRunId &&
78
+ parts[2] === nodeId &&
79
+ parts[3] === "session-events.jsonl");
80
+ }
81
+ function isPathInside(root, target) {
82
+ const normalizedRoot = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
83
+ const normalizedTarget = path.normalize(target);
84
+ return normalizedTarget === root || normalizedTarget.startsWith(normalizedRoot);
85
+ }
86
+ function resolveToolName(event) {
87
+ return (event.toolName ?? "").toLowerCase();
88
+ }
89
+ function eventTimestamp(event) {
90
+ return event.timestamp ?? event.at;
91
+ }
92
+ /**
93
+ * Parse a tool input path to a repo-relative path.
94
+ * Handles both absolute paths and paths that are already repo-relative.
95
+ */
96
+ function toRepoRelative(repoRoot, filePath) {
97
+ if (!filePath)
98
+ return null;
99
+ try {
100
+ const resolved = path.resolve(repoRoot, filePath);
101
+ const rel = path.relative(repoRoot, resolved);
102
+ if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) {
103
+ return null;
104
+ }
105
+ return rel.replaceAll(path.sep, "/");
106
+ }
107
+ catch {
108
+ return null;
109
+ }
110
+ }
111
+ async function readSessionEvents(filePath) {
112
+ try {
113
+ const raw = await readFile(filePath, "utf-8");
114
+ return raw
115
+ .split("\n")
116
+ .map((line) => line.trim())
117
+ .filter(Boolean)
118
+ .map((line) => {
119
+ try {
120
+ return JSON.parse(line);
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ })
126
+ .filter((event) => event !== null);
127
+ }
128
+ catch {
129
+ return [];
130
+ }
131
+ }
132
+ export async function extractSpecEvidence(repoRoot, dagRunId, nodeId) {
133
+ if (!isSafeObservabilityIdentifier(dagRunId) || !isSafeObservabilityIdentifier(nodeId)) {
134
+ return null;
135
+ }
136
+ const eventsPath = resolveSessionEventsPath(repoRoot, dagRunId, nodeId);
137
+ if (!eventsPath) {
138
+ return null;
139
+ }
140
+ const events = await readSessionEvents(eventsPath);
141
+ // Track successful read calls with their paths (dedup by path)
142
+ const readPaths = new Map();
143
+ const searches = [];
144
+ const kbQueries = [];
145
+ // Track tool_execution_start events by toolCallId for pairing.
146
+ // Only tool_execution_end events with a matching start are counted as reads.
147
+ const startMap = new Map();
148
+ // Scan events for tool calls
149
+ for (const event of events) {
150
+ const type = event.type;
151
+ const toolName = resolveToolName(event);
152
+ const toolCallId = event.toolCallId;
153
+ const toolInput = event.toolInput ?? event.input ?? {};
154
+ const ts = eventTimestamp(event);
155
+ // Collect tool_execution_start events by toolCallId
156
+ if (type === "tool_execution_start" && toolCallId) {
157
+ startMap.set(toolCallId, { toolName, input: toolInput });
158
+ continue;
159
+ }
160
+ // Process tool_execution_end events
161
+ if (type === "tool_execution_end") {
162
+ // Successful read calls: must have matching start event and no error
163
+ if (toolName === "read") {
164
+ const isErrored = event.isError === true ||
165
+ event.toolResult?.error != null ||
166
+ event.toolResult?.ok === false;
167
+ if (isErrored)
168
+ continue;
169
+ let filePath;
170
+ // Only paired read calls qualify: must have toolCallId, matching start
171
+ // with same toolName, and start must be a "read" tool.
172
+ if (toolCallId) {
173
+ const start = startMap.get(toolCallId);
174
+ if (start && start.toolName === "read") {
175
+ const startInput = start.input ?? {};
176
+ filePath = typeof startInput.path === "string" ? startInput.path : undefined;
177
+ startMap.delete(toolCallId);
178
+ }
179
+ // No fallback: unmatched end, mismatched toolName, or missing start → skip
180
+ }
181
+ // No legacy path: toolCallId-less read ends never qualify as spec reads
182
+ if (filePath) {
183
+ const relPath = toRepoRelative(repoRoot, filePath);
184
+ if (relPath && isSpecFilePath(relPath) && !readPaths.has(relPath)) {
185
+ readPaths.set(relPath, ts ?? "");
186
+ }
187
+ }
188
+ }
189
+ // Search/scan tool calls (grep, find, ls, glob)
190
+ if (["grep", "find", "ls", "glob"].includes(toolName)) {
191
+ const query = typeof toolInput.pattern === "string"
192
+ ? toolInput.pattern
193
+ : typeof toolInput.query === "string"
194
+ ? toolInput.query
195
+ : typeof toolInput.path === "string"
196
+ ? toolInput.path
197
+ : toolName;
198
+ searches.push({
199
+ tool: toolName,
200
+ query,
201
+ timestamp: ts,
202
+ });
203
+ }
204
+ // Knowledge base connector calls
205
+ if (isKnowledgeBaseTool(toolName)) {
206
+ kbQueries.push({
207
+ connector: toolName,
208
+ timestamp: ts,
209
+ });
210
+ }
211
+ }
212
+ }
213
+ // Determine status
214
+ let status;
215
+ const specReads = Array.from(readPaths.entries()).map(([p, t]) => ({
216
+ path: p,
217
+ timestamp: t || undefined,
218
+ }));
219
+ const hasInjection = true; // DAG nodes always have skills/resolved instructions injected
220
+ if (specReads.length > 0) {
221
+ status = "spec-read";
222
+ }
223
+ else if (kbQueries.length > 0) {
224
+ status = "kb-queried";
225
+ }
226
+ else if (searches.length > 0) {
227
+ status = "search-only";
228
+ }
229
+ else if (hasInjection) {
230
+ status = "spec-injected";
231
+ }
232
+ else {
233
+ status = "no-evidence";
234
+ }
235
+ // Build summary
236
+ const summaryLines = [];
237
+ if (status === "spec-read") {
238
+ summaryLines.push(`已读取 ${specReads.length} 个规范文件:${specReads.map((r) => r.path).join("、")}`);
239
+ }
240
+ if (searches.length > 0) {
241
+ summaryLines.push(`已执行 ${searches.length} 次检索操作。`);
242
+ }
243
+ if (kbQueries.length > 0) {
244
+ summaryLines.push(`已观察到 ${kbQueries.length} 次知识库查询。`);
245
+ }
246
+ if (status === "spec-injected") {
247
+ summaryLines.push("规范 skill 已注入但未观察到规范文件读取或知识库查询。");
248
+ }
249
+ if (status === "no-evidence") {
250
+ summaryLines.push("未观察到任何规范证据:无 skill 注入、无文件读取、无检索操作。");
251
+ }
252
+ return {
253
+ dagRunId,
254
+ nodeId,
255
+ status,
256
+ skillInjection: {
257
+ skills: [],
258
+ references: [],
259
+ },
260
+ specReads,
261
+ specSearches: searches,
262
+ knowledgeBaseQueries: kbQueries,
263
+ summary: summaryLines.join(" "),
264
+ };
265
+ }
266
+ export async function extractSpecEvidenceWithSkills(repoRoot, dagRunId, nodeId, skillInjection) {
267
+ const base = await extractSpecEvidence(repoRoot, dagRunId, nodeId);
268
+ if (!base)
269
+ return null;
270
+ base.skillInjection = skillInjection;
271
+ // Recompute status considering skill injection knowledge
272
+ if (base.status === "no-evidence" && skillInjection.skills.length > 0) {
273
+ base.status = "spec-injected";
274
+ base.summary = "规范 skill 已注入但未观察到规范文件读取或知识库查询。";
275
+ }
276
+ else if (base.status === "spec-injected" ||
277
+ (base.status === "no-evidence" && skillInjection.skills.length === 0)) {
278
+ base.summary = base.summary || "规范 skill 已注入但未观察到规范文件读取或知识库查询。";
279
+ }
280
+ return base;
281
+ }
@@ -0,0 +1,46 @@
1
+ /** Fetch and artifact helpers (Observe UI R5). */
2
+
3
+ export async function fetchJson(url) {
4
+ try {
5
+ const res = await fetch(url);
6
+ if (!res.ok) return null;
7
+ return await res.json();
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+
13
+ /**
14
+ * Read-only JSON fetch that preserves HTTP status so callers can distinguish
15
+ * 404 (missing) from 409 (ambiguous task identity). Body is parsed best-effort.
16
+ */
17
+ export async function fetchJsonResult(url) {
18
+ try {
19
+ const res = await fetch(url);
20
+ let body = null;
21
+ try {
22
+ body = await res.json();
23
+ } catch {
24
+ body = null;
25
+ }
26
+ return { ok: res.ok, status: res.status, body };
27
+ } catch {
28
+ return { ok: false, status: 0, body: null };
29
+ }
30
+ }
31
+
32
+ export function artifactUrl(artifactPath) {
33
+ return `/api/artifacts?path=${encodeURIComponent(artifactPath)}&tail=200`;
34
+ }
35
+
36
+ export function parseArtifactPreviewResponse(text) {
37
+ const parsed = JSON.parse(text);
38
+ if (!parsed || typeof parsed.content !== "string") {
39
+ throw new Error("Artifact preview response is invalid");
40
+ }
41
+ return {
42
+ content: parsed.content,
43
+ truncated: parsed.truncated === true,
44
+ };
45
+ }
46
+