@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.
- 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
- 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
- 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
- package/bin/binaries/probe-v0.6.0-rc341-x86_64-pc-windows-msvc.zip +0 -0
- 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
- package/build/agent/ProbeAgent.d.ts +37 -9
- package/build/agent/ProbeAgent.js +196 -62
- package/build/agent/acp/tools.js +1 -1
- package/build/agent/engines/codex.js +189 -52
- package/build/agent/engines/governed-answer-failure.js +164 -5
- package/build/agent/engines/governed-codex-exec.js +1227 -0
- package/build/agent/engines/governed-codex-profile.js +80 -28
- package/build/agent/governance/index.d.ts +18 -0
- package/build/agent/governance/index.js +14 -0
- package/build/index.js +26 -0
- package/cjs/agent/ProbeAgent.cjs +3291 -1117
- package/cjs/index.cjs +3318 -1119
- package/index.d.ts +53 -7
- package/package.json +1 -1
- package/src/agent/ProbeAgent.d.ts +37 -9
- package/src/agent/ProbeAgent.js +196 -62
- package/src/agent/acp/tools.js +1 -1
- package/src/agent/engines/codex.js +189 -52
- package/src/agent/engines/governed-answer-failure.js +164 -5
- package/src/agent/engines/governed-codex-exec.js +1227 -0
- package/src/agent/engines/governed-codex-profile.js +80 -28
- package/src/agent/governance/index.d.ts +18 -0
- package/src/agent/governance/index.js +14 -0
- package/src/index.js +26 -0
- package/bin/binaries/probe-v0.6.0-rc339-aarch64-apple-darwin.tar.gz +0 -0
|
@@ -0,0 +1,1227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-query Codex `exec --json` transport for the governed profile.
|
|
3
|
+
*
|
|
4
|
+
* This module intentionally has a smaller boundary than the MCP-server
|
|
5
|
+
* engine. It retains only the final agent answer, bounded usage, and public
|
|
6
|
+
* process facts. Raw JSONL events, stderr, reasoning, rollouts, and config
|
|
7
|
+
* contents never cross the returned result boundary.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
12
|
+
import { mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
14
|
+
import { isAbsolute, normalize } from 'node:path';
|
|
15
|
+
import { tmpdir } from 'node:os';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { BuiltInMCPServer } from '../mcp/built-in-server.js';
|
|
18
|
+
import Ajv from 'ajv';
|
|
19
|
+
import { governSpawnedProcess } from '../processSupervisor.js';
|
|
20
|
+
import { governedCodexDispatch } from './codex.js';
|
|
21
|
+
import { buildGovernedCodexInitialToolArgs, validateGovernedCodexProfile } from './governed-codex-profile.js';
|
|
22
|
+
import { GovernedAnswerFailure, governedAnswerFailure, normalizeGovernedAnswerFailure } from './governed-answer-failure.js';
|
|
23
|
+
|
|
24
|
+
export const GOVERNED_CODEX_EXEC_PROTOCOL = 'probe.governed-codex-exec/v1';
|
|
25
|
+
export const GOVERNED_CODEX_EXEC_ATTESTATION_VERSION = 'probe.governed-codex-exec-attestation/v1';
|
|
26
|
+
export const GOVERNED_CODEX_EXEC_TRANSPORT = 'exec-jsonl-default-auth-v1';
|
|
27
|
+
|
|
28
|
+
const DEFAULT_TIMEOUT_MS = 600000;
|
|
29
|
+
const MIN_TIMEOUT_MS = 10;
|
|
30
|
+
const MAX_TIMEOUT_MS = 3600000;
|
|
31
|
+
const STDOUT_BYTE_CAP = 4 * 1024 * 1024;
|
|
32
|
+
const STDERR_BYTE_CAP = 1024 * 1024;
|
|
33
|
+
const JSONL_LINE_BYTE_CAP = 1024 * 1024;
|
|
34
|
+
const MAX_TEXT_BYTES = 131072;
|
|
35
|
+
const MAX_EFFECTIVE_INPUT_BYTES = MAX_TEXT_BYTES;
|
|
36
|
+
const SAFE_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
37
|
+
const GOVERNED_CODEX_EXEC_FAILURE_CODES = new Set([
|
|
38
|
+
'ANSWER_CARDINALITY', 'CANCELLED', 'CANONICAL', 'CLEANUP', 'CONFIG', 'DUPLICATE', 'EVENT',
|
|
39
|
+
'EVENT_CATEGORY', 'EVENT_LIMIT', 'EVENT_ORDER', 'INCOMPLETE', 'INCOMPLETE_ITEM', 'ITEM',
|
|
40
|
+
'ITEM_ORDER', 'ITEM_STATUS', 'JSONL', 'MCP', 'MCP_EVIDENCE', 'ONE_QUERY', 'OUTPUT_OVERFLOW',
|
|
41
|
+
'SETUP', 'SPAWN', 'TIMEOUT', 'TOOL_POLICY', 'USAGE', 'VERSION', 'EXIT',
|
|
42
|
+
].map(code => `GOVERNED_CODEX_EXEC_${code}`));
|
|
43
|
+
const GOVERNED_CODEX_EXEC_ITEM_PREDICATES = new Set([
|
|
44
|
+
'item_keys', 'item_id', 'item_text', 'item_phase', 'item_summary', 'item_server',
|
|
45
|
+
'item_command', 'item_aggregated_output', 'item_exit_code', 'item_changes', 'item_status',
|
|
46
|
+
'item_error', 'item_started_payload', 'tool_id',
|
|
47
|
+
]);
|
|
48
|
+
const GOVERNED_CODEX_EXEC_ITEM_TYPES = new Set(['agent_message', 'reasoning', 'mcp_tool_call', 'command_execution', 'file_change']);
|
|
49
|
+
const GOVERNED_CODEX_EXEC_ITEM_EVENT_TYPES = new Set(['item.started', 'item.completed']);
|
|
50
|
+
const GOVERNED_CODEX_EXEC_FAILURE_EVENT_TYPES = new Set(['error', 'turn.failed']);
|
|
51
|
+
const GOVERNED_CODEX_EXEC_SCHEMA_PROVIDER_ERROR = Object.freeze({
|
|
52
|
+
type: 'invalid_request_error', status: 400, code: 'invalid_json_schema',
|
|
53
|
+
param: 'text.format.schema', schemaKeyword: 'uniqueItems',
|
|
54
|
+
});
|
|
55
|
+
const GOVERNED_CODEX_EXEC_SCHEMA_MAP_KEYS = new Set(['$defs', 'definitions', 'dependentSchemas', 'patternProperties', 'properties']);
|
|
56
|
+
const GOVERNED_CODEX_EXEC_SCHEMA_CHILD_KEYS = new Set([
|
|
57
|
+
'additionalItems', 'additionalProperties', 'contains', 'contentSchema', 'else', 'if', 'items',
|
|
58
|
+
'not', 'propertyNames', 'then', 'unevaluatedItems', 'unevaluatedProperties',
|
|
59
|
+
]);
|
|
60
|
+
const GOVERNED_CODEX_EXEC_SCHEMA_ARRAY_KEYS = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
|
|
61
|
+
const GOVERNED_CODEX_EXEC_SAFE_FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_.-]{0,63}$/;
|
|
62
|
+
const GOVERNED_CODEX_EXEC_FIELD_TYPES = new Set(['null', 'array', 'object', 'string', 'number', 'boolean']);
|
|
63
|
+
const USAGE_KEYS = new Set([
|
|
64
|
+
'input_tokens', 'cached_input_tokens', 'cache_write_input_tokens',
|
|
65
|
+
'output_tokens', 'reasoning_output_tokens'
|
|
66
|
+
]);
|
|
67
|
+
const PUBLIC_ITEM_TYPES = new Set(['agent_message', 'reasoning', 'mcp_tool_call']);
|
|
68
|
+
const NATIVE_ITEM_TYPES = new Set(['command_execution', 'file_change']);
|
|
69
|
+
const DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
70
|
+
const RAW_DIGEST = /^[0-9a-f]{64}$/;
|
|
71
|
+
const VERSION_TEXT = /^[\x20-\x7e]{1,256}$/;
|
|
72
|
+
|
|
73
|
+
function summarizeStderr(value) {
|
|
74
|
+
if (typeof value !== 'string' || value.length === 0) return null;
|
|
75
|
+
const bytes = Buffer.byteLength(value, 'utf8');
|
|
76
|
+
const normalized = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '').trim();
|
|
77
|
+
const safeMessage = /access token could not be refreshed because your refresh token was revoked/i.test(normalized)
|
|
78
|
+
? 'access_token_refresh_revoked' : undefined;
|
|
79
|
+
return Object.freeze({ source: 'codex-exec-stderr/v1', bytes, digest: sha256(value), ...(safeMessage ? { safeMessage } : {}) });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function fail(code, diagnostic = undefined, event = undefined) {
|
|
83
|
+
const error = new Error(`GOVERNED_CODEX_EXEC_${code}`);
|
|
84
|
+
error.code = `GOVERNED_CODEX_EXEC_${code}`;
|
|
85
|
+
const stderr = summarizeStderr(diagnostic);
|
|
86
|
+
if (stderr) Object.defineProperty(error, 'diagnostic', { value: stderr, enumerable: true });
|
|
87
|
+
if (event !== undefined) Object.defineProperty(error, 'event', { value: event, enumerable: true });
|
|
88
|
+
return error;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ownDataValue(value, key) {
|
|
92
|
+
if (!value || (typeof value !== 'object' && typeof value !== 'function')) return undefined;
|
|
93
|
+
try {
|
|
94
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
95
|
+
return descriptor && 'value' in descriptor ? descriptor.value : undefined;
|
|
96
|
+
} catch { return undefined; }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function describeFieldType(descriptor) {
|
|
100
|
+
if (!descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) return 'object';
|
|
101
|
+
const value = descriptor.value;
|
|
102
|
+
if (value === null) return 'null';
|
|
103
|
+
if (Array.isArray(value)) return 'array';
|
|
104
|
+
const type = typeof value;
|
|
105
|
+
return GOVERNED_CODEX_EXEC_FIELD_TYPES.has(type) ? type : 'object';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function describeFields(value) {
|
|
109
|
+
if (!value || (typeof value !== 'object' && typeof value !== 'function')) return Object.freeze([]);
|
|
110
|
+
let keys;
|
|
111
|
+
try { keys = Object.keys(value); } catch { return Object.freeze([]); }
|
|
112
|
+
const fields = [];
|
|
113
|
+
for (const key of keys.sort().slice(0, 32)) {
|
|
114
|
+
let descriptor;
|
|
115
|
+
try { descriptor = Object.getOwnPropertyDescriptor(value, key); } catch { continue; }
|
|
116
|
+
if (!descriptor) continue;
|
|
117
|
+
const rawName = key;
|
|
118
|
+
const name = GOVERNED_CODEX_EXEC_SAFE_FIELD_NAME.test(rawName) ? rawName : '<unsafe>';
|
|
119
|
+
const type = describeFieldType(descriptor);
|
|
120
|
+
const field = { name, type };
|
|
121
|
+
if (Object.prototype.hasOwnProperty.call(descriptor, 'value') && typeof descriptor.value === 'string') {
|
|
122
|
+
field.size = Buffer.byteLength(descriptor.value, 'utf8');
|
|
123
|
+
} else if (Object.prototype.hasOwnProperty.call(descriptor, 'value') && Array.isArray(descriptor.value)) {
|
|
124
|
+
field.size = descriptor.value.length;
|
|
125
|
+
}
|
|
126
|
+
fields.push(field);
|
|
127
|
+
}
|
|
128
|
+
fields.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
129
|
+
return Object.freeze(fields.map(field => Object.freeze(field)));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function rejectedItemEvent(event, predicate) {
|
|
133
|
+
const item = ownDataValue(event, 'item');
|
|
134
|
+
const itemType = ownDataValue(item, 'type');
|
|
135
|
+
if (!GOVERNED_CODEX_EXEC_ITEM_PREDICATES.has(predicate) ||
|
|
136
|
+
!GOVERNED_CODEX_EXEC_ITEM_EVENT_TYPES.has(ownDataValue(event, 'type')) ||
|
|
137
|
+
!GOVERNED_CODEX_EXEC_ITEM_TYPES.has(itemType)) return undefined;
|
|
138
|
+
return freeze({
|
|
139
|
+
source: 'codex-exec-rejected-item/v1', predicate,
|
|
140
|
+
eventType: ownDataValue(event, 'type'), itemType,
|
|
141
|
+
eventFields: describeFields(event), itemFields: describeFields(item),
|
|
142
|
+
...(predicate === 'item_status' && (item.status === 'failed' || item.status === 'declined')
|
|
143
|
+
? { itemStatus: item.status } : {}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function rejectedFailureEvent(event) {
|
|
148
|
+
const eventType = ownDataValue(event, 'type');
|
|
149
|
+
if (!GOVERNED_CODEX_EXEC_FAILURE_EVENT_TYPES.has(eventType)) return undefined;
|
|
150
|
+
const expectedKeys = eventType === 'error' ? ['message', 'type'] : ['error', 'type'];
|
|
151
|
+
if (!exactKeys(event, expectedKeys)) return undefined;
|
|
152
|
+
if (eventType === 'error' && typeof ownDataValue(event, 'message') !== 'string') return undefined;
|
|
153
|
+
if (eventType === 'turn.failed' &&
|
|
154
|
+
(!ownObject(ownDataValue(event, 'error')) || typeof ownDataValue(event.error, 'message') !== 'string')) return undefined;
|
|
155
|
+
const message = eventType === 'error' ? ownDataValue(event, 'message') : ownDataValue(event.error, 'message');
|
|
156
|
+
const providerError = classifySchemaProviderFailure(message);
|
|
157
|
+
return freeze({
|
|
158
|
+
source: 'codex-exec-rejected-failure/v1', category: 'failure', eventType,
|
|
159
|
+
eventFields: describeFields(event),
|
|
160
|
+
...(providerError ? { providerError } : {}),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function classifySchemaProviderFailure(message) {
|
|
165
|
+
if (typeof message !== 'string') return undefined;
|
|
166
|
+
let envelope;
|
|
167
|
+
try { envelope = JSON.parse(message); } catch { return undefined; }
|
|
168
|
+
if (!ownObject(envelope) || !exactKeys(envelope, ['error', 'status', 'type']) ||
|
|
169
|
+
envelope.type !== 'error' || envelope.status !== 400 || !ownObject(envelope.error) ||
|
|
170
|
+
!exactKeys(envelope.error, ['code', 'message', 'param', 'type']) ||
|
|
171
|
+
envelope.error.type !== 'invalid_request_error' || envelope.error.code !== 'invalid_json_schema' ||
|
|
172
|
+
envelope.error.param !== 'text.format.schema' || typeof envelope.error.message !== 'string' ||
|
|
173
|
+
!/["']uniqueItems["']\s+is\s+not\s+permitted(?:\.|$)/.test(envelope.error.message)) return undefined;
|
|
174
|
+
return GOVERNED_CODEX_EXEC_SCHEMA_PROVIDER_ERROR;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function lowerUnsupportedOutputSchema(schemaObject) {
|
|
178
|
+
let lowered = false;
|
|
179
|
+
const visitSchema = value => {
|
|
180
|
+
if (!ownObject(value)) return;
|
|
181
|
+
if (Object.prototype.hasOwnProperty.call(value, 'uniqueItems')) {
|
|
182
|
+
delete value.uniqueItems;
|
|
183
|
+
lowered = true;
|
|
184
|
+
}
|
|
185
|
+
for (const key of GOVERNED_CODEX_EXEC_SCHEMA_MAP_KEYS) {
|
|
186
|
+
const child = value[key];
|
|
187
|
+
if (ownObject(child)) for (const schema of Object.values(child)) visitSchema(schema);
|
|
188
|
+
}
|
|
189
|
+
for (const key of GOVERNED_CODEX_EXEC_SCHEMA_CHILD_KEYS) visitSchema(value[key]);
|
|
190
|
+
for (const key of GOVERNED_CODEX_EXEC_SCHEMA_ARRAY_KEYS) {
|
|
191
|
+
const children = value[key];
|
|
192
|
+
if (Array.isArray(children)) for (const schema of children) visitSchema(schema);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
visitSchema(schemaObject);
|
|
196
|
+
return lowered ? JSON.stringify(schemaObject) : null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function projectExecStderr(value) {
|
|
200
|
+
if (!ownObject(value)) return undefined;
|
|
201
|
+
const source = ownDataValue(value, 'source');
|
|
202
|
+
const bytes = ownDataValue(value, 'bytes');
|
|
203
|
+
const digest = ownDataValue(value, 'digest');
|
|
204
|
+
if (source !== 'codex-exec-stderr/v1' || !Number.isSafeInteger(bytes) || bytes < 1 || bytes > STDERR_BYTE_CAP ||
|
|
205
|
+
!DIGEST.test(digest)) return undefined;
|
|
206
|
+
const safeMessage = ownDataValue(value, 'safeMessage');
|
|
207
|
+
return Object.freeze({ source, bytes, digest, ...(safeMessage === 'access_token_refresh_revoked' ? { safeMessage } : {}) });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function projectExecItemFields(value) {
|
|
211
|
+
if (!Array.isArray(value) || value.length > 32) return undefined;
|
|
212
|
+
const fields = [];
|
|
213
|
+
let previousName = null;
|
|
214
|
+
for (const field of value) {
|
|
215
|
+
if (!ownObject(field)) return undefined;
|
|
216
|
+
const keys = Object.keys(field).sort();
|
|
217
|
+
if (keys.join(',') !== 'name,type' && keys.join(',') !== 'name,size,type') return undefined;
|
|
218
|
+
const name = ownDataValue(field, 'name');
|
|
219
|
+
const type = ownDataValue(field, 'type');
|
|
220
|
+
if (typeof name !== 'string' || (name !== '<unsafe>' && !GOVERNED_CODEX_EXEC_SAFE_FIELD_NAME.test(name)) ||
|
|
221
|
+
!GOVERNED_CODEX_EXEC_FIELD_TYPES.has(type) || (previousName !== null && name < previousName)) return undefined;
|
|
222
|
+
const hasSize = keys.includes('size');
|
|
223
|
+
const size = ownDataValue(field, 'size');
|
|
224
|
+
if (hasSize && (type !== 'string' && type !== 'array' || !Number.isSafeInteger(size) || size < 0)) return undefined;
|
|
225
|
+
if (!hasSize && (type === 'string' || type === 'array')) return undefined;
|
|
226
|
+
fields.push(Object.freeze({ name, type, ...(hasSize ? { size } : {}) }));
|
|
227
|
+
previousName = name;
|
|
228
|
+
}
|
|
229
|
+
return Object.freeze(fields);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function projectExecItemEvent(value) {
|
|
233
|
+
if (!ownObject(value)) return undefined;
|
|
234
|
+
const keys = Object.keys(value).sort();
|
|
235
|
+
const baseKeys = 'eventFields,eventType,itemFields,itemType,predicate,source';
|
|
236
|
+
const statusKeys = 'eventFields,eventType,itemFields,itemStatus,itemType,predicate,source';
|
|
237
|
+
if (keys.join(',') !== baseKeys && keys.join(',') !== statusKeys) return undefined;
|
|
238
|
+
const source = ownDataValue(value, 'source');
|
|
239
|
+
const predicate = ownDataValue(value, 'predicate');
|
|
240
|
+
const eventType = ownDataValue(value, 'eventType');
|
|
241
|
+
const itemType = ownDataValue(value, 'itemType');
|
|
242
|
+
const itemStatus = ownDataValue(value, 'itemStatus');
|
|
243
|
+
const eventFields = projectExecItemFields(ownDataValue(value, 'eventFields'));
|
|
244
|
+
const itemFields = projectExecItemFields(ownDataValue(value, 'itemFields'));
|
|
245
|
+
if (source !== 'codex-exec-rejected-item/v1' || !GOVERNED_CODEX_EXEC_ITEM_PREDICATES.has(predicate) ||
|
|
246
|
+
!GOVERNED_CODEX_EXEC_ITEM_EVENT_TYPES.has(eventType) || !GOVERNED_CODEX_EXEC_ITEM_TYPES.has(itemType) ||
|
|
247
|
+
!eventFields || !itemFields || (itemStatus !== undefined && predicate !== 'item_status') ||
|
|
248
|
+
(itemStatus !== undefined && itemStatus !== 'failed' && itemStatus !== 'declined') ||
|
|
249
|
+
(itemStatus === undefined && keys.includes('itemStatus'))) return undefined;
|
|
250
|
+
return freeze({ source, predicate, eventType, itemType, eventFields, itemFields,
|
|
251
|
+
...(itemStatus === undefined ? {} : { itemStatus }) });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function projectExecFailureEvent(value) {
|
|
255
|
+
if (!ownObject(value)) return undefined;
|
|
256
|
+
const keys = Object.keys(value).sort();
|
|
257
|
+
const baseKeys = 'category,eventFields,eventType,source';
|
|
258
|
+
const providerKeys = 'category,eventFields,eventType,providerError,source';
|
|
259
|
+
if (keys.join(',') !== baseKeys && keys.join(',') !== providerKeys) return undefined;
|
|
260
|
+
const category = ownDataValue(value, 'category');
|
|
261
|
+
const eventType = ownDataValue(value, 'eventType');
|
|
262
|
+
const eventFields = projectExecItemFields(ownDataValue(value, 'eventFields'));
|
|
263
|
+
const providerError = ownDataValue(value, 'providerError');
|
|
264
|
+
const providerErrorKeys = providerError && ownObject(providerError) ? Object.keys(providerError).sort().join(',') : null;
|
|
265
|
+
if (ownDataValue(value, 'source') !== 'codex-exec-rejected-failure/v1' || category !== 'failure' ||
|
|
266
|
+
!GOVERNED_CODEX_EXEC_FAILURE_EVENT_TYPES.has(eventType) || !eventFields ||
|
|
267
|
+
(keys.join(',') === providerKeys && providerErrorKeys !== 'code,param,schemaKeyword,status,type') ||
|
|
268
|
+
(keys.join(',') === baseKeys && providerError !== undefined)) return undefined;
|
|
269
|
+
if (providerError !== undefined &&
|
|
270
|
+
(ownDataValue(providerError, 'type') !== 'invalid_request_error' || ownDataValue(providerError, 'status') !== 400 ||
|
|
271
|
+
ownDataValue(providerError, 'code') !== 'invalid_json_schema' || ownDataValue(providerError, 'param') !== 'text.format.schema' ||
|
|
272
|
+
ownDataValue(providerError, 'schemaKeyword') !== 'uniqueItems')) return undefined;
|
|
273
|
+
return freeze({ source: ownDataValue(value, 'source'), category, eventType, eventFields,
|
|
274
|
+
...(providerError === undefined ? {} : { providerError: GOVERNED_CODEX_EXEC_SCHEMA_PROVIDER_ERROR }) });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Project an exec error into the closed public diagnostic carried by Probe. */
|
|
278
|
+
export function projectGovernedCodexExecFailure(error) {
|
|
279
|
+
const code = ownDataValue(error, 'code');
|
|
280
|
+
if (typeof code !== 'string' || !GOVERNED_CODEX_EXEC_FAILURE_CODES.has(code)) return null;
|
|
281
|
+
const stderr = projectExecStderr(ownDataValue(error, 'diagnostic'));
|
|
282
|
+
const rawEvent = ownDataValue(error, 'event');
|
|
283
|
+
const event = projectExecItemEvent(rawEvent) ?? projectExecFailureEvent(rawEvent);
|
|
284
|
+
return Object.freeze({ version: 'probe.governed-codex-exec-failure/v1', code,
|
|
285
|
+
...(stderr ? { stderr } : {}), ...(event ? { event } : {}) });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Normalize only the explicit exec transport; legacy paths retain their old shape. */
|
|
289
|
+
export function normalizeGovernedCodexExecFailure(error, boundary) {
|
|
290
|
+
if (error instanceof GovernedAnswerFailure) return error;
|
|
291
|
+
const diagnostic = projectGovernedCodexExecFailure(error);
|
|
292
|
+
if (!diagnostic) return normalizeGovernedAnswerFailure(error, 'provider_engine', null, null, null, null, null, boundary);
|
|
293
|
+
return governedAnswerFailure('provider_engine', null, null, null, null, null, null, boundary, null, diagnostic);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function canonicalJson(value) {
|
|
297
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value);
|
|
298
|
+
if (typeof value === 'number' && Number.isFinite(value)) return JSON.stringify(value);
|
|
299
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
300
|
+
if (ownObject(value)) return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
|
|
301
|
+
throw fail('CANONICAL');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function sha256(value) {
|
|
305
|
+
const bytes = Buffer.isBuffer(value) || value instanceof Uint8Array
|
|
306
|
+
? Buffer.from(value) : typeof value === 'string' ? Buffer.from(value, 'utf8') : Buffer.from(canonicalJson(value), 'utf8');
|
|
307
|
+
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function digestText(value) { return sha256(value); }
|
|
311
|
+
|
|
312
|
+
function requireDigest(value, label) {
|
|
313
|
+
if (typeof value !== 'string' || !DIGEST.test(value)) throw new TypeError(`Invalid ${label}`);
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function freeze(value) {
|
|
318
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
319
|
+
for (const child of Object.values(value)) freeze(child);
|
|
320
|
+
Object.freeze(value);
|
|
321
|
+
}
|
|
322
|
+
return value;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function ownObject(value) {
|
|
326
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) &&
|
|
327
|
+
(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function exactKeys(value, keys) {
|
|
331
|
+
if (!ownObject(value)) return false;
|
|
332
|
+
const actual = Object.keys(value).sort();
|
|
333
|
+
return actual.length === keys.length && keys.every(key => actual.includes(key));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function safeAbsoluteFile(value, label) {
|
|
337
|
+
if (typeof value !== 'string' || !isAbsolute(value) || value.includes('\0') || normalize(value) !== value) {
|
|
338
|
+
throw new TypeError(`Invalid ${label}`);
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const resolved = realpathSync(value);
|
|
342
|
+
if (!statSync(resolved).isFile()) throw new Error('not a file');
|
|
343
|
+
return resolved;
|
|
344
|
+
} catch {
|
|
345
|
+
throw new TypeError(`Invalid ${label}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function normalizeCliDigest(value) {
|
|
350
|
+
if (typeof value !== 'string') throw new TypeError('Missing Codex executable SHA-256');
|
|
351
|
+
const bare = /^[0-9a-f]{64}$/.test(value) ? `sha256:${value}` : value;
|
|
352
|
+
return requireDigest(bare, 'Codex executable SHA-256');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function executableDigest(command) {
|
|
356
|
+
try { return sha256(readFileSync(command)); } catch { throw new TypeError('Unable to read Codex executable'); }
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function safeVersion(value) {
|
|
360
|
+
if (typeof value !== 'string') throw fail('VERSION');
|
|
361
|
+
const version = value.trim();
|
|
362
|
+
if (!VERSION_TEXT.test(version) || version.includes('\n') || version.includes('\r')) throw fail('VERSION');
|
|
363
|
+
return version;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function validateTimeout(value) {
|
|
367
|
+
if (value === undefined) return DEFAULT_TIMEOUT_MS;
|
|
368
|
+
if (!Number.isSafeInteger(value) || value < MIN_TIMEOUT_MS || value > MAX_TIMEOUT_MS) {
|
|
369
|
+
throw new TypeError('Invalid governed Codex exec timeout');
|
|
370
|
+
}
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function validateSignal(value) {
|
|
375
|
+
if (value === undefined) return;
|
|
376
|
+
if (!value || typeof value !== 'object' || typeof value.aborted !== 'boolean' ||
|
|
377
|
+
typeof value.addEventListener !== 'function' || typeof value.removeEventListener !== 'function') {
|
|
378
|
+
throw new TypeError('Invalid governed Codex exec signal');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function tomlLiteral(value) {
|
|
383
|
+
if (typeof value === 'string') return JSON.stringify(value);
|
|
384
|
+
if (typeof value === 'boolean') return String(value);
|
|
385
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
386
|
+
if (Array.isArray(value)) {
|
|
387
|
+
if (value.some(item => item !== null && typeof item === 'object')) throw fail('CONFIG');
|
|
388
|
+
return `[${value.map(tomlLiteral).join(',')}]`;
|
|
389
|
+
}
|
|
390
|
+
throw fail('CONFIG');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function flattenConfig(value, prefix, output) {
|
|
394
|
+
if (!ownObject(value)) throw fail('CONFIG');
|
|
395
|
+
for (const key of Object.keys(value).sort()) {
|
|
396
|
+
if (!/^[A-Za-z0-9_-]+$/.test(key)) throw fail('CONFIG');
|
|
397
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
398
|
+
const child = value[key];
|
|
399
|
+
if (ownObject(child)) flattenConfig(child, path, output);
|
|
400
|
+
else output.push(`${path}=${tomlLiteral(child)}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function cloneEnvironment() {
|
|
405
|
+
const env = { ...process.env };
|
|
406
|
+
// `exec --ignore-user-config` uses the normal auth location when this is
|
|
407
|
+
// absent. Do not let a caller's isolated test/profile home leak into exec.
|
|
408
|
+
delete env.CODEX_HOME;
|
|
409
|
+
return env;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function bareDigest(value) {
|
|
413
|
+
const bytes = typeof value === 'string' ? Buffer.from(value, 'utf8') : Buffer.from(canonicalJson(value), 'utf8');
|
|
414
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function profileProjection(profile) {
|
|
418
|
+
const native = Array.isArray(profile.probeMcpTools);
|
|
419
|
+
const base = {
|
|
420
|
+
profileDigest: bareDigest(profile),
|
|
421
|
+
cwdDigest: bareDigest(profile.cwd),
|
|
422
|
+
...(native ? {
|
|
423
|
+
probeMcpToolsDigest: bareDigest(profile.probeMcpTools),
|
|
424
|
+
codexNativeToolsDigest: bareDigest(profile.codexNativeTools),
|
|
425
|
+
probeMcpTools: [...profile.probeMcpTools],
|
|
426
|
+
codexNativeTools: [...profile.codexNativeTools],
|
|
427
|
+
} : { probeToolsDigest: bareDigest(profile.probeTools) }),
|
|
428
|
+
model: profile.model,
|
|
429
|
+
reasoningEffort: profile.reasoningEffort,
|
|
430
|
+
sandbox: profile.sandbox,
|
|
431
|
+
approvalPolicy: profile.approvalPolicy,
|
|
432
|
+
};
|
|
433
|
+
return freeze(base);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function loopbackDigest(mcp, config) {
|
|
437
|
+
const server = config?.mcp_servers?.[mcp.name];
|
|
438
|
+
if (!ownObject(server) || server.url !== mcp.url || !Array.isArray(server.enabled_tools) ||
|
|
439
|
+
server.default_tools_approval_mode !== 'prompt' || !ownObject(server.tools)) throw fail('MCP');
|
|
440
|
+
const tools = server.enabled_tools.map(name => {
|
|
441
|
+
if (typeof name !== 'string' || !ownObject(server.tools[name]) || server.tools[name].approval_mode !== 'approve') throw fail('MCP');
|
|
442
|
+
return name;
|
|
443
|
+
});
|
|
444
|
+
return sha256({ version: 'probe.governed-codex-loopback/v1', name: mcp.name, url: mcp.url,
|
|
445
|
+
enabledTools: tools, defaultToolsApprovalMode: server.default_tools_approval_mode,
|
|
446
|
+
tools: Object.fromEntries(tools.sort().map(name => [name, { approvalMode: server.tools[name].approval_mode }])) });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function validatePrompt(prompt) {
|
|
450
|
+
if (typeof prompt !== 'string' || Buffer.byteLength(prompt, 'utf8') < 1 ||
|
|
451
|
+
Buffer.byteLength(prompt, 'utf8') > MAX_TEXT_BYTES || prompt.includes('\0')) {
|
|
452
|
+
throw new TypeError('Invalid governed Codex exec prompt');
|
|
453
|
+
}
|
|
454
|
+
return prompt;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function validateOutputSchema(schema) {
|
|
458
|
+
if (typeof schema !== 'string' || schema.length === 0 || schema.includes('\0') ||
|
|
459
|
+
Buffer.byteLength(schema, 'utf8') > MAX_TEXT_BYTES) {
|
|
460
|
+
throw new TypeError('Invalid governed Codex output schema');
|
|
461
|
+
}
|
|
462
|
+
let parsed;
|
|
463
|
+
try { parsed = JSON.parse(schema); } catch { throw new TypeError('Invalid governed Codex output schema'); }
|
|
464
|
+
try { new Ajv({ strict: false }).compile(parsed); } catch { throw new TypeError('Invalid governed Codex output schema'); }
|
|
465
|
+
const lowered = lowerUnsupportedOutputSchema(parsed);
|
|
466
|
+
if (lowered === null) return schema;
|
|
467
|
+
try { new Ajv({ strict: false }).compile(JSON.parse(lowered)); } catch { throw new TypeError('Invalid governed Codex output schema'); }
|
|
468
|
+
return lowered;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function verifyCodexVersion(command, cwd, signal, timeoutMs) {
|
|
472
|
+
let child;
|
|
473
|
+
try {
|
|
474
|
+
child = spawn(command, ['--version'], {
|
|
475
|
+
cwd, env: cloneEnvironment(), stdio: ['ignore', 'pipe', 'pipe'], shell: false,
|
|
476
|
+
detached: true, windowsHide: true,
|
|
477
|
+
});
|
|
478
|
+
} catch { throw fail('SPAWN'); }
|
|
479
|
+
const handle = governSpawnedProcess(child, {
|
|
480
|
+
captureStdout: true, stdoutByteCap: 4096, stderrByteCap: 4096,
|
|
481
|
+
signalScope: 'process-group', executionTimeoutMs: Math.min(timeoutMs, 30000), signal,
|
|
482
|
+
});
|
|
483
|
+
const receipt = await handle.result;
|
|
484
|
+
if (receipt.classification === 'aborted') throw fail('CANCELLED', receipt.stderr);
|
|
485
|
+
if (receipt.classification === 'execution_timeout') throw fail('TIMEOUT', receipt.stderr);
|
|
486
|
+
if (receipt.classification !== 'exited' || receipt.exitCode !== 0 || receipt.signal !== null ||
|
|
487
|
+
!receipt.barriers.stdoutEOF || !receipt.barriers.stderrEOF || !receipt.barriers.close) throw fail('VERSION', receipt.stderr);
|
|
488
|
+
return safeVersion(receipt.stdout);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Build the no-shell launch description used by the governed exec engine.
|
|
493
|
+
* The returned object deliberately does not expose the inherited environment.
|
|
494
|
+
*/
|
|
495
|
+
export function buildGovernedCodexExecLaunch({ codexPath, codexSha256, cliVersion, profile, prompt, mcp, modelInstructionsPath, modelInstructionsDigest, outputSchemaPath, outputSchemaDigest }) {
|
|
496
|
+
const command = safeAbsoluteFile(codexPath, 'Codex executable');
|
|
497
|
+
const cliSha256 = normalizeCliDigest(codexSha256);
|
|
498
|
+
const normalizedProfile = validateGovernedCodexProfile(profile);
|
|
499
|
+
const normalizedPrompt = validatePrompt(prompt);
|
|
500
|
+
if (!mcp || typeof mcp !== 'object' || typeof mcp.name !== 'string' || typeof mcp.url !== 'string') {
|
|
501
|
+
throw new TypeError('Invalid governed Codex exec MCP binding');
|
|
502
|
+
}
|
|
503
|
+
const initial = buildGovernedCodexInitialToolArgs({ profile: normalizedProfile, prompt: normalizedPrompt, mcp });
|
|
504
|
+
let effectiveConfig = initial.config;
|
|
505
|
+
let effectiveInstructionsDigest = modelInstructionsDigest;
|
|
506
|
+
let instructionsBytes = 0;
|
|
507
|
+
let normalizedOutputSchemaPath;
|
|
508
|
+
let effectiveOutputSchemaDigest;
|
|
509
|
+
let outputSchemaBytes = 0;
|
|
510
|
+
if (modelInstructionsPath === undefined && modelInstructionsDigest !== undefined) {
|
|
511
|
+
throw new TypeError('Model instructions digest requires a file');
|
|
512
|
+
}
|
|
513
|
+
if (outputSchemaPath === undefined && outputSchemaDigest !== undefined) {
|
|
514
|
+
throw new TypeError('Codex output schema digest requires a file');
|
|
515
|
+
}
|
|
516
|
+
if (modelInstructionsPath !== undefined) {
|
|
517
|
+
const instructionsPath = safeAbsoluteFile(modelInstructionsPath, 'model instructions file');
|
|
518
|
+
const instructions = readFileSync(instructionsPath);
|
|
519
|
+
instructionsBytes = instructions.length;
|
|
520
|
+
const actualInstructionsDigest = sha256(instructions);
|
|
521
|
+
if (effectiveInstructionsDigest !== undefined && effectiveInstructionsDigest !== actualInstructionsDigest) {
|
|
522
|
+
throw new TypeError('Model instructions digest mismatch');
|
|
523
|
+
}
|
|
524
|
+
effectiveInstructionsDigest = actualInstructionsDigest;
|
|
525
|
+
requireDigest(effectiveInstructionsDigest, 'model instructions digest');
|
|
526
|
+
effectiveConfig = freeze({ ...initial.config, model_instructions_file: instructionsPath });
|
|
527
|
+
}
|
|
528
|
+
if (outputSchemaPath !== undefined) {
|
|
529
|
+
normalizedOutputSchemaPath = safeAbsoluteFile(outputSchemaPath, 'Codex output schema file');
|
|
530
|
+
const outputSchema = readFileSync(normalizedOutputSchemaPath);
|
|
531
|
+
outputSchemaBytes = outputSchema.length;
|
|
532
|
+
const actualOutputSchemaDigest = sha256(outputSchema);
|
|
533
|
+
if (outputSchemaDigest !== undefined && outputSchemaDigest !== actualOutputSchemaDigest) {
|
|
534
|
+
throw new TypeError('Codex output schema digest mismatch');
|
|
535
|
+
}
|
|
536
|
+
effectiveOutputSchemaDigest = actualOutputSchemaDigest;
|
|
537
|
+
requireDigest(effectiveOutputSchemaDigest, 'Codex output schema digest');
|
|
538
|
+
}
|
|
539
|
+
const config = [];
|
|
540
|
+
flattenConfig(effectiveConfig, '', config);
|
|
541
|
+
// The MCP-server engine carries this as a request field. `exec` receives it
|
|
542
|
+
// through its normal TOML config surface instead.
|
|
543
|
+
config.push(`approval_policy=${tomlLiteral(normalizedProfile.approvalPolicy)}`);
|
|
544
|
+
const args = [
|
|
545
|
+
'exec', '--ignore-user-config', '--ignore-rules', '--ephemeral', '--json',
|
|
546
|
+
'--model', normalizedProfile.model, '--sandbox', normalizedProfile.sandbox,
|
|
547
|
+
'--cd', normalizedProfile.cwd,
|
|
548
|
+
...config.flatMap(entry => ['-c', entry]),
|
|
549
|
+
...(normalizedOutputSchemaPath === undefined ? [] : ['--output-schema', normalizedOutputSchemaPath]),
|
|
550
|
+
normalizedPrompt,
|
|
551
|
+
];
|
|
552
|
+
const dispatch = effectiveExecDispatch(normalizedPrompt, effectiveInstructionsDigest ?? null, instructionsBytes);
|
|
553
|
+
const promptDigest = dispatch.promptDigest;
|
|
554
|
+
const promptBytes = dispatch.promptBytes;
|
|
555
|
+
const configDigest = sha256(effectiveConfig);
|
|
556
|
+
const launchDigest = sha256({ version: 'probe.governed-codex-exec-launch/v1',
|
|
557
|
+
cliPath: command, cliSha256, ...(cliVersion === undefined ? {} : { cliVersion: safeVersion(cliVersion) }),
|
|
558
|
+
args: args.slice(0, -1), config: effectiveConfig, cwd: normalizedProfile.cwd,
|
|
559
|
+
promptDigest, promptBytes,
|
|
560
|
+
environmentPolicy: 'inherit-with-CODEX_HOME-omitted-v1',
|
|
561
|
+
...(effectiveInstructionsDigest === undefined ? {} : { instructionsDigest: effectiveInstructionsDigest, instructionsBytes }),
|
|
562
|
+
...(normalizedOutputSchemaPath === undefined ? {} : {
|
|
563
|
+
outputSchema: { path: normalizedOutputSchemaPath, digest: effectiveOutputSchemaDigest, bytes: outputSchemaBytes },
|
|
564
|
+
}) });
|
|
565
|
+
return Object.freeze({
|
|
566
|
+
command,
|
|
567
|
+
cliSha256,
|
|
568
|
+
...(cliVersion === undefined ? {} : { cliVersion: safeVersion(cliVersion) }),
|
|
569
|
+
args: Object.freeze(args),
|
|
570
|
+
argsWithoutPrompt: Object.freeze(args.slice(0, -1)),
|
|
571
|
+
config: effectiveConfig,
|
|
572
|
+
profile: normalizedProfile,
|
|
573
|
+
requested: profileProjection(normalizedProfile),
|
|
574
|
+
configDigest,
|
|
575
|
+
launchDigest,
|
|
576
|
+
promptDigest,
|
|
577
|
+
promptBytes,
|
|
578
|
+
...(effectiveInstructionsDigest === undefined ? {} : { instructionsBytes }),
|
|
579
|
+
...(effectiveInstructionsDigest === undefined ? {} : { instructionsDigest: effectiveInstructionsDigest }),
|
|
580
|
+
...(normalizedOutputSchemaPath === undefined ? {} : {
|
|
581
|
+
outputSchemaPath: normalizedOutputSchemaPath,
|
|
582
|
+
outputSchemaDigest: effectiveOutputSchemaDigest,
|
|
583
|
+
outputSchemaBytes,
|
|
584
|
+
}),
|
|
585
|
+
cwd: normalizedProfile.cwd,
|
|
586
|
+
model: normalizedProfile.model,
|
|
587
|
+
sandbox: normalizedProfile.sandbox,
|
|
588
|
+
approvalPolicy: normalizedProfile.approvalPolicy,
|
|
589
|
+
mcpName: mcp.name,
|
|
590
|
+
mcpUrl: mcp.url,
|
|
591
|
+
loopbackMcpDigest: loopbackDigest({ name: mcp.name, url: mcp.url }, effectiveConfig),
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function boundedCountSum(items, upperBound, onError) {
|
|
596
|
+
if (!Number.isSafeInteger(upperBound) || upperBound < 0) throw onError();
|
|
597
|
+
let total = 0;
|
|
598
|
+
for (const item of items) {
|
|
599
|
+
const count = typeof item === 'number' ? item : item?.count;
|
|
600
|
+
if (!Number.isSafeInteger(count) || count < 1 || total > Number.MAX_SAFE_INTEGER - count) throw onError();
|
|
601
|
+
total += count;
|
|
602
|
+
if (total > upperBound) throw onError();
|
|
603
|
+
}
|
|
604
|
+
return total;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function nextBoundedCount(current, increment, upperBound, onError) {
|
|
608
|
+
if (!Number.isSafeInteger(current) || current < 0 || !Number.isSafeInteger(increment) || increment < 1 ||
|
|
609
|
+
!Number.isSafeInteger(upperBound) || upperBound < 0 || current > Number.MAX_SAFE_INTEGER - increment) throw onError();
|
|
610
|
+
const next = current + increment;
|
|
611
|
+
if (next > upperBound) throw onError();
|
|
612
|
+
return next;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function validateUsedToolItems(value, completedItemCount) {
|
|
616
|
+
if (!Array.isArray(value) || !Number.isSafeInteger(completedItemCount) || completedItemCount < 1 || value.length > completedItemCount) {
|
|
617
|
+
throw new TypeError('Invalid exec used tool evidence');
|
|
618
|
+
}
|
|
619
|
+
const result = value.map(item => {
|
|
620
|
+
if (!exactKeys(item, ['category', 'name', 'status', 'count']) ||
|
|
621
|
+
!['mcp_tool_call', 'command_execution', 'file_change'].includes(item.category) ||
|
|
622
|
+
item.status !== 'completed' || !Number.isSafeInteger(item.count) || item.count < 1 || item.count > completedItemCount ||
|
|
623
|
+
(item.category === 'mcp_tool_call' ? typeof item.name !== 'string' : item.name !== null)) {
|
|
624
|
+
throw new TypeError('Invalid exec used tool evidence');
|
|
625
|
+
}
|
|
626
|
+
if (item.name !== null && (!/^[A-Za-z0-9_.:-]{1,128}$/.test(item.name))) throw new TypeError('Invalid exec used tool evidence');
|
|
627
|
+
return { category: item.category, name: item.name, status: 'completed', count: item.count };
|
|
628
|
+
});
|
|
629
|
+
const keys = result.map(item => `${item.category}\u0000${item.name ?? ''}`);
|
|
630
|
+
if (new Set(keys).size !== keys.length || keys.some((key, index) => index > 0 && key < keys[index - 1])) throw new TypeError('Invalid exec used tool evidence');
|
|
631
|
+
boundedCountSum(result, completedItemCount, () => new TypeError('Invalid exec used tool evidence'));
|
|
632
|
+
return result;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function validateProfileProjection(value, profile) {
|
|
636
|
+
const expected = profileProjection(profile);
|
|
637
|
+
if (!ownObject(value) || canonicalJson(value) !== canonicalJson(expected)) throw new TypeError('Invalid exec requested profile projection');
|
|
638
|
+
return expected;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function validateObserved(value, profileId = null) {
|
|
642
|
+
if (!ownObject(value)) throw new TypeError('Invalid exec observed evidence');
|
|
643
|
+
const keys = ['source', 'threadDigest', 'streamDigest', 'terminal', 'eventCount', 'completedItemCount', 'agentMessageCount', 'usedToolItems', 'probeMcpCallCount', 'finalDigest', 'finalBytes', 'processExitCode', 'processSignal'];
|
|
644
|
+
if (!exactKeys(value, keys) || value.source !== 'codex-exec-jsonl/v1' || value.terminal !== 'turn.completed' ||
|
|
645
|
+
value.processExitCode !== 0 || value.processSignal !== null ||
|
|
646
|
+
!DIGEST.test(value.threadDigest) || !DIGEST.test(value.streamDigest) || !DIGEST.test(value.finalDigest) ||
|
|
647
|
+
!Number.isSafeInteger(value.eventCount) || value.eventCount < 3 ||
|
|
648
|
+
!Number.isSafeInteger(value.completedItemCount) || value.completedItemCount < 1 || value.completedItemCount > value.eventCount - 3 ||
|
|
649
|
+
!Number.isSafeInteger(value.agentMessageCount) || value.agentMessageCount < 1 || value.agentMessageCount > value.completedItemCount ||
|
|
650
|
+
!Number.isSafeInteger(value.probeMcpCallCount) || value.probeMcpCallCount < 0 || value.probeMcpCallCount > value.completedItemCount ||
|
|
651
|
+
!Number.isSafeInteger(value.finalBytes) || value.finalBytes < 0 || value.finalBytes > MAX_TEXT_BYTES) throw new TypeError('Invalid exec observed evidence');
|
|
652
|
+
const usedToolItems = validateUsedToolItems(value.usedToolItems, value.completedItemCount);
|
|
653
|
+
const allowedNative = profileId === 'luna-xhigh-isolated-writer-v1' ? new Set(['command_execution', 'file_change'])
|
|
654
|
+
: profileId === 'luna-xhigh-readonly-native-exec-v1' ? new Set(['command_execution']) : new Set();
|
|
655
|
+
for (const item of usedToolItems) {
|
|
656
|
+
if (item.category === 'mcp_tool_call' && !['mcp__probe__search', 'mcp__probe__extract', 'mcp__probe__listFiles'].includes(item.name)) throw new TypeError('Invalid exec MCP tool');
|
|
657
|
+
if (item.category !== 'mcp_tool_call' && !allowedNative.has(item.category)) throw new TypeError('Invalid exec native tool');
|
|
658
|
+
}
|
|
659
|
+
const mcpToolItems = usedToolItems.filter(item => item.category === 'mcp_tool_call');
|
|
660
|
+
const mcpToolCount = boundedCountSum(mcpToolItems, value.completedItemCount, () => new TypeError('Invalid exec MCP evidence'));
|
|
661
|
+
if (value.probeMcpCallCount !== mcpToolCount) throw new TypeError('Invalid exec MCP evidence');
|
|
662
|
+
return { ...value, usedToolItems };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function validateUsageProjection(value) {
|
|
666
|
+
if (!exactKeys(value, ['status', 'inputTokens', 'cachedInputTokens', 'outputTokens']) || value.status !== 'observed' ||
|
|
667
|
+
!Number.isSafeInteger(value.inputTokens) || value.inputTokens < 0 ||
|
|
668
|
+
!Number.isSafeInteger(value.cachedInputTokens) || value.cachedInputTokens < 0 ||
|
|
669
|
+
!Number.isSafeInteger(value.outputTokens) || value.outputTokens < 0) throw new TypeError('Invalid exec usage');
|
|
670
|
+
return { ...value };
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Format the single public receipt for the normal-auth governed exec path.
|
|
675
|
+
* Raw prompts, event objects, stderr and environment values are intentionally
|
|
676
|
+
* not accepted by this boundary.
|
|
677
|
+
*/
|
|
678
|
+
export function formatGovernedCodexExecAttestation(input = {}) {
|
|
679
|
+
if (!ownObject(input)) throw new TypeError('Invalid exec attestation input');
|
|
680
|
+
const profile = validateGovernedCodexProfile(input.profile);
|
|
681
|
+
const requested = profileProjection(profile);
|
|
682
|
+
const required = ['profile', 'cliPath', 'cliSha256', 'cliVersion', 'configDigest', 'launchDigest', 'cwdDigest', 'loopbackMcpDigest', 'promptDigest', 'promptBytes', 'observed', 'usage'];
|
|
683
|
+
if (required.some(key => !Object.prototype.hasOwnProperty.call(input, key))) throw new TypeError('Invalid exec attestation input');
|
|
684
|
+
const cliPath = safeAbsoluteFile(input.cliPath, 'Codex executable');
|
|
685
|
+
const cliSha256 = normalizeCliDigest(input.cliSha256);
|
|
686
|
+
const actualCliSha256 = executableDigest(cliPath);
|
|
687
|
+
if (actualCliSha256 !== cliSha256) throw new TypeError('Codex executable SHA-256 mismatch');
|
|
688
|
+
const cliVersion = safeVersion(input.cliVersion);
|
|
689
|
+
const observed = validateObserved(input.observed, profile.profileId);
|
|
690
|
+
const usage = validateUsageProjection(input.usage);
|
|
691
|
+
if (!DIGEST.test(input.configDigest) || !DIGEST.test(input.launchDigest) || !RAW_DIGEST.test(input.cwdDigest) ||
|
|
692
|
+
!DIGEST.test(input.loopbackMcpDigest) || !DIGEST.test(input.promptDigest) ||
|
|
693
|
+
!Number.isSafeInteger(input.promptBytes) || input.promptBytes < 1 || input.promptBytes > MAX_TEXT_BYTES) throw new TypeError('Invalid exec attestation digest');
|
|
694
|
+
if (input.cwdDigest !== requested.cwdDigest) throw new TypeError('Invalid exec attestation cwd binding');
|
|
695
|
+
const enforced = {
|
|
696
|
+
source: 'probe-host-codex-exec-argv/v1', transport: GOVERNED_CODEX_EXEC_TRANSPORT,
|
|
697
|
+
cliPath, cliSha256, cliVersion, configDigest: input.configDigest, launchDigest: input.launchDigest,
|
|
698
|
+
cwdDigest: input.cwdDigest, codexHome: 'omitted', environmentPolicy: 'inherit-with-CODEX_HOME-omitted-v1',
|
|
699
|
+
ignoreUserConfig: true, ignoreRules: true, ephemeral: true, noShell: true,
|
|
700
|
+
loopbackMcpDigest: input.loopbackMcpDigest,
|
|
701
|
+
};
|
|
702
|
+
const receipt = {
|
|
703
|
+
version: GOVERNED_CODEX_EXEC_ATTESTATION_VERSION,
|
|
704
|
+
profileId: profile.profileId,
|
|
705
|
+
requested, enforced,
|
|
706
|
+
observed, ...(input.invocationDigest === undefined ? {} : {
|
|
707
|
+
executionContext: { source: 'caller', invocationDigest: requireDigest(input.invocationDigest, 'invocation digest') },
|
|
708
|
+
}),
|
|
709
|
+
dispatch: { source: 'probe-host-exec', tool: 'codex-exec', promptDigest: input.promptDigest, promptBytes: input.promptBytes },
|
|
710
|
+
evidence: { eventCount: observed.eventCount, completedItemCount: observed.completedItemCount,
|
|
711
|
+
agentMessageCount: observed.agentMessageCount, probeMcpCallCount: observed.probeMcpCallCount },
|
|
712
|
+
usage,
|
|
713
|
+
};
|
|
714
|
+
return freeze(receipt);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function validateGovernedCodexExecAttestation(input) {
|
|
718
|
+
const baseKeys = ['dispatch', 'enforced', 'evidence', 'observed', 'profileId', 'requested', 'usage', 'version'];
|
|
719
|
+
if (!ownObject(input) || !exactKeys(input, baseKeys) && !exactKeys(input, [...baseKeys, 'executionContext']) ||
|
|
720
|
+
input.version !== GOVERNED_CODEX_EXEC_ATTESTATION_VERSION ||
|
|
721
|
+
!['luna-xhigh-readonly-v1', 'luna-xhigh-readonly-native-exec-v1', 'luna-xhigh-isolated-writer-v1'].includes(input.profileId) ||
|
|
722
|
+
!ownObject(input.enforced) || !ownObject(input.requested) || !ownObject(input.observed) ||
|
|
723
|
+
!ownObject(input.dispatch) || !ownObject(input.evidence) || !ownObject(input.usage)) {
|
|
724
|
+
throw new TypeError('Invalid governed Codex exec attestation');
|
|
725
|
+
}
|
|
726
|
+
const requestedKeys = input.profileId === 'luna-xhigh-readonly-v1'
|
|
727
|
+
? ['approvalPolicy', 'cwdDigest', 'model', 'probeToolsDigest', 'profileDigest', 'reasoningEffort', 'sandbox']
|
|
728
|
+
: ['approvalPolicy', 'codexNativeTools', 'codexNativeToolsDigest', 'cwdDigest', 'model', 'probeMcpTools', 'probeMcpToolsDigest', 'profileDigest', 'reasoningEffort', 'sandbox'];
|
|
729
|
+
if (!exactKeys(input.requested, requestedKeys) || input.requested.model !== 'gpt-5.6-luna' || input.requested.reasoningEffort !== 'xhigh' ||
|
|
730
|
+
input.requested.approvalPolicy !== 'never' || !RAW_DIGEST.test(input.requested.profileDigest) || !RAW_DIGEST.test(input.requested.cwdDigest) ||
|
|
731
|
+
(input.profileId === 'luna-xhigh-readonly-v1'
|
|
732
|
+
? (!RAW_DIGEST.test(input.requested.probeToolsDigest) || input.requested.sandbox !== 'read-only')
|
|
733
|
+
: (!RAW_DIGEST.test(input.requested.probeMcpToolsDigest) || !RAW_DIGEST.test(input.requested.codexNativeToolsDigest) ||
|
|
734
|
+
JSON.stringify(input.requested.probeMcpTools) !== JSON.stringify(['search', 'extract', 'listFiles']) ||
|
|
735
|
+
JSON.stringify(input.requested.codexNativeTools) !== JSON.stringify(input.profileId === 'luna-xhigh-isolated-writer-v1' ? ['apply_patch', 'exec'] : ['exec']) ||
|
|
736
|
+
input.requested.sandbox !== (input.profileId === 'luna-xhigh-isolated-writer-v1' ? 'workspace-write' : 'read-only')))) throw new TypeError('Invalid governed Codex exec requested profile');
|
|
737
|
+
if (!exactKeys(input.enforced, ['cliPath', 'cliSha256', 'cliVersion', 'codexHome', 'configDigest', 'cwdDigest', 'environmentPolicy', 'ephemeral', 'ignoreRules', 'ignoreUserConfig', 'launchDigest', 'loopbackMcpDigest', 'noShell', 'source', 'transport']) ||
|
|
738
|
+
!DIGEST.test(input.enforced.cliSha256) || !DIGEST.test(input.enforced.configDigest) || !DIGEST.test(input.enforced.launchDigest) ||
|
|
739
|
+
!RAW_DIGEST.test(input.enforced.cwdDigest) || !DIGEST.test(input.enforced.loopbackMcpDigest) ||
|
|
740
|
+
input.enforced.transport !== GOVERNED_CODEX_EXEC_TRANSPORT || input.enforced.codexHome !== 'omitted' ||
|
|
741
|
+
input.enforced.environmentPolicy !== 'inherit-with-CODEX_HOME-omitted-v1' || input.enforced.source !== 'probe-host-codex-exec-argv/v1' ||
|
|
742
|
+
input.enforced.ignoreUserConfig !== true || input.enforced.ignoreRules !== true || input.enforced.ephemeral !== true || input.enforced.noShell !== true ||
|
|
743
|
+
!isAbsolute(input.enforced.cliPath) || !VERSION_TEXT.test(input.enforced.cliVersion) ||
|
|
744
|
+
input.dispatch.source !== 'probe-host-exec' || input.dispatch.tool !== 'codex-exec' || !exactKeys(input.dispatch, ['promptBytes', 'promptDigest', 'source', 'tool']) || !DIGEST.test(input.dispatch.promptDigest) ||
|
|
745
|
+
!Number.isSafeInteger(input.dispatch.promptBytes) || input.dispatch.promptBytes < 1 || input.dispatch.promptBytes > MAX_TEXT_BYTES || !exactKeys(input.evidence, ['agentMessageCount', 'completedItemCount', 'eventCount', 'probeMcpCallCount']) ||
|
|
746
|
+
input.evidence.eventCount !== input.observed.eventCount || input.evidence.completedItemCount !== input.observed.completedItemCount ||
|
|
747
|
+
input.evidence.agentMessageCount !== input.observed.agentMessageCount || input.evidence.probeMcpCallCount !== input.observed.probeMcpCallCount) throw new TypeError('Invalid governed Codex exec attestation');
|
|
748
|
+
if (executableDigest(safeAbsoluteFile(input.enforced.cliPath, 'Codex executable')) !== input.enforced.cliSha256) throw new TypeError('Codex executable SHA-256 mismatch');
|
|
749
|
+
if (input.enforced.cwdDigest !== input.requested.cwdDigest) throw new TypeError('Invalid governed Codex exec cwd binding');
|
|
750
|
+
validateObserved(input.observed, input.profileId);
|
|
751
|
+
validateUsageProjection(input.usage);
|
|
752
|
+
if (input.executionContext !== undefined && (!exactKeys(input.executionContext, ['invocationDigest', 'source']) || input.executionContext.source !== 'caller' || !DIGEST.test(input.executionContext.invocationDigest))) throw new TypeError('Invalid governed Codex exec execution context');
|
|
753
|
+
return freeze(input);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
export const buildGovernedCodexExecAttestation = formatGovernedCodexExecAttestation;
|
|
757
|
+
|
|
758
|
+
function effectiveExecDispatch(prompt, instructionsDigest = null, instructionsBytes = 0) {
|
|
759
|
+
const userDispatch = governedCodexDispatch(validatePrompt(prompt));
|
|
760
|
+
if (instructionsDigest !== null) requireDigest(instructionsDigest, 'model instructions digest');
|
|
761
|
+
if (!Number.isSafeInteger(instructionsBytes) || instructionsBytes < 0 || instructionsBytes > MAX_TEXT_BYTES) {
|
|
762
|
+
throw new TypeError('Invalid model instructions byte count');
|
|
763
|
+
}
|
|
764
|
+
// The public byte count is the documented total of private instruction-file
|
|
765
|
+
// bytes and exact user-prompt bytes; instruction text never crosses this boundary.
|
|
766
|
+
const promptBytes = instructionsBytes + userDispatch.promptBytes;
|
|
767
|
+
if (promptBytes < 1 || promptBytes > MAX_EFFECTIVE_INPUT_BYTES) throw new TypeError('Governed exec input exceeds byte bound');
|
|
768
|
+
return Object.freeze({
|
|
769
|
+
source: 'probe-host-exec', tool: 'codex-exec',
|
|
770
|
+
promptDigest: sha256({ version: 'probe.governed-codex-exec-input/v1',
|
|
771
|
+
instructions: { digest: instructionsDigest, bytes: instructionsBytes },
|
|
772
|
+
user: { digest: userDispatch.promptDigest, bytes: userDispatch.promptBytes } }),
|
|
773
|
+
promptBytes,
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
export function previewGovernedCodexExecDispatch(prompt, systemPrompt = '') {
|
|
778
|
+
if (typeof systemPrompt !== 'string' || systemPrompt.includes('\0') || Buffer.byteLength(systemPrompt, 'utf8') > MAX_TEXT_BYTES) {
|
|
779
|
+
throw new TypeError('Invalid model instructions');
|
|
780
|
+
}
|
|
781
|
+
return effectiveExecDispatch(prompt, systemPrompt.length === 0 ? null : sha256(systemPrompt), Buffer.byteLength(systemPrompt, 'utf8'));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function validateThreadStarted(event, state) {
|
|
785
|
+
if (!exactKeys(event, ['thread_id', 'type']) || event.type !== 'thread.started' ||
|
|
786
|
+
typeof event.thread_id !== 'string' || !SAFE_ID.test(event.thread_id) || state.threadId !== null) {
|
|
787
|
+
throw fail('EVENT_ORDER');
|
|
788
|
+
}
|
|
789
|
+
state.threadId = event.thread_id;
|
|
790
|
+
state.phase = 'thread';
|
|
791
|
+
state.streamRecords.push(Object.freeze({ event: 'thread.started', threadDigest: digestText(event.thread_id) }));
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function validateTurnStarted(event, state) {
|
|
795
|
+
if (!exactKeys(event, ['type']) || event.type !== 'turn.started' || state.phase !== 'thread') {
|
|
796
|
+
throw fail('EVENT_ORDER');
|
|
797
|
+
}
|
|
798
|
+
state.turnStarted = true;
|
|
799
|
+
state.phase = 'turn';
|
|
800
|
+
state.streamRecords.push(Object.freeze({ event: 'turn.started' }));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function itemCategoryAllowed(type, profile) {
|
|
804
|
+
if (PUBLIC_ITEM_TYPES.has(type)) return true;
|
|
805
|
+
if (type === 'command_execution') return profile.version !== 'probe.governed-codex-profile/v1';
|
|
806
|
+
if (type === 'file_change') return profile.version === 'probe.governed-codex-profile/v3';
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function mcpNameAllowed(name, profile) {
|
|
811
|
+
const tools = profile.probeTools ?? profile.probeMcpTools ?? [];
|
|
812
|
+
return typeof name === 'string' && tools.some(tool => name === `mcp__probe__${tool}`);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function lifecycleStatusAllowed(itemType, eventType, status, splitMcp = false) {
|
|
816
|
+
if (splitMcp) {
|
|
817
|
+
if (eventType === 'item.started') return status === 'in_progress';
|
|
818
|
+
return status === 'completed' || (status === 'failed' && itemType === 'mcp_tool_call');
|
|
819
|
+
}
|
|
820
|
+
if (status === undefined) return true;
|
|
821
|
+
if (eventType === 'item.started') return status === 'in_progress' || status === 'completed';
|
|
822
|
+
return status === 'completed' || (status === 'failed' &&
|
|
823
|
+
(itemType === 'command_execution' || itemType === 'mcp_tool_call'));
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function validateItem(event, state, profile) {
|
|
827
|
+
if (!exactKeys(event, ['item', 'type']) || !['item.started', 'item.completed'].includes(event.type) ||
|
|
828
|
+
!ownObject(event.item) || typeof event.item.type !== 'string' || !itemCategoryAllowed(event.item.type, profile) ||
|
|
829
|
+
state.phase !== 'turn') throw fail('EVENT_CATEGORY');
|
|
830
|
+
const item = event.item;
|
|
831
|
+
const mcpItem = item.type === 'mcp_tool_call';
|
|
832
|
+
const mcpHasName = mcpItem && Object.prototype.hasOwnProperty.call(item, 'name');
|
|
833
|
+
const mcpHasTool = mcpItem && Object.prototype.hasOwnProperty.call(item, 'tool');
|
|
834
|
+
const mcpHasError = mcpItem && Object.prototype.hasOwnProperty.call(item, 'error');
|
|
835
|
+
const mcpSplitVariant = mcpItem && (mcpHasTool || mcpHasError);
|
|
836
|
+
let allowedKeys = item.type === 'agent_message' ? ['id', 'phase', 'text', 'type']
|
|
837
|
+
: item.type === 'reasoning' ? ['id', 'summary', 'text', 'type']
|
|
838
|
+
: mcpItem ? (mcpSplitVariant ? ['arguments', 'error', 'id', 'result', 'server', 'status', 'tool', 'type']
|
|
839
|
+
: ['arguments', 'id', 'name', 'result', 'server', 'status', 'type'])
|
|
840
|
+
: item.type === 'command_execution' ? ['aggregated_output', 'command', 'exit_code', 'id', 'status', 'type']
|
|
841
|
+
: ['changes', 'id', 'status', 'type'];
|
|
842
|
+
if (mcpSplitVariant && (!mcpHasTool || !mcpHasError || mcpHasName)) {
|
|
843
|
+
throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_keys'));
|
|
844
|
+
}
|
|
845
|
+
if (Object.keys(item).some(key => !allowedKeys.includes(key))) throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_keys'));
|
|
846
|
+
if (item.id !== undefined && (typeof item.id !== 'string' || !SAFE_ID.test(item.id))) throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_id'));
|
|
847
|
+
if ((item.type === 'agent_message' || item.type === 'reasoning') && item.text !== undefined &&
|
|
848
|
+
(typeof item.text !== 'string' || Buffer.byteLength(item.text, 'utf8') > MAX_TEXT_BYTES)) throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_text'));
|
|
849
|
+
if (item.phase !== undefined && (typeof item.phase !== 'string' || !/^[A-Za-z0-9._:-]{1,64}$/.test(item.phase))) throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_phase'));
|
|
850
|
+
if (item.summary !== undefined && (!Array.isArray(item.summary) || item.summary.length > 32)) throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_summary'));
|
|
851
|
+
if (item.server !== undefined && (typeof item.server !== 'string' || !/^[A-Za-z0-9._:-]{1,128}$/.test(item.server))) throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_server'));
|
|
852
|
+
if (item.command !== undefined && (typeof item.command !== 'string' || Buffer.byteLength(item.command, 'utf8') > MAX_TEXT_BYTES)) throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_command'));
|
|
853
|
+
// Command transcripts are disposable framing data, not retained semantic
|
|
854
|
+
// text; JSONL line/stdout/event caps bound them before this parser.
|
|
855
|
+
if (item.aggregated_output !== undefined && typeof item.aggregated_output !== 'string') throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_aggregated_output'));
|
|
856
|
+
if (item.exit_code !== undefined && item.exit_code !== null && (!Number.isSafeInteger(item.exit_code) || item.exit_code < -255 || item.exit_code > 255)) throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_exit_code'));
|
|
857
|
+
if (item.changes !== undefined && (!Array.isArray(item.changes) || item.changes.length > 128)) throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_changes'));
|
|
858
|
+
const isTool = item.type === 'mcp_tool_call' || NATIVE_ITEM_TYPES.has(item.type);
|
|
859
|
+
if (isTool && (typeof item.id !== 'string' || !SAFE_ID.test(item.id))) throw fail('ITEM', undefined, rejectedItemEvent(event, 'tool_id'));
|
|
860
|
+
const toolName = mcpItem ? (mcpSplitVariant ? item.tool : item.name) : undefined;
|
|
861
|
+
if (mcpSplitVariant && item.error !== null) {
|
|
862
|
+
throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_error'));
|
|
863
|
+
}
|
|
864
|
+
if (mcpSplitVariant && event.type === 'item.started' &&
|
|
865
|
+
(!ownObject(item.arguments) || item.result !== null)) {
|
|
866
|
+
throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_started_payload'));
|
|
867
|
+
}
|
|
868
|
+
if (mcpItem && (typeof item.server !== 'string' || item.server !== state.mcpName)) throw fail('MCP');
|
|
869
|
+
let previous;
|
|
870
|
+
if (item.id !== undefined) {
|
|
871
|
+
previous = state.itemStates.get(item.id);
|
|
872
|
+
if (event.type === 'item.started' && previous !== undefined) throw fail('DUPLICATE');
|
|
873
|
+
if (event.type === 'item.completed' && (previous?.status === 'completed' || (previous && previous.type !== item.type))) throw fail('DUPLICATE');
|
|
874
|
+
if (event.type === 'item.completed' && isTool && previous === undefined) throw fail('ITEM_ORDER');
|
|
875
|
+
if (event.type === 'item.completed' && mcpSplitVariant &&
|
|
876
|
+
(previous?.mcpVariant !== 'split' || previous.server !== item.server || previous.tool !== toolName)) throw fail('ITEM_ORDER');
|
|
877
|
+
if (event.type === 'item.completed' && mcpItem && previous?.mcpVariant !== (mcpSplitVariant ? 'split' : 'combined')) throw fail('ITEM_ORDER');
|
|
878
|
+
state.itemStates.set(item.id, {
|
|
879
|
+
status: event.type === 'item.completed' ? 'completed' : 'started', type: item.type,
|
|
880
|
+
...(mcpItem ? { mcpVariant: mcpSplitVariant ? 'split' : 'combined', server: item.server, tool: toolName } : {}),
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
if (isTool) {
|
|
884
|
+
if (item.type === 'mcp_tool_call') {
|
|
885
|
+
if (!mcpNameAllowed(toolName, profile)) throw fail('TOOL_POLICY');
|
|
886
|
+
} else if (item.name !== undefined && item.name !== null) throw fail('TOOL_POLICY');
|
|
887
|
+
if (!lifecycleStatusAllowed(item.type, event.type, item.status)) {
|
|
888
|
+
throw fail('ITEM', undefined, rejectedItemEvent(event, 'item_status'));
|
|
889
|
+
}
|
|
890
|
+
if (mcpSplitVariant && !lifecycleStatusAllowed(item.type, event.type, item.status, true)) throw fail('ITEM_STATUS');
|
|
891
|
+
}
|
|
892
|
+
if (event.type === 'item.completed') {
|
|
893
|
+
if (!lifecycleStatusAllowed(item.type, event.type, item.status)) throw fail('ITEM_STATUS');
|
|
894
|
+
state.completedItemCount++;
|
|
895
|
+
if (item.type === 'agent_message') {
|
|
896
|
+
if (typeof item.text !== 'string') throw fail('ANSWER_CARDINALITY');
|
|
897
|
+
state.answer = item.text;
|
|
898
|
+
state.agentMessageCount++;
|
|
899
|
+
}
|
|
900
|
+
if (isTool) state.completedTools.push({
|
|
901
|
+
category: item.type, name: item.type === 'mcp_tool_call' ? toolName : null,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
const canonicalName = item.type === 'mcp_tool_call' ? toolName : item.name;
|
|
905
|
+
state.itemEvents++;
|
|
906
|
+
state.streamRecords.push(Object.freeze({
|
|
907
|
+
event: event.type, category: item.type,
|
|
908
|
+
...(item.id === undefined ? {} : { id: item.id }),
|
|
909
|
+
...(canonicalName === undefined ? {} : { name: canonicalName }),
|
|
910
|
+
...(item.status === undefined ? {} : { status: item.status }),
|
|
911
|
+
...(typeof item.text === 'string' ? { textDigest: digestText(item.text), textBytes: Buffer.byteLength(item.text, 'utf8') } : {}),
|
|
912
|
+
}));
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function validateUsage(value) {
|
|
916
|
+
if (!ownObject(value) || Object.keys(value).some(key => !USAGE_KEYS.has(key)) ||
|
|
917
|
+
!Object.prototype.hasOwnProperty.call(value, 'input_tokens') ||
|
|
918
|
+
!Object.prototype.hasOwnProperty.call(value, 'output_tokens')) throw fail('USAGE');
|
|
919
|
+
const usage = {};
|
|
920
|
+
for (const key of Object.keys(value).sort()) {
|
|
921
|
+
const count = value[key];
|
|
922
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > Number.MAX_SAFE_INTEGER) throw fail('USAGE');
|
|
923
|
+
usage[key] = count;
|
|
924
|
+
}
|
|
925
|
+
return Object.freeze(usage);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function validateTurnCompleted(event, state) {
|
|
929
|
+
if (!exactKeys(event, ['type', 'usage']) || event.type !== 'turn.completed' || state.phase !== 'turn' ||
|
|
930
|
+
state.answer === null || state.turnCompleted) throw fail('EVENT_ORDER');
|
|
931
|
+
for (const item of state.itemStates.values()) {
|
|
932
|
+
if (item.status === 'started' && (item.type === 'mcp_tool_call' || NATIVE_ITEM_TYPES.has(item.type))) throw fail('INCOMPLETE_ITEM');
|
|
933
|
+
}
|
|
934
|
+
state.usage = validateUsage(event.usage);
|
|
935
|
+
state.streamRecords.push(Object.freeze({ event: 'turn.completed', usage: state.usage }));
|
|
936
|
+
state.turnCompleted = true;
|
|
937
|
+
state.phase = 'completed';
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function consumeEvent(event, state, profile) {
|
|
941
|
+
if (!ownObject(event) || typeof event.type !== 'string') throw fail('EVENT');
|
|
942
|
+
if (state.turnCompleted) throw fail('EVENT_ORDER');
|
|
943
|
+
state.events++;
|
|
944
|
+
if (state.phase === 'initial') return validateThreadStarted(event, state);
|
|
945
|
+
if (state.phase === 'thread') return validateTurnStarted(event, state);
|
|
946
|
+
if (GOVERNED_CODEX_EXEC_FAILURE_EVENT_TYPES.has(event.type)) {
|
|
947
|
+
throw fail('EVENT_CATEGORY', undefined, rejectedFailureEvent(event));
|
|
948
|
+
}
|
|
949
|
+
if (event.type === 'item.started' || event.type === 'item.completed') return validateItem(event, state, profile);
|
|
950
|
+
if (event.type === 'turn.completed') return validateTurnCompleted(event, state);
|
|
951
|
+
throw fail('EVENT_CATEGORY');
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function parserState() {
|
|
955
|
+
return {
|
|
956
|
+
phase: 'initial', threadId: null, turnStarted: false, turnCompleted: false,
|
|
957
|
+
answer: null, usage: null, events: 0, itemEvents: 0, completedItemCount: 0,
|
|
958
|
+
agentMessageCount: 0, completedTools: [], streamRecords: [], itemStates: new Map(), mcpName: null,
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function aggregateTools(state) {
|
|
963
|
+
const counts = new Map();
|
|
964
|
+
for (const item of state.completedTools) {
|
|
965
|
+
const key = `${item.category}\u0000${item.name ?? ''}`;
|
|
966
|
+
const current = counts.get(key)?.count ?? 0;
|
|
967
|
+
const count = nextBoundedCount(current, 1, state.completedItemCount, () => fail('MCP_EVIDENCE'));
|
|
968
|
+
counts.set(key, { ...item, status: 'completed', count });
|
|
969
|
+
}
|
|
970
|
+
return [...counts.values()].sort((a, b) => `${a.category}:${a.name ?? ''}`.localeCompare(`${b.category}:${b.name ?? ''}`));
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function makeInternalResult(state, processReceipt, launch, invocationDigest, mcpEvidence) {
|
|
974
|
+
if (state.threadId === null || !state.turnStarted || !state.turnCompleted || state.answer === null || !state.usage) {
|
|
975
|
+
throw fail('INCOMPLETE');
|
|
976
|
+
}
|
|
977
|
+
if (processReceipt.classification !== 'exited' || processReceipt.exitCode !== 0 || processReceipt.signal !== null ||
|
|
978
|
+
!processReceipt.barriers.stdoutEOF || !processReceipt.barriers.stderrEOF || !processReceipt.barriers.close) {
|
|
979
|
+
throw fail(processReceipt.classification === 'execution_timeout' ? 'TIMEOUT' : 'EXIT', processReceipt.stderr);
|
|
980
|
+
}
|
|
981
|
+
const usedToolItems = aggregateTools(state);
|
|
982
|
+
const probeMcpCallCount = mcpEvidence.closed;
|
|
983
|
+
if (!Number.isSafeInteger(probeMcpCallCount) || probeMcpCallCount < 0 || probeMcpCallCount > state.completedItemCount) {
|
|
984
|
+
throw fail('MCP_EVIDENCE');
|
|
985
|
+
}
|
|
986
|
+
const mcpToolCount = boundedCountSum(usedToolItems.filter(item => item.category === 'mcp_tool_call'), state.completedItemCount, () => fail('MCP_EVIDENCE'));
|
|
987
|
+
if (probeMcpCallCount !== mcpToolCount) {
|
|
988
|
+
throw fail('MCP_EVIDENCE');
|
|
989
|
+
}
|
|
990
|
+
const observed = {
|
|
991
|
+
source: 'codex-exec-jsonl/v1', threadDigest: sha256({ version: 'probe.governed-codex-thread/v1', threadId: state.threadId }),
|
|
992
|
+
streamDigest: sha256({ version: 'probe.governed-codex-stream/v1', events: state.streamRecords }), terminal: 'turn.completed',
|
|
993
|
+
eventCount: state.events, completedItemCount: state.completedItemCount, agentMessageCount: state.agentMessageCount,
|
|
994
|
+
usedToolItems, probeMcpCallCount, finalDigest: digestText(state.answer), finalBytes: Buffer.byteLength(state.answer, 'utf8'),
|
|
995
|
+
processExitCode: 0, processSignal: null,
|
|
996
|
+
};
|
|
997
|
+
const attestation = formatGovernedCodexExecAttestation({
|
|
998
|
+
profile: launch.profile, cliPath: launch.command, cliSha256: launch.cliSha256, cliVersion: launch.cliVersion,
|
|
999
|
+
configDigest: launch.configDigest, launchDigest: launch.launchDigest, cwdDigest: launch.requested.cwdDigest, loopbackMcpDigest: launch.loopbackMcpDigest,
|
|
1000
|
+
promptDigest: launch.promptDigest, promptBytes: launch.promptBytes, observed,
|
|
1001
|
+
usage: { status: 'observed', inputTokens: state.usage.input_tokens, cachedInputTokens: state.usage.cached_input_tokens ?? 0, outputTokens: state.usage.output_tokens },
|
|
1002
|
+
invocationDigest,
|
|
1003
|
+
});
|
|
1004
|
+
return Object.freeze({
|
|
1005
|
+
version: GOVERNED_CODEX_EXEC_PROTOCOL,
|
|
1006
|
+
answer: state.answer,
|
|
1007
|
+
threadId: state.threadId,
|
|
1008
|
+
usage: state.usage,
|
|
1009
|
+
attestation,
|
|
1010
|
+
evidence: Object.freeze({ eventCount: state.events, completedItemCount: state.completedItemCount,
|
|
1011
|
+
agentMessageCount: state.agentMessageCount, usedToolItems: Object.freeze(usedToolItems.map(item => Object.freeze(item))), probeMcpCallCount }),
|
|
1012
|
+
process: Object.freeze({ classification: processReceipt.classification, exitCode: processReceipt.exitCode, signal: processReceipt.signal }),
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function processFailure(receipt) {
|
|
1017
|
+
if (receipt.classification === 'execution_timeout') return fail('TIMEOUT', receipt.stderr);
|
|
1018
|
+
if (receipt.classification === 'aborted') return fail('CANCELLED', receipt.stderr);
|
|
1019
|
+
if (receipt.classification === 'output_overflow') return fail('OUTPUT_OVERFLOW', receipt.stderr);
|
|
1020
|
+
if (receipt.classification === 'spawn_error') return fail('SPAWN', receipt.stderr);
|
|
1021
|
+
if (receipt.classification === 'cleanup_timeout') return fail('CLEANUP', receipt.stderr);
|
|
1022
|
+
if (receipt.signal) return fail('EXIT', receipt.stderr);
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Create a governed single-query exec engine. `run()` may be called once;
|
|
1028
|
+
* `close()` is idempotent and must be used by callers that do not run it.
|
|
1029
|
+
*/
|
|
1030
|
+
export async function createGovernedCodexExecEngine(options = {}) {
|
|
1031
|
+
if (!ownObject(options)) throw new TypeError('Invalid governed Codex exec options');
|
|
1032
|
+
if (!options.agent || typeof options.agent !== 'object') throw new TypeError('Invalid governed Codex exec agent');
|
|
1033
|
+
const profile = validateGovernedCodexProfile(options.profile);
|
|
1034
|
+
const prompt = options.prompt === undefined ? null : validatePrompt(options.prompt);
|
|
1035
|
+
const systemPrompt = options.systemPrompt === undefined ? '' : validatePrompt(options.systemPrompt);
|
|
1036
|
+
const configuredOutputSchema = Object.prototype.hasOwnProperty.call(options, 'schema')
|
|
1037
|
+
? validateOutputSchema(options.schema) : undefined;
|
|
1038
|
+
const codexPath = safeAbsoluteFile(options.codexPath, 'Codex executable');
|
|
1039
|
+
const codexSha256 = normalizeCliDigest(options.codexSha256);
|
|
1040
|
+
const timeoutMs = validateTimeout(options.timeoutMs);
|
|
1041
|
+
const executionTimeoutMs = validateTimeout(options.executionTimeoutMs ?? timeoutMs);
|
|
1042
|
+
validateSignal(options.signal);
|
|
1043
|
+
const actualSha = executableDigest(codexPath);
|
|
1044
|
+
if (actualSha !== codexSha256) throw new TypeError('Codex executable SHA-256 mismatch');
|
|
1045
|
+
const cliVersion = await verifyCodexVersion(codexPath, profile.cwd, options.signal, timeoutMs);
|
|
1046
|
+
|
|
1047
|
+
const sessionId = randomBytes(8).toString('hex');
|
|
1048
|
+
const mcpServer = new BuiltInMCPServer(options.agent, {
|
|
1049
|
+
port: 0, host: '127.0.0.1', debug: false,
|
|
1050
|
+
// Exec transport receipts count Probe MCP calls for every governed profile;
|
|
1051
|
+
// the server option only enables bounded call accounting and does not widen
|
|
1052
|
+
// the set of tools exposed to Codex.
|
|
1053
|
+
governedProfileVersion: 'probe.governed-codex-profile/v2',
|
|
1054
|
+
});
|
|
1055
|
+
let processHandle = null;
|
|
1056
|
+
let runPromise = null;
|
|
1057
|
+
let closePromise = null;
|
|
1058
|
+
let processReceipt = null;
|
|
1059
|
+
let launch = null;
|
|
1060
|
+
let primaryError = null;
|
|
1061
|
+
let state = parserState();
|
|
1062
|
+
let instructionDirectory = null;
|
|
1063
|
+
let instructionPath = null;
|
|
1064
|
+
let instructionDigest = null;
|
|
1065
|
+
let outputSchemaDirectory = null;
|
|
1066
|
+
let outputSchemaPath = null;
|
|
1067
|
+
|
|
1068
|
+
try {
|
|
1069
|
+
if (systemPrompt) {
|
|
1070
|
+
instructionDirectory = mkdtempSync(join(tmpdir(), 'probe-governed-exec-instructions-'));
|
|
1071
|
+
instructionPath = join(instructionDirectory, 'model-instructions.txt');
|
|
1072
|
+
writeFileSync(instructionPath, systemPrompt, { mode: 0o600, flag: 'wx' });
|
|
1073
|
+
instructionDigest = sha256(systemPrompt);
|
|
1074
|
+
}
|
|
1075
|
+
await mcpServer.start();
|
|
1076
|
+
const binding = mcpServer.getConfig();
|
|
1077
|
+
const run = (runPrompt = prompt, runOptions = {}) => {
|
|
1078
|
+
if (runPromise) return Promise.reject(fail('ONE_QUERY'));
|
|
1079
|
+
const activePrompt = validatePrompt(runPrompt);
|
|
1080
|
+
const activeSignal = runOptions.abortSignal ?? options.signal;
|
|
1081
|
+
validateSignal(activeSignal);
|
|
1082
|
+
const outputSchema = Object.prototype.hasOwnProperty.call(runOptions, 'schema')
|
|
1083
|
+
? validateOutputSchema(runOptions.schema) : configuredOutputSchema;
|
|
1084
|
+
if (outputSchema !== undefined) {
|
|
1085
|
+
outputSchemaDirectory = instructionDirectory ?? mkdtempSync(join(tmpdir(), 'probe-governed-exec-artifacts-'));
|
|
1086
|
+
outputSchemaPath = join(outputSchemaDirectory, 'output-schema.json');
|
|
1087
|
+
writeFileSync(outputSchemaPath, outputSchema, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
1088
|
+
}
|
|
1089
|
+
launch = buildGovernedCodexExecLaunch({
|
|
1090
|
+
codexPath, codexSha256, cliVersion, profile, prompt: activePrompt,
|
|
1091
|
+
mcp: { name: `probe_${sessionId}`, url: binding.url },
|
|
1092
|
+
...(instructionPath ? { modelInstructionsPath: instructionPath, modelInstructionsDigest: instructionDigest } : {}),
|
|
1093
|
+
...(outputSchemaPath ? { outputSchemaPath, outputSchemaDigest: sha256(outputSchema) } : {}),
|
|
1094
|
+
});
|
|
1095
|
+
state.mcpName = launch.mcpName;
|
|
1096
|
+
runPromise = (async () => {
|
|
1097
|
+
let child;
|
|
1098
|
+
try {
|
|
1099
|
+
child = spawn(launch.command, launch.args, {
|
|
1100
|
+
cwd: launch.cwd,
|
|
1101
|
+
env: cloneEnvironment(),
|
|
1102
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1103
|
+
shell: false,
|
|
1104
|
+
detached: true,
|
|
1105
|
+
windowsHide: true,
|
|
1106
|
+
});
|
|
1107
|
+
} catch {
|
|
1108
|
+
throw fail('SPAWN');
|
|
1109
|
+
}
|
|
1110
|
+
processHandle = governSpawnedProcess(child, {
|
|
1111
|
+
captureStdout: false,
|
|
1112
|
+
stdoutByteCap: STDOUT_BYTE_CAP,
|
|
1113
|
+
stderrByteCap: STDERR_BYTE_CAP,
|
|
1114
|
+
signalScope: 'process-group',
|
|
1115
|
+
executionTimeoutMs,
|
|
1116
|
+
signal: activeSignal,
|
|
1117
|
+
});
|
|
1118
|
+
const decoder = new StringDecoder('utf8');
|
|
1119
|
+
let pending = '';
|
|
1120
|
+
let stdoutBytes = 0;
|
|
1121
|
+
const consumeLine = line => {
|
|
1122
|
+
if (line.endsWith('\r')) line = line.slice(0, -1);
|
|
1123
|
+
if (line.length === 0) throw fail('JSONL');
|
|
1124
|
+
if (Buffer.byteLength(line, 'utf8') > JSONL_LINE_BYTE_CAP) throw fail('OUTPUT_OVERFLOW');
|
|
1125
|
+
let event;
|
|
1126
|
+
try { event = JSON.parse(line); } catch { throw fail('JSONL'); }
|
|
1127
|
+
consumeEvent(event, state, profile);
|
|
1128
|
+
};
|
|
1129
|
+
child.stdout.on('data', chunk => {
|
|
1130
|
+
if (primaryError) return;
|
|
1131
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(String(chunk), 'utf8');
|
|
1132
|
+
stdoutBytes += bytes;
|
|
1133
|
+
if (stdoutBytes > STDOUT_BYTE_CAP) {
|
|
1134
|
+
primaryError = fail('OUTPUT_OVERFLOW');
|
|
1135
|
+
void processHandle.terminate('stdout_overflow');
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
pending += decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1140
|
+
let newline;
|
|
1141
|
+
while ((newline = pending.indexOf('\n')) >= 0) {
|
|
1142
|
+
const line = pending.slice(0, newline);
|
|
1143
|
+
pending = pending.slice(newline + 1);
|
|
1144
|
+
consumeLine(line);
|
|
1145
|
+
}
|
|
1146
|
+
if (Buffer.byteLength(pending, 'utf8') > JSONL_LINE_BYTE_CAP) throw fail('OUTPUT_OVERFLOW');
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
primaryError = error instanceof Error && error.code?.startsWith('GOVERNED_CODEX_EXEC_') ? error : fail('JSONL');
|
|
1149
|
+
void processHandle.terminate(primaryError.code);
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
child.stdout.once('end', () => {
|
|
1153
|
+
if (primaryError) return;
|
|
1154
|
+
try {
|
|
1155
|
+
pending += decoder.end();
|
|
1156
|
+
if (pending.length > 0) consumeLine(pending);
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
primaryError = error instanceof Error && error.code?.startsWith('GOVERNED_CODEX_EXEC_') ? error : fail('JSONL');
|
|
1159
|
+
void processHandle.terminate(primaryError.code);
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
processReceipt = await processHandle.result;
|
|
1163
|
+
if (primaryError) {
|
|
1164
|
+
if (processReceipt.stderr) {
|
|
1165
|
+
const stderr = summarizeStderr(processReceipt.stderr);
|
|
1166
|
+
if (stderr && !primaryError.diagnostic) Object.defineProperty(primaryError, 'diagnostic', { value: stderr, enumerable: true });
|
|
1167
|
+
}
|
|
1168
|
+
throw primaryError;
|
|
1169
|
+
}
|
|
1170
|
+
const receiptFailure = processFailure(processReceipt);
|
|
1171
|
+
if (receiptFailure) throw receiptFailure;
|
|
1172
|
+
const evidence = mcpServer.getGovernedCallEvidence();
|
|
1173
|
+
return makeInternalResult(state, processReceipt, launch, runOptions.invocationDigest ?? options.invocationDigest, evidence);
|
|
1174
|
+
})();
|
|
1175
|
+
return runPromise;
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
const close = () => {
|
|
1179
|
+
if (closePromise) return closePromise;
|
|
1180
|
+
closePromise = (async () => {
|
|
1181
|
+
if (processHandle && !processReceipt) {
|
|
1182
|
+
try { processReceipt = await processHandle.terminate('closed'); } catch { if (!primaryError) primaryError = fail('CLEANUP'); }
|
|
1183
|
+
}
|
|
1184
|
+
try { await mcpServer.stop(); } catch { if (!primaryError) primaryError = fail('CLEANUP'); }
|
|
1185
|
+
if (instructionDirectory) { try { rmSync(instructionDirectory, { recursive: true, force: true }); } catch { if (!primaryError) primaryError = fail('CLEANUP'); } }
|
|
1186
|
+
if (outputSchemaDirectory && outputSchemaDirectory !== instructionDirectory) {
|
|
1187
|
+
try { rmSync(outputSchemaDirectory, { recursive: true, force: true }); } catch { if (!primaryError) primaryError = fail('CLEANUP'); }
|
|
1188
|
+
}
|
|
1189
|
+
if (primaryError && !runPromise) throw primaryError;
|
|
1190
|
+
})();
|
|
1191
|
+
return closePromise;
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
const query = async function* query(queryPrompt, queryOptions = {}) {
|
|
1195
|
+
const requestedPrompt = validatePrompt(queryPrompt);
|
|
1196
|
+
const result = await run(requestedPrompt, queryOptions);
|
|
1197
|
+
yield { type: 'text', content: result.answer };
|
|
1198
|
+
yield { type: 'metadata', data: { attestation: result.attestation, candidateBoundary: {
|
|
1199
|
+
selectedOrigin: result.attestation.observed.finalBytes > 0 ? 'raw_final' : 'none',
|
|
1200
|
+
selectedChunkCount: result.attestation.observed.finalBytes > 0 ? 1 : 0,
|
|
1201
|
+
selectedBytes: result.attestation.observed.finalBytes,
|
|
1202
|
+
resultTextItemCount: 0, resultTextBytes: 0,
|
|
1203
|
+
rawFinalMessageCount: result.attestation.observed.agentMessageCount,
|
|
1204
|
+
rawFinalPartCount: result.attestation.observed.agentMessageCount,
|
|
1205
|
+
rawFinalBytes: result.attestation.observed.finalBytes,
|
|
1206
|
+
} } };
|
|
1207
|
+
};
|
|
1208
|
+
return Object.freeze({ run, query, close, get launch() {
|
|
1209
|
+
return launch && Object.freeze({ ...launch, args: Object.freeze([...launch.args]) });
|
|
1210
|
+
} });
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
try { await mcpServer.stop(); } catch { /* preserve the setup failure */ }
|
|
1213
|
+
if (instructionDirectory) { try { rmSync(instructionDirectory, { recursive: true, force: true }); } catch { /* preserve setup failure */ } }
|
|
1214
|
+
if (outputSchemaDirectory && outputSchemaDirectory !== instructionDirectory) { try { rmSync(outputSchemaDirectory, { recursive: true, force: true }); } catch { /* preserve setup failure */ } }
|
|
1215
|
+
if (error instanceof TypeError || error?.code?.startsWith('GOVERNED_CODEX_EXEC_')) throw error;
|
|
1216
|
+
throw fail('SETUP');
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
export async function runGovernedCodexExec(options = {}) {
|
|
1221
|
+
const engine = await createGovernedCodexExecEngine(options);
|
|
1222
|
+
try {
|
|
1223
|
+
return await engine.run();
|
|
1224
|
+
} finally {
|
|
1225
|
+
await engine.close();
|
|
1226
|
+
}
|
|
1227
|
+
}
|