@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.
Files changed (81) hide show
  1. package/cli/index.js +99 -29
  2. package/cli/lib/setup-scanner-worker.js +2 -9
  3. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -1
  4. package/lib/agent-cli-provider/adapters/claude-recovery.js +17 -3
  5. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -1
  6. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
  7. package/lib/agent-cli-provider/adapters/index.js +7 -0
  8. package/lib/agent-cli-provider/adapters/index.js.map +1 -1
  9. package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -1
  10. package/lib/agent-cli-provider/adapters/pi.js +86 -97
  11. package/lib/agent-cli-provider/adapters/pi.js.map +1 -1
  12. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -1
  13. package/lib/agent-cli-provider/contract-parse.js +5 -1
  14. package/lib/agent-cli-provider/contract-parse.js.map +1 -1
  15. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
  16. package/lib/agent-cli-provider/invoke-evidence.js +8 -1
  17. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
  18. package/lib/agent-cli-provider/pi/credentials.d.ts +2 -0
  19. package/lib/agent-cli-provider/pi/credentials.d.ts.map +1 -0
  20. package/lib/agent-cli-provider/pi/credentials.js +51 -0
  21. package/lib/agent-cli-provider/pi/credentials.js.map +1 -0
  22. package/lib/agent-cli-provider/pi/events.d.ts +5 -0
  23. package/lib/agent-cli-provider/pi/events.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/pi/events.js +124 -0
  25. package/lib/agent-cli-provider/pi/events.js.map +1 -0
  26. package/lib/agent-cli-provider/pi/protocol-state.d.ts +6 -0
  27. package/lib/agent-cli-provider/pi/protocol-state.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/pi/protocol-state.js +138 -0
  29. package/lib/agent-cli-provider/pi/protocol-state.js.map +1 -0
  30. package/lib/agent-cli-provider/pi/release.d.ts +4 -0
  31. package/lib/agent-cli-provider/pi/release.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/pi/release.js +7 -0
  33. package/lib/agent-cli-provider/pi/release.js.map +1 -0
  34. package/lib/agent-cli-provider/probe-availability.d.ts +5 -0
  35. package/lib/agent-cli-provider/probe-availability.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/probe-availability.js +11 -0
  37. package/lib/agent-cli-provider/probe-availability.js.map +1 -0
  38. package/lib/agent-cli-provider/provider-registry.d.ts +13 -9
  39. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  40. package/lib/agent-cli-provider/provider-registry.js +42 -8
  41. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  42. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  43. package/lib/agent-cli-provider/single-agent-runtime.js +15 -5
  44. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  45. package/lib/agent-cli-provider/types.d.ts +3 -0
  46. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  47. package/lib/agent-cli-provider/types.js.map +1 -1
  48. package/lib/cluster-worker/engine-adapter.js +9 -1
  49. package/lib/docker-config.js +6 -1
  50. package/lib/provider-credential-path.js +36 -0
  51. package/lib/start-cluster.js +8 -0
  52. package/npm-shrinkwrap.json +2 -2
  53. package/package.json +1 -1
  54. package/src/agent/agent-lifecycle.js +25 -76
  55. package/src/agent/agent-liveness-poll.js +93 -0
  56. package/src/agent/agent-task-executor.js +98 -23
  57. package/src/agent/output-extraction.js +115 -0
  58. package/src/agent/pi-terminal-lifecycle.js +242 -0
  59. package/src/agent/provider-control-plane.js +26 -0
  60. package/src/agent/provider-terminal-failure.js +49 -24
  61. package/src/agent-cli-provider/adapters/claude-recovery.ts +15 -2
  62. package/src/agent-cli-provider/adapters/index.ts +6 -0
  63. package/src/agent-cli-provider/adapters/pi.ts +105 -125
  64. package/src/agent-cli-provider/contract-parse.ts +8 -1
  65. package/src/agent-cli-provider/invoke-evidence.ts +11 -1
  66. package/src/agent-cli-provider/pi/credentials.ts +47 -0
  67. package/src/agent-cli-provider/pi/events.ts +148 -0
  68. package/src/agent-cli-provider/pi/protocol-state.ts +163 -0
  69. package/src/agent-cli-provider/pi/release.ts +4 -0
  70. package/src/agent-cli-provider/probe-availability.ts +19 -0
  71. package/src/agent-cli-provider/provider-registry.ts +48 -9
  72. package/src/agent-cli-provider/single-agent-runtime.ts +27 -6
  73. package/src/agent-cli-provider/types.ts +3 -0
  74. package/src/config-validator.js +1 -3
  75. package/src/foreground-benchmark-files.js +81 -0
  76. package/src/foreground-benchmark-result.js +233 -0
  77. package/src/foreground-benchmark-run.js +72 -0
  78. package/src/isolation-manager.js +43 -29
  79. package/src/orchestrator.js +33 -1
  80. package/task-lib/runner.js +7 -0
  81. package/task-lib/watcher-output-runtime.js +16 -5
@@ -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
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@the-open-engine/zeroshot",
3
- "version": "6.36.0",
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.36.0",
9
+ "version": "6.38.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-open-engine/zeroshot",
3
- "version": "6.36.0",
3
+ "version": "6.38.0",
4
4
  "description": "Independent executor–verifier orchestration for software changes.",
5
5
  "main": "src/orchestrator.js",
6
6
  "bin": {
@@ -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 { isPlatformSupported } = require('./agent-stuck-detector');
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
- // Wait for in-flight execution to complete (up to 5 seconds)
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
- agent._currentExecution,
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
- } catch {
258
- // Ignore errors from cancelled execution
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
- agent._currentExecution = null;
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
- agent.livenessCheckInterval = setInterval(() => {
1272
- const hasRecoverableTask =
1273
- Boolean(agent.currentTask) ||
1274
- Boolean(agent.isolation?.enabled && agent.currentTaskId) ||
1275
- agent.nestedExecutions?.hasActive === true;
1276
- if (!hasRecoverableTask || agent.livenessTerminationStarted) {
1277
- return;
1278
- }
1279
-
1280
- const now = Date.now();
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 = providerFailure || extractProviderFailure(output, providerName);
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 || extractProviderFailure(state.output, providerName);
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
- const classified = state.skipStructuredResultCheck
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
- finalizeLogFollow(agent, state);
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 || extractProviderFailure(state.fullOutput, providerName);
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 ? new Date(timestampMatch[1]).getTime() : Date.now();
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,