@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/CHANGELOG.md +40 -0
- package/README.md +29 -12
- package/SECURITY.md +6 -2
- package/dist/accounts.d.ts +31 -1
- package/dist/accounts.js +433 -101
- package/dist/approvals.js +1 -1
- package/dist/auth-status.js +1 -1
- package/dist/auth-store.d.ts +2 -0
- package/dist/auth-store.js +73 -8
- package/dist/bridge.js +1 -1
- package/dist/browser/host.js +3 -3
- package/dist/browser/verify.js +1 -1
- package/dist/config.js +2 -2
- package/dist/constants.d.ts +1 -1
- package/dist/day-usage.js +1 -1
- package/dist/device.js +7 -7
- package/dist/engine.d.ts +12 -0
- package/dist/engine.js +77 -13
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -8
- package/dist/keys.d.ts +17 -2
- package/dist/keys.js +133 -64
- package/dist/kit.d.ts +15 -3
- package/dist/kit.js +89 -36
- package/dist/link.js +17 -11
- package/dist/migrate.js +3 -3
- package/dist/pick.js +5 -1
- package/dist/runs.js +28 -11
- package/dist/signin.d.ts +4 -0
- package/dist/signin.js +85 -27
- package/dist/testing/browser.js +2 -2
- package/dist/testing/contract.js +3 -3
- package/dist/testing/fake-gateway.js +28 -5
- package/dist/testing/index.js +4 -4
- package/dist/testing/model-stub.d.ts +4 -0
- package/dist/testing/model-stub.js +8 -1
- package/dist/thinking.d.ts +16 -0
- package/dist/thinking.js +62 -0
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/words.json +5 -1
- package/package.json +6 -5
- package/plugin/keys.js +3 -2
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
|
|
3
|
+
import { MEMBER_ID } from './members.js';
|
|
4
4
|
const strip = (a) => {
|
|
5
5
|
const { native, ...approval } = a;
|
|
6
6
|
void native;
|
package/dist/auth-status.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { signedInProviders, USABLE } from
|
|
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) {
|
package/dist/auth-store.d.ts
CHANGED
|
@@ -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)
|
package/dist/auth-store.js
CHANGED
|
@@ -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
|
|
11
|
-
import { removedAccountAgents, sweepOrphans } from
|
|
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) =>
|
|
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/')
|
|
381
|
-
|
|
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
|
-
|
|
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
|
|
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
|
/**
|
package/dist/browser/host.js
CHANGED
|
@@ -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
|
|
4
|
-
import { validateVerifiers, verifySignIn } from
|
|
5
|
-
import { dispatchResume, newResume, recoverResume } from
|
|
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; }
|
package/dist/browser/verify.js
CHANGED
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from 'node:path';
|
|
2
|
-
import { routes } from
|
|
3
|
-
import { MEMBER_ID, KEY_PREFIX, BASE_AGENT, keyAgentId } from
|
|
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))
|
package/dist/constants.d.ts
CHANGED
|
@@ -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 [
|
|
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
|
|
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
|
|
3
|
-
import { toAccountView } from
|
|
4
|
-
import { b64urlDecode, b64urlEncode, openNotice } from
|
|
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
|
|
422
|
-
export { readAgentUsage, agentUsageOf } from
|
|
423
|
-
export { readAgentDayUsage } from
|
|
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
|
|
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 {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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
|
|
359
|
-
// is written before its agent is created, so an
|
|
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
|
|
4
|
-
export { OpenClawKit } from
|
|
5
|
-
export { stateWords, toAccountView, words } from
|
|
6
|
-
export { chooseAccount, consider, resolveSelection } from
|
|
7
|
-
export { AuthStoreUnreadableError, AuthStoreSealSizeError } from
|
|
8
|
-
export { EngineAlreadyRunningError } from
|
|
9
|
-
export { readAgentDayUsage } from
|
|
10
|
-
export { readAgentUsage, agentUsageOf } from
|
|
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
|
-
|
|
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
|
-
|
|
45
|
+
remove(member: Member, o?: {
|
|
46
|
+
provider?: string;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
sweep(clear: boolean): Promise<boolean>;
|
|
34
49
|
};
|