@brunobracaioli/aibrain 0.2.0-beta.1 → 0.3.0-beta.13
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/README.md +67 -4
- package/dist/assets/claude/.claude-plugin/plugin.json +49 -1
- package/dist/assets/claude/hooks/context-compaction.ts +6062 -734
- package/dist/assets/claude/hooks/provider-window.js +263 -0
- package/dist/assets/claude/hooks/state-fitting.d.mts +47 -0
- package/dist/assets/claude/hooks/state-fitting.mjs +324 -0
- package/dist/assets/codex-context-compaction.mjs +2690 -266
- package/dist/assets/context-selection-fitting.mjs +194 -0
- package/dist/assets/context-state-fitting.mjs +324 -0
- package/dist/assets/context-telemetry-reporter.mjs +305 -0
- package/dist/assets/jev-paid-pilot-terminal.mjs +591 -125
- package/dist/claude-checkpoint.mjs +1338 -0
- package/dist/claude-observation.mjs +1336 -0
- package/dist/cli.mjs +250 -20
- package/dist/codex-compatibility.mjs +467 -0
- package/dist/codex-diagnostics.mjs +426 -0
- package/dist/codex-permissions.mjs +590 -0
- package/dist/codex-protocol-fingerprint.json +131 -0
- package/dist/context-telemetry.mjs +257 -0
- package/dist/launch.mjs +341 -22
- package/dist/mcp.mjs +329 -7
- package/dist/monitor.mjs +11 -0
- package/dist/package.json +1 -1
- package/dist/platform.mjs +33 -3
- package/dist/state.mjs +107 -4
- package/dist/telemetry-report.mjs +133 -0
- package/package.json +1 -1
package/dist/launch.mjs
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import { access, chmod, lstat, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { access, chmod, lstat, mkdir, mkdtemp, open, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { constants } from 'node:fs';
|
|
6
6
|
import { isAbsolute, join, resolve, sep } from 'node:path';
|
|
7
7
|
import { assertTestedNativeVersion, nativeVersion, resolveNative } from './platform.mjs';
|
|
8
|
+
import { codexCompatibilityError, resolveCodexCompatibility } from './codex-compatibility.mjs';
|
|
9
|
+
import {
|
|
10
|
+
claudeCheckpointConfigBinding,
|
|
11
|
+
claudeObservationConfigBinding,
|
|
12
|
+
telemetryConfigBinding,
|
|
13
|
+
} from './state.mjs';
|
|
8
14
|
import { codexPolicyJson, makePolicy } from './policy.mjs';
|
|
15
|
+
import {
|
|
16
|
+
CODEX_LAUNCH_ENVELOPE,
|
|
17
|
+
CODEX_MONITOR_CWD,
|
|
18
|
+
makeCodexLaunchEnvelope,
|
|
19
|
+
parseCodexLaunchArgs,
|
|
20
|
+
readEffectiveWritableRoots,
|
|
21
|
+
} from './codex-permissions.mjs';
|
|
9
22
|
|
|
10
23
|
const MCP_ALIAS = 'aibrain_context';
|
|
11
24
|
const PLUGIN_NAME = 'ai-brain-context-compaction';
|
|
@@ -17,12 +30,16 @@ const CLAUDE_HEADLESS_FLAGS = new Set([
|
|
|
17
30
|
'--input-format',
|
|
18
31
|
'--output-style',
|
|
19
32
|
]);
|
|
20
|
-
const RESERVED_CLAUDE_FLAGS = new Set([
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
const RESERVED_CLAUDE_FLAGS = new Set(['--mcp-config', '--plugin-dir', '--allowedTools']);
|
|
34
|
+
const MAX_CLAUDE_SETTINGS_BYTES = 64 * 1024;
|
|
35
|
+
const MAX_CLAUDE_SETTINGS_DEPTH = 12;
|
|
36
|
+
const MAX_CLAUDE_SETTINGS_NODES = 512;
|
|
37
|
+
const MAX_CLAUDE_SETTINGS_STRING_LENGTH = 16 * 1024;
|
|
38
|
+
const MAX_CLAUDE_PERMISSION_LIST_ITEMS = 256;
|
|
39
|
+
const MAX_CLAUDE_PERMISSION_STRING_LENGTH = 4096;
|
|
40
|
+
const CLAUDE_SETTINGS_SECTIONS = new Set(['sandbox', 'permissions']);
|
|
41
|
+
const CLAUDE_PERMISSION_LISTS = new Set(['allow', 'ask', 'deny', 'additionalDirectories']);
|
|
42
|
+
const CLAUDE_SETTINGS_FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
26
43
|
|
|
27
44
|
/**
|
|
28
45
|
* @typedef {object} LaunchConfig
|
|
@@ -33,6 +50,8 @@ const RESERVED_CLAUDE_FLAGS = new Set([
|
|
|
33
50
|
* @property {string|null} codexVersion
|
|
34
51
|
* @property {number} schemaVersion
|
|
35
52
|
* @property {import('./policy.mjs').ContextPolicy} [context]
|
|
53
|
+
* @property {{ enabled: boolean, disclosedAt?: string | null }} [telemetry]
|
|
54
|
+
* @property {{ shadowEnabled: boolean, diagnosticsEnabled: boolean, disclosedAt?: string | null }} [claudeObservation]
|
|
36
55
|
*/
|
|
37
56
|
|
|
38
57
|
/** @param {LaunchConfig} config */
|
|
@@ -105,6 +124,8 @@ function childEnvironment(root) {
|
|
|
105
124
|
delete environment.AIBRAIN_TOKEN_FILE;
|
|
106
125
|
delete environment.CODEX_CONTEXT_POLICY_JSON;
|
|
107
126
|
delete environment.CODEX_CONTEXT_COMPACT_AT;
|
|
127
|
+
delete environment[CODEX_LAUNCH_ENVELOPE];
|
|
128
|
+
delete environment[CODEX_MONITOR_CWD];
|
|
108
129
|
environment.AIBRAIN_STATE_DIR = resolve(root);
|
|
109
130
|
return environment;
|
|
110
131
|
}
|
|
@@ -112,6 +133,7 @@ function childEnvironment(root) {
|
|
|
112
133
|
/** @param {string[]} args */
|
|
113
134
|
function assertClaudeArgs(args) {
|
|
114
135
|
for (const argument of args) {
|
|
136
|
+
if (argument === '--') break;
|
|
115
137
|
const option = argument.split('=', 1)[0];
|
|
116
138
|
if (CLAUDE_HEADLESS_FLAGS.has(option) || /^-p(?:=|[^-])/u.test(argument))
|
|
117
139
|
throw new Error('claude_headless_mode_unsupported');
|
|
@@ -119,6 +141,180 @@ function assertClaudeArgs(args) {
|
|
|
119
141
|
}
|
|
120
142
|
}
|
|
121
143
|
|
|
144
|
+
/** @param {unknown} value @returns {value is Record<string, unknown>} */
|
|
145
|
+
function plainObject(value) {
|
|
146
|
+
return (
|
|
147
|
+
value !== null &&
|
|
148
|
+
typeof value === 'object' &&
|
|
149
|
+
!Array.isArray(value) &&
|
|
150
|
+
Object.getPrototypeOf(value) === Object.prototype
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** @param {unknown} value */
|
|
155
|
+
function validateClaudeSettingsValue(value, depth = 0, state = { nodes: 0 }) {
|
|
156
|
+
state.nodes += 1;
|
|
157
|
+
if (state.nodes > MAX_CLAUDE_SETTINGS_NODES || depth > MAX_CLAUDE_SETTINGS_DEPTH)
|
|
158
|
+
throw new Error('claude_settings_structure_invalid');
|
|
159
|
+
if (typeof value === 'string') {
|
|
160
|
+
if (value.length > MAX_CLAUDE_SETTINGS_STRING_LENGTH)
|
|
161
|
+
throw new Error('claude_settings_string_too_long');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (typeof value === 'number') {
|
|
165
|
+
if (!Number.isFinite(value) || Math.abs(value) > Number.MAX_SAFE_INTEGER)
|
|
166
|
+
throw new Error('claude_settings_value_invalid');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (value === null || typeof value === 'boolean') return;
|
|
170
|
+
if (Array.isArray(value)) {
|
|
171
|
+
if (value.length > MAX_CLAUDE_SETTINGS_NODES)
|
|
172
|
+
throw new Error('claude_settings_structure_invalid');
|
|
173
|
+
for (const item of value) validateClaudeSettingsValue(item, depth + 1, state);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!plainObject(value)) throw new Error('claude_settings_value_invalid');
|
|
177
|
+
for (const [key, item] of Object.entries(value)) {
|
|
178
|
+
if (CLAUDE_SETTINGS_FORBIDDEN_KEYS.has(key)) throw new Error('claude_settings_prototype_key');
|
|
179
|
+
if (key.length > 256) throw new Error('claude_settings_key_too_long');
|
|
180
|
+
validateClaudeSettingsValue(item, depth + 1, state);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** @param {unknown} value */
|
|
185
|
+
function validateClaudeSettingsFragment(value) {
|
|
186
|
+
if (!plainObject(value)) throw new Error('claude_settings_root_invalid');
|
|
187
|
+
validateClaudeSettingsValue(value);
|
|
188
|
+
for (const key of Object.keys(value)) {
|
|
189
|
+
if (!CLAUDE_SETTINGS_SECTIONS.has(key)) throw new Error('claude_settings_section_unsupported');
|
|
190
|
+
const section = value[key];
|
|
191
|
+
if (!plainObject(section)) throw new Error('claude_settings_section_invalid');
|
|
192
|
+
}
|
|
193
|
+
const permissions = value.permissions;
|
|
194
|
+
if (permissions !== undefined) {
|
|
195
|
+
const permissionObject = /** @type {Record<string, unknown>} */ (permissions);
|
|
196
|
+
for (const key of CLAUDE_PERMISSION_LISTS) {
|
|
197
|
+
const list = permissionObject[key];
|
|
198
|
+
if (list === undefined) continue;
|
|
199
|
+
if (
|
|
200
|
+
!Array.isArray(list) ||
|
|
201
|
+
list.length > MAX_CLAUDE_PERMISSION_LIST_ITEMS ||
|
|
202
|
+
list.some(
|
|
203
|
+
(item) => typeof item !== 'string' || item.length > MAX_CLAUDE_PERMISSION_STRING_LENGTH,
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
throw new Error('claude_settings_permission_list_invalid');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return /** @type {{sandbox?: Record<string, unknown>, permissions?: Record<string, unknown>}} */ (
|
|
210
|
+
value
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** @param {string} text */
|
|
215
|
+
function parseClaudeSettingsText(text) {
|
|
216
|
+
const bytes = Buffer.byteLength(text, 'utf8');
|
|
217
|
+
if (bytes > MAX_CLAUDE_SETTINGS_BYTES) throw new Error('claude_settings_too_large');
|
|
218
|
+
let value;
|
|
219
|
+
try {
|
|
220
|
+
value = JSON.parse(text);
|
|
221
|
+
} catch {
|
|
222
|
+
throw new Error('claude_settings_invalid_json');
|
|
223
|
+
}
|
|
224
|
+
return validateClaudeSettingsFragment(value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** @param {string} path */
|
|
228
|
+
async function readClaudeSettingsFile(path) {
|
|
229
|
+
let handle;
|
|
230
|
+
try {
|
|
231
|
+
const link = await lstat(path);
|
|
232
|
+
if (link.isSymbolicLink() || !link.isFile()) throw new Error('claude_settings_file_invalid');
|
|
233
|
+
handle = await open(
|
|
234
|
+
path,
|
|
235
|
+
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0),
|
|
236
|
+
);
|
|
237
|
+
const entry = await handle.stat();
|
|
238
|
+
if (!entry.isFile()) throw new Error('claude_settings_file_invalid');
|
|
239
|
+
if (entry.size > MAX_CLAUDE_SETTINGS_BYTES) throw new Error('claude_settings_too_large');
|
|
240
|
+
const buffer = Buffer.alloc(MAX_CLAUDE_SETTINGS_BYTES + 1);
|
|
241
|
+
let offset = 0;
|
|
242
|
+
while (offset < buffer.length) {
|
|
243
|
+
const result = await handle.read(buffer, offset, buffer.length - offset, null);
|
|
244
|
+
if (result.bytesRead === 0) break;
|
|
245
|
+
offset += result.bytesRead;
|
|
246
|
+
}
|
|
247
|
+
if (offset > MAX_CLAUDE_SETTINGS_BYTES) throw new Error('claude_settings_too_large');
|
|
248
|
+
let text;
|
|
249
|
+
try {
|
|
250
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, offset));
|
|
251
|
+
} catch {
|
|
252
|
+
throw new Error('claude_settings_invalid_utf8');
|
|
253
|
+
}
|
|
254
|
+
return parseClaudeSettingsText(text);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
if (error instanceof Error && error.message.startsWith('claude_settings_')) throw error;
|
|
257
|
+
throw new Error('claude_settings_file_invalid');
|
|
258
|
+
} finally {
|
|
259
|
+
await handle?.close().catch(() => undefined);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** @param {string} value @param {string} cwd */
|
|
264
|
+
async function readClaudeSettings(value, cwd) {
|
|
265
|
+
const first = value.trimStart()[0];
|
|
266
|
+
const explicitPath = isAbsolute(value) || value.startsWith('./') || value.startsWith('../');
|
|
267
|
+
if (Buffer.byteLength(value, 'utf8') > MAX_CLAUDE_SETTINGS_BYTES)
|
|
268
|
+
throw new Error('claude_settings_too_large');
|
|
269
|
+
try {
|
|
270
|
+
return validateClaudeSettingsFragment(JSON.parse(value));
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (error instanceof Error && error.message.startsWith('claude_settings_')) throw error;
|
|
273
|
+
if (!explicitPath && (first === '{' || first === '[' || first === '"' || first === '-'))
|
|
274
|
+
throw new Error('claude_settings_invalid_json');
|
|
275
|
+
}
|
|
276
|
+
return readClaudeSettingsFile(resolve(cwd, value));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @param {string[]} args
|
|
281
|
+
* @param {string} cwd
|
|
282
|
+
* @returns {Promise<{args: string[], settings?: {sandbox?: Record<string, unknown>, permissions?: Record<string, unknown>}}>}
|
|
283
|
+
*/
|
|
284
|
+
async function parseClaudeLaunchArgs(args, cwd) {
|
|
285
|
+
const forwarded = [];
|
|
286
|
+
let settingsValue;
|
|
287
|
+
let terminated = false;
|
|
288
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
289
|
+
const argument = args[index];
|
|
290
|
+
if (terminated) {
|
|
291
|
+
forwarded.push(argument);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (argument === '--') {
|
|
295
|
+
terminated = true;
|
|
296
|
+
forwarded.push(argument);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (argument === '--settings' || argument.startsWith('--settings=')) {
|
|
300
|
+
if (settingsValue !== undefined) throw new Error('claude_settings_duplicate');
|
|
301
|
+
const value =
|
|
302
|
+
argument === '--settings' ? args[++index] : argument.slice('--settings='.length);
|
|
303
|
+
if (value === undefined || value === '' || value.startsWith('-'))
|
|
304
|
+
throw new Error('claude_settings_value_required');
|
|
305
|
+
settingsValue = value;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
forwarded.push(argument);
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
args: forwarded,
|
|
312
|
+
...(settingsValue === undefined
|
|
313
|
+
? {}
|
|
314
|
+
: { settings: await readClaudeSettings(settingsValue, cwd) }),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
122
318
|
/** @param {string[]} args */
|
|
123
319
|
function assertCodexArgs(args) {
|
|
124
320
|
for (const argument of args) {
|
|
@@ -135,11 +331,35 @@ async function assertClaudeAssets(packageRoot) {
|
|
|
135
331
|
join(pluginRoot, '.claude-plugin', 'plugin.json'),
|
|
136
332
|
join(pluginRoot, 'hooks', 'hooks.json'),
|
|
137
333
|
join(pluginRoot, 'hooks', 'context-compaction.ts'),
|
|
334
|
+
join(pluginRoot, 'hooks', 'provider-window.js'),
|
|
335
|
+
join(pluginRoot, 'hooks', 'state-fitting.mjs'),
|
|
336
|
+
join(pluginRoot, 'hooks', 'state-fitting.d.mts'),
|
|
138
337
|
];
|
|
139
338
|
await Promise.all(required.map((path) => access(path, constants.R_OK)));
|
|
140
339
|
return pluginRoot;
|
|
141
340
|
}
|
|
142
341
|
|
|
342
|
+
/** @param {string} packageRoot */
|
|
343
|
+
async function assertTelemetryHelper(packageRoot) {
|
|
344
|
+
const helperPath = join(packageRoot, 'telemetry-report.mjs');
|
|
345
|
+
await access(helperPath, constants.R_OK);
|
|
346
|
+
return helperPath;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** @param {string} packageRoot */
|
|
350
|
+
async function assertClaudeObservationHelper(packageRoot) {
|
|
351
|
+
const helperPath = join(packageRoot, 'claude-observation.mjs');
|
|
352
|
+
await access(helperPath, constants.R_OK);
|
|
353
|
+
return helperPath;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** @param {string} packageRoot */
|
|
357
|
+
async function assertClaudeCheckpointHelper(packageRoot) {
|
|
358
|
+
const helperPath = join(packageRoot, 'claude-checkpoint.mjs');
|
|
359
|
+
await access(helperPath, constants.R_OK);
|
|
360
|
+
return helperPath;
|
|
361
|
+
}
|
|
362
|
+
|
|
143
363
|
/** @param {string} root @param {string} prefix */
|
|
144
364
|
async function launchDirectory(root, prefix) {
|
|
145
365
|
const parent = await privateDirectory(join(root, 'runtime'));
|
|
@@ -183,12 +403,31 @@ function waitForChild(child, directory) {
|
|
|
183
403
|
/** @param {LaunchConfig} config @param {string} root @param {string[]} args */
|
|
184
404
|
export async function launchClaude(config, root, args) {
|
|
185
405
|
const validated = assertConfig(config);
|
|
186
|
-
|
|
406
|
+
const launchArgs = await parseClaudeLaunchArgs(args, process.cwd());
|
|
407
|
+
assertClaudeArgs(launchArgs.args);
|
|
187
408
|
const executable = resolveNative('claude');
|
|
188
409
|
if (!executable) throw new Error('claude_not_found');
|
|
189
410
|
const currentVersion = nativeVersion(executable);
|
|
190
411
|
assertTestedNativeVersion('claude', currentVersion);
|
|
191
412
|
const pluginRoot = await assertClaudeAssets(validated.packageRoot);
|
|
413
|
+
const telemetryActive =
|
|
414
|
+
validated.telemetry?.enabled === true && validated.telemetry?.disclosedAt != null;
|
|
415
|
+
const telemetryHelperPath = telemetryActive
|
|
416
|
+
? await assertTelemetryHelper(validated.packageRoot)
|
|
417
|
+
: undefined;
|
|
418
|
+
const telemetryConfigDigest = telemetryActive ? telemetryConfigBinding(validated) : undefined;
|
|
419
|
+
const observationActive =
|
|
420
|
+
validated.claudeObservation?.shadowEnabled === true ||
|
|
421
|
+
validated.claudeObservation?.diagnosticsEnabled === true;
|
|
422
|
+
const observationHelperPath = observationActive
|
|
423
|
+
? await assertClaudeObservationHelper(validated.packageRoot)
|
|
424
|
+
: undefined;
|
|
425
|
+
const observationConfigDigest = observationActive
|
|
426
|
+
? claudeObservationConfigBinding(validated)
|
|
427
|
+
: undefined;
|
|
428
|
+
const checkpointHelperPath = await assertClaudeCheckpointHelper(validated.packageRoot);
|
|
429
|
+
const checkpointConfigDigest = claudeCheckpointConfigBinding(validated);
|
|
430
|
+
const checkpointStateDir = await privateDirectory(join(root, 'claude', 'checkpoints'));
|
|
192
431
|
const directory = await launchDirectory(root, 'claude');
|
|
193
432
|
const settingsPath = join(directory, 'settings.json');
|
|
194
433
|
const mcpPath = join(directory, 'mcp.json');
|
|
@@ -213,21 +452,63 @@ export async function launchClaude(config, root, args) {
|
|
|
213
452
|
}),
|
|
214
453
|
enabled: true,
|
|
215
454
|
disclosed: true,
|
|
455
|
+
compactionStrategy: 'native_summary_notes',
|
|
216
456
|
preservationMode: 'upstream_compatible',
|
|
217
457
|
diagnosticMode: false,
|
|
458
|
+
telemetryEnabled: validated.telemetry?.enabled === true,
|
|
459
|
+
telemetryDisclosed:
|
|
460
|
+
validated.telemetry?.enabled === true && validated.telemetry?.disclosedAt != null,
|
|
461
|
+
telemetryClientVersion: currentVersion.match(/\b\d+\.\d+\.\d+\b/u)?.[0] ?? '',
|
|
462
|
+
...(telemetryActive
|
|
463
|
+
? {
|
|
464
|
+
telemetryNodePath: process.execPath,
|
|
465
|
+
telemetryHelperPath,
|
|
466
|
+
telemetryStateDir: resolve(root),
|
|
467
|
+
telemetryConfigDigest,
|
|
468
|
+
}
|
|
469
|
+
: {}),
|
|
470
|
+
...(observationActive
|
|
471
|
+
? {
|
|
472
|
+
observationNodePath: process.execPath,
|
|
473
|
+
observationHelperPath,
|
|
474
|
+
observationStateDir: resolve(root),
|
|
475
|
+
observationConfigDigest,
|
|
476
|
+
observationShadowEnabled: validated.claudeObservation?.shadowEnabled === true,
|
|
477
|
+
observationDiagnosticsEnabled: validated.claudeObservation?.diagnosticsEnabled === true,
|
|
478
|
+
}
|
|
479
|
+
: {}),
|
|
480
|
+
checkpointNodePath: process.execPath,
|
|
481
|
+
checkpointHelperPath,
|
|
482
|
+
checkpointStateDir,
|
|
483
|
+
checkpointConfigDigest,
|
|
484
|
+
checkpointBinding: checkpointConfigDigest,
|
|
218
485
|
};
|
|
219
486
|
if (!legacy && context.goal !== undefined) options.goal = context.goal;
|
|
487
|
+
const requiredAllow = [
|
|
488
|
+
`mcp__${MCP_ALIAS}__brain_context_status`,
|
|
489
|
+
`mcp__${MCP_ALIAS}__brain_context_compact`,
|
|
490
|
+
`mcp__${MCP_ALIAS}__brain_context_select`,
|
|
491
|
+
`mcp__${MCP_ALIAS}__brain_context_budget`,
|
|
492
|
+
...(options.telemetryEnabled && options.telemetryDisclosed
|
|
493
|
+
? [`mcp__${MCP_ALIAS}__brain_context_report`]
|
|
494
|
+
: []),
|
|
495
|
+
];
|
|
496
|
+
const userPermissions = launchArgs.settings?.permissions ?? {};
|
|
497
|
+
const userAllow = Array.isArray(userPermissions.allow) ? userPermissions.allow : [];
|
|
498
|
+
/** @type {string[]} */
|
|
499
|
+
const mergedAllow = [];
|
|
500
|
+
for (const rule of [...userAllow, ...requiredAllow]) {
|
|
501
|
+
if (!mergedAllow.includes(rule)) mergedAllow.push(rule);
|
|
502
|
+
}
|
|
220
503
|
const settings = {
|
|
504
|
+
...(launchArgs.settings?.sandbox === undefined ? {} : { sandbox: launchArgs.settings.sandbox }),
|
|
221
505
|
pluginConfigs: {
|
|
222
506
|
[PLUGIN_NAME]: { options },
|
|
223
507
|
[PLUGIN_INLINE_NAME]: { options: { ...options } },
|
|
224
508
|
},
|
|
225
509
|
permissions: {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
`mcp__${MCP_ALIAS}__brain_context_compact`,
|
|
229
|
-
`mcp__${MCP_ALIAS}__brain_context_select`,
|
|
230
|
-
],
|
|
510
|
+
...userPermissions,
|
|
511
|
+
allow: mergedAllow,
|
|
231
512
|
},
|
|
232
513
|
};
|
|
233
514
|
const mcp = {
|
|
@@ -259,10 +540,10 @@ export async function launchClaude(config, root, args) {
|
|
|
259
540
|
'--mcp-config',
|
|
260
541
|
mcpPath,
|
|
261
542
|
'--allowedTools',
|
|
262
|
-
|
|
543
|
+
requiredAllow.join(','),
|
|
263
544
|
'--plugin-dir',
|
|
264
545
|
pluginRoot,
|
|
265
|
-
...args,
|
|
546
|
+
...launchArgs.args,
|
|
266
547
|
],
|
|
267
548
|
{ cwd: process.cwd(), env: environment, stdio: 'inherit' },
|
|
268
549
|
);
|
|
@@ -280,34 +561,72 @@ export async function launchCodex(config, root, args) {
|
|
|
280
561
|
const executable = resolveNative('codex');
|
|
281
562
|
const node = resolveNative('node');
|
|
282
563
|
if (!executable || !node) throw new Error('codex_or_node_not_found');
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (
|
|
564
|
+
const compatibility = await resolveCodexCompatibility(executable);
|
|
565
|
+
if (compatibility.status === 'rejected') throw new Error(codexCompatibilityError(compatibility));
|
|
566
|
+
if (compatibility.version === null) throw new Error('codex_version_unverified');
|
|
567
|
+
if (compatibility.status === 'protocol_match')
|
|
568
|
+
process.stderr.write(
|
|
569
|
+
`aibrain: Codex ${compatibility.version} matched the local protocol contract; native behavioral qualification is not established.\n`,
|
|
570
|
+
);
|
|
571
|
+
const launchPlan = await parseCodexLaunchArgs(args, process.cwd());
|
|
287
572
|
const packageRoot = resolve(validated.packageRoot);
|
|
288
573
|
const terminal = join(packageRoot, 'assets', 'jev-paid-pilot-terminal.mjs');
|
|
289
574
|
const monitor = join(packageRoot, 'monitor.mjs');
|
|
290
|
-
|
|
575
|
+
const diagnosticModule = join(packageRoot, 'codex-diagnostics.mjs');
|
|
576
|
+
await Promise.all([
|
|
577
|
+
access(terminal, constants.R_OK),
|
|
578
|
+
access(monitor, constants.X_OK),
|
|
579
|
+
access(diagnosticModule, constants.R_OK),
|
|
580
|
+
]);
|
|
291
581
|
const checkpoints = await privateDirectory(join(root, 'codex', 'checkpoints'));
|
|
292
582
|
const notes = await privateDirectory(join(root, 'codex', 'notes'));
|
|
293
583
|
const directory = await launchDirectory(root, 'codex');
|
|
294
584
|
const environment = childEnvironment(root);
|
|
585
|
+
try {
|
|
586
|
+
if (
|
|
587
|
+
launchPlan.additionalRoots.length > 0 ||
|
|
588
|
+
Object.keys(launchPlan.server).length > 0 ||
|
|
589
|
+
launchPlan.profile !== undefined
|
|
590
|
+
) {
|
|
591
|
+
const configuredRoots =
|
|
592
|
+
launchPlan.additionalRoots.length > 0
|
|
593
|
+
? await readEffectiveWritableRoots(
|
|
594
|
+
executable,
|
|
595
|
+
launchPlan.effectiveCwd,
|
|
596
|
+
launchPlan.profile,
|
|
597
|
+
environment,
|
|
598
|
+
)
|
|
599
|
+
: [];
|
|
600
|
+
const envelope = makeCodexLaunchEnvelope(launchPlan, configuredRoots);
|
|
601
|
+
environment[CODEX_LAUNCH_ENVELOPE] = JSON.stringify(envelope);
|
|
602
|
+
}
|
|
603
|
+
} catch (error) {
|
|
604
|
+
await rm(directory, { recursive: true, force: true });
|
|
605
|
+
throw error;
|
|
606
|
+
}
|
|
295
607
|
environment.AI_BRAIN_MCP_SERVER = MCP_ALIAS;
|
|
296
608
|
environment.CODEX_BIN = executable;
|
|
297
609
|
environment.CODEX_MONITOR_COMMAND = monitor;
|
|
610
|
+
environment[CODEX_MONITOR_CWD] = launchPlan.effectiveCwd;
|
|
298
611
|
environment.CODEX_CONTEXT_RECOVERY_ENABLED = '1';
|
|
299
612
|
environment.CODEX_CONTEXT_RECOVERY_DISCLOSED = '1';
|
|
300
613
|
environment.CODEX_CONTEXT_CHECKPOINT_DIR = checkpoints;
|
|
301
614
|
environment.CODEX_CONTEXT_RECOVERY_DIR = notes;
|
|
615
|
+
environment.CODEX_CONTEXT_DIAGNOSTICS_MODULE = diagnosticModule;
|
|
616
|
+
environment.CODEX_CONTEXT_DIAGNOSTICS_DIR = resolve(root);
|
|
617
|
+
environment.CODEX_CONTEXT_DIAGNOSTICS_ENABLED = '1';
|
|
302
618
|
environment.AIBRAIN_MCP_URL = validated.endpoint;
|
|
303
619
|
environment.AIBRAIN_MCP_TOKEN = validated.token;
|
|
304
|
-
environment.CODEX_CLIENT_VERSION =
|
|
620
|
+
environment.CODEX_CLIENT_VERSION = compatibility.version;
|
|
621
|
+
environment.CODEX_CONTEXT_TELEMETRY_ENABLED = validated.telemetry?.enabled === true ? '1' : '0';
|
|
622
|
+
environment.CODEX_CONTEXT_TELEMETRY_DISCLOSED =
|
|
623
|
+
validated.telemetry?.enabled === true && validated.telemetry?.disclosedAt != null ? '1' : '0';
|
|
305
624
|
if (validated.schemaVersion === 2 && validated.context !== undefined) {
|
|
306
625
|
environment.CODEX_CONTEXT_POLICY_JSON = codexPolicyJson(validated.context);
|
|
307
626
|
} else {
|
|
308
627
|
delete environment.CODEX_CONTEXT_POLICY_JSON;
|
|
309
628
|
}
|
|
310
|
-
const child = spawn(node, [terminal, '--', ...
|
|
629
|
+
const child = spawn(node, [terminal, '--', ...launchPlan.nativeArgs], {
|
|
311
630
|
cwd: process.cwd(),
|
|
312
631
|
env: environment,
|
|
313
632
|
stdio: 'inherit',
|