@brunobracaioli/aibrain 0.3.0-beta.7 → 0.4.0-beta.2

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.
@@ -16,10 +16,11 @@ export const MAX_LOG_FILES = 3;
16
16
  const LOCK_WAIT_MS = 250;
17
17
  const LOCK_STALE_MS = 30_000;
18
18
  const ADMISSION_MS = 60 * 60 * 1000;
19
- export const ADAPTER_VERSION = '0.3.0-beta.7';
19
+ export const ADAPTER_VERSION = '0.4.0-beta.2';
20
20
  const MAX_DIAGNOSTIC_COUNT = 10_000_000;
21
21
  const MAX_DIAGNOSTIC_BYTES = 128 * 1024 * 1024;
22
22
  const CHECKPOINT_BACKENDS = new Set(['native_store', 'private_file']);
23
+ const PROVIDER_RESPONSE_FORMATS = new Set(['structured', 'text', 'both', 'missing']);
23
24
  const DIAGNOSTIC_REASONS = new Set([
24
25
  'all_recent',
25
26
  'goal_missing',
@@ -38,6 +39,27 @@ const DIAGNOSTIC_REASONS = new Set([
38
39
  'proof_expired',
39
40
  'proof_mismatch',
40
41
  'native_fallback_unconfirmed',
42
+ 'native_summary_proof_expired',
43
+ 'native_summary_host_view_mismatch',
44
+ 'native_summary_host_view_ambiguous',
45
+ 'native_summary_host_view_unavailable',
46
+ 'native_summary_note_read_failed',
47
+ 'native_summary_attachment_uncertain',
48
+ 'plan_session_mismatch',
49
+ 'plan_revision_mismatch',
50
+ 'plan_projection_mismatch',
51
+ 'plan_options_mismatch',
52
+ 'plan_expired',
53
+ 'plan_parse_envelope_invalid',
54
+ 'plan_parse_metadata_invalid',
55
+ 'plan_parse_decisions_invalid',
56
+ 'plan_parse_fitting_invalid',
57
+ 'plan_parse_stats_invalid',
58
+ 'plan_parse_response_too_large',
59
+ 'plan_parse_failed',
60
+ 'session_changed_during_planning',
61
+ 'snapshot_changed_during_planning',
62
+ 'historical_notes_changed_during_planning',
41
63
  ]);
42
64
  const ADMISSION_DETAILS = new Set(['state_budget_exceeded', 'shared_admission_denied']);
43
65
  const ADMISSION_REASONS = new Set([
@@ -80,6 +102,7 @@ const CHECKPOINT_ERRORS = new Set([
80
102
  const COMPACT_ERROR_CODES = new Set([
81
103
  'state_budget_exceeded',
82
104
  'shared_admission_exceeded',
105
+ 'budget_response_invalid',
83
106
  'provider_budget_exceeded',
84
107
  'provider_unavailable',
85
108
  'provider_auth_denied',
@@ -113,6 +136,8 @@ const DIAGNOSTIC_STAGES = new Set([
113
136
  'fallback',
114
137
  'compact_entry',
115
138
  'compact_exit',
139
+ 'native_summary_proof',
140
+ 'native_fallback_proof',
116
141
  'native_error',
117
142
  ]);
118
143
  const DIAGNOSTIC_CATEGORIES = new Set([
@@ -370,9 +395,19 @@ function isSafeLogRecord(record) {
370
395
  'request_id',
371
396
  'correlation_id',
372
397
  'retry_after_ms',
398
+ 'proof_expected_count',
399
+ 'proof_actual_count',
400
+ 'proof_match',
401
+ 'proof_match_offset',
402
+ 'proof_view_source',
403
+ 'proof_api_view_match',
404
+ 'proof_api_count',
405
+ 'proof_delivery_source',
406
+ 'proof_delivery_match_count',
373
407
  'byte_count',
374
408
  'char_count',
375
409
  'rpc_status',
410
+ 'provider_response_format',
376
411
  'would_keep',
377
412
  'applied',
378
413
  'provider_confirmed',
@@ -422,10 +457,26 @@ function isSafeLogRecord(record) {
422
457
  !Number.isSafeInteger(record.retry_after_ms) ||
423
458
  record.retry_after_ms < 0 ||
424
459
  record.retry_after_ms > 3_600_000)) ||
460
+ (record.proof_match !== undefined &&
461
+ (typeof record.proof_match !== 'string' ||
462
+ !['exact', 'active_suffix', 'mismatch', 'ambiguous'].includes(record.proof_match))) ||
463
+ (record.proof_view_source !== undefined &&
464
+ (typeof record.proof_view_source !== 'string' ||
465
+ !['default', 'api'].includes(record.proof_view_source))) ||
466
+ (record.proof_api_view_match !== undefined &&
467
+ (typeof record.proof_api_view_match !== 'string' ||
468
+ !['exact', 'mismatch', 'unavailable', 'not_checked'].includes(
469
+ record.proof_api_view_match,
470
+ ))) ||
471
+ (record.proof_delivery_source !== undefined &&
472
+ (typeof record.proof_delivery_source !== 'string' ||
473
+ !['atomic_result', 'legacy_append'].includes(record.proof_delivery_source))) ||
425
474
  (record.hook_version !== undefined && !validVersion(record.hook_version)) ||
426
475
  (record.checkpoint_backend !== undefined &&
427
476
  (typeof record.checkpoint_backend !== 'string' ||
428
- !CHECKPOINT_BACKENDS.has(record.checkpoint_backend)))
477
+ !CHECKPOINT_BACKENDS.has(record.checkpoint_backend))) ||
478
+ (record.provider_response_format !== undefined &&
479
+ !PROVIDER_RESPONSE_FORMATS.has(String(record.provider_response_format)))
429
480
  )
430
481
  return false;
431
482
  for (const key of [
@@ -441,6 +492,11 @@ function isSafeLogRecord(record) {
441
492
  'projection_total',
442
493
  'selected_count',
443
494
  'deferred_count',
495
+ 'proof_expected_count',
496
+ 'proof_actual_count',
497
+ 'proof_match_offset',
498
+ 'proof_api_count',
499
+ 'proof_delivery_match_count',
444
500
  ]) {
445
501
  const value = record[key];
446
502
  const lowerBound = key === 'rpc_status' ? -100_000 : 0;
@@ -733,8 +789,18 @@ function diagnosticRecord(record) {
733
789
  'request_id',
734
790
  'correlation_id',
735
791
  'retry_after_ms',
792
+ 'proof_expected_count',
793
+ 'proof_actual_count',
794
+ 'proof_match',
795
+ 'proof_match_offset',
796
+ 'proof_view_source',
797
+ 'proof_api_view_match',
798
+ 'proof_api_count',
799
+ 'proof_delivery_source',
800
+ 'proof_delivery_match_count',
736
801
  'byte_count',
737
802
  'rpc_status',
803
+ 'provider_response_format',
738
804
  'reason',
739
805
  ]);
740
806
  if (
@@ -745,7 +811,23 @@ function diagnosticRecord(record) {
745
811
  !MODES.has(String(record.mode)) ||
746
812
  !validVersion(record.client_version) ||
747
813
  !ISO.test(String(record.observed_at)) ||
748
- !Number.isFinite(Date.parse(String(record.observed_at)))
814
+ !Number.isFinite(Date.parse(String(record.observed_at))) ||
815
+ (record.proof_match !== undefined &&
816
+ (typeof record.proof_match !== 'string' ||
817
+ !['exact', 'active_suffix', 'mismatch', 'ambiguous'].includes(record.proof_match))) ||
818
+ (record.proof_view_source !== undefined &&
819
+ (typeof record.proof_view_source !== 'string' ||
820
+ !['default', 'api'].includes(record.proof_view_source))) ||
821
+ (record.proof_api_view_match !== undefined &&
822
+ (typeof record.proof_api_view_match !== 'string' ||
823
+ !['exact', 'mismatch', 'unavailable', 'not_checked'].includes(
824
+ record.proof_api_view_match,
825
+ ))) ||
826
+ (record.proof_delivery_source !== undefined &&
827
+ (typeof record.proof_delivery_source !== 'string' ||
828
+ !['atomic_result', 'legacy_append'].includes(record.proof_delivery_source))) ||
829
+ (record.provider_response_format !== undefined &&
830
+ !PROVIDER_RESPONSE_FORMATS.has(String(record.provider_response_format)))
749
831
  )
750
832
  throw new Error('invalid_input');
751
833
  /** @type {Record<string, unknown>} */
@@ -824,6 +906,25 @@ function diagnosticRecord(record) {
824
906
  throw new Error('invalid_input');
825
907
  output.retry_after_ms = record.retry_after_ms;
826
908
  }
909
+ for (const key of ['proof_expected_count', 'proof_actual_count', 'proof_match_offset']) {
910
+ const value = record[key];
911
+ if (
912
+ value !== undefined &&
913
+ (typeof value !== 'number' ||
914
+ !Number.isSafeInteger(value) ||
915
+ value < 0 ||
916
+ value > MAX_DIAGNOSTIC_COUNT)
917
+ )
918
+ throw new Error('invalid_input');
919
+ if (value !== undefined) output[key] = value;
920
+ }
921
+ if (record.proof_match !== undefined) output.proof_match = String(record.proof_match);
922
+ if (record.proof_view_source !== undefined)
923
+ output.proof_view_source = String(record.proof_view_source);
924
+ if (record.proof_api_view_match !== undefined)
925
+ output.proof_api_view_match = String(record.proof_api_view_match);
926
+ if (record.proof_delivery_source !== undefined)
927
+ output.proof_delivery_source = String(record.proof_delivery_source);
827
928
  if (record.hook_version !== undefined) {
828
929
  if (!validVersion(record.hook_version)) throw new Error('invalid_input');
829
930
  output.hook_version = record.hook_version;
@@ -836,6 +937,14 @@ function diagnosticRecord(record) {
836
937
  throw new Error('invalid_input');
837
938
  output.checkpoint_backend = record.checkpoint_backend;
838
939
  }
940
+ if (record.provider_response_format !== undefined) {
941
+ if (
942
+ typeof record.provider_response_format !== 'string' ||
943
+ !PROVIDER_RESPONSE_FORMATS.has(record.provider_response_format)
944
+ )
945
+ throw new Error('invalid_input');
946
+ output.provider_response_format = record.provider_response_format;
947
+ }
839
948
  for (const key of [
840
949
  'duration_ms',
841
950
  'item_count',
@@ -847,6 +956,11 @@ function diagnosticRecord(record) {
847
956
  'projection_total',
848
957
  'selected_count',
849
958
  'deferred_count',
959
+ 'proof_expected_count',
960
+ 'proof_actual_count',
961
+ 'proof_match_offset',
962
+ 'proof_api_count',
963
+ 'proof_delivery_match_count',
850
964
  ]) {
851
965
  const numeric = record[key];
852
966
  if (numeric !== undefined) {
package/dist/cli.mjs CHANGED
@@ -15,21 +15,38 @@ import {
15
15
  stateRoot,
16
16
  tokenDigest,
17
17
  } from './state.mjs';
18
- import { isTestedNativeVersion, nativeVersion, platformCheck, resolveNative } from './platform.mjs';
18
+ import {
19
+ captureLockPlatformCheck,
20
+ nativeVersion,
21
+ platformCheck,
22
+ resolveNative,
23
+ } from './platform.mjs';
19
24
  import { launchClaude, launchCodex } from './launch.mjs';
25
+ import { claudeCompatibilityError, resolveClaudeCompatibility } from './claude-compatibility.mjs';
26
+ import { codexCompatibilityError, resolveCodexCompatibility } from './codex-compatibility.mjs';
20
27
  import { providerFreeProbe } from './mcp.mjs';
21
28
  import { codexPolicyJson, makePolicy, publicPolicy, isProfile } from './policy.mjs';
22
29
  import { readDiagnostics } from './claude-observation.mjs';
23
30
  import { readCodexDiagnostics } from './codex-diagnostics.mjs';
31
+ import { checkEngineeringLicense, licenseLine, unavailableLicense } from './engineering.mjs';
32
+ import {
33
+ isPackLocale,
34
+ readLastLaunch,
35
+ readStoredLocale,
36
+ resolvePackLocale,
37
+ writeStoredLocale,
38
+ } from './engineering-pack.mjs';
24
39
 
25
- const VERSION = '0.3.0-beta.7';
40
+ const VERSION = '0.4.0-beta.2';
41
+ // The command is `aisac`; `aibrain` is an alias installed from the same entry.
42
+ const COMMAND = 'aisac';
26
43
  const ENDPOINT = 'https://mcp.ai-brain.aisac.cloud/mcp';
27
44
  const PACKAGE_ROOT = dirname(fileURLToPath(import.meta.url));
28
45
  const LEGACY_DISCLOSURE_VERSION = 'aibrain-paid-context-v1-2026-10-09';
29
46
 
30
47
  function usage() {
31
48
  console.log(
32
- `aibrain ${VERSION}\n\nUsage:\n aibrain setup [--client claude|codex|both] [--context-profile upstream|conservative]\n [--compact-at 1..95] [--keep-threshold 0..1] [--preserve-recent 1..100]\n [--min-reduction 0..1] [--truncate-head-chars 0..4096]\n [--max-state-tokens 1000..25000] [--max-request-tokens 1000..30000]\n [--tool-policy compatible|read_only] [--goal TEXT]\n aibrain doctor [--json]\n aibrain status [--json]\n aibrain disable\n aibrain setup --undo\n aibrain claude [-- native-args...]\n aibrain codex [-- native-args...]\n\nPolicy notes:\n Automatic monitoring asks for compaction at compact-at percent; state/request\n limits are estimated upper bounds and do not override server quotas.\n min-reduction, truncate-head-chars and tool-policy apply only to Claude;\n Codex keeps native compaction and same-session recovery.`,
49
+ `${COMMAND} ${VERSION}\n\nUsage:\n ${COMMAND} setup [--client claude|codex|both] [--context-profile upstream|conservative]\n [--compact-at 1..95] [--keep-threshold 0..1] [--preserve-recent 1..100]\n [--min-reduction 0..1] [--truncate-head-chars 0..4096]\n [--max-state-tokens 1000..25000] [--max-request-tokens 1000..30000]\n [--tool-policy compatible|read_only] [--goal TEXT]\n ${COMMAND} doctor [--json]\n ${COMMAND} status [--json]\n ${COMMAND} disable\n ${COMMAND} setup --undo\n ${COMMAND} claude [-- native-args...]\n ${COMMAND} codex [-- native-args...]\n\nPolicy notes:\n Automatic monitoring asks for compaction at compact-at percent; state/request\n limits are estimated upper bounds and do not override server quotas.\n min-reduction, truncate-head-chars and tool-policy apply only to Claude;\n Codex keeps native compaction and same-session recovery.`,
33
50
  );
34
51
  console.log(
35
52
  'Telemetry: setup --telemetry on|off (omitted preserves the stored choice; fresh defaults off).',
@@ -37,11 +54,14 @@ function usage() {
37
54
  console.log(
38
55
  'Claude shadow: setup --claude-shadow on|off and --claude-diagnostics on|off (preview stays applied=false).',
39
56
  );
57
+ console.log(
58
+ 'AISAC Engineering: `aisac claude` loads the licensed method; setup --lang pt-BR|en picks its language; setup and doctor report the license; status stays offline. `aibrain` is an alias of `aisac`.',
59
+ );
40
60
  }
41
61
 
42
62
  // Telemetry is opt-in; setup omits the flag to preserve the stored choice.
43
63
  function version() {
44
- console.log(`aibrain ${VERSION}`);
64
+ console.log(`${COMMAND} ${VERSION}`);
45
65
  }
46
66
 
47
67
  async function maskedPrompt() {
@@ -297,6 +317,26 @@ async function updateLocalOptOut(previous, args) {
297
317
  return true;
298
318
  }
299
319
 
320
+ /** Store the AISAC Engineering pack language; local only, no network call. @param {'pt-BR' | 'en'} locale */
321
+ async function setPackLocale(locale) {
322
+ const root = await ensureStateRoot();
323
+ await writeStoredLocale(root, locale);
324
+ console.log(
325
+ `AISAC Engineering pack language set to ${locale} for future launches; no network call was made.`,
326
+ );
327
+ }
328
+
329
+ /** Offline view of the pack language and the last launch (status, doctor). */
330
+ async function engineeringPackState() {
331
+ const root = stateRoot();
332
+ const stored = await readStoredLocale(root);
333
+ return {
334
+ locale: resolvePackLocale(stored),
335
+ localeSource: stored === null ? 'system' : 'setup',
336
+ lastLaunch: await readLastLaunch(root),
337
+ };
338
+ }
339
+
300
340
  /** @param {string} endpoint */
301
341
  function validateEndpoint(endpoint) {
302
342
  if (endpoint.length > 2048) throw new Error('endpoint_too_long');
@@ -372,6 +412,7 @@ async function setup(args) {
372
412
  '--telemetry',
373
413
  '--claude-shadow',
374
414
  '--claude-diagnostics',
415
+ '--lang',
375
416
  ]),
376
417
  new Set([
377
418
  '--client',
@@ -383,10 +424,14 @@ async function setup(args) {
383
424
  '--telemetry',
384
425
  '--claude-shadow',
385
426
  '--claude-diagnostics',
427
+ '--lang',
386
428
  ]),
387
429
  );
388
430
  if (args.includes('--undo') && args.length !== 1)
389
431
  throw new Error('setup_undo_cannot_have_options');
432
+ const lang = option(args, '--lang');
433
+ if (lang !== undefined && !isPackLocale(lang)) throw new Error('invalid_lang');
434
+ if (lang !== undefined && args.length === 2) return setPackLocale(lang);
390
435
  const previous = await readConfig();
391
436
  if (await updateLocalOptOut(previous, args)) return;
392
437
  const localOptOut =
@@ -437,14 +482,20 @@ async function setup(args) {
437
482
  if ((clients === 'claude' || clients === 'both') && !claude) throw new Error('claude_not_found');
438
483
  if ((clients === 'codex' || clients === 'both') && (!codex || !node))
439
484
  throw new Error('codex_or_node_not_found');
440
- const claudeVersion = claude ? nativeVersion(claude) : null;
441
- const codexVersion = codex ? nativeVersion(codex) : null;
442
- const claudeSupported = claudeVersion !== null && isTestedNativeVersion('claude', claudeVersion);
443
- const codexSupported = codexVersion !== null && isTestedNativeVersion('codex', codexVersion);
444
- if ((clients === 'claude' || clients === 'both') && !claudeSupported)
445
- throw new Error('claude_version_unverified');
446
- if ((clients === 'codex' || clients === 'both') && !codexSupported)
447
- throw new Error('codex_version_unverified');
485
+ const claudeCompatibility =
486
+ (clients === 'claude' || clients === 'both') && claude !== null
487
+ ? await resolveClaudeCompatibility(claude, PACKAGE_ROOT)
488
+ : null;
489
+ if (claudeCompatibility?.status === 'rejected')
490
+ throw new Error(claudeCompatibilityError(claudeCompatibility));
491
+ const claudeVersion = claudeCompatibility?.version ?? (claude ? nativeVersion(claude) : null);
492
+ let codexCompatibility = null;
493
+ if (clients === 'codex' || clients === 'both') {
494
+ codexCompatibility = codex ? await resolveCodexCompatibility(codex) : null;
495
+ if (codexCompatibility?.status === 'rejected')
496
+ throw new Error(codexCompatibilityError(codexCompatibility));
497
+ }
498
+ const codexVersion = codexCompatibility?.version ?? (codex ? nativeVersion(codex) : null);
448
499
  const config = {
449
500
  schemaVersion: selection.v2 ? 2 : 1,
450
501
  packageVersion: VERSION,
@@ -482,6 +533,10 @@ async function setup(args) {
482
533
  console.log(
483
534
  `Configured ${clients} in ${root}. Provider-free access verified; no provider batch was used.`,
484
535
  );
536
+ if (lang !== undefined) await setPackLocale(lang);
537
+ // Display only: the server decides again on every pack request (ADR-0047).
538
+ const line = licenseLine(await checkEngineeringLicense(endpoint, token));
539
+ if (line !== null) console.log(line);
485
540
  }
486
541
 
487
542
  /** @param {boolean} json */
@@ -509,19 +564,36 @@ async function doctor(json) {
509
564
  codex: resolveNative('codex'),
510
565
  node: resolveNative('node'),
511
566
  };
512
- const nativeVersions = {
513
- claude: native.claude ? nativeVersion(native.claude) : '',
567
+ const claudeCompatibility =
568
+ native.claude !== null ? await resolveClaudeCompatibility(native.claude, PACKAGE_ROOT) : null;
569
+ const codexCompatibility =
570
+ native.codex !== null ? await resolveCodexCompatibility(native.codex) : null;
571
+ const rawNativeVersions = {
572
+ claude: claudeCompatibility?.version ?? '',
514
573
  codex: native.codex ? nativeVersion(native.codex) : '',
515
574
  };
575
+ const nativeVersions = {
576
+ ...rawNativeVersions,
577
+ codex: codexCompatibility?.version ?? rawNativeVersions.codex,
578
+ };
579
+ // Optional capability: its state never changes doctor's exit code.
580
+ const engineering = config
581
+ ? await checkEngineeringLicense(config.endpoint, config.token)
582
+ : unavailableLicense('not_configured');
516
583
  const needsClaude = config?.clientSelection === 'claude' || config?.clientSelection === 'both';
517
584
  const needsCodex = config?.clientSelection === 'codex' || config?.clientSelection === 'both';
585
+ const captureLockPlatform = needsCodex ? captureLockPlatformCheck() : null;
518
586
  const nativeReady =
519
587
  (!needsClaude ||
520
- (native.claude !== null && isTestedNativeVersion('claude', nativeVersions.claude))) &&
588
+ (native.claude !== null &&
589
+ (claudeCompatibility?.status === 'qualified' ||
590
+ claudeCompatibility?.status === 'host_checked'))) &&
521
591
  (!needsCodex ||
522
592
  (native.codex !== null &&
523
593
  native.node !== null &&
524
- isTestedNativeVersion('codex', nativeVersions.codex)));
594
+ captureLockPlatform?.ok === true &&
595
+ (codexCompatibility?.status === 'qualified' ||
596
+ codexCompatibility?.status === 'protocol_match')));
525
597
  const result = {
526
598
  version: VERSION,
527
599
  platform: platform.ok ? process.platform : platform.code,
@@ -535,6 +607,16 @@ async function doctor(json) {
535
607
  node: native.node !== null,
536
608
  },
537
609
  nativeVersions,
610
+ claudeCompatibility: claudeCompatibility?.status ?? 'unavailable',
611
+ claudeCompatibilityReason: claudeCompatibility?.reason ?? 'claude_not_found',
612
+ claudeCompatibilityVersion: claudeCompatibility?.version ?? null,
613
+ codexCompatibility: codexCompatibility?.status ?? 'unavailable',
614
+ codexCompatibilityReason: codexCompatibility?.reason ?? 'codex_not_found',
615
+ codexCompatibilityVersion: codexCompatibility?.version ?? null,
616
+ captureLockBackend:
617
+ captureLockPlatform?.ok === true
618
+ ? 'available'
619
+ : (captureLockPlatform?.code ?? 'not_required'),
538
620
  nativeReady,
539
621
  context: publicPolicy(policy),
540
622
  telemetry: {
@@ -549,6 +631,8 @@ async function doctor(json) {
549
631
  config?.claudeObservation?.disclosedAt !== null &&
550
632
  config?.claudeObservation?.disclosedAt !== undefined,
551
633
  },
634
+ engineering,
635
+ engineeringPack: await engineeringPackState(),
552
636
  paidOrPilot: probe?.ok
553
637
  ? 'provider_free_probe_ok; paid entitlement or pilot invitation remains server-enforced'
554
638
  : config
@@ -584,6 +668,7 @@ async function status(json) {
584
668
  config?.claudeObservation?.disclosedAt !== null &&
585
669
  config?.claudeObservation?.disclosedAt !== undefined,
586
670
  },
671
+ engineeringPack: await engineeringPackState(),
587
672
  monitoredSession: null,
588
673
  monitoringStatus: 'not_observed',
589
674
  };
@@ -643,17 +728,9 @@ async function main(argv) {
643
728
  throw new Error('disclosure_outdated');
644
729
  if (config.clientSelection !== command && config.clientSelection !== 'both')
645
730
  throw new Error('client_not_configured');
646
- if (
647
- command === 'claude' &&
648
- (typeof config.claudeVersion !== 'string' ||
649
- !isTestedNativeVersion('claude', config.claudeVersion))
650
- )
651
- throw new Error('claude_version_unverified');
652
- if (
653
- command === 'codex' &&
654
- (typeof config.codexVersion !== 'string' ||
655
- !isTestedNativeVersion('codex', config.codexVersion))
656
- )
731
+ // The native executable is authoritative. The stored setup version is
732
+ // metadata only, so patch updates do not block a fresh compatibility check.
733
+ if (command === 'codex' && typeof config.codexVersion !== 'string')
657
734
  throw new Error('codex_version_unverified');
658
735
  const root = stateRoot();
659
736
  const result = await (command === 'claude'
@@ -688,7 +765,7 @@ main(cliArgs).catch((error) => {
688
765
  }),
689
766
  );
690
767
  } else {
691
- console.error(`aibrain: ${errorMessage(error)}`);
768
+ console.error(`${COMMAND}: ${errorMessage(error)}`);
692
769
  }
693
770
  process.exitCode = 1;
694
771
  });