@the-open-engine/zeroshot 6.39.2 → 6.41.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 (73) hide show
  1. package/README.md +18 -0
  2. package/cli/agent-provider-boundary.js +19 -0
  3. package/cli/agent-provider-boundary.ts +79 -0
  4. package/cli/export-stream.js +94 -0
  5. package/cli/export-stream.ts +118 -0
  6. package/cli/index.js +102 -54
  7. package/cli/json-export.js +2 -38
  8. package/cli/json-export.ts +2 -56
  9. package/cli/semantic-canonical.js +60 -0
  10. package/cli/semantic-canonical.ts +59 -0
  11. package/cli/semantic-contract.js +97 -0
  12. package/cli/semantic-contract.ts +153 -0
  13. package/cli/semantic-events.js +53 -0
  14. package/cli/semantic-events.ts +65 -0
  15. package/cli/semantic-evidence.js +63 -0
  16. package/cli/semantic-evidence.ts +65 -0
  17. package/cli/semantic-export.js +186 -0
  18. package/cli/semantic-export.ts +245 -0
  19. package/cli/semantic-json.js +69 -0
  20. package/cli/semantic-json.ts +69 -0
  21. package/cli/semantic-line-scanner.js +56 -0
  22. package/cli/semantic-line-scanner.ts +54 -0
  23. package/cli/semantic-parser.js +78 -0
  24. package/cli/semantic-parser.ts +95 -0
  25. package/cli/semantic-provider-line.js +91 -0
  26. package/cli/semantic-provider-line.ts +106 -0
  27. package/cli/trace-evidence.js +86 -0
  28. package/cli/trace-evidence.ts +115 -0
  29. package/cli/trace-export.js +120 -0
  30. package/cli/trace-export.ts +164 -0
  31. package/cli/trace-output-record.js +15 -0
  32. package/cli/trace-output-record.ts +18 -0
  33. package/cli/trace-output.js +98 -0
  34. package/cli/trace-output.ts +119 -0
  35. package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -1
  36. package/lib/agent-cli-provider/log-prefix.js +8 -6
  37. package/lib/agent-cli-provider/log-prefix.js.map +1 -1
  38. package/lib/cluster/connection-types.d.cts +2 -2
  39. package/lib/cluster/connection-types.d.mts +2 -2
  40. package/lib/cluster/connection-types.d.ts +2 -2
  41. package/lib/cluster/generated/protocol-schema.cjs +1 -1
  42. package/lib/cluster/generated/protocol-schema.mjs +1 -1
  43. package/lib/cluster/generated/protocol.cjs +4 -4
  44. package/lib/cluster/generated/protocol.d.cts +271 -9
  45. package/lib/cluster/generated/protocol.d.mts +271 -9
  46. package/lib/cluster/generated/protocol.d.ts +271 -9
  47. package/lib/cluster/generated/protocol.mjs +4 -4
  48. package/lib/stream-json-parser.js +6 -5
  49. package/npm-shrinkwrap.json +2 -2
  50. package/package.json +9 -18
  51. package/scripts/generate-cluster-types.js +16 -1
  52. package/scripts/node-release-analyzer.js +17 -0
  53. package/scripts/node-release-commits.js +82 -0
  54. package/scripts/release-dry-run.js +1 -1
  55. package/scripts/release-preflight.js +3 -3
  56. package/scripts/semantic-release-notes.js +8 -2
  57. package/src/agent/agent-task-executor.js +36 -40
  58. package/src/agent/output-extraction-json.js +5 -6
  59. package/src/agent/output-extraction-json.ts +4 -7
  60. package/src/agent-cli-provider/log-prefix.ts +12 -7
  61. package/src/claude-task-runner.js +9 -9
  62. package/src/cluster/connection-types.ts +2 -2
  63. package/src/cluster/generated/protocol-schema.ts +1 -1
  64. package/src/cluster/generated/protocol.ts +67 -5
  65. package/src/legacy-lib/stream-json-parser.ts +10 -5
  66. package/src/providers/index.js +1 -19
  67. package/src/task-log-line.d.ts +20 -0
  68. package/src/task-log-line.js +80 -0
  69. package/task-lib/commands/logs.js +5 -8
  70. package/task-lib/sdk-watcher-output.js +31 -0
  71. package/task-lib/sdk-watcher.js +4 -11
  72. package/task-lib/watcher-output-runtime.js +30 -16
  73. package/scripts/rust-distribution.js +0 -924
package/README.md CHANGED
@@ -37,6 +37,12 @@ zeroshot
37
37
 
38
38
  Requires **Node ≥ 22** and one supported provider. Guided setup detects installed providers, chooses a default, and configures worktree isolation for fresh repositories. Linux and macOS today; Windows is deferred.
39
39
 
40
+ This repository also contains **Zeroshot Rust**, the independent native product and `zeroshot-rust`
41
+ CLI. It has its own releases and self-hosted target image; see the
42
+ [distribution contract](docs/zeroshot-rust-distribution.md) and
43
+ [target image guide](docker/zeroshot-rust-target/README.md). The remainder of this README describes
44
+ the established Node product.
45
+
40
46
  ## First run
41
47
 
42
48
  ```bash
@@ -146,6 +152,8 @@ zeroshot logs <id> -f # stream logs
146
152
  zeroshot resume <id> [prompt] # resume a stopped or failed run
147
153
  zeroshot stop <id> # graceful stop
148
154
  zeroshot kill <id> # force stop
155
+ zeroshot export <id> --format trace --output run.trace.jsonl
156
+ zeroshot export <id> --format semantic --output run.semantic.jsonl
149
157
 
150
158
  zeroshot providers # provider availability and defaults
151
159
  zeroshot settings # effective settings
@@ -158,6 +166,16 @@ template supports `{{issue_number}}`, `{{issue_title}}`, and `{{issue_reference}
158
166
  expand to empty text for tasks without an issue, so manual runs never emit `Closes #unknown`.
159
167
  The unrendered template is retained for detached and resumed runs.
160
168
 
169
+ The `trace` export is a deterministic, provider-neutral research bundle. It preserves the ordered
170
+ cluster ledger, exact selected prompts, and exact raw task-log bytes without interpreting a Claude,
171
+ Codex, Pi, or other provider protocol. Missing evidence is recorded explicitly in its footer. File
172
+ exports are create-only: choose a new output path rather than replacing an existing bundle. Live
173
+ tasks are exported only as explicitly incomplete snapshots.
174
+ The separate `semantic` export runs those task bytes through Zeroshot's existing stateful provider
175
+ adapters and emits bounded `text`, `thinking`, `tool_call`, `tool_result`, and `result` events.
176
+ Zeroshot-owned wrapper and stderr records remain native-only. Parser diagnostics affect only
177
+ semantic completeness; they do not alter the native trace or run.
178
+
161
179
  </details>
162
180
 
163
181
  <details>
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stripTimestampPrefix = exports.getProviderAdapter = void 0;
4
+ function isRuntime(value, method) {
5
+ return (value !== null && typeof value === 'object' && typeof Reflect.get(value, method) === 'function');
6
+ }
7
+ function isAdapterRuntime(value) {
8
+ return isRuntime(value, 'getProviderAdapter');
9
+ }
10
+ function isPrefixRuntime(value) {
11
+ return isRuntime(value, 'stripTimestampPrefix');
12
+ }
13
+ const adapters = require('../lib/agent-cli-provider/adapters');
14
+ const prefixes = require('../lib/agent-cli-provider/log-prefix');
15
+ if (!isAdapterRuntime(adapters) || !isPrefixRuntime(prefixes)) {
16
+ throw new Error('Built agent-provider runtime is unavailable');
17
+ }
18
+ exports.getProviderAdapter = adapters.getProviderAdapter;
19
+ exports.stripTimestampPrefix = prefixes.stripTimestampPrefix;
@@ -0,0 +1,79 @@
1
+ interface TextEvent {
2
+ readonly type: 'text' | 'thinking';
3
+ readonly text: string;
4
+ }
5
+
6
+ interface ToolCallEvent {
7
+ readonly type: 'tool_call';
8
+ readonly toolName: string | null | undefined;
9
+ readonly toolId: string | null | undefined;
10
+ readonly input: unknown;
11
+ }
12
+
13
+ interface ToolResultEvent {
14
+ readonly type: 'tool_result';
15
+ readonly toolId: string | null | undefined;
16
+ readonly content: unknown;
17
+ readonly isError: unknown;
18
+ }
19
+
20
+ interface ResultEvent {
21
+ readonly type: 'result';
22
+ readonly success: boolean;
23
+ readonly result?: unknown;
24
+ readonly error?: unknown;
25
+ readonly cost?: unknown;
26
+ readonly duration?: unknown;
27
+ readonly inputTokens?: number;
28
+ readonly outputTokens?: number;
29
+ readonly cacheReadInputTokens?: number;
30
+ readonly cacheCreationInputTokens?: number;
31
+ readonly modelUsage?: unknown;
32
+ readonly requests?: number;
33
+ readonly usageSource?: unknown;
34
+ readonly usageCompleteness?: unknown;
35
+ readonly invocation?: unknown;
36
+ readonly ompSdk?: unknown;
37
+ }
38
+
39
+ export type OutputEvent = TextEvent | ToolCallEvent | ToolResultEvent | ResultEvent;
40
+ export type ProviderParseResult = OutputEvent | readonly OutputEvent[] | null;
41
+
42
+ export interface ProviderAdapter {
43
+ readonly id: string;
44
+ readonly adapterVersion: string;
45
+ createParserState(): object;
46
+ parseEvent(line: string, state: object): ProviderParseResult;
47
+ finishParsing?(state: object): ProviderParseResult;
48
+ }
49
+
50
+ interface AdapterRuntime {
51
+ getProviderAdapter(provider: string): ProviderAdapter;
52
+ }
53
+
54
+ interface PrefixRuntime {
55
+ stripTimestampPrefix(line: string): string;
56
+ }
57
+
58
+ function isRuntime(value: unknown, method: string): boolean {
59
+ return (
60
+ value !== null && typeof value === 'object' && typeof Reflect.get(value, method) === 'function'
61
+ );
62
+ }
63
+
64
+ function isAdapterRuntime(value: unknown): value is AdapterRuntime {
65
+ return isRuntime(value, 'getProviderAdapter');
66
+ }
67
+
68
+ function isPrefixRuntime(value: unknown): value is PrefixRuntime {
69
+ return isRuntime(value, 'stripTimestampPrefix');
70
+ }
71
+
72
+ const adapters: unknown = require('../lib/agent-cli-provider/adapters');
73
+ const prefixes: unknown = require('../lib/agent-cli-provider/log-prefix');
74
+ if (!isAdapterRuntime(adapters) || !isPrefixRuntime(prefixes)) {
75
+ throw new Error('Built agent-provider runtime is unavailable');
76
+ }
77
+
78
+ export const getProviderAdapter = adapters.getProviderAdapter;
79
+ export const stripTimestampPrefix = prefixes.stripTimestampPrefix;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nullableString = nullableString;
4
+ exports.isRecord = isRecord;
5
+ exports.compareText = compareText;
6
+ exports.sameFileSnapshot = sameFileSnapshot;
7
+ exports.createExclusiveDestination = createExclusiveDestination;
8
+ exports.createReplacingDestination = createReplacingDestination;
9
+ exports.createRecordWriter = createRecordWriter;
10
+ const crypto = require("crypto");
11
+ const fs = require("fs");
12
+ function nullableString(value) {
13
+ return typeof value === 'string' ? value : null;
14
+ }
15
+ function isRecord(value) {
16
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
17
+ }
18
+ function compareText(left, right) {
19
+ if (left < right)
20
+ return -1;
21
+ return left > right ? 1 : 0;
22
+ }
23
+ function sameFileSnapshot(before, after) {
24
+ return (before.dev === after.dev &&
25
+ before.ino === after.ino &&
26
+ before.size === after.size &&
27
+ before.mtimeNs === after.mtimeNs &&
28
+ before.ctimeNs === after.ctimeNs);
29
+ }
30
+ function writeAll(fd, value, label) {
31
+ const bytes = Buffer.from(value);
32
+ let offset = 0;
33
+ while (offset < bytes.length) {
34
+ const written = fs.writeSync(fd, bytes, offset, bytes.length - offset);
35
+ if (!Number.isInteger(written) || written <= 0) {
36
+ throw new Error(`${label} export destination stopped accepting bytes`);
37
+ }
38
+ offset += written;
39
+ }
40
+ }
41
+ function streamDestination(stdout, label) {
42
+ if (typeof stdout.fd === 'number' && Number.isInteger(stdout.fd)) {
43
+ const fd = stdout.fd;
44
+ return { close() { }, write: (value) => writeAll(fd, value, label) };
45
+ }
46
+ return {
47
+ close() { },
48
+ write(value) {
49
+ stdout.write(value);
50
+ },
51
+ };
52
+ }
53
+ function createExclusiveDestination(outputPath, stdout, label) {
54
+ if (!outputPath)
55
+ return streamDestination(stdout, label);
56
+ const flags = fs.constants.O_WRONLY |
57
+ fs.constants.O_CREAT |
58
+ fs.constants.O_EXCL |
59
+ (fs.constants.O_NOFOLLOW || 0);
60
+ const fd = fs.openSync(outputPath, flags, 0o600);
61
+ try {
62
+ fs.fchmodSync(fd, 0o600);
63
+ }
64
+ catch (error) {
65
+ fs.closeSync(fd);
66
+ throw error;
67
+ }
68
+ return { close: () => fs.closeSync(fd), write: (value) => writeAll(fd, value, label) };
69
+ }
70
+ function createReplacingDestination(outputPath, stdout) {
71
+ if (!outputPath)
72
+ return streamDestination(stdout, 'JSON');
73
+ const fd = fs.openSync(outputPath, 'w');
74
+ return { close: () => fs.closeSync(fd), write: (value) => writeAll(fd, value, 'JSON') };
75
+ }
76
+ function createRecordWriter(destination) {
77
+ const digest = crypto.createHash('sha256');
78
+ let records = 0;
79
+ const encode = (record) => `${JSON.stringify(record)}\n`;
80
+ return {
81
+ get records() {
82
+ return records;
83
+ },
84
+ write(record) {
85
+ const line = encode(record);
86
+ digest.update(line);
87
+ destination.write(line);
88
+ records += 1;
89
+ },
90
+ finish(record) {
91
+ destination.write(encode({ ...record, preceding_records: records, records_sha256: digest.digest('hex') }));
92
+ },
93
+ };
94
+ }
@@ -0,0 +1,118 @@
1
+ import crypto = require('crypto');
2
+ import fs = require('fs');
3
+
4
+ export interface ExportStream {
5
+ readonly fd?: number;
6
+ write(value: string): unknown;
7
+ }
8
+
9
+ export interface Destination {
10
+ close(): void;
11
+ write(value: string): void;
12
+ }
13
+
14
+ export interface RecordWriter {
15
+ readonly records: number;
16
+ finish(record: Record<string, unknown>): void;
17
+ write(record: Record<string, unknown>): void;
18
+ }
19
+
20
+ export function nullableString(value: unknown): string | null {
21
+ return typeof value === 'string' ? value : null;
22
+ }
23
+
24
+ export function isRecord(value: unknown): value is Record<string, unknown> {
25
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
26
+ }
27
+
28
+ export function compareText(left: string, right: string): number {
29
+ if (left < right) return -1;
30
+ return left > right ? 1 : 0;
31
+ }
32
+
33
+ export function sameFileSnapshot(before: fs.BigIntStats, after: fs.BigIntStats): boolean {
34
+ return (
35
+ before.dev === after.dev &&
36
+ before.ino === after.ino &&
37
+ before.size === after.size &&
38
+ before.mtimeNs === after.mtimeNs &&
39
+ before.ctimeNs === after.ctimeNs
40
+ );
41
+ }
42
+
43
+ function writeAll(fd: number, value: string, label: string): void {
44
+ const bytes = Buffer.from(value);
45
+ let offset = 0;
46
+ while (offset < bytes.length) {
47
+ const written = fs.writeSync(fd, bytes, offset, bytes.length - offset);
48
+ if (!Number.isInteger(written) || written <= 0) {
49
+ throw new Error(`${label} export destination stopped accepting bytes`);
50
+ }
51
+ offset += written;
52
+ }
53
+ }
54
+
55
+ function streamDestination(stdout: ExportStream, label: string): Destination {
56
+ if (typeof stdout.fd === 'number' && Number.isInteger(stdout.fd)) {
57
+ const fd = stdout.fd;
58
+ return { close(): void {}, write: (value) => writeAll(fd, value, label) };
59
+ }
60
+ return {
61
+ close(): void {},
62
+ write(value): void {
63
+ stdout.write(value);
64
+ },
65
+ };
66
+ }
67
+
68
+ export function createExclusiveDestination(
69
+ outputPath: string | null | undefined,
70
+ stdout: ExportStream,
71
+ label: string
72
+ ): Destination {
73
+ if (!outputPath) return streamDestination(stdout, label);
74
+ const flags =
75
+ fs.constants.O_WRONLY |
76
+ fs.constants.O_CREAT |
77
+ fs.constants.O_EXCL |
78
+ (fs.constants.O_NOFOLLOW || 0);
79
+ const fd = fs.openSync(outputPath, flags, 0o600);
80
+ try {
81
+ fs.fchmodSync(fd, 0o600);
82
+ } catch (error) {
83
+ fs.closeSync(fd);
84
+ throw error;
85
+ }
86
+ return { close: () => fs.closeSync(fd), write: (value) => writeAll(fd, value, label) };
87
+ }
88
+
89
+ export function createReplacingDestination(
90
+ outputPath: string | null | undefined,
91
+ stdout: ExportStream
92
+ ): Destination {
93
+ if (!outputPath) return streamDestination(stdout, 'JSON');
94
+ const fd = fs.openSync(outputPath, 'w');
95
+ return { close: () => fs.closeSync(fd), write: (value) => writeAll(fd, value, 'JSON') };
96
+ }
97
+
98
+ export function createRecordWriter(destination: Destination): RecordWriter {
99
+ const digest = crypto.createHash('sha256');
100
+ let records = 0;
101
+ const encode = (record: Record<string, unknown>): string => `${JSON.stringify(record)}\n`;
102
+ return {
103
+ get records(): number {
104
+ return records;
105
+ },
106
+ write(record): void {
107
+ const line = encode(record);
108
+ digest.update(line);
109
+ destination.write(line);
110
+ records += 1;
111
+ },
112
+ finish(record): void {
113
+ destination.write(
114
+ encode({ ...record, preceding_records: records, records_sha256: digest.digest('hex') })
115
+ );
116
+ },
117
+ };
118
+ }
package/cli/index.js CHANGED
@@ -59,6 +59,7 @@ const {
59
59
  } = require('../lib/provider-names');
60
60
  const { commandExists } = require('../lib/provider-detection');
61
61
  const { getProvider, parseProviderChunk } = require('../src/providers');
62
+ const { decodeTaskLogLine } = require('../src/task-log-line');
62
63
  const { readClustersFileSync } = require('../lib/clusters-registry');
63
64
  const { MOUNT_PRESETS, resolveEnvs } = require('../lib/docker-config');
64
65
  const {
@@ -1430,19 +1431,10 @@ function startClusterDbPolling(cluster, clusterId, isActive, options) {
1430
1431
  }
1431
1432
 
1432
1433
  function parseIncrementalTaskLogLine(line) {
1433
- const trimmed = line.trim();
1434
- if (!trimmed.startsWith('{')) {
1435
- return null;
1436
- }
1437
-
1438
- let timestamp = Date.now();
1439
- let jsonContent = trimmed;
1440
-
1441
- const timestampMatch = jsonContent.match(/^\[(\d{13})\](.*)$/);
1442
- if (timestampMatch) {
1443
- timestamp = parseInt(timestampMatch[1], 10);
1444
- jsonContent = timestampMatch[2];
1445
- }
1434
+ const decoded = decodeTaskLogLine(line.trim());
1435
+ if (!decoded.providerOutput) return null;
1436
+ const timestamp = decoded.timestamp ?? Date.now();
1437
+ const jsonContent = decoded.content.trim();
1446
1438
 
1447
1439
  if (!jsonContent.startsWith('{')) {
1448
1440
  return null;
@@ -2619,37 +2611,7 @@ function findAgentFromLifecycle(agents, taskId, lifecycleMessages) {
2619
2611
  }
2620
2612
 
2621
2613
  function parseTaskLogLine(line) {
2622
- const trimmed = line.trim();
2623
- if (!trimmed.startsWith('[')) {
2624
- return null;
2625
- }
2626
-
2627
- const parsed = parseTimestampedLine(trimmed);
2628
- if (!parsed.jsonContent.startsWith('{')) {
2629
- return null;
2630
- }
2631
-
2632
- try {
2633
- const json = JSON.parse(parsed.jsonContent);
2634
- if (json.type === 'system' && json.subtype === 'init') {
2635
- return null;
2636
- }
2637
- } catch {
2638
- return null;
2639
- }
2640
-
2641
- return parsed;
2642
- }
2643
-
2644
- function parseTimestampedLine(line) {
2645
- const timestampMatch = line.match(/^\[(\d{13})\](.*)$/);
2646
- if (!timestampMatch) {
2647
- return { timestamp: Date.now(), jsonContent: line };
2648
- }
2649
- return {
2650
- timestamp: parseInt(timestampMatch[1], 10),
2651
- jsonContent: timestampMatch[2],
2652
- };
2614
+ return parseIncrementalTaskLogLine(line);
2653
2615
  }
2654
2616
 
2655
2617
  function buildTaskLogMessage({ taskId, timestamp, jsonContent, cluster, agent, iteration }) {
@@ -3405,9 +3367,9 @@ program
3405
3367
  .command('export <cluster-id>')
3406
3368
  .helpGroup('Maintenance:')
3407
3369
  .description('Export cluster conversation')
3408
- .option('-f, --format <format>', 'Export format: json, markdown, html', 'html')
3370
+ .option('-f, --format <format>', 'Export format: json, trace, semantic, markdown, html', 'html')
3409
3371
  .option('-o, --output <file>', 'Output file (auto-generated for html)')
3410
- .action((clusterId, options) => {
3372
+ .action(async (clusterId, options) => {
3411
3373
  try {
3412
3374
  // Get messages from DB
3413
3375
  const Ledger = require('../src/ledger');
@@ -3418,14 +3380,7 @@ program
3418
3380
  throw new Error(`Cluster ${clusterId} not found (no DB file)`);
3419
3381
  }
3420
3382
 
3421
- // JSON export
3422
- if (options.format === 'json') {
3423
- exportClusterJson(dbPath, clusterId, options.output || null);
3424
- if (options.output) {
3425
- console.log(`Exported to ${options.output}`);
3426
- }
3427
- return;
3428
- }
3383
+ if (await exportClusterDataFormat(dbPath, clusterId, options)) return;
3429
3384
 
3430
3385
  const ledger = new Ledger(dbPath);
3431
3386
  const messages = ledger.getAll(clusterId);
@@ -6202,6 +6157,98 @@ function exportClusterJson(dbPath, clusterId, outputPath) {
6202
6157
  );
6203
6158
  }
6204
6159
 
6160
+ function tryExportClusterTraceSnapshot(dbPath, clusterId, outputPath, readTask, allowedLogRoot) {
6161
+ const Ledger = require('../src/ledger');
6162
+ const { streamClusterTraceExport } = require('./trace-export');
6163
+ const ledger = new Ledger(dbPath, { readonly: true });
6164
+ try {
6165
+ return ledger.withReadSnapshot(() => {
6166
+ if (ledger.needsAgentOutputReconciliation(clusterId)) return false;
6167
+ streamClusterTraceExport({
6168
+ ledger,
6169
+ clusterId,
6170
+ readTask,
6171
+ allowedLogRoot,
6172
+ outputPath,
6173
+ });
6174
+ return true;
6175
+ });
6176
+ } finally {
6177
+ ledger.close();
6178
+ }
6179
+ }
6180
+
6181
+ function exportClusterTrace(dbPath, clusterId, outputPath, readTask, allowedLogRoot) {
6182
+ const Ledger = require('../src/ledger');
6183
+ const maxReconciliationAttempts = 3;
6184
+ for (let attempt = 0; attempt <= maxReconciliationAttempts; attempt += 1) {
6185
+ if (tryExportClusterTraceSnapshot(dbPath, clusterId, outputPath, readTask, allowedLogRoot)) {
6186
+ return;
6187
+ }
6188
+ if (attempt === maxReconciliationAttempts) break;
6189
+ const reconciliationLedger = new Ledger(dbPath);
6190
+ reconciliationLedger.close();
6191
+ }
6192
+ throw new Error(
6193
+ `Cluster ${clusterId} output changed during ${maxReconciliationAttempts} reconciliation attempts`
6194
+ );
6195
+ }
6196
+
6197
+ function tryExportClusterSemanticSnapshot(dbPath, clusterId, outputPath, readTask, allowedLogRoot) {
6198
+ const Ledger = require('../src/ledger');
6199
+ const { streamClusterSemanticExport } = require('./semantic-export');
6200
+ const ledger = new Ledger(dbPath, { readonly: true });
6201
+ try {
6202
+ return ledger.withReadSnapshot(() => {
6203
+ if (ledger.needsAgentOutputReconciliation(clusterId)) return false;
6204
+ streamClusterSemanticExport({
6205
+ ledger,
6206
+ clusterId,
6207
+ readTask,
6208
+ allowedLogRoot,
6209
+ outputPath,
6210
+ });
6211
+ return true;
6212
+ });
6213
+ } finally {
6214
+ ledger.close();
6215
+ }
6216
+ }
6217
+
6218
+ function exportClusterSemantic(dbPath, clusterId, outputPath, readTask, allowedLogRoot) {
6219
+ const Ledger = require('../src/ledger');
6220
+ const maxReconciliationAttempts = 3;
6221
+ for (let attempt = 0; attempt <= maxReconciliationAttempts; attempt += 1) {
6222
+ if (tryExportClusterSemanticSnapshot(dbPath, clusterId, outputPath, readTask, allowedLogRoot)) {
6223
+ return;
6224
+ }
6225
+ if (attempt === maxReconciliationAttempts) break;
6226
+ const reconciliationLedger = new Ledger(dbPath);
6227
+ reconciliationLedger.close();
6228
+ }
6229
+ throw new Error(
6230
+ `Cluster ${clusterId} output changed during ${maxReconciliationAttempts} reconciliation attempts`
6231
+ );
6232
+ }
6233
+
6234
+ async function exportClusterDataFormat(dbPath, clusterId, options) {
6235
+ if (options.format === 'json') {
6236
+ exportClusterJson(dbPath, clusterId, options.output || null);
6237
+ if (options.output) console.log(`Exported to ${options.output}`);
6238
+ return true;
6239
+ }
6240
+ if (options.format !== 'trace' && options.format !== 'semantic') return false;
6241
+
6242
+ const [{ getTask }, { LOGS_DIR }] = await Promise.all([
6243
+ import('../task-lib/store.js'),
6244
+ import('../task-lib/config.js'),
6245
+ ]);
6246
+ const exporter = options.format === 'trace' ? exportClusterTrace : exportClusterSemantic;
6247
+ exporter(dbPath, clusterId, options.output || null, getTask, LOGS_DIR);
6248
+ if (options.output) console.log(`Exported ${options.format} to ${options.output}`);
6249
+ return true;
6250
+ }
6251
+
6205
6252
  // Main entry point
6206
6253
  async function main() {
6207
6254
  printLegacyDistroNotice();
@@ -6258,4 +6305,5 @@ module.exports = {
6258
6305
  buildCompletionPrompt,
6259
6306
  resolveRunMode,
6260
6307
  killRunningClusters,
6308
+ parseTaskLogLine,
6261
6309
  };
@@ -1,8 +1,5 @@
1
1
  "use strict";
2
- const fs = require("fs");
3
- function isInteger(value) {
4
- return Number.isInteger(value);
5
- }
2
+ const export_stream_1 = require("./export-stream");
6
3
  function indentJson(value, spaces) {
7
4
  const prefix = ' '.repeat(spaces);
8
5
  return JSON.stringify(value, null, 2)
@@ -10,41 +7,8 @@ function indentJson(value, spaces) {
10
7
  .map((line) => `${prefix}${line}`)
11
8
  .join('\n');
12
9
  }
13
- function writeAll(fd, value) {
14
- const bytes = Buffer.from(value);
15
- let offset = 0;
16
- while (offset < bytes.length) {
17
- const written = fs.writeSync(fd, bytes, offset, bytes.length - offset);
18
- if (!Number.isInteger(written) || written <= 0) {
19
- throw new Error('JSON export destination stopped accepting bytes');
20
- }
21
- offset += written;
22
- }
23
- }
24
- function createDestination(outputPath, stdout) {
25
- if (outputPath) {
26
- const fd = fs.openSync(outputPath, 'w');
27
- return {
28
- close: () => fs.closeSync(fd),
29
- write: (value) => writeAll(fd, value),
30
- };
31
- }
32
- if (isInteger(stdout.fd)) {
33
- const fd = stdout.fd;
34
- return {
35
- close() { },
36
- write: (value) => writeAll(fd, value),
37
- };
38
- }
39
- return {
40
- close() { },
41
- write: (value) => {
42
- stdout.write(value);
43
- },
44
- };
45
- }
46
10
  function streamClusterJsonExport({ ledger, clusterId, outputPath = null, stdout = process.stdout, }) {
47
- const destination = createDestination(outputPath, stdout);
11
+ const destination = (0, export_stream_1.createReplacingDestination)(outputPath, stdout);
48
12
  const iterator = ledger.iterateAll(clusterId);
49
13
  try {
50
14
  destination.write(`{\n "cluster_id": ${JSON.stringify(clusterId)},\n "messages": `);
@@ -1,4 +1,4 @@
1
- import fs = require('fs');
1
+ import { createReplacingDestination, type ExportStream } from './export-stream';
2
2
 
3
3
  interface LedgerMessageIterator extends Iterator<unknown> {
4
4
  return?: () => IteratorResult<unknown>;
@@ -8,11 +8,6 @@ interface ClusterLedger {
8
8
  iterateAll(clusterId: string): LedgerMessageIterator;
9
9
  }
10
10
 
11
- interface ExportStream {
12
- readonly fd?: number;
13
- write(value: string): unknown;
14
- }
15
-
16
11
  interface JsonExportOptions {
17
12
  ledger: ClusterLedger;
18
13
  clusterId: string;
@@ -20,15 +15,6 @@ interface JsonExportOptions {
20
15
  stdout?: ExportStream;
21
16
  }
22
17
 
23
- interface Destination {
24
- close(): void;
25
- write(value: string): void;
26
- }
27
-
28
- function isInteger(value: unknown): value is number {
29
- return Number.isInteger(value);
30
- }
31
-
32
18
  function indentJson(value: unknown, spaces: number): string {
33
19
  const prefix = ' '.repeat(spaces);
34
20
  return JSON.stringify(value, null, 2)
@@ -37,53 +23,13 @@ function indentJson(value: unknown, spaces: number): string {
37
23
  .join('\n');
38
24
  }
39
25
 
40
- function writeAll(fd: number, value: string): void {
41
- const bytes = Buffer.from(value);
42
- let offset = 0;
43
- while (offset < bytes.length) {
44
- const written = fs.writeSync(fd, bytes, offset, bytes.length - offset);
45
- if (!Number.isInteger(written) || written <= 0) {
46
- throw new Error('JSON export destination stopped accepting bytes');
47
- }
48
- offset += written;
49
- }
50
- }
51
-
52
- function createDestination(
53
- outputPath: string | null | undefined,
54
- stdout: ExportStream
55
- ): Destination {
56
- if (outputPath) {
57
- const fd = fs.openSync(outputPath, 'w');
58
- return {
59
- close: () => fs.closeSync(fd),
60
- write: (value) => writeAll(fd, value),
61
- };
62
- }
63
-
64
- if (isInteger(stdout.fd)) {
65
- const fd = stdout.fd;
66
- return {
67
- close(): void {},
68
- write: (value) => writeAll(fd, value),
69
- };
70
- }
71
-
72
- return {
73
- close(): void {},
74
- write: (value): void => {
75
- stdout.write(value);
76
- },
77
- };
78
- }
79
-
80
26
  function streamClusterJsonExport({
81
27
  ledger,
82
28
  clusterId,
83
29
  outputPath = null,
84
30
  stdout = process.stdout,
85
31
  }: JsonExportOptions): void {
86
- const destination = createDestination(outputPath, stdout);
32
+ const destination = createReplacingDestination(outputPath, stdout);
87
33
  const iterator = ledger.iterateAll(clusterId);
88
34
  try {
89
35
  destination.write(`{\n "cluster_id": ${JSON.stringify(clusterId)},\n "messages": `);