@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.
- package/CHANGELOG.md +21 -0
- package/README.md +7 -1
- package/dist/accounts.d.ts +47 -0
- package/dist/accounts.js +499 -0
- package/dist/auth-store.js +20 -0
- package/dist/device.d.ts +4 -1
- package/dist/engine.js +1 -1
- package/dist/kit.d.ts +25 -1
- package/dist/kit.js +51 -0
- package/dist/locks.d.ts +5 -0
- package/dist/locks.js +85 -0
- package/dist/members.d.ts +3 -0
- package/dist/members.js +13 -9
- package/dist/pick.d.ts +7 -3
- package/dist/pick.js +34 -5
- package/dist/runs.d.ts +7 -0
- package/dist/runs.js +87 -9
- package/dist/signin.d.ts +1 -0
- package/dist/signin.js +12 -2
- package/dist/testing/contract.js +0 -1
- package/dist/testing/fake-gateway.d.ts +6 -0
- package/dist/testing/fake-gateway.js +300 -11
- package/dist/types.d.ts +19 -0
- package/package.json +6 -6
- package/plugin/accounts.js +224 -0
- package/plugin/index.js +2 -1
|
@@ -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
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
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.
|
|
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.
|
|
64
|
-
"@byokit/relay": "0.6.
|
|
65
|
-
"@byokit/discover": "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.
|
|
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.
|
|
73
|
+
"@byokit/ui": "0.7.0"
|
|
74
74
|
},
|
|
75
75
|
"publishConfig": {
|
|
76
76
|
"access": "public"
|