@byokit/openclaw 0.16.3 → 0.16.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.16.5 (2026-10-18)
6
+
7
+ - Dependency update: pins @byokit/accounts 0.28.0.
8
+
9
+ ## 0.16.4 (2026-10-18)
10
+
11
+ FIX: Re-check the installed Claude login at every setup detection and switch out of fallback when it becomes signed in.
12
+
5
13
  ## 0.16.3 (2026-10-18)
6
14
 
7
15
  - Dependency update: pins @byokit/relay 0.8.2.
package/README.md CHANGED
@@ -387,7 +387,7 @@ streams, [`examples/openclaw-kit`](../../examples/openclaw-kit) for a whole app,
387
387
 
388
388
  Subscription sign-ins are offered by default. API-billed routes remain in `kit.routes()` with `offer: false`;
389
389
  apps can offer them when the app or person opts in, labelled API key (billed per use). Each `Route` carries `billing` (`subscription`, `api` or
390
- `local`) so the app can label what is billed. Proxy routes, compatibility aliases, local runtimes stay off. Native Claude Code sign-in is offered by default. The engine picks the installed Claude login when it starts; a person who signs in to Claude on that machine after the engine started restarts the engine to use it. A plan key (OpenCode Go: `kit.addAccount(member, { authChoice: 'opencode-go' }, on)`) asks for `Plan key`; `paste` it and the engine live-tests it before `done`. A paired device starts the same sign-in with either `via` and pastes the key over the sealed link, which keeps it on the host. A run reaches the pinned engine's static Go models (default `opencode-go/deepseek-v4-pro`); the kit keeps OpenCode Go's `x-opencode-session` header on every request, the setup probe included. A live-only Go model such as Muse needs no app config: the kit registers the plan's live catalog with each model's API, so `run({ model: 'opencode-go/muse-spark-1.3-contributor' })` runs on the account (or with `auth: 'apiKey'` on a Go key); an app that sets `config.models.providers['opencode-go'].models` keeps its own list. A choice the pinned Gateway cannot set up has readiness `no_upstream_flow` and is never offered. Pass the accounts the app offers in `KitOptions.offered` (the provider, auth choice or alias each route carries, e.g. `'openrouter'`); the kit allow-lists the plugin of every route those name, so an opted-in sign-in is not blocked. An app can still name a plugin directly in `config.plugins.allow`, and pass the route's `choice` explicitly to `kit.signIn()`.
390
+ `local`) so the app can label what is billed. Proxy routes, compatibility aliases, local runtimes stay off. Native Claude Code sign-in is offered by default. The kit re-checks the installed Claude login at each setup detection and, once it is signed in, restarts the engine onto it. A plan key (OpenCode Go: `kit.addAccount(member, { authChoice: 'opencode-go' }, on)`) asks for `Plan key`; `paste` it and the engine live-tests it before `done`. A paired device starts the same sign-in with either `via` and pastes the key over the sealed link, which keeps it on the host. A run reaches the pinned engine's static Go models (default `opencode-go/deepseek-v4-pro`); the kit keeps OpenCode Go's `x-opencode-session` header on every request, the setup probe included. A live-only Go model such as Muse needs no app config: the kit registers the plan's live catalog with each model's API, so `run({ model: 'opencode-go/muse-spark-1.3-contributor' })` runs on the account (or with `auth: 'apiKey'` on a Go key); an app that sets `config.models.providers['opencode-go'].models` keeps its own list. A choice the pinned Gateway cannot set up has readiness `no_upstream_flow` and is never offered. Pass the accounts the app offers in `KitOptions.offered` (the provider, auth choice or alias each route carries, e.g. `'openrouter'`); the kit allow-lists the plugin of every route those name, so an opted-in sign-in is not blocked. An app can still name a plugin directly in `config.plugins.allow`, and pass the route's `choice` explicitly to `kit.signIn()`.
391
391
  Offered provider plugins are allowed by default alongside the app's plugin ids. A route whose provider plugin ships outside the engine (Z.AI's GLM Coding Plan, for one) has readiness `needs_plugin` until the app calls `await kit.installPlugin(route.needs.plugin)`: it installs that plugin into the kit's own engine at exactly the engine's version and pinned npm integrity, keeps it allowed across restarts, and rejects `PluginInstallError` (`code` `version` or `integrity`) with nothing installed on a mismatch. Discovery never installs.
392
392
 
393
393
  ## API at a glance
@@ -748,4 +748,4 @@ frozen (docs/runtime-kits.md §5).
748
748
 
749
749
  Apache-2.0. See [LICENSE](LICENSE); the bundled engine's MIT text is [engine/OPENCLAW-LICENSE](engine/OPENCLAW-LICENSE).
750
750
 
751
- Claude: `anthropic-cli` uses your own unmodified Claude Code login on this machine (provider `claude-cli`, plugin `anthropic`; subscription billing; `kit.signedIn(member, 'claude-cli')` is true once Claude Code is signed in, and `'anthropic'` stays the API-billed provider a Claude Code login never satisfies. Before 0.7.0 this route reported provider `anthropic`; that value is deprecated, kept as `deprecatedProvider` until 0.9.0), and the engine uses the installed Claude Code’s own login as is: it runs with your `HOME` (and your own `CLAUDE_CONFIG_DIR` when you set one), so a signed-in Claude Code needs no sign-in step. Only when the installed `claude auth status --json` is not signed in does the engine use its own fallback folder `CLAUDE_CONFIG_DIR=<stateDir>/openclaw/home/.claude`; `kit.signIn(member, { authChoice: 'anthropic-cli' }, on)` then starts `claude auth login --claudeai` with that `CLAUDE_CONFIG_DIR` (in its own login HOME). When the status check gave no answer (a timeout, say), the engine keeps your own login location, and a sign-in it does not detect fails at once with “Claude Code is installed here, but its sign-in could not be checked. Run claude once on this machine to sign in, then try again.” Codex and the engine’s other agent CLIs likewise see your `HOME` and your own `CODEX_HOME`, never a kit-owned one. The gateway and doctor run with your `HOME` too; it is not an access boundary, since engine processes run with your account’s own rights. The waiting view shows the native sign-in address and asks for the returned code; send it through the same handle's `paste`, or `openclawDevice(link).signIn.paste('claude-cli', code)` on the phone. Cancel and expiry stop only this login. The `setup.activate` call is the one commit point: a phone cancel or engine shutdown before it (even with a ready native row) declines with nothing activated; after it a cancel changes nothing, and the result is `done` once the engine restart is confirmed, or says the sign-in was saved but the engine did not restart in time. Engine shutdown also ends that restart wait. An already-ready native row can be detected and activated without a new login. If engine detection disagrees, the stale ready Claude roster row is cleared through Accounts, which deletes that folder's Claude login files (`.credentials.json`, `.claude.json`) but keeps the folder, and one fresh login is started. Pass the installed Claude binary's directory in `enginePath`. For remote ChatGPT use `openai-device-code` with `via: 'code'`; it needs no localhost callback on the phone. Native sign-out is through Claude Code. `apiKey` is explicit API billing (“API key (billed per use)”, `offer: false`): `signIn` asks for the key through `paste`, then engine activation verifies it. The `setup-token` choice is listed but never offered: the pinned Gateway has no app-guided sign-in for it. The kit never imports Claude credentials or implements direct Claude.ai OAuth; [Anthropic’s terms](https://code.claude.com/docs/en/legal-and-compliance#authentication-and-credential-use) apply.
751
+ Claude: `anthropic-cli` uses your own unmodified Claude Code login on this machine (provider `claude-cli`, plugin `anthropic`; subscription billing; `kit.signedIn(member, 'claude-cli')` is true once Claude Code is signed in, and `'anthropic'` stays the API-billed provider a Claude Code login never satisfies. Before 0.7.0 this route reported provider `anthropic`; that value is deprecated, kept as `deprecatedProvider` until 0.9.0), and the engine uses the installed Claude Code’s own login as is: it runs with your `HOME` (and your own `CLAUDE_CONFIG_DIR` when you set one), so a signed-in Claude Code needs no sign-in step. Only when the installed `claude auth status --json` is not signed in does the engine use its own fallback folder `CLAUDE_CONFIG_DIR=<stateDir>/openclaw/home/.claude`; `kit.signIn(member, { authChoice: 'anthropic-cli' }, on)` then starts `claude auth login --claudeai` with that `CLAUDE_CONFIG_DIR` (in its own login HOME). When the status check gave no answer (a timeout, say), the engine keeps your own login location, and a sign-in it does not detect fails at once with “Claude Code is installed here, but its sign-in could not be checked. Run claude once on this machine to sign in, then try again.” For the installed Codex subscription, pass its binary directory in `enginePath` and run with `model: 'openai-codex/gpt-6-luna'` (or another Codex model). `kit.signedIn(member, 'openai-codex')` asks the installed `codex login status`; it is ready only for a ChatGPT subscription login. The pinned engine's supported CLI backend runs `codex exec` with your normal `HOME` and no `CODEX_HOME` override, never copies or links your login, and keeps its own config, sessions and workspace isolated. The gateway and doctor deliberately retain `CODEX_HOME=<stateDir>/openclaw/home/.codex` so the engine itself cannot import or refresh the installed login. Upstream's discovery-only CLI probes also use that isolated override; the actual model call does not. The Codex CLI owns its own sign-in and sign-out, with no API-key fallback. The gateway and doctor run with your `HOME` too; it is not an access boundary, since engine processes run with your account’s own rights. The waiting view shows the native sign-in address and asks for the returned code; send it through the same handle's `paste`, or `openclawDevice(link).signIn.paste('claude-cli', code)` on the phone. Cancel and expiry stop only this login. The `setup.activate` call is the one commit point: a phone cancel or engine shutdown before it (even with a ready native row) declines with nothing activated; after it a cancel changes nothing, and the result is `done` once the engine restart is confirmed, or says the sign-in was saved but the engine did not restart in time. Engine shutdown also ends that restart wait. An already-ready native row can be detected and activated without a new login. If engine detection disagrees, the stale ready Claude roster row is cleared through Accounts, which deletes that folder's Claude login files (`.credentials.json`, `.claude.json`) but keeps the folder, and one fresh login is started. Pass the installed Claude binary's directory in `enginePath`. For remote ChatGPT use `openai-device-code` with `via: 'code'`; it needs no localhost callback on the phone. Native sign-out is through Claude Code. `apiKey` is explicit API billing (“API key (billed per use)”, `offer: false`): `signIn` asks for the key through `paste`, then engine activation verifies it. The `setup-token` choice is listed but never offered: the pinned Gateway has no app-guided sign-in for it. The kit never imports Claude credentials or implements direct Claude.ai OAuth; [Anthropic’s terms](https://code.claude.com/docs/en/legal-and-compliance#authentication-and-credential-use) apply.
@@ -21,7 +21,7 @@ export declare function authStatus(request: GatewayTransport['request'], agentId
21
21
  * Witness only app-owned file metadata (absence included); changed/unreadable state and auth/lifecycle mutations recheck.
22
22
  * Remote revocation still comes from the real run's signed-out error (a local status read cannot prove it either).
23
23
  */
24
- export declare function createAuthStatus(root: string, request: GatewayTransport['request']): {
24
+ export declare function createAuthStatus(root: string, request: GatewayTransport['request'], codex?: () => Promise<boolean>): {
25
25
  read: (agentId: string, refresh?: boolean, native?: boolean, reuse?: boolean) => Promise<AuthStatus>;
26
26
  invalidate: () => void;
27
27
  };
@@ -19,7 +19,7 @@ async function nativeStatus(request, agentId, status) {
19
19
  * Witness only app-owned file metadata (absence included); changed/unreadable state and auth/lifecycle mutations recheck.
20
20
  * Remote revocation still comes from the real run's signed-out error (a local status read cannot prove it either).
21
21
  */
22
- export function createAuthStatus(root, request) {
22
+ export function createAuthStatus(root, request, codex) {
23
23
  const snapshots = new Map();
24
24
  let generation = 0;
25
25
  const invalidate = () => { generation++; snapshots.clear(); };
@@ -55,6 +55,11 @@ export function createAuthStatus(root, request) {
55
55
  snapshots.delete(key);
56
56
  snapshots.delete(`${agentId}:false`);
57
57
  const profiles = await authStatus(request, agentId, refresh);
58
+ if (codex) {
59
+ profiles.providers = (profiles.providers ?? []).filter(row => (typeof row === 'string' ? row : row.provider) !== 'openai-codex');
60
+ if (await codex())
61
+ profiles.providers.push({ provider: 'openai-codex', status: 'ok' });
62
+ }
58
63
  const status = native ? await nativeStatus(request, agentId, profiles) : profiles;
59
64
  if (before !== undefined && epoch === generation && witness(agentId) === before) {
60
65
  for (const [route, answer] of (native ? [[false, profiles], [true, status]] : [[false, profiles]])) {
package/dist/bridge.js CHANGED
@@ -37,6 +37,7 @@ export function writePlugin(dir, o) {
37
37
  id: o.id,
38
38
  name: 'BYOKit bridge',
39
39
  activation: { onStartup: true },
40
+ cliBackends: ['openai-codex'],
40
41
  contracts: { tools: o.tools.map((t) => t.name), ...(o.browser ? { agentToolResultMiddleware: ['openclaw', 'codex'] } : {}) },
41
42
  configSchema: { type: 'object', additionalProperties: false },
42
43
  };
package/dist/engine.d.ts CHANGED
@@ -69,7 +69,10 @@ export declare class Engine {
69
69
  };
70
70
  /** The engine's own shutdown signal; aborted by stop() and replaced by the next start(). */
71
71
  get stopSignal(): AbortSignal;
72
+ installedCodexSignedIn(): Promise<boolean>;
72
73
  private installedClaudeSignedOut;
74
+ /** Re-select the installed login on setup detection; only a confirmed login switches away from fallback. */
75
+ refreshInstalledClaudeLogin(): Promise<boolean>;
73
76
  /** The fallback Claude sign-in for a machine whose installed Claude Code is signed out: it signs into the engine's folder. */
74
77
  nativeClaudeLogin(): ReturnType<typeof remoteCliHost>;
75
78
  /** Whether a fallback sign-in would write a folder this engine's gateway does not read: it started on the person's
package/dist/engine.js CHANGED
@@ -207,7 +207,7 @@ export class Engine {
207
207
  putOnce(join(this.root, 'npmrc'), ''); // engine npm runs read this empty config, never the person's ~/.npmrc
208
208
  this.sweepAccountOrphans();
209
209
  if (this.o.spawnEngine)
210
- this.claudeFallback = await this.installedClaudeSignedOut();
210
+ this.claudeFallback = (await this.installedClaudeSignedOut()) ?? false;
211
211
  try {
212
212
  await this.authStore.prepare();
213
213
  this.credentialsLocked = false;
@@ -303,7 +303,7 @@ export class Engine {
303
303
  const pluginDir = join(this.root, 'plugin');
304
304
  mkdirSync(pluginDir, { recursive: true, mode: 0o700 });
305
305
  // The shipped plugin follows the kit on every prepare too: a state dir from an older kit must not keep its gate.
306
- for (const f of ['package.json', 'index.js', 'keys.js', 'usage.js', 'accounts.js', 'catalog.js']) {
306
+ for (const f of ['package.json', 'index.js', 'keys.js', 'usage.js', 'accounts.js', 'catalog.js', 'codex.js']) {
307
307
  const source = join(kitDir, 'plugin', f);
308
308
  putChanged(join(pluginDir, f), readFileSync(source, 'utf8'));
309
309
  }
@@ -348,6 +348,8 @@ export class Engine {
348
348
  return { entry: this.entry, env: {
349
349
  PATH: this.path, LANG: 'C.UTF-8', ...personal(), OPENCLAW_HOME: home,
350
350
  OPENCLAW_STATE_DIR: join(this.root, 'state'), OPENCLAW_CONFIG_PATH: join(this.root, 'openclaw.json'),
351
+ // Upstream eagerly imports/refreshes Codex OAuth. Only the CLI may see the person's login.
352
+ CODEX_HOME: join(home, '.codex'),
351
353
  XDG_CACHE_HOME: join(home, '.cache'), ...this.claudeFallback ? { CLAUDE_CONFIG_DIR: this.claudeFolder } : {},
352
354
  TMPDIR: join(this.root, 'tmp'), npm_config_cache: join(this.root, 'npm-cache'), npm_config_userconfig: join(this.root, 'npmrc'),
353
355
  OPENCLAW_NO_RESPAWN: '1', OPENCLAW_SKIP_CHANNELS: '1', OPENCLAW_DISABLE_BONJOUR: '1',
@@ -358,6 +360,18 @@ export class Engine {
358
360
  }
359
361
  /** The engine's own shutdown signal; aborted by stop() and replaced by the next start(). */
360
362
  get stopSignal() { return this.stopOwner.signal; }
363
+ async installedCodexSignedIn() {
364
+ if (!this.o.spawnEngine)
365
+ return false;
366
+ const env = { PATH: this.path, LANG: 'C.UTF-8', ...personal() };
367
+ delete env.CODEX_HOME;
368
+ const bin = onPath(env.PATH, 'codex');
369
+ if (!bin)
370
+ return false;
371
+ return new Promise(resolve => execFile(bin, ['login', 'status'], { env, timeout: 15_000, maxBuffer: 256 * 1024 }, (error, stdout, stderr) => {
372
+ resolve(!error && /^Logged in using ChatGPT\b/m.test(stdout + stderr));
373
+ }));
374
+ }
361
375
  installedClaudeSignedOut() {
362
376
  const env = { PATH: this.path, LANG: 'C.UTF-8', TMPDIR: join(this.root, 'tmp'), ...personal() };
363
377
  const bin = onPath(env.PATH, 'claude');
@@ -376,9 +390,19 @@ export class Engine {
376
390
  return resolve(true);
377
391
  }
378
392
  this.o.log?.(`claude: sign-in status unknown, using the person's own login location (${String(error?.message ?? 'unparseable output').slice(0, 200)})`);
379
- resolve(false);
393
+ resolve(undefined);
380
394
  }));
381
395
  }
396
+ /** Re-select the installed login on setup detection; only a confirmed login switches away from fallback. */
397
+ async refreshInstalledClaudeLogin() {
398
+ if (!this.o.spawnEngine || !this.claudeFallback)
399
+ return false;
400
+ const signedOut = await this.installedClaudeSignedOut();
401
+ if (signedOut !== false)
402
+ return false;
403
+ this.claudeFallback = false;
404
+ return true;
405
+ }
382
406
  /** The fallback Claude sign-in for a machine whose installed Claude Code is signed out: it signs into the engine's folder. */
383
407
  nativeClaudeLogin() {
384
408
  if (this.claudeLogin)
package/dist/kit.js CHANGED
@@ -679,7 +679,7 @@ export class OpenClawKit {
679
679
  ...(o.browser ? { browserConfig: () => this.browserConfig() } : {}),
680
680
  gateBuiltins: o.gateBuiltins !== false, spawnEngine: o.spawnEngine !== false,
681
681
  onState: (s) => this.setState(s), onExit: () => this.closed('engine exited') });
682
- this.admission = createAuthStatus(this.engine.root, (method, params, options) => this.request()(method, params, options));
682
+ this.admission = createAuthStatus(this.engine.root, (method, params, options) => this.request()(method, params, options), () => this.engine.installedCodexSignedIn());
683
683
  this.keys = createKeys({ root: this.engine.root,
684
684
  prepareOwner: (agentId) => this.prepareKeyOwner(agentId),
685
685
  indexLane: (member, route) => this.indexKeyLane(member, route),
@@ -997,7 +997,13 @@ export class OpenClawKit {
997
997
  const transport = this.transport;
998
998
  if (!transport || this.current.phase !== 'ready')
999
999
  return () => Promise.reject(new Error('gateway not ready'));
1000
- return (method, params, options) => {
1000
+ return async (method, params, options) => {
1001
+ // The gateway's own restart is in-process (OPENCLAW_NO_RESPAWN) and keeps CLAUDE_CONFIG_DIR: a new engine process drops it.
1002
+ if (method === 'openclaw.setup.detect' && await this.engine.refreshInstalledClaudeLogin()) {
1003
+ await this.stop();
1004
+ await this.start();
1005
+ return this.request()(method, params, options);
1006
+ }
1001
1007
  const changesAuth = /^(models\.authLogout|openclaw\.setup\.(auth\.start|activate)|wizard\.(next|cancel)|config\.(apply|patch|set)|agents\.(create|update|delete)|secrets\.(reload|store\.(set|delete))|byokit\.keys)$/.test(method);
1002
1008
  if (!changesAuth || (method === 'byokit.keys' && params?.action === 'ready'))
1003
1009
  return transport.request(method, params, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/openclaw",
3
- "version": "0.16.3",
3
+ "version": "0.16.5",
4
4
  "description": "The OpenClaw runtime kit: the pinned engine's full operator surface with plain-words helpers for members, sign-in, runs and approvals.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -60,7 +60,7 @@
60
60
  "prepack": "tsc -b && node ../../scripts/fix-words-dts.cjs"
61
61
  },
62
62
  "dependencies": {
63
- "@byokit/accounts": "0.27.1",
63
+ "@byokit/accounts": "0.28.0",
64
64
  "@byokit/pair": "0.10.4",
65
65
  "@byokit/relay": "0.8.2",
66
66
  "@byokit/discover": "0.8.4",
@@ -0,0 +1,28 @@
1
+ // Supported pinned-engine CLI backend: Codex owns its installed login; the gateway never imports it.
2
+ export function registerCodex(api) {
3
+ api.registerCliBackend({
4
+ id: 'openai-codex', modelProvider: 'openai',
5
+ authEpochMode: 'profile-only', autoSelectAuthProfile: false,
6
+ bundleMcp: true, bundleMcpMode: 'codex-config-overrides',
7
+ nativeToolMode: 'none', toolAvailabilityEnforcement: 'execution-args', sideQuestionToolMode: 'disabled',
8
+ config: {
9
+ command: 'codex', input: 'stdin', output: 'jsonl', modelArg: '--model', sessionMode: 'none',
10
+ args: ['exec', '--json', '--ephemeral', '--skip-git-repo-check', '--ignore-user-config', '--ignore-rules',
11
+ '--sandbox', 'read-only', '-c', 'forced_login_method="chatgpt"',
12
+ '-c', 'features.shell_tool=false', '-c', 'features.unified_exec=false', '-c', 'features.apps=false',
13
+ '-c', 'features.plugins=false', '-c', 'features.hooks=false', '-c', 'features.multi_agent=false',
14
+ '-c', 'features.image_generation=false', '-c', 'web_search="disabled"', '-c', 'project_doc_max_bytes=0', '-'],
15
+ clearEnv: ['CODEX_HOME', 'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'CODEX_OSS_BASE_URL'],
16
+ systemPromptFileConfigArg: '-c', systemPromptFileConfigKey: 'model_instructions_file', systemPromptWhen: 'always',
17
+ },
18
+ parseJsonlEvent(line) {
19
+ const event = JSON.parse(line);
20
+ if (event.type === 'item.completed' && event.item?.type === 'agent_message') return { kind: 'text', text: event.item.text };
21
+ if (event.type === 'turn.completed') return { kind: 'result', usage: {
22
+ input: event.usage?.input_tokens, output: event.usage?.output_tokens, cacheRead: event.usage?.cached_input_tokens,
23
+ } };
24
+ if (event.type === 'turn.failed' || event.type === 'error') return { kind: 'result', errorText: event.error?.message ?? event.message ?? 'Codex failed' };
25
+ return null;
26
+ },
27
+ });
28
+ }
package/plugin/index.js CHANGED
@@ -7,6 +7,7 @@ import { registerKeys } from './keys.js';
7
7
  import { registerUsage } from './usage.js';
8
8
  import { registerAccounts } from './accounts.js';
9
9
  import { registerCatalog } from './catalog.js';
10
+ import { registerCodex } from './codex.js';
10
11
 
11
12
  /** TypeBox's Kind is a global symbol, so these schemas are valid TypeBox without importing typebox. */
12
13
  const Kind = Symbol.for('TypeBox.Kind');
@@ -149,6 +150,7 @@ export default {
149
150
  jsonSchema: { type: 'object', additionalProperties: false, properties: {} },
150
151
  },
151
152
  register(api) {
153
+ registerCodex(api);
152
154
  if (api.registerGatewayMethod) { registerKeys(api); registerUsage(api); registerAccounts(api); registerCatalog(api); }
153
155
  if (BROWSER) {
154
156
  if (typeof api.registerAgentToolResultMiddleware !== 'function') throw new Error('browser result protection unavailable');