@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/mcp.mjs
CHANGED
|
@@ -3,6 +3,9 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
3
3
|
const MAX_BODY = 256 * 1024;
|
|
4
4
|
const MAX_SESSION_ID = 256;
|
|
5
5
|
const TIMEOUT_MS = 10_000;
|
|
6
|
+
const MAX_BUDGET_SNAPSHOT_AGE_MS = 15_000;
|
|
7
|
+
const MAX_RETRY_AFTER_MS = 3_600_000;
|
|
8
|
+
const SAFE_IDENTIFIER = /^[A-Za-z0-9._:-]{1,128}$/u;
|
|
6
9
|
const MAX_ENDPOINT_LENGTH = 2_048;
|
|
7
10
|
const SUPPORTED_PROTOCOL_VERSIONS = new Set([
|
|
8
11
|
'2024-11-05',
|
|
@@ -147,11 +150,12 @@ async function responseObject(response) {
|
|
|
147
150
|
* @param {JsonObject} body
|
|
148
151
|
* @param {string | undefined} sessionId
|
|
149
152
|
* @param {string | undefined} protocolVersion
|
|
153
|
+
* @param {number} [timeoutMs]
|
|
150
154
|
* @returns {Promise<CallResult>}
|
|
151
155
|
*/
|
|
152
|
-
async function call(url, token, body, sessionId, protocolVersion) {
|
|
156
|
+
async function call(url, token, body, sessionId, protocolVersion, timeoutMs = TIMEOUT_MS) {
|
|
153
157
|
const controller = new AbortController();
|
|
154
|
-
const timer = setTimeout(() => controller.abort(),
|
|
158
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
155
159
|
const expectedId = Object.hasOwn(body, 'id') ? body.id : undefined;
|
|
156
160
|
try {
|
|
157
161
|
const response = await fetch(url, {
|
|
@@ -216,6 +220,324 @@ async function call(url, token, body, sessionId, protocolVersion) {
|
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Send one already validated, content-free telemetry report over the existing
|
|
225
|
+
* authenticated MCP transport. The tool name and all protocol fields are
|
|
226
|
+
* fixed here; callers cannot select a server or tool.
|
|
227
|
+
*
|
|
228
|
+
* @param {string} endpoint
|
|
229
|
+
* @param {string} token
|
|
230
|
+
* @param {JsonObject} report
|
|
231
|
+
* @param {number} [deadlineMs]
|
|
232
|
+
* @returns {Promise<{accepted:true, duplicate:boolean}>}
|
|
233
|
+
*/
|
|
234
|
+
export async function reportContextTelemetry(endpoint, token, report, deadlineMs = 8_000) {
|
|
235
|
+
if (!Number.isFinite(deadlineMs) || deadlineMs <= 0 || deadlineMs > 8_000)
|
|
236
|
+
throw new Error('mcp_deadline_invalid');
|
|
237
|
+
const url = endpointUrl(endpoint).toString();
|
|
238
|
+
if (typeof token !== 'string' || token.trim() === '') throw new Error('mcp_auth_denied');
|
|
239
|
+
const deadline = Date.now() + deadlineMs;
|
|
240
|
+
const remaining = () => {
|
|
241
|
+
const value = deadline - Date.now();
|
|
242
|
+
if (value <= 0) throw new Error('mcp_request_timeout');
|
|
243
|
+
return Math.min(TIMEOUT_MS, value);
|
|
244
|
+
};
|
|
245
|
+
const initialized = await call(
|
|
246
|
+
url,
|
|
247
|
+
token,
|
|
248
|
+
{
|
|
249
|
+
jsonrpc: '2.0',
|
|
250
|
+
id: randomUUID(),
|
|
251
|
+
method: 'initialize',
|
|
252
|
+
params: {
|
|
253
|
+
protocolVersion: '2025-03-26',
|
|
254
|
+
capabilities: {},
|
|
255
|
+
clientInfo: { name: 'aibrain-telemetry-report', version: '0.3.0-beta.9' },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
undefined,
|
|
259
|
+
undefined,
|
|
260
|
+
remaining(),
|
|
261
|
+
);
|
|
262
|
+
const initializeResult = initialized.parsed?.result;
|
|
263
|
+
if (
|
|
264
|
+
!initialized.response.ok ||
|
|
265
|
+
initialized.parsed?.error ||
|
|
266
|
+
!isObject(initializeResult) ||
|
|
267
|
+
typeof initializeResult.protocolVersion !== 'string' ||
|
|
268
|
+
!SUPPORTED_PROTOCOL_VERSIONS.has(initializeResult.protocolVersion)
|
|
269
|
+
)
|
|
270
|
+
throw new Error(responseError(initialized.response, 'mcp_initialize_denied'));
|
|
271
|
+
const protocolVersion = initializeResult.protocolVersion;
|
|
272
|
+
const initializedNotification = await call(
|
|
273
|
+
url,
|
|
274
|
+
token,
|
|
275
|
+
{ jsonrpc: '2.0', method: 'notifications/initialized', params: {} },
|
|
276
|
+
initialized.sessionId,
|
|
277
|
+
protocolVersion,
|
|
278
|
+
remaining(),
|
|
279
|
+
);
|
|
280
|
+
if (
|
|
281
|
+
!initializedNotification.response.ok &&
|
|
282
|
+
![202, 204].includes(initializedNotification.response.status)
|
|
283
|
+
)
|
|
284
|
+
throw new Error(responseError(initializedNotification.response, 'mcp_initialize_denied'));
|
|
285
|
+
const reportResponse = await call(
|
|
286
|
+
url,
|
|
287
|
+
token,
|
|
288
|
+
{
|
|
289
|
+
jsonrpc: '2.0',
|
|
290
|
+
id: randomUUID(),
|
|
291
|
+
method: 'tools/call',
|
|
292
|
+
params: { name: 'brain_context_report', arguments: report },
|
|
293
|
+
},
|
|
294
|
+
initialized.sessionId,
|
|
295
|
+
protocolVersion,
|
|
296
|
+
remaining(),
|
|
297
|
+
);
|
|
298
|
+
const result = reportResponse.parsed?.result;
|
|
299
|
+
if (!reportResponse.response.ok || reportResponse.parsed?.error || !isObject(result))
|
|
300
|
+
throw new Error(responseError(reportResponse.response, 'mcp_report_rejected'));
|
|
301
|
+
if (result.isError === true)
|
|
302
|
+
throw new Error(structuredErrorCode(result) ?? 'mcp_report_rejected');
|
|
303
|
+
const structured = isObject(result.structuredContent) ? result.structuredContent : result;
|
|
304
|
+
if (
|
|
305
|
+
!isObject(structured) ||
|
|
306
|
+
structured.accepted !== true ||
|
|
307
|
+
typeof structured.duplicate !== 'boolean'
|
|
308
|
+
)
|
|
309
|
+
throw new Error('mcp_report_rejected');
|
|
310
|
+
return { accepted: true, duplicate: structured.duplicate };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Ask the fixed v2 selection tool for a shadow decision. The caller supplies
|
|
315
|
+
* only the already-redacted projection; this function owns the destination,
|
|
316
|
+
* MCP lifecycle and response binding.
|
|
317
|
+
* @param {string} endpoint
|
|
318
|
+
* @param {string} token
|
|
319
|
+
* @param {JsonObject} request
|
|
320
|
+
* @param {number} [deadlineMs]
|
|
321
|
+
* @returns {Promise<JsonObject>}
|
|
322
|
+
*/
|
|
323
|
+
export async function selectContextShadow(endpoint, token, request, deadlineMs = 15_000) {
|
|
324
|
+
if (!Number.isFinite(deadlineMs) || deadlineMs <= 0 || deadlineMs > 15_000)
|
|
325
|
+
throw new Error('mcp_deadline_invalid');
|
|
326
|
+
const url = endpointUrl(endpoint).toString();
|
|
327
|
+
if (typeof token !== 'string' || token.trim() === '') throw new Error('mcp_auth_denied');
|
|
328
|
+
const projection = isObject(request.projection) ? request.projection : null;
|
|
329
|
+
const options = isObject(request.options) ? request.options : null;
|
|
330
|
+
if (projection === null || options === null) throw new Error('mcp_shadow_request_invalid');
|
|
331
|
+
const deadline = Date.now() + deadlineMs;
|
|
332
|
+
const remaining = () => {
|
|
333
|
+
const value = deadline - Date.now();
|
|
334
|
+
if (value <= 0) throw new Error('mcp_request_timeout');
|
|
335
|
+
return Math.min(TIMEOUT_MS, value);
|
|
336
|
+
};
|
|
337
|
+
const initialized = await call(
|
|
338
|
+
url,
|
|
339
|
+
token,
|
|
340
|
+
{
|
|
341
|
+
jsonrpc: '2.0',
|
|
342
|
+
id: randomUUID(),
|
|
343
|
+
method: 'initialize',
|
|
344
|
+
params: {
|
|
345
|
+
protocolVersion: '2025-03-26',
|
|
346
|
+
capabilities: {},
|
|
347
|
+
clientInfo: { name: 'aibrain-claude-shadow', version: '0.3.0-beta.9' },
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
undefined,
|
|
351
|
+
undefined,
|
|
352
|
+
remaining(),
|
|
353
|
+
);
|
|
354
|
+
const initializeResult = initialized.parsed?.result;
|
|
355
|
+
if (
|
|
356
|
+
!initialized.response.ok ||
|
|
357
|
+
initialized.parsed?.error ||
|
|
358
|
+
!isObject(initializeResult) ||
|
|
359
|
+
typeof initializeResult.protocolVersion !== 'string' ||
|
|
360
|
+
!SUPPORTED_PROTOCOL_VERSIONS.has(initializeResult.protocolVersion)
|
|
361
|
+
)
|
|
362
|
+
throw new Error(responseError(initialized.response, 'mcp_initialize_denied'));
|
|
363
|
+
const protocolVersion = initializeResult.protocolVersion;
|
|
364
|
+
const initializedNotification = await call(
|
|
365
|
+
url,
|
|
366
|
+
token,
|
|
367
|
+
{ jsonrpc: '2.0', method: 'notifications/initialized', params: {} },
|
|
368
|
+
initialized.sessionId,
|
|
369
|
+
protocolVersion,
|
|
370
|
+
remaining(),
|
|
371
|
+
);
|
|
372
|
+
if (
|
|
373
|
+
!initializedNotification.response.ok &&
|
|
374
|
+
![202, 204].includes(initializedNotification.response.status)
|
|
375
|
+
)
|
|
376
|
+
throw new Error(responseError(initializedNotification.response, 'mcp_initialize_denied'));
|
|
377
|
+
const response = await call(
|
|
378
|
+
url,
|
|
379
|
+
token,
|
|
380
|
+
{
|
|
381
|
+
jsonrpc: '2.0',
|
|
382
|
+
id: randomUUID(),
|
|
383
|
+
method: 'tools/call',
|
|
384
|
+
params: { name: 'brain_context_select', arguments: request },
|
|
385
|
+
},
|
|
386
|
+
initialized.sessionId,
|
|
387
|
+
protocolVersion,
|
|
388
|
+
remaining(),
|
|
389
|
+
);
|
|
390
|
+
const result = response.parsed?.result;
|
|
391
|
+
if (!response.response.ok || response.parsed?.error || !isObject(result))
|
|
392
|
+
throw new Error(responseError(response.response, 'mcp_shadow_rejected'));
|
|
393
|
+
if (result.isError === true)
|
|
394
|
+
throw new Error(structuredErrorCode(result) ?? 'mcp_shadow_rejected');
|
|
395
|
+
const structured = isObject(result.structuredContent) ? result.structuredContent : result;
|
|
396
|
+
if (!isObject(structured)) throw new Error('mcp_shadow_rejected');
|
|
397
|
+
return structured;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Read the provider-free shared capacity snapshot. The snapshot is advisory;
|
|
402
|
+
* atomic admission remains authoritative and this helper never retries or
|
|
403
|
+
* reserves capacity.
|
|
404
|
+
* @param {string} endpoint
|
|
405
|
+
* @param {string} token
|
|
406
|
+
* @param {number} [deadlineMs]
|
|
407
|
+
* @returns {Promise<JsonObject>}
|
|
408
|
+
*/
|
|
409
|
+
export async function readContextBudget(endpoint, token, deadlineMs = 8_000) {
|
|
410
|
+
if (!Number.isFinite(deadlineMs) || deadlineMs <= 0 || deadlineMs > 8_000)
|
|
411
|
+
throw new Error('mcp_deadline_invalid');
|
|
412
|
+
const url = endpointUrl(endpoint).toString();
|
|
413
|
+
if (typeof token !== 'string' || token.trim() === '') throw new Error('mcp_auth_denied');
|
|
414
|
+
const fetchedAt = Date.now();
|
|
415
|
+
const deadline = Date.now() + deadlineMs;
|
|
416
|
+
const remaining = () => {
|
|
417
|
+
const value = deadline - Date.now();
|
|
418
|
+
if (value <= 0) throw new Error('mcp_request_timeout');
|
|
419
|
+
return Math.min(TIMEOUT_MS, value);
|
|
420
|
+
};
|
|
421
|
+
const initialized = await call(
|
|
422
|
+
url,
|
|
423
|
+
token,
|
|
424
|
+
{
|
|
425
|
+
jsonrpc: '2.0',
|
|
426
|
+
id: randomUUID(),
|
|
427
|
+
method: 'initialize',
|
|
428
|
+
params: {
|
|
429
|
+
protocolVersion: '2025-03-26',
|
|
430
|
+
capabilities: {},
|
|
431
|
+
clientInfo: { name: 'aibrain-context-budget', version: '0.3.0-beta.9' },
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
undefined,
|
|
435
|
+
undefined,
|
|
436
|
+
remaining(),
|
|
437
|
+
);
|
|
438
|
+
const initializeResult = initialized.parsed?.result;
|
|
439
|
+
if (
|
|
440
|
+
!initialized.response.ok ||
|
|
441
|
+
initialized.parsed?.error ||
|
|
442
|
+
!isObject(initializeResult) ||
|
|
443
|
+
typeof initializeResult.protocolVersion !== 'string' ||
|
|
444
|
+
!SUPPORTED_PROTOCOL_VERSIONS.has(initializeResult.protocolVersion)
|
|
445
|
+
)
|
|
446
|
+
throw new Error(responseError(initialized.response, 'mcp_initialize_denied'));
|
|
447
|
+
const protocolVersion = initializeResult.protocolVersion;
|
|
448
|
+
const initializedNotification = await call(
|
|
449
|
+
url,
|
|
450
|
+
token,
|
|
451
|
+
{ jsonrpc: '2.0', method: 'notifications/initialized', params: {} },
|
|
452
|
+
initialized.sessionId,
|
|
453
|
+
protocolVersion,
|
|
454
|
+
remaining(),
|
|
455
|
+
);
|
|
456
|
+
if (
|
|
457
|
+
!initializedNotification.response.ok &&
|
|
458
|
+
![202, 204].includes(initializedNotification.response.status)
|
|
459
|
+
)
|
|
460
|
+
throw new Error(responseError(initializedNotification.response, 'mcp_initialize_denied'));
|
|
461
|
+
const response = await call(
|
|
462
|
+
url,
|
|
463
|
+
token,
|
|
464
|
+
{
|
|
465
|
+
jsonrpc: '2.0',
|
|
466
|
+
id: randomUUID(),
|
|
467
|
+
method: 'tools/call',
|
|
468
|
+
params: { name: 'brain_context_budget', arguments: { schema_version: 1 } },
|
|
469
|
+
},
|
|
470
|
+
initialized.sessionId,
|
|
471
|
+
protocolVersion,
|
|
472
|
+
remaining(),
|
|
473
|
+
);
|
|
474
|
+
const result = response.parsed?.result;
|
|
475
|
+
if (!response.response.ok || response.parsed?.error || !isObject(result))
|
|
476
|
+
throw new Error(responseError(response.response, 'mcp_budget_unavailable'));
|
|
477
|
+
if (result.isError === true) throw new Error(structuredErrorCode(result) ?? 'mcp_budget_denied');
|
|
478
|
+
const structured = isObject(result.structuredContent) ? result.structuredContent : result;
|
|
479
|
+
if (!isObject(structured)) throw new Error('mcp_budget_unavailable');
|
|
480
|
+
const numericFields = [
|
|
481
|
+
structured.available_requests,
|
|
482
|
+
structured.available_tokens,
|
|
483
|
+
structured.output_token_allowance,
|
|
484
|
+
structured.max_batches_per_operation,
|
|
485
|
+
structured.retry_after_ms,
|
|
486
|
+
structured.provider_batches,
|
|
487
|
+
];
|
|
488
|
+
const numericFieldsValid = numericFields.every(
|
|
489
|
+
(value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0,
|
|
490
|
+
);
|
|
491
|
+
const maxBatches = structured.max_batches_per_operation;
|
|
492
|
+
const retryAfter = structured.retry_after_ms;
|
|
493
|
+
const serverTime = Date.parse(String(structured.server_time));
|
|
494
|
+
const expiresAt = Date.parse(String(structured.expires_at));
|
|
495
|
+
const windowEnd = Date.parse(String(structured.window_end));
|
|
496
|
+
const localElapsed = Date.now() - fetchedAt;
|
|
497
|
+
const allowed = new Set([
|
|
498
|
+
'schema_version',
|
|
499
|
+
'server_time',
|
|
500
|
+
'expires_at',
|
|
501
|
+
'window_end',
|
|
502
|
+
'available_requests',
|
|
503
|
+
'available_tokens',
|
|
504
|
+
'output_token_allowance',
|
|
505
|
+
'max_batches_per_operation',
|
|
506
|
+
'account_in_flight',
|
|
507
|
+
'retry_after_ms',
|
|
508
|
+
'provider_batches',
|
|
509
|
+
'provider_model',
|
|
510
|
+
'correlation_id',
|
|
511
|
+
]);
|
|
512
|
+
if (
|
|
513
|
+
Object.keys(structured).some((key) => !allowed.has(key)) ||
|
|
514
|
+
structured.schema_version !== 1 ||
|
|
515
|
+
!validTimestamp(structured.server_time) ||
|
|
516
|
+
!validTimestamp(structured.expires_at) ||
|
|
517
|
+
!validTimestamp(structured.window_end) ||
|
|
518
|
+
!Number.isFinite(serverTime) ||
|
|
519
|
+
!Number.isFinite(expiresAt) ||
|
|
520
|
+
!Number.isFinite(windowEnd) ||
|
|
521
|
+
localElapsed < 0 ||
|
|
522
|
+
localElapsed > MAX_BUDGET_SNAPSHOT_AGE_MS ||
|
|
523
|
+
expiresAt <= serverTime ||
|
|
524
|
+
expiresAt - serverTime > MAX_BUDGET_SNAPSHOT_AGE_MS ||
|
|
525
|
+
expiresAt > windowEnd ||
|
|
526
|
+
!numericFieldsValid ||
|
|
527
|
+
typeof maxBatches !== 'number' ||
|
|
528
|
+
maxBatches > 4 ||
|
|
529
|
+
typeof retryAfter !== 'number' ||
|
|
530
|
+
retryAfter > MAX_RETRY_AFTER_MS ||
|
|
531
|
+
typeof structured.account_in_flight !== 'boolean' ||
|
|
532
|
+
(structured.provider_model !== null &&
|
|
533
|
+
(typeof structured.provider_model !== 'string' || structured.provider_model.length > 128)) ||
|
|
534
|
+
typeof structured.correlation_id !== 'string' ||
|
|
535
|
+
!SAFE_IDENTIFIER.test(structured.correlation_id)
|
|
536
|
+
)
|
|
537
|
+
throw new Error('mcp_budget_response_invalid');
|
|
538
|
+
return structured;
|
|
539
|
+
}
|
|
540
|
+
|
|
219
541
|
/** @param {unknown} value @returns {boolean} */
|
|
220
542
|
function validTimestamp(value) {
|
|
221
543
|
return typeof value === 'string' && Number.isFinite(Date.parse(value));
|
|
@@ -256,7 +578,7 @@ async function providerFreeProbeV1(endpoint, token) {
|
|
|
256
578
|
params: {
|
|
257
579
|
protocolVersion: '2025-03-26',
|
|
258
580
|
capabilities: {},
|
|
259
|
-
clientInfo: { name: 'aibrain', version: '0.
|
|
581
|
+
clientInfo: { name: 'aibrain', version: '0.3.0-beta.9' },
|
|
260
582
|
},
|
|
261
583
|
},
|
|
262
584
|
undefined,
|
|
@@ -310,7 +632,7 @@ async function providerFreeProbeV1(endpoint, token) {
|
|
|
310
632
|
arguments: {
|
|
311
633
|
schema_version: 1,
|
|
312
634
|
client: 'codex_app_server',
|
|
313
|
-
client_version: '0.
|
|
635
|
+
client_version: '0.3.0-beta.9',
|
|
314
636
|
session_ref: sessionRef,
|
|
315
637
|
revision,
|
|
316
638
|
observed_at: new Date().toISOString(),
|
|
@@ -402,7 +724,7 @@ export async function providerFreeProbeV2(endpoint, token, policy, clients) {
|
|
|
402
724
|
params: {
|
|
403
725
|
protocolVersion: '2025-03-26',
|
|
404
726
|
capabilities: {},
|
|
405
|
-
clientInfo: { name: 'aibrain', version: '0.
|
|
727
|
+
clientInfo: { name: 'aibrain', version: '0.3.0-beta.9' },
|
|
406
728
|
},
|
|
407
729
|
},
|
|
408
730
|
undefined,
|
|
@@ -606,7 +928,7 @@ export async function providerFreeProbeV2(endpoint, token, policy, clients) {
|
|
|
606
928
|
await callTool(2, 'brain_context_select', {
|
|
607
929
|
schema_version: 2,
|
|
608
930
|
client: clients === 'claude' ? 'claude_code' : 'codex_app_server',
|
|
609
|
-
client_version: '0.
|
|
931
|
+
client_version: '0.3.0-beta.9',
|
|
610
932
|
session_ref: sessionRef,
|
|
611
933
|
revision,
|
|
612
934
|
observed_at: now,
|
|
@@ -619,7 +941,7 @@ export async function providerFreeProbeV2(endpoint, token, policy, clients) {
|
|
|
619
941
|
await callTool(3, 'brain_context_compact', {
|
|
620
942
|
schema_version: 2,
|
|
621
943
|
client: 'claude_code',
|
|
622
|
-
client_version: '0.
|
|
944
|
+
client_version: '0.3.0-beta.9',
|
|
623
945
|
session_ref: sessionRef,
|
|
624
946
|
revision,
|
|
625
947
|
observed_at: now,
|
package/dist/monitor.mjs
CHANGED
|
@@ -6,6 +6,12 @@ import { constants } from 'node:fs';
|
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import {
|
|
10
|
+
CODEX_APP_SERVER_PROFILE,
|
|
11
|
+
CODEX_LAUNCH_ENVELOPE,
|
|
12
|
+
ownedAppServerConfigArgs,
|
|
13
|
+
parseCodexLaunchEnvelope,
|
|
14
|
+
} from './codex-permissions.mjs';
|
|
9
15
|
|
|
10
16
|
const root = dirname(fileURLToPath(import.meta.url));
|
|
11
17
|
const endpoint = process.env.AIBRAIN_MCP_URL?.trim();
|
|
@@ -25,6 +31,7 @@ if (parsed.username || parsed.password || parsed.hash)
|
|
|
25
31
|
if (parsed.search) throw new Error('aibrain_monitor_endpoint_query');
|
|
26
32
|
const canonical = join(root, 'assets', 'codex-context-compaction.mjs');
|
|
27
33
|
await access(canonical, constants.R_OK);
|
|
34
|
+
const launchEnvelope = parseCodexLaunchEnvelope(process.env[CODEX_LAUNCH_ENVELOPE]);
|
|
28
35
|
const policyText = process.env.CODEX_CONTEXT_POLICY_JSON;
|
|
29
36
|
let policy;
|
|
30
37
|
if (policyText === undefined) {
|
|
@@ -80,6 +87,9 @@ const env = {
|
|
|
80
87
|
CODEX_RECOVERY_MCP_TOKEN: token,
|
|
81
88
|
CODEX_CONTEXT_COMPACT_AT: String(policy?.compactAtPercent ?? 85),
|
|
82
89
|
};
|
|
90
|
+
delete env[CODEX_LAUNCH_ENVELOPE];
|
|
91
|
+
delete env[CODEX_APP_SERVER_PROFILE];
|
|
92
|
+
if (launchEnvelope?.profile !== undefined) env[CODEX_APP_SERVER_PROFILE] = launchEnvelope.profile;
|
|
83
93
|
if (policy !== null) env.CODEX_CONTEXT_POLICY_JSON = JSON.stringify(policy);
|
|
84
94
|
else delete env.CODEX_CONTEXT_POLICY_JSON;
|
|
85
95
|
delete env.JEV_API_KEY;
|
|
@@ -98,6 +108,7 @@ const child = spawn(
|
|
|
98
108
|
`mcp_servers.aibrain_context.url=${parsed.toString()}`,
|
|
99
109
|
'-c',
|
|
100
110
|
'mcp_servers.aibrain_context.bearer_token_env_var="CODEX_RECOVERY_MCP_TOKEN"',
|
|
111
|
+
...(launchEnvelope === null ? [] : ownedAppServerConfigArgs(launchEnvelope)),
|
|
101
112
|
],
|
|
102
113
|
{ cwd: process.cwd(), env, stdio: 'inherit' },
|
|
103
114
|
);
|
package/dist/package.json
CHANGED
package/dist/platform.mjs
CHANGED
|
@@ -70,13 +70,43 @@ export function nativeVersion(executable, environment = process.env) {
|
|
|
70
70
|
export function isTestedNativeVersion(client, version) {
|
|
71
71
|
const value = version.trim();
|
|
72
72
|
if (client === 'claude')
|
|
73
|
-
return /^(?:(?:claude(?:\s+code)?)\s+)?2\.1\.(?:294|295|296)(?:\s+\(Claude\s+Code\))?$/iu.test(
|
|
73
|
+
return /^(?:(?:claude(?:\s+code)?)\s+)?2\.1\.(?:294|295|296|299|300|301|302)(?:\s+\(Claude\s+Code\))?$/iu.test(
|
|
74
74
|
value,
|
|
75
75
|
);
|
|
76
|
-
return /^(?:codex(?:-cli)?\s+)?0\.16(?:1\.0|2\.0|2\.1)$/iu.test(value);
|
|
76
|
+
return /^(?:codex(?:-cli)?\s+)?0\.16(?:1\.0|2\.0|2\.1|3\.0)$/iu.test(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const TESTED_NATIVE_VERSIONS = Object.freeze({
|
|
80
|
+
claude: Object.freeze([
|
|
81
|
+
'2.1.294',
|
|
82
|
+
'2.1.295',
|
|
83
|
+
'2.1.296',
|
|
84
|
+
'2.1.299',
|
|
85
|
+
'2.1.300',
|
|
86
|
+
'2.1.301',
|
|
87
|
+
'2.1.302',
|
|
88
|
+
]),
|
|
89
|
+
codex: Object.freeze(['0.161.0', '0.162.0', '0.162.1', '0.163.0']),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/** @param {'claude'|'codex'} client @param {string} version */
|
|
93
|
+
export function nativeVersionDiagnostic(client, version) {
|
|
94
|
+
const detected = version.trim();
|
|
95
|
+
const versionPattern =
|
|
96
|
+
client === 'claude'
|
|
97
|
+
? /^(?:(?:claude(?:\s+code)?)\s+)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})(-[A-Za-z0-9.-]+)?(?:\s+\(Claude\s+Code\))?$/iu
|
|
98
|
+
: /^(?:codex(?:-cli)?\s+)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})(-[A-Za-z0-9.-]+)?$/iu;
|
|
99
|
+
const versionMatch = detected.match(versionPattern);
|
|
100
|
+
const safeDetected = versionMatch
|
|
101
|
+
? `${versionMatch[1]}.${versionMatch[2]}.${versionMatch[3]}${versionMatch[4] ? '-prerelease' : ''}`
|
|
102
|
+
: 'unavailable';
|
|
103
|
+
const label = client === 'claude' ? 'Claude Code' : 'Codex CLI';
|
|
104
|
+
const tested = TESTED_NATIVE_VERSIONS[client].join(', ');
|
|
105
|
+
return `${client}_version_unverified: ${label} detected version ${safeDetected}; tested stable versions: ${tested}. Update AI Brain to a compatible version, then run aibrain doctor --json or install a qualified stable native version.`;
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
/** @param {'claude'|'codex'} client @param {string} version */
|
|
80
109
|
export function assertTestedNativeVersion(client, version) {
|
|
81
|
-
if (!isTestedNativeVersion(client, version))
|
|
110
|
+
if (!isTestedNativeVersion(client, version))
|
|
111
|
+
throw new Error(nativeVersionDiagnostic(client, version));
|
|
82
112
|
}
|
package/dist/state.mjs
CHANGED
|
@@ -30,7 +30,9 @@ import { isValidPolicy } from './policy.mjs';
|
|
|
30
30
|
* token: string,
|
|
31
31
|
* tokenSha256: string,
|
|
32
32
|
* probe: Probe,
|
|
33
|
-
* context?: import('./policy.mjs').ContextPolicy
|
|
33
|
+
* context?: import('./policy.mjs').ContextPolicy,
|
|
34
|
+
* telemetry?: { enabled: boolean, disclosedAt: string | null },
|
|
35
|
+
* claudeObservation?: { shadowEnabled: boolean, diagnosticsEnabled: boolean, disclosedAt: string | null }
|
|
34
36
|
* }} Config
|
|
35
37
|
*/
|
|
36
38
|
|
|
@@ -125,6 +127,35 @@ function isDisclosure(value) {
|
|
|
125
127
|
return value === DISCLOSURE_VERSION || value === LEGACY_DISCLOSURE_VERSION;
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
/** @param {unknown} value */
|
|
131
|
+
function isTelemetry(value) {
|
|
132
|
+
return (
|
|
133
|
+
isRecord(value) &&
|
|
134
|
+
typeof value.enabled === 'boolean' &&
|
|
135
|
+
(value.disclosedAt === null ||
|
|
136
|
+
(typeof value.disclosedAt === 'string' &&
|
|
137
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value.disclosedAt) &&
|
|
138
|
+
Number.isFinite(Date.parse(value.disclosedAt)))) &&
|
|
139
|
+
Object.keys(value).every((key) => ['enabled', 'disclosedAt'].includes(key))
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** @param {unknown} value */
|
|
144
|
+
function isClaudeObservation(value) {
|
|
145
|
+
return (
|
|
146
|
+
isRecord(value) &&
|
|
147
|
+
typeof value.shadowEnabled === 'boolean' &&
|
|
148
|
+
typeof value.diagnosticsEnabled === 'boolean' &&
|
|
149
|
+
(value.disclosedAt === null ||
|
|
150
|
+
(typeof value.disclosedAt === 'string' &&
|
|
151
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value.disclosedAt) &&
|
|
152
|
+
Number.isFinite(Date.parse(value.disclosedAt)))) &&
|
|
153
|
+
Object.keys(value).every((key) =>
|
|
154
|
+
['shadowEnabled', 'diagnosticsEnabled', 'disclosedAt'].includes(key),
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
128
159
|
/** @param {unknown} value */
|
|
129
160
|
function isConfig(value) {
|
|
130
161
|
if (!isRecord(value)) return false;
|
|
@@ -166,6 +197,9 @@ function isConfig(value) {
|
|
|
166
197
|
)
|
|
167
198
|
return false;
|
|
168
199
|
if (value.schemaVersion === 1 && value.context !== undefined) return false;
|
|
200
|
+
if (value.telemetry !== undefined && !isTelemetry(value.telemetry)) return false;
|
|
201
|
+
if (value.claudeObservation !== undefined && !isClaudeObservation(value.claudeObservation))
|
|
202
|
+
return false;
|
|
169
203
|
const allowed = new Set([
|
|
170
204
|
'schemaVersion',
|
|
171
205
|
'packageVersion',
|
|
@@ -180,6 +214,8 @@ function isConfig(value) {
|
|
|
180
214
|
'tokenSha256',
|
|
181
215
|
'probe',
|
|
182
216
|
'context',
|
|
217
|
+
'telemetry',
|
|
218
|
+
'claudeObservation',
|
|
183
219
|
]);
|
|
184
220
|
return Object.keys(value).every((key) => allowed.has(key));
|
|
185
221
|
}
|
|
@@ -189,9 +225,9 @@ export function isValidConfig(value) {
|
|
|
189
225
|
return isConfig(value);
|
|
190
226
|
}
|
|
191
227
|
|
|
192
|
-
/** @returns {Promise<Config | null>} */
|
|
193
|
-
export async function readConfig() {
|
|
194
|
-
const root = stateRoot();
|
|
228
|
+
/** @param {string | undefined} [rootOverride] @returns {Promise<Config | null>} */
|
|
229
|
+
export async function readConfig(rootOverride) {
|
|
230
|
+
const root = rootOverride === undefined ? stateRoot() : resolve(rootOverride);
|
|
195
231
|
await assertSafePath(root);
|
|
196
232
|
const path = join(root, 'config.json');
|
|
197
233
|
try {
|
|
@@ -254,6 +290,73 @@ export async function tokenDigest(token) {
|
|
|
254
290
|
return createHash('sha256').update(token, 'utf8').digest('hex');
|
|
255
291
|
}
|
|
256
292
|
|
|
293
|
+
/** @param {{endpoint: string, token: string, telemetry?: {enabled: boolean, disclosedAt?: string | null}}} config */
|
|
294
|
+
export function telemetryConfigBinding(config) {
|
|
295
|
+
if (
|
|
296
|
+
!isRecord(config) ||
|
|
297
|
+
typeof config.endpoint !== 'string' ||
|
|
298
|
+
typeof config.token !== 'string' ||
|
|
299
|
+
!isRecord(config.telemetry) ||
|
|
300
|
+
config.telemetry.enabled !== true ||
|
|
301
|
+
typeof config.telemetry.disclosedAt !== 'string'
|
|
302
|
+
)
|
|
303
|
+
throw new Error('telemetry_state_mismatch');
|
|
304
|
+
let endpoint;
|
|
305
|
+
try {
|
|
306
|
+
endpoint = new URL(config.endpoint).toString();
|
|
307
|
+
} catch {
|
|
308
|
+
throw new Error('telemetry_state_mismatch');
|
|
309
|
+
}
|
|
310
|
+
return createHash('sha256')
|
|
311
|
+
.update(`${endpoint}\u0000${config.token}\u0000${config.telemetry.disclosedAt}`, 'utf8')
|
|
312
|
+
.digest('hex');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** @param {{endpoint: string, token: string, claudeObservation?: {shadowEnabled?: boolean, diagnosticsEnabled?: boolean, disclosedAt?: string | null}}} config */
|
|
316
|
+
export function claudeObservationConfigBinding(config) {
|
|
317
|
+
if (
|
|
318
|
+
!isRecord(config) ||
|
|
319
|
+
typeof config.endpoint !== 'string' ||
|
|
320
|
+
typeof config.token !== 'string' ||
|
|
321
|
+
!isRecord(config.claudeObservation) ||
|
|
322
|
+
typeof config.claudeObservation.disclosedAt !== 'string'
|
|
323
|
+
)
|
|
324
|
+
throw new Error('claude_observation_state_mismatch');
|
|
325
|
+
let endpoint;
|
|
326
|
+
try {
|
|
327
|
+
endpoint = new URL(config.endpoint).toString();
|
|
328
|
+
} catch {
|
|
329
|
+
throw new Error('claude_observation_state_mismatch');
|
|
330
|
+
}
|
|
331
|
+
return createHash('sha256')
|
|
332
|
+
.update(`${endpoint}\u0000${config.token}\u0000${config.claudeObservation.disclosedAt}`, 'utf8')
|
|
333
|
+
.digest('hex');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Bind launcher-managed Claude checkpoints to the authenticated account and
|
|
338
|
+
* disclosure, independently of shadow/diagnostics opt-ins.
|
|
339
|
+
* @param {{endpoint: string, token: string, disclosedAt?: string}} config
|
|
340
|
+
*/
|
|
341
|
+
export function claudeCheckpointConfigBinding(config) {
|
|
342
|
+
if (
|
|
343
|
+
!isRecord(config) ||
|
|
344
|
+
typeof config.endpoint !== 'string' ||
|
|
345
|
+
typeof config.token !== 'string' ||
|
|
346
|
+
typeof config.disclosedAt !== 'string'
|
|
347
|
+
)
|
|
348
|
+
throw new Error('claude_checkpoint_state_mismatch');
|
|
349
|
+
let endpoint;
|
|
350
|
+
try {
|
|
351
|
+
endpoint = new URL(config.endpoint).toString();
|
|
352
|
+
} catch {
|
|
353
|
+
throw new Error('claude_checkpoint_state_mismatch');
|
|
354
|
+
}
|
|
355
|
+
return createHash('sha256')
|
|
356
|
+
.update(`${endpoint}\u0000${config.token}\u0000${config.disclosedAt}`, 'utf8')
|
|
357
|
+
.digest('hex');
|
|
358
|
+
}
|
|
359
|
+
|
|
257
360
|
/** @param {string} path */
|
|
258
361
|
export async function readPrivateToken(path) {
|
|
259
362
|
const resolved = resolve(path);
|