@byokit/openclaw 0.10.0 → 0.11.1

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.
@@ -2,7 +2,7 @@
2
2
  // `new OpenClawKit({ transport: fake.factory, spawnEngine: false, ... })` or drive `fake.transport.request`
3
3
  // directly. No engine, no network, no account.
4
4
  import { connect } from 'node:net';
5
- import { randomUUID } from 'node:crypto';
5
+ import { createHash, randomUUID } from 'node:crypto';
6
6
  import { readFileSync } from 'node:fs';
7
7
  import { dirname, join } from 'node:path';
8
8
  import { ENGINE_VERSION, PROTOCOL_VERSION } from "../constants.js";
@@ -14,6 +14,13 @@ const DEVICE_STEP = {
14
14
  deviceCode: { code: 'CREW-2026', expiresInMinutes: 15 }, externalUrl: 'https://auth.openai.com/codex/device',
15
15
  };
16
16
  const PROGRESS_STEP = { id: 'step-wait', type: 'progress', message: 'Waiting for authorization', executor: 'gateway' };
17
+ // ChatGPT's browser route: the pin hands its unsigned authorize URL to the next step as `externalUrl`.
18
+ const BROWSER_STEP = {
19
+ id: 'step-browser', type: 'note', executor: 'client', message: 'Open this URL in your LOCAL browser.',
20
+ externalUrl: 'https://auth.openai.com/oauth/authorize?response_type=code&client_id=fake-client&redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback&state=fake-state',
21
+ };
22
+ // The pin's agents.create into an existing workspace rewrites IDENTITY.md's Name line (mergeIdentityMarkdownContent).
23
+ const IDENTITY_NAME = /^- \*\*Name:\*\*.*$/m;
17
24
  const EVENTS = [
18
25
  'agent',
19
26
  'exec.approval.requested', 'exec.approval.resolved',
@@ -23,6 +30,12 @@ const EVENTS = [
23
30
  /** Window between a run's last event and its completion, so an abort issued on the text event still lands. */
24
31
  const ABORT_WINDOW_MS = 25;
25
32
  const BRIDGE_TIMEOUT_MS = 30_000;
33
+ // The plugin's accounts.js status thresholds: auth selection's refresh margin and auth-health's oauth warn window.
34
+ const EXPIRING_MS = 300_000;
35
+ const OAUTH_WARN_MS = 86_400_000;
36
+ // The plugin's account agent shape (D9) and agent id shape, as plugin/accounts.js tests them.
37
+ const ACCOUNT_AGENT = /--[0-9a-f]{6}$/;
38
+ const AGENT_ID = /^[a-z][a-z0-9-]{0,31}$/;
26
39
  const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
27
40
  function mergeInto(target, patch) {
28
41
  for (const [key, value] of Object.entries(patch)) {
@@ -100,8 +113,36 @@ export function fakeGateway(script, o) {
100
113
  const handlers = new Map();
101
114
  const agents = new Map();
102
115
  const keys = new Map();
116
+ const profiles = new Map();
117
+ const orders = new Map();
118
+ const models = new Map();
103
119
  const wizards = new Map();
120
+ // Each workspace's files by name, as `agents.files.get/set` and the create-time IDENTITY.md rewrite see them.
121
+ const workspaces = new Map();
122
+ const filesOf = (agentId) => {
123
+ const agent = agents.get(agentId);
124
+ if (!agent)
125
+ throw new Error(`unknown agent id "${agentId}"`);
126
+ const workspace = agent.workspace ?? `workspace-${agentId}`;
127
+ let files = workspaces.get(workspace);
128
+ if (!files) {
129
+ files = new Map();
130
+ workspaces.set(workspace, files);
131
+ }
132
+ return { workspace, files };
133
+ };
134
+ const fileAnswer = (agentId, name) => {
135
+ const { workspace, files } = filesOf(agentId);
136
+ const content = files.get(name);
137
+ return { agentId, workspace, file: { name, path: join(workspace, name), missing: content === undefined,
138
+ ...(content === undefined ? {} : { size: Buffer.byteLength(content), content }) } };
139
+ };
104
140
  const runs = new Map();
141
+ // The engine's own cached read of each provider's usage endpoint (O14): cold until a `models.authStatus`
142
+ // answer has carried the provider once (no `usage` on that first answer), then whatever `setUsage` stored.
143
+ // A pre-sign-in read names no provider, so it warms nothing: the journey's first room read stays cold.
144
+ const usage = new Map();
145
+ const warmed = new Set();
105
146
  const configBox = { hash: 'hash-1', config: {} };
106
147
  let hashSeq = 1;
107
148
  const ensureAgent = (id, workspace) => {
@@ -114,10 +155,92 @@ export function fakeGateway(script, o) {
114
155
  agent.workspace = workspace;
115
156
  return agent;
116
157
  };
117
- const signInAgent = (id, provider) => {
158
+ const storeOf = (id) => {
159
+ let store = profiles.get(id);
160
+ if (!store) {
161
+ store = {};
162
+ profiles.set(id, store);
163
+ }
164
+ return store;
165
+ };
166
+ const orderOf = (id) => {
167
+ let order = orders.get(id);
168
+ if (!order) {
169
+ order = {};
170
+ orders.set(id, order);
171
+ }
172
+ return order;
173
+ };
174
+ // As the plugin's accounts.js: `who` only exists for an OAuth profile that stores a provider account id.
175
+ const whoOf = (credential) => credential.type === 'oauth' && typeof credential.accountId === 'string' && credential.accountId.length > 0
176
+ ? createHash('sha256').update(`${credential.provider}\n${credential.accountId}`).digest('hex').slice(0, 16) : undefined;
177
+ const hasSecret = (value, ref) => (typeof value === 'string' && value.length > 0) || (!!ref && typeof ref === 'object' && !Array.isArray(ref));
178
+ // The plugin's token expiry state (credential-state.ts): missing, invalid, expired, or valid.
179
+ const expiryState = (expires, now) => {
180
+ if (expires === undefined)
181
+ return 'missing';
182
+ if (typeof expires !== 'number' || !Number.isFinite(expires) || expires <= 0 || expires > 8.64e13)
183
+ return 'invalid';
184
+ return expires <= now ? 'expired' : 'valid';
185
+ };
186
+ // The plugin's profile status (auth-health.ts) from the credential alone: static, ok, expiring, expired or missing.
187
+ const statusOf = (credential, now) => {
188
+ if (credential.type === 'api_key')
189
+ return hasSecret(credential.key, credential.keyRef) ? 'static' : 'missing';
190
+ if (credential.type === 'token') {
191
+ if (!hasSecret(credential.token, credential.tokenRef))
192
+ return 'missing';
193
+ const state = expiryState(credential.expires, now);
194
+ if (state === 'invalid')
195
+ return 'missing';
196
+ if (state === 'expired')
197
+ return 'expired';
198
+ if (credential.expires === undefined)
199
+ return 'static';
200
+ return credential.expires - now <= EXPIRING_MS ? 'expiring' : 'ok';
201
+ }
202
+ if (!hasSecret(credential.access, undefined) && !hasSecret(credential.refresh, undefined))
203
+ return 'missing';
204
+ const expires = credential.expires;
205
+ const state = expiryState(expires, now);
206
+ if (state === 'invalid' || state === 'missing')
207
+ return 'missing';
208
+ if (state === 'expired')
209
+ return 'expired';
210
+ return expires - now <= OAUTH_WARN_MS ? 'expiring' : 'ok';
211
+ };
212
+ const entryPrimaryOf = (id) => {
213
+ const entries = configBox.config.agents?.entries;
214
+ const entry = entries?.[id];
215
+ const ref = entry?.model;
216
+ if (typeof ref === 'string')
217
+ return ref;
218
+ if (isPlainObject(ref) && typeof ref.primary === 'string')
219
+ return ref.primary;
220
+ return undefined;
221
+ };
222
+ const primaryOfId = (id) => entryPrimaryOf(id) ?? models.get(id);
223
+ const setPrimaryOfId = (id, primary) => {
224
+ models.set(id, primary);
225
+ const entries = configBox.config.agents?.entries;
226
+ const entry = entries?.[id];
227
+ if (entry && isPlainObject(entry)) {
228
+ const current = entry.model;
229
+ if (isPlainObject(current))
230
+ current.primary = primary;
231
+ else
232
+ entry.model = primary;
233
+ }
234
+ };
235
+ const signInAgent = (id, provider, extra) => {
118
236
  const agent = ensureAgent(id);
119
237
  if (!agent.providers.includes(provider))
120
238
  agent.providers.push(provider);
239
+ // The pin names the first profile of a provider `<provider>:default` and points the agent's model at it.
240
+ storeOf(id)[`${provider}:default`] = { provider, type: 'oauth', status: 'ok', access: 'fake-access',
241
+ refresh: 'fake-refresh', expires: Date.now() + 30 * 86_400_000, accountId: `fake-${id}`, ...extra };
242
+ orderOf(id)[provider] = [`${provider}:default`];
243
+ setPrimaryOfId(id, `${provider}/default@${provider}:default`);
121
244
  };
122
245
  // Every transport the factory made; events fan out to all of them, drop() closes all of them.
123
246
  const live = new Set();
@@ -213,20 +336,69 @@ export function fakeGateway(script, o) {
213
336
  }),
214
337
  'agents.create': (p) => {
215
338
  const id = String(p.name);
339
+ if (agents.has(id))
340
+ throw new Error(`agent "${id}" already exists`);
216
341
  const workspace = p.workspace ? String(p.workspace) : undefined;
342
+ if (workspace) {
343
+ const files = workspaces.get(workspace) ?? new Map();
344
+ workspaces.set(workspace, files);
345
+ const identity = files.get('IDENTITY.md') ?? '# IDENTITY.md - Agent Identity\n\n';
346
+ files.set('IDENTITY.md', IDENTITY_NAME.test(identity) ? identity.replace(IDENTITY_NAME, `- **Name:** ${id}`) : `${identity}- **Name:** ${id}\n`);
347
+ }
217
348
  // With rosterApplyMs the fake mirrors the pinned engine: create answers before the roster hot-reload
218
349
  // makes the agent visible to agents.list (Crewhouse saw ~113 ms).
219
350
  if (o?.rosterApplyMs)
220
351
  setTimeout(() => ensureAgent(id, workspace), o.rosterApplyMs);
221
352
  else
222
353
  ensureAgent(id, workspace);
223
- return { id };
354
+ return { ok: true, agentId: id, name: id, workspace: workspace ?? `workspace-${id}` };
355
+ },
356
+ // deleteFiles: false keeps the workspace, as the kit always asks; the agent's own store and model go.
357
+ 'agents.delete': (p) => {
358
+ const id = String(p.agentId);
359
+ if (!agents.delete(id))
360
+ throw new Error(`agent "${id}" not found`);
361
+ profiles.delete(id);
362
+ orders.delete(id);
363
+ models.delete(id);
364
+ return { ok: true, agentId: id, removedBindings: 0 };
365
+ },
366
+ 'agents.files.get': (p) => fileAnswer(String(p.agentId), String(p.name)),
367
+ 'agents.files.set': (p) => {
368
+ filesOf(String(p.agentId)).files.set(String(p.name), String(p.content));
369
+ return { ok: true, ...fileAnswer(String(p.agentId), String(p.name)) };
224
370
  },
225
371
  // One profile per signed-in provider, `<provider>:default`, as the pin lists an agent's own profiles.
226
- 'models.authStatus': (p) => ({
227
- providers: (agents.get(String(p?.agentId ?? 'main'))?.providers ?? []).map((provider) => ({ provider,
228
- profiles: [{ profileId: `${provider}:default`, type: 'oauth', status: 'ok' }] })),
229
- }),
372
+ // Plus its cached usage once warm: cold on the first answer carrying the provider (no `usage`), then the
373
+ // stored windows.
374
+ 'models.authStatus': (p) => {
375
+ const id = String(p?.agentId ?? 'main');
376
+ const store = storeOf(id);
377
+ const now = Date.now();
378
+ return {
379
+ providers: (agents.get(id)?.providers ?? []).map((provider) => {
380
+ const profileId = `${provider}:default`;
381
+ const credential = store[profileId];
382
+ const key = `${id}\n${provider}`;
383
+ const cold = !warmed.has(key);
384
+ warmed.add(key);
385
+ const windows = cold ? undefined : usage.get(id)?.get(provider);
386
+ return { provider,
387
+ profiles: [{ profileId, type: credential?.type ?? 'oauth',
388
+ status: credential ? statusOf(credential, now) : 'ok' }],
389
+ ...(windows !== undefined ? { usage: { windows } } : {}) };
390
+ }),
391
+ };
392
+ },
393
+ // The runtime-allowed catalog for the agent's signed-in providers (O14): one available row each. A test
394
+ // scripting exact availability scripts this method with `handle`.
395
+ 'models.list': (p) => {
396
+ const id = String(p?.agentId ?? 'main');
397
+ return {
398
+ models: (agents.get(id)?.providers ?? []).map((provider) => ({ id: `${provider}/default`,
399
+ name: 'Default', provider, available: true })),
400
+ };
401
+ },
230
402
  'models.authLogout': (p) => {
231
403
  const agent = agents.get(String(p?.agentId ?? 'main'));
232
404
  // The pin refuses a profileIds list naming a profile the agent does not hold.
@@ -235,6 +407,17 @@ export function fakeGateway(script, o) {
235
407
  throw new Error('profileIds contain unavailable auth profiles');
236
408
  if (agent)
237
409
  agent.providers = agent.providers.filter((provider) => provider !== p.provider);
410
+ const store = profiles.get(String(p.agentId ?? 'main'));
411
+ if (store)
412
+ for (const profileId of Object.keys(store))
413
+ if (store[profileId].provider === p.provider)
414
+ delete store[profileId];
415
+ const order = orders.get(String(p.agentId ?? 'main'));
416
+ if (order)
417
+ delete order[String(p.provider)];
418
+ const pin = models.get(String(p.agentId ?? 'main'));
419
+ if (typeof pin === 'string' && pin.includes(`@${String(p.provider)}:`))
420
+ models.delete(String(p.agentId ?? 'main'));
238
421
  if (keys.get(String(p.agentId))?.provider === p.provider)
239
422
  keys.delete(String(p.agentId));
240
423
  return {};
@@ -255,6 +438,102 @@ export function fakeGateway(script, o) {
255
438
  }
256
439
  return { ok: key.sealed, ...(key.sealed ? { model: key.model } : {}) };
257
440
  },
441
+ // Account sealing (5.15): the same five actions, branches and refusals the shipped plugin method serves.
442
+ 'byokit.accounts': (p) => {
443
+ const action = String(p.action ?? '');
444
+ const entries = configBox.config.agents?.entries;
445
+ const known = (id) => Boolean(id) && AGENT_ID.test(id) && ((Boolean(entries) && id in entries) || agents.has(id));
446
+ if (action === 'adopt') {
447
+ const from = String(p.from), to = String(p.to), provider = String(p.provider);
448
+ if (from === to || !known(from) || !known(to))
449
+ return { ok: false };
450
+ if (typeof p.who !== 'string' || !p.who)
451
+ return { ok: false };
452
+ const fromOrder = orderOf(from), named = fromOrder[provider] ?? [];
453
+ const fromId = named.length === 1 ? named[0] : undefined;
454
+ const credential = fromId ? storeOf(from)[fromId] : undefined;
455
+ if (!fromId || !credential || credential.provider !== provider || whoOf(credential) !== p.who)
456
+ return { ok: false };
457
+ const target = primaryOfId(to);
458
+ if (typeof target !== 'string')
459
+ return { ok: false };
460
+ const toStore = storeOf(to), toOrder = orderOf(to);
461
+ for (const [otherId, other] of Object.entries(toStore))
462
+ if (ACCOUNT_AGENT.test(to) ? otherId !== fromId : other.provider === provider && otherId !== fromId)
463
+ delete toStore[otherId];
464
+ toStore[fromId] = credential;
465
+ toOrder[provider] = [fromId];
466
+ if (!ensureAgent(to).providers.includes(provider))
467
+ ensureAgent(to).providers.push(provider);
468
+ delete storeOf(from)[fromId];
469
+ if (fromOrder[provider]) {
470
+ fromOrder[provider] = fromOrder[provider].filter((x) => x !== fromId);
471
+ if (!fromOrder[provider].length)
472
+ delete fromOrder[provider];
473
+ }
474
+ if (!Object.values(storeOf(from)).some((c) => c.provider === provider))
475
+ ensureAgent(from).providers = ensureAgent(from).providers.filter((x) => x !== provider);
476
+ const cut = target.lastIndexOf('@');
477
+ setPrimaryOfId(to, `${cut > 0 ? target.slice(0, cut) : target}@${fromId}`);
478
+ return { ok: true };
479
+ }
480
+ const id = String(p.agentId ?? '');
481
+ if (!known(id))
482
+ return { ok: false };
483
+ const store = storeOf(id), order = orderOf(id);
484
+ if (action === 'list')
485
+ return { ok: true, profiles: Object.entries(store).map(([profileId, credential]) => ({ provider: credential.provider,
486
+ profileId, type: credential.type, status: statusOf(credential, Date.now()), ...(whoOf(credential) ? { who: whoOf(credential) } : {}) })) };
487
+ if (action === 'clear') {
488
+ profiles.set(id, {});
489
+ orders.set(id, {});
490
+ const cleared = agents.get(id);
491
+ if (cleared)
492
+ cleared.providers = [];
493
+ return { ok: true };
494
+ }
495
+ if (action === 'ready') {
496
+ const provider = String(p.provider ?? '');
497
+ const named = order[provider] ?? [];
498
+ const credential = named.length === 1 ? store[named[0]] : undefined;
499
+ if (!provider || !credential || credential.provider !== provider)
500
+ return { ok: false };
501
+ const status = statusOf(credential, Date.now());
502
+ if (status === 'missing' || status === 'expired')
503
+ return { ok: false };
504
+ return { ok: true, type: credential.type, ...(typeof credential.expires === 'number' ? { expiresAt: credential.expires } : {}),
505
+ ...(whoOf(credential) ? { who: whoOf(credential) } : {}) };
506
+ }
507
+ if (action === 'seal') {
508
+ const primary = primaryOfId(id) ?? '';
509
+ const at = primary.lastIndexOf('@');
510
+ if (at < 1)
511
+ return { ok: false };
512
+ const profileId = primary.slice(at + 1);
513
+ const credential = store[profileId];
514
+ const account = ACCOUNT_AGENT.test(id);
515
+ if (!credential || credential.provider !== p.provider || (!account && credential.type === 'api_key'))
516
+ return { ok: false };
517
+ for (const [otherId, other] of Object.entries(store))
518
+ if (account ? otherId !== profileId : other.provider === p.provider && otherId !== profileId)
519
+ delete store[otherId];
520
+ // An account agent's whole order is replaced; a member's keeps its other providers, as plugin/accounts.js does.
521
+ const provider = String(p.provider);
522
+ if (account)
523
+ orders.set(id, { [provider]: [profileId] });
524
+ else
525
+ order[provider] = [profileId];
526
+ const sealedAgent = agents.get(id);
527
+ if (sealedAgent) {
528
+ if (account)
529
+ sealedAgent.providers = [provider];
530
+ else if (!sealedAgent.providers.includes(provider))
531
+ sealedAgent.providers.push(provider);
532
+ }
533
+ return { ok: true, ...(whoOf(credential) ? { who: whoOf(credential) } : {}) };
534
+ }
535
+ return { ok: false };
536
+ },
258
537
  'openclaw.setup.activate': (p) => {
259
538
  if (p.kind !== 'api-key')
260
539
  throw new Error('only the API-key setup path is simulated');
@@ -267,8 +546,11 @@ export function fakeGateway(script, o) {
267
546
  return { ok: true, status: 'ok', modelRef: model };
268
547
  },
269
548
  'openclaw.setup.auth.start': (p) => {
549
+ // The pin's single setup admission: a second start while a session runs is refused.
550
+ if (wizards.size)
551
+ throw new Error('setup is already in progress');
270
552
  const sessionId = String(p.sessionId ?? `fake-${randomUUID()}`);
271
- wizards.set(sessionId, { agentId: String(p.agentId ?? 'main'), answered: false, pulls: 0 });
553
+ wizards.set(sessionId, { agentId: String(p.agentId ?? 'main'), authChoice: String(p.authChoice ?? ''), answered: false, pulls: 0 });
272
554
  return { sessionId, done: false, status: 'running' };
273
555
  },
274
556
  'wizard.next': (p) => {
@@ -280,7 +562,7 @@ export function fakeGateway(script, o) {
280
562
  return { done: false, step: PROGRESS_STEP };
281
563
  }
282
564
  if (!session.answered)
283
- return { done: false, step: DEVICE_STEP };
565
+ return { done: false, step: session.authChoice === 'openai' ? BROWSER_STEP : DEVICE_STEP };
284
566
  if (session.pulls++ === 0)
285
567
  return { done: false, step: PROGRESS_STEP };
286
568
  wizards.delete(String(p.sessionId));
@@ -397,8 +679,7 @@ export function fakeGateway(script, o) {
397
679
  const hello = () => ({
398
680
  protocol: PROTOCOL_VERSION,
399
681
  server: { version: ENGINE_VERSION },
400
- // The methods the fake serves. The generated tables are type-only until O2 ships runtime names; O11's
401
- // generated.test.ts cross-checks the real engine's hello against the committed tables.
682
+ // The methods the fake serves. The generated tables are type-only until O2 ships runtime names.
402
683
  methods: [...handlers.keys()].sort(),
403
684
  events: [...EVENTS],
404
685
  });
@@ -457,6 +738,14 @@ export function fakeGateway(script, o) {
457
738
  failNext,
458
739
  drop,
459
740
  handle: (method, fn) => { handlers.set(method, fn); },
741
+ setUsage: (agentId, provider, windows) => {
742
+ let perAgent = usage.get(agentId);
743
+ if (!perAgent) {
744
+ perAgent = new Map();
745
+ usage.set(agentId, perAgent);
746
+ }
747
+ perAgent.set(provider, windows);
748
+ },
460
749
  transport: factory({ port: 0, token: 'test', identityPath: '', bridgeSock: '' }),
461
750
  };
462
751
  }
package/dist/types.d.ts CHANGED
@@ -50,6 +50,8 @@ export interface RunSpec<S extends OutputSchema | undefined = OutputSchema | und
50
50
  }[];
51
51
  thinking?: 'off' | 'low' | 'medium' | 'high';
52
52
  model?: string;
53
+ account?: AccountRef | 'default' | 'auto';
54
+ needs?: string[];
53
55
  /** Explicit paid key choice. Uses a separate member-owned agent and separate session history. */
54
56
  auth?: 'apiKey';
55
57
  tools?: string[];
@@ -60,6 +62,14 @@ export type RunEvent = {
60
62
  } | {
61
63
  type: 'thinking';
62
64
  tokens: number;
65
+ } | {
66
+ type: 'account';
67
+ account: AccountId;
68
+ model: string;
69
+ sessionKey: string;
70
+ how: 'chosen' | 'default' | 'auto';
71
+ why: PickWhy;
72
+ considered: Considered[];
63
73
  } | {
64
74
  type: 'text';
65
75
  text: string;
@@ -98,6 +108,8 @@ export type RunEnd<T = unknown> = {
98
108
  data?: T;
99
109
  usage?: RunUsage;
100
110
  planWindow?: PlanWindow;
111
+ account?: AccountId;
112
+ model?: string;
101
113
  } | {
102
114
  ok: false;
103
115
  aborted: true;
@@ -319,6 +331,13 @@ export type AccountPick = {
319
331
  code: 'none' | 'not_included' | 'unknown_account' | 'bound' | 'paid';
320
332
  reason: string;
321
333
  considered: Considered[];
334
+ } | {
335
+ ok: false;
336
+ code: 'resting';
337
+ account: AccountId;
338
+ until?: number;
339
+ reason: string;
340
+ considered: Considered[];
322
341
  };
323
342
  export type MoveResult = {
324
343
  ok: true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/openclaw",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
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,17 +60,17 @@
60
60
  "prepack": "tsc -b && node ../../scripts/fix-words-dts.cjs"
61
61
  },
62
62
  "dependencies": {
63
- "@byokit/pair": "0.9.0",
64
- "@byokit/relay": "0.6.0",
65
- "@byokit/discover": "0.7.0",
63
+ "@byokit/pair": "0.10.0",
64
+ "@byokit/relay": "0.6.1",
65
+ "@byokit/discover": "0.8.1",
66
66
  "@byokit/seal": "0.3.0",
67
- "@byokit/secrets": "0.5.0",
67
+ "@byokit/secrets": "0.5.1",
68
68
  "@openclaw/gateway-client": "2026.8.35",
69
69
  "@openclaw/gateway-protocol": "2026.8.35",
70
70
  "ws": "8.22.0"
71
71
  },
72
72
  "devDependencies": {
73
- "@byokit/ui": "0.6.0"
73
+ "@byokit/ui": "0.7.0"
74
74
  },
75
75
  "publishConfig": {
76
76
  "access": "public"