@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.
- package/README.md +18 -0
- package/cli/agent-provider-boundary.js +19 -0
- package/cli/agent-provider-boundary.ts +79 -0
- package/cli/export-stream.js +94 -0
- package/cli/export-stream.ts +118 -0
- package/cli/index.js +102 -54
- package/cli/json-export.js +2 -38
- package/cli/json-export.ts +2 -56
- package/cli/semantic-canonical.js +60 -0
- package/cli/semantic-canonical.ts +59 -0
- package/cli/semantic-contract.js +97 -0
- package/cli/semantic-contract.ts +153 -0
- package/cli/semantic-events.js +53 -0
- package/cli/semantic-events.ts +65 -0
- package/cli/semantic-evidence.js +63 -0
- package/cli/semantic-evidence.ts +65 -0
- package/cli/semantic-export.js +186 -0
- package/cli/semantic-export.ts +245 -0
- package/cli/semantic-json.js +69 -0
- package/cli/semantic-json.ts +69 -0
- package/cli/semantic-line-scanner.js +56 -0
- package/cli/semantic-line-scanner.ts +54 -0
- package/cli/semantic-parser.js +78 -0
- package/cli/semantic-parser.ts +95 -0
- package/cli/semantic-provider-line.js +91 -0
- package/cli/semantic-provider-line.ts +106 -0
- package/cli/trace-evidence.js +86 -0
- package/cli/trace-evidence.ts +115 -0
- package/cli/trace-export.js +120 -0
- package/cli/trace-export.ts +164 -0
- package/cli/trace-output-record.js +15 -0
- package/cli/trace-output-record.ts +18 -0
- package/cli/trace-output.js +98 -0
- package/cli/trace-output.ts +119 -0
- package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -1
- package/lib/agent-cli-provider/log-prefix.js +8 -6
- package/lib/agent-cli-provider/log-prefix.js.map +1 -1
- package/lib/cluster/connection-types.d.cts +2 -2
- package/lib/cluster/connection-types.d.mts +2 -2
- package/lib/cluster/connection-types.d.ts +2 -2
- package/lib/cluster/generated/protocol-schema.cjs +1 -1
- package/lib/cluster/generated/protocol-schema.mjs +1 -1
- package/lib/cluster/generated/protocol.cjs +4 -4
- package/lib/cluster/generated/protocol.d.cts +271 -9
- package/lib/cluster/generated/protocol.d.mts +271 -9
- package/lib/cluster/generated/protocol.d.ts +271 -9
- package/lib/cluster/generated/protocol.mjs +4 -4
- package/lib/stream-json-parser.js +6 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +9 -18
- package/scripts/generate-cluster-types.js +16 -1
- package/scripts/node-release-analyzer.js +17 -0
- package/scripts/node-release-commits.js +82 -0
- package/scripts/release-dry-run.js +1 -1
- package/scripts/release-preflight.js +3 -3
- package/scripts/semantic-release-notes.js +8 -2
- package/src/agent/agent-task-executor.js +36 -40
- package/src/agent/output-extraction-json.js +5 -6
- package/src/agent/output-extraction-json.ts +4 -7
- package/src/agent-cli-provider/log-prefix.ts +12 -7
- package/src/claude-task-runner.js +9 -9
- package/src/cluster/connection-types.ts +2 -2
- package/src/cluster/generated/protocol-schema.ts +1 -1
- package/src/cluster/generated/protocol.ts +67 -5
- package/src/legacy-lib/stream-json-parser.ts +10 -5
- package/src/providers/index.js +1 -19
- package/src/task-log-line.d.ts +20 -0
- package/src/task-log-line.js +80 -0
- package/task-lib/commands/logs.js +5 -8
- package/task-lib/sdk-watcher-output.js +31 -0
- package/task-lib/sdk-watcher.js +4 -11
- package/task-lib/watcher-output-runtime.js +30 -16
- package/scripts/rust-distribution.js +0 -924
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseOpenedEvidence = parseOpenedEvidence;
|
|
4
|
+
const crypto = require("crypto");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const export_stream_1 = require("./export-stream");
|
|
7
|
+
const semantic_contract_1 = require("./semantic-contract");
|
|
8
|
+
const semantic_events_1 = require("./semantic-events");
|
|
9
|
+
const semantic_line_scanner_1 = require("./semantic-line-scanner");
|
|
10
|
+
const semantic_provider_line_1 = require("./semantic-provider-line");
|
|
11
|
+
const trace_output_1 = require("./trace-output");
|
|
12
|
+
function readEvidence(fd, byteLength, scanner, digest) {
|
|
13
|
+
let bytes = 0;
|
|
14
|
+
while (bytes < byteLength) {
|
|
15
|
+
const remaining = byteLength - bytes;
|
|
16
|
+
const buffer = Buffer.allocUnsafe(Math.min(trace_output_1.TRACE_OUTPUT_CHUNK_BYTES, remaining));
|
|
17
|
+
const read = fs.readSync(fd, buffer, 0, buffer.length, bytes);
|
|
18
|
+
if (read === 0)
|
|
19
|
+
break;
|
|
20
|
+
const chunk = read === buffer.length ? buffer : buffer.subarray(0, read);
|
|
21
|
+
digest.update(chunk);
|
|
22
|
+
scanner.consume(chunk, bytes);
|
|
23
|
+
bytes += read;
|
|
24
|
+
}
|
|
25
|
+
scanner.finish(bytes);
|
|
26
|
+
return bytes;
|
|
27
|
+
}
|
|
28
|
+
function finishAdapter(adapter, state, context) {
|
|
29
|
+
try {
|
|
30
|
+
const final = adapter.finishParsing?.(state) ?? null;
|
|
31
|
+
for (const event of (0, semantic_events_1.emittedEvents)(final))
|
|
32
|
+
(0, semantic_events_1.emitEvent)(context, event, null, 'finish');
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'parser_finish_error', null);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function reportProjectionCompleteness(context) {
|
|
39
|
+
if (context.provider !== 'pi' && context.logFormat === null) {
|
|
40
|
+
if (!context.issueCodes.has('legacy_ambiguous_channels')) {
|
|
41
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'legacy_ambiguous_channels', null);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!context.terminalResultEmitted)
|
|
45
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'terminal_result_missing', null);
|
|
46
|
+
}
|
|
47
|
+
function sourceMatches(evidence, bytes, digest) {
|
|
48
|
+
const after = fs.fstatSync(evidence.fd, { bigint: true });
|
|
49
|
+
return (bytes === evidence.byteLength &&
|
|
50
|
+
digest.digest('hex') === evidence.sha256 &&
|
|
51
|
+
(0, export_stream_1.sameFileSnapshot)(evidence.before, after));
|
|
52
|
+
}
|
|
53
|
+
function parseOpenedEvidence(evidence, context, adapter) {
|
|
54
|
+
if (evidence.fd === null || evidence.before === null || evidence.byteLength === null)
|
|
55
|
+
return false;
|
|
56
|
+
const stableEvidence = {
|
|
57
|
+
fd: evidence.fd,
|
|
58
|
+
before: evidence.before,
|
|
59
|
+
byteLength: evidence.byteLength,
|
|
60
|
+
sha256: evidence.sha256,
|
|
61
|
+
};
|
|
62
|
+
const state = adapter.createParserState();
|
|
63
|
+
const digest = crypto.createHash('sha256');
|
|
64
|
+
const scanner = new semantic_line_scanner_1.BoundedLineScanner((line, source) => (0, semantic_provider_line_1.handleProviderLine)(context, state, line, source));
|
|
65
|
+
try {
|
|
66
|
+
const bytes = readEvidence(stableEvidence.fd, stableEvidence.byteLength, scanner, digest);
|
|
67
|
+
finishAdapter(adapter, state, context);
|
|
68
|
+
reportProjectionCompleteness(context);
|
|
69
|
+
const complete = sourceMatches(stableEvidence, bytes, digest);
|
|
70
|
+
if (!complete)
|
|
71
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'log_changed_during_parse', null);
|
|
72
|
+
return complete;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'log_read_failed', null);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import crypto = require('crypto');
|
|
2
|
+
import fs = require('fs');
|
|
3
|
+
import type { ProviderAdapter } from './agent-provider-boundary';
|
|
4
|
+
import { sameFileSnapshot } from './export-stream';
|
|
5
|
+
import { reportDiagnostic, type OpenedEvidence, type TaskProjection } from './semantic-contract';
|
|
6
|
+
import { emitEvent, emittedEvents } from './semantic-events';
|
|
7
|
+
import { BoundedLineScanner } from './semantic-line-scanner';
|
|
8
|
+
import { handleProviderLine } from './semantic-provider-line';
|
|
9
|
+
import { TRACE_OUTPUT_CHUNK_BYTES } from './trace-output';
|
|
10
|
+
|
|
11
|
+
function readEvidence(
|
|
12
|
+
fd: number,
|
|
13
|
+
byteLength: number,
|
|
14
|
+
scanner: BoundedLineScanner,
|
|
15
|
+
digest: crypto.Hash
|
|
16
|
+
): number {
|
|
17
|
+
let bytes = 0;
|
|
18
|
+
while (bytes < byteLength) {
|
|
19
|
+
const remaining = byteLength - bytes;
|
|
20
|
+
const buffer = Buffer.allocUnsafe(Math.min(TRACE_OUTPUT_CHUNK_BYTES, remaining));
|
|
21
|
+
const read = fs.readSync(fd, buffer, 0, buffer.length, bytes);
|
|
22
|
+
if (read === 0) break;
|
|
23
|
+
const chunk = read === buffer.length ? buffer : buffer.subarray(0, read);
|
|
24
|
+
digest.update(chunk);
|
|
25
|
+
scanner.consume(chunk, bytes);
|
|
26
|
+
bytes += read;
|
|
27
|
+
}
|
|
28
|
+
scanner.finish(bytes);
|
|
29
|
+
return bytes;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function finishAdapter(adapter: ProviderAdapter, state: object, context: TaskProjection): void {
|
|
33
|
+
try {
|
|
34
|
+
const final = adapter.finishParsing?.(state) ?? null;
|
|
35
|
+
for (const event of emittedEvents(final)) emitEvent(context, event, null, 'finish');
|
|
36
|
+
} catch {
|
|
37
|
+
reportDiagnostic(context, 'parser_finish_error', null);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function reportProjectionCompleteness(context: TaskProjection): void {
|
|
42
|
+
if (context.provider !== 'pi' && context.logFormat === null) {
|
|
43
|
+
if (!context.issueCodes.has('legacy_ambiguous_channels')) {
|
|
44
|
+
reportDiagnostic(context, 'legacy_ambiguous_channels', null);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!context.terminalResultEmitted) reportDiagnostic(context, 'terminal_result_missing', null);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface StableEvidence {
|
|
51
|
+
fd: number;
|
|
52
|
+
before: fs.BigIntStats;
|
|
53
|
+
byteLength: number;
|
|
54
|
+
sha256: string | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sourceMatches(evidence: StableEvidence, bytes: number, digest: crypto.Hash): boolean {
|
|
58
|
+
const after = fs.fstatSync(evidence.fd, { bigint: true });
|
|
59
|
+
return (
|
|
60
|
+
bytes === evidence.byteLength &&
|
|
61
|
+
digest.digest('hex') === evidence.sha256 &&
|
|
62
|
+
sameFileSnapshot(evidence.before, after)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function parseOpenedEvidence(
|
|
67
|
+
evidence: OpenedEvidence,
|
|
68
|
+
context: TaskProjection,
|
|
69
|
+
adapter: ProviderAdapter
|
|
70
|
+
): boolean {
|
|
71
|
+
if (evidence.fd === null || evidence.before === null || evidence.byteLength === null)
|
|
72
|
+
return false;
|
|
73
|
+
const stableEvidence: StableEvidence = {
|
|
74
|
+
fd: evidence.fd,
|
|
75
|
+
before: evidence.before,
|
|
76
|
+
byteLength: evidence.byteLength,
|
|
77
|
+
sha256: evidence.sha256,
|
|
78
|
+
};
|
|
79
|
+
const state = adapter.createParserState();
|
|
80
|
+
const digest = crypto.createHash('sha256');
|
|
81
|
+
const scanner = new BoundedLineScanner((line, source) =>
|
|
82
|
+
handleProviderLine(context, state, line, source)
|
|
83
|
+
);
|
|
84
|
+
try {
|
|
85
|
+
const bytes = readEvidence(stableEvidence.fd, stableEvidence.byteLength, scanner, digest);
|
|
86
|
+
finishAdapter(adapter, state, context);
|
|
87
|
+
reportProjectionCompleteness(context);
|
|
88
|
+
const complete = sourceMatches(stableEvidence, bytes, digest);
|
|
89
|
+
if (!complete) reportDiagnostic(context, 'log_changed_during_parse', null);
|
|
90
|
+
return complete;
|
|
91
|
+
} catch {
|
|
92
|
+
reportDiagnostic(context, 'log_read_failed', null);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleProviderLine = handleProviderLine;
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
const agent_provider_boundary_1 = require("./agent-provider-boundary");
|
|
6
|
+
const semantic_contract_1 = require("./semantic-contract");
|
|
7
|
+
const semantic_events_1 = require("./semantic-events");
|
|
8
|
+
const task_log_line_1 = require("../src/task-log-line");
|
|
9
|
+
function jsonLineIssue(content, providerId) {
|
|
10
|
+
const trimmed = content.trim();
|
|
11
|
+
if (!trimmed)
|
|
12
|
+
return null;
|
|
13
|
+
const candidate = providerId === 'pi' || trimmed.startsWith('{') || trimmed.startsWith('[');
|
|
14
|
+
if (!candidate)
|
|
15
|
+
return null;
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = JSON.parse(trimmed);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return 'malformed_json';
|
|
22
|
+
}
|
|
23
|
+
return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
24
|
+
? null
|
|
25
|
+
: 'invalid_json_shape';
|
|
26
|
+
}
|
|
27
|
+
function decodeLine(context, line, source) {
|
|
28
|
+
if (line === null) {
|
|
29
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'line_too_large', source);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return new node_util_1.TextDecoder('utf-8', { fatal: true }).decode(line);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'invalid_utf8', source);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function handleProviderLine(context, state, line, source) {
|
|
41
|
+
const rawLine = decodeLine(context, line, source);
|
|
42
|
+
if (rawLine === null)
|
|
43
|
+
return;
|
|
44
|
+
const timestamp = /^\[(\d{13})\]/.exec(rawLine);
|
|
45
|
+
source.timestamp_ms = timestamp ? Number(timestamp[1]) : null;
|
|
46
|
+
if (skipOwnedOrAmbiguous(context, rawLine, source))
|
|
47
|
+
return;
|
|
48
|
+
let content = (0, agent_provider_boundary_1.stripTimestampPrefix)(rawLine);
|
|
49
|
+
if (context.logFormat === 'channel-framed-v2') {
|
|
50
|
+
const decoded = (0, task_log_line_1.decodeTaskLogLine)(rawLine);
|
|
51
|
+
content = decoded.channel === 'provider_stdout' ? decoded.content : null;
|
|
52
|
+
}
|
|
53
|
+
if (content === null) {
|
|
54
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'unframed_log_line', source);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
parseProviderContent(context, state, content, source);
|
|
58
|
+
}
|
|
59
|
+
function skipOwnedOrAmbiguous(context, rawLine, source) {
|
|
60
|
+
const owned = (0, semantic_contract_1.classifyOwnedLogLine)(rawLine);
|
|
61
|
+
if (owned === 'format-v1') {
|
|
62
|
+
context.logFormat = 'stderr-tagged-v1';
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (owned === 'format-v2') {
|
|
66
|
+
context.logFormat = 'channel-framed-v2';
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (owned === 'owned')
|
|
70
|
+
return true;
|
|
71
|
+
if (context.provider !== 'pi' && context.logFormat === null) {
|
|
72
|
+
if (!context.issueCodes.has('legacy_ambiguous_channels')) {
|
|
73
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'legacy_ambiguous_channels', source);
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
function parseProviderContent(context, state, content, source) {
|
|
80
|
+
const lineIssue = jsonLineIssue(content, context.adapter?.id || '');
|
|
81
|
+
if (lineIssue)
|
|
82
|
+
(0, semantic_contract_1.reportDiagnostic)(context, lineIssue, source);
|
|
83
|
+
try {
|
|
84
|
+
const result = context.adapter?.parseEvent(content, state) ?? null;
|
|
85
|
+
for (const event of (0, semantic_events_1.emittedEvents)(result))
|
|
86
|
+
(0, semantic_events_1.emitEvent)(context, event, source, 'line');
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'parser_error', source);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { TextDecoder } from 'node:util';
|
|
2
|
+
import { stripTimestampPrefix, type ProviderAdapter } from './agent-provider-boundary';
|
|
3
|
+
import {
|
|
4
|
+
classifyOwnedLogLine,
|
|
5
|
+
reportDiagnostic,
|
|
6
|
+
type SourceSpan,
|
|
7
|
+
type TaskProjection,
|
|
8
|
+
} from './semantic-contract';
|
|
9
|
+
import { emitEvent, emittedEvents } from './semantic-events';
|
|
10
|
+
import { decodeTaskLogLine } from '../src/task-log-line';
|
|
11
|
+
|
|
12
|
+
function jsonLineIssue(content: string, providerId: string): string | null {
|
|
13
|
+
const trimmed = content.trim();
|
|
14
|
+
if (!trimmed) return null;
|
|
15
|
+
const candidate = providerId === 'pi' || trimmed.startsWith('{') || trimmed.startsWith('[');
|
|
16
|
+
if (!candidate) return null;
|
|
17
|
+
let parsed: unknown;
|
|
18
|
+
try {
|
|
19
|
+
parsed = JSON.parse(trimmed);
|
|
20
|
+
} catch {
|
|
21
|
+
return 'malformed_json';
|
|
22
|
+
}
|
|
23
|
+
return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
24
|
+
? null
|
|
25
|
+
: 'invalid_json_shape';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function decodeLine(
|
|
29
|
+
context: TaskProjection,
|
|
30
|
+
line: Buffer | null,
|
|
31
|
+
source: SourceSpan
|
|
32
|
+
): string | null {
|
|
33
|
+
if (line === null) {
|
|
34
|
+
reportDiagnostic(context, 'line_too_large', source);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(line);
|
|
39
|
+
} catch {
|
|
40
|
+
reportDiagnostic(context, 'invalid_utf8', source);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function handleProviderLine(
|
|
46
|
+
context: TaskProjection,
|
|
47
|
+
state: ReturnType<ProviderAdapter['createParserState']>,
|
|
48
|
+
line: Buffer | null,
|
|
49
|
+
source: SourceSpan
|
|
50
|
+
): void {
|
|
51
|
+
const rawLine = decodeLine(context, line, source);
|
|
52
|
+
if (rawLine === null) return;
|
|
53
|
+
const timestamp = /^\[(\d{13})\]/.exec(rawLine);
|
|
54
|
+
source.timestamp_ms = timestamp ? Number(timestamp[1]) : null;
|
|
55
|
+
if (skipOwnedOrAmbiguous(context, rawLine, source)) return;
|
|
56
|
+
let content: string | null = stripTimestampPrefix(rawLine);
|
|
57
|
+
if (context.logFormat === 'channel-framed-v2') {
|
|
58
|
+
const decoded = decodeTaskLogLine(rawLine);
|
|
59
|
+
content = decoded.channel === 'provider_stdout' ? decoded.content : null;
|
|
60
|
+
}
|
|
61
|
+
if (content === null) {
|
|
62
|
+
reportDiagnostic(context, 'unframed_log_line', source);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
parseProviderContent(context, state, content, source);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function skipOwnedOrAmbiguous(
|
|
69
|
+
context: TaskProjection,
|
|
70
|
+
rawLine: string,
|
|
71
|
+
source: SourceSpan
|
|
72
|
+
): boolean {
|
|
73
|
+
const owned = classifyOwnedLogLine(rawLine);
|
|
74
|
+
if (owned === 'format-v1') {
|
|
75
|
+
context.logFormat = 'stderr-tagged-v1';
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (owned === 'format-v2') {
|
|
79
|
+
context.logFormat = 'channel-framed-v2';
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (owned === 'owned') return true;
|
|
83
|
+
if (context.provider !== 'pi' && context.logFormat === null) {
|
|
84
|
+
if (!context.issueCodes.has('legacy_ambiguous_channels')) {
|
|
85
|
+
reportDiagnostic(context, 'legacy_ambiguous_channels', source);
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseProviderContent(
|
|
93
|
+
context: TaskProjection,
|
|
94
|
+
state: ReturnType<ProviderAdapter['createParserState']>,
|
|
95
|
+
content: string,
|
|
96
|
+
source: SourceSpan
|
|
97
|
+
): void {
|
|
98
|
+
const lineIssue = jsonLineIssue(content, context.adapter?.id || '');
|
|
99
|
+
if (lineIssue) reportDiagnostic(context, lineIssue, source);
|
|
100
|
+
try {
|
|
101
|
+
const result = context.adapter?.parseEvent(content, state) ?? null;
|
|
102
|
+
for (const event of emittedEvents(result)) emitEvent(context, event, source, 'line');
|
|
103
|
+
} catch {
|
|
104
|
+
reportDiagnostic(context, 'parser_error', source);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasTerminalTaskStatus = hasTerminalTaskStatus;
|
|
4
|
+
exports.forEachLedgerMessage = forEachLedgerMessage;
|
|
5
|
+
exports.collectTaskCauses = collectTaskCauses;
|
|
6
|
+
exports.logicalTaskRef = logicalTaskRef;
|
|
7
|
+
exports.expectedLogPath = expectedLogPath;
|
|
8
|
+
exports.readTraceTask = readTraceTask;
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const export_stream_1 = require("./export-stream");
|
|
11
|
+
const TASK_LIFECYCLE_EVENTS = new Set([
|
|
12
|
+
'TASK_ID_ASSIGNED',
|
|
13
|
+
'TASK_STARTED',
|
|
14
|
+
'TASK_COMPLETED',
|
|
15
|
+
'TASK_FAILED',
|
|
16
|
+
]);
|
|
17
|
+
const TERMINAL_TASK_STATUSES = new Set(['completed', 'failed', 'killed', 'stale', 'cancelled']);
|
|
18
|
+
function hasTerminalTaskStatus(task) {
|
|
19
|
+
const status = (0, export_stream_1.nullableString)(task.status);
|
|
20
|
+
return status !== null && TERMINAL_TASK_STATUSES.has(status);
|
|
21
|
+
}
|
|
22
|
+
function forEachLedgerMessage(ledger, clusterId, consume) {
|
|
23
|
+
for (const message of ledger.iterateAll(clusterId))
|
|
24
|
+
consume(message);
|
|
25
|
+
}
|
|
26
|
+
function recordData(message) {
|
|
27
|
+
if (!(0, export_stream_1.isRecord)(message) || !(0, export_stream_1.isRecord)(message.content))
|
|
28
|
+
return null;
|
|
29
|
+
return (0, export_stream_1.isRecord)(message.content.data) ? message.content.data : null;
|
|
30
|
+
}
|
|
31
|
+
function messageSender(message, data) {
|
|
32
|
+
const declaredAgent = (0, export_stream_1.nullableString)(data.agent);
|
|
33
|
+
if (declaredAgent)
|
|
34
|
+
return declaredAgent;
|
|
35
|
+
if (!(0, export_stream_1.isRecord)(message))
|
|
36
|
+
return null;
|
|
37
|
+
return (0, export_stream_1.nullableString)(message.sender) || null;
|
|
38
|
+
}
|
|
39
|
+
function isTaskEvidenceMessage(message, data) {
|
|
40
|
+
if (!(0, export_stream_1.isRecord)(message))
|
|
41
|
+
return false;
|
|
42
|
+
if (message.topic === 'AGENT_OUTPUT')
|
|
43
|
+
return true;
|
|
44
|
+
return (message.topic === 'AGENT_LIFECYCLE' &&
|
|
45
|
+
TASK_LIFECYCLE_EVENTS.has((0, export_stream_1.nullableString)(data.event) || ''));
|
|
46
|
+
}
|
|
47
|
+
function collectTaskCauses(ledger, clusterId) {
|
|
48
|
+
const causes = new Map();
|
|
49
|
+
forEachLedgerMessage(ledger, clusterId, (message) => {
|
|
50
|
+
const data = recordData(message);
|
|
51
|
+
if (!data || !isTaskEvidenceMessage(message, data))
|
|
52
|
+
return;
|
|
53
|
+
const taskId = (0, export_stream_1.nullableString)(data.taskId);
|
|
54
|
+
if (!taskId)
|
|
55
|
+
return;
|
|
56
|
+
const cause = causes.get(taskId) || { agentIds: new Set() };
|
|
57
|
+
const sender = messageSender(message, data);
|
|
58
|
+
if (sender)
|
|
59
|
+
cause.agentIds.add(sender);
|
|
60
|
+
causes.set(taskId, cause);
|
|
61
|
+
});
|
|
62
|
+
return causes;
|
|
63
|
+
}
|
|
64
|
+
function logicalTaskRef(taskId, leaf) {
|
|
65
|
+
return `zeroshot-trace://task/${encodeURIComponent(taskId)}/${leaf}`;
|
|
66
|
+
}
|
|
67
|
+
function expectedLogPath(allowedLogRoot, taskId) {
|
|
68
|
+
const resolvedRoot = path.resolve(allowedLogRoot);
|
|
69
|
+
const expected = path.resolve(resolvedRoot, `${taskId}.log`);
|
|
70
|
+
return path.dirname(expected) === resolvedRoot ? expected : null;
|
|
71
|
+
}
|
|
72
|
+
function readTraceTask(taskId, readTask) {
|
|
73
|
+
let task;
|
|
74
|
+
try {
|
|
75
|
+
task = readTask(taskId);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return { task: null, issue: `task:${taskId}:task_row_unreadable` };
|
|
79
|
+
}
|
|
80
|
+
if (!task)
|
|
81
|
+
return { task: null, issue: `task:${taskId}:task_row_missing` };
|
|
82
|
+
if (task.id !== undefined && task.id !== taskId) {
|
|
83
|
+
return { task: null, issue: `task:${taskId}:task_row_identity_mismatch` };
|
|
84
|
+
}
|
|
85
|
+
return { task, issue: null };
|
|
86
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import path = require('path');
|
|
2
|
+
import { isRecord, nullableString } from './export-stream';
|
|
3
|
+
|
|
4
|
+
const TASK_LIFECYCLE_EVENTS = new Set([
|
|
5
|
+
'TASK_ID_ASSIGNED',
|
|
6
|
+
'TASK_STARTED',
|
|
7
|
+
'TASK_COMPLETED',
|
|
8
|
+
'TASK_FAILED',
|
|
9
|
+
]);
|
|
10
|
+
const TERMINAL_TASK_STATUSES = new Set(['completed', 'failed', 'killed', 'stale', 'cancelled']);
|
|
11
|
+
|
|
12
|
+
export interface ClusterLedger {
|
|
13
|
+
iterateAll(clusterId: string): Iterable<unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TraceTask {
|
|
17
|
+
id?: unknown;
|
|
18
|
+
fullPrompt?: unknown;
|
|
19
|
+
prompt?: unknown;
|
|
20
|
+
status?: unknown;
|
|
21
|
+
createdAt?: unknown;
|
|
22
|
+
updatedAt?: unknown;
|
|
23
|
+
exitCode?: unknown;
|
|
24
|
+
provider?: unknown;
|
|
25
|
+
model?: unknown;
|
|
26
|
+
logFile?: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TaskCause {
|
|
30
|
+
agentIds: Set<string>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TraceTaskRead {
|
|
34
|
+
issue: string | null;
|
|
35
|
+
task: TraceTask | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function hasTerminalTaskStatus(task: TraceTask): boolean {
|
|
39
|
+
const status = nullableString(task.status);
|
|
40
|
+
return status !== null && TERMINAL_TASK_STATUSES.has(status);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function forEachLedgerMessage(
|
|
44
|
+
ledger: ClusterLedger,
|
|
45
|
+
clusterId: string,
|
|
46
|
+
consume: (message: unknown) => void
|
|
47
|
+
): void {
|
|
48
|
+
for (const message of ledger.iterateAll(clusterId)) consume(message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function recordData(message: unknown): Record<string, unknown> | null {
|
|
52
|
+
if (!isRecord(message) || !isRecord(message.content)) return null;
|
|
53
|
+
return isRecord(message.content.data) ? message.content.data : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function messageSender(message: unknown, data: Record<string, unknown>): string | null {
|
|
57
|
+
const declaredAgent = nullableString(data.agent);
|
|
58
|
+
if (declaredAgent) return declaredAgent;
|
|
59
|
+
if (!isRecord(message)) return null;
|
|
60
|
+
return nullableString(message.sender) || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isTaskEvidenceMessage(message: unknown, data: Record<string, unknown>): boolean {
|
|
64
|
+
if (!isRecord(message)) return false;
|
|
65
|
+
if (message.topic === 'AGENT_OUTPUT') return true;
|
|
66
|
+
return (
|
|
67
|
+
message.topic === 'AGENT_LIFECYCLE' &&
|
|
68
|
+
TASK_LIFECYCLE_EVENTS.has(nullableString(data.event) || '')
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function collectTaskCauses(
|
|
73
|
+
ledger: ClusterLedger,
|
|
74
|
+
clusterId: string
|
|
75
|
+
): Map<string, TaskCause> {
|
|
76
|
+
const causes = new Map<string, TaskCause>();
|
|
77
|
+
forEachLedgerMessage(ledger, clusterId, (message) => {
|
|
78
|
+
const data = recordData(message);
|
|
79
|
+
if (!data || !isTaskEvidenceMessage(message, data)) return;
|
|
80
|
+
const taskId = nullableString(data.taskId);
|
|
81
|
+
if (!taskId) return;
|
|
82
|
+
const cause = causes.get(taskId) || { agentIds: new Set<string>() };
|
|
83
|
+
const sender = messageSender(message, data);
|
|
84
|
+
if (sender) cause.agentIds.add(sender);
|
|
85
|
+
causes.set(taskId, cause);
|
|
86
|
+
});
|
|
87
|
+
return causes;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function logicalTaskRef(taskId: string, leaf: 'prompt' | 'output'): string {
|
|
91
|
+
return `zeroshot-trace://task/${encodeURIComponent(taskId)}/${leaf}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function expectedLogPath(allowedLogRoot: string, taskId: string): string | null {
|
|
95
|
+
const resolvedRoot = path.resolve(allowedLogRoot);
|
|
96
|
+
const expected = path.resolve(resolvedRoot, `${taskId}.log`);
|
|
97
|
+
return path.dirname(expected) === resolvedRoot ? expected : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function readTraceTask(
|
|
101
|
+
taskId: string,
|
|
102
|
+
readTask: (taskId: string) => TraceTask | null
|
|
103
|
+
): TraceTaskRead {
|
|
104
|
+
let task: TraceTask | null;
|
|
105
|
+
try {
|
|
106
|
+
task = readTask(taskId);
|
|
107
|
+
} catch {
|
|
108
|
+
return { task: null, issue: `task:${taskId}:task_row_unreadable` };
|
|
109
|
+
}
|
|
110
|
+
if (!task) return { task: null, issue: `task:${taskId}:task_row_missing` };
|
|
111
|
+
if (task.id !== undefined && task.id !== taskId) {
|
|
112
|
+
return { task: null, issue: `task:${taskId}:task_row_identity_mismatch` };
|
|
113
|
+
}
|
|
114
|
+
return { task, issue: null };
|
|
115
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const export_stream_1 = require("./export-stream");
|
|
4
|
+
const trace_evidence_1 = require("./trace-evidence");
|
|
5
|
+
const trace_output_1 = require("./trace-output");
|
|
6
|
+
const TRACE_SCHEMA_VERSION = 'zeroshot.trace.v1';
|
|
7
|
+
const TRACE_MEDIA_TYPE = 'application/x-zeroshot-trace+jsonl';
|
|
8
|
+
function nullableInteger(value) {
|
|
9
|
+
return typeof value === 'number' && Number.isInteger(value) ? value : null;
|
|
10
|
+
}
|
|
11
|
+
function assertOutputDoesNotReplaceTaskLog(outputPath, allowedLogRoot, taskIds) {
|
|
12
|
+
if (!outputPath)
|
|
13
|
+
return;
|
|
14
|
+
const resolvedOutput = path.resolve(outputPath);
|
|
15
|
+
for (const taskId of taskIds) {
|
|
16
|
+
if ((0, trace_evidence_1.expectedLogPath)(allowedLogRoot, taskId) === resolvedOutput) {
|
|
17
|
+
throw new Error('Trace export output cannot replace a source task log');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function taskPrompt(task) {
|
|
22
|
+
return (0, export_stream_1.nullableString)(task.fullPrompt) ?? (0, export_stream_1.nullableString)(task.prompt);
|
|
23
|
+
}
|
|
24
|
+
function streamTask(options) {
|
|
25
|
+
const { writer, taskId, cause, readTask, allowedLogRoot, issues } = options;
|
|
26
|
+
const agentIds = [...cause.agentIds].sort(export_stream_1.compareText);
|
|
27
|
+
if (agentIds.length > 1)
|
|
28
|
+
issues.push(`task:${taskId}:ambiguous_agent`);
|
|
29
|
+
const taskRead = (0, trace_evidence_1.readTraceTask)(taskId, readTask);
|
|
30
|
+
const task = taskRead.task;
|
|
31
|
+
if (taskRead.issue)
|
|
32
|
+
issues.push(taskRead.issue);
|
|
33
|
+
const taskTerminal = task !== null && (0, trace_evidence_1.hasTerminalTaskStatus)(task);
|
|
34
|
+
if (task && !taskTerminal)
|
|
35
|
+
issues.push(`task:${taskId}:task_not_terminal`);
|
|
36
|
+
const promptRef = (0, trace_evidence_1.logicalTaskRef)(taskId, 'prompt');
|
|
37
|
+
const rawOutputRef = (0, trace_evidence_1.logicalTaskRef)(taskId, 'output');
|
|
38
|
+
writer.write({
|
|
39
|
+
record_type: 'task',
|
|
40
|
+
task_id: taskId,
|
|
41
|
+
agent_id: agentIds.length === 1 ? agentIds[0] : null,
|
|
42
|
+
provider: (0, export_stream_1.nullableString)(task?.provider),
|
|
43
|
+
model: (0, export_stream_1.nullableString)(task?.model),
|
|
44
|
+
status: (0, export_stream_1.nullableString)(task?.status),
|
|
45
|
+
created_at: (0, export_stream_1.nullableString)(task?.createdAt),
|
|
46
|
+
updated_at: (0, export_stream_1.nullableString)(task?.updatedAt),
|
|
47
|
+
exit_code: nullableInteger(task?.exitCode),
|
|
48
|
+
prompt_ref: promptRef,
|
|
49
|
+
prompt: task ? taskPrompt(task) : null,
|
|
50
|
+
raw_output_ref: rawOutputRef,
|
|
51
|
+
});
|
|
52
|
+
if (!task) {
|
|
53
|
+
(0, trace_output_1.writeUnavailableOutput)(writer, taskId, rawOutputRef);
|
|
54
|
+
return { bytes: 0 };
|
|
55
|
+
}
|
|
56
|
+
return (0, trace_output_1.streamTaskOutput)({
|
|
57
|
+
writer,
|
|
58
|
+
taskId,
|
|
59
|
+
task,
|
|
60
|
+
allowedLogRoot,
|
|
61
|
+
rawOutputRef,
|
|
62
|
+
taskTerminal,
|
|
63
|
+
issues,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function streamClusterTraceExport(options) {
|
|
67
|
+
const { ledger, clusterId, readTask, allowedLogRoot, outputPath = null } = options;
|
|
68
|
+
const causes = (0, trace_evidence_1.collectTaskCauses)(ledger, clusterId);
|
|
69
|
+
assertOutputDoesNotReplaceTaskLog(outputPath, allowedLogRoot, causes.keys());
|
|
70
|
+
const destination = (0, export_stream_1.createExclusiveDestination)(outputPath, options.stdout ?? process.stdout, 'Trace');
|
|
71
|
+
const writer = (0, export_stream_1.createRecordWriter)(destination);
|
|
72
|
+
let ledgerMessages = 0;
|
|
73
|
+
let taskOutputBytes = 0;
|
|
74
|
+
const issues = [];
|
|
75
|
+
try {
|
|
76
|
+
writer.write({
|
|
77
|
+
record_type: 'header',
|
|
78
|
+
schema_version: TRACE_SCHEMA_VERSION,
|
|
79
|
+
media_type: TRACE_MEDIA_TYPE,
|
|
80
|
+
cluster_id: clusterId,
|
|
81
|
+
chunk_bytes: trace_output_1.TRACE_OUTPUT_CHUNK_BYTES,
|
|
82
|
+
});
|
|
83
|
+
(0, trace_evidence_1.forEachLedgerMessage)(ledger, clusterId, (message) => {
|
|
84
|
+
writer.write({ record_type: 'ledger_message', message });
|
|
85
|
+
ledgerMessages += 1;
|
|
86
|
+
});
|
|
87
|
+
const ordered = [...causes.entries()].sort(([left], [right]) => (0, export_stream_1.compareText)(left, right));
|
|
88
|
+
for (const [taskId, cause] of ordered) {
|
|
89
|
+
taskOutputBytes += streamTask({
|
|
90
|
+
writer,
|
|
91
|
+
taskId,
|
|
92
|
+
cause,
|
|
93
|
+
readTask,
|
|
94
|
+
allowedLogRoot,
|
|
95
|
+
issues,
|
|
96
|
+
}).bytes;
|
|
97
|
+
}
|
|
98
|
+
issues.sort(export_stream_1.compareText);
|
|
99
|
+
writer.finish({
|
|
100
|
+
record_type: 'footer',
|
|
101
|
+
complete: issues.length === 0,
|
|
102
|
+
ledger_messages: ledgerMessages,
|
|
103
|
+
tasks: causes.size,
|
|
104
|
+
task_output_bytes: taskOutputBytes,
|
|
105
|
+
issues,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
destination.close();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
module.exports = {
|
|
113
|
+
TRACE_OUTPUT_CHUNK_BYTES: trace_output_1.TRACE_OUTPUT_CHUNK_BYTES,
|
|
114
|
+
collectTaskCauses: trace_evidence_1.collectTaskCauses,
|
|
115
|
+
expectedLogPath: trace_evidence_1.expectedLogPath,
|
|
116
|
+
hasTerminalTaskStatus: trace_evidence_1.hasTerminalTaskStatus,
|
|
117
|
+
logicalTaskRef: trace_evidence_1.logicalTaskRef,
|
|
118
|
+
readTraceTask: trace_evidence_1.readTraceTask,
|
|
119
|
+
streamClusterTraceExport,
|
|
120
|
+
};
|