@byokit/openclaw 0.13.0 → 0.14.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.
package/dist/approvals.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Native approvals: exec.approval.*, plugin.approval.* and question.* surfaced through the same Approval shape (5.9, O5).
2
2
  import { randomBytes } from 'node:crypto';
3
- import { MEMBER_ID } from "./members.js";
3
+ import { MEMBER_ID } from './members.js';
4
4
  const strip = (a) => {
5
5
  const { native, ...approval } = a;
6
6
  void native;
@@ -1,6 +1,6 @@
1
1
  import { statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { signedInProviders, USABLE } from "./runs.js";
3
+ import { signedInProviders, USABLE } from './runs.js';
4
4
  /** Native Claude login stays in Claude Code. The pin's profile status does not include this synthetic auth;
5
5
  * setup detection asks the CLI itself, without handing credentials to the kit. */
6
6
  export async function authStatus(request, agentId, refresh = false, native = false) {
@@ -53,6 +53,8 @@ export declare class AuthStore {
53
53
  * and in `home` exactly the sealed paths — regenerable caches and the directories still holding them are left in place. */
54
54
  private remove;
55
55
  private sealObjects;
56
+ /** Re-attach held-back sealed paths a damaged index kept out of the live inventory. */
57
+ private mergeHeldBack;
56
58
  private persist;
57
59
  /** `mkdir -p` for a restore target that records the parent of every directory it creates: a new directory
58
60
  * changes its parent's entries, so the parent needs a directory fsync too. Stops at the store root (it exists)
@@ -7,8 +7,9 @@
7
7
  import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, writeFileSync, closeSync, fsyncSync, openSync } from 'node:fs';
8
8
  import { createHash } from 'node:crypto';
9
9
  import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
10
- import { EngineAlreadyRunningError, pidAlive as live } from "./engine-status.js";
11
- import { removedAccountAgents, sweepOrphans } from "./accounts.js";
10
+ import { EngineAlreadyRunningError, pidAlive as live } from './engine-status.js';
11
+ import { damagedAccountMembers, heldBackAgent, removedAccountAgents, sweepOrphans } from './accounts.js';
12
+ const agentDirOf = (path) => path.startsWith('state/agents/') ? path.split('/')[2] : undefined;
12
13
  const encoder = new TextEncoder();
13
14
  const archive = (name) => !name.endsWith('.sealed') && (/^(auth-profiles|auth-state|auth|oauth)\.json\.(migrated-.+|sqlite-import\..+\.bak)$/.test(name)
14
15
  || name.endsWith('.moved-to-engine'));
@@ -322,8 +323,12 @@ export class AuthStore {
322
323
  catch {
323
324
  throw new AuthStoreUnreadableError('invalid-snapshot');
324
325
  }
326
+ // Removed agents leave the sealed store; damaged-index agents stay sealed (held back) until their index is repaired.
325
327
  const gone = removedAccountAgents(this.o.root);
326
- const keep = (path) => !path.startsWith('state/agents/') || !gone.has(path.split('/')[2]);
328
+ const keep = (path) => {
329
+ const agent = agentDirOf(path);
330
+ return agent === undefined || !gone.has(agent);
331
+ };
327
332
  const kept = { v: 1, dirs: saved.dirs.filter(keep), files: saved.files.filter(([path]) => keep(path)) };
328
333
  if (kept.dirs.length !== saved.dirs.length || kept.files.length !== saved.files.length) {
329
334
  this.seal(this.file, JSON.stringify(kept));
@@ -375,10 +380,16 @@ export class AuthStore {
375
380
  let total = 0;
376
381
  const root = realpathSync(this.o.root);
377
382
  const gone = removedAccountAgents(this.o.root);
383
+ const damaged = damagedAccountMembers(this.o.root);
378
384
  const walk = (path) => {
379
385
  const name = relative(this.o.root, path).split('\\').join('/');
380
- if (name.startsWith('state/agents/') && gone.has(name.split('/')[2]))
381
- return; // a late refresh never enters a seal
386
+ if (name.startsWith('state/agents/')) {
387
+ const agent = name.split('/')[2];
388
+ if (gone.has(agent))
389
+ return; // a late refresh never enters a seal
390
+ if (heldBackAgent(agent, damaged))
391
+ return; // damaged index: leave sealed copy; never reseal a late live write
392
+ }
382
393
  if (name.startsWith('home/') && nonCredential(name))
383
394
  return; // regenerable home caches stay on disk; skipping a directory skips its subtree
384
395
  let stat;
@@ -449,7 +460,7 @@ export class AuthStore {
449
460
  return { dirs, files, objects };
450
461
  }
451
462
  collect() {
452
- sweepOrphans(this.o.root);
463
+ sweepOrphans(this.o.root, this.o.log);
453
464
  const { dirs, files, objects } = this.inventory();
454
465
  for (const dir of dirs) {
455
466
  try {
@@ -536,10 +547,48 @@ export class AuthStore {
536
547
  this.seal(target, payload);
537
548
  this.objectHashes.set(object, hash);
538
549
  }
550
+ // Held-back agent objects stay sealed while their index is damaged (inventory skipped their live trees).
551
+ const damaged = damagedAccountMembers(this.o.root);
552
+ if (damaged.size) {
553
+ for (const entry of readdirSync(this.objects)) {
554
+ if (keep.has(entry))
555
+ continue;
556
+ try {
557
+ const agent = agentDirOf(this.readObject(join(this.objects, entry))[0]);
558
+ if (agent !== undefined && heldBackAgent(agent, damaged))
559
+ keep.add(entry);
560
+ }
561
+ catch { /* unreadable object: leave for the next start's auth-store check */ }
562
+ }
563
+ }
539
564
  for (const entry of readdirSync(this.objects))
540
565
  if (!keep.has(entry))
541
566
  rmSync(join(this.objects, entry), { force: true });
542
567
  }
568
+ /** Re-attach held-back sealed paths a damaged index kept out of the live inventory. */
569
+ mergeHeldBack(previous, saved) {
570
+ if (!previous)
571
+ return;
572
+ const damaged = damagedAccountMembers(this.o.root);
573
+ if (!damaged.size)
574
+ return;
575
+ const held = (path) => {
576
+ const agent = agentDirOf(path);
577
+ return agent !== undefined && heldBackAgent(agent, damaged);
578
+ };
579
+ const dirs = new Set(saved.dirs);
580
+ for (const dir of previous.dirs)
581
+ if (held(dir) && !dirs.has(dir)) {
582
+ saved.dirs.push(dir);
583
+ dirs.add(dir);
584
+ }
585
+ const files = new Set(saved.files.map(([path]) => path));
586
+ for (const entry of previous.files)
587
+ if (held(entry[0]) && !files.has(entry[0])) {
588
+ saved.files.push(entry);
589
+ files.add(entry[0]);
590
+ }
591
+ }
543
592
  async persist() {
544
593
  if (!this.o.seal)
545
594
  return;
@@ -557,6 +606,7 @@ export class AuthStore {
557
606
  // A running engine always leaves files behind (its SQLite stores), so a genuine sign-out still re-seals.
558
607
  if (!saved.files.length && !objects.length && previous)
559
608
  return;
609
+ this.mergeHeldBack(previous, saved);
560
610
  // The payload is built exactly once; verification decrypts the sealed bytes and compares to it.
561
611
  this.seal(this.file, JSON.stringify(saved));
562
612
  if (previous?.dirs.some(nonCredential))
@@ -582,14 +632,25 @@ export class AuthStore {
582
632
  if (!this.o.seal)
583
633
  return;
584
634
  const saved = await this.read();
635
+ const damaged = damagedAccountMembers(this.o.root);
636
+ const skipHeld = (path) => {
637
+ const agent = agentDirOf(path);
638
+ return agent !== undefined && heldBackAgent(agent, damaged);
639
+ };
585
640
  // A crashed host may leave a newer live state; prepare seals that before restore.
586
641
  put(this.restoring, encoder.encode('1'));
587
642
  const changed = new Set();
588
643
  if (saved) {
589
- for (const dir of saved.dirs.sort((a, b) => a.split('/').length - b.split('/').length))
644
+ for (const dir of saved.dirs.sort((a, b) => a.split('/').length - b.split('/').length)) {
645
+ if (skipHeld(dir))
646
+ continue;
590
647
  this.ensureRestoreDirs(join(this.o.root, dir), changed);
591
- for (const [path, data] of saved.files)
648
+ }
649
+ for (const [path, data] of saved.files) {
650
+ if (skipHeld(path))
651
+ continue;
592
652
  putRestore(join(this.o.root, path), Buffer.from(data, 'base64'), changed);
653
+ }
593
654
  }
594
655
  if (existsSync(this.objects)) {
595
656
  for (const entry of readdirSync(this.objects)) {
@@ -598,6 +659,8 @@ export class AuthStore {
598
659
  // no hash, so the next seal reseals it in the current mode even when unchanged.
599
660
  if (!this.o.seal.upgrade?.(bytes))
600
661
  this.objectHashes.set(entry, objectHash(payload));
662
+ if (skipHeld(path))
663
+ continue;
601
664
  // An object path can be several directories below an existing one, and those directories are not in
602
665
  // `saved.dirs` (they sit in a non-credential subtree). Create them through ensureRestoreDirs so the
603
666
  // parent of every directory this restore creates — not just each object's own directory — is fsynced
@@ -667,7 +730,9 @@ export class AuthStore {
667
730
  // keeps the sealed credentials instead of replacing them with an empty snapshot.
668
731
  if (!saved.files.length && !objects.length && previous)
669
732
  return;
733
+ this.mergeHeldBack(previous, saved);
670
734
  this.seal(this.file, JSON.stringify(saved));
735
+ // Database objects stay at their last stop-time seal; only the credential blob is refreshed here.
671
736
  });
672
737
  }
673
738
  start() {
package/dist/bridge.js CHANGED
@@ -3,7 +3,7 @@ import { randomBytes } from 'node:crypto';
3
3
  import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { createServer } from 'node:net';
5
5
  import { rm } from 'node:fs/promises';
6
- import { words } from "./words.js";
6
+ import { words } from './words.js';
7
7
  /** Newline-framed JSON, one request per connection; anything larger is not a gate request. */
8
8
  const FRAME_CAP = 1_000_000;
9
9
  /**
@@ -1,8 +1,8 @@
1
1
  // Internal host runtime (5.17). Production handoff is deliberately unprotected until W7/O17 qualification.
2
2
  import { randomBytes } from 'node:crypto';
3
- import { BrowserStoreError, checkUrl, validOrigin } from "./store.js";
4
- import { validateVerifiers, verifySignIn } from "./verify.js";
5
- import { dispatchResume, newResume, recoverResume } from "./resume.js";
3
+ import { BrowserStoreError, checkUrl, validOrigin } from './store.js';
4
+ import { validateVerifiers, verifySignIn } from './verify.js';
5
+ import { dispatchResume, newResume, recoverResume } from './resume.js';
6
6
  export class SignInRefused extends Error {
7
7
  why;
8
8
  constructor(why) { super(`sign-in refused: ${why}`); this.why = why; }
@@ -1,4 +1,4 @@
1
- import { checkUrl, validOrigin } from "./store.js";
1
+ import { checkUrl, validOrigin } from './store.js';
2
2
  export function validateVerifiers(verifiers) {
3
3
  for (const v of verifiers) {
4
4
  validOrigin(v.origin);
package/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { dirname, join } from 'node:path';
2
- import { routes } from "./routes.js";
3
- import { MEMBER_ID, KEY_PREFIX, BASE_AGENT, keyAgentId } from "./members.js";
2
+ import { routes } from './routes.js';
3
+ import { MEMBER_ID, KEY_PREFIX, BASE_AGENT, keyAgentId } from './members.js';
4
4
  const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
5
5
  const merge = (a, b) => {
6
6
  for (const [k, v] of Object.entries(b))
@@ -1,4 +1,4 @@
1
1
  export declare const ENGINE_VERSION = "2026.8.35";
2
2
  export declare const PROTOCOL_VERSION = 4;
3
3
  export declare const LIVE_CATALOGS: string[];
4
- export declare const OPERATOR_SCOPES: readonly ["operator.read", "operator.write", "operator.admin", "operator.approvals", "operator.questions", "operator.pairing", "operator.talk"];
4
+ export declare const OPERATOR_SCOPES: readonly ['operator.read', 'operator.write', 'operator.admin', 'operator.approvals', 'operator.questions', 'operator.pairing', 'operator.talk'];
package/dist/day-usage.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Portable additive day reader. No host files, pricing guesses or side totals for persisted turns.
2
- import { agentUsageOf } from "./usage.js";
2
+ import { agentUsageOf } from './usage.js';
3
3
  const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
4
4
  const count = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0;
5
5
  const text = (v) => typeof v === 'string' && v.length > 0 && v.length <= 512;
package/dist/device.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { openBoxFromSeed, boxKeyPairFromSeed } from '@byokit/seal';
2
- import { memberOf } from "./attribution.js";
3
- import { toAccountView } from "./words.js";
4
- import { b64urlDecode, b64urlEncode, openNotice } from "./notices.js";
2
+ import { memberOf } from './attribution.js';
3
+ import { toAccountView } from './words.js';
4
+ import { b64urlDecode, b64urlEncode, openNotice } from './notices.js';
5
5
  /**
6
6
  * The host ended a stream with its own words (e.g. a refusal). Portable twin of link's PublicLinkError:
7
7
  * same shape ({ code: 'failed', sealed: true }) so a screen can show .message the same way. A device-side
@@ -418,8 +418,8 @@ export function openclawDevice(link) {
418
418
  };
419
419
  }
420
420
  // Portable, re-exported by ./device (7.3).
421
- export { openNotice } from "./notices.js";
422
- export { readAgentUsage, agentUsageOf } from "./usage.js";
423
- export { readAgentDayUsage } from "./day-usage.js";
421
+ export { openNotice } from './notices.js';
422
+ export { readAgentUsage, agentUsageOf } from './usage.js';
423
+ export { readAgentDayUsage } from './day-usage.js';
424
424
  // The kit's sentences, so a phone or browser shows the same words the computer does (5.14).
425
- export { stateWords, toAccountView, words } from "./words.js";
425
+ export { stateWords, toAccountView, words } from './words.js';
package/dist/engine.d.ts CHANGED
@@ -1,6 +1,10 @@
1
+ import { remoteCliHost } from '@byokit/accounts/cli';
1
2
  import { AuthStore, AuthStoreSealSizeError } from './auth-store.ts';
2
3
  import type { KitOptions } from './kit.ts';
3
4
  import type { KitState, ToolSpec } from './types.ts';
5
+ /** The app's stateDir as an absolute path with no symlinked ancestor (macOS /tmp and /var are symlinks):
6
+ * the native Claude login's accounts store refuses any other, so a relative or symlinked stateDir failed it. */
7
+ export declare function canonicalStateDir(dir: string): string;
4
8
  export type EngineOptions = Pick<KitOptions, 'stateDir' | 'engineDir' | 'npmPath' | 'enginePath' | 'config' | 'offered' | 'appOwnedSessions' | 'installPolicy' | 'log' | 'bridge' | 'authSeal'> & {
5
9
  pluginId: string;
6
10
  tools: ToolSpec[];
@@ -28,6 +32,10 @@ export declare class Engine {
28
32
  private starting?;
29
33
  readonly authStore: AuthStore;
30
34
  private stopping;
35
+ private stopOwner;
36
+ private claudeLogin?;
37
+ private claudeAccounts?;
38
+ private claudeReset?;
31
39
  private credentialsLocked;
32
40
  private repaired;
33
41
  private prepared?;
@@ -49,6 +57,10 @@ export declare class Engine {
49
57
  entry: string;
50
58
  env: Record<string, string>;
51
59
  };
60
+ /** The engine's own shutdown signal; aborted by stop() and replaced by the next start(). */
61
+ get stopSignal(): AbortSignal;
62
+ nativeClaudeLogin(): ReturnType<typeof remoteCliHost>;
63
+ resetNativeClaudeLogin(): Promise<void>;
52
64
  doctor(timeoutMs: number): {
53
65
  status: number | null;
54
66
  };
package/dist/engine.js CHANGED
@@ -1,18 +1,31 @@
1
1
  import { spawn, spawnSync } from 'node:child_process';
2
2
  import { randomBytes, generateKeyPairSync, randomUUID } from 'node:crypto';
3
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, copyFileSync, rmSync, realpathSync, statSync } from 'node:fs';
3
+ import { accessSync, constants, closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, copyFileSync, rmSync, realpathSync, statSync } from 'node:fs';
4
4
  import { createServer } from 'node:net';
5
- import { basename, join, dirname } from 'node:path';
5
+ import { basename, join, dirname, resolve } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { setTimeout as delay } from 'node:timers/promises';
8
- import { AuthStore, AuthStoreUnreadableError, AuthStoreSealSizeError } from "./auth-store.js";
9
- import { EngineAlreadyRunningError, pidAlive, StartedProcesses } from "./engine-status.js";
10
- import { EnginePatchError, atomic, prepareEngineSet, processStartTime, readPatchSet, sha256, verifyEngineSet } from "./engine-patches.js";
11
- import { ENGINE_VERSION } from "./constants.js";
12
- import { appendUsageBoot } from "./usage-boots.js";
13
- import { reconcileConfig, appRecoveryPrefixes } from "./config.js";
14
- import { writePlugin, resolveBridge } from "./bridge.js";
15
- import { sweepOrphans } from "./accounts.js";
8
+ import { cliAccounts, remoteCliHost } from '@byokit/accounts/cli';
9
+ import { AuthStore, AuthStoreUnreadableError, AuthStoreSealSizeError } from './auth-store.js';
10
+ import { EngineAlreadyRunningError, pidAlive, StartedProcesses } from './engine-status.js';
11
+ import { EnginePatchError, atomic, prepareEngineSet, processStartTime, readPatchSet, sha256, verifyEngineSet } from './engine-patches.js';
12
+ import { ENGINE_VERSION } from './constants.js';
13
+ import { appendUsageBoot } from './usage-boots.js';
14
+ import { reconcileConfig, appRecoveryPrefixes } from './config.js';
15
+ import { writePlugin, resolveBridge } from './bridge.js';
16
+ import { sweepOrphans } from './accounts.js';
17
+ /** The app's stateDir as an absolute path with no symlinked ancestor (macOS /tmp and /var are symlinks):
18
+ * the native Claude login's accounts store refuses any other, so a relative or symlinked stateDir failed it. */
19
+ export function canonicalStateDir(dir) {
20
+ const path = resolve(dir);
21
+ try {
22
+ return realpathSync(path);
23
+ }
24
+ catch {
25
+ const parent = dirname(path);
26
+ return parent === path ? path : join(canonicalStateDir(parent), basename(path));
27
+ }
28
+ }
16
29
  const kitDir = dirname(dirname(fileURLToPath(import.meta.url)));
17
30
  const putOnce = (path, data) => { if (!existsSync(path))
18
31
  writeFileSync(path, data, { mode: 0o600, flag: 'wx' }); };
@@ -76,6 +89,13 @@ export class Engine {
76
89
  starting;
77
90
  authStore;
78
91
  stopping = false;
92
+ // Aborted by stop(): a sign-in drive that has not reached its commit point then declines at once, the key paste
93
+ // included (it has no native host signal of its own). start() replaces it, so an earlier shutdown cannot decline
94
+ // a later drive.
95
+ stopOwner = new AbortController();
96
+ claudeLogin;
97
+ claudeAccounts;
98
+ claudeReset;
79
99
  credentialsLocked = false;
80
100
  repaired = false;
81
101
  prepared;
@@ -87,6 +107,7 @@ export class Engine {
87
107
  appOwnedSessionPrefixes;
88
108
  constructor(o) {
89
109
  this.appOwnedSessionPrefixes = appRecoveryPrefixes(o.appOwnedSessions);
110
+ o = { ...o, stateDir: canonicalStateDir(o.stateDir) };
90
111
  this.o = o;
91
112
  const bridge = resolveBridge(o.bridge);
92
113
  this.root = join(o.stateDir, 'openclaw');
@@ -285,6 +306,42 @@ export class Engine {
285
306
  BYOKIT_APP_OWNED_SESSION_PREFIXES: JSON.stringify(this.appOwnedSessionPrefixes),
286
307
  } };
287
308
  }
309
+ /** The engine's own shutdown signal; aborted by stop() and replaced by the next start(). */
310
+ get stopSignal() { return this.stopOwner.signal; }
311
+ nativeClaudeLogin() {
312
+ if (this.claudeLogin)
313
+ return this.claudeLogin;
314
+ const { env } = this.doctorContext();
315
+ const bin = env.PATH.split(':').map(path => join(path, 'claude')).find(path => { try {
316
+ accessSync(path, constants.X_OK);
317
+ return true;
318
+ }
319
+ catch {
320
+ return false;
321
+ } });
322
+ const bins = bin ? { claude: bin } : {};
323
+ // The login runs in its own HOME: CLAUDE_CONFIG_DIR points it at the engine's folder, which accounts refuses as a HOME's default login.
324
+ for (const dir of ['home', 'login-home'])
325
+ mkdirSync(join(this.root, dir), { recursive: true, mode: 0o700 });
326
+ const cli = cliAccounts({ stateDir: this.root, bins, env: { PATH: env.PATH, HOME: join(this.root, 'login-home'), LANG: env.LANG, TMPDIR: env.TMPDIR, XDG_CONFIG_HOME: env.XDG_CONFIG_HOME, XDG_CACHE_HOME: env.XDG_CACHE_HOME, XDG_DATA_HOME: env.XDG_DATA_HOME, XDG_STATE_HOME: env.XDG_STATE_HOME, }, managedFolders: { claude: env.CLAUDE_CONFIG_DIR } });
327
+ this.claudeAccounts = cli;
328
+ this.claudeLogin = remoteCliHost({ cli, bins });
329
+ return this.claudeLogin;
330
+ }
331
+ resetNativeClaudeLogin() {
332
+ if (this.claudeReset)
333
+ return this.claudeReset;
334
+ const cli = this.claudeAccounts, host = this.claudeLogin;
335
+ if (!cli || !host || host.signal.aborted)
336
+ return Promise.resolve();
337
+ this.claudeReset = (async () => { for (const row of await cli.list()) {
338
+ if (host.signal.aborted)
339
+ return;
340
+ if (row.provider === 'claude' && row.state === 'ready' && cli.folderOf(row.id) === join(this.root, 'home', '.claude'))
341
+ await cli.remove(row.id);
342
+ } })().finally(() => { this.claudeReset = undefined; });
343
+ return this.claudeReset;
344
+ }
288
345
  doctor(timeoutMs) {
289
346
  const { entry, env } = this.doctorContext();
290
347
  if (this.o.spawnEngine)
@@ -355,11 +412,12 @@ export class Engine {
355
412
  }
356
413
  // The account orphan sweep (5.15): a late engine write after a removal cannot be ruled out by a bounded check, so
357
414
  // prepare and the sealed restore remove each `<root>/state/agents/<member>--<hex6>` directory a removal recorded as
358
- // gone and no index names. No roster here: the gateway is down, and no add is in flight while preparing (an entry
359
- // is written before its agent is created, so an account being added is named).
415
+ // gone and no index names, and drop member-store auth profiles a removal recorded as `dropped`. No roster here: the
416
+ // gateway is down, and no add is in flight while preparing (an entry is written before its agent is created, so an
417
+ // account being added is named).
360
418
  sweepAccountOrphans() {
361
419
  try {
362
- sweepOrphans(this.root);
420
+ sweepOrphans(this.root, this.o.log);
363
421
  }
364
422
  catch (error) {
365
423
  this.o.log?.(`prepare: orphans are not swept: ${String(error.message).slice(0, 200)}`);
@@ -369,6 +427,7 @@ export class Engine {
369
427
  if (this.starting)
370
428
  return this.starting;
371
429
  this.stopping = false;
430
+ this.stopOwner = new AbortController();
372
431
  const pending = this.startOnce();
373
432
  this.starting = pending;
374
433
  void pending.finally(() => { if (this.starting === pending)
@@ -514,6 +573,11 @@ export class Engine {
514
573
  withAuthStore(task) { return this.authStore.offline(task); }
515
574
  async stop() {
516
575
  this.stopping = true;
576
+ this.stopOwner.abort();
577
+ await this.claudeLogin?.close();
578
+ await this.claudeReset?.catch(() => { });
579
+ this.claudeLogin = undefined;
580
+ this.claudeAccounts = undefined;
517
581
  await this.starting?.catch(() => { });
518
582
  const child = this.child;
519
583
  await this.started.terminate(delay);
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  export { ENGINE_VERSION, OPERATOR_SCOPES, PROTOCOL_VERSION } from './constants.ts';
2
- export type { Account, AccountId, AccountPick, AccountRef, Approval, CallOptions, Considered, Decision, Defaults, ModelInfo, MoveResult, PickWhy, Room, RoomSpan, RunSelection, SignInState, GatewayEventName, GatewayEventPayload, GatewayMethod, GatewayParams, GatewayResult, GatewayTransport, GateResult, Hello, KitState, KitEventName, KitEventPayload, BrowserPing, LearningCapture, LearningMode, Member, PlanWindow, Route, RunEnd, RunEvent, RunRef, RunSpec, OutputSchema, SchemaOutput, RunUsage, SignInOptions, SignInView, ToolHost, ToolSpec, } from './types.ts';
2
+ export type { Account, AccountId, AccountPick, AccountRef, Approval, CallOptions, Considered, Decision, Defaults, ModelInfo, MoveResult, PickWhy, Room, RoomSpan, RunSelection, SignInState, GatewayEventName, GatewayEventPayload, GatewayMethod, GatewayParams, GatewayResult, GatewayTransport, GateResult, Hello, KitState, KitEventName, KitEventPayload, BrowserPing, LearningCapture, LearningMode, Member, PlanWindow, Route, RunEnd, RunEvent, RunRef, RunSpec, OutputSchema, SchemaOutput, RunUsage, ThinkingAsked, ThinkingReport, ThinkingUsed, SignInOptions, SignInView, ToolHost, ToolSpec, } from './types.ts';
3
3
  export type { GatewayMethods } from './generated/methods.ts';
4
4
  export type { JsonValue } from './output.ts';
5
5
  export type { RouteFacts, RouteView } from './routes.ts';
6
6
  export { OpenClawKit, type KitOptions, type RetainedLogin } from './kit.ts';
7
- export type { AddKeyResult, KeyFailureReason } from './keys.ts';
7
+ export type { AddKeyHandle, AddKeyResult, KeyFailureReason } from './keys.ts';
8
8
  export { stateWords, toAccountView, words, type AccountView, type WordKey } from './words.ts';
9
9
  export { chooseAccount, consider, resolveSelection, type RoomOf } from './pick.ts';
10
10
  export { AuthStoreUnreadableError, AuthStoreSealSizeError } from './auth-store.ts';
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // @byokit/openclaw — the OpenClaw runtime kit, host side (docs/runtime-kits.md 5.3). Entries: `.` (this),
2
2
  // `./device` (portable), `./link` (Node host-side adapter), `./testing` (fakes and contract suite).
3
- export { ENGINE_VERSION, OPERATOR_SCOPES, PROTOCOL_VERSION } from "./constants.js";
4
- export { OpenClawKit } from "./kit.js";
5
- export { stateWords, toAccountView, words } from "./words.js";
6
- export { chooseAccount, consider, resolveSelection } from "./pick.js";
7
- export { AuthStoreUnreadableError, AuthStoreSealSizeError } from "./auth-store.js";
8
- export { EngineAlreadyRunningError } from "./engine-status.js";
9
- export { readAgentDayUsage } from "./day-usage.js";
10
- export { readAgentUsage, agentUsageOf } from "./usage.js";
3
+ export { ENGINE_VERSION, OPERATOR_SCOPES, PROTOCOL_VERSION } from './constants.js';
4
+ export { OpenClawKit } from './kit.js';
5
+ export { stateWords, toAccountView, words } from './words.js';
6
+ export { chooseAccount, consider, resolveSelection } from './pick.js';
7
+ export { AuthStoreUnreadableError, AuthStoreSealSizeError } from './auth-store.js';
8
+ export { EngineAlreadyRunningError } from './engine-status.js';
9
+ export { readAgentDayUsage } from './day-usage.js';
10
+ export { readAgentUsage, agentUsageOf } from './usage.js';
package/dist/keys.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type ActivationCtx } from './activation.ts';
2
+ import { type RouteView } from './routes.ts';
2
3
  import type { Member, ModelInfo } from './types.ts';
3
4
  export type KeyFailureReason = 'auth' | 'billing' | 'empty' | 'model' | 'network' | 'engine' | 'inference' | 'busy';
4
5
  export type AddKeyResult = {
@@ -7,6 +8,10 @@ export type AddKeyResult = {
7
8
  status: 'invalid' | 'not_included' | 'retry';
8
9
  reason: KeyFailureReason;
9
10
  };
11
+ /** The add resolves like a Promise; `cancel()` stops a pending add and clears any partial key (retry/busy). */
12
+ export type AddKeyHandle = Promise<AddKeyResult> & {
13
+ cancel(): void;
14
+ };
10
15
  export declare function createKeys(ctx: ActivationCtx & {
11
16
  root: string;
12
17
  ensure(member: Member): Promise<{
@@ -14,6 +19,12 @@ export declare function createKeys(ctx: ActivationCtx & {
14
19
  }>;
15
20
  restarting?(): boolean;
16
21
  prepareOwner(agentId: string): Promise<void>;
22
+ indexLane(member: Member, route: RouteView): Promise<void>;
23
+ dropLane(member: Member, provider?: string): Promise<void>;
24
+ backfillLane(member: Member, model: string | undefined): Promise<void>;
25
+ /** The provider config headers a live-catalog key's calls and probes must carry (`null` clears them),
26
+ * written through the kit's own config patch. The Go workspace header is the one user. */
27
+ setProviderHeaders(provider: string, headers: Record<string, string> | null): Promise<void>;
17
28
  }): {
18
29
  models: (member: Member, o: {
19
30
  authChoice: string;
@@ -23,12 +34,16 @@ export declare function createKeys(ctx: ActivationCtx & {
23
34
  authChoice: string;
24
35
  apiKey: string;
25
36
  model?: string;
26
- }): Promise<AddKeyResult>;
37
+ orgId?: string;
38
+ }): AddKeyHandle;
27
39
  ready(member: Member, o?: {
28
40
  once?: boolean;
29
41
  }): Promise<{
30
42
  agentId: string;
31
43
  model: string;
32
44
  } | undefined>;
33
- sweep(): Promise<boolean>;
45
+ remove(member: Member, o?: {
46
+ provider?: string;
47
+ }): Promise<void>;
48
+ sweep(clear: boolean): Promise<boolean>;
34
49
  };