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