@byokit/openclaw 0.14.0 → 0.14.4
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 +38 -0
- package/README.md +12 -2
- package/dist/accounts.js +11 -5
- package/dist/approvals.js +1 -1
- package/dist/auth-status.js +1 -1
- package/dist/auth-store.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/browser/host.js +3 -3
- package/dist/browser/verify.js +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +7 -3
- 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 +10 -0
- package/dist/engine.js +45 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -8
- package/dist/keys.d.ts +14 -1
- package/dist/keys.js +56 -8
- package/dist/kit.d.ts +15 -1
- package/dist/kit.js +99 -31
- package/dist/link.js +5 -5
- package/dist/members.d.ts +3 -0
- package/dist/members.js +10 -0
- package/dist/migrate.js +3 -3
- package/dist/pick.js +1 -1
- package/dist/plugins.d.ts +17 -0
- package/dist/plugins.js +40 -0
- package/dist/routes.json +288 -288
- package/dist/runs.js +6 -6
- package/dist/signin.d.ts +5 -0
- package/dist/signin.js +55 -5
- package/dist/testing/browser.js +2 -2
- package/dist/testing/contract.js +3 -3
- package/dist/testing/fake-gateway.js +14 -8
- 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/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/engine/plugins.json +150 -0
- package/package.json +6 -6
- package/policy/policy.mjs +43 -14
package/dist/runs.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Runs: one Gateway run per spec, streamed through typed events, ending in a classified RunEnd (5.8, O8).
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
|
-
import { authStatus } from
|
|
5
|
-
import { outputSchema } from
|
|
6
|
-
import { words } from
|
|
7
|
-
import { classify } from
|
|
8
|
-
import { providerOf } from
|
|
9
|
-
import { catalogModelOf, clampThinking, levelsOf } from
|
|
4
|
+
import { authStatus } from './auth-status.js';
|
|
5
|
+
import { outputSchema } from './output.js';
|
|
6
|
+
import { words } from './words.js';
|
|
7
|
+
import { classify } from './classify.js';
|
|
8
|
+
import { providerOf } from './pick.js';
|
|
9
|
+
import { catalogModelOf, clampThinking, levelsOf } from './thinking.js';
|
|
10
10
|
// The gateway gives up on a silent run after an hour; we wait ten minutes longer for the reply frame itself.
|
|
11
11
|
const WAIT_MS = 3_600_000;
|
|
12
12
|
const WAIT_CLIENT_MS = 3_610_000;
|
package/dist/signin.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export type SignInCtx = {
|
|
|
15
15
|
restarted(authBefore: string | undefined, signal?: AbortSignal): Promise<boolean>;
|
|
16
16
|
stopSignal?: AbortSignal;
|
|
17
17
|
fresh?: boolean;
|
|
18
|
+
setProviderHeaders?(provider: string, headers: Record<string, string> | null): Promise<void>;
|
|
19
|
+
listWorkspaces?(key: string, signal: AbortSignal): Promise<{
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
}[]>;
|
|
18
23
|
};
|
|
19
24
|
/**
|
|
20
25
|
* Drive the engine's provider-owned login. `on` sees every view the person watches; `done` resolves once, and
|
package/dist/signin.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// sign-in; OAuth credentials stay in the engine/CLI. Explicit API keys go only to engine activation (D11).
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { createServer } from 'node:http';
|
|
5
|
-
import { activate, LATE_RESTART } from
|
|
6
|
-
import { authStatus } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
5
|
+
import { activate, LATE_RESTART } from './activation.js';
|
|
6
|
+
import { authStatus } from './auth-status.js';
|
|
7
|
+
import { LIVE_CATALOGS } from './constants.js';
|
|
8
|
+
import { routes } from './routes.js';
|
|
9
|
+
import { signedInProviders } from './runs.js';
|
|
10
|
+
import { words } from './words.js';
|
|
10
11
|
// The pin's wizard contract (2026.8.1): steps arrive only from `wizard.next` (wizard.status answers {status, error}
|
|
11
12
|
// and never carries a step), and one session at a time holds the gateway's single setup admission.
|
|
12
13
|
const BUSY = /setup is already in progress|SETUP_ADMISSION_BUSY/i;
|
|
@@ -298,6 +299,55 @@ export function signIn(ctx, member, o, on) {
|
|
|
298
299
|
if (!apiKey)
|
|
299
300
|
return { state: 'failed', via, why: signal.aborted ? 'declined' : 'expired' };
|
|
300
301
|
secret = apiKey.trim() || undefined;
|
|
302
|
+
// A live-catalog key can act for several workspaces, and the Go gateway refuses every call until one is
|
|
303
|
+
// named (403 Workspace selection is required): the engine carries the workspace only from its provider
|
|
304
|
+
// config headers, so resolve it here the way the kit's key lane does, and write the header before the
|
|
305
|
+
// activation so the activation probe itself carries it. One workspace is used unasked; several ask here,
|
|
306
|
+
// answered through the same paste (an id or an exact name); a key the console refuses (a workspace-bound
|
|
307
|
+
// service key) needs none; a listing that cannot be read fails the sign-in before anything changes.
|
|
308
|
+
if (secret && keyRoute && LIVE_CATALOGS.includes(keyRoute.provider) && ctx.setProviderHeaders && ctx.listWorkspaces) {
|
|
309
|
+
let workspaces;
|
|
310
|
+
try {
|
|
311
|
+
workspaces = await ctx.listWorkspaces(secret, signal);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return { state: 'failed', via, why: 'failed', error: 'The workspace list could not be read. Try again.' };
|
|
315
|
+
}
|
|
316
|
+
if (signal.aborted)
|
|
317
|
+
return cancelled();
|
|
318
|
+
const pickable = workspaces.filter((w) => w.id);
|
|
319
|
+
let orgId = workspaces.length === 1 ? pickable[0]?.id ?? '' : '';
|
|
320
|
+
let retrying = false;
|
|
321
|
+
while (orgId === '' && pickable.length > 1) {
|
|
322
|
+
if (signal.aborted)
|
|
323
|
+
return cancelled();
|
|
324
|
+
// A re-ask says a different sentence, so an app that watches the prompt sees the answer was not taken.
|
|
325
|
+
// Names alone: the ids are unbreakable noise on a phone; the answer may still be an id.
|
|
326
|
+
say({ prompt: `${retrying ? 'That is not one of them. ' : ''}Which workspace? Paste one of: ${pickable.map((w) => w.name).join(', ')}` });
|
|
327
|
+
retrying = true;
|
|
328
|
+
const text = returned ?? await new Promise((resolve) => {
|
|
329
|
+
const stop = (value) => { clearTimeout(timer); signal.removeEventListener('abort', abort); pasteIn = undefined; resolve(value); };
|
|
330
|
+
const abort = () => stop();
|
|
331
|
+
const timer = setTimeout(abort, PASTE_MS);
|
|
332
|
+
pasteIn = stop;
|
|
333
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
334
|
+
if (signal.aborted)
|
|
335
|
+
abort();
|
|
336
|
+
});
|
|
337
|
+
returned = undefined;
|
|
338
|
+
if (!text)
|
|
339
|
+
return { state: 'failed', via, why: signal.aborted ? 'declined' : 'expired' };
|
|
340
|
+
const given = text.trim().toLowerCase();
|
|
341
|
+
const match = pickable.find((w) => w.id.toLowerCase() === given)
|
|
342
|
+
?? pickable.find((w) => w.name.toLowerCase() === given);
|
|
343
|
+
if (match)
|
|
344
|
+
orgId = match.id;
|
|
345
|
+
}
|
|
346
|
+
if (signal.aborted)
|
|
347
|
+
return cancelled();
|
|
348
|
+
if (orgId)
|
|
349
|
+
await ctx.setProviderHeaders(keyRoute.provider, { 'x-opencode-org-id': orgId });
|
|
350
|
+
}
|
|
301
351
|
}
|
|
302
352
|
// The commit point: a phone cancel or host shutdown that landed before it activates nothing. Done waits for the gateway restart an `auth` change causes (activation.ts).
|
|
303
353
|
// For Claude only host shutdown cuts that wait; a late restart has persisted the route and says saved. A key's cancel still declines its wait; a late key keeps the shared words.
|
package/dist/testing/browser.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fixtureBrowserHost, secureOrigin } from
|
|
2
|
-
import { emptySignIns, validateSignIns } from
|
|
1
|
+
import { fixtureBrowserHost, secureOrigin } from '../browser/host.js';
|
|
2
|
+
import { emptySignIns, validateSignIns } from '../browser/store.js';
|
|
3
3
|
function channel() {
|
|
4
4
|
let pending;
|
|
5
5
|
let value;
|
package/dist/testing/contract.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
// and whose calls answer `note: <input.text>`. An optional model is a started model stub the kit was pointed at with `useModelStub`.
|
|
8
8
|
import { test } from 'node:test';
|
|
9
9
|
import assert from 'node:assert/strict';
|
|
10
|
-
import { PROTOCOL_VERSION } from
|
|
11
|
-
import { GATEWAY_CAPS } from
|
|
12
|
-
import { releaseStub, stubHolding } from
|
|
10
|
+
import { PROTOCOL_VERSION } from '../constants.js';
|
|
11
|
+
import { GATEWAY_CAPS } from '../transport.js';
|
|
12
|
+
import { releaseStub, stubHolding } from './model-stub.js';
|
|
13
13
|
/** The contract drives pass-through by method name, so results arrive untyped and are narrowed per case. */
|
|
14
14
|
const call = (kit, method, params) => kit.call(method, params);
|
|
15
15
|
const REQUIRED_METHODS = [
|
|
@@ -5,9 +5,9 @@ import { connect } from 'node:net';
|
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
|
-
import { ENGINE_VERSION, PROTOCOL_VERSION } from
|
|
9
|
-
import { toolCalls } from
|
|
10
|
-
import { routes } from
|
|
8
|
+
import { ENGINE_VERSION, PROTOCOL_VERSION } from '../constants.js';
|
|
9
|
+
import { toolCalls } from './model-stub.js';
|
|
10
|
+
import { routes } from '../routes.js';
|
|
11
11
|
// The device-code script from Crewhouse's openclaw-wizard.test.ts, ported: the step, then progress, then done.
|
|
12
12
|
const DEVICE_STEP = {
|
|
13
13
|
id: 'step-device', type: 'note', executor: 'client',
|
|
@@ -40,7 +40,9 @@ const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArr
|
|
|
40
40
|
function mergeInto(target, patch) {
|
|
41
41
|
for (const [key, value] of Object.entries(patch)) {
|
|
42
42
|
const current = target[key];
|
|
43
|
-
if (
|
|
43
|
+
if (value === null)
|
|
44
|
+
delete target[key]; // JSON merge-patch: null deletes, as the engine's config.patch does.
|
|
45
|
+
else if (isPlainObject(current) && isPlainObject(value))
|
|
44
46
|
mergeInto(current, value);
|
|
45
47
|
else
|
|
46
48
|
target[key] = value;
|
|
@@ -378,11 +380,15 @@ export function fakeGateway(script, o) {
|
|
|
378
380
|
// deleteFiles: false keeps the workspace, as the kit always asks; the agent's own store and model go.
|
|
379
381
|
'agents.delete': (p) => {
|
|
380
382
|
const id = String(p.agentId);
|
|
381
|
-
if (!agents.
|
|
383
|
+
if (!agents.has(id))
|
|
382
384
|
throw new Error(`agent "${id}" not found`);
|
|
383
|
-
profiles.delete(id);
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
const drop = () => { agents.delete(id); profiles.delete(id); orders.delete(id); models.delete(id); };
|
|
386
|
+
// With rosterApplyMs the fake mirrors the pinned engine: delete answers before the roster hot-reload
|
|
387
|
+
// drops the agent from agents.list (the create side lags the same way).
|
|
388
|
+
if (o?.rosterApplyMs)
|
|
389
|
+
setTimeout(drop, o.rosterApplyMs);
|
|
390
|
+
else
|
|
391
|
+
drop();
|
|
386
392
|
return { ok: true, agentId: id, removedBindings: 0 };
|
|
387
393
|
},
|
|
388
394
|
// model: null clears the agent's primary (config.patch cannot delete a key); a string sets it.
|
package/dist/testing/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The kit's fakes, contract suite and scripted model (5.11). Built in O7.
|
|
2
|
-
export { fakeGateway } from
|
|
3
|
-
export { fakeBrowserHost, createBrowserHostFake, browserHostContract, memorySignInStore } from
|
|
4
|
-
export { openclawContract } from
|
|
5
|
-
export { STUB_USAGE, startModelStub, useModelStub, releaseStub, stubHolding, toolCalls } from
|
|
2
|
+
export { fakeGateway } from './fake-gateway.js';
|
|
3
|
+
export { fakeBrowserHost, createBrowserHostFake, browserHostContract, memorySignInStore } from './browser.js';
|
|
4
|
+
export { openclawContract } from './contract.js';
|
|
5
|
+
export { STUB_USAGE, startModelStub, useModelStub, releaseStub, stubHolding, toolCalls } from './model-stub.js';
|
|
@@ -17,6 +17,7 @@ export type StubCall = {
|
|
|
17
17
|
path: string;
|
|
18
18
|
body: StubRequest;
|
|
19
19
|
session?: string;
|
|
20
|
+
org?: string;
|
|
20
21
|
};
|
|
21
22
|
/** The token usage every stub reply reports when asked (`stream_options.include_usage`), per model call. */
|
|
22
23
|
export declare const STUB_USAGE: {
|
|
@@ -45,6 +46,9 @@ export type ModelStubOptions = {
|
|
|
45
46
|
idPattern?: RegExp;
|
|
46
47
|
routingMarker?: string;
|
|
47
48
|
onCall?: (call: StubCall) => void;
|
|
49
|
+
/** The Go gateway's own rule, for tests that prove the workspace header: a Go-route request without
|
|
50
|
+
* `x-opencode-org-id` (or with another workspace's) is refused exactly as the real one refuses it. */
|
|
51
|
+
goWorkspace?: string;
|
|
48
52
|
};
|
|
49
53
|
export declare function startModelStub(script?: string[], o?: ModelStubOptions): Promise<ModelStub>;
|
|
50
54
|
export declare function useModelStub(kit: OpenClawKit, stub: ModelStub): Promise<void>;
|
|
@@ -78,9 +78,16 @@ export function startModelStub(script = [], o = {}) {
|
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
const call = { authorization: String(req.headers.authorization ?? ''), path: req.url ?? '', body,
|
|
81
|
-
...(req.headers['x-opencode-session'] ? { session: String(req.headers['x-opencode-session']) } : {})
|
|
81
|
+
...(req.headers['x-opencode-session'] ? { session: String(req.headers['x-opencode-session']) } : {}),
|
|
82
|
+
...(req.headers['x-opencode-org-id'] ? { org: String(req.headers['x-opencode-org-id']) } : {}) };
|
|
82
83
|
calls.push(call);
|
|
83
84
|
o.onCall?.(call); // Fixture evidence is persisted before any response/hold awaits.
|
|
85
|
+
if (o.goWorkspace !== undefined && call.path.startsWith('/zen/go/')) {
|
|
86
|
+
if (call.org !== o.goWorkspace) {
|
|
87
|
+
res.writeHead(403, { 'content-type': 'application/json' });
|
|
88
|
+
return res.end(JSON.stringify({ type: 'error', error: { type: 'permission_error', message: 'Workspace selection is required' } }));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
84
91
|
if (req.url === '/api/embed' || req.url === '/api/embeddings') {
|
|
85
92
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
86
93
|
return res.end(JSON.stringify({
|
package/dist/transport.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { GatewayTransport } from './types.ts';
|
|
|
3
3
|
* Client caps for the operator connection. `approvals` makes this connection an approval client, so the
|
|
4
4
|
* engine delivers exec/plugin.approval.requested to it (B7); without it the broadcast set is empty.
|
|
5
5
|
*/
|
|
6
|
-
export declare const GATEWAY_CAPS: readonly [
|
|
6
|
+
export declare const GATEWAY_CAPS: readonly ['tool-events', 'approvals'];
|
|
7
7
|
/**
|
|
8
8
|
* Read the device key pair from `device.json`. The kit writes `{ privateKey, publicKey }` (PEM strings);
|
|
9
9
|
* existing Crewhouse state carries `{ deviceId, publicKeyPem, privateKeyPem }` instead. Both shapes are
|
package/dist/transport.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, createPrivateKey, createPublicKey, sign } from 'node:crypto';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { GatewayClient } from '@openclaw/gateway-client';
|
|
4
|
-
import { OPERATOR_SCOPES, PROTOCOL_VERSION } from
|
|
4
|
+
import { OPERATOR_SCOPES, PROTOCOL_VERSION } from './constants.js';
|
|
5
5
|
/**
|
|
6
6
|
* Client caps for the operator connection. `approvals` makes this connection an approval client, so the
|
|
7
7
|
* engine delivers exec/plugin.approval.requested to it (B7); without it the broadcast set is empty.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
{
|
|
2
|
+
"amazon-bedrock": {
|
|
3
|
+
"spec": "@openclaw/amazon-bedrock-provider@2026.8.35",
|
|
4
|
+
"integrity": "sha512-wYOiY7RaRo0SFXxcBVcqWnfwBH4TNc921Vlz7adBS+T5DIhtbnyzcAsMS1XsS9KvrYU3n1yW5SDdexvxop04hA=="
|
|
5
|
+
},
|
|
6
|
+
"amazon-bedrock-mantle": {
|
|
7
|
+
"spec": "@openclaw/amazon-bedrock-mantle-provider@2026.8.35",
|
|
8
|
+
"integrity": "sha512-bolUz+/osnBgarPBAko/+POthZ57JUXZ6T2nGMdQz+TfmdfnDhdZtXBRgy8JFjeDVgnNneWX4P1iuCgRRIsoJQ=="
|
|
9
|
+
},
|
|
10
|
+
"anthropic-vertex": {
|
|
11
|
+
"spec": "@openclaw/anthropic-vertex-provider@2026.8.35",
|
|
12
|
+
"integrity": "sha512-zQ5OYT7WvCgbyUnInz3+al/jCrN3RE2yMlC2wopVgjP9IMg1jANK0wPXArXWecT1p+j8+i2/TvxPI55U10Vdeg=="
|
|
13
|
+
},
|
|
14
|
+
"arcee": {
|
|
15
|
+
"spec": "@openclaw/arcee-provider@2026.8.35",
|
|
16
|
+
"integrity": "sha512-ViygKglSVjq5hQGVkduqe39Iiw9OBht/kTxOctLsgRvUzOWOqZvA9NvLybUtj230xLFCb/AVs1f3iGFRoXMBCA=="
|
|
17
|
+
},
|
|
18
|
+
"baseten": {
|
|
19
|
+
"spec": "@openclaw/baseten-provider@2026.8.35",
|
|
20
|
+
"integrity": "sha512-7zieofesRMB9fp41Ilh4VIFLIKJfRe1eT5ZYp3NKGwyON5rH0X11IcMXL1nRzIbIRyKnodfvl+H1EPO8NC4exA=="
|
|
21
|
+
},
|
|
22
|
+
"byteplus": {
|
|
23
|
+
"spec": "@openclaw/byteplus-provider@2026.8.35",
|
|
24
|
+
"integrity": "sha512-6Z9uHUxVSAt9TNNe5eiZfPhsExgInueEVCOeHxrZ6OybULJwDGx/cBJEY1LAy3MXzwCLX8ayENEtutcajxC0ew=="
|
|
25
|
+
},
|
|
26
|
+
"cerebras": {
|
|
27
|
+
"spec": "@openclaw/cerebras-provider@2026.8.35",
|
|
28
|
+
"integrity": "sha512-rqe2RMXn2hea0IpZuC01KwknMBOoH2fD9spGAvmffL8sX8qai+wSXW0Cn0pSOz9T+i862N5N3RXQP/K35QOvdA=="
|
|
29
|
+
},
|
|
30
|
+
"chutes": {
|
|
31
|
+
"spec": "@openclaw/chutes-provider@2026.8.35",
|
|
32
|
+
"integrity": "sha512-76+x2QS3NEgml0YnxV7fNbS7wO93oRtjlIFwkw0aUBYBzg1744aE3wEXXWkLcGpjVxp9bvgcpJ18Hp+gaW8F0A=="
|
|
33
|
+
},
|
|
34
|
+
"cloudflare-ai-gateway": {
|
|
35
|
+
"spec": "@openclaw/cloudflare-ai-gateway-provider@2026.8.35",
|
|
36
|
+
"integrity": "sha512-TjJJHTRdIiTVAM75w1xOed6VR0hQbAe30gNOc2ighi9G7FWU5wb3VeSQ9CtcJsAHYS5kurQNCf45owJfzkWGTg=="
|
|
37
|
+
},
|
|
38
|
+
"cohere": {
|
|
39
|
+
"spec": "@openclaw/cohere-provider@2026.8.35",
|
|
40
|
+
"integrity": "sha512-lA35F/ztGyYLAyRsrygogmL9WxU3M4X1lRSLCr1zZakXJDj0c6g46XtMmqxjtdQNCTo8TFni0gs4lqowZNq0OQ=="
|
|
41
|
+
},
|
|
42
|
+
"comfy": {
|
|
43
|
+
"spec": "@openclaw/comfy-provider@2026.8.35",
|
|
44
|
+
"integrity": "sha512-KHnjNwTCoy5l8Quq5zu7CwPBfg8XdTlI1W3GLg2CcamR+jksjbLpPzvrIObN8MineWVwpwmPgsdP3PyfyKxIaQ=="
|
|
45
|
+
},
|
|
46
|
+
"deepinfra": {
|
|
47
|
+
"spec": "@openclaw/deepinfra-provider@2026.8.35",
|
|
48
|
+
"integrity": "sha512-r4jktr88r3AFGmZRKtAjLkEOUQG5OAz+bXTwHpZxz4M28pbik3bJU2eBBaHAzahE7QdP9Xi3C3eIxoUl6/yi9w=="
|
|
49
|
+
},
|
|
50
|
+
"deepseek": {
|
|
51
|
+
"spec": "@openclaw/deepseek-provider@2026.8.35",
|
|
52
|
+
"integrity": "sha512-UVT+YusrPGSHLD7xd3YzJ45Hfe+ezFKEVBgaxPfYAUoXKvwN/OftG/kVga+bkjoxxSrp5Q+yI0Ec1kPBDhQ2RQ=="
|
|
53
|
+
},
|
|
54
|
+
"featherless": {
|
|
55
|
+
"spec": "@openclaw/featherless-provider@2026.8.35",
|
|
56
|
+
"integrity": "sha512-3XzG0avloplS8NVcNtq57JN91Sq6YnFcNSVcwz9IN8Q0oKcyxt5pabWfS6jCvsaBfV3sFaRf1Amn2PNrQKgHgQ=="
|
|
57
|
+
},
|
|
58
|
+
"fireworks": {
|
|
59
|
+
"spec": "@openclaw/fireworks-provider@2026.8.35",
|
|
60
|
+
"integrity": "sha512-CUms5nyK72D5VyNyXH5FZdy73gjlLMA3Vz6fpc6Z9ZtR6LxHjET7e91pM2cnjm6AkO4pUbh3sgiOeU/+lCYNPg=="
|
|
61
|
+
},
|
|
62
|
+
"gmi": {
|
|
63
|
+
"spec": "@openclaw/gmi-provider@2026.8.35",
|
|
64
|
+
"integrity": "sha512-OOLyjF58RdhwGgvaIyudhwpKzunSWbaOlyfUUr+oTFkOhb3ZzfLMx9Kqweql0ug04fh/aQ6s9IvX2jK2adAbBA=="
|
|
65
|
+
},
|
|
66
|
+
"groq": {
|
|
67
|
+
"spec": "@openclaw/groq-provider@2026.8.35",
|
|
68
|
+
"integrity": "sha512-/VmTp3GXaz1F06HfG0Xc83E8Ul/tsjQ6DRd6bscjmHOH8JtKv1U+AgQa8Uj5wMPmop9ttbqPXCqHuenMViankg=="
|
|
69
|
+
},
|
|
70
|
+
"kilocode": {
|
|
71
|
+
"spec": "@openclaw/kilocode-provider@2026.8.35",
|
|
72
|
+
"integrity": "sha512-O8hp8BQqAVElY2UXdE5EBBwzba7xevStF7TIi9TE59NTADK2JojK9bStF6rX4y9Ctp5NeMpf+KyYFTiSfm/N1A=="
|
|
73
|
+
},
|
|
74
|
+
"kimi": {
|
|
75
|
+
"spec": "@openclaw/kimi-provider@2026.8.35",
|
|
76
|
+
"integrity": "sha512-jeS3BmTXoa8A3OWXg6TB+vlKj1t+bvJFZQIvbXSGUNm2vIUSNXLYvgzNgmhF24MdwHwyBPdcUnJVZyMCklbEBw=="
|
|
77
|
+
},
|
|
78
|
+
"longcat": {
|
|
79
|
+
"spec": "@openclaw/longcat-provider@2026.8.35",
|
|
80
|
+
"integrity": "sha512-E4hHADSRpkHtiy6FfoHQpYD94usn+J0sVmY2fn94ftNNV4SjVvILqybjBiqfAlVy5FVOPRytL6P2y6hkdd52Cg=="
|
|
81
|
+
},
|
|
82
|
+
"meta": {
|
|
83
|
+
"spec": "@openclaw/meta-provider@2026.8.35",
|
|
84
|
+
"integrity": "sha512-3cEwNKmJW1wIwQE40crK1O4X4eo6n4xzH1v/7raa79DeoTOJ8pc8PWa66stv51ESwWhYDcCFVHU7XtnrbXyUXA=="
|
|
85
|
+
},
|
|
86
|
+
"mistral": {
|
|
87
|
+
"spec": "@openclaw/mistral-provider@2026.8.35",
|
|
88
|
+
"integrity": "sha512-TNULKvqsMMXz1ZNr89UPv9ucqcUYcw1hKLqoOo0yB/yRUazNErepds1qOzYJYA4lksylvhvvruDs9vIUMbfTjA=="
|
|
89
|
+
},
|
|
90
|
+
"moonshot": {
|
|
91
|
+
"spec": "@openclaw/moonshot-provider@2026.8.35",
|
|
92
|
+
"integrity": "sha512-gk9C4Hg/+K9taU/cRgNO77QzmPw46fo7klNB6y7qhefJQwoknbtCJLgnMpVEo97uiAxYxCTYRNvI+fOKF7Em5A=="
|
|
93
|
+
},
|
|
94
|
+
"novita": {
|
|
95
|
+
"spec": "@openclaw/novita-provider@2026.8.35",
|
|
96
|
+
"integrity": "sha512-TNui/LbLCsakaj677E+Xw2D1hRztQMOJWLj86JcqOfS7QzfEM6rA+FRs996HnXUIpqXeaefHc0qdVghKEXhgOw=="
|
|
97
|
+
},
|
|
98
|
+
"opencode": {
|
|
99
|
+
"spec": "@openclaw/opencode-provider@2026.8.35",
|
|
100
|
+
"integrity": "sha512-6wRKxfmgETuusys4K74cB0ixZVqQCgJBvD2g74cQAgx8w7BMr7+2965uYbU1sMjspreOigjgYLfePcMgV5conQ=="
|
|
101
|
+
},
|
|
102
|
+
"pixverse": {
|
|
103
|
+
"spec": "@openclaw/pixverse-provider@2026.8.35",
|
|
104
|
+
"integrity": "sha512-BbAp0Nfphem5LAytazWWAB5TJTM7L5zC7cWd+7r4iHU5iO7CyZYAGS5rjeLV3XEUwD2TAoWcHKBe8TiRXgsbaQ=="
|
|
105
|
+
},
|
|
106
|
+
"qianfan": {
|
|
107
|
+
"spec": "@openclaw/qianfan-provider@2026.8.35",
|
|
108
|
+
"integrity": "sha512-LSSz68Bv5/vRemKRrVNoDXA9zLx7yymMEY94fP5cgat9rsR83Fa6bA5hyEucutExz5wf2FJWmWqEJy2snm0k9A=="
|
|
109
|
+
},
|
|
110
|
+
"qwen": {
|
|
111
|
+
"spec": "@openclaw/qwen-provider@2026.8.35",
|
|
112
|
+
"integrity": "sha512-TaCx2LEOZgcXrWv0FLdBuFHoFvwDVNH+loGcO3MUIwhIBPoQTHD7Q+7DTOcLeK8qrQA/zGvWitT2WismCQJ7pw=="
|
|
113
|
+
},
|
|
114
|
+
"stepfun": {
|
|
115
|
+
"spec": "@openclaw/stepfun-provider@2026.8.35",
|
|
116
|
+
"integrity": "sha512-JBUKhN36cjUsMea8AOfX8yROEb7Zp01YPLpjzI/jnosalq2mhfYJY4kJTLou2RGwblDXx1dj0vB2HWidA+e/yw=="
|
|
117
|
+
},
|
|
118
|
+
"synthetic": {
|
|
119
|
+
"spec": "@openclaw/synthetic-provider@2026.8.35",
|
|
120
|
+
"integrity": "sha512-Lfs2lFTvsMEDNqZG2myoX8n7sbmIEVXMYV/Roa4umnPLsLT2aeqLkvWyesQWl9XRneSnGNtWS6yhbKLaqXU0Ng=="
|
|
121
|
+
},
|
|
122
|
+
"tencent": {
|
|
123
|
+
"spec": "@openclaw/tencent-provider@2026.8.35",
|
|
124
|
+
"integrity": "sha512-8khAe7ZaR0o3KPxiWnnyGaLFwDTF1ft1Cr3v1YrBntL1Nr8OvFM1XP/Lyifn3MJ7SK4lajYxLVsfuq2E18Jxeg=="
|
|
125
|
+
},
|
|
126
|
+
"venice": {
|
|
127
|
+
"spec": "@openclaw/venice-provider@2026.8.35",
|
|
128
|
+
"integrity": "sha512-c/Au2qkMeneJRKKm2o0/Fkf7dpcJR5RVJPmEPZhOsFaOUoq3zAk+Is/42khZ136Yj/UyQ09nFTUJsqmaeKoZVA=="
|
|
129
|
+
},
|
|
130
|
+
"vercel-ai-gateway": {
|
|
131
|
+
"spec": "@openclaw/vercel-ai-gateway-provider@2026.8.35",
|
|
132
|
+
"integrity": "sha512-gL9aGnfVGKVvB9mvMDRmIPOPTqQUqTr11DU60nn7BqDUCpLckoRUSXxzH3Rituxj+5dyoLI1LL5RiA9SPoImRA=="
|
|
133
|
+
},
|
|
134
|
+
"volcengine": {
|
|
135
|
+
"spec": "@openclaw/volcengine-provider@2026.8.35",
|
|
136
|
+
"integrity": "sha512-7dqdTtFgJcEBiSdzt5gLLPop/y6rK5FuobjiAGDwKxbPVENo0hmWJ8HGWbLKzRXerhzK20pvzF/+rr3OGDkRPQ=="
|
|
137
|
+
},
|
|
138
|
+
"vydra": {
|
|
139
|
+
"spec": "@openclaw/vydra-provider@2026.8.35",
|
|
140
|
+
"integrity": "sha512-Z0xHR8t+0Ge2GCgVDIlH1WLq4XeI5WoOgvkMvDvb4CQkxW+c251L0gbg37R6CNPkdfDFn8mh5/yIQrkazNj7dQ=="
|
|
141
|
+
},
|
|
142
|
+
"xiaomi": {
|
|
143
|
+
"spec": "@openclaw/xiaomi-provider@2026.8.35",
|
|
144
|
+
"integrity": "sha512-xtH3ESbMBcPQIciYiJCBP2ijd6MpP6tU/y27dJ3PGF8SluKZlbUfTUrqjT9U/NHo/bwafRISxL/HtAKYSm3Jkg=="
|
|
145
|
+
},
|
|
146
|
+
"zai": {
|
|
147
|
+
"spec": "@openclaw/zai-provider@2026.8.35",
|
|
148
|
+
"integrity": "sha512-g3ceSnLS84lNW2KlHznrO9vPXhX4ul1nQ+Dc0K4ccHdLOVfQ0DjjgV1NbT4CQZiTlN7Z2UFtGzl5EQeD1oCPfQ=="
|
|
149
|
+
}
|
|
150
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byokit/openclaw",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
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,10 +60,10 @@
|
|
|
60
60
|
"prepack": "tsc -b && node ../../scripts/fix-words-dts.cjs"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@byokit/accounts": "0.
|
|
64
|
-
"@byokit/pair": "0.10.
|
|
65
|
-
"@byokit/relay": "0.
|
|
66
|
-
"@byokit/discover": "0.8.
|
|
63
|
+
"@byokit/accounts": "0.24.3",
|
|
64
|
+
"@byokit/pair": "0.10.2",
|
|
65
|
+
"@byokit/relay": "0.8.0",
|
|
66
|
+
"@byokit/discover": "0.8.3",
|
|
67
67
|
"@byokit/seal": "0.3.0",
|
|
68
68
|
"@byokit/secrets": "0.5.1",
|
|
69
69
|
"@openclaw/gateway-client": "2026.8.35",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"ws": "8.22.0"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@byokit/ui": "0.
|
|
74
|
+
"@byokit/ui": "0.9.0"
|
|
75
75
|
},
|
|
76
76
|
"publishConfig": {
|
|
77
77
|
"access": "public"
|
package/policy/policy.mjs
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
// BYOKIT_TRUSTED_SKILLS path to the trusted-skills JSON file (Crewhouse's trusted-skills.json shape):
|
|
7
7
|
// either a list [{ id|name, version, sha256|hash }] or a map { "<id>": { version, sha256 } }.
|
|
8
8
|
// BYOKIT_OWN_ROOTS JSON array of own content roots; skills sourced under them are the app's own.
|
|
9
|
+
// BYOKIT_PLUGIN_PINS JSON array [{ spec: '<package>@<version>', integrity }] of the external plugins the kit
|
|
10
|
+
// installs (engine/plugins.json at the engine pin).
|
|
9
11
|
// Rules: any request kind containing 'depend' is blocked everywhere (own roots included);
|
|
10
12
|
// dependency installers (a skill installSpec carrying bins/formula/package/module/url/archive) are blocked;
|
|
11
13
|
// skill installs are allowed only from own roots or with an exact id + version + SKILL.md sha256 trusted match
|
|
12
14
|
// (the version is request.origin.version when present, else the SKILL.md frontmatter version);
|
|
13
|
-
// plugin installs are allowed only
|
|
15
|
+
// plugin installs are allowed only from the npm registry at exactly a pinned spec, with the pinned integrity.
|
|
14
16
|
import { createHash } from 'node:crypto';
|
|
15
17
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
16
18
|
import { relative, resolve } from 'node:path';
|
|
@@ -139,27 +141,54 @@ function decideSkill(request, trusted, roots) {
|
|
|
139
141
|
return allow();
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
function pins() {
|
|
145
|
+
try {
|
|
146
|
+
const list = JSON.parse(process.env.BYOKIT_PLUGIN_PINS ?? '[]');
|
|
147
|
+
return Array.isArray(list) ? list.filter((p) => isRecord(p) && typeof p.spec === 'string' && typeof p.integrity === 'string') : [];
|
|
148
|
+
} catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
142
153
|
function decidePlugin(request) {
|
|
143
154
|
const plugin = isRecord(request.plugin) ? request.plugin : {};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
// N4: the scope name above is self-declared (the candidate manifest's own
|
|
149
|
-
// `name`). Allow only with engine proof of registry origin on the pinned
|
|
150
|
-
// engine: source.kind 'npm' (engine-derived from the install request kind;
|
|
151
|
-
// npm installs always carry authority 'third-party') plus the
|
|
152
|
-
// operator-requested specifier in the @openclaw/ scope. Anything else is a
|
|
155
|
+
// N4: the names in `plugin` are self-declared (the candidate manifest's own `name`, or only its id for the
|
|
156
|
+
// dependency tree). Only engine proof of registry origin on the pinned engine counts: source.kind 'npm'
|
|
157
|
+
// (engine-derived from the install request kind) plus the operator-requested specifier. Anything else is a
|
|
153
158
|
// local/archive/git/file candidate spoofing the name. Fail closed.
|
|
154
159
|
const source = isRecord(request.source) ? request.source : undefined;
|
|
155
160
|
const specifier =
|
|
156
161
|
isRecord(request.request) && typeof request.request.requestedSpecifier === 'string'
|
|
157
162
|
? request.request.requestedSpecifier
|
|
158
163
|
: undefined;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
const npm = source?.kind === 'npm' && specifier !== undefined;
|
|
165
|
+
const scope = npm ? specifier : [plugin.packageName, plugin.pluginId, request.targetName].find((v) => typeof v === 'string');
|
|
166
|
+
if (!scope || !scope.startsWith('@openclaw/')) {
|
|
167
|
+
return block(`only the @openclaw/ plugin scope is allowed, not ${scope ?? request.targetName}`);
|
|
168
|
+
}
|
|
169
|
+
if (!npm) {
|
|
170
|
+
return block(`@openclaw/ plugin install is not from the npm registry: ${specifier ?? request.targetName}`, [
|
|
171
|
+
{ ruleId: 'plugin-source', severity: 'critical', message: 'self-declared @openclaw/ name without registry source proof' },
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
// B7: exactly a pinned spec (`<package>@<engine version>`), never a range, tag or latest.
|
|
175
|
+
const pin = pins().find((p) => p.spec === specifier);
|
|
176
|
+
if (!pin) return block(`not a pinned plugin version: ${specifier}`);
|
|
177
|
+
// The preflight carries the registry's resolution as a file; the engine then checks the installed package
|
|
178
|
+
// against that resolution's integrity, so a matching pin here holds for the bytes installed.
|
|
179
|
+
if (request.sourcePathKind === 'file') {
|
|
180
|
+
let resolution;
|
|
181
|
+
try {
|
|
182
|
+
resolution = JSON.parse(readFileSync(request.sourcePath, 'utf8')).resolution;
|
|
183
|
+
} catch {
|
|
184
|
+
return block(`pinned plugin integrity unreadable: ${specifier}`);
|
|
185
|
+
}
|
|
186
|
+
if (!isRecord(resolution) || `${resolution.name}@${resolution.version}` !== pin.spec) {
|
|
187
|
+
return block(`not a pinned plugin version: ${specifier}`);
|
|
188
|
+
}
|
|
189
|
+
if (resolution.integrity !== pin.integrity) return block(`pinned plugin integrity mismatch: ${specifier}`);
|
|
190
|
+
}
|
|
191
|
+
return allow();
|
|
163
192
|
}
|
|
164
193
|
|
|
165
194
|
function decide(request) {
|