@brunobracaioli/aibrain 0.4.0-beta.7 → 0.4.0-beta.9

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 CHANGED
@@ -58,7 +58,7 @@ notes or lock files to work around a reported storage error.
58
58
 
59
59
  The reviewed native compatibility set is Claude Code 2.1.294, 2.1.295, 2.1.296,
60
60
  2.1.299, 2.1.300, 2.1.301, 2.1.302 or 2.1.303, and Codex CLI 0.161.0,
61
- 0.162.0, 0.162.1, 0.163.0 or 0.164.0. A newer stable Codex 0.x release may continue only
61
+ 0.162.0, 0.162.1, 0.163.0, 0.164.0 or 0.166.0 (0.165.0 was never qualified). A newer stable Codex 0.x release may continue only
62
62
  after its locally generated experimental protocol schemas and transport flags
63
63
  match the bundled adapter contract; prereleases, older releases and new majors
64
64
  remain rejected. Starting with beta14, newer stable Claude 2.1 patches can
@@ -74,6 +74,11 @@ record formats. Native launch/resume qualification is separate from a new
74
74
  model/JEV compaction qualification. A later native release that changes the
75
75
  consumed contract can still require an AI Brain compatibility update.
76
76
 
77
+ `0.4.0-beta.9` refreshes the bundled contract to Codex 0.166.0. Its delta is
78
+ additive: optional item timing, an image generation id, an attach-to-source fork
79
+ flag and two error codes. See the
80
+ [Codex 0.166 qualification record](../../docs/reference/codex-166-compatibility-2026-11-04.md).
81
+
77
82
  The Codex terminal bridge permits the native session picker's separate read-only
78
83
  connection while keeping the primary conversation and monitor intact. A picker
79
84
  cannot compact, start a turn, inject history or take ownership of the primary
@@ -16,7 +16,7 @@ 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.4.0-beta.7';
19
+ export const ADAPTER_VERSION = '0.4.0-beta.9';
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']);
package/dist/cli.mjs CHANGED
@@ -40,7 +40,7 @@ import { extensionsSummary, hooksCommand, mcpCommand } from './extensions-cli.mj
40
40
  import { sessionServers } from './mcp-servers.mjs';
41
41
  import { anyFormatter } from './guardrails/format.mjs';
42
42
 
43
- const VERSION = '0.4.0-beta.7';
43
+ const VERSION = '0.4.0-beta.9';
44
44
  // The command is `aisac`; `aibrain` is an alias installed from the same entry.
45
45
  const COMMAND = 'aisac';
46
46
  const ENDPOINT = 'https://mcp.ai-brain.aisac.cloud/mcp';
@@ -26,7 +26,14 @@ const PAGINATION_CONTRACT_VALID =
26
26
  fingerprint.roots[PAGINATION_CONTRACT.response_schema]?.sha256 &&
27
27
  typeof PAGINATION_CONTRACT.graph_sha256 === 'string' &&
28
28
  PAGINATION_CONTRACT.graph_sha256.length === 64);
29
- const QUALIFIED_VERSIONS = new Set(['0.161.0', '0.162.0', '0.162.1', '0.163.0', '0.164.0']);
29
+ const QUALIFIED_VERSIONS = new Set([
30
+ '0.161.0',
31
+ '0.162.0',
32
+ '0.162.1',
33
+ '0.163.0',
34
+ '0.164.0',
35
+ '0.166.0',
36
+ ]);
30
37
  const ANNOTATIONS = new Set([
31
38
  '$comment',
32
39
  'title',
@@ -475,7 +482,50 @@ export async function resolveCodexCompatibility(executable, baseEnvironment = pr
475
482
  }
476
483
  }
477
484
 
485
+ /** @param {string} left @param {string} right */
486
+ function compareStableVersions(left, right) {
487
+ const a = left.split('.').map(Number);
488
+ const b = right.split('.').map(Number);
489
+ for (let index = 0; index < 3; index += 1) {
490
+ const difference = (a[index] ?? 0) - (b[index] ?? 0);
491
+ if (difference !== 0) return difference;
492
+ }
493
+ return 0;
494
+ }
495
+
496
+ /** The newest Codex release this client was qualified against. */
497
+ export const LATEST_QUALIFIED_CODEX = /** @type {string} */ (
498
+ [...QUALIFIED_VERSIONS].sort(compareStableVersions).at(-1)
499
+ );
500
+
501
+ // Rejections a user fixes by installing a qualified Codex (or a newer aisac):
502
+ // the installed release's contract or transport differs from the bundled one.
503
+ const CONTRACT_REJECTIONS = new Set([
504
+ 'protocol_schema_mismatch',
505
+ 'protocol_method_missing',
506
+ 'schema_root_missing_or_invalid',
507
+ 'schema_set_too_large',
508
+ 'schema_generation_failed',
509
+ 'transport_flags_missing',
510
+ 'protocol_probe_failed',
511
+ ]);
512
+
513
+ /** @param {{reason:string, version:string|null}} decision @returns {string|null} */
514
+ export function codexCompatibilityRemedy(decision) {
515
+ const detected = decision.version === null ? 'The installed Codex' : `Codex ${decision.version}`;
516
+ const reinstall = `npm install -g @openai/codex@${LATEST_QUALIFIED_CODEX}`;
517
+ if (CONTRACT_REJECTIONS.has(decision.reason))
518
+ return `${detected} is not qualified by this aisac (newest qualified: ${LATEST_QUALIFIED_CODEX}). Install the qualified Codex with \`${reinstall}\`, or check for an aisac release that qualifies it with \`npm install -g @aisac/cli@beta\`.`;
519
+ if (decision.reason === 'version_unqualified')
520
+ return `${detected} is older than this aisac supports. Update it with \`${reinstall}\`.`;
521
+ if (decision.reason === 'version_not_stable_0x')
522
+ return `${detected} is not a stable 0.x release. Install a qualified one with \`${reinstall}\`.`;
523
+ return null;
524
+ }
525
+
478
526
  /** @param {{reason:string, version:string|null}} decision @returns {string} */
479
527
  export function codexCompatibilityError(decision) {
480
- return `codex_protocol_incompatible:${decision.reason}:detected=${decision.version ?? 'unavailable'};run aisac doctor --json`;
528
+ const coded = `codex_protocol_incompatible:${decision.reason}:detected=${decision.version ?? 'unavailable'};run aisac doctor --json`;
529
+ const remedy = codexCompatibilityRemedy(decision);
530
+ return remedy === null ? coded : `${coded}\n${remedy}`;
481
531
  }
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "version": 3,
3
- "native": "0.164.0",
3
+ "native": "0.166.0",
4
4
  "consumed_pagination": {
5
5
  "method": "thread/turns/list",
6
6
  "request_schema": "v2/ThreadTurnsListParams.json",
7
7
  "response_schema": "v2/ThreadTurnsListResponse.json",
8
8
  "request_schema_sha256": "29717980579b77eb4c7cbd1118dd238fcb3e010f3ef5236d74bd4e4f6e02c005",
9
- "response_schema_sha256": "f46dddf14b7b6e8e484cb998a7d0b2efa3250167b9f2c366b83b22fe494c250f",
10
- "graph_sha256": "20ba733654372954440b0cbee2964230934d064b9c2c3ee3c4ed850b08f7e370",
9
+ "response_schema_sha256": "53d96dae44edcecee02d8dcce5645800a52a62444f288ff48376e99e506561c6",
10
+ "graph_sha256": "a9008170cbf190054a75f4a539db7fe271e30cf85a157ad27e2dc2192785f311",
11
11
  "ordering": "asc",
12
12
  "items_view": "full"
13
13
  },
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "v2/ThreadTurnsListResponse.json": {
20
20
  "methods": null,
21
- "sha256": "f46dddf14b7b6e8e484cb998a7d0b2efa3250167b9f2c366b83b22fe494c250f"
21
+ "sha256": "53d96dae44edcecee02d8dcce5645800a52a62444f288ff48376e99e506561c6"
22
22
  },
23
23
  "ClientRequest.json": {
24
24
  "methods": [
@@ -39,7 +39,7 @@
39
39
  "config/read",
40
40
  "account/read"
41
41
  ],
42
- "sha256": "415beba5414aeb1af34954de5c3d76c0f5a1c78f30f0a51cb291c471e920b365"
42
+ "sha256": "950bcefcb938bdf04b84305fdcf6767b05db782a0853ef1356fe05d607b0f5b3"
43
43
  },
44
44
  "ClientNotification.json": {
45
45
  "methods": ["initialized"],
@@ -78,7 +78,7 @@
78
78
  "thread/compacted",
79
79
  "warning"
80
80
  ],
81
- "sha256": "590a474f19639e9c1b57238354c89ab0ef5f9d4022561ba2ed5f286cdf254181"
81
+ "sha256": "7f739a3a39a248692147f1a5b2212f440d36350bf2bcca0a5393abd32c188bdf"
82
82
  },
83
83
  "JSONRPCResponse.json": {
84
84
  "methods": null,
@@ -106,7 +106,7 @@
106
106
  },
107
107
  "v2/ThreadForkResponse.json": {
108
108
  "methods": null,
109
- "sha256": "b1e26ab55f9fcbe108d325491d29678040d0776953093626fbcd9608593df31c"
109
+ "sha256": "36af2650a2f2fddd081dd6cb92a91632d0fc12e819fb47ceadcaf238990bec1e"
110
110
  },
111
111
  "v2/ThreadInjectItemsResponse.json": {
112
112
  "methods": null,
@@ -114,15 +114,15 @@
114
114
  },
115
115
  "v2/ThreadReadResponse.json": {
116
116
  "methods": null,
117
- "sha256": "2de20539e09dd7110bd5b705f519288329bacc162d1c14c0c4af33ff27085e24"
117
+ "sha256": "136c873857d2ba0e1aa0836d96ec8d81bfc30a9209e058226e910e0179563e6b"
118
118
  },
119
119
  "v2/ThreadResumeResponse.json": {
120
120
  "methods": null,
121
- "sha256": "bf6130a2dd9494a878e406f2d13535930ebc56f1a66cc1f597528537652a6a65"
121
+ "sha256": "ea37ce77b7a1b2fff78191dac117013ef817cfe1f0ab04f95d1bdc847ceacbb9"
122
122
  },
123
123
  "v2/ThreadStartResponse.json": {
124
124
  "methods": null,
125
- "sha256": "b1e26ab55f9fcbe108d325491d29678040d0776953093626fbcd9608593df31c"
125
+ "sha256": "36af2650a2f2fddd081dd6cb92a91632d0fc12e819fb47ceadcaf238990bec1e"
126
126
  }
127
127
  },
128
128
  "requiredClientFlags": ["app-server", "--listen", "stdio://"],
@@ -167,6 +167,10 @@ export function hookIds() {
167
167
  * @property {Readonly<Record<CatalogLocale, string>>} [loginNote] Why and how this login differs.
168
168
  * @property {Readonly<Record<CatalogLocale, string>>} description
169
169
  * @property {Readonly<Record<CatalogLocale, string>>} reaches What the server can reach (FR7).
170
+ * @property {readonly string[]} [blockedTools] Tools that spend money, removed in both
171
+ * clients whatever the user's permission mode is (FR7.2). Exact names.
172
+ * @property {readonly string[]} [blockedToolPrefixes] Name prefixes removed in Claude Code
173
+ * only, through a glob deny rule; Codex documents exact names only (FR7.2).
170
174
  */
171
175
 
172
176
  /** Session name of a catalog server, so it never collides with the user's own. */
@@ -215,6 +219,9 @@ export const MCP_CATALOG = Object.freeze([
215
219
  source: 'https://github.com/supabase-community/supabase-mcp',
216
220
  transport: { type: 'http', url: 'https://mcp.supabase.com/mcp?read_only=true' },
217
221
  login: 'host-oauth',
222
+ // read_only=true should already leave these out; the vendor does not publish
223
+ // what it removes, so they are blocked as a second layer (FR7.2).
224
+ blockedTools: ['create_project', 'confirm_cost', 'create_branch'],
218
225
  description: {
219
226
  en: "Supabase's hosted server, read-only: tables, SQL, logs, advisors and docs.",
220
227
  'pt-BR':
@@ -234,15 +241,19 @@ export const MCP_CATALOG = Object.freeze([
234
241
  source: 'https://vercel.com/docs/agent-resources/vercel-mcp',
235
242
  transport: { type: 'http', url: 'https://mcp.vercel.com' },
236
243
  login: 'host-oauth',
244
+ // buy_domain charges the team after a quote, and its confirm flag is a model
245
+ // argument; a domain transfer may be charged (FR7.1, FR7.2).
246
+ blockedTools: ['buy_domain', 'create_or_transfer_domain'],
247
+ blockedToolPrefixes: ['buy_'],
237
248
  description: {
238
249
  en: "Vercel's hosted server: projects, deployments, logs, domains and environment variables.",
239
250
  'pt-BR':
240
251
  'Servidor hospedado da Vercel: projetos, deploys, logs, domínios e variáveis de ambiente.',
241
252
  },
242
253
  reaches: {
243
- en: 'The teams you grant at login, with the access of your Vercel user: projects, deployments, build and runtime logs, domains and DNS, and environment variables, including decrypted values. It also has write tools (deploy, change settings, DNS records) and no read-only mode, so keep the confirmation of each call on.',
254
+ en: 'The teams you grant at login, with the access of your Vercel user: projects, deployments, build and runtime logs, domains and DNS, and environment variables, including decrypted values. It also has write tools (deployments, environment variables, DNS records, firewall rules, project settings) and no read-only mode, so keep the confirmation of each call on. Buying domains is blocked by AISAC.',
244
255
  'pt-BR':
245
- 'Os times que você liberar no login, com o acesso do seu usuário Vercel: projetos, deploys, logs de build e de execução, domínios e DNS, e variáveis de ambiente, inclusive valores decifrados. Também tem ferramentas de escrita (deploy, configurações, registros DNS) e não tem modo somente leitura, então mantenha a confirmação de cada chamada ligada.',
256
+ 'Os times que você liberar no login, com o acesso do seu usuário Vercel: projetos, deploys, logs de build e de execução, domínios e DNS, e variáveis de ambiente, inclusive valores decifrados. Também tem ferramentas de escrita (deploys, variáveis de ambiente, registros DNS, regras de firewall, configurações de projeto) e não tem modo somente leitura, então mantenha a confirmação de cada chamada ligada. A compra de domínios é bloqueada pela AISAC.',
246
257
  },
247
258
  },
248
259
  {
@@ -273,6 +273,11 @@ export function serverInfo(entry, locale, item) {
273
273
  note: 'Hosted by the vendor: its tools can change without a client release, so keep the confirmation of each call on.',
274
274
  },
275
275
  reaches: entry.reaches[locale],
276
+ // Tools that spend money, removed whatever the permission mode is (FR7.2).
277
+ blockedTools: [...(entry.blockedTools ?? [])],
278
+ ...(entry.blockedToolPrefixes === undefined
279
+ ? {}
280
+ : { blockedToolPrefixes: [...entry.blockedToolPrefixes] }),
276
281
  login,
277
282
  ...(variable === undefined ? {} : { tokenVariable: variable }),
278
283
  ...(entry.loginNote === undefined ? {} : { loginNote: entry.loginNote[locale] }),
@@ -293,6 +298,16 @@ function printInfo(info) {
293
298
  const where = info.transport.type === 'local' ? info.transport.command : info.transport.url;
294
299
  console.log(`Transport: ${info.transport.type}, ${where}. ${info.transport.note}`);
295
300
  console.log(`Reaches: ${info.reaches}`);
301
+ if (info.blockedTools.length > 0) {
302
+ const prefixes = info.blockedToolPrefixes ?? [];
303
+ const claudeOnly =
304
+ prefixes.length === 0
305
+ ? ''
306
+ : ` In Claude Code, also any tool whose name starts with ${prefixes.join(', ')}.`;
307
+ console.log(
308
+ `Blocked by AISAC, because they spend money: ${info.blockedTools.join(', ')}.${claudeOnly} They stay blocked even if you allow every tool or skip permission prompts.`,
309
+ );
310
+ }
296
311
  if (info.loginNote !== undefined) console.log(`Login: ${info.loginNote}`);
297
312
  console.log(`Log in from Claude Code: ${info.login.claude}`);
298
313
  console.log(`Log in from Codex: ${info.login.codex}`);
package/dist/launch.mjs CHANGED
@@ -19,7 +19,13 @@ import {
19
19
  writeLaunchMarker,
20
20
  } from './engineering-pack.mjs';
21
21
  import { prepareGuardrails } from './guardrails-plugin.mjs';
22
- import { CODEX_MCP_ENV, claudeMcpEntries, mcpLaunchLines, sessionServers } from './mcp-servers.mjs';
22
+ import {
23
+ CODEX_MCP_ENV,
24
+ claudeDenyRules,
25
+ claudeMcpEntries,
26
+ mcpLaunchLines,
27
+ sessionServers,
28
+ } from './mcp-servers.mjs';
23
29
  import { enabledServerIds, readPreferences } from './extensions-preferences.mjs';
24
30
  import {
25
31
  CODEX_ENGINEERING_ENV,
@@ -561,6 +567,14 @@ export async function launchClaude(config, root, args, dependencies = {}) {
561
567
  for (const rule of [...userAllow, ...requiredAllow]) {
562
568
  if (!mergedAllow.includes(rule)) mergedAllow.push(rule);
563
569
  }
570
+ // Catalog tools that spend money stay denied whatever the user allows or the
571
+ // permission mode is (spec aisac-engineering-extensions FR7.2).
572
+ const userDeny = Array.isArray(userPermissions.deny) ? userPermissions.deny : [];
573
+ /** @type {unknown[]} */
574
+ const mergedDeny = [];
575
+ for (const rule of [...userDeny, ...claudeDenyRules()]) {
576
+ if (!mergedDeny.includes(rule)) mergedDeny.push(rule);
577
+ }
564
578
  const settings = {
565
579
  ...(launchArgs.settings?.sandbox === undefined ? {} : { sandbox: launchArgs.settings.sandbox }),
566
580
  pluginConfigs: {
@@ -570,6 +584,7 @@ export async function launchClaude(config, root, args, dependencies = {}) {
570
584
  permissions: {
571
585
  ...userPermissions,
572
586
  allow: mergedAllow,
587
+ deny: mergedDeny,
573
588
  },
574
589
  };
575
590
  try {
@@ -8,7 +8,7 @@
8
8
  * third-party credential: logins live in the host's own store, and a token
9
9
  * variable is only named, never read here.
10
10
  */
11
- import { MCP_SERVER_PREFIX, mcpById } from './extensions-catalog.mjs';
11
+ import { MCP_CATALOG, MCP_SERVER_PREFIX, mcpById } from './extensions-catalog.mjs';
12
12
  import { tomlBasicString } from './codex-engineering.mjs';
13
13
 
14
14
  /** @typedef {import('./extensions-catalog.mjs').McpEntry} McpEntry */
@@ -80,7 +80,27 @@ export function claudeMcpEntries(entries) {
80
80
  }
81
81
 
82
82
  /**
83
- * Session-only `-c` overrides for the owned Codex app-server.
83
+ * Claude Code deny rules for the tools that spend money (FR7.2). They cover every
84
+ * catalog server, loaded or not, so the settings file can be written before the
85
+ * license is confirmed; a rule for a server that is not loaded matches nothing.
86
+ * Claude evaluates deny rules before allow rules, in every permission mode, and a
87
+ * deny rule removes the tool from the model's context (measured M1, Claude 2.1.318).
88
+ * @param {readonly McpEntry[]} [entries]
89
+ * @returns {string[]}
90
+ */
91
+ export function claudeDenyRules(entries = MCP_CATALOG) {
92
+ return entries.flatMap((entry) => {
93
+ const prefix = `mcp__${serverName(entry.id)}__`;
94
+ return [
95
+ ...(entry.blockedTools ?? []).map((tool) => `${prefix}${tool}`),
96
+ ...(entry.blockedToolPrefixes ?? []).map((start) => `${prefix}${start}*`),
97
+ ];
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Session-only `-c` overrides for the owned Codex app-server. A server with
103
+ * blocked tools gets `disabled_tools` (FR7.2; measured M2, Codex 0.164.0).
84
104
  * @param {readonly McpEntry[]} entries
85
105
  * @returns {string[]}
86
106
  */
@@ -88,6 +108,11 @@ export function codexMcpConfigArgs(entries) {
88
108
  return entries.flatMap((entry) => {
89
109
  const key = `mcp_servers.${serverName(entry.id)}`;
90
110
  const { transport } = entry;
111
+ const blocked = entry.blockedTools ?? [];
112
+ const disabled =
113
+ blocked.length === 0
114
+ ? []
115
+ : ['-c', `${key}.disabled_tools=[${blocked.map(tomlBasicString).join(',')}]`];
91
116
  if (transport.type === 'stdio')
92
117
  return [
93
118
  '-c',
@@ -96,6 +121,7 @@ export function codexMcpConfigArgs(entries) {
96
121
  `${key}.args=[${transport.args.map(tomlBasicString).join(',')}]`,
97
122
  '-c',
98
123
  `${key}.startup_timeout_sec=${STDIO_STARTUP_TIMEOUT_SEC}`,
124
+ ...disabled,
99
125
  ];
100
126
  return [
101
127
  '-c',
@@ -103,6 +129,7 @@ export function codexMcpConfigArgs(entries) {
103
129
  ...(transport.bearerEnv === undefined
104
130
  ? []
105
131
  : ['-c', `${key}.bearer_token_env_var=${tomlBasicString(transport.bearerEnv)}`]),
132
+ ...disabled,
106
133
  ];
107
134
  });
108
135
  }
package/dist/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@brunobracaioli/aibrain",
3
- "version": "0.4.0-beta.7",
3
+ "version": "0.4.0-beta.9",
4
4
  "type": "module"
5
5
  }
package/dist/platform.mjs CHANGED
@@ -86,7 +86,7 @@ export function isTestedNativeVersion(client, version) {
86
86
  return /^(?:(?:claude(?:\s+code)?)\s+)?2\.1\.(?:294|295|296|299|300|301|302|303)(?:\s+\(Claude\s+Code\))?$/iu.test(
87
87
  value,
88
88
  );
89
- return /^(?:codex(?:-cli)?\s+)?0\.16(?:1\.0|2\.0|2\.1|3\.0|4\.0)$/iu.test(value);
89
+ return /^(?:codex(?:-cli)?\s+)?0\.16(?:1\.0|2\.0|2\.1|3\.0|4\.0|6\.0)$/iu.test(value);
90
90
  }
91
91
 
92
92
  const TESTED_NATIVE_VERSIONS = Object.freeze({
@@ -100,7 +100,7 @@ const TESTED_NATIVE_VERSIONS = Object.freeze({
100
100
  '2.1.302',
101
101
  '2.1.303',
102
102
  ]),
103
- codex: Object.freeze(['0.161.0', '0.162.0', '0.162.1', '0.163.0', '0.164.0']),
103
+ codex: Object.freeze(['0.161.0', '0.162.0', '0.162.1', '0.163.0', '0.164.0', '0.166.0']),
104
104
  });
105
105
 
106
106
  /** @param {'claude'|'codex'} client @param {string} version */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brunobracaioli/aibrain",
3
- "version": "0.4.0-beta.7",
3
+ "version": "0.4.0-beta.9",
4
4
  "private": false,
5
5
  "description": "AISAC client for Claude Code and Codex: AI Brain memory, context management and AISAC Engineering",
6
6
  "license": "UNLICENSED",