@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.
- 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
- 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
- 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
- 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
- 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
- package/build/agent/ProbeAgent.d.ts +105 -4
- package/build/agent/ProbeAgent.js +209 -12
- package/build/agent/bashExecutor.js +36 -101
- package/build/agent/engines/codex.js +367 -88
- package/build/agent/engines/governed-answer-failure.js +152 -0
- package/build/agent/engines/governed-codex-profile.js +198 -0
- package/build/agent/governance/acknowledgedJsonlChannel.js +328 -0
- package/build/agent/governance/atomicTerminalReceipt.js +188 -0
- package/build/agent/governance/index.d.ts +130 -0
- package/build/agent/governance/index.js +8 -0
- package/build/agent/mcp/built-in-server.js +152 -53
- package/build/agent/mcp/index.d.ts +65 -0
- package/build/agent/mcp/index.js +6 -1
- package/build/agent/probeTool.js +1 -1
- package/build/agent/processSupervisor.js +351 -0
- package/build/agent/tools.js +14 -8
- package/build/index.js +2 -0
- package/build/utils/provider.js +9 -3
- package/cjs/agent/ProbeAgent.cjs +13463 -12187
- package/cjs/index.cjs +75974 -74139
- package/index.d.ts +149 -4
- package/package.json +6 -2
- package/src/agent/ProbeAgent.d.ts +105 -4
- package/src/agent/ProbeAgent.js +209 -12
- package/src/agent/bashExecutor.js +36 -101
- package/src/agent/engines/codex.js +367 -88
- package/src/agent/engines/governed-answer-failure.js +152 -0
- package/src/agent/engines/governed-codex-profile.js +198 -0
- package/src/agent/governance/acknowledgedJsonlChannel.js +328 -0
- package/src/agent/governance/atomicTerminalReceipt.js +188 -0
- package/src/agent/governance/index.d.ts +130 -0
- package/src/agent/governance/index.js +8 -0
- package/src/agent/mcp/built-in-server.js +152 -53
- package/src/agent/mcp/index.d.ts +65 -0
- package/src/agent/mcp/index.js +6 -1
- package/src/agent/probeTool.js +1 -1
- package/src/agent/processSupervisor.js +351 -0
- package/src/agent/tools.js +14 -8
- package/src/index.js +2 -0
- package/src/utils/provider.js +9 -3
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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 '../
|
|
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.
|
|
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
|
-
|
|
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 {
|