@the-open-engine/zeroshot 6.34.0 → 6.34.2
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/cli/index.js +34 -8
- package/cli/json-export.js +77 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/agent/agent-task-executor.js +485 -125
- package/src/ledger.js +364 -31
- package/src/template-resolver.js +5 -0
- package/task-lib/watcher-output-runtime.js +203 -39
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import { StringDecoder } from 'string_decoder';
|
|
2
4
|
import {
|
|
3
5
|
detectProviderFatalError,
|
|
4
6
|
detectProviderStreamingModeError,
|
|
@@ -9,6 +11,9 @@ import { terminateProcess } from './process-termination.js';
|
|
|
9
11
|
|
|
10
12
|
export const COMMAND_CLEANUP_UNINITIALIZED = Symbol('command-cleanup-uninitialized');
|
|
11
13
|
|
|
14
|
+
const MAX_CODEX_CONTROL_RECORD_BYTES = 64 * 1024;
|
|
15
|
+
const MAX_WATCHER_CONTROL_RECORD_BYTES = 1024 * 1024;
|
|
16
|
+
|
|
12
17
|
export function spawnWatcherProvider(command, finalArgs, options) {
|
|
13
18
|
return spawn(command, finalArgs, {
|
|
14
19
|
...options,
|
|
@@ -32,10 +37,162 @@ export async function terminateWatcherProvider(providerProcess, options = {}) {
|
|
|
32
37
|
return result.terminated;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
function createCodexOutputPassthrough({ log, captureProviderSession }) {
|
|
41
|
+
const decoder = new StringDecoder('utf8');
|
|
42
|
+
let atLineStart = true;
|
|
43
|
+
let inspectable = true;
|
|
44
|
+
let inspectionBytes = 0;
|
|
45
|
+
let inspectionParts = [];
|
|
46
|
+
|
|
47
|
+
function inspectPart(part) {
|
|
48
|
+
if (!inspectable || !part) return;
|
|
49
|
+
inspectionBytes += Buffer.byteLength(part);
|
|
50
|
+
if (inspectionBytes > MAX_CODEX_CONTROL_RECORD_BYTES) {
|
|
51
|
+
inspectable = false;
|
|
52
|
+
inspectionParts = [];
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
inspectionParts.push(part);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function finishLine() {
|
|
59
|
+
if (inspectable) captureProviderSession(inspectionParts.join(''));
|
|
60
|
+
atLineStart = true;
|
|
61
|
+
inspectable = true;
|
|
62
|
+
inspectionBytes = 0;
|
|
63
|
+
inspectionParts = [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writeText(text, timestamp) {
|
|
67
|
+
if (!text) return;
|
|
68
|
+
const logged = [];
|
|
69
|
+
let offset = 0;
|
|
70
|
+
while (offset < text.length) {
|
|
71
|
+
if (atLineStart) {
|
|
72
|
+
logged.push(`[${timestamp}]`);
|
|
73
|
+
atLineStart = false;
|
|
74
|
+
}
|
|
75
|
+
const newline = text.indexOf('\n', offset);
|
|
76
|
+
if (newline === -1) {
|
|
77
|
+
const part = text.slice(offset);
|
|
78
|
+
inspectPart(part);
|
|
79
|
+
logged.push(part);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
const part = text.slice(offset, newline);
|
|
83
|
+
inspectPart(part);
|
|
84
|
+
logged.push(part, '\n');
|
|
85
|
+
finishLine();
|
|
86
|
+
offset = newline + 1;
|
|
87
|
+
}
|
|
88
|
+
log(logged.join(''));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
consume(chunk) {
|
|
93
|
+
const text = typeof chunk === 'string' ? chunk : decoder.write(chunk);
|
|
94
|
+
writeText(text, Date.now());
|
|
95
|
+
},
|
|
96
|
+
flush() {
|
|
97
|
+
writeText(decoder.end(), Date.now());
|
|
98
|
+
if (!atLineStart) {
|
|
99
|
+
finishLine();
|
|
100
|
+
log('\n');
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createBoundedLinePassthrough({ log, handleLine, deferRawUntilOverflow = false }) {
|
|
107
|
+
const decoder = new StringDecoder('utf8');
|
|
108
|
+
let atLineStart = true;
|
|
109
|
+
let lineTimestamp = null;
|
|
110
|
+
let byteLength = 0;
|
|
111
|
+
let inspectable = true;
|
|
112
|
+
let inspectionParts = [];
|
|
113
|
+
let digest = createHash('sha256');
|
|
114
|
+
let rawOverflowStreaming = false;
|
|
115
|
+
|
|
116
|
+
function inspectPart(part) {
|
|
117
|
+
if (!part) return;
|
|
118
|
+
byteLength += Buffer.byteLength(part);
|
|
119
|
+
digest.update(part);
|
|
120
|
+
if (!inspectable) {
|
|
121
|
+
if (deferRawUntilOverflow && log) log(part);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (byteLength > MAX_WATCHER_CONTROL_RECORD_BYTES) {
|
|
125
|
+
if (deferRawUntilOverflow && log) {
|
|
126
|
+
log(`[${lineTimestamp}]${inspectionParts.join('')}${part}`);
|
|
127
|
+
rawOverflowStreaming = true;
|
|
128
|
+
}
|
|
129
|
+
inspectable = false;
|
|
130
|
+
inspectionParts = [];
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
inspectionParts.push(part);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function finishLine() {
|
|
137
|
+
const oversized = !inspectable;
|
|
138
|
+
const line = inspectable
|
|
139
|
+
? inspectionParts.join('')
|
|
140
|
+
: `[ZEROSHOT] Provider output record retained in task log but omitted from watcher inspection ` +
|
|
141
|
+
`(byte_length=${byteLength}, sha256=${digest.digest('hex')})`;
|
|
142
|
+
handleLine(line, lineTimestamp || Date.now(), { oversized });
|
|
143
|
+
atLineStart = true;
|
|
144
|
+
lineTimestamp = null;
|
|
145
|
+
byteLength = 0;
|
|
146
|
+
inspectable = true;
|
|
147
|
+
inspectionParts = [];
|
|
148
|
+
digest = createHash('sha256');
|
|
149
|
+
rawOverflowStreaming = false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function appendRaw(logged, ...parts) {
|
|
153
|
+
if (log && !deferRawUntilOverflow) logged.push(...parts);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function writeText(text) {
|
|
157
|
+
if (!text) return;
|
|
158
|
+
const logged = [];
|
|
159
|
+
let offset = 0;
|
|
160
|
+
while (offset < text.length) {
|
|
161
|
+
if (atLineStart) {
|
|
162
|
+
lineTimestamp = Date.now();
|
|
163
|
+
appendRaw(logged, `[${lineTimestamp}]`);
|
|
164
|
+
atLineStart = false;
|
|
165
|
+
}
|
|
166
|
+
const newline = text.indexOf('\n', offset);
|
|
167
|
+
if (newline === -1) {
|
|
168
|
+
const part = text.slice(offset);
|
|
169
|
+
inspectPart(part);
|
|
170
|
+
appendRaw(logged, part);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
const part = text.slice(offset, newline);
|
|
174
|
+
inspectPart(part);
|
|
175
|
+
appendRaw(logged, part, '\n');
|
|
176
|
+
if (deferRawUntilOverflow && rawOverflowStreaming && log) log('\n');
|
|
177
|
+
finishLine();
|
|
178
|
+
offset = newline + 1;
|
|
179
|
+
}
|
|
180
|
+
if (log && logged.length > 0) log(logged.join(''));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
consume(chunk) {
|
|
185
|
+
writeText(typeof chunk === 'string' ? chunk : decoder.write(chunk));
|
|
186
|
+
},
|
|
187
|
+
flush() {
|
|
188
|
+
writeText(decoder.end());
|
|
189
|
+
if (!atLineStart) {
|
|
190
|
+
if (deferRawUntilOverflow && rawOverflowStreaming && log) log('\n');
|
|
191
|
+
finishLine();
|
|
192
|
+
if (log && !deferRawUntilOverflow) log('\n');
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
39
196
|
}
|
|
40
197
|
|
|
41
198
|
export function resolveWatcherCommand(config, commandSpec, fallbackArgs, normalizeProviderName) {
|
|
@@ -189,6 +346,23 @@ export function createWatcherOutputRuntime({
|
|
|
189
346
|
let streamingModeError = null;
|
|
190
347
|
let fatalError = null;
|
|
191
348
|
const captureProviderSession = providerSessionCapture?.captureLine || (() => {});
|
|
349
|
+
const codexOutputPassthrough =
|
|
350
|
+
providerName === 'codex' ? createCodexOutputPassthrough({ log, captureProviderSession }) : null;
|
|
351
|
+
const outputPassthrough = codexOutputPassthrough
|
|
352
|
+
? null
|
|
353
|
+
: createBoundedLinePassthrough({
|
|
354
|
+
log,
|
|
355
|
+
deferRawUntilOverflow: silentJsonMode,
|
|
356
|
+
handleLine: (line, timestamp, { oversized }) =>
|
|
357
|
+
handleOutputLine(line, timestamp, {
|
|
358
|
+
alreadyLogged: !silentJsonMode,
|
|
359
|
+
oversized,
|
|
360
|
+
}),
|
|
361
|
+
});
|
|
362
|
+
const stderrPassthrough = createBoundedLinePassthrough({
|
|
363
|
+
log,
|
|
364
|
+
handleLine: (line, timestamp) => maybeHandleFatalError(line, timestamp),
|
|
365
|
+
});
|
|
192
366
|
|
|
193
367
|
function maybeHandleFatalError(line, timestamp) {
|
|
194
368
|
if (fatalError) return false;
|
|
@@ -217,52 +391,38 @@ export function createWatcherOutputRuntime({
|
|
|
217
391
|
}
|
|
218
392
|
}
|
|
219
393
|
|
|
220
|
-
function handleOutputLine(line, timestamp) {
|
|
394
|
+
function handleOutputLine(line, timestamp, { alreadyLogged = false, oversized = false } = {}) {
|
|
395
|
+
if (silentJsonMode && oversized) {
|
|
396
|
+
fatalError =
|
|
397
|
+
`Provider structured output exceeded the ${MAX_WATCHER_CONTROL_RECORD_BYTES}-byte ` +
|
|
398
|
+
'watcher inspection limit; complete output remains in the task log';
|
|
399
|
+
log(`[${timestamp}][FATAL] ${fatalError}\n`);
|
|
400
|
+
stopProvider(timestamp);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
221
403
|
captureProviderSession(line);
|
|
222
404
|
if (silentJsonMode && !line.trim()) return;
|
|
223
405
|
maybeHandleFatalError(line, timestamp);
|
|
224
406
|
if (captureStreamingError(line, timestamp)) return;
|
|
225
407
|
if (silentJsonMode) {
|
|
226
408
|
maybeCaptureStructuredOutput(line);
|
|
227
|
-
} else {
|
|
409
|
+
} else if (!alreadyLogged) {
|
|
228
410
|
log(`[${timestamp}]${line}\n`);
|
|
229
411
|
}
|
|
230
412
|
}
|
|
231
413
|
|
|
232
|
-
function consumeOutput(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
for (const line of lines) handleOutputLine(line, timestamp);
|
|
236
|
-
return remaining;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function consumeStderr(buffer, chunk) {
|
|
240
|
-
const timestamp = Date.now();
|
|
241
|
-
const { lines, remaining } = splitBufferLines(buffer, chunk.toString());
|
|
242
|
-
for (const line of lines) log(`[${timestamp}]${line}\n`);
|
|
243
|
-
return remaining;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function flushOutput(buffer, timestamp) {
|
|
247
|
-
if (!buffer.trim()) return;
|
|
248
|
-
captureProviderSession(buffer);
|
|
249
|
-
if (!enableRecovery) {
|
|
250
|
-
if (!silentJsonMode) log(`[${timestamp}]${buffer}\n`);
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
maybeHandleFatalError(buffer, timestamp);
|
|
254
|
-
if (captureStreamingError(buffer, timestamp)) return;
|
|
255
|
-
if (silentJsonMode) {
|
|
256
|
-
maybeCaptureStructuredOutput(buffer);
|
|
414
|
+
function consumeOutput(_buffer, chunk) {
|
|
415
|
+
if (codexOutputPassthrough) {
|
|
416
|
+
codexOutputPassthrough.consume(chunk);
|
|
257
417
|
} else {
|
|
258
|
-
|
|
418
|
+
outputPassthrough.consume(chunk);
|
|
259
419
|
}
|
|
420
|
+
return '';
|
|
260
421
|
}
|
|
261
422
|
|
|
262
|
-
function
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
log(`[${timestamp}]${buffer}\n`);
|
|
423
|
+
function consumeStderr(_buffer, chunk) {
|
|
424
|
+
stderrPassthrough.consume(chunk);
|
|
425
|
+
return '';
|
|
266
426
|
}
|
|
267
427
|
|
|
268
428
|
function attemptRecovery(code, timestamp) {
|
|
@@ -282,10 +442,14 @@ export function createWatcherOutputRuntime({
|
|
|
282
442
|
return recovered;
|
|
283
443
|
}
|
|
284
444
|
|
|
285
|
-
function complete({ code, signal,
|
|
445
|
+
function complete({ code, signal, stderrBuffer = null }) {
|
|
286
446
|
const timestamp = Date.now();
|
|
287
|
-
|
|
288
|
-
|
|
447
|
+
if (codexOutputPassthrough) {
|
|
448
|
+
codexOutputPassthrough.flush();
|
|
449
|
+
} else {
|
|
450
|
+
outputPassthrough.flush();
|
|
451
|
+
}
|
|
452
|
+
if (stderrBuffer !== null) stderrPassthrough.flush();
|
|
289
453
|
const recovered = attemptRecovery(code, timestamp);
|
|
290
454
|
const sessionIdentityError = providerSessionCapture?.getCompletionError() || null;
|
|
291
455
|
if (silentJsonMode && finalResultJson) log(`${finalResultJson}\n`);
|