@byokit/openclaw 0.14.2 → 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 +16 -0
- package/README.md +8 -1
- package/dist/accounts.js +7 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +5 -1
- package/dist/engine.d.ts +7 -0
- package/dist/engine.js +23 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/keys.d.ts +10 -1
- package/dist/keys.js +36 -6
- package/dist/kit.d.ts +13 -2
- package/dist/kit.js +72 -7
- package/dist/members.d.ts +3 -0
- package/dist/members.js +10 -0
- package/dist/plugins.d.ts +17 -0
- package/dist/plugins.js +40 -0
- package/dist/routes.json +288 -288
- package/dist/signin.d.ts +5 -0
- package/dist/signin.js +50 -0
- package/dist/testing/fake-gateway.js +8 -4
- package/engine/plugins.json +150 -0
- package/package.json +6 -6
- package/policy/policy.mjs +43 -14
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
|
@@ -4,6 +4,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
4
4
|
import { createServer } from 'node:http';
|
|
5
5
|
import { activate, LATE_RESTART } from './activation.js';
|
|
6
6
|
import { authStatus } from './auth-status.js';
|
|
7
|
+
import { LIVE_CATALOGS } from './constants.js';
|
|
7
8
|
import { routes } from './routes.js';
|
|
8
9
|
import { signedInProviders } from './runs.js';
|
|
9
10
|
import { words } from './words.js';
|
|
@@ -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.
|
|
@@ -380,11 +380,15 @@ export function fakeGateway(script, o) {
|
|
|
380
380
|
// deleteFiles: false keeps the workspace, as the kit always asks; the agent's own store and model go.
|
|
381
381
|
'agents.delete': (p) => {
|
|
382
382
|
const id = String(p.agentId);
|
|
383
|
-
if (!agents.
|
|
383
|
+
if (!agents.has(id))
|
|
384
384
|
throw new Error(`agent "${id}" not found`);
|
|
385
|
-
profiles.delete(id);
|
|
386
|
-
|
|
387
|
-
|
|
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();
|
|
388
392
|
return { ok: true, agentId: id, removedBindings: 0 };
|
|
389
393
|
},
|
|
390
394
|
// model: null clears the agent's primary (config.patch cannot delete a key); a string sets it.
|
|
@@ -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.24.
|
|
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) {
|