@probelabs/probe 0.6.0-rc331 → 0.6.0-rc334

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 (45) hide show
  1. package/bin/binaries/{probe-v0.6.0-rc331-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc334-aarch64-apple-darwin.tar.gz} +0 -0
  2. package/bin/binaries/{probe-v0.6.0-rc331-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc334-aarch64-unknown-linux-musl.tar.gz} +0 -0
  3. package/bin/binaries/{probe-v0.6.0-rc331-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc334-x86_64-apple-darwin.tar.gz} +0 -0
  4. package/bin/binaries/{probe-v0.6.0-rc331-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc334-x86_64-pc-windows-msvc.zip} +0 -0
  5. package/bin/binaries/{probe-v0.6.0-rc331-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc334-x86_64-unknown-linux-musl.tar.gz} +0 -0
  6. package/build/agent/ProbeAgent.d.ts +105 -4
  7. package/build/agent/ProbeAgent.js +209 -12
  8. package/build/agent/bashExecutor.js +36 -101
  9. package/build/agent/engines/codex.js +367 -88
  10. package/build/agent/engines/governed-answer-failure.js +152 -0
  11. package/build/agent/engines/governed-codex-profile.js +198 -0
  12. package/build/agent/governance/acknowledgedJsonlChannel.js +328 -0
  13. package/build/agent/governance/atomicTerminalReceipt.js +188 -0
  14. package/build/agent/governance/index.d.ts +130 -0
  15. package/build/agent/governance/index.js +8 -0
  16. package/build/agent/mcp/built-in-server.js +152 -53
  17. package/build/agent/mcp/index.d.ts +65 -0
  18. package/build/agent/mcp/index.js +6 -1
  19. package/build/agent/probeTool.js +1 -1
  20. package/build/agent/processSupervisor.js +351 -0
  21. package/build/agent/tools.js +14 -8
  22. package/build/index.js +2 -0
  23. package/build/utils/provider.js +9 -3
  24. package/cjs/agent/ProbeAgent.cjs +13463 -12187
  25. package/cjs/index.cjs +75974 -74139
  26. package/index.d.ts +149 -4
  27. package/package.json +6 -2
  28. package/src/agent/ProbeAgent.d.ts +105 -4
  29. package/src/agent/ProbeAgent.js +209 -12
  30. package/src/agent/bashExecutor.js +36 -101
  31. package/src/agent/engines/codex.js +367 -88
  32. package/src/agent/engines/governed-answer-failure.js +152 -0
  33. package/src/agent/engines/governed-codex-profile.js +198 -0
  34. package/src/agent/governance/acknowledgedJsonlChannel.js +328 -0
  35. package/src/agent/governance/atomicTerminalReceipt.js +188 -0
  36. package/src/agent/governance/index.d.ts +130 -0
  37. package/src/agent/governance/index.js +8 -0
  38. package/src/agent/mcp/built-in-server.js +152 -53
  39. package/src/agent/mcp/index.d.ts +65 -0
  40. package/src/agent/mcp/index.js +6 -1
  41. package/src/agent/probeTool.js +1 -1
  42. package/src/agent/processSupervisor.js +351 -0
  43. package/src/agent/tools.js +14 -8
  44. package/src/index.js +2 -0
  45. package/src/utils/provider.js +9 -3
@@ -63,7 +63,7 @@ export interface ProbeAgentOptions {
63
63
  /** Use a delegated code-search subagent for the search tool (default: true) */
64
64
  searchDelegate?: boolean;
65
65
  /** Force specific AI provider */
66
- provider?: 'anthropic' | 'openai' | 'google' | 'bedrock';
66
+ provider?: 'anthropic' | 'openai' | 'google' | 'bedrock' | 'codex';
67
67
  /** Override model name */
68
68
  model?: string;
69
69
  /** Enable debug mode */
@@ -80,6 +80,8 @@ export interface ProbeAgentOptions {
80
80
  mcpServers?: any[];
81
81
  /** List of allowed tool names. Use ['*'] for all tools (default), [] or null for no tools (raw AI mode), or specific tool names like ['search', 'query', 'extract']. Supports exclusion with '!' prefix (e.g., ['*', '!bash']). */
82
82
  allowedTools?: string[] | null;
83
+ /** Attested, fail-closed Codex runtime profile. Requires provider codex and an exact allowedTools match. */
84
+ governedCodexProfile?: GovernedCodexProfile;
83
85
  /** Convenience flag to disable all tools (equivalent to allowedTools: []). Takes precedence over allowedTools if set. */
84
86
  disableTools?: boolean;
85
87
  /** Retry configuration for handling transient API failures */
@@ -154,6 +156,8 @@ export interface TimeoutWindingDownEvent {
154
156
  * Tool execution event data
155
157
  */
156
158
  export interface ToolCallEvent {
159
+ /** Digest of validated arguments, or null only when an admitted call is rejected before validation. */
160
+ argumentsDigest?: string | null;
157
161
  /** Unique tool call identifier */
158
162
  id: string;
159
163
  /** Name of the tool being called */
@@ -176,6 +180,13 @@ export interface ToolCallEvent {
176
180
  duration?: number;
177
181
  }
178
182
 
183
+ /** Content-free aggregate for attested Codex-native execution capability use. */
184
+ export interface GovernedCodexNativeToolAggregate {
185
+ name: 'exec';
186
+ status: 'completed';
187
+ count: number;
188
+ }
189
+
179
190
  /**
180
191
  * Token usage statistics
181
192
  */
@@ -228,6 +239,88 @@ export interface AnswerOptions {
228
239
  maxIterations?: number;
229
240
  }
230
241
 
242
+ export interface GovernedAnswerOptions extends AnswerOptions {
243
+ schema: string;
244
+ }
245
+
246
+ export interface GovernedInvocationAnswerOptions extends GovernedAnswerOptions {
247
+ invocationDigest: string;
248
+ }
249
+
250
+ export interface GovernedIdentifiedAnswerOptions extends GovernedInvocationAnswerOptions {
251
+ resultIdentity: 'probe.governed-result-identity/v1';
252
+ }
253
+
254
+ export interface GovernedAnswerDispatchOptions {
255
+ schema: string;
256
+ }
257
+
258
+ export interface GovernedAnswerDispatch {
259
+ readonly source: 'probe-host-tools-call';
260
+ readonly tool: 'codex';
261
+ readonly promptDigest: `sha256:${string}`;
262
+ readonly promptBytes: number;
263
+ }
264
+
265
+ export interface GovernedResultIdentity {
266
+ version: 'probe.governed-result-identity/v1';
267
+ source: 'probe-host-schema-valid-json';
268
+ resultDigest: string;
269
+ canonicalBytes: number;
270
+ }
271
+
272
+ export type GovernedCodexProfile =
273
+ { 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
+ /** 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; };
276
+
277
+ export interface GovernedCodexRuntimeAttestation {
278
+ version: 'probe.governed-codex-attestation/v1';
279
+ profileId: 'luna-xhigh-readonly-v1';
280
+ requested: { profileDigest: string; cwdDigest: string; probeToolsDigest: string; model: 'gpt-5.6-luna'; reasoningEffort: 'xhigh'; sandbox: 'read-only'; approvalPolicy: 'never'; };
281
+ observed: { source: 'session_configured'; model: 'gpt-5.6-luna'; modelProviderId: 'openai'; reasoningEffort: 'xhigh'; approvalPolicy: 'never'; cwdDigest: string; permissionProfileDigest: string; filesystem: 'restricted-read-root'; network: 'restricted'; };
282
+ evidence: { eventCount: 1; };
283
+ usage: { status: 'unavailable'; };
284
+ }
285
+
286
+ export interface GovernedAnswerResult {
287
+ data: unknown;
288
+ runtimeAttestation: GovernedCodexRuntimeAttestation | GovernedCodexRuntimeAttestationV3;
289
+ }
290
+
291
+ export interface GovernedCodexRuntimeAttestationV2 {
292
+ version: 'probe.governed-codex-attestation/v2';
293
+ profileId: 'luna-xhigh-readonly-v1';
294
+ requested: { profileDigest: string; cwdDigest: string; probeToolsDigest: string; model: 'gpt-5.6-luna'; reasoningEffort: 'xhigh'; sandbox: 'read-only'; approvalPolicy: 'never'; };
295
+ observed: { source: 'session_configured'; model: 'gpt-5.6-luna'; modelProviderId: 'openai'; reasoningEffort: 'xhigh'; approvalPolicy: 'never'; cwdDigest: string; permissionProfileDigest: string; filesystem: 'restricted-read-root'; network: 'restricted'; };
296
+ executionContext: { source: 'caller'; invocationDigest: string; };
297
+ dispatch: { source: 'probe-host-tools-call'; tool: 'codex'; promptDigest: string; promptBytes: number; };
298
+ evidence: { eventCount: 1; };
299
+ usage: { status: 'unavailable'; };
300
+ }
301
+
302
+ export interface GovernedInvocationAnswerResult {
303
+ data: unknown;
304
+ runtimeAttestation: GovernedCodexRuntimeAttestationV2 | GovernedCodexRuntimeAttestationV3;
305
+ }
306
+
307
+ export interface GovernedIdentifiedAnswerResult {
308
+ data: unknown;
309
+ runtimeAttestation: GovernedCodexRuntimeAttestationV2 | GovernedCodexRuntimeAttestationV3;
310
+ resultIdentity: GovernedResultIdentity;
311
+ }
312
+
313
+ export interface GovernedCodexRuntimeAttestationV3 {
314
+ 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[]; }; };
318
+ executionContext?: { source: 'caller'; invocationDigest: string; };
319
+ dispatch?: { source: 'probe-host-tools-call'; tool: 'codex'; promptDigest: string; promptBytes: number; };
320
+ evidence: { sessionEventCount: 1; nativeCallCount: number; probeMcpCallCount: number; };
321
+ usage: { status: 'unavailable'; };
322
+ }
323
+
231
324
  /**
232
325
  * Clone options for creating a new agent with shared history
233
326
  */
@@ -268,6 +361,7 @@ export declare class ProbeAgent {
268
361
 
269
362
  /** Whether operations have been cancelled */
270
363
  cancelled: boolean;
364
+ readonly abortSignal: AbortSignal;
271
365
 
272
366
  /** AI provider being used */
273
367
  readonly clientApiProvider?: string;
@@ -297,6 +391,11 @@ export declare class ProbeAgent {
297
391
  */
298
392
  answer(message: string, images?: any[], options?: AnswerOptions): Promise<string>;
299
393
 
394
+ answerGoverned(message: string, options: GovernedIdentifiedAnswerOptions, images?: any[]): Promise<GovernedIdentifiedAnswerResult>;
395
+ answerGoverned(message: string, options: GovernedInvocationAnswerOptions, images?: any[]): Promise<GovernedInvocationAnswerResult>;
396
+ answerGoverned(message: string, options: GovernedAnswerOptions, images?: any[]): Promise<GovernedAnswerResult>;
397
+ previewGovernedAnswerDispatch(message: string, options: GovernedAnswerDispatchOptions): Promise<Readonly<GovernedAnswerDispatch>>;
398
+
300
399
  /**
301
400
  * Get token usage statistics
302
401
  * @returns Current token usage information
@@ -308,6 +407,8 @@ export declare class ProbeAgent {
308
407
  */
309
408
  cancel(): void;
310
409
 
410
+ /** Close engine subprocess and MCP resources. */ close(): Promise<void>;
411
+
311
412
  /**
312
413
  * Clear the conversation history
313
414
  */
@@ -337,9 +438,9 @@ export declare class ProbeAgent {
337
438
  * ProbeAgent Events interface
338
439
  */
339
440
  export interface ProbeAgentEvents {
340
- on(event: 'toolCall', listener: (event: ToolCallEvent) => void): this;
341
- emit(event: 'toolCall', event: ToolCallEvent): boolean;
342
- removeListener(event: 'toolCall', listener: (event: ToolCallEvent) => void): this;
441
+ on(event: 'toolCall', listener: (event: ToolCallEvent | GovernedCodexNativeToolAggregate) => void): this;
442
+ emit(event: 'toolCall', event: ToolCallEvent | GovernedCodexNativeToolAggregate): boolean;
443
+ removeListener(event: 'toolCall', listener: (event: ToolCallEvent | GovernedCodexNativeToolAggregate) => void): this;
343
444
  removeAllListeners(event?: 'toolCall'): this;
344
445
  }
345
446
 
@@ -1,9 +1,5 @@
1
1
  // Core ProbeAgent class adapted from examples/chat/probeChat.js
2
2
 
3
- // Load .env file if present (silent fail if not found)
4
- import dotenv from 'dotenv';
5
- dotenv.config();
6
-
7
3
  // ============================================================================
8
4
  // Timeout Configuration Constants
9
5
  // ============================================================================
@@ -29,7 +25,7 @@ export const ENGINE_ACTIVITY_TIMEOUT_MAX = 600000;
29
25
 
30
26
  import { createProviderInstance, DEFAULT_MODELS } from '../utils/provider.js';
31
27
  import { streamText, generateText, tool, stepCountIs, jsonSchema, Output } from 'ai';
32
- import { randomUUID } from 'crypto';
28
+ import { createHash, randomUUID } from 'crypto';
33
29
  import { EventEmitter } from 'events';
34
30
  import { existsSync } from 'fs';
35
31
  import { readFile, stat, readdir } from 'fs/promises';
@@ -70,7 +66,7 @@ import {
70
66
  clearToolExecutionData
71
67
  } from './probeTool.js';
72
68
  import { createMockProvider } from './mockProvider.js';
73
- import { listFilesByLevel } from '../index.js';
69
+ import { listFilesByLevel } from '../utils/file-lister.js';
74
70
  import {
75
71
  cleanSchemaResponse,
76
72
  isJsonSchema,
@@ -106,6 +102,69 @@ import {
106
102
  createTaskCompletionBlockedMessage
107
103
  } from './tasks/index.js';
108
104
  import { z } from 'zod';
105
+ import { validateGovernedCodexProfile } from './engines/governed-codex-profile.js';
106
+ import { governedAnswerFailure, normalizeGovernedAnswerFailure } from './engines/governed-answer-failure.js';
107
+
108
+ const GOVERNED_RESULT_IDENTITY = 'probe.governed-result-identity/v1';
109
+ const GOVERNED_RESULT_DOMAIN = 'probe.governed-result-identity/data/v1';
110
+
111
+ function normalizeGovernedJson(value) {
112
+ if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;
113
+ if (typeof value === 'number') {
114
+ if (!Number.isFinite(value)) throw new TypeError('answerGoverned validated result is not canonical JSON');
115
+ return Object.is(value, -0) ? 0 : value;
116
+ }
117
+ if (Array.isArray(value)) return value.map(normalizeGovernedJson);
118
+ if (typeof value === 'object' && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) {
119
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, child]) => [key, normalizeGovernedJson(child)]));
120
+ }
121
+ throw new TypeError('answerGoverned validated result is not canonical JSON');
122
+ }
123
+
124
+ function freezeGovernedTree(value) {
125
+ if (value && typeof value === 'object') {
126
+ for (const child of Object.values(value)) freezeGovernedTree(child);
127
+ Object.freeze(value);
128
+ }
129
+ return value;
130
+ }
131
+
132
+ function identifyGovernedResult(value) {
133
+ const data = freezeGovernedTree(normalizeGovernedJson(value));
134
+ const canonical = Buffer.from(JSON.stringify(data), 'utf8');
135
+ const byteLength = Buffer.alloc(8);
136
+ byteLength.writeBigUInt64BE(BigInt(canonical.length));
137
+ const resultDigest = `sha256:${createHash('sha256').update(GOVERNED_RESULT_DOMAIN, 'utf8').update(Buffer.from([0])).update(byteLength).update(canonical).digest('hex')}`;
138
+ const resultIdentity = Object.freeze({ version: GOVERNED_RESULT_IDENTITY, source: 'probe-host-schema-valid-json', resultDigest, canonicalBytes: canonical.length });
139
+ return { data, resultIdentity };
140
+ }
141
+
142
+ function governedSchemaResultValidationFailure(validation) {
143
+ const subreason = validation?.error === 'Invalid schema provided' ||
144
+ validation?.error === 'Schema compilation failed' ? 'schema_definition'
145
+ : validation?.error === 'Schema validation failed' ? 'schema_mismatch'
146
+ : 'response_json';
147
+ const schemaResultValidationKeyword = subreason === 'schema_mismatch'
148
+ ? classifyGovernedSchemaResultValidationKeyword(validation) : null;
149
+ return governedAnswerFailure('schema_result_validation', null, null, null, null, subreason,
150
+ schemaResultValidationKeyword);
151
+ }
152
+
153
+ const GOVERNED_SCHEMA_RESULT_VALIDATION_KEYWORDS = new Set([
154
+ 'required', 'additionalProperties', 'type', 'pattern', 'enum', 'minItems', 'maxItems',
155
+ ]);
156
+
157
+ function classifyGovernedSchemaResultValidationKeyword(validation) {
158
+ const errors = validation?.schemaErrors;
159
+ if (!Array.isArray(errors) || errors.length === 0) return 'unknown';
160
+ const recognized = new Set();
161
+ for (const error of errors) {
162
+ const keyword = error?.keyword;
163
+ if (!GOVERNED_SCHEMA_RESULT_VALIDATION_KEYWORDS.has(keyword)) return 'unknown';
164
+ recognized.add(keyword);
165
+ }
166
+ return recognized.size === 1 ? [...recognized][0] : 'multiple';
167
+ }
109
168
 
110
169
  // Maximum tool iterations to prevent infinite loops - configurable via MAX_TOOL_ITERATIONS env var
111
170
  const MAX_TOOL_ITERATIONS = (() => {
@@ -287,6 +346,7 @@ export class ProbeAgent {
287
346
  this.debug = options.debug || process.env.DEBUG === '1';
288
347
  this.cancelled = false;
289
348
  this._abortController = new AbortController();
349
+ this._codexNativeSystemPromptPromise = null;
290
350
  this._activeSubagents = new Map(); // sessionId → subagent ProbeAgent instance
291
351
  this.tracer = options.tracer || null;
292
352
  this.outline = !!options.outline;
@@ -322,6 +382,13 @@ export class ProbeAgent {
322
382
  // Accepted values: 'off' (default), 'low', 'medium', 'high', or a number (budget tokens)
323
383
  this.thinkingEffort = options.thinkingEffort || null;
324
384
 
385
+ if (options.governedCodexProfile !== undefined) {
386
+ if (options.provider !== 'codex') throw new TypeError('governedCodexProfile requires provider codex'); const profile = validateGovernedCodexProfile(options.governedCodexProfile);
387
+ const probeTools = profile.probeTools ?? profile.probeMcpTools;
388
+ 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
+ this.governedCodexProfile = profile;
390
+ }
391
+
325
392
  // Tool filtering configuration
326
393
  // Parse allowedTools option: ['*'] = all tools, [] or null = no tools, ['tool1', 'tool2'] = specific tools
327
394
  // Supports exclusion with '!' prefix: ['*', '!bash'] = all tools except bash
@@ -1752,6 +1819,7 @@ export class ProbeAgent {
1752
1819
  result = ProbeAgent._wrapEngineStreamWithLimiter(result, limiter, this.debug);
1753
1820
  }
1754
1821
  } catch (error) {
1822
+ if (this.governedCodexProfile) throw error;
1755
1823
  if (this.debug) {
1756
1824
  const engineType = useClaudeCode ? 'Claude Code' : 'Codex';
1757
1825
  console.log(`[DEBUG] Failed to use ${engineType} engine, falling back to Vercel:`, error.message);
@@ -2415,7 +2483,7 @@ export class ProbeAgent {
2415
2483
 
2416
2484
  // For Codex CLI, use a cleaner system prompt without XML formatting
2417
2485
  // since it has native MCP support for tools
2418
- const systemPrompt = await this.getCodexNativeSystemPrompt();
2486
+ const systemPrompt = await this._getCachedCodexNativeSystemPrompt();
2419
2487
 
2420
2488
  this.engine = await createCodexEngine({
2421
2489
  agent: this, // Pass reference to ProbeAgent for tool access
@@ -2424,7 +2492,8 @@ export class ProbeAgent {
2424
2492
  sessionId: this.options?.sessionId,
2425
2493
  debug: this.debug,
2426
2494
  allowedTools: this.allowedTools, // Pass tool filtering configuration
2427
- model: this.model // Pass model name (e.g., gpt-5.2, o3, etc.)
2495
+ model: this.model, // Pass model name (e.g., gpt-5.2, o3, etc.)
2496
+ governedCodexProfile: this.governedCodexProfile
2428
2497
  });
2429
2498
  if (this.debug) {
2430
2499
  console.log('[DEBUG] Using Codex CLI engine with Probe tools');
@@ -2434,6 +2503,7 @@ export class ProbeAgent {
2434
2503
  }
2435
2504
  return this.engine;
2436
2505
  } catch (error) {
2506
+ if (this.governedCodexProfile) throw error;
2437
2507
  console.warn('[WARNING] Failed to load Codex CLI engine:', error.message);
2438
2508
  console.warn('[WARNING] Falling back to Vercel AI SDK');
2439
2509
  this.clientApiProvider = null;
@@ -3230,6 +3300,43 @@ ${extractGuidance2}
3230
3300
  return systemPrompt;
3231
3301
  }
3232
3302
 
3303
+ /**
3304
+ * Resolve the Codex system prompt once so preview and runtime bind identical bytes.
3305
+ * @returns {Promise<string>}
3306
+ * @private
3307
+ */
3308
+ _getCachedCodexNativeSystemPrompt() {
3309
+ if (!this._codexNativeSystemPromptPromise) {
3310
+ this._codexNativeSystemPromptPromise = this.getCodexNativeSystemPrompt();
3311
+ }
3312
+ return this._codexNativeSystemPromptPromise;
3313
+ }
3314
+
3315
+ _prepareGovernedAnswerPrompt(message, options) {
3316
+ if (!this.governedCodexProfile) throw new Error('answerGoverned requires governedCodexProfile');
3317
+ if (!message || typeof message !== 'string' || message.trim().length === 0) throw new Error('Message is required and must be a non-empty string');
3318
+ if (!options) throw new Error('answerGoverned requires a valid JSON schema string');
3319
+ const schema = options.schema;
3320
+ if (typeof schema !== 'string' || !schema.trim() || !isJsonSchema(schema)) throw new Error('answerGoverned requires a valid JSON schema string');
3321
+ return { schema, prompt: message.trim() + generateSchemaInstructions(schema, { debug: this.debug }) };
3322
+ }
3323
+
3324
+ /**
3325
+ * Preview the exact governed initial Codex dispatch without acquiring an engine or MCP server.
3326
+ * @param {string} message
3327
+ * @param {{schema: string}} options
3328
+ * @returns {Promise<{source: 'probe-host-tools-call', tool: 'codex', promptDigest: string, promptBytes: number}>}
3329
+ */
3330
+ async previewGovernedAnswerDispatch(message, options) {
3331
+ if (!options || Reflect.ownKeys(options).length !== 1 || !Object.prototype.hasOwnProperty.call(options, 'schema')) {
3332
+ throw new Error('previewGovernedAnswerDispatch requires exactly {schema}');
3333
+ }
3334
+ const { prompt } = this._prepareGovernedAnswerPrompt(message, options);
3335
+ const systemPrompt = await this._getCachedCodexNativeSystemPrompt();
3336
+ const { previewGovernedCodexInitialDispatch } = await import('./engines/codex.js');
3337
+ return previewGovernedCodexInitialDispatch({ systemPrompt, customPrompt: this.customPrompt, prompt });
3338
+ }
3339
+
3233
3340
  /**
3234
3341
  * Get the system message with instructions for the AI (XML Tool Format)
3235
3342
  */
@@ -3392,6 +3499,94 @@ Follow these instructions carefully:
3392
3499
  return systemMessage;
3393
3500
  }
3394
3501
 
3502
+ /**
3503
+ * Return one schema-validated result with its governed Codex attestation.
3504
+ * @param {string} message - The user's question
3505
+ * @param {Object} options - Governed answer options
3506
+ * @param {string} options.schema - Required JSON schema
3507
+ * @param {Array} [images] - Unsupported; must be empty
3508
+ * @returns {Promise<{data: unknown, runtimeAttestation: Object}>}
3509
+ */
3510
+ async answerGoverned(message, options, images = []) {
3511
+ const { schema, prompt } = this._prepareGovernedAnswerPrompt(message, options);
3512
+ if (!Array.isArray(images) || images.length > 0) throw new Error('answerGoverned does not support images');
3513
+
3514
+ const hasInvocationDigest = Object.prototype.hasOwnProperty.call(options,
3515
+ 'invocationDigest');
3516
+ const invocationDigest = hasInvocationDigest ? options.invocationDigest : undefined;
3517
+ if (hasInvocationDigest && (typeof invocationDigest !== 'string' || !/^sha256:[0-9a-f]{64}$/.test(invocationDigest))) {
3518
+ throw new TypeError('answerGoverned invocationDigest must match sha256:<64 lowercase hexadecimal digits>');
3519
+ }
3520
+ const hasResultIdentity = Object.prototype.hasOwnProperty.call(options,
3521
+ 'resultIdentity');
3522
+ const requestedResultIdentity = hasResultIdentity ? options.resultIdentity : undefined;
3523
+ if (hasResultIdentity && requestedResultIdentity !== GOVERNED_RESULT_IDENTITY) {
3524
+ throw new TypeError('answerGoverned resultIdentity must equal probe.governed-result-identity/v1');
3525
+ }
3526
+ if (hasResultIdentity && !hasInvocationDigest) {
3527
+ throw new TypeError('answerGoverned resultIdentity requires an own invocationDigest');
3528
+ }
3529
+
3530
+ let engine, answerFailure = null;
3531
+ try {
3532
+ try { engine = await this.getEngine(); }
3533
+ catch { throw governedAnswerFailure('provider_engine'); }
3534
+ if (!engine?.query) throw governedAnswerFailure('provider_engine');
3535
+ const candidateChunks = [];
3536
+ let runtimeAttestation;
3537
+ let attestationCount = 0;
3538
+ let nativeToolBatch;
3539
+ let nativeToolBatchCount = 0;
3540
+ const queryOptions = hasInvocationDigest
3541
+ ? { abortSignal: this._abortController.signal, invocationDigest: invocationDigest }
3542
+ : { abortSignal: this._abortController.signal };
3543
+ for await (const chunk of engine.query(prompt, queryOptions)) {
3544
+ if (chunk.type === 'text' && chunk.content) candidateChunks.push(chunk.content);
3545
+ else if (chunk.type === 'metadata' && chunk.data?.attestation) {
3546
+ runtimeAttestation = chunk.data.attestation;
3547
+ attestationCount++;
3548
+ } else if (chunk.type === 'toolBatch') {
3549
+ nativeToolBatch = chunk;
3550
+ nativeToolBatchCount++;
3551
+ } else if (chunk.type === 'error') throw normalizeGovernedAnswerFailure(chunk.error, 'unknown');
3552
+ }
3553
+ if (hasInvocationDigest) {
3554
+ const expectedAttestation = this.governedCodexProfile?.version === 'probe.governed-codex-profile/v2'
3555
+ ? 'probe.governed-codex-attestation/v3' : 'probe.governed-codex-attestation/v2';
3556
+ if (attestationCount !== 1 || runtimeAttestation?.version !== expectedAttestation || runtimeAttestation?.executionContext?.source !== 'caller' || runtimeAttestation?.executionContext?.invocationDigest !== invocationDigest) {
3557
+ throw new Error('Expected exactly one matching governed invocation attestation');
3558
+ }
3559
+ } else if (attestationCount !== 1) throw new Error(`Expected exactly one governed runtime attestation; received ${attestationCount}`);
3560
+ if (this.governedCodexProfile?.version === 'probe.governed-codex-profile/v2') {
3561
+ if (nativeToolBatchCount !== 1 || !Number.isSafeInteger(nativeToolBatch?.total) ||
3562
+ !Array.isArray(nativeToolBatch?.tools) || nativeToolBatch.total !== runtimeAttestation?.observed?.nativeTools?.total ||
3563
+ JSON.stringify(nativeToolBatch.tools) !== JSON.stringify(runtimeAttestation?.observed?.nativeTools?.tools)) {
3564
+ throw new Error('Expected exactly one matching governed native capability aggregate');
3565
+ }
3566
+ for (const toolEvent of nativeToolBatch.tools) this.events.emit('toolCall', toolEvent);
3567
+ } else if (nativeToolBatchCount !== 0) throw new Error('Unexpected governed native capability aggregate');
3568
+ const validation = validateJsonResponse(candidateChunks.join(''), {debug:this.debug,schema});
3569
+ if (!validation.isValid) throw governedSchemaResultValidationFailure(validation);
3570
+ if (hasResultIdentity) {
3571
+ let identified;
3572
+ try { identified = identifyGovernedResult(validation.parsed); }
3573
+ catch { throw governedAnswerFailure('schema_result_validation', null, null, null, null, 'result_identity'); }
3574
+ const { data, resultIdentity } = identified;
3575
+ freezeGovernedTree(runtimeAttestation);
3576
+ return Object.freeze({ data, runtimeAttestation, resultIdentity });
3577
+ }
3578
+ return { data: validation.parsed, runtimeAttestation };
3579
+ } catch (error) {
3580
+ answerFailure = normalizeGovernedAnswerFailure(error, 'unknown');
3581
+ throw answerFailure;
3582
+ } finally {
3583
+ if (engine) {
3584
+ try { await engine.close(); }
3585
+ catch { if (!answerFailure) throw governedAnswerFailure('unknown'); }
3586
+ }
3587
+ }
3588
+ }
3589
+
3395
3590
  /**
3396
3591
  * Answer a question using the agentic flow
3397
3592
  * @param {string} message - The user's question
@@ -3643,14 +3838,14 @@ Follow these instructions carefully:
3643
3838
  }
3644
3839
 
3645
3840
  // Send the message directly to Codex and collect the response
3646
- try {
3647
- const engine = await this.getEngine();
3841
+ let engine; try {
3842
+ engine = await this.getEngine();
3648
3843
  if (engine && engine.query) {
3649
3844
  let assistantResponseContent = '';
3650
3845
  let toolBatch = null;
3651
3846
 
3652
3847
  // Query Codex directly with the message and schema
3653
- for await (const chunk of engine.query(message, options)) {
3848
+ for await (const chunk of engine.query(message, this.governedCodexProfile ? { ...options, abortSignal: this._abortController.signal } : options)) {
3654
3849
  if (chunk.type === 'text' && chunk.content) {
3655
3850
  assistantResponseContent += chunk.content;
3656
3851
  if (options.onStream) {
@@ -3702,7 +3897,7 @@ Follow these instructions carefully:
3702
3897
  console.error('[DEBUG] Codex error:', error);
3703
3898
  }
3704
3899
  throw error;
3705
- }
3900
+ } finally { if (this.governedCodexProfile && engine) await engine.close(); }
3706
3901
  }
3707
3902
 
3708
3903
  if (this.debug) {
@@ -5659,6 +5854,8 @@ Double-check your response based on the criteria above. If everything looks good
5659
5854
  this._abortController.abort();
5660
5855
  }
5661
5856
 
5857
+ if (this.governedCodexProfile && this.engine?.close) await this.engine.close();
5858
+
5662
5859
  // Clean up MCP bridge
5663
5860
  if (this.mcpBridge) {
5664
5861
  try {