@probelabs/probe 0.6.0-rc339 → 0.6.0-rc341

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 (30) hide show
  1. package/bin/binaries/{probe-v0.6.0-rc339-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc341-aarch64-apple-darwin.tar.gz} +0 -0
  2. package/bin/binaries/{probe-v0.6.0-rc339-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc341-aarch64-unknown-linux-musl.tar.gz} +0 -0
  3. package/bin/binaries/{probe-v0.6.0-rc339-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc341-x86_64-apple-darwin.tar.gz} +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc341-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/{probe-v0.6.0-rc339-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc341-x86_64-unknown-linux-musl.tar.gz} +0 -0
  6. package/build/agent/ProbeAgent.d.ts +37 -9
  7. package/build/agent/ProbeAgent.js +196 -62
  8. package/build/agent/acp/tools.js +1 -1
  9. package/build/agent/engines/codex.js +189 -52
  10. package/build/agent/engines/governed-answer-failure.js +164 -5
  11. package/build/agent/engines/governed-codex-exec.js +1227 -0
  12. package/build/agent/engines/governed-codex-profile.js +80 -28
  13. package/build/agent/governance/index.d.ts +18 -0
  14. package/build/agent/governance/index.js +14 -0
  15. package/build/index.js +26 -0
  16. package/cjs/agent/ProbeAgent.cjs +3291 -1117
  17. package/cjs/index.cjs +3318 -1119
  18. package/index.d.ts +53 -7
  19. package/package.json +1 -1
  20. package/src/agent/ProbeAgent.d.ts +37 -9
  21. package/src/agent/ProbeAgent.js +196 -62
  22. package/src/agent/acp/tools.js +1 -1
  23. package/src/agent/engines/codex.js +189 -52
  24. package/src/agent/engines/governed-answer-failure.js +164 -5
  25. package/src/agent/engines/governed-codex-exec.js +1227 -0
  26. package/src/agent/engines/governed-codex-profile.js +80 -28
  27. package/src/agent/governance/index.d.ts +18 -0
  28. package/src/agent/governance/index.js +14 -0
  29. package/src/index.js +26 -0
  30. package/bin/binaries/probe-v0.6.0-rc339-aarch64-apple-darwin.tar.gz +0 -0
@@ -82,6 +82,12 @@ export interface ProbeAgentOptions {
82
82
  allowedTools?: string[] | null;
83
83
  /** Attested, fail-closed Codex runtime profile. Requires provider codex and an exact allowedTools match. */
84
84
  governedCodexProfile?: GovernedCodexProfile;
85
+ /** Transport for an attested governed Codex profile. The exec transport requires codexBin and codexSha256. */
86
+ governedCodexTransport?: 'mcp-server-v1' | 'exec-jsonl-default-auth-v1';
87
+ /** Absolute Codex executable used by exec-jsonl-default-auth-v1. */
88
+ codexBin?: string;
89
+ /** Caller-supplied SHA-256 (sha256:<64 hex> or bare 64 hex) for codexBin. */
90
+ codexSha256?: string;
85
91
  /** Convenience flag to disable all tools (equivalent to allowedTools: []). Takes precedence over allowedTools if set. */
86
92
  disableTools?: boolean;
87
93
  /** Retry configuration for handling transient API failures */
@@ -104,9 +110,9 @@ export interface ProbeAgentOptions {
104
110
  completionPrompt?: string;
105
111
  /** Enable task management system for tracking multi-step progress */
106
112
  enableTasks?: boolean;
107
- /** Timeout in ms for AI requests (default: 120000 or REQUEST_TIMEOUT env var). Used to abort hung requests. */
113
+ /** Timeout in ms for AI requests (default: 120000 or REQUEST_TIMEOUT env var). `0` disables the per-request deadline; Codex startup remains bounded. */
108
114
  requestTimeout?: number;
109
- /** Maximum timeout in ms for the entire operation including all retries and fallbacks (default: 300000 or MAX_OPERATION_TIMEOUT env var). This is the absolute maximum time for streamTextWithRetryAndFallback. */
115
+ /** Maximum timeout in ms for the entire operation including all retries and fallbacks (default: 300000 or MAX_OPERATION_TIMEOUT env var). `0` disables the overall operation deadline. */
110
116
  maxOperationTimeout?: number;
111
117
  /** Timeout behavior: 'graceful' winds down with bonus steps, 'hard' aborts immediately, 'negotiated' uses a timeout observer (separate LLM call) to decide extensions (default: 'graceful'). Env var: TIMEOUT_BEHAVIOR */
112
118
  timeoutBehavior?: 'graceful' | 'hard' | 'negotiated';
@@ -272,7 +278,9 @@ export interface GovernedResultIdentity {
272
278
  export type GovernedCodexProfile =
273
279
  { version: 'probe.governed-codex-profile/v1'; profileId: 'luna-xhigh-readonly-v1'; engine: 'codex'; model: 'gpt-5.6-luna'; reasoningEffort: 'xhigh'; sandbox: 'read-only'; approvalPolicy: 'never'; cwd: string; probeTools: ['search', 'extract', 'listFiles']; fallback: false; retries: 0; }
274
280
  /** Admits pinned-protocol `exec` inside the attested sandbox; does not claim commands are semantically safe. */
275
- | { version: 'probe.governed-codex-profile/v2'; profileId: 'luna-xhigh-readonly-native-exec-v1'; engine: 'codex'; model: 'gpt-5.6-luna'; reasoningEffort: 'xhigh'; sandbox: 'read-only'; approvalPolicy: 'never'; cwd: string; probeMcpTools: ['search', 'extract', 'listFiles']; codexNativeTools: ['exec']; fallback: false; retries: 0; };
281
+ | { version: 'probe.governed-codex-profile/v2'; profileId: 'luna-xhigh-readonly-native-exec-v1'; engine: 'codex'; model: 'gpt-5.6-luna'; reasoningEffort: 'xhigh'; sandbox: 'read-only'; approvalPolicy: 'never'; cwd: string; probeMcpTools: ['search', 'extract', 'listFiles']; codexNativeTools: ['exec']; fallback: false; retries: 0; }
282
+ /** Admits observed native `apply_patch` and `exec` inside the configured workspace-write cwd; callers bind that cwd to an isolated worktree. */
283
+ | { version: 'probe.governed-codex-profile/v3'; profileId: 'luna-xhigh-isolated-writer-v1'; engine: 'codex'; model: 'gpt-5.6-luna'; reasoningEffort: 'xhigh'; sandbox: 'workspace-write'; approvalPolicy: 'never'; cwd: string; probeMcpTools: ['search', 'extract', 'listFiles']; codexNativeTools: ['apply_patch', 'exec']; fallback: false; retries: 0; };
276
284
 
277
285
  export interface GovernedCodexRuntimeAttestation {
278
286
  version: 'probe.governed-codex-attestation/v1';
@@ -285,7 +293,7 @@ export interface GovernedCodexRuntimeAttestation {
285
293
 
286
294
  export interface GovernedAnswerResult {
287
295
  data: unknown;
288
- runtimeAttestation: GovernedCodexRuntimeAttestation | GovernedCodexRuntimeAttestationV3;
296
+ runtimeAttestation: GovernedCodexRuntimeAttestation | GovernedCodexRuntimeAttestationV3 | GovernedCodexExecAttestation;
289
297
  }
290
298
 
291
299
  export interface GovernedCodexRuntimeAttestationV2 {
@@ -301,26 +309,46 @@ export interface GovernedCodexRuntimeAttestationV2 {
301
309
 
302
310
  export interface GovernedInvocationAnswerResult {
303
311
  data: unknown;
304
- runtimeAttestation: GovernedCodexRuntimeAttestationV2 | GovernedCodexRuntimeAttestationV3;
312
+ runtimeAttestation: GovernedCodexRuntimeAttestationV2 | GovernedCodexRuntimeAttestationV3 | GovernedCodexExecAttestation;
305
313
  }
306
314
 
307
315
  export interface GovernedIdentifiedAnswerResult {
308
316
  data: unknown;
309
- runtimeAttestation: GovernedCodexRuntimeAttestationV2 | GovernedCodexRuntimeAttestationV3;
317
+ runtimeAttestation: GovernedCodexRuntimeAttestationV2 | GovernedCodexRuntimeAttestationV3 | GovernedCodexExecAttestation;
310
318
  resultIdentity: GovernedResultIdentity;
311
319
  }
312
320
 
313
321
  export interface GovernedCodexRuntimeAttestationV3 {
314
322
  version: 'probe.governed-codex-attestation/v3';
315
- profileId: 'luna-xhigh-readonly-native-exec-v1';
316
- requested: { profileDigest: string; cwdDigest: string; probeMcpToolsDigest: string; codexNativeToolsDigest: string; probeMcpTools: ['search', 'extract', 'listFiles']; codexNativeTools: ['exec']; model: 'gpt-5.6-luna'; reasoningEffort: 'xhigh'; sandbox: 'read-only'; approvalPolicy: 'never'; };
317
- observed: { source: 'session_configured+raw_response_item'; model: 'gpt-5.6-luna'; modelProviderId: 'openai'; reasoningEffort: 'xhigh'; approvalPolicy: 'never'; cwdDigest: string; permissionProfileDigest: string; filesystem: 'restricted-read-root'; network: 'restricted'; nativeTools: { total: number; tools: GovernedCodexNativeToolAggregate[]; }; };
323
+ profileId: 'luna-xhigh-readonly-native-exec-v1' | 'luna-xhigh-isolated-writer-v1';
324
+ requested:
325
+ | { profileDigest: string; cwdDigest: string; probeMcpToolsDigest: string; codexNativeToolsDigest: string; probeMcpTools: ['search', 'extract', 'listFiles']; codexNativeTools: ['exec']; model: 'gpt-5.6-luna'; reasoningEffort: 'xhigh'; sandbox: 'read-only'; approvalPolicy: 'never'; }
326
+ | { profileDigest: string; cwdDigest: string; probeMcpToolsDigest: string; codexNativeToolsDigest: string; probeMcpTools: ['search', 'extract', 'listFiles']; codexNativeTools: ['apply_patch', 'exec']; model: 'gpt-5.6-luna'; reasoningEffort: 'xhigh'; sandbox: 'workspace-write'; approvalPolicy: 'never'; };
327
+ observed:
328
+ | { source: 'session_configured+raw_response_item'; model: 'gpt-5.6-luna'; modelProviderId: 'openai'; reasoningEffort: 'xhigh'; approvalPolicy: 'never'; cwdDigest: string; permissionProfileDigest: string; filesystem: 'restricted-read-root'; network: 'restricted'; nativeTools: { total: number; tools: GovernedCodexNativeToolAggregate[]; }; }
329
+ | { source: 'session_configured+raw_response_item'; model: 'gpt-5.6-luna'; modelProviderId: 'openai'; reasoningEffort: 'xhigh'; approvalPolicy: 'never'; cwdDigest: string; permissionProfileDigest: string; filesystem: 'restricted-write-cwd'; network: 'restricted'; nativeTools: { total: number; tools: GovernedCodexNativeToolAggregate[]; }; };
318
330
  executionContext?: { source: 'caller'; invocationDigest: string; };
319
331
  dispatch?: { source: 'probe-host-tools-call'; tool: 'codex'; promptDigest: string; promptBytes: number; };
320
332
  evidence: { sessionEventCount: 1; nativeCallCount: number; probeMcpCallCount: number; };
321
333
  usage: { status: 'unavailable'; };
322
334
  }
323
335
 
336
+ export interface GovernedCodexExecAttestation {
337
+ version: 'probe.governed-codex-exec-attestation/v1';
338
+ profileId: 'luna-xhigh-readonly-v1' | 'luna-xhigh-readonly-native-exec-v1' | 'luna-xhigh-isolated-writer-v1';
339
+ requested: Record<string, unknown>;
340
+ enforced: {
341
+ source: 'probe-host-codex-exec-argv/v1'; transport: 'exec-jsonl-default-auth-v1'; cliPath: string; cliSha256: `sha256:${string}`; cliVersion: string;
342
+ configDigest: `sha256:${string}`; launchDigest: `sha256:${string}`; cwdDigest: string; codexHome: 'omitted';
343
+ environmentPolicy: 'inherit-with-CODEX_HOME-omitted-v1'; ignoreUserConfig: true; ignoreRules: true; ephemeral: true; noShell: true; loopbackMcpDigest: `sha256:${string}`;
344
+ };
345
+ observed: { source: 'codex-exec-jsonl/v1'; threadDigest: string; streamDigest: string; terminal: 'turn.completed'; eventCount: number; completedItemCount: number; agentMessageCount: number; usedToolItems: Array<{ category: 'mcp_tool_call' | 'command_execution' | 'file_change'; name: string | null; status: 'completed'; count: number }>; probeMcpCallCount: number; finalDigest: string; finalBytes: number; processExitCode: 0; processSignal: null; };
346
+ executionContext?: { source: 'caller'; invocationDigest: string };
347
+ dispatch: { source: 'probe-host-exec'; tool: 'codex-exec'; promptDigest: string; promptBytes: number };
348
+ evidence: { eventCount: number; completedItemCount: number; agentMessageCount: number; probeMcpCallCount: number };
349
+ usage: { status: 'observed'; inputTokens: number; cachedInputTokens: number; outputTokens: number };
350
+ }
351
+
324
352
  /**
325
353
  * Clone options for creating a new agent with shared history
326
354
  */
@@ -23,6 +23,10 @@ export const ENGINE_ACTIVITY_TIMEOUT_MIN = 5000;
23
23
  */
24
24
  export const ENGINE_ACTIVITY_TIMEOUT_MAX = 600000;
25
25
 
26
+ const REQUEST_TIMEOUT_DEFAULT = 120000;
27
+ const REQUEST_TIMEOUT_MIN = 1000;
28
+ const REQUEST_TIMEOUT_MAX = 3600000;
29
+
26
30
  import { createProviderInstance, DEFAULT_MODELS } from '../utils/provider.js';
27
31
  import { streamText, generateText, tool, stepCountIs, jsonSchema, Output } from 'ai';
28
32
  import { createHash, randomUUID } from 'crypto';
@@ -103,10 +107,16 @@ import {
103
107
  } from './tasks/index.js';
104
108
  import { z } from 'zod';
105
109
  import { validateGovernedCodexProfile } from './engines/governed-codex-profile.js';
110
+ import { normalizeGovernedCodexExecFailure, previewGovernedCodexExecDispatch, validateGovernedCodexExecAttestation } from './engines/governed-codex-exec.js';
106
111
  import { governedAnswerFailure, normalizeGovernedAnswerFailure } from './engines/governed-answer-failure.js';
107
112
 
108
113
  const GOVERNED_RESULT_IDENTITY = 'probe.governed-result-identity/v1';
109
114
  const GOVERNED_RESULT_DOMAIN = 'probe.governed-result-identity/data/v1';
115
+ const GOVERNED_CODEX_EXEC_TRANSPORT = 'exec-jsonl-default-auth-v1';
116
+
117
+ function isGovernedCodexNativeProfile(profile) {
118
+ return Array.isArray(profile?.codexNativeTools);
119
+ }
110
120
 
111
121
  function normalizeGovernedJson(value) {
112
122
  if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;
@@ -129,6 +139,33 @@ function freezeGovernedTree(value) {
129
139
  return value;
130
140
  }
131
141
 
142
+ const GOVERNED_CANDIDATE_ORIGINS = new Set(['result_content', 'raw_final', 'none']);
143
+ const GOVERNED_CANDIDATE_BOUNDARY_FIELDS = [
144
+ 'selectedChunkCount', 'selectedBytes', 'resultTextItemCount', 'resultTextBytes',
145
+ 'rawFinalMessageCount', 'rawFinalPartCount', 'rawFinalBytes'
146
+ ];
147
+
148
+ function governedCandidateUint(value) {
149
+ return Number.isSafeInteger(value) && value >= 0 ? value : 0;
150
+ }
151
+
152
+ function governedCandidateOwn(value, key) {
153
+ if (!value || typeof value !== 'object') return undefined;
154
+ try {
155
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
156
+ return descriptor && 'value' in descriptor ? descriptor.value : undefined;
157
+ } catch { return undefined; }
158
+ }
159
+
160
+ function projectGovernedCandidateBoundary(value) {
161
+ const selectedOriginValue = governedCandidateOwn(value, 'selectedOrigin');
162
+ const selectedOrigin = GOVERNED_CANDIDATE_ORIGINS.has(selectedOriginValue)
163
+ ? selectedOriginValue : 'none';
164
+ const projected = { selectedOrigin };
165
+ for (const field of GOVERNED_CANDIDATE_BOUNDARY_FIELDS) projected[field] = governedCandidateUint(governedCandidateOwn(value, field));
166
+ return Object.freeze(projected);
167
+ }
168
+
132
169
  function identifyGovernedResult(value) {
133
170
  const data = freezeGovernedTree(normalizeGovernedJson(value));
134
171
  const canonical = Buffer.from(JSON.stringify(data), 'utf8');
@@ -329,8 +366,8 @@ export class ProbeAgent {
329
366
  * @param {number} [options.fallback.maxTotalAttempts=10] - Maximum total attempts across all providers
330
367
  * @param {string} [options.completionPrompt] - Custom prompt to run after completion for validation/review (runs before mermaid/JSON validation)
331
368
  * @param {number} [options.maxOutputTokens] - Maximum tokens for tool output before truncation (default: 20000, can also be set via PROBE_MAX_OUTPUT_TOKENS env var)
332
- * @param {number} [options.requestTimeout] - Timeout in ms for AI requests (default: 120000 or REQUEST_TIMEOUT env var). Used to abort hung requests.
333
- * @param {number} [options.maxOperationTimeout] - Maximum timeout in ms for the entire operation including all retries and fallbacks (default: 300000 or MAX_OPERATION_TIMEOUT env var). This is the absolute maximum time for streamTextWithRetryAndFallback.
369
+ * @param {number} [options.requestTimeout] - Timeout in ms for AI requests (default: 120000 or REQUEST_TIMEOUT env var). `0` disables the per-request deadline; the Codex startup handshake remains bounded.
370
+ * @param {number} [options.maxOperationTimeout] - Maximum timeout in ms for the entire operation including all retries and fallbacks (default: 300000 or MAX_OPERATION_TIMEOUT env var). `0` disables the overall operation deadline.
334
371
  * @param {string|number} [options.thinkingEffort] - Native thinking/reasoning effort level: 'low', 'medium', 'high', or a number (budget tokens). When set, passes provider-specific thinking options to the LLM via providerOptions.
335
372
  */
336
373
  constructor(options = {}) {
@@ -353,6 +390,9 @@ export class ProbeAgent {
353
390
  this.searchDelegate = options.searchDelegate !== undefined ? !!options.searchDelegate : true;
354
391
  this.searchDelegateProvider = options.searchDelegateProvider || null;
355
392
  this.searchDelegateModel = options.searchDelegateModel || null;
393
+ this.governedCodexTransport = options.governedCodexTransport || 'mcp-server-v1';
394
+ this.governedCodexBin = options.codexBin ?? options.codexPath;
395
+ this.governedCodexSha256 = options.codexSha256;
356
396
  this.maxResponseTokens = options.maxResponseTokens || (() => {
357
397
  const val = parseInt(process.env.MAX_RESPONSE_TOKENS || '0', 10);
358
398
  if (isNaN(val) || val < 0 || val > 200000) {
@@ -384,6 +424,10 @@ export class ProbeAgent {
384
424
 
385
425
  if (options.governedCodexProfile !== undefined) {
386
426
  if (options.provider !== 'codex') throw new TypeError('governedCodexProfile requires provider codex'); const profile = validateGovernedCodexProfile(options.governedCodexProfile);
427
+ if (!['mcp-server-v1', GOVERNED_CODEX_EXEC_TRANSPORT].includes(this.governedCodexTransport)) throw new TypeError('Invalid governedCodexTransport');
428
+ if (this.governedCodexTransport === GOVERNED_CODEX_EXEC_TRANSPORT &&
429
+ (typeof this.governedCodexBin !== 'string' || !isAbsolute(this.governedCodexBin) ||
430
+ typeof this.governedCodexSha256 !== 'string')) throw new TypeError('exec-jsonl-default-auth-v1 requires codexBin and codexSha256');
387
431
  const probeTools = profile.probeTools ?? profile.probeMcpTools;
388
432
  if (options.disableTools || !Array.isArray(options.allowedTools) || options.allowedTools.length !== probeTools.length || options.allowedTools.some((tool, index) => tool !== probeTools[index])) throw new TypeError('allowedTools must exactly match governedCodexProfile Probe MCP tools');
389
433
  this.governedCodexProfile = profile;
@@ -498,19 +542,21 @@ export class ProbeAgent {
498
542
  // When set, every AI API call acquires a slot before calling the provider.
499
543
  this.concurrencyLimiter = options.concurrencyLimiter || null;
500
544
 
501
- // Request timeout configuration (default 2 minutes)
502
- // Validates env var to prevent NaN or unreasonable values
503
- this.requestTimeout = options.requestTimeout ?? (() => {
504
- if (process.env.REQUEST_TIMEOUT) {
505
- const parsed = parseInt(process.env.REQUEST_TIMEOUT, 10);
506
- // Validate: must be positive number between 1s and 1 hour
507
- if (isNaN(parsed) || parsed < 1000 || parsed > 3600000) {
508
- return 120000; // Default 2 minutes
509
- }
510
- return parsed;
511
- }
512
- return 120000;
513
- })();
545
+ // Request timeout configuration (default 2 minutes for ordinary providers).
546
+ // Codex keeps its standalone 10-minute default unless this is explicitly configured.
547
+ const optionRequestTimeout = options.requestTimeout;
548
+ const validOptionRequestTimeout = Number.isInteger(optionRequestTimeout) &&
549
+ (optionRequestTimeout === 0 ||
550
+ (optionRequestTimeout >= REQUEST_TIMEOUT_MIN && optionRequestTimeout <= REQUEST_TIMEOUT_MAX));
551
+ const requestTimeoutEnv = process.env.REQUEST_TIMEOUT;
552
+ const explicitEnvRequestTimeoutZero = typeof requestTimeoutEnv === 'string' && requestTimeoutEnv.trim() === '0';
553
+ const parsedRequestTimeout = parseInt(requestTimeoutEnv, 10);
554
+ const validEnvRequestTimeout = explicitEnvRequestTimeoutZero || (!isNaN(parsedRequestTimeout) &&
555
+ parsedRequestTimeout >= REQUEST_TIMEOUT_MIN && parsedRequestTimeout <= REQUEST_TIMEOUT_MAX);
556
+ this._requestTimeoutExplicit = validOptionRequestTimeout || validEnvRequestTimeout;
557
+ this.requestTimeout = validOptionRequestTimeout ? optionRequestTimeout
558
+ : explicitEnvRequestTimeoutZero ? 0
559
+ : validEnvRequestTimeout ? parsedRequestTimeout : REQUEST_TIMEOUT_DEFAULT;
514
560
  if (this.debug) {
515
561
  console.log(`[DEBUG] Request timeout: ${this.requestTimeout}ms`);
516
562
  }
@@ -519,13 +565,16 @@ export class ProbeAgent {
519
565
  // This is the absolute maximum time including all retries and fallbacks
520
566
  // Validates env var to prevent NaN or unreasonable values
521
567
  this.maxOperationTimeout = options.maxOperationTimeout ?? (() => {
522
- if (process.env.MAX_OPERATION_TIMEOUT) {
523
- const parsed = parseInt(process.env.MAX_OPERATION_TIMEOUT, 10);
524
- // Validate: must be positive number between 1s and 2 hours
525
- if (isNaN(parsed) || parsed < 1000 || parsed > 7200000) {
568
+ const operationTimeoutEnv = process.env.MAX_OPERATION_TIMEOUT;
569
+ const explicitEnvOperationTimeoutZero = typeof operationTimeoutEnv === 'string' && operationTimeoutEnv.trim() === '0';
570
+ if (operationTimeoutEnv) {
571
+ const parsed = parseInt(operationTimeoutEnv, 10);
572
+ // Validate: zero disables the overall deadline; positive values remain 1s to 2h.
573
+ if (!explicitEnvOperationTimeoutZero &&
574
+ (isNaN(parsed) || parsed < 1000 || parsed > 7200000)) {
526
575
  return 300000; // Default 5 minutes
527
576
  }
528
- return parsed;
577
+ return explicitEnvOperationTimeoutZero ? 0 : parsed;
529
578
  }
530
579
  return 300000;
531
580
  })();
@@ -1014,7 +1063,7 @@ export class ProbeAgent {
1014
1063
  // Timeout settings for delegate subagents to inherit
1015
1064
  timeoutBehavior: this.timeoutBehavior,
1016
1065
  maxOperationTimeout: this.maxOperationTimeout,
1017
- requestTimeout: this.requestTimeout,
1066
+ requestTimeout: this._requestTimeoutExplicit ? this.requestTimeout : undefined,
1018
1067
  gracefulTimeoutBonusSteps: this.gracefulTimeoutBonusSteps,
1019
1068
  negotiatedTimeoutBudget: this.negotiatedTimeoutBudget,
1020
1069
  negotiatedTimeoutMaxRequests: this.negotiatedTimeoutMaxRequests,
@@ -1411,10 +1460,11 @@ export class ProbeAgent {
1411
1460
  * @param {AbortSignal} abortSignal - Signal for aborting the operation
1412
1461
  * @param {number} requestTimeout - Per-request timeout in ms
1413
1462
  * @param {Object} timeoutState - Object with timeoutId property (mutable for cleanup)
1463
+ * @param {boolean} [disableActivityTimeout=false] - Disable the post-hoc host activity check for explicit Codex no-deadline requests
1414
1464
  * @returns {Object} - streamText-compatible result with textStream
1415
1465
  * @private
1416
1466
  */
1417
- _createEngineTextStreamResult(engineStream, abortSignal, requestTimeout, timeoutState) {
1467
+ _createEngineTextStreamResult(engineStream, abortSignal, requestTimeout, timeoutState, disableActivityTimeout = false) {
1418
1468
  // Activity timeout for engine stream - validates env var against defined bounds
1419
1469
  const activityTimeout = (() => {
1420
1470
  const parsed = parseInt(process.env.ENGINE_ACTIVITY_TIMEOUT, 10);
@@ -1442,7 +1492,7 @@ export class ProbeAgent {
1442
1492
  const now = Date.now();
1443
1493
 
1444
1494
  // Check for activity timeout (no data received for too long)
1445
- if (now - lastActivity > activityTimeout) {
1495
+ if (!disableActivityTimeout && now - lastActivity > activityTimeout) {
1446
1496
  throw new Error(`Engine stream timeout - no activity for ${activityTimeout}ms`);
1447
1497
  }
1448
1498
 
@@ -1516,7 +1566,8 @@ export class ProbeAgent {
1516
1566
  // Get the engine's query result and wrap with timeout handling
1517
1567
  const engineStream = engine.query(prompt, engineOptions);
1518
1568
  return this._createEngineTextStreamResult(
1519
- engineStream, controller.signal, this.requestTimeout, timeoutState
1569
+ engineStream, controller.signal, this.requestTimeout, timeoutState,
1570
+ this.clientApiProvider === 'codex' && this._requestTimeoutExplicit && this.requestTimeout === 0
1520
1571
  );
1521
1572
  }
1522
1573
 
@@ -2445,8 +2496,10 @@ export class ProbeAgent {
2445
2496
  return this.engine;
2446
2497
  }
2447
2498
 
2499
+ const governedCodex = this.governedCodexProfile !== undefined;
2500
+
2448
2501
  // Try Claude Code engine if requested
2449
- if (this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true') {
2502
+ if (!governedCodex && (this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true')) {
2450
2503
  try {
2451
2504
  const { createEnhancedClaudeCLIEngine } = await import('./engines/enhanced-claude-code.js');
2452
2505
 
@@ -2477,8 +2530,18 @@ export class ProbeAgent {
2477
2530
  }
2478
2531
 
2479
2532
  // Try Codex CLI engine if requested
2480
- if (this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true') {
2533
+ if (governedCodex || this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true') {
2481
2534
  try {
2535
+ if (governedCodex && this.governedCodexTransport === GOVERNED_CODEX_EXEC_TRANSPORT) {
2536
+ const { createGovernedCodexExecEngine } = await import('./engines/governed-codex-exec.js');
2537
+ const systemPrompt = await this._getCachedCodexNativeSystemPrompt();
2538
+ this.engine = await createGovernedCodexExecEngine({
2539
+ agent: this, profile: this.governedCodexProfile,
2540
+ codexPath: this.governedCodexBin, codexSha256: this.governedCodexSha256,
2541
+ systemPrompt, timeoutMs: this._requestTimeoutExplicit ? this.requestTimeout : undefined,
2542
+ });
2543
+ return this.engine;
2544
+ }
2482
2545
  const { createCodexEngine } = await import('./engines/codex.js');
2483
2546
 
2484
2547
  // For Codex CLI, use a cleaner system prompt without XML formatting
@@ -2492,6 +2555,7 @@ export class ProbeAgent {
2492
2555
  debug: this.debug,
2493
2556
  allowedTools: this.allowedTools, // Pass tool filtering configuration
2494
2557
  model: this.model, // Pass model name (e.g., gpt-5.2, o3, etc.)
2558
+ requestTimeout: this._requestTimeoutExplicit ? this.requestTimeout : undefined,
2495
2559
  governedCodexProfile: this.governedCodexProfile
2496
2560
  });
2497
2561
  if (this.debug) {
@@ -2503,8 +2567,9 @@ export class ProbeAgent {
2503
2567
  return this.engine;
2504
2568
  } catch (error) {
2505
2569
  if (this.governedCodexProfile) {
2506
- throw normalizeGovernedAnswerFailure(error, 'provider_engine', null, null, null,
2507
- null, null, 'acquire');
2570
+ throw this.governedCodexTransport === GOVERNED_CODEX_EXEC_TRANSPORT
2571
+ ? normalizeGovernedCodexExecFailure(error, 'acquire')
2572
+ : normalizeGovernedAnswerFailure(error, 'provider_engine', null, null, null, null, null, 'acquire');
2508
2573
  }
2509
2574
  console.warn('[WARNING] Failed to load Codex CLI engine:', error.message);
2510
2575
  console.warn('[WARNING] Falling back to Vercel AI SDK');
@@ -3335,6 +3400,7 @@ ${extractGuidance2}
3335
3400
  }
3336
3401
  const { prompt } = this._prepareGovernedAnswerPrompt(message, options);
3337
3402
  const systemPrompt = await this._getCachedCodexNativeSystemPrompt();
3403
+ if (this.governedCodexTransport === GOVERNED_CODEX_EXEC_TRANSPORT) return previewGovernedCodexExecDispatch(prompt, systemPrompt);
3338
3404
  const { previewGovernedCodexInitialDispatch } = await import('./engines/codex.js');
3339
3405
  return previewGovernedCodexInitialDispatch({ systemPrompt, prompt });
3340
3406
  }
@@ -3530,41 +3596,46 @@ Follow these instructions carefully:
3530
3596
  }
3531
3597
 
3532
3598
  let engine, answerFailure = null;
3599
+ const normalizeProviderFailure = (error, boundary) => this.governedCodexTransport === GOVERNED_CODEX_EXEC_TRANSPORT
3600
+ ? normalizeGovernedCodexExecFailure(error, boundary)
3601
+ : normalizeGovernedAnswerFailure(error, 'provider_engine', null, null, null, null, null, boundary);
3533
3602
  try {
3534
3603
  try { engine = await this.getEngine(); }
3535
3604
  catch (error) {
3536
- throw normalizeGovernedAnswerFailure(error, 'provider_engine', null, null, null,
3537
- null, null, 'acquire');
3605
+ throw normalizeProviderFailure(error, 'acquire');
3538
3606
  }
3539
3607
  if (!engine?.query) throw governedAnswerFailure('internal_contract');
3540
3608
  const candidateChunks = [];
3541
3609
  let runtimeAttestation;
3610
+ let candidateBoundary;
3542
3611
  let attestationCount = 0;
3543
3612
  let nativeToolBatch;
3544
3613
  let nativeToolBatchCount = 0;
3545
- const queryOptions = hasInvocationDigest
3546
- ? { abortSignal: this._abortController.signal, invocationDigest: invocationDigest }
3547
- : { abortSignal: this._abortController.signal };
3614
+ const queryOptions = {
3615
+ abortSignal: this._abortController.signal,
3616
+ ...(hasInvocationDigest ? { invocationDigest } : {}),
3617
+ ...(this.governedCodexTransport === GOVERNED_CODEX_EXEC_TRANSPORT ? {} : { schema }),
3618
+ };
3548
3619
  try {
3549
3620
  for await (const chunk of engine.query(prompt, queryOptions)) {
3550
3621
  if (chunk.type === 'text' && chunk.content) candidateChunks.push(chunk.content);
3551
3622
  else if (chunk.type === 'metadata' && chunk.data?.attestation) {
3552
3623
  runtimeAttestation = chunk.data.attestation;
3624
+ if (chunk.data.candidateBoundary !== undefined) candidateBoundary = projectGovernedCandidateBoundary(chunk.data.candidateBoundary);
3553
3625
  attestationCount++;
3554
3626
  } else if (chunk.type === 'toolBatch') {
3555
3627
  nativeToolBatch = chunk;
3556
3628
  nativeToolBatchCount++;
3557
3629
  } else if (chunk.type === 'error') {
3558
- throw normalizeGovernedAnswerFailure(chunk.error, 'provider_engine', null, null, null,
3559
- null, null, 'query');
3630
+ throw normalizeProviderFailure(chunk.error, 'query');
3560
3631
  }
3561
3632
  }
3562
3633
  } catch (error) {
3563
- throw normalizeGovernedAnswerFailure(error, 'provider_engine', null, null, null,
3564
- null, null, 'query');
3634
+ throw normalizeProviderFailure(error, 'query');
3565
3635
  }
3566
3636
  if (hasInvocationDigest) {
3567
- const expectedAttestation = this.governedCodexProfile?.version === 'probe.governed-codex-profile/v2'
3637
+ const execTransport = this.governedCodexTransport === GOVERNED_CODEX_EXEC_TRANSPORT;
3638
+ const expectedAttestation = execTransport ? 'probe.governed-codex-exec-attestation/v1' : isGovernedCodexNativeProfile(this.governedCodexProfile)
3568
3639
  ? 'probe.governed-codex-attestation/v3' : 'probe.governed-codex-attestation/v2';
3569
3640
  if (attestationCount !== 1 || runtimeAttestation?.version !== expectedAttestation || runtimeAttestation?.executionContext?.source !== 'caller' || runtimeAttestation?.executionContext?.invocationDigest !== invocationDigest) {
3570
3641
  throw governedAnswerFailure('native_event_grammar', 'live_envelope_session', 'attestation', null,
@@ -3574,7 +3645,10 @@ Follow these instructions carefully:
3574
3645
  throw governedAnswerFailure('native_event_grammar', 'live_envelope_session', 'attestation', null,
3575
3646
  'invocation_attestation');
3576
3647
  }
3577
- if (this.governedCodexProfile?.version === 'probe.governed-codex-profile/v2') {
3648
+ if (runtimeAttestation?.version === 'probe.governed-codex-exec-attestation/v1') {
3649
+ try { validateGovernedCodexExecAttestation(runtimeAttestation); }
3650
+ catch { throw governedAnswerFailure('native_event_grammar', 'live_envelope_session', 'attestation', null, 'invocation_attestation'); }
3651
+ } else if (isGovernedCodexNativeProfile(this.governedCodexProfile)) {
3578
3652
  if (nativeToolBatchCount !== 1 || !Number.isSafeInteger(nativeToolBatch?.total) ||
3579
3653
  !Array.isArray(nativeToolBatch?.tools) || nativeToolBatch.total !== runtimeAttestation?.observed?.nativeTools?.total ||
3580
3654
  JSON.stringify(nativeToolBatch.tools) !== JSON.stringify(runtimeAttestation?.observed?.nativeTools?.tools)) {
@@ -3586,7 +3660,23 @@ Follow these instructions carefully:
3586
3660
  throw governedAnswerFailure('native_event_grammar', 'live_envelope_session', 'attestation', null,
3587
3661
  'native_capability_aggregate');
3588
3662
  }
3589
- const validation = validateJsonResponse(candidateChunks.join(''), {debug:this.debug,schema});
3663
+ const candidateText = candidateChunks.join('');
3664
+ if (runtimeAttestation?.version === 'probe.governed-codex-exec-attestation/v1') {
3665
+ const candidateDigest = `sha256:${createHash('sha256').update(candidateText, 'utf8').digest('hex')}`;
3666
+ if (runtimeAttestation.observed?.finalDigest !== candidateDigest ||
3667
+ runtimeAttestation.observed?.finalBytes !== Buffer.byteLength(candidateText, 'utf8')) {
3668
+ throw governedAnswerFailure('native_event_grammar', 'live_envelope_session', 'attestation', null,
3669
+ 'internal_contract');
3670
+ }
3671
+ }
3672
+ const candidate = Object.freeze({
3673
+ version: 'probe.governed-answer-candidate/v1',
3674
+ text: candidateText,
3675
+ boundary: candidateBoundary || projectGovernedCandidateBoundary(undefined),
3676
+ });
3677
+ try { await this.hooks.emit(HOOK_TYPES.MESSAGE_ASSISTANT, candidate); }
3678
+ catch { /* Candidate observation is not part of parsing or authority. */ }
3679
+ const validation = validateJsonResponse(candidateText, {debug:this.debug,schema});
3590
3680
  if (!validation.isValid) throw governedSchemaResultValidationFailure(validation);
3591
3681
  if (hasResultIdentity) {
3592
3682
  let identified;
@@ -3604,8 +3694,7 @@ Follow these instructions carefully:
3604
3694
  if (engine) {
3605
3695
  try { await engine.close(); }
3606
3696
  catch (error) {
3607
- if (!answerFailure) throw normalizeGovernedAnswerFailure(error, 'provider_engine', null, null, null,
3608
- null, null, 'close');
3697
+ if (!answerFailure) throw normalizeProviderFailure(error, 'close');
3609
3698
  }
3610
3699
  }
3611
3700
  }
@@ -3780,8 +3869,9 @@ Follow these instructions carefully:
3780
3869
  const maxIterations = (options._maxIterationsOverride) ? baseMaxIterations : (options.schema ? baseMaxIterations + 4 : baseMaxIterations);
3781
3870
 
3782
3871
  // Check if we're using CLI-based engines which handle their own agentic loop
3783
- const isClaudeCode = this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true';
3784
- const isCodex = this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true';
3872
+ const governedCodex = this.governedCodexProfile !== undefined;
3873
+ const isClaudeCode = !governedCodex && (this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true');
3874
+ const isCodex = governedCodex || this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true';
3785
3875
 
3786
3876
  if (isClaudeCode) {
3787
3877
  // For Claude Code, bypass the tool loop entirely - it handles its own internal dialogue
@@ -3862,28 +3952,54 @@ Follow these instructions carefully:
3862
3952
  }
3863
3953
 
3864
3954
  // Send the message directly to Codex and collect the response
3865
- let engine; try {
3866
- engine = await this.getEngine();
3955
+ let engine;
3956
+ let queryFailure = null;
3957
+ const governedExecTransport = this.governedCodexProfile &&
3958
+ this.governedCodexTransport === GOVERNED_CODEX_EXEC_TRANSPORT;
3959
+ const normalizeCodexProviderFailure = (error, boundary) => governedExecTransport
3960
+ ? normalizeGovernedCodexExecFailure(error, boundary) : error;
3961
+ try {
3962
+ try {
3963
+ engine = await this.getEngine();
3964
+ } catch (error) {
3965
+ throw normalizeCodexProviderFailure(error, 'acquire');
3966
+ }
3867
3967
  if (engine && engine.query) {
3868
3968
  let assistantResponseContent = '';
3869
3969
  let toolBatch = null;
3970
+ let queryOptions = this.governedCodexProfile
3971
+ ? { ...options, abortSignal: this._abortController.signal }
3972
+ : options;
3973
+ if (this.governedCodexTransport === GOVERNED_CODEX_EXEC_TRANSPORT) {
3974
+ const { schema: requestedSchema, ...withoutSchema } = options;
3975
+ queryOptions = {
3976
+ ...withoutSchema,
3977
+ ...(typeof requestedSchema === 'string' && isJsonSchema(requestedSchema) ? { schema: requestedSchema } : {}),
3978
+ abortSignal: this._abortController.signal,
3979
+ };
3980
+ }
3870
3981
 
3871
3982
  // Query Codex directly with the message and schema
3872
- for await (const chunk of engine.query(message, this.governedCodexProfile ? { ...options, abortSignal: this._abortController.signal } : options)) {
3873
- if (chunk.type === 'text' && chunk.content) {
3874
- assistantResponseContent += chunk.content;
3875
- if (options.onStream) {
3876
- options.onStream(chunk.content);
3877
- }
3878
- } else if (chunk.type === 'toolBatch' && chunk.tools) {
3879
- // Store tool batch for processing after response
3880
- toolBatch = chunk.tools;
3881
- if (this.debug) {
3882
- console.log(`[DEBUG] Received batch of ${chunk.tools.length} tool events from Codex`);
3983
+ try {
3984
+ for await (const chunk of engine.query(message, this.governedCodexProfile ? queryOptions : options)) {
3985
+ if (chunk.type === 'text' && chunk.content) {
3986
+ assistantResponseContent += chunk.content;
3987
+ if (options.onStream) {
3988
+ options.onStream(chunk.content);
3989
+ }
3990
+ } else if (chunk.type === 'toolBatch' && chunk.tools) {
3991
+ // Store tool batch for processing after response
3992
+ toolBatch = chunk.tools;
3993
+ if (this.debug) {
3994
+ console.log(`[DEBUG] Received batch of ${chunk.tools.length} tool events from Codex`);
3995
+ }
3996
+ } else if (chunk.type === 'error') {
3997
+ throw normalizeCodexProviderFailure(chunk.error, 'query');
3883
3998
  }
3884
- } else if (chunk.type === 'error') {
3885
- throw chunk.error;
3886
3999
  }
4000
+ } catch (error) {
4001
+ queryFailure = normalizeCodexProviderFailure(error, 'query');
4002
+ throw queryFailure;
3887
4003
  }
3888
4004
 
3889
4005
  // Emit tool events after response is complete (batch mode)
@@ -3921,7 +4037,19 @@ Follow these instructions carefully:
3921
4037
  console.error('[DEBUG] Codex error:', error);
3922
4038
  }
3923
4039
  throw error;
3924
- } finally { if (this.governedCodexProfile && engine) await engine.close(); }
4040
+ } finally {
4041
+ if (this.governedCodexProfile && engine) {
4042
+ if (!governedExecTransport) {
4043
+ await engine.close();
4044
+ } else {
4045
+ try {
4046
+ await engine.close();
4047
+ } catch (error) {
4048
+ if (!queryFailure) throw normalizeCodexProviderFailure(error, 'close');
4049
+ }
4050
+ }
4051
+ }
4052
+ }
3925
4053
  }
3926
4054
 
3927
4055
  if (this.debug) {
@@ -5492,7 +5620,12 @@ Double-check your response based on the criteria above. If everything looks good
5492
5620
  return finalResult;
5493
5621
 
5494
5622
  } catch (error) {
5495
- console.error(`[ERROR] ProbeAgent.answer failed:`, error);
5623
+ // Governed failures are already closed and are surfaced to the caller;
5624
+ // logging the Error object here would re-expose provider-owned fields.
5625
+ // Preserve the historical diagnostic for ordinary answer() calls.
5626
+ if (!this.governedCodexProfile) {
5627
+ console.error(`[ERROR] ProbeAgent.answer failed:`, error);
5628
+ }
5496
5629
 
5497
5630
  // Clean up tool execution data
5498
5631
  clearToolExecutionData(this.sessionId);
@@ -5656,6 +5789,7 @@ Double-check your response based on the criteria above. If everything looks good
5656
5789
  cwd: this.cwd, // Preserve explicit working directory
5657
5790
  provider: this.clientApiProvider,
5658
5791
  model: this.clientApiModel,
5792
+ requestTimeout: this._requestTimeoutExplicit ? this.requestTimeout : undefined,
5659
5793
  debug: this.debug,
5660
5794
  outline: this.outline,
5661
5795
  searchDelegate: this.searchDelegate,
@@ -5878,7 +6012,7 @@ Double-check your response based on the criteria above. If everything looks good
5878
6012
  this._abortController.abort();
5879
6013
  }
5880
6014
 
5881
- if (this.governedCodexProfile && this.engine?.close) await this.engine.close();
6015
+ if (this.engine?.close) await this.engine.close();
5882
6016
 
5883
6017
  // Clean up MCP bridge
5884
6018
  if (this.mcpBridge) {
@@ -322,7 +322,7 @@ export class ACPToolManager {
322
322
  },
323
323
  language: {
324
324
  type: 'string',
325
- description: 'Programming language to search in (rust, javascript, python, go, crystal, etc.)'
325
+ description: 'Programming language to search in (rust, javascript, python, go, bash, qml, crystal, solidity, haskell, etc.)'
326
326
  },
327
327
  max_results: {
328
328
  type: 'number',