@tea-agent/loop-agent 0.5.0 → 0.7.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 (136) hide show
  1. package/AGENTS.md +142 -142
  2. package/CHANGELOG.md +132 -98
  3. package/README.md +195 -195
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/application/dag/args.js +9 -1
  7. package/dist/application/dag/run-dag.js +16 -2
  8. package/dist/cli/command-definitions.js +22 -4
  9. package/dist/cli/help.js +3 -2
  10. package/dist/cli/program.js +7 -5
  11. package/dist/commands/import-prd.js +76 -0
  12. package/dist/commands/init.js +467 -457
  13. package/dist/commands/instructions.js +90 -58
  14. package/dist/commands/loop-benchmark.js +11 -11
  15. package/dist/commands/pi-reuse-benchmark.js +16 -16
  16. package/dist/executors/cursor-executor.js +1 -1
  17. package/dist/executors/dag-pi-executor.js +1 -0
  18. package/dist/executors/pi-sdk-executor.js +63 -1
  19. package/dist/shared/preview.js +39 -0
  20. package/dist/task/config-types.js +3 -0
  21. package/dist/task/runtime.js +27 -27
  22. package/dist/task/source-references.js +221 -0
  23. package/dist/worker/cli.js +62 -1
  24. package/dist/worker/loop-agent/loop-agent-client.js +97 -5
  25. package/dist/worker/materialize/harness-task-materializer.js +166 -5
  26. package/dist/worker/observability/event-store.js +82 -0
  27. package/dist/worker/observability/events.js +79 -0
  28. package/dist/worker/observability/progress-composite.js +33 -0
  29. package/dist/worker/observability/read-model.js +1013 -0
  30. package/dist/worker/observability/snapshot-store.js +43 -0
  31. package/dist/worker/observability/types.js +1 -0
  32. package/dist/worker/observe/paths.js +64 -0
  33. package/dist/worker/observe/routes.js +423 -0
  34. package/dist/worker/observe/server.js +61 -0
  35. package/dist/worker/observe/static/app.js +1419 -0
  36. package/dist/worker/observe/static/index.html +63 -0
  37. package/dist/worker/observe/static/styles.css +613 -0
  38. package/dist/worker/pool/failure-routing.js +41 -6
  39. package/dist/worker/pool/run-store.js +50 -0
  40. package/dist/worker/progress-reporter.js +0 -18
  41. package/dist/worker/run-task/run-task.js +327 -92
  42. package/dist/worker/runner/run-ready.js +112 -4
  43. package/dist/worker/task-spec/schema.js +2 -1
  44. package/dist/workflows/dag/canvas-observer.js +275 -275
  45. package/dist/workflows/dag/event-observer.js +132 -0
  46. package/dist/workflows/dag/init-hybrid.js +182 -21
  47. package/dist/workflows/dag/observer-compose.js +52 -0
  48. package/docs/README.md +75 -72
  49. package/docs/agent-dag-recovery-playbook.md +184 -184
  50. package/docs/agent-dag-runner.md +42 -42
  51. package/docs/architecture/runtime-boundaries.md +162 -147
  52. package/docs/cursor-executor-usage.md +25 -25
  53. package/docs/decisions/README.md +3 -3
  54. package/docs/design/README.md +49 -36
  55. package/docs/development-principles.md +73 -73
  56. package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -1749
  57. package/docs/exec-plans/README.md +6 -6
  58. package/docs/exec-plans/active/README.md +12 -7
  59. package/docs/exec-plans/completed/README.md +32 -19
  60. package/docs/feature-workflow.md +186 -186
  61. package/docs/harness-methodology-debugging.md +153 -153
  62. package/docs/harness-methodology-tdd.md +130 -130
  63. package/docs/harness-methodology-verification.md +27 -27
  64. package/docs/init-surface.manifest.json +208 -199
  65. package/docs/loop-agent-harness.md +55 -42
  66. package/docs/production-readiness.md +96 -96
  67. package/docs/progress/README.md +3 -3
  68. package/docs/reports/README.md +9 -5
  69. package/docs/skills/README.md +6 -6
  70. package/docs/skills/vetted-skill-registry.md +26 -26
  71. package/docs/templates/adr.md +60 -60
  72. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  73. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  74. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  75. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  76. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  77. package/docs/templates/agent-dag-report.schema.json +454 -454
  78. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  79. package/docs/templates/agent-dag.base.json +195 -195
  80. package/docs/templates/agent-dag.final-verification.json +190 -190
  81. package/docs/templates/agent-dag.schema.json +316 -316
  82. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  83. package/docs/templates/exec-plan.md +64 -64
  84. package/docs/templates/feature-spec.md +53 -53
  85. package/docs/templates/hybrid-dag.json +193 -193
  86. package/docs/templates/init-evolution-review.md +33 -33
  87. package/docs/templates/interactive-ui-round2-experiment.md +66 -0
  88. package/docs/templates/production-readiness-checklist.md +57 -57
  89. package/docs/templates/progress-log.md +17 -17
  90. package/docs/templates/project-start-checklist.md +9 -9
  91. package/docs/templates/qa-report.md +48 -48
  92. package/docs/templates/sprint-contract.md +29 -29
  93. package/docs/templates/worker-dogfood-evidence.md +52 -0
  94. package/docs/templates/worker-dogfood-setup.md +48 -0
  95. package/docs/verification-matrix.md +41 -41
  96. package/examples/decision-gate-agent-dag.json +123 -123
  97. package/examples/example-dag.json +51 -51
  98. package/examples/hybrid-loop-agent-dag.json +194 -194
  99. package/harness.json +70 -69
  100. package/package.json +66 -66
  101. package/skills/ai-engineering-context/SKILL.md +48 -48
  102. package/skills/code-review-core/SKILL.md +20 -20
  103. package/skills/codebase-scout/SKILL.md +19 -19
  104. package/skills/init-capability-evolution/SKILL.md +69 -69
  105. package/skills/loop-agent/SKILL.md +149 -147
  106. package/skills/loop-agent/references/README.md +67 -67
  107. package/skills/loop-agent/references/command-reference.md +412 -403
  108. package/skills/loop-agent/references/harness-policy.md +263 -259
  109. package/skills/loop-agent/references/hybrid-dag.md +216 -216
  110. package/skills/loop-agent/references/learned/README.md +21 -21
  111. package/skills/loop-agent/references/long-running-loop.md +59 -59
  112. package/skills/loop-agent/references/model-routing.md +36 -36
  113. package/skills/loop-agent/references/multi-worktree.md +54 -54
  114. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  115. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  116. package/skills/loop-agent/references/pi-prompt.md +23 -23
  117. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +81 -81
  118. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  119. package/skills/loop-agent/references/task-workflow.md +89 -84
  120. package/skills/loop-agent/references/verification-and-failure-handling.md +128 -128
  121. package/skills/requesting-code-review/SKILL.md +101 -101
  122. package/skills/requesting-code-review/code-reviewer.md +168 -168
  123. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  124. package/skills/systematic-debugging/SKILL.md +296 -296
  125. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  126. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  127. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  128. package/skills/systematic-debugging/find-polluter.sh +63 -63
  129. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  130. package/skills/systematic-debugging/test-academic.md +14 -14
  131. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  132. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  133. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  134. package/skills/test-driven-development/SKILL.md +20 -20
  135. package/skills/verification-before-completion/SKILL.md +154 -154
  136. package/skills/webapp-testing/SKILL.md +19 -19
@@ -1,4 +1,5 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
1
+ import { createHash } from "node:crypto";
2
+ import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import YAML from "yaml";
4
5
  import { writeTaskConfig, writeTaskArtifactFile } from "../../infrastructure/harness/task-store.js";
@@ -39,6 +40,7 @@ export async function materializeTaskSpec(options) {
39
40
  forbiddenPaths: options.taskSpec.constraints.forbidden_paths,
40
41
  hardConstraints: options.taskSpec.constraints.hard_constraints,
41
42
  complexity: mapRiskLevelToComplexity(options.taskSpec.risk_level),
43
+ capabilities: options.taskSpec.capabilities ?? [],
42
44
  verifyMode: options.taskSpec.verify.mode,
43
45
  verifyPreset: options.taskSpec.verify.preset,
44
46
  verifyQuota: options.taskSpec.verify.quota,
@@ -51,7 +53,25 @@ export async function materializeTaskSpec(options) {
51
53
  taskYamlPath: path.join(paths.sourceDir, "task.yaml"),
52
54
  };
53
55
  await mkdir(paths.sourceDir, { recursive: true });
54
- await writeFile(sourcePaths.requirementPath, renderRequirementMarkdown(options.taskSpec), "utf-8");
56
+ const referencePaths = await materializeSourceDocs({
57
+ taskSpec: options.taskSpec,
58
+ taskSpecPath: options.taskSpecPath,
59
+ sourceDir: paths.sourceDir,
60
+ });
61
+ const sourceDocTrace = await buildSourceDocTrace({
62
+ taskSpec: options.taskSpec,
63
+ taskSpecPath: options.taskSpecPath,
64
+ referencePaths,
65
+ });
66
+ const acceptanceSummaries = await buildAcceptanceSummaries({
67
+ taskSpec: options.taskSpec,
68
+ taskSpecPath: options.taskSpecPath,
69
+ referencePaths,
70
+ });
71
+ await writeFile(sourcePaths.requirementPath, renderRequirementMarkdown(options.taskSpec, {
72
+ sourceDocTrace,
73
+ acceptanceSummaries,
74
+ }), "utf-8");
55
75
  await writeFile(sourcePaths.constraintsPath, renderConstraintsMarkdown(options.taskSpec), "utf-8");
56
76
  await writeFile(sourcePaths.taskYamlPath, YAML.stringify(options.taskSpec), "utf-8");
57
77
  const profileMapping = resolveLoopAgentProfile(options.taskSpec);
@@ -62,11 +82,31 @@ export async function materializeTaskSpec(options) {
62
82
  featureId: options.taskSpec.feature_id,
63
83
  loopAgentProfile: profileMapping.loopAgentProfile,
64
84
  taskConfigPath: paths.taskConfigPath,
65
- source: sourcePaths,
85
+ source: {
86
+ ...sourcePaths,
87
+ // Reference copies make the upstream requirement/design/test cases concrete.
88
+ referencePaths,
89
+ },
66
90
  };
67
91
  await writeTaskArtifactFile(options.repoRoot, harnessTaskId, "materialize-manifest.json", `${JSON.stringify(manifest, null, 2)}\n`);
68
92
  return manifest;
69
93
  }
94
+ async function materializeSourceDocs(input) {
95
+ const referenceDir = path.join(input.sourceDir, "references");
96
+ const taskSpecDir = path.dirname(path.resolve(input.taskSpecPath));
97
+ const referencePaths = {};
98
+ await mkdir(referenceDir, { recursive: true });
99
+ for (const [key, relativePath] of Object.entries(input.taskSpec.source_docs)) {
100
+ if (!relativePath)
101
+ continue;
102
+ const sourcePath = path.resolve(taskSpecDir, relativePath);
103
+ const extension = path.extname(sourcePath) || ".md";
104
+ const targetPath = path.join(referenceDir, `${key}${extension}`);
105
+ await copyFile(sourcePath, targetPath);
106
+ referencePaths[key] = targetPath;
107
+ }
108
+ return referencePaths;
109
+ }
70
110
  export function buildHarnessTaskId(taskSpec, now) {
71
111
  const date = now.toISOString().slice(0, 10);
72
112
  const slug = slugify(`${taskSpec.id}-${taskSpec.title}`);
@@ -81,16 +121,24 @@ function slugify(value) {
81
121
  .replace(/^-+|-+$/g, "")
82
122
  .replace(/-{2,}/g, "-");
83
123
  }
84
- function renderRequirementMarkdown(taskSpec) {
124
+ /** Exported for unit tests: derived contract must point back to immutable references. */
125
+ export function renderRequirementMarkdown(taskSpec, options = {}) {
85
126
  const lines = [
86
127
  `# ${taskSpec.title}`,
87
128
  "",
129
+ "> Derived execution contract from TaskSpec.",
130
+ "> Authoritative sources: `source/references/*` (immutable copies of TaskSpec source_docs).",
131
+ "> On conflict, prefer `source/references/*` over this file. Do not invent missing acceptance text.",
132
+ "",
88
133
  `TaskSpec business id: ${taskSpec.id}`,
89
134
  `Feature id: ${taskSpec.feature_id}`,
90
135
  `Task type: ${taskSpec.type}`,
91
136
  `Risk level: ${taskSpec.risk_level}`,
92
137
  "",
93
138
  ];
139
+ if ((taskSpec.capabilities?.length ?? 0) > 0) {
140
+ lines.push("## Capabilities", "", ...bulletLines(taskSpec.capabilities ?? []), "");
141
+ }
94
142
  if (taskSpec.description) {
95
143
  lines.push("## Description", "", taskSpec.description, "");
96
144
  }
@@ -104,10 +152,123 @@ function renderRequirementMarkdown(taskSpec) {
104
152
  if (taskSpec.scope.open_questions.length > 0) {
105
153
  lines.push("## Open Questions", "", ...bulletLines(taskSpec.scope.open_questions), "");
106
154
  }
107
- lines.push("## Acceptance References", "", ...bulletLines(taskSpec.acceptance_refs), "");
155
+ const acceptanceLines = options.acceptanceSummaries && options.acceptanceSummaries.length > 0
156
+ ? options.acceptanceSummaries.map((entry) => `- ${entry.id}: ${entry.summary}`)
157
+ : bulletLines(taskSpec.acceptance_refs);
158
+ lines.push("## Acceptance References", "", ...acceptanceLines, "");
159
+ if (options.sourceDocTrace && options.sourceDocTrace.length > 0) {
160
+ lines.push("## Source Docs / Traceability", "", ...options.sourceDocTrace.map((entry) => `- ${entry.key}: ${entry.materializedPath} (from ${entry.sourcePath}; sha256=${entry.sha256.slice(0, 12)}…)`), "");
161
+ }
162
+ else {
163
+ lines.push("## Source Docs / Traceability", "", ...bulletLines(Object.entries(taskSpec.source_docs).map(([key, value]) => `${key}: source/references/${key}.* ← ${value}`)), "");
164
+ }
108
165
  lines.push("## Required Outputs", "", ...bulletLines(taskSpec.outputs.required), "");
109
166
  return `${lines.join("\n").trimEnd()}\n`;
110
167
  }
168
+ async function buildSourceDocTrace(input) {
169
+ const entries = [];
170
+ for (const [key, relativePath] of Object.entries(input.taskSpec.source_docs)) {
171
+ if (!relativePath)
172
+ continue;
173
+ const materializedAbsolute = input.referencePaths[key];
174
+ if (!materializedAbsolute)
175
+ continue;
176
+ const content = await readFile(materializedAbsolute);
177
+ const sha256 = createHash("sha256").update(content).digest("hex");
178
+ const extension = path.extname(materializedAbsolute) || ".md";
179
+ entries.push({
180
+ key,
181
+ sourcePath: relativePath.split(path.sep).join("/"),
182
+ materializedPath: `references/${key}${extension}`,
183
+ sha256,
184
+ });
185
+ }
186
+ entries.sort((left, right) => left.key.localeCompare(right.key));
187
+ return entries;
188
+ }
189
+ async function buildAcceptanceSummaries(input) {
190
+ const acceptanceAbsolute = input.referencePaths.acceptance ??
191
+ path.resolve(path.dirname(path.resolve(input.taskSpecPath)), input.taskSpec.source_docs.acceptance);
192
+ let raw = "";
193
+ try {
194
+ raw = await readFile(acceptanceAbsolute, "utf-8");
195
+ }
196
+ catch {
197
+ return input.taskSpec.acceptance_refs.map((id) => ({
198
+ id,
199
+ summary: "(acceptance source unreadable; see source/references/acceptance.*)",
200
+ }));
201
+ }
202
+ const byId = indexAcceptanceItems(raw);
203
+ return input.taskSpec.acceptance_refs.map((id) => ({
204
+ id,
205
+ summary: byId.get(id) ??
206
+ "(no summary found in acceptance source; re-read source/references/acceptance.*)",
207
+ }));
208
+ }
209
+ /** Pull short human summaries for acceptance IDs from YAML or Markdown sources. */
210
+ export function indexAcceptanceItems(raw) {
211
+ const map = new Map();
212
+ const trimmed = raw.trim();
213
+ if (!trimmed)
214
+ return map;
215
+ // YAML AcceptanceSpec: { acceptance: [ { id, title, then, ... } ] }
216
+ try {
217
+ const parsed = YAML.parse(trimmed);
218
+ const items = extractAcceptanceArray(parsed);
219
+ for (const item of items) {
220
+ if (!item || typeof item !== "object")
221
+ continue;
222
+ const record = item;
223
+ const id = typeof record.id === "string" ? record.id : undefined;
224
+ if (!id)
225
+ continue;
226
+ const title = typeof record.title === "string" ? record.title.trim() : "";
227
+ const then = typeof record.then === "string" ? record.then.trim() : "";
228
+ const given = typeof record.given === "string" ? record.given.trim() : "";
229
+ const when = typeof record.when === "string" ? record.when.trim() : "";
230
+ const parts = [
231
+ title,
232
+ then ? `then: ${then}` : "",
233
+ !then && given ? `given: ${given}` : "",
234
+ !then && when ? `when: ${when}` : "",
235
+ ].filter(Boolean);
236
+ map.set(id, compactSummary(parts.join(" — ") || id));
237
+ }
238
+ if (map.size > 0)
239
+ return map;
240
+ }
241
+ catch {
242
+ // fall through to markdown heuristics
243
+ }
244
+ // Markdown / free text: lines like "AC-BE-001: ..." or "- AC-BE-001 ..."
245
+ for (const line of trimmed.split("\n")) {
246
+ const match = line.match(/(?:^|\s)((?:AC|REQ)[-A-Z0-9]+)\s*[::\-–—]\s*(.+)$/i);
247
+ if (!match)
248
+ continue;
249
+ const id = match[1];
250
+ const summary = compactSummary(match[2]);
251
+ if (!map.has(id))
252
+ map.set(id, summary);
253
+ }
254
+ return map;
255
+ }
256
+ function extractAcceptanceArray(parsed) {
257
+ if (!parsed || typeof parsed !== "object")
258
+ return [];
259
+ const root = parsed;
260
+ if (Array.isArray(root.acceptance))
261
+ return root.acceptance;
262
+ if (Array.isArray(parsed))
263
+ return parsed;
264
+ return [];
265
+ }
266
+ function compactSummary(value, maxChars = 180) {
267
+ const normalized = value.replace(/\s+/g, " ").trim();
268
+ if (normalized.length <= maxChars)
269
+ return normalized;
270
+ return `${normalized.slice(0, maxChars - 1)}…`;
271
+ }
111
272
  function renderConstraintsMarkdown(taskSpec) {
112
273
  const lines = [
113
274
  `# Execution Constraints for ${taskSpec.id}`,
@@ -0,0 +1,82 @@
1
+ import { appendFile, mkdir } from "node:fs/promises";
2
+ import { appendFileSync, mkdirSync } from "node:fs";
3
+ import path from "node:path";
4
+ function writeDiagnostic(context, error) {
5
+ const message = error instanceof Error ? error.message : String(error);
6
+ process.stderr.write(`[worker-event-store] ${context}: ${message}\n`);
7
+ }
8
+ function serializeEvent(event) {
9
+ return `${JSON.stringify(event)}\n`;
10
+ }
11
+ /** Identifiers used as event-store path segments must not alter the route. */
12
+ export function isSafeObservabilityIdentifier(value) {
13
+ return value !== "." && value !== ".." && !/[\\/\0]/.test(value);
14
+ }
15
+ export function createWorkerEventStore(jsonlPath) {
16
+ return {
17
+ async append(event) {
18
+ try {
19
+ await mkdir(path.dirname(jsonlPath), { recursive: true });
20
+ await appendFile(jsonlPath, serializeEvent(event), "utf-8");
21
+ }
22
+ catch (error) {
23
+ writeDiagnostic(`append failed (${jsonlPath})`, error);
24
+ }
25
+ },
26
+ async appendMany(events) {
27
+ for (const event of events) {
28
+ await this.append(event);
29
+ }
30
+ },
31
+ appendSyncSafe(event) {
32
+ try {
33
+ mkdirSync(path.dirname(jsonlPath), { recursive: true });
34
+ appendFileSync(jsonlPath, serializeEvent(event), "utf-8");
35
+ }
36
+ catch (error) {
37
+ writeDiagnostic(`appendSyncSafe failed (${jsonlPath})`, error);
38
+ }
39
+ },
40
+ path() {
41
+ return jsonlPath;
42
+ },
43
+ };
44
+ }
45
+ export function createRoutedWorkerEventStore(repoRoot) {
46
+ const observabilityRoot = path.join(path.resolve(repoRoot), ".task-pool", "observability");
47
+ const globalStore = createWorkerEventStore(path.join(observabilityRoot, "events.jsonl"));
48
+ let queue = Promise.resolve();
49
+ function storesFor(event) {
50
+ const stores = [globalStore];
51
+ if (event.batchRunId && isSafeObservabilityIdentifier(event.batchRunId)) {
52
+ stores.push(createWorkerEventStore(path.join(observabilityRoot, "batches", event.batchRunId, "events.jsonl")));
53
+ }
54
+ if (event.workerRunId && isSafeObservabilityIdentifier(event.workerRunId)) {
55
+ stores.push(createWorkerEventStore(path.join(observabilityRoot, "runs", event.workerRunId, "events.jsonl")));
56
+ }
57
+ return stores;
58
+ }
59
+ const store = {
60
+ append(event) {
61
+ const write = queue.then(async () => {
62
+ await Promise.all(storesFor(event).map((target) => target.append(event)));
63
+ });
64
+ queue = write.catch(() => { });
65
+ return write;
66
+ },
67
+ async appendMany(events) {
68
+ for (const event of events) {
69
+ await store.append(event);
70
+ }
71
+ },
72
+ appendSyncSafe(event) {
73
+ for (const target of storesFor(event)) {
74
+ target.appendSyncSafe(event);
75
+ }
76
+ },
77
+ path() {
78
+ return globalStore.path();
79
+ },
80
+ };
81
+ return store;
82
+ }
@@ -0,0 +1,79 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
3
+ const WORKER_EVENT_SOURCES = new Set([
4
+ "worker",
5
+ "loop-agent-command",
6
+ "dag",
7
+ "shell",
8
+ "artifact",
9
+ "observe",
10
+ ]);
11
+ const WORKER_EVENT_TYPES = new Set([
12
+ "batch.started",
13
+ "batch.finished",
14
+ "readyQueue.computed",
15
+ "task.queued",
16
+ "task.started",
17
+ "task.finished",
18
+ "task.reused",
19
+ "step.started",
20
+ "step.heartbeat",
21
+ "step.output",
22
+ "step.finished",
23
+ "command.started",
24
+ "command.output",
25
+ "command.heartbeat",
26
+ "command.finished",
27
+ "dag.run.started",
28
+ "dag.node.started",
29
+ "dag.node.output",
30
+ "dag.node.finished",
31
+ "dag.run.finished",
32
+ "artifact.written",
33
+ "failure.routed",
34
+ "state.updated",
35
+ ]);
36
+ export function createWorkerEvent(input, options) {
37
+ const now = options?.now ?? (() => new Date());
38
+ const randomUuid = options?.randomUuid ?? randomUUID;
39
+ return {
40
+ ...input,
41
+ schemaVersion: 1,
42
+ id: input.id ?? randomUuid(),
43
+ at: input.at ?? now().toISOString(),
44
+ spanId: input.spanId ?? randomUuid(),
45
+ };
46
+ }
47
+ export function parseWorkerEventLine(line) {
48
+ try {
49
+ const parsed = JSON.parse(line);
50
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
51
+ return undefined;
52
+ }
53
+ return isWorkerEvent(parsed) ? parsed : undefined;
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ function isWorkerEvent(value) {
60
+ const event = value;
61
+ return (event.schemaVersion === 1 &&
62
+ typeof event.id === "string" &&
63
+ event.id.length > 0 &&
64
+ typeof event.at === "string" &&
65
+ !Number.isNaN(Date.parse(event.at)) &&
66
+ typeof event.spanId === "string" &&
67
+ event.spanId.length > 0 &&
68
+ typeof event.source === "string" &&
69
+ WORKER_EVENT_SOURCES.has(event.source) &&
70
+ typeof event.type === "string" &&
71
+ WORKER_EVENT_TYPES.has(event.type) &&
72
+ typeof event.label === "string");
73
+ }
74
+ export function truncatePreview(text, maxBytes = 4096) {
75
+ return truncateUtf8Preview(text, maxBytes);
76
+ }
77
+ export function redactForPreview(text) {
78
+ return redactSecrets(text);
79
+ }
@@ -0,0 +1,33 @@
1
+ import { createProgressReporter, } from "../progress-reporter.js";
2
+ import { createWorkerEvent } from "./events.js";
3
+ export function createCompositeProgressReporter(options) {
4
+ const textReporter = createProgressReporter({
5
+ quiet: options.quiet,
6
+ now: options.now,
7
+ isTty: options.isTty,
8
+ sink: options.sink,
9
+ });
10
+ const eventStore = options.eventStore;
11
+ const context = options.context ?? {};
12
+ const now = options.now ?? (() => new Date());
13
+ return {
14
+ ...textReporter,
15
+ async event(input) {
16
+ if (!eventStore)
17
+ return;
18
+ try {
19
+ const event = createWorkerEvent({
20
+ ...input,
21
+ batchRunId: input.batchRunId ?? context.batchRunId,
22
+ workerRunId: input.workerRunId ?? context.workerRunId,
23
+ taskId: input.taskId ?? context.taskId,
24
+ }, { now });
25
+ await eventStore.append(event);
26
+ }
27
+ catch (error) {
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ process.stderr.write(`[progress-composite] event append failed: ${message}\n`);
30
+ }
31
+ },
32
+ };
33
+ }