@byokit/openclaw 0.16.4 → 0.16.6

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.6 (2026-10-18)
6
+
7
+ - SECURITY: Run `openai-codex/<model>` through the installed Codex CLI on the ChatGPT subscription using the pinned engine's supported CLI backend; keep engine state isolated and prevent the engine from importing or refreshing the installed Codex login.
8
+
9
+ ## 0.16.5 (2026-10-18)
10
+
11
+ - Dependency update: pins @byokit/accounts 0.28.0.
12
+
5
13
  ## 0.16.4 (2026-10-18)
6
14
 
7
15
  FIX: Re-check the installed Claude login at every setup detection and switch out of fallback when it becomes signed in.
package/README.md CHANGED
@@ -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,6 +69,7 @@ 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;
73
74
  /** Re-select the installed login on setup detection; only a confirmed login switches away from fallback. */
74
75
  refreshInstalledClaudeLogin(): Promise<boolean>;
package/dist/engine.js CHANGED
@@ -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');
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),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/openclaw",
3
- "version": "0.16.4",
3
+ "version": "0.16.6",
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');