@byokit/openclaw 0.16.1 → 0.16.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 +7 -0
- package/README.md +17 -4
- package/SECURITY.md +10 -3
- package/dist/engine.d.ts +8 -0
- package/dist/engine.js +66 -21
- package/dist/kit.d.ts +11 -0
- package/dist/kit.js +96 -10
- package/dist/signin.d.ts +1 -0
- package/dist/signin.js +2 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.16.2 (2026-10-18)
|
|
6
|
+
|
|
7
|
+
- Dependency update: pins @byokit/accounts 0.27.1.
|
|
8
|
+
- FIX: (from @byokit/accounts 0.27.1) classifyFailure returns needs_workspace for an OpenCode Go key that acts for several workspaces, so the app can ask the person to pick one instead of showing a run error.
|
|
9
|
+
|
|
10
|
+
- FIX: A first start that misses its gateway handshake recovers by itself. A handshake timeout, or an engine exit before hello, now moves the state to `restarting` with `retryAt` on the same backoff as a reconnect after `ready` (1 s, 2 s, 4 s, capped at 30 s), then starts the engine again; the fifth missed attempt moves to `failed` with why `handshake`, as before. Each `start()` call has its own five attempts and resolves once the kit is ready and rejects once it has given up or is stopped, `stop()` cancels its pending retry, and the new `KitOptions.handshakeMs` (default 90 000) sets the hello deadline per attempt.
|
|
11
|
+
|
|
5
12
|
## 0.16.1 (2026-10-17)
|
|
6
13
|
|
|
7
14
|
- Dependency update: pins @byokit/accounts 0.27.0.
|
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. 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 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()`.
|
|
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
|
|
@@ -518,7 +518,8 @@ itself — no manual file surgery:
|
|
|
518
518
|
`cleanup`/`restoring` transitions are completed from the authenticated snapshot; empty live trees are treated as
|
|
519
519
|
debris.
|
|
520
520
|
- An engine exit code 78 means the engine asked for repair: the kit's own repair path runs
|
|
521
|
-
`doctor --fix --yes` with `doctorContext()`'s exact env.
|
|
521
|
+
`doctor --fix --yes` with `doctorContext()`'s exact env. Call `doctorContext()` after `prepare()` or `start()`: its
|
|
522
|
+
`CLAUDE_CONFIG_DIR` reflects the Claude login the engine chose there, and before that it reflects only the installed login.
|
|
522
523
|
|
|
523
524
|
What a killed home leaves behind: live plaintext `state/` and `home/` (never resealed), `auth-store.lock/` with a
|
|
524
525
|
dead owner pid, `gateway.pid` and `gateway.identity` (a still-live orphan Gateway is the dangerous case),
|
|
@@ -600,6 +601,17 @@ const oc = openclawDevice(link);
|
|
|
600
601
|
for await (const e of oc.run('Say hello.')) console.log(e);
|
|
601
602
|
```
|
|
602
603
|
|
|
604
|
+
## First start that misses its handshake
|
|
605
|
+
|
|
606
|
+
`start()` waits up to `KitOptions.handshakeMs` (default 90 000) for the gateway's hello on each attempt. A timeout, or an
|
|
607
|
+
engine exit before hello (except an over-cap auth-store seal refusal, which fails at once with `auth-store-seal-size`), moves the state to `restarting` with `retryAt` on the same backoff as a reconnect after
|
|
608
|
+
`ready` (1 s, 2 s, 4 s, capped at 30 s), then starts the engine again. A transport that closes before hello keeps
|
|
609
|
+
retrying inside the transport, so it ends as the timeout; a refused connect (a bad token) fails at once. The fifth
|
|
610
|
+
missed attempt moves the state to `{ phase: 'failed', why: 'handshake' }` and `start()` rejects. Each `start()` call
|
|
611
|
+
has its own five attempts, and `stop()` cancels its pending retry and makes that `start()` reject. Show `stateWords(kit.state)` while the state is
|
|
612
|
+
`restarting`; the tests pass a short `handshakeMs`. A timeout that fires while the engine is repairing (exit 78) waits for the
|
|
613
|
+
repair to end before the engine is stopped and retried.
|
|
614
|
+
|
|
603
615
|
## Isolation and billing
|
|
604
616
|
|
|
605
617
|
The kit writes only under the `stateDir` the app passes, resolved to one absolute path through no symlink (so a
|
|
@@ -713,7 +725,8 @@ compromised engine/plugin, memory dump, or stolen host key can read credentials.
|
|
|
713
725
|
against replacement with an older authentic snapshot. Gateway/device keys, inline config secrets, logs, engine
|
|
714
726
|
install/cache files and app workspaces are outside this adapter's sealing scope; protect them separately.
|
|
715
727
|
`doctorContext()` is an advanced escape hatch: launching an external doctor bypasses this lifecycle; use the
|
|
716
|
-
kit's migration method for sealed stores.
|
|
728
|
+
kit's migration method for sealed stores. Call it after `prepare()` or `start()`, because its Claude login folder
|
|
729
|
+
is the one chosen there; before that it reflects only the installed login.
|
|
717
730
|
|
|
718
731
|
See [SECURITY.md](SECURITY.md) for the credential and approval threat model, private-directory requirements,
|
|
719
732
|
retained-login cleanup and sealed approval limits.
|
|
@@ -735,4 +748,4 @@ frozen (docs/runtime-kits.md §5).
|
|
|
735
748
|
|
|
736
749
|
Apache-2.0. See [LICENSE](LICENSE); the bundled engine's MIT text is [engine/OPENCLAW-LICENSE](engine/OPENCLAW-LICENSE).
|
|
737
750
|
|
|
738
|
-
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),
|
|
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.
|
package/SECURITY.md
CHANGED
|
@@ -87,9 +87,16 @@ already copied credentials or erase snapshots/backups.
|
|
|
87
87
|
|
|
88
88
|
## Engine isolation and installation
|
|
89
89
|
|
|
90
|
-
`src/engine.ts` builds the gateway/doctor environment explicitly:
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
`src/engine.ts` builds the gateway/doctor environment explicitly: the engine's own
|
|
91
|
+
home (`OPENCLAW_HOME`), cache, tmp, config and state paths point into the app's tree,
|
|
92
|
+
while `HOME` (and the person's own `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`,
|
|
93
|
+
`CLAUDE_CONFIG_DIR` and `CODEX_HOME` when set) is the person's for every engine process
|
|
94
|
+
(gateway, doctor and the agent CLIs), so the installed, signed-in logins are used as is. HOME is not an
|
|
95
|
+
access boundary: the engine runs with the person's own account rights and can open any path that account
|
|
96
|
+
can, including other CLIs' credentials and `~/.ssh`. Engine npm runs use the engine's own npm cache and config
|
|
97
|
+
(`npm_config_cache`, `npm_config_userconfig`), not the person's `~/.npm` or `~/.npmrc`. It does not forward shell API keys, `NODE_OPTIONS` or shell environment
|
|
98
|
+
snapshots. Only when the installed `claude auth status --json` is not signed in does the
|
|
99
|
+
gateway get `CLAUDE_CONFIG_DIR` at the engine's fallback folder. The native fallback
|
|
93
100
|
Claude sign-in child (`claude auth login`, started through `@byokit/accounts/cli`) gets
|
|
94
101
|
the same treatment: its env is only `PATH`, its own login HOME
|
|
95
102
|
(`openclaw/login-home`), `LANG`, `TMPDIR` and the `XDG_*` variables — never the gateway
|
package/dist/engine.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export declare class Engine {
|
|
|
37
37
|
private claudeAccounts?;
|
|
38
38
|
private claudeReset?;
|
|
39
39
|
private credentialsLocked;
|
|
40
|
+
private claudeFallback;
|
|
40
41
|
private repaired;
|
|
41
42
|
private prepared?;
|
|
42
43
|
patchSet?: string | null;
|
|
@@ -60,13 +61,20 @@ export declare class Engine {
|
|
|
60
61
|
code: number | null;
|
|
61
62
|
output: string;
|
|
62
63
|
}>;
|
|
64
|
+
private get path();
|
|
65
|
+
private get claudeFolder();
|
|
63
66
|
doctorContext(): {
|
|
64
67
|
entry: string;
|
|
65
68
|
env: Record<string, string>;
|
|
66
69
|
};
|
|
67
70
|
/** The engine's own shutdown signal; aborted by stop() and replaced by the next start(). */
|
|
68
71
|
get stopSignal(): AbortSignal;
|
|
72
|
+
private installedClaudeSignedOut;
|
|
73
|
+
/** The fallback Claude sign-in for a machine whose installed Claude Code is signed out: it signs into the engine's folder. */
|
|
69
74
|
nativeClaudeLogin(): ReturnType<typeof remoteCliHost>;
|
|
75
|
+
/** Whether a fallback sign-in would write a folder this engine's gateway does not read: it started on the person's
|
|
76
|
+
* login location (installed sign-in, or a probe with no answer), and an in-process restart keeps that env. */
|
|
77
|
+
claudeLoginUnread(): boolean;
|
|
70
78
|
resetNativeClaudeLogin(): Promise<void>;
|
|
71
79
|
doctor(timeoutMs: number): {
|
|
72
80
|
status: number | null;
|
package/dist/engine.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
1
|
+
import { execFile, spawn, spawnSync } from 'node:child_process';
|
|
2
2
|
import { randomBytes, generateKeyPairSync, randomUUID } from 'node:crypto';
|
|
3
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 { homedir } from 'node:os';
|
|
5
6
|
import { basename, join, dirname, resolve } from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
@@ -34,6 +35,23 @@ const putChanged = (path, data) => {
|
|
|
34
35
|
if (!existsSync(path) || readFileSync(path, 'utf8') !== data)
|
|
35
36
|
writeFileSync(path, data, { mode: 0o600 });
|
|
36
37
|
};
|
|
38
|
+
// The person's own homes for every engine process: the gateway, doctor and agent CLIs use the installed, signed-in logins as is.
|
|
39
|
+
const personal = () => {
|
|
40
|
+
const env = { HOME: homedir() };
|
|
41
|
+
for (const name of ['XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME', 'CLAUDE_CONFIG_DIR', 'CODEX_HOME']) {
|
|
42
|
+
const value = process.env[name];
|
|
43
|
+
if (value)
|
|
44
|
+
env[name] = value;
|
|
45
|
+
}
|
|
46
|
+
return env;
|
|
47
|
+
};
|
|
48
|
+
const onPath = (path, name) => path.split(':').map(dir => join(dir, name)).find(file => { try {
|
|
49
|
+
accessSync(file, constants.X_OK);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
} });
|
|
37
55
|
const supports = (list, value) => !list ||
|
|
38
56
|
(!list.includes(`!${value}`) && (list.includes('any') || list.every(item => item.startsWith('!')) || list.includes(value)));
|
|
39
57
|
// First failed check of the most recent installMatches call; retained with the npm facts in
|
|
@@ -98,6 +116,8 @@ export class Engine {
|
|
|
98
116
|
claudeAccounts;
|
|
99
117
|
claudeReset;
|
|
100
118
|
credentialsLocked = false;
|
|
119
|
+
// True when the last start found no installed Claude Code login: the engine then uses its own fallback folder.
|
|
120
|
+
claudeFallback = false;
|
|
101
121
|
repaired = false;
|
|
102
122
|
prepared;
|
|
103
123
|
patchSet;
|
|
@@ -184,7 +204,10 @@ export class Engine {
|
|
|
184
204
|
await this.recoverOrphan();
|
|
185
205
|
for (const d of [this.root, ...(!this.o.authSeal ? [join(this.root, 'home'), join(this.root, 'state')] : []), join(this.root, 'tmp'), join(this.root, 'usage'), join(this.root, 'install-home'), join(this.root, 'npm-cache'), join(this.o.stateDir, 'logs')])
|
|
186
206
|
mkdirSync(d, { recursive: true, mode: 0o700 });
|
|
207
|
+
putOnce(join(this.root, 'npmrc'), ''); // engine npm runs read this empty config, never the person's ~/.npmrc
|
|
187
208
|
this.sweepAccountOrphans();
|
|
209
|
+
if (this.o.spawnEngine)
|
|
210
|
+
this.claudeFallback = await this.installedClaudeSignedOut();
|
|
188
211
|
try {
|
|
189
212
|
await this.authStore.prepare();
|
|
190
213
|
this.credentialsLocked = false;
|
|
@@ -304,7 +327,7 @@ export class Engine {
|
|
|
304
327
|
const npm = this.o.npmPath && this.o.npmPath.includes('/') ? [dirname(this.o.npmPath)] : (process.env.PATH ?? '').split(':');
|
|
305
328
|
return new Promise((resolve, reject) => {
|
|
306
329
|
const child = spawn(process.execPath, [entry, 'plugins', 'install', spec, '--force', '--accept-capabilities'], {
|
|
307
|
-
cwd: env.
|
|
330
|
+
cwd: env.OPENCLAW_HOME, env: { ...env, PATH: [...npm, env.PATH].join(':'), npm_config_cache: join(this.root, 'npm-cache') },
|
|
308
331
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
309
332
|
});
|
|
310
333
|
let output = '';
|
|
@@ -317,14 +340,17 @@ export class Engine {
|
|
|
317
340
|
child.once('close', code => { clearTimeout(timer); this.started.forget(child); resolve({ code, output }); });
|
|
318
341
|
});
|
|
319
342
|
}
|
|
343
|
+
get path() { return ['/usr/bin', '/bin', ...this.o.enginePath ?? []].join(':'); }
|
|
344
|
+
// The engine's own Claude folder: the fallback login writes it and, only with claudeFallback, the gateway reads it.
|
|
345
|
+
get claudeFolder() { return join(this.root, 'home', '.claude'); }
|
|
320
346
|
doctorContext() {
|
|
321
347
|
const home = join(this.root, 'home');
|
|
322
348
|
return { entry: this.entry, env: {
|
|
323
|
-
PATH:
|
|
349
|
+
PATH: this.path, LANG: 'C.UTF-8', ...personal(), OPENCLAW_HOME: home,
|
|
324
350
|
OPENCLAW_STATE_DIR: join(this.root, 'state'), OPENCLAW_CONFIG_PATH: join(this.root, 'openclaw.json'),
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
351
|
+
XDG_CACHE_HOME: join(home, '.cache'), ...this.claudeFallback ? { CLAUDE_CONFIG_DIR: this.claudeFolder } : {},
|
|
352
|
+
TMPDIR: join(this.root, 'tmp'), npm_config_cache: join(this.root, 'npm-cache'), npm_config_userconfig: join(this.root, 'npmrc'),
|
|
353
|
+
OPENCLAW_NO_RESPAWN: '1', OPENCLAW_SKIP_CHANNELS: '1', OPENCLAW_DISABLE_BONJOUR: '1',
|
|
328
354
|
OPENCLAW_EXEC_SHELL_SNAPSHOT: '0', OPENCLAW_LOAD_SHELL_ENV: '0', OPENCLAW_GATEWAY_TOKEN: this.token,
|
|
329
355
|
BYOKIT_BRIDGE_SOCK: this.bridgeSock,
|
|
330
356
|
BYOKIT_APP_OWNED_SESSION_PREFIXES: JSON.stringify(this.appOwnedSessionPrefixes),
|
|
@@ -332,26 +358,45 @@ export class Engine {
|
|
|
332
358
|
}
|
|
333
359
|
/** The engine's own shutdown signal; aborted by stop() and replaced by the next start(). */
|
|
334
360
|
get stopSignal() { return this.stopOwner.signal; }
|
|
361
|
+
installedClaudeSignedOut() {
|
|
362
|
+
const env = { PATH: this.path, LANG: 'C.UTF-8', TMPDIR: join(this.root, 'tmp'), ...personal() };
|
|
363
|
+
const bin = onPath(env.PATH, 'claude');
|
|
364
|
+
if (!bin)
|
|
365
|
+
return Promise.resolve(true);
|
|
366
|
+
return new Promise(resolve => execFile(bin, ['auth', 'status', '--json'], { env, timeout: 15_000, maxBuffer: 256 * 1024 }, (error, stdout) => {
|
|
367
|
+
let loggedIn;
|
|
368
|
+
try {
|
|
369
|
+
loggedIn = JSON.parse(stdout).loggedIn;
|
|
370
|
+
}
|
|
371
|
+
catch { /* no parsed answer */ }
|
|
372
|
+
if (loggedIn === true)
|
|
373
|
+
return resolve(false);
|
|
374
|
+
if (loggedIn === false) {
|
|
375
|
+
this.o.log?.('claude: no installed sign-in, the engine uses its own sign-in folder');
|
|
376
|
+
return resolve(true);
|
|
377
|
+
}
|
|
378
|
+
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);
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
/** The fallback Claude sign-in for a machine whose installed Claude Code is signed out: it signs into the engine's folder. */
|
|
335
383
|
nativeClaudeLogin() {
|
|
336
384
|
if (this.claudeLogin)
|
|
337
385
|
return this.claudeLogin;
|
|
338
|
-
const { env } = this.doctorContext();
|
|
339
|
-
const bin = env.PATH
|
|
340
|
-
accessSync(path, constants.X_OK);
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
return false;
|
|
345
|
-
} });
|
|
386
|
+
const { env } = this.doctorContext(), home = join(this.root, 'home');
|
|
387
|
+
const bin = onPath(env.PATH, 'claude');
|
|
346
388
|
const bins = bin ? { claude: bin } : {};
|
|
347
389
|
// 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.
|
|
348
390
|
for (const dir of ['home', 'login-home'])
|
|
349
391
|
mkdirSync(join(this.root, dir), { recursive: true, mode: 0o700 });
|
|
350
|
-
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:
|
|
392
|
+
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: join(home, '.config'), XDG_CACHE_HOME: join(home, '.cache'), XDG_DATA_HOME: join(home, '.local/share'), XDG_STATE_HOME: join(home, '.local/state') }, managedFolders: { claude: this.claudeFolder } });
|
|
351
393
|
this.claudeAccounts = cli;
|
|
352
394
|
this.claudeLogin = remoteCliHost({ cli, bins });
|
|
353
395
|
return this.claudeLogin;
|
|
354
396
|
}
|
|
397
|
+
/** Whether a fallback sign-in would write a folder this engine's gateway does not read: it started on the person's
|
|
398
|
+
* login location (installed sign-in, or a probe with no answer), and an in-process restart keeps that env. */
|
|
399
|
+
claudeLoginUnread() { return Boolean(this.o.spawnEngine) && !this.claudeFallback; }
|
|
355
400
|
resetNativeClaudeLogin() {
|
|
356
401
|
if (this.claudeReset)
|
|
357
402
|
return this.claudeReset;
|
|
@@ -361,7 +406,7 @@ export class Engine {
|
|
|
361
406
|
this.claudeReset = (async () => { for (const row of await cli.list()) {
|
|
362
407
|
if (host.signal.aborted)
|
|
363
408
|
return;
|
|
364
|
-
if (row.provider === 'claude' && row.state === 'ready' && cli.folderOf(row.id) ===
|
|
409
|
+
if (row.provider === 'claude' && row.state === 'ready' && cli.folderOf(row.id) === this.claudeFolder)
|
|
365
410
|
await cli.remove(row.id);
|
|
366
411
|
} })().finally(() => { this.claudeReset = undefined; });
|
|
367
412
|
return this.claudeReset;
|
|
@@ -370,7 +415,7 @@ export class Engine {
|
|
|
370
415
|
const { entry, env } = this.doctorContext();
|
|
371
416
|
if (this.o.spawnEngine)
|
|
372
417
|
verifyEngineSet(this.setDir, this.wantedSet, installMatches);
|
|
373
|
-
const result = spawnSync(process.execPath, [entry, 'doctor', '--fix', '--yes', '--non-interactive'], { cwd: env.
|
|
418
|
+
const result = spawnSync(process.execPath, [entry, 'doctor', '--fix', '--yes', '--non-interactive'], { cwd: env.OPENCLAW_HOME, env, timeout: timeoutMs, stdio: 'pipe' });
|
|
374
419
|
if (this.o.spawnEngine)
|
|
375
420
|
verifyEngineSet(this.setDir, this.wantedSet, installMatches);
|
|
376
421
|
return { status: result.status };
|
|
@@ -389,7 +434,7 @@ export class Engine {
|
|
|
389
434
|
&& identity.pid === pid && identity.startTime === processStartTime(pid)
|
|
390
435
|
&& realpathSync(`${proc}/exe`) === realpathSync(process.execPath)
|
|
391
436
|
&& realpathSync(`${proc}/cwd`) === realpathSync(home)
|
|
392
|
-
&& env.includes(`
|
|
437
|
+
&& env.includes(`OPENCLAW_HOME=${home}`)
|
|
393
438
|
&& env.includes(`OPENCLAW_STATE_DIR=${join(this.root, 'state')}`)
|
|
394
439
|
&& env.includes(`OPENCLAW_CONFIG_PATH=${join(this.root, 'openclaw.json')}`)
|
|
395
440
|
&& existsSync(owner) && !pidAlive(Number(readFileSync(owner, 'utf8')));
|
|
@@ -527,7 +572,7 @@ export class Engine {
|
|
|
527
572
|
}
|
|
528
573
|
// Detached keeps the engine out of the host's session; stop() still signals its pid alone, never its group.
|
|
529
574
|
try {
|
|
530
|
-
this.child = spawn(process.execPath, [entry, 'gateway', '--port', String(this.port)], { cwd: env.
|
|
575
|
+
this.child = spawn(process.execPath, [entry, 'gateway', '--port', String(this.port)], { cwd: env.OPENCLAW_HOME, env, detached: true, stdio: ['ignore', fd, fd] });
|
|
531
576
|
}
|
|
532
577
|
catch (error) {
|
|
533
578
|
if (env.BYOKIT_ENGINE_BOOT)
|
|
@@ -552,7 +597,7 @@ export class Engine {
|
|
|
552
597
|
if (this.child !== child || this.stopping)
|
|
553
598
|
return;
|
|
554
599
|
this.child = undefined;
|
|
555
|
-
void this.authStore.stop().then(() => undefined, refusedOnly).then((refused) => { this.exitedState(refused); this.o.onExit(null); }, () => this.state('failed', 'exited'));
|
|
600
|
+
void this.authStore.stop().then(() => undefined, refusedOnly).then((refused) => { this.exitedState(refused); this.o.onExit(null); }, () => { this.state('failed', 'exited'); this.o.onExit(null); });
|
|
556
601
|
});
|
|
557
602
|
if (!child.pid) {
|
|
558
603
|
this.state('failed', 'exited');
|
|
@@ -588,7 +633,7 @@ export class Engine {
|
|
|
588
633
|
}
|
|
589
634
|
this.exitedState(refused);
|
|
590
635
|
this.o.onExit(code);
|
|
591
|
-
})().catch(() => this.state('failed', 'exited'));
|
|
636
|
+
})().catch(() => { this.state('failed', 'exited'); this.o.onExit(code); });
|
|
592
637
|
});
|
|
593
638
|
return { port: this.port, token: this.token, identityPath };
|
|
594
639
|
}
|
package/dist/kit.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export type KitOptions = {
|
|
|
35
35
|
};
|
|
36
36
|
callbackPort?: number;
|
|
37
37
|
approvalTimeoutMs?: number;
|
|
38
|
+
handshakeMs?: number;
|
|
38
39
|
transport?: (ctx: {
|
|
39
40
|
port: number;
|
|
40
41
|
token: string;
|
|
@@ -74,8 +75,13 @@ export declare class OpenClawKit {
|
|
|
74
75
|
private signInDisconnects;
|
|
75
76
|
private readonly phaseListeners;
|
|
76
77
|
private starting?;
|
|
78
|
+
private stopped;
|
|
77
79
|
private stopping;
|
|
78
80
|
private failures;
|
|
81
|
+
private reachedReady;
|
|
82
|
+
private startRun?;
|
|
83
|
+
private retry?;
|
|
84
|
+
private engineExit?;
|
|
79
85
|
private swept;
|
|
80
86
|
private catalogs;
|
|
81
87
|
browser?: BrowserHost;
|
|
@@ -99,13 +105,17 @@ export declare class OpenClawKit {
|
|
|
99
105
|
get state(): KitState;
|
|
100
106
|
prepare(): Promise<void>;
|
|
101
107
|
start(): Promise<void>;
|
|
108
|
+
private attempts;
|
|
102
109
|
private connect;
|
|
110
|
+
private repairEnded;
|
|
103
111
|
private disconnect;
|
|
104
112
|
private engineRestarting;
|
|
105
113
|
private authSnapshot;
|
|
106
114
|
private restarted;
|
|
107
115
|
private closed;
|
|
116
|
+
private backoff;
|
|
108
117
|
stop(): Promise<void>;
|
|
118
|
+
private teardown;
|
|
109
119
|
private closeBrowsers;
|
|
110
120
|
private request;
|
|
111
121
|
call<M extends GatewayMethod>(method: M, params: GatewayParams<M>, o?: CallOptions): Promise<GatewayResult<M>>;
|
|
@@ -221,6 +231,7 @@ export declare class OpenClawKit {
|
|
|
221
231
|
setLearning(mode: LearningMode | 'default'): LearningCapture;
|
|
222
232
|
restoreLearning(captured: LearningCapture): LearningCapture;
|
|
223
233
|
private assertLearningWritable;
|
|
234
|
+
/** The env for an external doctor. Its Claude login folder is the one chosen at `prepare()`/`start()`; before that it is the installed-login view only. */
|
|
224
235
|
doctorContext(): {
|
|
225
236
|
entry: string;
|
|
226
237
|
env: Record<string, string>;
|
package/dist/kit.js
CHANGED
|
@@ -32,6 +32,8 @@ import { launchBroker } from './browser/broker.js';
|
|
|
32
32
|
/** The engine's learning-mode config key (5.6 "Engine learning state"). */
|
|
33
33
|
const LEARNING_KEY = 'skills.workshop.autonomous.mode';
|
|
34
34
|
const RESTART_MS = 60_000;
|
|
35
|
+
/** Consecutive failed starts before `failed/handshake` (5.4 step 2): the first start plus four retries. */
|
|
36
|
+
const START_ATTEMPTS = 5;
|
|
35
37
|
const operatorMethods = new Set([
|
|
36
38
|
'agent',
|
|
37
39
|
'agent.identity.get',
|
|
@@ -452,8 +454,13 @@ export class OpenClawKit {
|
|
|
452
454
|
signInDisconnects = new Set();
|
|
453
455
|
phaseListeners = new Set();
|
|
454
456
|
starting;
|
|
457
|
+
stopped = Promise.resolve();
|
|
455
458
|
stopping = false;
|
|
456
459
|
failures = 0;
|
|
460
|
+
reachedReady = false;
|
|
461
|
+
startRun;
|
|
462
|
+
retry;
|
|
463
|
+
engineExit;
|
|
457
464
|
swept = false;
|
|
458
465
|
catalogs = Promise.resolve();
|
|
459
466
|
browser;
|
|
@@ -737,20 +744,52 @@ export class OpenClawKit {
|
|
|
737
744
|
if (this.engine.patchSet !== undefined)
|
|
738
745
|
this.setState(this.current);
|
|
739
746
|
}
|
|
740
|
-
start() {
|
|
747
|
+
async start() {
|
|
741
748
|
if (this.current.phase === 'ready')
|
|
742
|
-
return
|
|
749
|
+
return;
|
|
750
|
+
if (this.stopping) {
|
|
751
|
+
await this.starting?.catch(() => undefined);
|
|
752
|
+
await this.stopped;
|
|
753
|
+
}
|
|
743
754
|
if (this.starting)
|
|
744
755
|
return this.starting;
|
|
745
756
|
this.stopping = false;
|
|
746
|
-
|
|
757
|
+
this.retry?.abort();
|
|
758
|
+
const task = this.attempts(this.reachedReady ? 1 : START_ATTEMPTS);
|
|
747
759
|
this.starting = task;
|
|
748
760
|
void task.finally(() => { if (this.starting === task)
|
|
749
761
|
this.starting = undefined; }).catch(() => { });
|
|
750
762
|
return task;
|
|
751
763
|
}
|
|
764
|
+
// One start() call's attempts (5.4 step 2): a missed hello waits the reconnect backoff and starts the engine afresh
|
|
765
|
+
// until `budget` attempts have missed. The call owns its wait (stop() aborts it) and, on a first start, every close.
|
|
766
|
+
async attempts(budget) {
|
|
767
|
+
const run = this.startRun = new AbortController();
|
|
768
|
+
try {
|
|
769
|
+
for (let missed = 0;;) {
|
|
770
|
+
const miss = await this.connect();
|
|
771
|
+
if (!miss) {
|
|
772
|
+
if (this.stopping)
|
|
773
|
+
throw new Error('kit stopped before ready');
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (++missed === budget) {
|
|
777
|
+
this.setState({ phase: 'failed', why: 'handshake' });
|
|
778
|
+
throw miss;
|
|
779
|
+
}
|
|
780
|
+
await this.backoff(missed - 1, run.signal);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
finally {
|
|
784
|
+
if (this.startRun === run)
|
|
785
|
+
this.startRun = undefined;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
// Resolves the error of a missed hello (a deadline miss, or an engine exit before hello) for attempts() to retry.
|
|
752
789
|
async connect() {
|
|
753
790
|
let transport;
|
|
791
|
+
const timeout = new Error('gateway handshake timed out');
|
|
792
|
+
const exited = new Error('engine exited before the gateway handshake');
|
|
754
793
|
try {
|
|
755
794
|
await this.prepareBrowsers();
|
|
756
795
|
const ctx = await this.engine.start();
|
|
@@ -761,7 +800,9 @@ export class OpenClawKit {
|
|
|
761
800
|
this.transport = transport;
|
|
762
801
|
this.off = [transport.onEvent((e) => { for (const fn of this.listeners)
|
|
763
802
|
fn(e); }), transport.onClose((why) => this.closed(why))];
|
|
764
|
-
const
|
|
803
|
+
const deadline = delay(this.o.handshakeMs ?? 90_000, undefined, { ref: false }).then(() => { throw timeout; });
|
|
804
|
+
const exit = new Promise((_, reject) => { this.engineExit = () => reject(exited); });
|
|
805
|
+
const hello = await Promise.race([transport.start(), deadline, exit]);
|
|
765
806
|
if (this.stopping || this.transport !== transport)
|
|
766
807
|
return;
|
|
767
808
|
if (hello.protocol !== PROTOCOL_VERSION) {
|
|
@@ -782,9 +823,10 @@ export class OpenClawKit {
|
|
|
782
823
|
signIn: (agentId, o, on, fresh) => startSignIn({ ...this.signInCtx(), ensure: async () => ({ agentId }), fresh }, agentId, o, on),
|
|
783
824
|
routes: this.routeMap, log: this.o.log, reseal: () => this.engine.authStore.reseal() });
|
|
784
825
|
// The fail-closed tool bridge feeds the kit's single Approval surface (5.9, O5).
|
|
785
|
-
await this.bridge.start();
|
|
826
|
+
await Promise.race([this.bridge.start(), exit]);
|
|
786
827
|
this.off.push(transport.onEvent((e) => this.approvalsCtl.handleEvent(e)));
|
|
787
828
|
this.failures = 0;
|
|
829
|
+
this.reachedReady = true;
|
|
788
830
|
this.setState({ phase: 'ready' });
|
|
789
831
|
this.syncCatalogs();
|
|
790
832
|
// A failed key add whose abandon clear never reached the engine leaves an unsealed key with no ready marker;
|
|
@@ -803,6 +845,9 @@ export class OpenClawKit {
|
|
|
803
845
|
const patchFailure = this.current.why === 'engine-patch';
|
|
804
846
|
if (transport && this.transport === transport)
|
|
805
847
|
await this.disconnect();
|
|
848
|
+
if (error === timeout && this.current.phase === 'repairing')
|
|
849
|
+
await this.repairEnded();
|
|
850
|
+
const engineFailure = this.current.phase === 'failed' ? this.current : undefined;
|
|
806
851
|
const refused = await this.engine.stop().then(() => undefined, refusedOnly);
|
|
807
852
|
await this.closeBrowsers();
|
|
808
853
|
const sealRefusal = refused ?? (error instanceof AuthStoreSealSizeError ? error : undefined);
|
|
@@ -822,12 +867,32 @@ export class OpenClawKit {
|
|
|
822
867
|
this.setState({ phase: 'failed', why: 'engine-patch' });
|
|
823
868
|
throw error;
|
|
824
869
|
}
|
|
870
|
+
// A missed hello on a loaded host is the engine still coming up: attempts() decides whether to retry.
|
|
871
|
+
if (engineFailure?.why === 'auth-store-seal-size' && !this.stopping) {
|
|
872
|
+
this.setState(engineFailure);
|
|
873
|
+
throw error;
|
|
874
|
+
}
|
|
875
|
+
const miss = error === exited && engineFailure?.why === 'exited' ? exited : error === timeout ? timeout : undefined;
|
|
876
|
+
if (miss && !this.stopping)
|
|
877
|
+
return miss;
|
|
825
878
|
if (needsUpdate)
|
|
826
879
|
this.setState({ phase: 'needs-update', why: 'version' });
|
|
827
880
|
if (!needsUpdate && !this.stopping)
|
|
828
881
|
this.setState({ phase: 'failed', why: 'handshake' });
|
|
829
882
|
throw error;
|
|
830
883
|
}
|
|
884
|
+
finally {
|
|
885
|
+
this.engineExit = undefined;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
repairEnded() {
|
|
889
|
+
return new Promise((resolve) => {
|
|
890
|
+
const check = () => { if (this.current.phase !== 'repairing') {
|
|
891
|
+
this.phaseListeners.delete(check);
|
|
892
|
+
resolve();
|
|
893
|
+
} };
|
|
894
|
+
this.phaseListeners.add(check);
|
|
895
|
+
});
|
|
831
896
|
}
|
|
832
897
|
async disconnect() {
|
|
833
898
|
this.admission.invalidate();
|
|
@@ -874,8 +939,14 @@ export class OpenClawKit {
|
|
|
874
939
|
signal?.addEventListener('abort', abort, { once: true });
|
|
875
940
|
});
|
|
876
941
|
}
|
|
877
|
-
closed(
|
|
878
|
-
|
|
942
|
+
closed(why) {
|
|
943
|
+
// A first start's loop owns an engine exit until it reaches ready; other closes are the transport client's to retry.
|
|
944
|
+
if (this.startRun && !this.reachedReady) {
|
|
945
|
+
if (why === 'engine exited')
|
|
946
|
+
this.engineExit?.();
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const engineFailed = why === 'engine exited' && this.current.phase === 'failed' && this.current.why === 'exited';
|
|
879
950
|
if (this.stopping || (this.current.phase !== 'ready' && !engineFailed))
|
|
880
951
|
return;
|
|
881
952
|
for (const fn of this.signInDisconnects)
|
|
@@ -885,13 +956,26 @@ export class OpenClawKit {
|
|
|
885
956
|
this.setState({ phase: 'failed', why: 'handshake' });
|
|
886
957
|
return;
|
|
887
958
|
}
|
|
888
|
-
|
|
959
|
+
this.retry?.abort();
|
|
960
|
+
const retry = this.retry = new AbortController();
|
|
961
|
+
void this.backoff(this.failures++, retry.signal).then(() => { if (!this.stopping && this.current.phase === 'restarting')
|
|
962
|
+
void this.start().catch(() => { }); }, () => { });
|
|
963
|
+
}
|
|
964
|
+
// The reconnect backoff (step 3): state restarting, resolving at retryAt = now + min(30 s, 1 s × 2^failures).
|
|
965
|
+
backoff(failures, signal) {
|
|
966
|
+
const retryAt = Date.now() + Math.min(30_000, 1000 * 2 ** failures);
|
|
889
967
|
this.setState({ phase: 'restarting', retryAt });
|
|
890
|
-
|
|
891
|
-
void this.start().catch(() => { }); });
|
|
968
|
+
return delay(retryAt - Date.now(), undefined, { signal });
|
|
892
969
|
}
|
|
893
970
|
async stop() {
|
|
894
971
|
this.stopping = true;
|
|
972
|
+
this.reachedReady = false;
|
|
973
|
+
this.startRun?.abort();
|
|
974
|
+
this.retry?.abort();
|
|
975
|
+
this.stopped = this.teardown();
|
|
976
|
+
await this.stopped;
|
|
977
|
+
}
|
|
978
|
+
async teardown() {
|
|
895
979
|
await this.disconnect();
|
|
896
980
|
await this.engine.stop();
|
|
897
981
|
await this.closeBrowsers();
|
|
@@ -971,6 +1055,7 @@ export class OpenClawKit {
|
|
|
971
1055
|
ensure: (member) => this.ensureMember(member), callbackPort: this.o.callbackPort ?? 1455,
|
|
972
1056
|
nativeClaudeLogin: () => this.engine.nativeClaudeLogin(),
|
|
973
1057
|
resetNativeClaudeLogin: () => this.engine.resetNativeClaudeLogin(),
|
|
1058
|
+
claudeLoginUnread: () => this.engine.claudeLoginUnread(),
|
|
974
1059
|
onDisconnect: (fn) => { this.signInDisconnects.add(fn); return () => { this.signInDisconnects.delete(fn); }; },
|
|
975
1060
|
stopSignal: this.engine.stopSignal,
|
|
976
1061
|
authConfig: () => this.authSnapshot(),
|
|
@@ -1382,5 +1467,6 @@ export class OpenClawKit {
|
|
|
1382
1467
|
if (this.o.config?.skills !== undefined)
|
|
1383
1468
|
throw new Error(`${op}: KitOptions.config also passes skills; that option wins at every boot (5.6)`);
|
|
1384
1469
|
}
|
|
1470
|
+
/** The env for an external doctor. Its Claude login folder is the one chosen at `prepare()`/`start()`; before that it is the installed-login view only. */
|
|
1385
1471
|
doctorContext() { return this.engine.doctorContext(); }
|
|
1386
1472
|
}
|
package/dist/signin.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type SignInCtx = {
|
|
|
10
10
|
callbackPort: number;
|
|
11
11
|
nativeClaudeLogin?(): ReturnType<typeof remoteCliHost>;
|
|
12
12
|
resetNativeClaudeLogin?(): Promise<void>;
|
|
13
|
+
claudeLoginUnread?(): boolean;
|
|
13
14
|
onDisconnect?(fn: () => void): () => void;
|
|
14
15
|
authConfig(): string;
|
|
15
16
|
restarted(authBefore: string | undefined, signal?: AbortSignal): Promise<boolean>;
|
package/dist/signin.js
CHANGED
|
@@ -242,6 +242,8 @@ export function signIn(ctx, member, o, on) {
|
|
|
242
242
|
};
|
|
243
243
|
native = ctx.nativeClaudeLogin?.();
|
|
244
244
|
if (!await detected()) {
|
|
245
|
+
if (ctx.claudeLoginUnread?.())
|
|
246
|
+
return { state: 'failed', via, why: 'failed', error: '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.' };
|
|
245
247
|
if (!native)
|
|
246
248
|
return { state: 'failed', via, why: 'failed', error: 'Claude Code sign-in is not available on this engine.' };
|
|
247
249
|
let started = await native.start('claude', { signal });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byokit/openclaw",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
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.
|
|
63
|
+
"@byokit/accounts": "0.27.1",
|
|
64
64
|
"@byokit/pair": "0.10.3",
|
|
65
65
|
"@byokit/relay": "0.8.1",
|
|
66
66
|
"@byokit/discover": "0.8.4",
|