@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
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const { spawn, spawnSync } = require('child_process');
|
|
14
|
+
const { createHash } = require('crypto');
|
|
14
15
|
const path = require('path');
|
|
15
16
|
const fs = require('fs');
|
|
17
|
+
const { StringDecoder } = require('string_decoder');
|
|
16
18
|
const { getNestedExecutionRegistry, TaskExecutionHandle } = require('./task-execution-handle');
|
|
17
19
|
const os = require('os');
|
|
18
20
|
const { parseProviderChunk, getProvider } = require('../providers');
|
|
@@ -57,6 +59,13 @@ const {
|
|
|
57
59
|
isStructuredOutputInvalidError,
|
|
58
60
|
} = require('./structured-output-error');
|
|
59
61
|
const TASK_TERMINAL_STATUSES = new Set(['completed', 'failed', 'killed', 'stale']);
|
|
62
|
+
const MAX_CONTROL_PLANE_RECORD_BYTES = 1024 * 1024;
|
|
63
|
+
const MAX_CONTROL_PLANE_OUTPUT_BYTES = 2 * 1024 * 1024;
|
|
64
|
+
const MAX_CONTROL_PLANE_OUTPUT_RECORDS = 1024;
|
|
65
|
+
const MAX_LIVE_OUTPUT_BYTES = 512 * 1024;
|
|
66
|
+
const MAX_LIVE_OUTPUT_RECORDS = 512;
|
|
67
|
+
const CONTROL_PLANE_OMISSION_MARKER_BYTES = 1024;
|
|
68
|
+
const LOG_READ_CHUNK_BYTES = 64 * 1024;
|
|
60
69
|
function runCommandWithTimeout(command, args, options = {}, callback = null) {
|
|
61
70
|
const timeout = options.timeout ?? 30000;
|
|
62
71
|
if (timeout <= 0) {
|
|
@@ -1218,16 +1227,194 @@ async function waitForTaskReady(agent, taskId, maxRetries = 10, delayMs = 200) {
|
|
|
1218
1227
|
const MAX_STATUS_FAILURES = 30;
|
|
1219
1228
|
|
|
1220
1229
|
function createLogFollowState() {
|
|
1221
|
-
|
|
1222
|
-
|
|
1230
|
+
const state = {
|
|
1231
|
+
controlPlaneOutput: createControlPlaneOutputState(),
|
|
1223
1232
|
logFilePath: null,
|
|
1224
1233
|
lastSize: 0,
|
|
1225
1234
|
pollInterval: null,
|
|
1226
1235
|
statusCheckInterval: null,
|
|
1227
1236
|
resolved: false,
|
|
1228
|
-
lineBuffer:
|
|
1237
|
+
lineBuffer: createLogRecordBuffer(),
|
|
1238
|
+
logDecoder: new StringDecoder('utf8'),
|
|
1229
1239
|
consecutiveExecFailures: 0,
|
|
1230
1240
|
};
|
|
1241
|
+
defineControlPlaneOutputAccessor(state, 'output');
|
|
1242
|
+
return state;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function createControlPlaneOutputState(options = {}) {
|
|
1246
|
+
const maxBytes = options.maxBytes || MAX_CONTROL_PLANE_OUTPUT_BYTES;
|
|
1247
|
+
const maxRecords = options.maxRecords || MAX_CONTROL_PLANE_OUTPUT_RECORDS;
|
|
1248
|
+
const liveByteLimit = options.liveByteLimit || MAX_LIVE_OUTPUT_BYTES;
|
|
1249
|
+
const liveRecordLimit = options.liveRecordLimit || MAX_LIVE_OUTPUT_RECORDS;
|
|
1250
|
+
if (maxBytes <= CONTROL_PLANE_OMISSION_MARKER_BYTES || maxRecords < 1) {
|
|
1251
|
+
throw new Error('Control-plane output bounds must retain space for at least one record');
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
records: [],
|
|
1255
|
+
head: 0,
|
|
1256
|
+
byteLength: 0,
|
|
1257
|
+
maxBytes,
|
|
1258
|
+
maxRecords,
|
|
1259
|
+
liveByteLimit,
|
|
1260
|
+
liveRecordLimit,
|
|
1261
|
+
liveBytes: 0,
|
|
1262
|
+
liveRecords: 0,
|
|
1263
|
+
nextSequence: 0,
|
|
1264
|
+
lastLiveSequence: -1,
|
|
1265
|
+
liveSuppressed: false,
|
|
1266
|
+
terminalFlushed: false,
|
|
1267
|
+
omittedBytes: 0,
|
|
1268
|
+
omittedRecords: 0,
|
|
1269
|
+
omittedDigest: createHash('sha256'),
|
|
1270
|
+
prefixOmitted: false,
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
function defineControlPlaneOutputAccessor(state, property) {
|
|
1275
|
+
Object.defineProperty(state, property, {
|
|
1276
|
+
configurable: true,
|
|
1277
|
+
enumerable: true,
|
|
1278
|
+
get: () => snapshotControlPlaneOutput(state.controlPlaneOutput),
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function ensureControlPlaneOutputState(state = {}) {
|
|
1283
|
+
if (state.controlPlaneOutput) return state;
|
|
1284
|
+
let existingOutput = '';
|
|
1285
|
+
if (typeof state.output === 'string') {
|
|
1286
|
+
existingOutput = state.output;
|
|
1287
|
+
} else if (typeof state.fullOutput === 'string') {
|
|
1288
|
+
existingOutput = state.fullOutput;
|
|
1289
|
+
}
|
|
1290
|
+
state.controlPlaneOutput = createControlPlaneOutputState();
|
|
1291
|
+
if (Object.hasOwn(state, 'output')) delete state.output;
|
|
1292
|
+
if (Object.hasOwn(state, 'fullOutput')) delete state.fullOutput;
|
|
1293
|
+
defineControlPlaneOutputAccessor(state, 'output');
|
|
1294
|
+
if (existingOutput) {
|
|
1295
|
+
replayCompleteLogContent(existingOutput, (content) =>
|
|
1296
|
+
appendControlPlaneRecord(state.controlPlaneOutput, {
|
|
1297
|
+
content,
|
|
1298
|
+
timestamp: Date.now(),
|
|
1299
|
+
type: isValidJsonLine(content) ? 'json' : 'text',
|
|
1300
|
+
})
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
return state;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function retainedControlPlaneRecords(outputState) {
|
|
1307
|
+
return outputState.records.slice(outputState.head);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function compactControlPlaneRecords(outputState) {
|
|
1311
|
+
if (outputState.head >= 64 && outputState.head * 2 >= outputState.records.length) {
|
|
1312
|
+
outputState.records = outputState.records.slice(outputState.head);
|
|
1313
|
+
outputState.head = 0;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function omitOldestControlPlaneRecord(outputState) {
|
|
1318
|
+
const record = outputState.records[outputState.head++];
|
|
1319
|
+
outputState.byteLength -= record.byteLength;
|
|
1320
|
+
outputState.omittedBytes += record.byteLength;
|
|
1321
|
+
outputState.omittedRecords++;
|
|
1322
|
+
outputState.omittedDigest.update(record.text);
|
|
1323
|
+
compactControlPlaneRecords(outputState);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function appendControlPlaneRecord(outputState, { content, timestamp, type }) {
|
|
1327
|
+
const text = `${content}\n`;
|
|
1328
|
+
const record = {
|
|
1329
|
+
sequence: outputState.nextSequence++,
|
|
1330
|
+
content,
|
|
1331
|
+
timestamp,
|
|
1332
|
+
type,
|
|
1333
|
+
text,
|
|
1334
|
+
byteLength: Buffer.byteLength(text),
|
|
1335
|
+
};
|
|
1336
|
+
outputState.records.push(record);
|
|
1337
|
+
outputState.byteLength += record.byteLength;
|
|
1338
|
+
|
|
1339
|
+
const payloadLimit = outputState.maxBytes - CONTROL_PLANE_OMISSION_MARKER_BYTES;
|
|
1340
|
+
while (
|
|
1341
|
+
outputState.byteLength > payloadLimit ||
|
|
1342
|
+
outputState.records.length - outputState.head > outputState.maxRecords
|
|
1343
|
+
) {
|
|
1344
|
+
omitOldestControlPlaneRecord(outputState);
|
|
1345
|
+
}
|
|
1346
|
+
return record;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function controlPlaneOmissionRecord(outputState) {
|
|
1350
|
+
if (!outputState.prefixOmitted && outputState.omittedRecords === 0) return null;
|
|
1351
|
+
const digest = outputState.omittedRecords
|
|
1352
|
+
? `, sha256=${outputState.omittedDigest.copy().digest('hex')}`
|
|
1353
|
+
: '';
|
|
1354
|
+
const known = outputState.omittedRecords
|
|
1355
|
+
? `records=${outputState.omittedRecords}, byte_length=${outputState.omittedBytes}${digest}`
|
|
1356
|
+
: 'byte_length=unknown';
|
|
1357
|
+
const content =
|
|
1358
|
+
`[ZEROSHOT] Earlier provider output omitted from the bounded control-plane tail (${known}). ` +
|
|
1359
|
+
'Complete output remains in the task log.';
|
|
1360
|
+
return {
|
|
1361
|
+
content,
|
|
1362
|
+
timestamp: Date.now(),
|
|
1363
|
+
type: 'text',
|
|
1364
|
+
text: `${content}\n`,
|
|
1365
|
+
byteLength: Buffer.byteLength(`${content}\n`),
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function snapshotControlPlaneOutput(outputState) {
|
|
1370
|
+
const omission = controlPlaneOmissionRecord(outputState);
|
|
1371
|
+
const records = retainedControlPlaneRecords(outputState);
|
|
1372
|
+
return `${omission ? omission.text : ''}${records.map((record) => record.text).join('')}`;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
function resetControlPlaneTail(outputState, { prefixOmitted = false } = {}) {
|
|
1376
|
+
outputState.records = [];
|
|
1377
|
+
outputState.head = 0;
|
|
1378
|
+
outputState.byteLength = 0;
|
|
1379
|
+
outputState.omittedBytes = 0;
|
|
1380
|
+
outputState.omittedRecords = 0;
|
|
1381
|
+
outputState.omittedDigest = createHash('sha256');
|
|
1382
|
+
outputState.prefixOmitted = prefixOmitted;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function publishLiveControlPlaneRecord(outputState, record, publish) {
|
|
1386
|
+
if (
|
|
1387
|
+
outputState.liveSuppressed ||
|
|
1388
|
+
outputState.liveBytes + record.byteLength > outputState.liveByteLimit ||
|
|
1389
|
+
outputState.liveRecords + 1 > outputState.liveRecordLimit
|
|
1390
|
+
) {
|
|
1391
|
+
outputState.liveSuppressed = true;
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
publish(record);
|
|
1395
|
+
outputState.liveBytes += record.byteLength;
|
|
1396
|
+
outputState.liveRecords++;
|
|
1397
|
+
outputState.lastLiveSequence = record.sequence;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
function flushTerminalControlPlaneOutput(outputState, publish) {
|
|
1401
|
+
if (outputState.terminalFlushed) return;
|
|
1402
|
+
outputState.terminalFlushed = true;
|
|
1403
|
+
if (outputState.liveSuppressed) {
|
|
1404
|
+
const omission = controlPlaneOmissionRecord(outputState);
|
|
1405
|
+
if (omission) publish(omission);
|
|
1406
|
+
}
|
|
1407
|
+
for (const record of retainedControlPlaneRecords(outputState)) {
|
|
1408
|
+
if (record.sequence > outputState.lastLiveSequence) publish(record);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
function createLogRecordBuffer() {
|
|
1413
|
+
return {
|
|
1414
|
+
byteLength: 0,
|
|
1415
|
+
fragments: [],
|
|
1416
|
+
oversized: null,
|
|
1417
|
+
};
|
|
1231
1418
|
}
|
|
1232
1419
|
|
|
1233
1420
|
function lookupLogFilePath(ctPath, taskId) {
|
|
@@ -1276,31 +1463,16 @@ function isValidJsonLine(content) {
|
|
|
1276
1463
|
}
|
|
1277
1464
|
}
|
|
1278
1465
|
|
|
1279
|
-
function
|
|
1280
|
-
if (!line.trim()) return;
|
|
1281
|
-
|
|
1282
|
-
const { timestamp, content } = parseTimestampedLine(line);
|
|
1283
|
-
if (shouldSkipLogLine(content)) {
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
const isValidJson = isValidJsonLine(content);
|
|
1288
|
-
state.output += content + '\n';
|
|
1289
|
-
|
|
1290
|
-
if (!state.nested) {
|
|
1291
|
-
agent.lastOutputTime = Date.now();
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1466
|
+
function publishAgentOutputRecord(agent, providerName, record) {
|
|
1294
1467
|
agent._publish({
|
|
1295
1468
|
topic: 'AGENT_OUTPUT',
|
|
1296
1469
|
receiver: 'broadcast',
|
|
1297
1470
|
metadata: buildRawLogOnlyMetadata(),
|
|
1298
|
-
timestamp,
|
|
1471
|
+
timestamp: record.timestamp,
|
|
1299
1472
|
content: {
|
|
1300
|
-
text: content,
|
|
1301
1473
|
data: {
|
|
1302
|
-
type:
|
|
1303
|
-
line: content,
|
|
1474
|
+
type: record.type,
|
|
1475
|
+
line: record.content,
|
|
1304
1476
|
agent: agent.id,
|
|
1305
1477
|
role: agent.role,
|
|
1306
1478
|
iteration: agent.iteration,
|
|
@@ -1310,15 +1482,114 @@ function broadcastAgentLine({ agent, providerName, state, line }) {
|
|
|
1310
1482
|
});
|
|
1311
1483
|
}
|
|
1312
1484
|
|
|
1313
|
-
function
|
|
1314
|
-
|
|
1315
|
-
|
|
1485
|
+
function broadcastAgentLine({ agent, providerName, state, line }) {
|
|
1486
|
+
const followerState = ensureControlPlaneOutputState(state);
|
|
1487
|
+
if (!line.trim()) return;
|
|
1488
|
+
|
|
1489
|
+
const { timestamp, content } = parseTimestampedLine(line);
|
|
1490
|
+
if (shouldSkipLogLine(content)) {
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const isValidJson = isValidJsonLine(content);
|
|
1495
|
+
const record = appendControlPlaneRecord(followerState.controlPlaneOutput, {
|
|
1496
|
+
content,
|
|
1497
|
+
timestamp,
|
|
1498
|
+
type: isValidJson ? 'json' : 'text',
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
if (!followerState.nested) {
|
|
1502
|
+
agent.lastOutputTime = Date.now();
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
publishLiveControlPlaneRecord(followerState.controlPlaneOutput, record, (item) =>
|
|
1506
|
+
publishAgentOutputRecord(agent, providerName, item)
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
function flushAgentOutput(agent, providerName, state) {
|
|
1511
|
+
const followerState = ensureControlPlaneOutputState(state);
|
|
1512
|
+
flushTerminalControlPlaneOutput(followerState.controlPlaneOutput, (record) =>
|
|
1513
|
+
publishAgentOutputRecord(agent, providerName, record)
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function appendLogRecordFragment(buffer, fragment) {
|
|
1518
|
+
const fragmentBytes = Buffer.byteLength(fragment);
|
|
1519
|
+
buffer.byteLength += fragmentBytes;
|
|
1520
|
+
if (buffer.oversized) {
|
|
1521
|
+
buffer.oversized.digest.update(fragment);
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
if (buffer.byteLength <= MAX_CONTROL_PLANE_RECORD_BYTES) {
|
|
1525
|
+
buffer.fragments.push(fragment);
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
const retainedPrefix = buffer.fragments.join('');
|
|
1530
|
+
const prefixProbe = `${retainedPrefix}${fragment.slice(0, Math.max(0, 32 - retainedPrefix.length))}`;
|
|
1531
|
+
const timestampPrefix = prefixProbe.match(/^\[\d{13}\]/)?.[0] || '';
|
|
1532
|
+
const digest = createHash('sha256');
|
|
1533
|
+
for (const retained of buffer.fragments) digest.update(retained);
|
|
1534
|
+
digest.update(fragment);
|
|
1535
|
+
buffer.fragments = [];
|
|
1536
|
+
buffer.oversized = { digest, timestampPrefix };
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function completeLogRecord(buffer, onLine, skipEmpty = false) {
|
|
1540
|
+
let line;
|
|
1541
|
+
if (buffer.oversized) {
|
|
1542
|
+
const digest = buffer.oversized.digest.digest('hex');
|
|
1543
|
+
line =
|
|
1544
|
+
`${buffer.oversized.timestampPrefix}[ZEROSHOT] Provider output record retained in task log ` +
|
|
1545
|
+
`but omitted from the control plane (byte_length=${buffer.byteLength}, sha256=${digest})`;
|
|
1546
|
+
} else {
|
|
1547
|
+
line = buffer.fragments.join('');
|
|
1548
|
+
}
|
|
1549
|
+
buffer.byteLength = 0;
|
|
1550
|
+
buffer.fragments = [];
|
|
1551
|
+
buffer.oversized = null;
|
|
1552
|
+
if (!skipEmpty || line.trim()) onLine(line);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
function appendContentToBuffer(state, content, onLine, skipEmpty = false) {
|
|
1556
|
+
let offset = 0;
|
|
1557
|
+
while (offset < content.length) {
|
|
1558
|
+
const newline = content.indexOf('\n', offset);
|
|
1559
|
+
if (newline === -1) {
|
|
1560
|
+
appendLogRecordFragment(state.lineBuffer, content.slice(offset));
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
appendLogRecordFragment(state.lineBuffer, content.slice(offset, newline));
|
|
1564
|
+
completeLogRecord(state.lineBuffer, onLine, skipEmpty);
|
|
1565
|
+
offset = newline + 1;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1316
1568
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1569
|
+
function replayCompleteLogContent(content, onLine) {
|
|
1570
|
+
const replayState = { lineBuffer: createLogRecordBuffer() };
|
|
1571
|
+
appendContentToBuffer(replayState, content, onLine, true);
|
|
1572
|
+
if (replayState.lineBuffer.byteLength > 0) {
|
|
1573
|
+
completeLogRecord(replayState.lineBuffer, onLine, true);
|
|
1319
1574
|
}
|
|
1575
|
+
}
|
|
1320
1576
|
|
|
1321
|
-
|
|
1577
|
+
function readLogFileDelta({ fsModule, logFilePath, state, currentSize, onNewContent }) {
|
|
1578
|
+
const fd = fsModule.openSync(logFilePath, 'r');
|
|
1579
|
+
try {
|
|
1580
|
+
let offset = state.lastSize;
|
|
1581
|
+
const buffer = Buffer.allocUnsafe(LOG_READ_CHUNK_BYTES);
|
|
1582
|
+
while (offset < currentSize) {
|
|
1583
|
+
const requested = Math.min(buffer.length, currentSize - offset);
|
|
1584
|
+
const bytesRead = fsModule.readSync(fd, buffer, 0, requested, offset);
|
|
1585
|
+
if (bytesRead === 0) break;
|
|
1586
|
+
onNewContent(state.logDecoder.write(buffer.subarray(0, bytesRead)));
|
|
1587
|
+
offset += bytesRead;
|
|
1588
|
+
}
|
|
1589
|
+
state.lastSize = offset;
|
|
1590
|
+
} finally {
|
|
1591
|
+
fsModule.closeSync(fd);
|
|
1592
|
+
}
|
|
1322
1593
|
}
|
|
1323
1594
|
|
|
1324
1595
|
function pollLogFileForUpdates({ agent, fsModule, ctPath, taskId, state, onNewContent }) {
|
|
@@ -1340,13 +1611,13 @@ function pollLogFileForUpdates({ agent, fsModule, ctPath, taskId, state, onNewCo
|
|
|
1340
1611
|
const currentSize = stats.size;
|
|
1341
1612
|
|
|
1342
1613
|
if (currentSize > state.lastSize) {
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1614
|
+
readLogFileDelta({
|
|
1615
|
+
fsModule,
|
|
1616
|
+
logFilePath: state.logFilePath,
|
|
1617
|
+
state,
|
|
1618
|
+
currentSize,
|
|
1619
|
+
onNewContent,
|
|
1620
|
+
});
|
|
1350
1621
|
}
|
|
1351
1622
|
} catch (err) {
|
|
1352
1623
|
const error = /** @type {Error} */ (err);
|
|
@@ -1536,7 +1807,29 @@ function finalizeLogFollow(agent, state) {
|
|
|
1536
1807
|
}
|
|
1537
1808
|
}
|
|
1538
1809
|
|
|
1539
|
-
function
|
|
1810
|
+
function settleHostStatusFailure({ agent, providerName, state, resolve, text, data, error }) {
|
|
1811
|
+
if (state.resolved) return;
|
|
1812
|
+
state.resolved = true;
|
|
1813
|
+
finalizeLogFollow(agent, state);
|
|
1814
|
+
flushAgentOutput(agent, providerName, state);
|
|
1815
|
+
agent._publish({
|
|
1816
|
+
topic: 'AGENT_ERROR',
|
|
1817
|
+
receiver: 'broadcast',
|
|
1818
|
+
content: { text, data },
|
|
1819
|
+
});
|
|
1820
|
+
resolve({ success: false, output: state.output, error });
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
function handleStatusExecError({
|
|
1824
|
+
agent,
|
|
1825
|
+
providerName,
|
|
1826
|
+
state,
|
|
1827
|
+
ctPath,
|
|
1828
|
+
taskId,
|
|
1829
|
+
error,
|
|
1830
|
+
stderr,
|
|
1831
|
+
resolve,
|
|
1832
|
+
}) {
|
|
1540
1833
|
if (!error) {
|
|
1541
1834
|
return false;
|
|
1542
1835
|
}
|
|
@@ -1561,30 +1854,20 @@ function handleStatusExecError({ agent, state, ctPath, taskId, error, stderr, re
|
|
|
1561
1854
|
`[Agent ${agent.id}] ⚠️ Task ${taskId} not found - will restart to ensure completion`
|
|
1562
1855
|
);
|
|
1563
1856
|
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
},
|
|
1579
|
-
},
|
|
1580
|
-
});
|
|
1581
|
-
|
|
1582
|
-
resolve({
|
|
1583
|
-
success: false,
|
|
1584
|
-
output: state.output,
|
|
1585
|
-
error: `Task not found - restarting for safety`,
|
|
1586
|
-
});
|
|
1587
|
-
}
|
|
1857
|
+
settleHostStatusFailure({
|
|
1858
|
+
agent,
|
|
1859
|
+
providerName,
|
|
1860
|
+
state,
|
|
1861
|
+
resolve,
|
|
1862
|
+
text: `Task ${taskId} not found - restarting for safety`,
|
|
1863
|
+
data: {
|
|
1864
|
+
taskId,
|
|
1865
|
+
error: 'task_not_found',
|
|
1866
|
+
role: agent.role,
|
|
1867
|
+
iteration: agent.iteration,
|
|
1868
|
+
},
|
|
1869
|
+
error: 'Task not found - restarting for safety',
|
|
1870
|
+
});
|
|
1588
1871
|
|
|
1589
1872
|
return true;
|
|
1590
1873
|
}
|
|
@@ -1602,31 +1885,21 @@ function handleStatusExecError({ agent, state, ctPath, taskId, error, stderr, re
|
|
|
1602
1885
|
console.error(` Stderr: ${stderr || 'none'}`);
|
|
1603
1886
|
console.error(` This may indicate zeroshot is not in PATH or task storage is corrupted.`);
|
|
1604
1887
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
},
|
|
1621
|
-
},
|
|
1622
|
-
});
|
|
1623
|
-
|
|
1624
|
-
resolve({
|
|
1625
|
-
success: false,
|
|
1626
|
-
output: state.output,
|
|
1627
|
-
error: `Status polling failed ${MAX_STATUS_FAILURES} times - task may not exist`,
|
|
1628
|
-
});
|
|
1629
|
-
}
|
|
1888
|
+
settleHostStatusFailure({
|
|
1889
|
+
agent,
|
|
1890
|
+
providerName,
|
|
1891
|
+
state,
|
|
1892
|
+
resolve,
|
|
1893
|
+
text: `Task ${taskId} polling failed after ${MAX_STATUS_FAILURES} consecutive failures`,
|
|
1894
|
+
data: {
|
|
1895
|
+
taskId,
|
|
1896
|
+
error: 'polling_timeout',
|
|
1897
|
+
attempts: state.consecutiveExecFailures,
|
|
1898
|
+
role: agent.role,
|
|
1899
|
+
iteration: agent.iteration,
|
|
1900
|
+
},
|
|
1901
|
+
error: `Status polling failed ${MAX_STATUS_FAILURES} times - task may not exist`,
|
|
1902
|
+
});
|
|
1630
1903
|
|
|
1631
1904
|
return true;
|
|
1632
1905
|
}
|
|
@@ -1681,6 +1954,7 @@ function handleStatusCompletion({
|
|
|
1681
1954
|
state.resolved = true;
|
|
1682
1955
|
|
|
1683
1956
|
finalizeLogFollow(agent, state);
|
|
1957
|
+
flushAgentOutput(agent, providerName, state);
|
|
1684
1958
|
|
|
1685
1959
|
buildCompletionResult({
|
|
1686
1960
|
agent,
|
|
@@ -1703,6 +1977,7 @@ function buildKillHandler({ agent, taskId, state, providerName, resolve }) {
|
|
|
1703
1977
|
if (state.resolved) return;
|
|
1704
1978
|
state.resolved = true;
|
|
1705
1979
|
finalizeLogFollow(agent, state);
|
|
1980
|
+
flushAgentOutput(agent, providerName, state);
|
|
1706
1981
|
if (!state.nested) {
|
|
1707
1982
|
agent._stopLivenessCheck();
|
|
1708
1983
|
}
|
|
@@ -1762,7 +2037,18 @@ function createLogFollower({
|
|
|
1762
2037
|
(error, stdout, stderr) => {
|
|
1763
2038
|
if (state.resolved) return;
|
|
1764
2039
|
|
|
1765
|
-
if (
|
|
2040
|
+
if (
|
|
2041
|
+
handleStatusExecError({
|
|
2042
|
+
agent,
|
|
2043
|
+
providerName,
|
|
2044
|
+
state,
|
|
2045
|
+
ctPath,
|
|
2046
|
+
taskId,
|
|
2047
|
+
error,
|
|
2048
|
+
stderr,
|
|
2049
|
+
resolve,
|
|
2050
|
+
})
|
|
2051
|
+
) {
|
|
1766
2052
|
return;
|
|
1767
2053
|
}
|
|
1768
2054
|
|
|
@@ -2203,7 +2489,7 @@ async function spawnClaudeTaskIsolatedExecution(agent, context, options = {}) {
|
|
|
2203
2489
|
* - Result: 10-20% overall latency reduction
|
|
2204
2490
|
*/
|
|
2205
2491
|
function createIsolatedLogState(skipStructuredResultCheck = false, nested = false) {
|
|
2206
|
-
|
|
2492
|
+
const state = {
|
|
2207
2493
|
taskExited: false,
|
|
2208
2494
|
resolved: false,
|
|
2209
2495
|
terminationPromise: null,
|
|
@@ -2211,14 +2497,20 @@ function createIsolatedLogState(skipStructuredResultCheck = false, nested = fals
|
|
|
2211
2497
|
durableTaskStatus: null,
|
|
2212
2498
|
lifecycleHandle: null,
|
|
2213
2499
|
logFilePath: null,
|
|
2214
|
-
|
|
2500
|
+
controlPlaneOutput: createControlPlaneOutputState(),
|
|
2215
2501
|
tailProcess: null,
|
|
2502
|
+
rawBytesSeen: 0,
|
|
2503
|
+
ignoreTailOutput: false,
|
|
2216
2504
|
statusCheckInterval: null,
|
|
2217
2505
|
timeoutTimer: null,
|
|
2218
|
-
lineBuffer:
|
|
2506
|
+
lineBuffer: createLogRecordBuffer(),
|
|
2507
|
+
tailDecoder: new StringDecoder('utf8'),
|
|
2219
2508
|
skipStructuredResultCheck,
|
|
2220
2509
|
nested,
|
|
2221
2510
|
};
|
|
2511
|
+
defineControlPlaneOutputAccessor(state, 'output');
|
|
2512
|
+
defineControlPlaneOutputAccessor(state, 'fullOutput');
|
|
2513
|
+
return state;
|
|
2222
2514
|
}
|
|
2223
2515
|
|
|
2224
2516
|
function buildIsolatedCleanup(state) {
|
|
@@ -2341,6 +2633,41 @@ async function resolveIsolatedLogFilePath(manager, clusterId, taskId, state) {
|
|
|
2341
2633
|
return state.logFilePath;
|
|
2342
2634
|
}
|
|
2343
2635
|
|
|
2636
|
+
async function captureIsolatedFinalOutputTail(manager, clusterId, logFilePath, state) {
|
|
2637
|
+
stopIsolatedTailForSettlement(state);
|
|
2638
|
+
const sizeResult = await manager.execInContainer(clusterId, [
|
|
2639
|
+
'sh',
|
|
2640
|
+
'-c',
|
|
2641
|
+
`wc -c < "${logFilePath}" 2>/dev/null || echo 0`,
|
|
2642
|
+
]);
|
|
2643
|
+
const fileSize = Number.parseInt(sizeResult.stdout.trim(), 10);
|
|
2644
|
+
const missingBytes = Number.isSafeInteger(fileSize)
|
|
2645
|
+
? Math.max(0, fileSize - state.rawBytesSeen)
|
|
2646
|
+
: 0;
|
|
2647
|
+
|
|
2648
|
+
if (missingBytes > 0) {
|
|
2649
|
+
const readBytes = Math.min(missingBytes, MAX_CONTROL_PLANE_OUTPUT_BYTES);
|
|
2650
|
+
const finalReadResult = await manager.execInContainer(clusterId, [
|
|
2651
|
+
'sh',
|
|
2652
|
+
'-c',
|
|
2653
|
+
`tail -c ${readBytes} "${logFilePath}" 2>/dev/null || echo ""`,
|
|
2654
|
+
]);
|
|
2655
|
+
if (finalReadResult.code === 0 && finalReadResult.stdout) {
|
|
2656
|
+
if (missingBytes > readBytes) {
|
|
2657
|
+
resetControlPlaneTail(state.controlPlaneOutput, { prefixOmitted: true });
|
|
2658
|
+
state.lineBuffer = createLogRecordBuffer();
|
|
2659
|
+
}
|
|
2660
|
+
appendIsolatedContent(state, finalReadResult.stdout, (line) =>
|
|
2661
|
+
retainIsolatedLine(state, line)
|
|
2662
|
+
);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
if (state.lineBuffer.byteLength > 0) {
|
|
2667
|
+
completeLogRecord(state.lineBuffer, (line) => retainIsolatedLine(state, line), true);
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2344
2671
|
function settleIsolatedTerminalStatus({
|
|
2345
2672
|
agent,
|
|
2346
2673
|
manager,
|
|
@@ -2353,7 +2680,6 @@ function settleIsolatedTerminalStatus({
|
|
|
2353
2680
|
cleanup,
|
|
2354
2681
|
resolve,
|
|
2355
2682
|
reject,
|
|
2356
|
-
onLine,
|
|
2357
2683
|
}) {
|
|
2358
2684
|
if (state.resolved) return Promise.resolve();
|
|
2359
2685
|
if (state.terminalSettlementPromise) return state.terminalSettlementPromise;
|
|
@@ -2367,19 +2693,9 @@ function settleIsolatedTerminalStatus({
|
|
|
2367
2693
|
const logFilePath = await resolveIsolatedLogFilePath(manager, clusterId, taskId, state);
|
|
2368
2694
|
await new Promise((settle) => setTimeout(settle, 200));
|
|
2369
2695
|
if (state.resolved) return;
|
|
2370
|
-
|
|
2371
|
-
'sh',
|
|
2372
|
-
'-c',
|
|
2373
|
-
`cat "${logFilePath}" 2>/dev/null || echo ""`,
|
|
2374
|
-
]);
|
|
2696
|
+
await captureIsolatedFinalOutputTail(manager, clusterId, logFilePath, state);
|
|
2375
2697
|
if (state.resolved) return;
|
|
2376
|
-
|
|
2377
|
-
if (finalReadResult.code === 0 && finalReadResult.stdout) {
|
|
2378
|
-
state.fullOutput = finalReadResult.stdout;
|
|
2379
|
-
for (const line of state.fullOutput.split('\n')) {
|
|
2380
|
-
if (line.trim()) onLine(line);
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2698
|
+
flushIsolatedOutput(agent, providerName, taskId, state);
|
|
2383
2699
|
|
|
2384
2700
|
const vertexModelError =
|
|
2385
2701
|
providerName === 'claude'
|
|
@@ -2463,9 +2779,9 @@ function buildIsolatedLifecycleHandle({
|
|
|
2463
2779
|
cleanup,
|
|
2464
2780
|
resolve,
|
|
2465
2781
|
reject,
|
|
2466
|
-
onLine,
|
|
2467
2782
|
}) {
|
|
2468
|
-
const settleCancellation = (reason, details) =>
|
|
2783
|
+
const settleCancellation = (reason, details) => {
|
|
2784
|
+
flushIsolatedOutput(agent, providerName, taskId, state);
|
|
2469
2785
|
settleIsolatedFollower({
|
|
2470
2786
|
agent,
|
|
2471
2787
|
state,
|
|
@@ -2480,6 +2796,7 @@ function buildIsolatedLifecycleHandle({
|
|
|
2480
2796
|
tokenUsage: extractTokenUsage(state.fullOutput, providerName),
|
|
2481
2797
|
},
|
|
2482
2798
|
});
|
|
2799
|
+
};
|
|
2483
2800
|
const terminate = (reason = 'Task killed', details = {}) => {
|
|
2484
2801
|
if (state.durableTaskTerminal) {
|
|
2485
2802
|
if (state.nested) settleCancellation(reason, details);
|
|
@@ -2511,7 +2828,6 @@ function buildIsolatedLifecycleHandle({
|
|
|
2511
2828
|
cleanup,
|
|
2512
2829
|
resolve,
|
|
2513
2830
|
reject,
|
|
2514
|
-
onLine,
|
|
2515
2831
|
});
|
|
2516
2832
|
return termination;
|
|
2517
2833
|
}
|
|
@@ -2539,11 +2855,14 @@ function buildIsolatedLifecycleHandle({
|
|
|
2539
2855
|
};
|
|
2540
2856
|
}
|
|
2541
2857
|
|
|
2542
|
-
function
|
|
2858
|
+
function parseIsolatedLogLine(line) {
|
|
2543
2859
|
const timestampMatch = line.match(/^\[(\d{4}-\d{2}-\d{2}T[^\]]+)\]\s*(.*)$/);
|
|
2544
2860
|
const timestamp = timestampMatch ? new Date(timestampMatch[1]).getTime() : Date.now();
|
|
2545
2861
|
const content = timestampMatch ? timestampMatch[2] : line;
|
|
2862
|
+
return { timestamp, content };
|
|
2863
|
+
}
|
|
2546
2864
|
|
|
2865
|
+
function publishIsolatedOutputRecord(agent, providerName, taskId, record) {
|
|
2547
2866
|
agent.messageBus.publish({
|
|
2548
2867
|
cluster_id: agent.cluster.id,
|
|
2549
2868
|
topic: 'AGENT_OUTPUT',
|
|
@@ -2551,31 +2870,64 @@ function broadcastIsolatedLine({ agent, providerName, taskId, state, line }) {
|
|
|
2551
2870
|
metadata: buildRawLogOnlyMetadata(),
|
|
2552
2871
|
content: {
|
|
2553
2872
|
data: {
|
|
2554
|
-
line: content,
|
|
2873
|
+
line: record.content,
|
|
2555
2874
|
taskId,
|
|
2556
2875
|
iteration: agent.iteration,
|
|
2557
2876
|
provider: providerName,
|
|
2558
2877
|
},
|
|
2559
2878
|
},
|
|
2879
|
+
timestamp: record.timestamp,
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
function retainIsolatedLine(state, line) {
|
|
2884
|
+
const { timestamp, content } = parseIsolatedLogLine(line);
|
|
2885
|
+
return appendControlPlaneRecord(state.controlPlaneOutput, {
|
|
2886
|
+
content,
|
|
2560
2887
|
timestamp,
|
|
2888
|
+
type: isValidJsonLine(content) ? 'json' : 'text',
|
|
2561
2889
|
});
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
function broadcastIsolatedLine({ agent, providerName, taskId, state, line }) {
|
|
2893
|
+
const followerState = ensureControlPlaneOutputState(state);
|
|
2894
|
+
const record = retainIsolatedLine(followerState, line);
|
|
2895
|
+
publishLiveControlPlaneRecord(followerState.controlPlaneOutput, record, (item) =>
|
|
2896
|
+
publishIsolatedOutputRecord(agent, providerName, taskId, item)
|
|
2897
|
+
);
|
|
2562
2898
|
|
|
2563
|
-
if (!
|
|
2899
|
+
if (!followerState.nested) {
|
|
2564
2900
|
agent.lastOutputTime = Date.now();
|
|
2565
2901
|
}
|
|
2566
2902
|
}
|
|
2567
2903
|
|
|
2904
|
+
function flushIsolatedOutput(agent, providerName, taskId, state) {
|
|
2905
|
+
const followerState = ensureControlPlaneOutputState(state);
|
|
2906
|
+
flushTerminalControlPlaneOutput(followerState.controlPlaneOutput, (record) =>
|
|
2907
|
+
publishIsolatedOutputRecord(agent, providerName, taskId, record)
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2568
2911
|
function appendIsolatedContent(state, content, onLine) {
|
|
2569
|
-
state
|
|
2570
|
-
|
|
2912
|
+
appendContentToBuffer(state, content, onLine, true);
|
|
2913
|
+
}
|
|
2571
2914
|
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2915
|
+
function consumeIsolatedTailChunk(state, data, onLine) {
|
|
2916
|
+
const chunk = typeof data === 'string' ? data : state.tailDecoder.write(data);
|
|
2917
|
+
if (!chunk || state.ignoreTailOutput) return;
|
|
2918
|
+
state.rawBytesSeen += Buffer.byteLength(chunk);
|
|
2919
|
+
appendIsolatedContent(state, chunk, onLine);
|
|
2920
|
+
}
|
|
2577
2921
|
|
|
2578
|
-
|
|
2922
|
+
function stopIsolatedTailForSettlement(state) {
|
|
2923
|
+
state.ignoreTailOutput = true;
|
|
2924
|
+
if (!state.tailProcess) return;
|
|
2925
|
+
try {
|
|
2926
|
+
state.tailProcess.kill('SIGTERM');
|
|
2927
|
+
} catch {
|
|
2928
|
+
// Ignore - process may already be dead.
|
|
2929
|
+
}
|
|
2930
|
+
state.tailProcess = null;
|
|
2579
2931
|
}
|
|
2580
2932
|
|
|
2581
2933
|
function startIsolatedTail({ agent, manager, clusterId, logFilePath, state, onLine }) {
|
|
@@ -2586,9 +2938,7 @@ function startIsolatedTail({ agent, manager, clusterId, logFilePath, state, onLi
|
|
|
2586
2938
|
]);
|
|
2587
2939
|
|
|
2588
2940
|
state.tailProcess.stdout.on('data', (data) => {
|
|
2589
|
-
|
|
2590
|
-
state.fullOutput += chunk;
|
|
2591
|
-
appendIsolatedContent(state, chunk, onLine);
|
|
2941
|
+
consumeIsolatedTailChunk(state, data, onLine);
|
|
2592
2942
|
});
|
|
2593
2943
|
|
|
2594
2944
|
state.tailProcess.stderr.on('data', (data) => {
|
|
@@ -2599,6 +2949,7 @@ function startIsolatedTail({ agent, manager, clusterId, logFilePath, state, onLi
|
|
|
2599
2949
|
});
|
|
2600
2950
|
|
|
2601
2951
|
state.tailProcess.on('close', (exitCode) => {
|
|
2952
|
+
consumeIsolatedTailChunk(state, state.tailDecoder.end(), onLine);
|
|
2602
2953
|
if (!state.taskExited) {
|
|
2603
2954
|
agent._log(`[${agent.id}] tail process exited with code ${exitCode}`);
|
|
2604
2955
|
}
|
|
@@ -2620,7 +2971,6 @@ async function checkIsolatedStatus({
|
|
|
2620
2971
|
cleanup,
|
|
2621
2972
|
resolve,
|
|
2622
2973
|
reject,
|
|
2623
|
-
onLine,
|
|
2624
2974
|
}) {
|
|
2625
2975
|
if (state.taskExited) return;
|
|
2626
2976
|
|
|
@@ -2673,7 +3023,6 @@ async function checkIsolatedStatus({
|
|
|
2673
3023
|
cleanup,
|
|
2674
3024
|
resolve,
|
|
2675
3025
|
reject,
|
|
2676
|
-
onLine,
|
|
2677
3026
|
});
|
|
2678
3027
|
}
|
|
2679
3028
|
|
|
@@ -2688,7 +3037,6 @@ function startIsolatedStatusChecks({
|
|
|
2688
3037
|
cleanup,
|
|
2689
3038
|
resolve,
|
|
2690
3039
|
reject,
|
|
2691
|
-
onLine,
|
|
2692
3040
|
}) {
|
|
2693
3041
|
state.statusCheckInterval = setInterval(() => {
|
|
2694
3042
|
checkIsolatedStatus({
|
|
@@ -2702,7 +3050,6 @@ function startIsolatedStatusChecks({
|
|
|
2702
3050
|
cleanup,
|
|
2703
3051
|
resolve,
|
|
2704
3052
|
reject,
|
|
2705
|
-
onLine,
|
|
2706
3053
|
}).catch((statusErr) => {
|
|
2707
3054
|
agent._log(`[${agent.id}] Status check error (will retry): ${statusErr.message}`);
|
|
2708
3055
|
});
|
|
@@ -2736,7 +3083,6 @@ function followClaudeTaskLogsIsolated(agent, taskId, options = {}) {
|
|
|
2736
3083
|
cleanup,
|
|
2737
3084
|
resolve,
|
|
2738
3085
|
reject,
|
|
2739
|
-
onLine,
|
|
2740
3086
|
});
|
|
2741
3087
|
// Only register the lifecycle handle on the agent for top-level tasks.
|
|
2742
3088
|
if (!options.nested) {
|
|
@@ -2807,7 +3153,6 @@ function followClaudeTaskLogsIsolated(agent, taskId, options = {}) {
|
|
|
2807
3153
|
cleanup,
|
|
2808
3154
|
resolve,
|
|
2809
3155
|
reject,
|
|
2810
|
-
onLine,
|
|
2811
3156
|
});
|
|
2812
3157
|
|
|
2813
3158
|
if (agent.timeout > 0 && !agent.enableLivenessCheck && !options.nested) {
|
|
@@ -3091,4 +3436,19 @@ module.exports = {
|
|
|
3091
3436
|
buildTaskRunArgs,
|
|
3092
3437
|
rebuildProviderSessionAfterCommit,
|
|
3093
3438
|
killTask,
|
|
3439
|
+
createLogFollowState,
|
|
3440
|
+
createIsolatedLogState,
|
|
3441
|
+
createControlPlaneOutputState,
|
|
3442
|
+
appendControlPlaneRecord,
|
|
3443
|
+
createLogRecordBuffer,
|
|
3444
|
+
appendContentToBuffer,
|
|
3445
|
+
consumeIsolatedTailChunk,
|
|
3446
|
+
flushAgentOutput,
|
|
3447
|
+
flushIsolatedOutput,
|
|
3448
|
+
CONTROL_PLANE_OUTPUT_LIMITS: Object.freeze({
|
|
3449
|
+
maxBytes: MAX_CONTROL_PLANE_OUTPUT_BYTES,
|
|
3450
|
+
maxRecords: MAX_CONTROL_PLANE_OUTPUT_RECORDS,
|
|
3451
|
+
liveBytes: MAX_LIVE_OUTPUT_BYTES,
|
|
3452
|
+
liveRecords: MAX_LIVE_OUTPUT_RECORDS,
|
|
3453
|
+
}),
|
|
3094
3454
|
};
|