@the-open-engine/zeroshot 6.37.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 (74) hide show
  1. package/cli/lib/setup-scanner-worker.js +2 -9
  2. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -1
  3. package/lib/agent-cli-provider/adapters/claude-recovery.js +17 -3
  4. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -1
  5. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
  6. package/lib/agent-cli-provider/adapters/index.js +7 -0
  7. package/lib/agent-cli-provider/adapters/index.js.map +1 -1
  8. package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -1
  9. package/lib/agent-cli-provider/adapters/pi.js +86 -97
  10. package/lib/agent-cli-provider/adapters/pi.js.map +1 -1
  11. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -1
  12. package/lib/agent-cli-provider/contract-parse.js +5 -1
  13. package/lib/agent-cli-provider/contract-parse.js.map +1 -1
  14. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
  15. package/lib/agent-cli-provider/invoke-evidence.js +8 -1
  16. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
  17. package/lib/agent-cli-provider/pi/credentials.d.ts +2 -0
  18. package/lib/agent-cli-provider/pi/credentials.d.ts.map +1 -0
  19. package/lib/agent-cli-provider/pi/credentials.js +51 -0
  20. package/lib/agent-cli-provider/pi/credentials.js.map +1 -0
  21. package/lib/agent-cli-provider/pi/events.d.ts +5 -0
  22. package/lib/agent-cli-provider/pi/events.d.ts.map +1 -0
  23. package/lib/agent-cli-provider/pi/events.js +124 -0
  24. package/lib/agent-cli-provider/pi/events.js.map +1 -0
  25. package/lib/agent-cli-provider/pi/protocol-state.d.ts +6 -0
  26. package/lib/agent-cli-provider/pi/protocol-state.d.ts.map +1 -0
  27. package/lib/agent-cli-provider/pi/protocol-state.js +138 -0
  28. package/lib/agent-cli-provider/pi/protocol-state.js.map +1 -0
  29. package/lib/agent-cli-provider/pi/release.d.ts +4 -0
  30. package/lib/agent-cli-provider/pi/release.d.ts.map +1 -0
  31. package/lib/agent-cli-provider/pi/release.js +7 -0
  32. package/lib/agent-cli-provider/pi/release.js.map +1 -0
  33. package/lib/agent-cli-provider/probe-availability.d.ts +5 -0
  34. package/lib/agent-cli-provider/probe-availability.d.ts.map +1 -0
  35. package/lib/agent-cli-provider/probe-availability.js +11 -0
  36. package/lib/agent-cli-provider/probe-availability.js.map +1 -0
  37. package/lib/agent-cli-provider/provider-registry.d.ts +13 -9
  38. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  39. package/lib/agent-cli-provider/provider-registry.js +42 -8
  40. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  41. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  42. package/lib/agent-cli-provider/single-agent-runtime.js +15 -5
  43. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  44. package/lib/agent-cli-provider/types.d.ts +3 -0
  45. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  46. package/lib/agent-cli-provider/types.js.map +1 -1
  47. package/lib/cluster-worker/engine-adapter.js +9 -1
  48. package/lib/docker-config.js +6 -1
  49. package/lib/provider-credential-path.js +36 -0
  50. package/lib/start-cluster.js +8 -0
  51. package/npm-shrinkwrap.json +2 -2
  52. package/package.json +1 -1
  53. package/src/agent/agent-task-executor.js +79 -20
  54. package/src/agent/output-extraction.js +115 -0
  55. package/src/agent/pi-terminal-lifecycle.js +242 -0
  56. package/src/agent/provider-control-plane.js +26 -0
  57. package/src/agent/provider-terminal-failure.js +49 -24
  58. package/src/agent-cli-provider/adapters/claude-recovery.ts +15 -2
  59. package/src/agent-cli-provider/adapters/index.ts +6 -0
  60. package/src/agent-cli-provider/adapters/pi.ts +105 -125
  61. package/src/agent-cli-provider/contract-parse.ts +8 -1
  62. package/src/agent-cli-provider/invoke-evidence.ts +11 -1
  63. package/src/agent-cli-provider/pi/credentials.ts +47 -0
  64. package/src/agent-cli-provider/pi/events.ts +148 -0
  65. package/src/agent-cli-provider/pi/protocol-state.ts +163 -0
  66. package/src/agent-cli-provider/pi/release.ts +4 -0
  67. package/src/agent-cli-provider/probe-availability.ts +19 -0
  68. package/src/agent-cli-provider/provider-registry.ts +48 -9
  69. package/src/agent-cli-provider/single-agent-runtime.ts +27 -6
  70. package/src/agent-cli-provider/types.ts +3 -0
  71. package/src/config-validator.js +1 -3
  72. package/src/isolation-manager.js +43 -29
  73. package/src/orchestrator.js +33 -1
  74. package/task-lib/watcher-output-runtime.js +16 -5
@@ -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,
@@ -2060,7 +2100,7 @@ function buildKillHandler({ agent, taskId, state, providerName, resolve }) {
2060
2100
  error: reason,
2061
2101
  code: details.code || null,
2062
2102
  taskId,
2063
- tokenUsage: extractTokenUsage(state.output, providerName),
2103
+ tokenUsage: extractTokenUsage(state.output, providerName, state),
2064
2104
  });
2065
2105
  },
2066
2106
  };
@@ -2734,6 +2774,7 @@ async function captureIsolatedFinalOutputTail(
2734
2774
  ]);
2735
2775
  if (finalReadResult.code === 0 && finalReadResult.stdout) {
2736
2776
  if (missingBytes > readBytes) {
2777
+ if (providerName === 'pi') state.piProtocolPrefixOmitted = true;
2737
2778
  resetControlPlaneTail(state.controlPlaneOutput, { prefixOmitted: true });
2738
2779
  state.lineBuffer = createLogRecordBuffer();
2739
2780
  }
@@ -2760,19 +2801,21 @@ function resolveIsolatedFailure({
2760
2801
  providerName,
2761
2802
  state,
2762
2803
  status,
2804
+ statusOutput,
2763
2805
  isNotFound,
2764
2806
  clusterId,
2765
2807
  logFilePath,
2766
2808
  }) {
2767
2809
  if (success) return { providerFailure: null, errorContext: structuredError };
2768
2810
  const providerFailure =
2769
- state.providerFailure || extractProviderFailure(state.fullOutput, providerName);
2811
+ state.providerFailure ||
2812
+ (providerName === 'pi' ? null : extractProviderFailure(state.fullOutput, providerName));
2770
2813
  const errorContext =
2771
2814
  providerFailure?.error ||
2772
2815
  structuredError ||
2773
2816
  extractErrorContext({
2774
2817
  output: state.fullOutput,
2775
- statusOutput: status ? `Status: ${status}` : '',
2818
+ statusOutput: statusOutput || (status ? `Status: ${status}` : ''),
2776
2819
  taskId,
2777
2820
  isNotFound,
2778
2821
  providerName,
@@ -2797,6 +2840,7 @@ function settleIsolatedTerminalStatus({
2797
2840
  taskId,
2798
2841
  providerName,
2799
2842
  status,
2843
+ statusOutput = '',
2800
2844
  isNotFound = false,
2801
2845
  state,
2802
2846
  cleanup,
@@ -2805,7 +2849,6 @@ function settleIsolatedTerminalStatus({
2805
2849
  }) {
2806
2850
  if (state.resolved) return Promise.resolve();
2807
2851
  if (state.terminalSettlementPromise) return state.terminalSettlementPromise;
2808
-
2809
2852
  state.taskExited = true;
2810
2853
  if (status) {
2811
2854
  state.durableTaskTerminal = true;
@@ -2842,6 +2885,15 @@ function settleIsolatedTerminalStatus({
2842
2885
  success = evaluated.success;
2843
2886
  structuredError = evaluated.error;
2844
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;
2845
2897
  const { providerFailure, errorContext } = resolveIsolatedFailure({
2846
2898
  success,
2847
2899
  structuredError,
@@ -2850,6 +2902,7 @@ function settleIsolatedTerminalStatus({
2850
2902
  providerName,
2851
2903
  state,
2852
2904
  status,
2905
+ statusOutput,
2853
2906
  isNotFound,
2854
2907
  clusterId,
2855
2908
  logFilePath,
@@ -2860,7 +2913,6 @@ function settleIsolatedTerminalStatus({
2860
2913
  ? state._cachedParsedResult
2861
2914
  : await agent._parseResultOutput(state.fullOutput);
2862
2915
  }
2863
-
2864
2916
  settleIsolatedFollower({
2865
2917
  agent,
2866
2918
  state,
@@ -2872,7 +2924,7 @@ function settleIsolatedTerminalStatus({
2872
2924
  taskId,
2873
2925
  parsedResult,
2874
2926
  error: errorContext,
2875
- tokenUsage: extractTokenUsage(state.fullOutput, providerName),
2927
+ tokenUsage: extractTokenUsage(state.fullOutput, providerName, state),
2876
2928
  providerFailure,
2877
2929
  vertexModelError,
2878
2930
  },
@@ -2908,7 +2960,7 @@ function buildIsolatedLifecycleHandle({
2908
2960
  error: reason,
2909
2961
  code: details.code || null,
2910
2962
  taskId,
2911
- tokenUsage: extractTokenUsage(state.fullOutput, providerName),
2963
+ tokenUsage: extractTokenUsage(state.fullOutput, providerName, state),
2912
2964
  },
2913
2965
  });
2914
2966
  };
@@ -2971,10 +3023,14 @@ function buildIsolatedLifecycleHandle({
2971
3023
  }
2972
3024
 
2973
3025
  function parseIsolatedLogLine(line) {
2974
- const timestampMatch = line.match(/^\[(\d{4}-\d{2}-\d{2}T[^\]]+)\]\s*(.*)$/);
2975
- 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();
2976
3032
  const content = timestampMatch ? timestampMatch[2] : line;
2977
- return { timestamp, content };
3033
+ return { timestamp, content, timestamped: timestampMatch !== null };
2978
3034
  }
2979
3035
 
2980
3036
  function publishIsolatedOutputRecord(agent, providerName, taskId, record) {
@@ -2996,7 +3052,8 @@ function publishIsolatedOutputRecord(agent, providerName, taskId, record) {
2996
3052
  }
2997
3053
 
2998
3054
  function retainIsolatedLine(state, providerName, line) {
2999
- const { timestamp, content } = parseIsolatedLogLine(line);
3055
+ const { timestamp, content, timestamped } = parseIsolatedLogLine(line);
3056
+ if (!content.trim() || shouldSkipLogLine(content, providerName, timestamped)) return null;
3000
3057
  const controlPlaneContent = redactTerminalFailureForControlPlane(state, providerName, content);
3001
3058
  return appendControlPlaneRecord(state.controlPlaneOutput, {
3002
3059
  content: controlPlaneContent,
@@ -3008,6 +3065,7 @@ function retainIsolatedLine(state, providerName, line) {
3008
3065
  function broadcastIsolatedLine({ agent, providerName, taskId, state, line }) {
3009
3066
  const followerState = ensureControlPlaneOutputState(state);
3010
3067
  const record = retainIsolatedLine(followerState, providerName, line);
3068
+ if (record === null) return;
3011
3069
  publishLiveControlPlaneRecord(followerState.controlPlaneOutput, record, (item) =>
3012
3070
  publishIsolatedOutputRecord(agent, providerName, taskId, item)
3013
3071
  );
@@ -3134,6 +3192,7 @@ async function checkIsolatedStatus({
3134
3192
  taskId,
3135
3193
  providerName,
3136
3194
  status,
3195
+ statusOutput,
3137
3196
  isNotFound,
3138
3197
  state,
3139
3198
  cleanup,
@@ -347,6 +347,7 @@ function extractClaudeVertexModelError(output, { useVertex = false } = {}) {
347
347
  * - Codex: {type:"turn.failed", error:{message:"msg"}}
348
348
  * - Gemini: {type:"result", status:"error", error:{message:"msg"}} or {type:"error", severity:"error"}
349
349
  * - Opencode: {type:"session.error", error:{message:"msg"}}
350
+ * - Pi: latest assistant message_end, finalized only by a later agent_settled
350
351
  *
351
352
  * @param {string} providerName - Active provider whose terminal errors may be inspected
352
353
  * @returns {{error: string, provider: string}|null} Error info or null
@@ -397,6 +398,22 @@ function opencodeFailureFromObject(obj) {
397
398
  };
398
399
  }
399
400
 
401
+ function piFailureFromMessage(message) {
402
+ const stopReason = message.stopReason;
403
+ const errorMessage = message.errorMessage;
404
+ if (stopReason === 'deferred') {
405
+ return {
406
+ ...cliErrorDetail('Pi turn deferred without completing.', 'Pi turn deferred'),
407
+ provider: 'pi',
408
+ };
409
+ }
410
+ if (stopReason !== 'error' && stopReason !== 'aborted' && !errorMessage) return null;
411
+ return {
412
+ ...cliErrorDetail(errorMessage || stopReason, 'Pi turn failed'),
413
+ provider: 'pi',
414
+ };
415
+ }
416
+
400
417
  function failureFromProviderObject(obj, providerName) {
401
418
  if (providerName === 'claude') return claudeFailureFromObject(obj);
402
419
  if (providerName === 'codex') return codexFailureFromObject(obj);
@@ -405,7 +422,105 @@ function failureFromProviderObject(obj, providerName) {
405
422
  return null;
406
423
  }
407
424
 
425
+ function piProtocolFailure(message) {
426
+ return { ...cliErrorDetail(message, message), provider: 'pi' };
427
+ }
428
+
429
+ const PI_STOP_REASONS = new Set(['stop', 'length', 'toolUse', 'error', 'aborted', 'deferred']);
430
+ const PI_USAGE_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'];
431
+ const PI_COST_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'total'];
432
+
433
+ function parsePiProtocolObject(content) {
434
+ if (!content.startsWith('{')) return null;
435
+ try {
436
+ const parsed = JSON.parse(content);
437
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
438
+ } catch {
439
+ return null;
440
+ }
441
+ }
442
+
443
+ function isPiRecord(value) {
444
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
445
+ }
446
+
447
+ function hasPiNumericFields(value, fields) {
448
+ return isPiRecord(value) && fields.every((field) => typeof value[field] === 'number');
449
+ }
450
+
451
+ function isValidPiUsage(usage) {
452
+ if (!hasPiNumericFields(usage, PI_USAGE_FIELDS)) return false;
453
+ if (!hasPiNumericFields(usage.cost, PI_COST_FIELDS)) return false;
454
+ return (
455
+ (usage.cacheWrite1h === undefined || typeof usage.cacheWrite1h === 'number') &&
456
+ (usage.reasoning === undefined || typeof usage.reasoning === 'number')
457
+ );
458
+ }
459
+
460
+ function isValidPiAssistantMessage(message) {
461
+ return (
462
+ isPiRecord(message) &&
463
+ message.role === 'assistant' &&
464
+ Array.isArray(message.content) &&
465
+ isValidPiUsage(message.usage) &&
466
+ PI_STOP_REASONS.has(message.stopReason)
467
+ );
468
+ }
469
+
470
+ function piMessageRole(message) {
471
+ if (!message || typeof message !== 'object' || Array.isArray(message)) return null;
472
+ return typeof message.role === 'string' ? message.role : null;
473
+ }
474
+
475
+ function applyPiProtocolLine(content, state) {
476
+ if (!content) return null;
477
+ if (
478
+ content.startsWith('[ZEROSHOT] Earlier provider output omitted') ||
479
+ content.startsWith('[ZEROSHOT] Provider output record retained') ||
480
+ content.startsWith('[ZEROSHOT][PROVIDER_STDERR] ')
481
+ ) {
482
+ return null;
483
+ }
484
+ if (state.settled) return piProtocolFailure('Pi emitted output after agent_settled.');
485
+ const parsed = parsePiProtocolObject(content);
486
+ if (parsed === null) return piProtocolFailure('Pi JSON stream contained malformed output.');
487
+ if (typeof parsed.type !== 'string') {
488
+ return piProtocolFailure('Pi JSON stream contained an event without a valid type.');
489
+ }
490
+ if (parsed.type === 'message_end') {
491
+ const role = piMessageRole(parsed.message);
492
+ if (role === null) return piProtocolFailure('Pi message_end did not contain a valid message.');
493
+ if (role !== 'assistant') return null;
494
+ if (!isValidPiAssistantMessage(parsed.message)) {
495
+ return piProtocolFailure('Pi message_end did not contain a valid assistant message.');
496
+ }
497
+ state.latestAssistant = parsed.message;
498
+ } else if (parsed.type === 'agent_settled') {
499
+ state.settled = true;
500
+ }
501
+ return null;
502
+ }
503
+
504
+ function extractSettledPiFailure(lines) {
505
+ const state = { latestAssistant: null, settled: false };
506
+ for (const line of lines) {
507
+ const content = stripTimestamp(line);
508
+ const failure = applyPiProtocolLine(content, state);
509
+ if (failure) return failure;
510
+ }
511
+
512
+ if (!state.settled) return piProtocolFailure('Pi JSON stream ended before agent_settled.');
513
+ if (state.latestAssistant === null) {
514
+ return piProtocolFailure('Pi settled without a valid assistant message_end.');
515
+ }
516
+ return piFailureFromMessage(state.latestAssistant);
517
+ }
518
+
408
519
  function extractCliFailure(output, providerName = 'claude') {
520
+ if (providerName === 'pi') {
521
+ const lines = typeof output === 'string' ? output.split('\n') : [];
522
+ return extractSettledPiFailure(lines);
523
+ }
409
524
  if (!output || typeof output !== 'string') return null;
410
525
 
411
526
  const lines = output.split('\n');
@@ -0,0 +1,242 @@
1
+ // @ts-nocheck
2
+
3
+ const { parseProviderEvent, providerFailureFields } = require('./provider-control-plane');
4
+
5
+ const ZEROSHOT_PI_METADATA_PREFIXES = [
6
+ '[ZEROSHOT] Earlier provider output omitted',
7
+ '[ZEROSHOT][PROVIDER_STDERR] ',
8
+ '[ZEROSHOT][FATAL] ',
9
+ ];
10
+ const ZEROSHOT_RETAINED_RECORD_PREFIX = '[ZEROSHOT] Provider output record retained';
11
+ const PI_STOP_REASONS = new Set(['stop', 'length', 'toolUse', 'error', 'aborted', 'deferred']);
12
+ const PI_REQUIRED_TOKEN_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'];
13
+ const PI_OPTIONAL_TOKEN_FIELDS = ['cacheWrite1h', 'reasoning'];
14
+ const PI_TOKEN_FIELDS = [...PI_REQUIRED_TOKEN_FIELDS, ...PI_OPTIONAL_TOKEN_FIELDS];
15
+ const PI_COST_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'total'];
16
+ const PI_PENDING_EVENT_TYPES = new Set([
17
+ 'turn_end',
18
+ 'agent_end',
19
+ 'auto_retry_start',
20
+ 'auto_retry_end',
21
+ ]);
22
+
23
+ function pendingFailureMetadata(failure) {
24
+ return {
25
+ zeroshot_pending_failure: {
26
+ provider: failure.provider,
27
+ category: failure.category,
28
+ kind: failure.classification.kind,
29
+ retryable: failure.classification.retryable,
30
+ diagnostic: failure.diagnostic,
31
+ },
32
+ };
33
+ }
34
+
35
+ function numericFields(fields, source) {
36
+ return Object.fromEntries(
37
+ fields.map((field) => [field, typeof source[field] === 'number' ? source[field] : 0])
38
+ );
39
+ }
40
+
41
+ function redactedUsage(usage) {
42
+ const value = usage && typeof usage === 'object' && !Array.isArray(usage) ? usage : {};
43
+ return {
44
+ ...numericFields(PI_REQUIRED_TOKEN_FIELDS, value),
45
+ ...(typeof value.cacheWrite1h === 'number' ? { cacheWrite1h: value.cacheWrite1h } : {}),
46
+ ...(typeof value.reasoning === 'number' ? { reasoning: value.reasoning } : {}),
47
+ cost: numericFields(PI_COST_FIELDS, isRecord(value.cost) ? value.cost : {}),
48
+ };
49
+ }
50
+
51
+ function isRecord(value) {
52
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
53
+ }
54
+
55
+ function hasNumericFields(value, fields) {
56
+ return isRecord(value) && fields.every((field) => typeof value[field] === 'number');
57
+ }
58
+
59
+ function addNumericFields(target, source, fields) {
60
+ for (const field of fields) {
61
+ if (typeof source[field] === 'number') target[field] = (target[field] || 0) + source[field];
62
+ }
63
+ }
64
+
65
+ function rememberTokenUsage(state, usage) {
66
+ if (!hasNumericFields(usage, PI_REQUIRED_TOKEN_FIELDS)) return false;
67
+ if (!hasNumericFields(usage.cost, PI_COST_FIELDS)) return false;
68
+ if (
69
+ !PI_OPTIONAL_TOKEN_FIELDS.every(
70
+ (field) => usage[field] === undefined || typeof usage[field] === 'number'
71
+ )
72
+ ) {
73
+ return false;
74
+ }
75
+ state.piUsage = state.piUsage || { tokens: {}, cost: {} };
76
+ addNumericFields(state.piUsage.tokens, usage, PI_TOKEN_FIELDS);
77
+ addNumericFields(state.piUsage.cost, usage.cost, PI_COST_FIELDS);
78
+ return true;
79
+ }
80
+
81
+ function getPiTokenUsage(state) {
82
+ if (!state?.piUsage) return null;
83
+ const { tokens, cost } = state.piUsage;
84
+ return {
85
+ inputTokens: tokens.input || 0,
86
+ outputTokens: tokens.output || 0,
87
+ cacheReadInputTokens: tokens.cacheRead || 0,
88
+ cacheCreationInputTokens: tokens.cacheWrite || 0,
89
+ totalCostUsd: cost.total || 0,
90
+ durationMs: null,
91
+ modelUsage: { ...tokens, cost: { ...cost } },
92
+ };
93
+ }
94
+
95
+ function isValidAssistantMessage(message) {
96
+ return Array.isArray(message?.content) && PI_STOP_REASONS.has(message.stopReason);
97
+ }
98
+
99
+ function redactPendingFailure(failure, parsed) {
100
+ const pending = pendingFailureMetadata(failure);
101
+ if (parsed.type === 'agent_end') {
102
+ return JSON.stringify({
103
+ type: parsed.type,
104
+ messages: [],
105
+ willRetry: parsed.willRetry === true,
106
+ ...pending,
107
+ });
108
+ }
109
+ if (parsed.type === 'auto_retry_end') {
110
+ return JSON.stringify({ type: parsed.type, success: false, ...pending });
111
+ }
112
+ if (parsed.type === 'auto_retry_start') {
113
+ return JSON.stringify({
114
+ type: parsed.type,
115
+ ...numericFields(
116
+ ['attempt', 'maxAttempts', 'delayMs'].filter((field) => typeof parsed[field] === 'number'),
117
+ parsed
118
+ ),
119
+ ...pending,
120
+ });
121
+ }
122
+ return JSON.stringify({
123
+ type: parsed.type,
124
+ message: {
125
+ role: 'assistant',
126
+ content: [],
127
+ usage: redactedUsage(parsed.message?.usage),
128
+ stopReason: 'error',
129
+ errorMessage: 'Pi provider turn failed; awaiting agent settlement.',
130
+ },
131
+ ...(parsed.type === 'turn_end' ? { toolResults: [] } : {}),
132
+ ...pending,
133
+ });
134
+ }
135
+
136
+ function finalFailureEnvelope(failure) {
137
+ return JSON.stringify({
138
+ type: 'agent_settled',
139
+ ...providerFailureFields(failure),
140
+ });
141
+ }
142
+
143
+ function rememberProtocolFailure(state, content, extractProviderFailure) {
144
+ const failure = extractProviderFailure(content, 'pi');
145
+ if (failure) {
146
+ state.piProtocolFailure = failure;
147
+ state.providerFailure = failure;
148
+ }
149
+ return failure;
150
+ }
151
+
152
+ function failProtocolLine(state, content, extractProviderFailure) {
153
+ const failure = rememberProtocolFailure(state, content, extractProviderFailure);
154
+ return failure ? finalFailureEnvelope(failure) : content;
155
+ }
156
+
157
+ function handleMessageEnd(state, content, parsed, extractProviderFailure) {
158
+ const role = parsed.message?.role;
159
+ if (typeof role !== 'string') return failProtocolLine(state, content, extractProviderFailure);
160
+ const hasCountedUsage = role === 'assistant' || parsed.message?.usage !== undefined;
161
+ if (
162
+ ['assistant', 'toolResult'].includes(role) &&
163
+ hasCountedUsage &&
164
+ !rememberTokenUsage(state, parsed.message?.usage)
165
+ ) {
166
+ return failProtocolLine(state, 'invalid Pi usage', extractProviderFailure);
167
+ }
168
+ if (role !== 'assistant') return content;
169
+ if (!isValidAssistantMessage(parsed.message)) {
170
+ return failProtocolLine(state, content, extractProviderFailure);
171
+ }
172
+ const failure = extractProviderFailure(
173
+ `${content}\n${JSON.stringify({ type: 'agent_settled' })}`,
174
+ 'pi'
175
+ );
176
+ state.piLatestAssistantObserved = true;
177
+ state.pendingPiFailure = failure;
178
+ if (failure) return redactPendingFailure(failure, parsed);
179
+ if (!state.piProtocolFailure) state.providerFailure = null;
180
+ return content;
181
+ }
182
+
183
+ function usageForEvent(parsed) {
184
+ if (parsed.type === 'compaction_end' && parsed.result?.usage !== undefined) {
185
+ return parsed.result.usage;
186
+ }
187
+ if (
188
+ parsed.type === 'entry_appended' &&
189
+ parsed.entry?.type === 'branch_summary' &&
190
+ parsed.entry.usage !== undefined
191
+ ) {
192
+ return parsed.entry.usage;
193
+ }
194
+ return undefined;
195
+ }
196
+
197
+ function handleSettlement(state, content, extractProviderFailure) {
198
+ state.piProtocolSettled = true;
199
+ const missingAssistantFailure = state.piLatestAssistantObserved
200
+ ? null
201
+ : extractProviderFailure(JSON.stringify({ type: 'agent_settled' }), 'pi');
202
+ const failure = state.piProtocolFailure || state.pendingPiFailure || missingAssistantFailure;
203
+ state.pendingPiFailure = null;
204
+ state.providerFailure = failure || null;
205
+ return failure ? finalFailureEnvelope(failure) : content;
206
+ }
207
+
208
+ function redactPiFailureForControlPlane(state, content, extractProviderFailure) {
209
+ if (content.startsWith(ZEROSHOT_RETAINED_RECORD_PREFIX)) {
210
+ state.piProtocolPrefixOmitted = true;
211
+ return content;
212
+ }
213
+ if (ZEROSHOT_PI_METADATA_PREFIXES.some((prefix) => content.startsWith(prefix))) return content;
214
+ const parsed = parseProviderEvent(content);
215
+ if (parsed === null || typeof parsed.type !== 'string') {
216
+ return failProtocolLine(state, content, extractProviderFailure);
217
+ }
218
+ if (state.piProtocolSettled) {
219
+ return failProtocolLine(
220
+ state,
221
+ `${JSON.stringify({ type: 'agent_settled' })}\n${content}`,
222
+ extractProviderFailure
223
+ );
224
+ }
225
+ state.piProtocolObserved = true;
226
+
227
+ if (parsed.type === 'message_end') {
228
+ return handleMessageEnd(state, content, parsed, extractProviderFailure);
229
+ }
230
+ const usage = usageForEvent(parsed);
231
+ if (usage !== undefined && !rememberTokenUsage(state, usage)) {
232
+ return failProtocolLine(state, 'invalid Pi usage', extractProviderFailure);
233
+ }
234
+ if (state.pendingPiFailure && PI_PENDING_EVENT_TYPES.has(parsed.type)) {
235
+ return redactPendingFailure(state.pendingPiFailure, parsed);
236
+ }
237
+ return parsed.type === 'agent_settled'
238
+ ? handleSettlement(state, content, extractProviderFailure)
239
+ : content;
240
+ }
241
+
242
+ module.exports = { getPiTokenUsage, redactPiFailureForControlPlane };
@@ -0,0 +1,26 @@
1
+ // @ts-nocheck
2
+
3
+ function parseProviderEvent(content) {
4
+ try {
5
+ const parsed = JSON.parse(content);
6
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+
12
+ function providerFailureFields(failure) {
13
+ return {
14
+ error: { message: failure.error },
15
+ zeroshot_failure: {
16
+ provider: failure.provider,
17
+ event: failure.event,
18
+ category: failure.category,
19
+ kind: failure.classification.kind,
20
+ retryable: failure.classification.retryable,
21
+ diagnostic: failure.diagnostic,
22
+ },
23
+ };
24
+ }
25
+
26
+ module.exports = { parseProviderEvent, providerFailureFields };