@the-open-engine/zeroshot 6.36.0 → 6.38.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/cli/index.js +99 -29
- package/cli/lib/setup-scanner-worker.js +2 -9
- package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude-recovery.js +17 -3
- package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -1
- package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/index.js +7 -0
- package/lib/agent-cli-provider/adapters/index.js.map +1 -1
- package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/pi.js +86 -97
- package/lib/agent-cli-provider/adapters/pi.js.map +1 -1
- package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-parse.js +5 -1
- package/lib/agent-cli-provider/contract-parse.js.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.js +8 -1
- package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
- package/lib/agent-cli-provider/pi/credentials.d.ts +2 -0
- package/lib/agent-cli-provider/pi/credentials.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/credentials.js +51 -0
- package/lib/agent-cli-provider/pi/credentials.js.map +1 -0
- package/lib/agent-cli-provider/pi/events.d.ts +5 -0
- package/lib/agent-cli-provider/pi/events.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/events.js +124 -0
- package/lib/agent-cli-provider/pi/events.js.map +1 -0
- package/lib/agent-cli-provider/pi/protocol-state.d.ts +6 -0
- package/lib/agent-cli-provider/pi/protocol-state.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/protocol-state.js +138 -0
- package/lib/agent-cli-provider/pi/protocol-state.js.map +1 -0
- package/lib/agent-cli-provider/pi/release.d.ts +4 -0
- package/lib/agent-cli-provider/pi/release.d.ts.map +1 -0
- package/lib/agent-cli-provider/pi/release.js +7 -0
- package/lib/agent-cli-provider/pi/release.js.map +1 -0
- package/lib/agent-cli-provider/probe-availability.d.ts +5 -0
- package/lib/agent-cli-provider/probe-availability.d.ts.map +1 -0
- package/lib/agent-cli-provider/probe-availability.js +11 -0
- package/lib/agent-cli-provider/probe-availability.js.map +1 -0
- package/lib/agent-cli-provider/provider-registry.d.ts +13 -9
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +42 -8
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +15 -5
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +3 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster-worker/engine-adapter.js +9 -1
- package/lib/docker-config.js +6 -1
- package/lib/provider-credential-path.js +36 -0
- package/lib/start-cluster.js +8 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/agent/agent-lifecycle.js +25 -76
- package/src/agent/agent-liveness-poll.js +93 -0
- package/src/agent/agent-task-executor.js +98 -23
- package/src/agent/output-extraction.js +115 -0
- package/src/agent/pi-terminal-lifecycle.js +242 -0
- package/src/agent/provider-control-plane.js +26 -0
- package/src/agent/provider-terminal-failure.js +49 -24
- package/src/agent-cli-provider/adapters/claude-recovery.ts +15 -2
- package/src/agent-cli-provider/adapters/index.ts +6 -0
- package/src/agent-cli-provider/adapters/pi.ts +105 -125
- package/src/agent-cli-provider/contract-parse.ts +8 -1
- package/src/agent-cli-provider/invoke-evidence.ts +11 -1
- package/src/agent-cli-provider/pi/credentials.ts +47 -0
- package/src/agent-cli-provider/pi/events.ts +148 -0
- package/src/agent-cli-provider/pi/protocol-state.ts +163 -0
- package/src/agent-cli-provider/pi/release.ts +4 -0
- package/src/agent-cli-provider/probe-availability.ts +19 -0
- package/src/agent-cli-provider/provider-registry.ts +48 -9
- package/src/agent-cli-provider/single-agent-runtime.ts +27 -6
- package/src/agent-cli-provider/types.ts +3 -0
- package/src/config-validator.js +1 -3
- package/src/foreground-benchmark-files.js +81 -0
- package/src/foreground-benchmark-result.js +233 -0
- package/src/foreground-benchmark-run.js +72 -0
- package/src/isolation-manager.js +43 -29
- package/src/orchestrator.js +33 -1
- package/task-lib/runner.js +7 -0
- package/task-lib/watcher-output-runtime.js +16 -5
package/lib/start-cluster.js
CHANGED
|
@@ -326,6 +326,7 @@ function buildStartOptionsFromPlan({
|
|
|
326
326
|
providerOverride,
|
|
327
327
|
modelOverride,
|
|
328
328
|
forceProvider,
|
|
329
|
+
preparedWorktree,
|
|
329
330
|
environment,
|
|
330
331
|
}) {
|
|
331
332
|
const targetCwd = environment ? resolveTargetCwd() : options.cwd;
|
|
@@ -337,6 +338,7 @@ function buildStartOptionsFromPlan({
|
|
|
337
338
|
? firstTruthy(options.dockerImage, process.env.ZEROSHOT_DOCKER_IMAGE)
|
|
338
339
|
: optionalValue(options.dockerImage),
|
|
339
340
|
worktree: plan.isolation === 'worktree',
|
|
341
|
+
preparedWorktree: preparedWorktree || undefined,
|
|
340
342
|
autoPr: plan.delivery !== 'none',
|
|
341
343
|
autoMerge: plan.autoMerge,
|
|
342
344
|
autoPush: environment ? process.env.ZEROSHOT_PUSH === '1' : options.autoPush === true,
|
|
@@ -368,6 +370,7 @@ function buildTrustedStartOptions({
|
|
|
368
370
|
options = {},
|
|
369
371
|
settings = {},
|
|
370
372
|
providerOverride,
|
|
373
|
+
modelOverride,
|
|
371
374
|
forceProvider,
|
|
372
375
|
}) {
|
|
373
376
|
if (!plan || !Object.isFrozen(plan)) {
|
|
@@ -389,13 +392,18 @@ function buildTrustedStartOptions({
|
|
|
389
392
|
if (canonical.isolation === 'none') {
|
|
390
393
|
throw new Error('Trusted worker start requires worktree or docker isolation');
|
|
391
394
|
}
|
|
395
|
+
if (options.preparedWorktree && canonical.isolation !== 'worktree') {
|
|
396
|
+
throw new Error('Prepared worktree requires canonical worktree isolation');
|
|
397
|
+
}
|
|
392
398
|
return buildStartOptionsFromPlan({
|
|
393
399
|
clusterId,
|
|
394
400
|
plan: canonical,
|
|
395
401
|
options,
|
|
396
402
|
settings,
|
|
397
403
|
providerOverride,
|
|
404
|
+
modelOverride,
|
|
398
405
|
forceProvider,
|
|
406
|
+
preparedWorktree: options.preparedWorktree,
|
|
399
407
|
environment: false,
|
|
400
408
|
});
|
|
401
409
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-open-engine/zeroshot",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.38.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@the-open-engine/zeroshot",
|
|
9
|
-
"version": "6.
|
|
9
|
+
"version": "6.38.0",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ const { executeHook } = require('./agent-hook-executor');
|
|
|
17
17
|
const IsolationManager = require('../isolation-manager');
|
|
18
18
|
const crypto = require('crypto');
|
|
19
19
|
const { bufferMessage, scheduleDrain, drainBufferedMessages } = require('../message-buffer');
|
|
20
|
-
const {
|
|
20
|
+
const { createLivenessPoll } = require('./agent-liveness-poll');
|
|
21
21
|
const { normalizeProviderName, getDefaultProviderId } = require('../../lib/provider-names');
|
|
22
22
|
const { loadSettings } = require('../../lib/settings');
|
|
23
23
|
const { findPlatformMismatchReason } = require('./validation-platform');
|
|
@@ -223,7 +223,7 @@ async function stop(agent) {
|
|
|
223
223
|
stopLivenessCheck(agent);
|
|
224
224
|
|
|
225
225
|
const hasNestedExecutions = agent.nestedExecutions?.hasActive === true;
|
|
226
|
-
if (!agent.running && !agent.currentTask && !hasNestedExecutions) {
|
|
226
|
+
if (!agent.running && !agent.currentTask && !hasNestedExecutions && !agent._currentExecution) {
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
229
|
|
|
@@ -242,26 +242,26 @@ async function stop(agent) {
|
|
|
242
242
|
throw new Error(`Task shutdown could not confirm termination: ${termination.reason}`);
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// This prevents write-after-close race conditions
|
|
248
|
-
if (agent._currentExecution) {
|
|
245
|
+
const currentExecution = agent._currentExecution;
|
|
246
|
+
if (currentExecution) {
|
|
249
247
|
let executionTimeout = null;
|
|
250
248
|
try {
|
|
251
|
-
await Promise.race([
|
|
252
|
-
|
|
249
|
+
const outcome = await Promise.race([
|
|
250
|
+
Promise.resolve(currentExecution).then(
|
|
251
|
+
() => 'settled',
|
|
252
|
+
() => 'settled'
|
|
253
|
+
),
|
|
253
254
|
new Promise((resolve) => {
|
|
254
|
-
executionTimeout = setTimeout(resolve, 5000);
|
|
255
|
+
executionTimeout = setTimeout(() => resolve('timeout'), 5000);
|
|
255
256
|
}),
|
|
256
257
|
]);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
} finally {
|
|
260
|
-
if (executionTimeout) {
|
|
261
|
-
clearTimeout(executionTimeout);
|
|
258
|
+
if (outcome === 'timeout') {
|
|
259
|
+
throw new Error(`Agent ${agent.id} execution did not settle after task termination`);
|
|
262
260
|
}
|
|
263
|
-
|
|
261
|
+
} finally {
|
|
262
|
+
if (executionTimeout) clearTimeout(executionTimeout);
|
|
264
263
|
}
|
|
264
|
+
if (agent._currentExecution === currentExecution) agent._currentExecution = null;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
agent._log(`Agent ${agent.id} stopped`);
|
|
@@ -1268,67 +1268,16 @@ function startLivenessCheck(agent) {
|
|
|
1268
1268
|
agent.livenessTerminationAttempts = 0;
|
|
1269
1269
|
agent.livenessTerminationRetryAt = 0;
|
|
1270
1270
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
if (agent.livenessTerminationContext) {
|
|
1282
|
-
if (now >= agent.livenessTerminationRetryAt) {
|
|
1283
|
-
attemptLivenessTermination(agent, settings);
|
|
1284
|
-
}
|
|
1285
|
-
return;
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
const taskStartedAt = agent.taskStartedAt || agent.lastOutputTime || now;
|
|
1289
|
-
const lastOutputTime = agent.lastOutputTime || taskStartedAt;
|
|
1290
|
-
const taskRuntime = now - taskStartedAt;
|
|
1291
|
-
const timeSinceLastOutput = now - lastOutputTime;
|
|
1292
|
-
|
|
1293
|
-
if (configuredTimeout && taskRuntime >= configuredTimeout) {
|
|
1294
|
-
const reason = `Task timed out after ${configuredTimeout}ms`;
|
|
1295
|
-
beginLivenessTermination(agent, settings, reason, 'AGENT_TASK_TIMEOUT', {
|
|
1296
|
-
taskId: agent.currentTaskId,
|
|
1297
|
-
taskRuntime,
|
|
1298
|
-
timeout: configuredTimeout,
|
|
1299
|
-
});
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
if (timeSinceLastOutput < staleDuration) {
|
|
1304
|
-
agent.consecutiveStaleWarnings = 0;
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
agent.consecutiveStaleWarnings += 1;
|
|
1309
|
-
agent._publishLifecycle('AGENT_STALE_WARNING', {
|
|
1310
|
-
taskId: agent.currentTaskId,
|
|
1311
|
-
timeSinceLastOutput,
|
|
1312
|
-
staleDuration,
|
|
1313
|
-
lastOutputTime,
|
|
1314
|
-
consecutiveWarnings: agent.consecutiveStaleWarnings,
|
|
1315
|
-
warningsBeforeKill,
|
|
1316
|
-
processDiagnosticsAvailable: isPlatformSupported(),
|
|
1317
|
-
analysis: `Provider produced no output for ${timeSinceLastOutput}ms`,
|
|
1318
|
-
});
|
|
1319
|
-
|
|
1320
|
-
if (agent.consecutiveStaleWarnings < warningsBeforeKill) {
|
|
1321
|
-
return;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
const reason = `Provider produced no output for ${timeSinceLastOutput}ms`;
|
|
1325
|
-
beginLivenessTermination(agent, settings, reason, 'PROVIDER_INACTIVITY_TIMEOUT', {
|
|
1326
|
-
taskId: agent.currentTaskId,
|
|
1327
|
-
timeSinceLastOutput,
|
|
1328
|
-
staleDuration,
|
|
1329
|
-
consecutiveWarnings: agent.consecutiveStaleWarnings,
|
|
1330
|
-
});
|
|
1331
|
-
}, checkIntervalMs);
|
|
1271
|
+
const poll = createLivenessPoll({
|
|
1272
|
+
agent,
|
|
1273
|
+
settings,
|
|
1274
|
+
configuredTimeout,
|
|
1275
|
+
staleDuration,
|
|
1276
|
+
warningsBeforeKill,
|
|
1277
|
+
attemptTermination: attemptLivenessTermination,
|
|
1278
|
+
beginTermination: beginLivenessTermination,
|
|
1279
|
+
});
|
|
1280
|
+
agent.livenessCheckInterval = setInterval(poll, checkIntervalMs);
|
|
1332
1281
|
}
|
|
1333
1282
|
|
|
1334
1283
|
const MAX_LIVENESS_TERMINATION_ATTEMPTS = 3;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const { isPlatformSupported } = require('./agent-stuck-detector');
|
|
2
|
+
|
|
3
|
+
function hasRecoverableTask(agent) {
|
|
4
|
+
return (
|
|
5
|
+
Boolean(agent.currentTask) ||
|
|
6
|
+
Boolean(agent.isolation?.enabled && agent.currentTaskId) ||
|
|
7
|
+
agent.nestedExecutions?.hasActive === true
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function handlePendingTermination(agent, settings, now, attemptTermination) {
|
|
12
|
+
if (!agent.livenessTerminationContext) return false;
|
|
13
|
+
if (now >= agent.livenessTerminationRetryAt) attemptTermination(agent, settings);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function taskTiming(agent, now) {
|
|
18
|
+
const taskStartedAt = agent.taskStartedAt || agent.lastOutputTime || now;
|
|
19
|
+
const lastOutputTime = agent.lastOutputTime || taskStartedAt;
|
|
20
|
+
return {
|
|
21
|
+
taskRuntime: now - taskStartedAt,
|
|
22
|
+
timeSinceLastOutput: now - lastOutputTime,
|
|
23
|
+
lastOutputTime,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleTaskTimeout(context, timing) {
|
|
28
|
+
const { agent, settings, configuredTimeout, beginTermination } = context;
|
|
29
|
+
if (!configuredTimeout || timing.taskRuntime < configuredTimeout) return false;
|
|
30
|
+
beginTermination(
|
|
31
|
+
agent,
|
|
32
|
+
settings,
|
|
33
|
+
`Task timed out after ${configuredTimeout}ms`,
|
|
34
|
+
'AGENT_TASK_TIMEOUT',
|
|
35
|
+
{
|
|
36
|
+
taskId: agent.currentTaskId,
|
|
37
|
+
taskRuntime: timing.taskRuntime,
|
|
38
|
+
timeout: configuredTimeout,
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function publishStaleWarning(context, timing) {
|
|
45
|
+
const { agent, staleDuration, warningsBeforeKill } = context;
|
|
46
|
+
agent.consecutiveStaleWarnings += 1;
|
|
47
|
+
agent._publishLifecycle('AGENT_STALE_WARNING', {
|
|
48
|
+
taskId: agent.currentTaskId,
|
|
49
|
+
timeSinceLastOutput: timing.timeSinceLastOutput,
|
|
50
|
+
staleDuration,
|
|
51
|
+
lastOutputTime: timing.lastOutputTime,
|
|
52
|
+
consecutiveWarnings: agent.consecutiveStaleWarnings,
|
|
53
|
+
warningsBeforeKill,
|
|
54
|
+
processDiagnosticsAvailable: isPlatformSupported(),
|
|
55
|
+
analysis: `Provider produced no output for ${timing.timeSinceLastOutput}ms`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function terminateForInactivity(context, timing) {
|
|
60
|
+
const { agent, settings, staleDuration, beginTermination } = context;
|
|
61
|
+
beginTermination(
|
|
62
|
+
agent,
|
|
63
|
+
settings,
|
|
64
|
+
`Provider produced no output for ${timing.timeSinceLastOutput}ms`,
|
|
65
|
+
'PROVIDER_INACTIVITY_TIMEOUT',
|
|
66
|
+
{
|
|
67
|
+
taskId: agent.currentTaskId,
|
|
68
|
+
timeSinceLastOutput: timing.timeSinceLastOutput,
|
|
69
|
+
staleDuration,
|
|
70
|
+
consecutiveWarnings: agent.consecutiveStaleWarnings,
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function createLivenessPoll(context) {
|
|
76
|
+
const { agent, settings, staleDuration, warningsBeforeKill, attemptTermination } = context;
|
|
77
|
+
return () => {
|
|
78
|
+
if (!hasRecoverableTask(agent) || agent.livenessTerminationStarted) return;
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
if (handlePendingTermination(agent, settings, now, attemptTermination)) return;
|
|
81
|
+
const timing = taskTiming(agent, now);
|
|
82
|
+
if (handleTaskTimeout(context, timing)) return;
|
|
83
|
+
if (timing.timeSinceLastOutput < staleDuration) {
|
|
84
|
+
agent.consecutiveStaleWarnings = 0;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
publishStaleWarning(context, timing);
|
|
88
|
+
if (agent.consecutiveStaleWarnings < warningsBeforeKill) return;
|
|
89
|
+
terminateForInactivity(context, timing);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { createLivenessPoll };
|
|
@@ -56,6 +56,8 @@ const {
|
|
|
56
56
|
const { extractClaudeVertexModelError } = require('./output-extraction');
|
|
57
57
|
const {
|
|
58
58
|
extractProviderFailure,
|
|
59
|
+
extractProviderCompletionFailure,
|
|
60
|
+
getPiTokenUsage,
|
|
59
61
|
redactTerminalFailureForControlPlane,
|
|
60
62
|
} = require('./provider-terminal-failure');
|
|
61
63
|
const {
|
|
@@ -377,7 +379,9 @@ function extractErrorContext({
|
|
|
377
379
|
}
|
|
378
380
|
}
|
|
379
381
|
|
|
380
|
-
const terminalFailure =
|
|
382
|
+
const terminalFailure =
|
|
383
|
+
providerFailure ||
|
|
384
|
+
(providerName === 'pi' ? null : extractProviderFailure(output, providerName));
|
|
381
385
|
if (terminalFailure) {
|
|
382
386
|
return sanitizeErrorMessage(terminalFailure.error);
|
|
383
387
|
}
|
|
@@ -449,8 +453,12 @@ function extractErrorContext({
|
|
|
449
453
|
* @param {string} output - Full NDJSON output from Claude CLI
|
|
450
454
|
* @returns {Object|null} Token usage data or null if not found
|
|
451
455
|
*/
|
|
452
|
-
function extractTokenUsage(output, providerName = getDefaultProviderId()) {
|
|
456
|
+
function extractTokenUsage(output, providerName = getDefaultProviderId(), state = null) {
|
|
453
457
|
if (!output) return null;
|
|
458
|
+
if (providerName === 'pi') {
|
|
459
|
+
const retainedUsage = getPiTokenUsage(state);
|
|
460
|
+
if (retainedUsage !== null) return retainedUsage;
|
|
461
|
+
}
|
|
454
462
|
|
|
455
463
|
const events = parseProviderChunk(providerName, output);
|
|
456
464
|
const resultEvent = events.find((event) => event.type === 'result');
|
|
@@ -1465,21 +1473,38 @@ function lookupLogFilePath(ctPath, taskId) {
|
|
|
1465
1473
|
function parseTimestampedLine(line) {
|
|
1466
1474
|
let timestamp = Date.now();
|
|
1467
1475
|
let content = line.replace(/\r$/, '');
|
|
1476
|
+
let timestamped = false;
|
|
1468
1477
|
|
|
1469
1478
|
const timestampMatch = content.match(/^\[(\d{13})\](.*)$/);
|
|
1470
1479
|
if (timestampMatch) {
|
|
1471
1480
|
timestamp = parseInt(timestampMatch[1], 10);
|
|
1472
1481
|
content = timestampMatch[2];
|
|
1482
|
+
timestamped = true;
|
|
1473
1483
|
}
|
|
1474
1484
|
|
|
1475
|
-
return { timestamp, content };
|
|
1485
|
+
return { timestamp, content, timestamped };
|
|
1476
1486
|
}
|
|
1477
1487
|
|
|
1478
|
-
function shouldSkipLogLine(content) {
|
|
1488
|
+
function shouldSkipLogLine(content, providerName, timestamped) {
|
|
1489
|
+
if (providerName === 'pi') {
|
|
1490
|
+
if (
|
|
1491
|
+
content.startsWith('[ZEROSHOT][PROVIDER_STDERR] ') ||
|
|
1492
|
+
content.startsWith('[ZEROSHOT][FATAL] ')
|
|
1493
|
+
) {
|
|
1494
|
+
return true;
|
|
1495
|
+
}
|
|
1496
|
+
if (timestamped) return false;
|
|
1497
|
+
return (
|
|
1498
|
+
/^={50}$/.test(content) ||
|
|
1499
|
+
/^Finished: \d{4}-\d{2}-\d{2}T[^\s]+Z$/.test(content) ||
|
|
1500
|
+
/^Exit code: (?:null|-?\d+), Signal: (?:null|[A-Z0-9]+)$/.test(content)
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1479
1503
|
return (
|
|
1480
1504
|
content.startsWith('===') ||
|
|
1481
1505
|
content.startsWith('Finished:') ||
|
|
1482
1506
|
content.startsWith('Exit code:') ||
|
|
1507
|
+
content.startsWith('[ZEROSHOT][PROVIDER_STDERR] ') ||
|
|
1483
1508
|
(content.includes('"type":"system"') && content.includes('"subtype":"init"'))
|
|
1484
1509
|
);
|
|
1485
1510
|
}
|
|
@@ -1520,8 +1545,8 @@ function broadcastAgentLine({ agent, providerName, state, line }) {
|
|
|
1520
1545
|
const followerState = ensureControlPlaneOutputState(state);
|
|
1521
1546
|
if (!line.trim()) return;
|
|
1522
1547
|
|
|
1523
|
-
const { timestamp, content } = parseTimestampedLine(line);
|
|
1524
|
-
if (shouldSkipLogLine(content)) {
|
|
1548
|
+
const { timestamp, content, timestamped } = parseTimestampedLine(line);
|
|
1549
|
+
if (shouldSkipLogLine(content, providerName, timestamped)) {
|
|
1525
1550
|
return;
|
|
1526
1551
|
}
|
|
1527
1552
|
const controlPlaneContent = redactTerminalFailureForControlPlane(
|
|
@@ -1764,7 +1789,8 @@ function buildFailureContext({ agent, taskId, providerName, state, stdout, provi
|
|
|
1764
1789
|
function resolveCompletionFailure({ classified, agent, taskId, providerName, state, stdout }) {
|
|
1765
1790
|
if (classified.success) return { providerFailure: null, errorContext: classified.error };
|
|
1766
1791
|
const providerFailure =
|
|
1767
|
-
state.providerFailure ||
|
|
1792
|
+
state.providerFailure ||
|
|
1793
|
+
(providerName === 'pi' ? null : extractProviderFailure(state.output, providerName));
|
|
1768
1794
|
return {
|
|
1769
1795
|
providerFailure,
|
|
1770
1796
|
errorContext:
|
|
@@ -1774,6 +1800,14 @@ function resolveCompletionFailure({ classified, agent, taskId, providerName, sta
|
|
|
1774
1800
|
};
|
|
1775
1801
|
}
|
|
1776
1802
|
|
|
1803
|
+
function enforceProviderCompletionTruth({ providerName, output, state, success, error }) {
|
|
1804
|
+
if (providerName !== 'pi' || !success) return { success, error };
|
|
1805
|
+
const failure = extractProviderCompletionFailure(output, providerName, state);
|
|
1806
|
+
if (!failure) return { success, error };
|
|
1807
|
+
state.providerFailure = failure;
|
|
1808
|
+
return { success: false, error: failure.error };
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1777
1811
|
async function buildCompletionResult({
|
|
1778
1812
|
agent,
|
|
1779
1813
|
taskId,
|
|
@@ -1784,7 +1818,7 @@ async function buildCompletionResult({
|
|
|
1784
1818
|
taskInfo = getTask(taskId),
|
|
1785
1819
|
}) {
|
|
1786
1820
|
const { isCompleted } = parseStatusFlags(stripAnsiCodes(stdout));
|
|
1787
|
-
|
|
1821
|
+
let classified = state.skipStructuredResultCheck
|
|
1788
1822
|
? { success, error: null }
|
|
1789
1823
|
: await evaluateStructuredSuccess({
|
|
1790
1824
|
agent,
|
|
@@ -1793,6 +1827,12 @@ async function buildCompletionResult({
|
|
|
1793
1827
|
success,
|
|
1794
1828
|
allowRecovery: isCompleted,
|
|
1795
1829
|
});
|
|
1830
|
+
classified = enforceProviderCompletionTruth({
|
|
1831
|
+
providerName,
|
|
1832
|
+
output: state.output,
|
|
1833
|
+
state,
|
|
1834
|
+
...classified,
|
|
1835
|
+
});
|
|
1796
1836
|
const resumeIdentityError = classified.success ? validateCompletedResumeIdentity(taskInfo) : null;
|
|
1797
1837
|
if (resumeIdentityError) {
|
|
1798
1838
|
classified.success = false;
|
|
@@ -1823,7 +1863,7 @@ async function buildCompletionResult({
|
|
|
1823
1863
|
// so downstream hooks and {{result.*}} substitution never re-parse.
|
|
1824
1864
|
parsedResult: state._cachedParsedResult || null,
|
|
1825
1865
|
error: errorContext,
|
|
1826
|
-
tokenUsage: extractTokenUsage(state.output, providerName),
|
|
1866
|
+
tokenUsage: extractTokenUsage(state.output, providerName, state),
|
|
1827
1867
|
providerSession: providerSessionFromCompletedTask({
|
|
1828
1868
|
agent,
|
|
1829
1869
|
providerName,
|
|
@@ -1866,6 +1906,19 @@ function finalizeLogFollow(agent, state) {
|
|
|
1866
1906
|
}
|
|
1867
1907
|
}
|
|
1868
1908
|
|
|
1909
|
+
function finishHostLogCapture(agent, state, pollLogFile, processNewContent, broadcastLine) {
|
|
1910
|
+
// A task status can become terminal before the watcher has flushed its final
|
|
1911
|
+
// filesystem write (observed on Modal). Stop the timers first, then take one
|
|
1912
|
+
// authoritative catch-up read and finish any final UTF-8/non-newline record.
|
|
1913
|
+
finalizeLogFollow(agent, state);
|
|
1914
|
+
pollLogFile();
|
|
1915
|
+
const decoderTail = state.logDecoder.end();
|
|
1916
|
+
if (decoderTail) processNewContent(decoderTail);
|
|
1917
|
+
if (state.lineBuffer.byteLength > 0) {
|
|
1918
|
+
completeLogRecord(state.lineBuffer, broadcastLine, true);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1869
1922
|
function settleHostStatusFailure({ agent, providerName, state, resolve, text, data, error }) {
|
|
1870
1923
|
if (state.resolved) return;
|
|
1871
1924
|
state.resolved = true;
|
|
@@ -1986,6 +2039,7 @@ function handleStatusCompletion({
|
|
|
1986
2039
|
state,
|
|
1987
2040
|
stdout,
|
|
1988
2041
|
pollLogFile,
|
|
2042
|
+
finishLogCapture,
|
|
1989
2043
|
resolve,
|
|
1990
2044
|
reject,
|
|
1991
2045
|
}) {
|
|
@@ -2012,7 +2066,7 @@ function handleStatusCompletion({
|
|
|
2012
2066
|
if (state.resolved) return;
|
|
2013
2067
|
state.resolved = true;
|
|
2014
2068
|
|
|
2015
|
-
|
|
2069
|
+
finishLogCapture();
|
|
2016
2070
|
flushAgentOutput(agent, providerName, state);
|
|
2017
2071
|
|
|
2018
2072
|
buildCompletionResult({
|
|
@@ -2046,7 +2100,7 @@ function buildKillHandler({ agent, taskId, state, providerName, resolve }) {
|
|
|
2046
2100
|
error: reason,
|
|
2047
2101
|
code: details.code || null,
|
|
2048
2102
|
taskId,
|
|
2049
|
-
tokenUsage: extractTokenUsage(state.output, providerName),
|
|
2103
|
+
tokenUsage: extractTokenUsage(state.output, providerName, state),
|
|
2050
2104
|
});
|
|
2051
2105
|
},
|
|
2052
2106
|
};
|
|
@@ -2066,14 +2120,12 @@ function createLogFollower({
|
|
|
2066
2120
|
const state = createLogFollowState();
|
|
2067
2121
|
state.skipStructuredResultCheck = skipStructuredResultCheck;
|
|
2068
2122
|
state.nested = nested;
|
|
2069
|
-
|
|
2070
2123
|
state.logFilePath = lookupLogFilePath(ctPath, taskId);
|
|
2071
2124
|
if (state.logFilePath) {
|
|
2072
2125
|
agent._log(`📋 Agent ${agent.id}: Following ct logs for ${taskId}`);
|
|
2073
2126
|
} else {
|
|
2074
2127
|
agent._log(`⏳ Agent ${agent.id}: Waiting for log file...`);
|
|
2075
2128
|
}
|
|
2076
|
-
|
|
2077
2129
|
const broadcastLine = (line) => broadcastAgentLine({ agent, providerName, state, line });
|
|
2078
2130
|
const processNewContent = (content) => appendContentToBuffer(state, content, broadcastLine);
|
|
2079
2131
|
const pollLogFile = () =>
|
|
@@ -2085,6 +2137,8 @@ function createLogFollower({
|
|
|
2085
2137
|
state,
|
|
2086
2138
|
onNewContent: processNewContent,
|
|
2087
2139
|
});
|
|
2140
|
+
const finishLogCapture = () =>
|
|
2141
|
+
finishHostLogCapture(agent, state, pollLogFile, processNewContent, broadcastLine);
|
|
2088
2142
|
|
|
2089
2143
|
state.pollInterval = setInterval(pollLogFile, 300);
|
|
2090
2144
|
|
|
@@ -2120,6 +2174,7 @@ function createLogFollower({
|
|
|
2120
2174
|
state,
|
|
2121
2175
|
stdout,
|
|
2122
2176
|
pollLogFile,
|
|
2177
|
+
finishLogCapture,
|
|
2123
2178
|
resolve,
|
|
2124
2179
|
reject,
|
|
2125
2180
|
});
|
|
@@ -2719,6 +2774,7 @@ async function captureIsolatedFinalOutputTail(
|
|
|
2719
2774
|
]);
|
|
2720
2775
|
if (finalReadResult.code === 0 && finalReadResult.stdout) {
|
|
2721
2776
|
if (missingBytes > readBytes) {
|
|
2777
|
+
if (providerName === 'pi') state.piProtocolPrefixOmitted = true;
|
|
2722
2778
|
resetControlPlaneTail(state.controlPlaneOutput, { prefixOmitted: true });
|
|
2723
2779
|
state.lineBuffer = createLogRecordBuffer();
|
|
2724
2780
|
}
|
|
@@ -2745,19 +2801,21 @@ function resolveIsolatedFailure({
|
|
|
2745
2801
|
providerName,
|
|
2746
2802
|
state,
|
|
2747
2803
|
status,
|
|
2804
|
+
statusOutput,
|
|
2748
2805
|
isNotFound,
|
|
2749
2806
|
clusterId,
|
|
2750
2807
|
logFilePath,
|
|
2751
2808
|
}) {
|
|
2752
2809
|
if (success) return { providerFailure: null, errorContext: structuredError };
|
|
2753
2810
|
const providerFailure =
|
|
2754
|
-
state.providerFailure ||
|
|
2811
|
+
state.providerFailure ||
|
|
2812
|
+
(providerName === 'pi' ? null : extractProviderFailure(state.fullOutput, providerName));
|
|
2755
2813
|
const errorContext =
|
|
2756
2814
|
providerFailure?.error ||
|
|
2757
2815
|
structuredError ||
|
|
2758
2816
|
extractErrorContext({
|
|
2759
2817
|
output: state.fullOutput,
|
|
2760
|
-
statusOutput: status ? `Status: ${status}` : '',
|
|
2818
|
+
statusOutput: statusOutput || (status ? `Status: ${status}` : ''),
|
|
2761
2819
|
taskId,
|
|
2762
2820
|
isNotFound,
|
|
2763
2821
|
providerName,
|
|
@@ -2782,6 +2840,7 @@ function settleIsolatedTerminalStatus({
|
|
|
2782
2840
|
taskId,
|
|
2783
2841
|
providerName,
|
|
2784
2842
|
status,
|
|
2843
|
+
statusOutput = '',
|
|
2785
2844
|
isNotFound = false,
|
|
2786
2845
|
state,
|
|
2787
2846
|
cleanup,
|
|
@@ -2790,7 +2849,6 @@ function settleIsolatedTerminalStatus({
|
|
|
2790
2849
|
}) {
|
|
2791
2850
|
if (state.resolved) return Promise.resolve();
|
|
2792
2851
|
if (state.terminalSettlementPromise) return state.terminalSettlementPromise;
|
|
2793
|
-
|
|
2794
2852
|
state.taskExited = true;
|
|
2795
2853
|
if (status) {
|
|
2796
2854
|
state.durableTaskTerminal = true;
|
|
@@ -2827,6 +2885,15 @@ function settleIsolatedTerminalStatus({
|
|
|
2827
2885
|
success = evaluated.success;
|
|
2828
2886
|
structuredError = evaluated.error;
|
|
2829
2887
|
}
|
|
2888
|
+
const completionTruth = enforceProviderCompletionTruth({
|
|
2889
|
+
providerName,
|
|
2890
|
+
output: state.fullOutput,
|
|
2891
|
+
state,
|
|
2892
|
+
success,
|
|
2893
|
+
error: structuredError,
|
|
2894
|
+
});
|
|
2895
|
+
success = completionTruth.success;
|
|
2896
|
+
structuredError = completionTruth.error;
|
|
2830
2897
|
const { providerFailure, errorContext } = resolveIsolatedFailure({
|
|
2831
2898
|
success,
|
|
2832
2899
|
structuredError,
|
|
@@ -2835,6 +2902,7 @@ function settleIsolatedTerminalStatus({
|
|
|
2835
2902
|
providerName,
|
|
2836
2903
|
state,
|
|
2837
2904
|
status,
|
|
2905
|
+
statusOutput,
|
|
2838
2906
|
isNotFound,
|
|
2839
2907
|
clusterId,
|
|
2840
2908
|
logFilePath,
|
|
@@ -2845,7 +2913,6 @@ function settleIsolatedTerminalStatus({
|
|
|
2845
2913
|
? state._cachedParsedResult
|
|
2846
2914
|
: await agent._parseResultOutput(state.fullOutput);
|
|
2847
2915
|
}
|
|
2848
|
-
|
|
2849
2916
|
settleIsolatedFollower({
|
|
2850
2917
|
agent,
|
|
2851
2918
|
state,
|
|
@@ -2857,7 +2924,7 @@ function settleIsolatedTerminalStatus({
|
|
|
2857
2924
|
taskId,
|
|
2858
2925
|
parsedResult,
|
|
2859
2926
|
error: errorContext,
|
|
2860
|
-
tokenUsage: extractTokenUsage(state.fullOutput, providerName),
|
|
2927
|
+
tokenUsage: extractTokenUsage(state.fullOutput, providerName, state),
|
|
2861
2928
|
providerFailure,
|
|
2862
2929
|
vertexModelError,
|
|
2863
2930
|
},
|
|
@@ -2893,7 +2960,7 @@ function buildIsolatedLifecycleHandle({
|
|
|
2893
2960
|
error: reason,
|
|
2894
2961
|
code: details.code || null,
|
|
2895
2962
|
taskId,
|
|
2896
|
-
tokenUsage: extractTokenUsage(state.fullOutput, providerName),
|
|
2963
|
+
tokenUsage: extractTokenUsage(state.fullOutput, providerName, state),
|
|
2897
2964
|
},
|
|
2898
2965
|
});
|
|
2899
2966
|
};
|
|
@@ -2956,10 +3023,14 @@ function buildIsolatedLifecycleHandle({
|
|
|
2956
3023
|
}
|
|
2957
3024
|
|
|
2958
3025
|
function parseIsolatedLogLine(line) {
|
|
2959
|
-
const timestampMatch = line.match(/^\[(\d{4}-\d{2}-\d{2}T[^\]]+)\]\s*(.*)$/);
|
|
2960
|
-
const timestamp = timestampMatch
|
|
3026
|
+
const timestampMatch = line.match(/^\[(\d{13}|\d{4}-\d{2}-\d{2}T[^\]]+)\]\s*(.*)$/);
|
|
3027
|
+
const timestamp = timestampMatch
|
|
3028
|
+
? /^\d{13}$/.test(timestampMatch[1])
|
|
3029
|
+
? Number.parseInt(timestampMatch[1], 10)
|
|
3030
|
+
: new Date(timestampMatch[1]).getTime()
|
|
3031
|
+
: Date.now();
|
|
2961
3032
|
const content = timestampMatch ? timestampMatch[2] : line;
|
|
2962
|
-
return { timestamp, content };
|
|
3033
|
+
return { timestamp, content, timestamped: timestampMatch !== null };
|
|
2963
3034
|
}
|
|
2964
3035
|
|
|
2965
3036
|
function publishIsolatedOutputRecord(agent, providerName, taskId, record) {
|
|
@@ -2981,7 +3052,8 @@ function publishIsolatedOutputRecord(agent, providerName, taskId, record) {
|
|
|
2981
3052
|
}
|
|
2982
3053
|
|
|
2983
3054
|
function retainIsolatedLine(state, providerName, line) {
|
|
2984
|
-
const { timestamp, content } = parseIsolatedLogLine(line);
|
|
3055
|
+
const { timestamp, content, timestamped } = parseIsolatedLogLine(line);
|
|
3056
|
+
if (!content.trim() || shouldSkipLogLine(content, providerName, timestamped)) return null;
|
|
2985
3057
|
const controlPlaneContent = redactTerminalFailureForControlPlane(state, providerName, content);
|
|
2986
3058
|
return appendControlPlaneRecord(state.controlPlaneOutput, {
|
|
2987
3059
|
content: controlPlaneContent,
|
|
@@ -2993,6 +3065,7 @@ function retainIsolatedLine(state, providerName, line) {
|
|
|
2993
3065
|
function broadcastIsolatedLine({ agent, providerName, taskId, state, line }) {
|
|
2994
3066
|
const followerState = ensureControlPlaneOutputState(state);
|
|
2995
3067
|
const record = retainIsolatedLine(followerState, providerName, line);
|
|
3068
|
+
if (record === null) return;
|
|
2996
3069
|
publishLiveControlPlaneRecord(followerState.controlPlaneOutput, record, (item) =>
|
|
2997
3070
|
publishIsolatedOutputRecord(agent, providerName, taskId, item)
|
|
2998
3071
|
);
|
|
@@ -3119,6 +3192,7 @@ async function checkIsolatedStatus({
|
|
|
3119
3192
|
taskId,
|
|
3120
3193
|
providerName,
|
|
3121
3194
|
status,
|
|
3195
|
+
statusOutput,
|
|
3122
3196
|
isNotFound,
|
|
3123
3197
|
state,
|
|
3124
3198
|
cleanup,
|
|
@@ -3546,6 +3620,7 @@ module.exports = {
|
|
|
3546
3620
|
consumeIsolatedTailChunk,
|
|
3547
3621
|
flushAgentOutput,
|
|
3548
3622
|
flushIsolatedOutput,
|
|
3623
|
+
finishHostLogCapture,
|
|
3549
3624
|
CONTROL_PLANE_OUTPUT_LIMITS: Object.freeze({
|
|
3550
3625
|
maxBytes: MAX_CONTROL_PLANE_OUTPUT_BYTES,
|
|
3551
3626
|
maxRecords: MAX_CONTROL_PLANE_OUTPUT_RECORDS,
|