mlclaw 0.3.1 → 0.3.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/Dockerfile +24 -12
- package/README.md +8 -6
- package/assets/hf-logo.png +0 -0
- package/dist/hf-tooling-seed.js +18 -0
- package/dist/mlclaw-space-runtime.js +479 -118
- package/dist/mlclaw.mjs +26 -15
- package/entrypoint.sh +13 -11
- package/package.json +7 -8
- package/hf-broker.scope.json +0 -38
|
@@ -26,7 +26,7 @@ var DEFAULT_BRAND_NAME = "ML Claw";
|
|
|
26
26
|
var DEFAULT_THEME_COLOR = "#111827";
|
|
27
27
|
var DEFAULT_LOGO_ASSET = "mlclaw.svg";
|
|
28
28
|
var DEFAULT_HUGGING_FACE_ASSET = "hf-logo.svg";
|
|
29
|
-
var
|
|
29
|
+
var DEFAULT_HUGGING_FACE_PNG_ASSET = "hf-logo.png";
|
|
30
30
|
function resolveBranding(env, agentName) {
|
|
31
31
|
const defaultName = defaultBrandName(agentName);
|
|
32
32
|
const name = cleanText(env.MLCLAW_BRAND_NAME) ?? defaultName;
|
|
@@ -41,16 +41,13 @@ function resolveBranding(env, agentName) {
|
|
|
41
41
|
),
|
|
42
42
|
favicon32Asset: normalizeAssetRef(
|
|
43
43
|
env.MLCLAW_BRAND_FAVICON_32 ?? env.MLCLAW_BRAND_FAVICON_PNG ?? env.MLCLAW_BRAND_FAVICON,
|
|
44
|
-
|
|
44
|
+
DEFAULT_HUGGING_FACE_PNG_ASSET
|
|
45
45
|
),
|
|
46
46
|
faviconIcoAsset: normalizeAssetRef(
|
|
47
47
|
env.MLCLAW_BRAND_FAVICON_ICO ?? env.MLCLAW_BRAND_FAVICON,
|
|
48
48
|
DEFAULT_HUGGING_FACE_ASSET
|
|
49
49
|
),
|
|
50
|
-
appleTouchIconAsset: normalizeAssetRef(
|
|
51
|
-
env.MLCLAW_BRAND_APPLE_TOUCH_ICON ?? env.MLCLAW_BRAND_ASSISTANT_AVATAR,
|
|
52
|
-
DEFAULT_ASSISTANT_AVATAR_ASSET
|
|
53
|
-
)
|
|
50
|
+
appleTouchIconAsset: normalizeAssetRef(env.MLCLAW_BRAND_APPLE_TOUCH_ICON, DEFAULT_HUGGING_FACE_PNG_ASSET)
|
|
54
51
|
};
|
|
55
52
|
}
|
|
56
53
|
function publicBranding(branding) {
|
|
@@ -62,33 +59,37 @@ function publicBranding(branding) {
|
|
|
62
59
|
};
|
|
63
60
|
}
|
|
64
61
|
function brandingManifest(branding) {
|
|
65
|
-
return `${JSON.stringify(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
62
|
+
return `${JSON.stringify(
|
|
63
|
+
{
|
|
64
|
+
name: branding.name,
|
|
65
|
+
short_name: branding.shortName,
|
|
66
|
+
description: `${branding.name} browser gateway`,
|
|
67
|
+
start_url: "./",
|
|
68
|
+
display: "standalone",
|
|
69
|
+
theme_color: branding.themeColor,
|
|
70
|
+
background_color: branding.themeColor,
|
|
71
|
+
icons: [
|
|
72
|
+
{
|
|
73
|
+
src: "./favicon.svg",
|
|
74
|
+
sizes: "any",
|
|
75
|
+
type: "image/svg+xml",
|
|
76
|
+
purpose: "any"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
src: "./favicon-32.png",
|
|
80
|
+
sizes: "32x32",
|
|
81
|
+
type: "image/png"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
src: "./apple-touch-icon.png",
|
|
85
|
+
sizes: "180x180",
|
|
86
|
+
type: "image/png"
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
null,
|
|
91
|
+
2
|
|
92
|
+
)}
|
|
92
93
|
`;
|
|
93
94
|
}
|
|
94
95
|
function defaultBrandName(agentName) {
|
|
@@ -4697,14 +4698,13 @@ var approvalSchema = external_exports.object({
|
|
|
4697
4698
|
pending_expires_at: external_exports.string().datetime({ offset: true }).optional(),
|
|
4698
4699
|
active_expires_at: external_exports.string().datetime({ offset: true }).optional(),
|
|
4699
4700
|
requested_duration_seconds: external_exports.number().int().positive().safe(),
|
|
4700
|
-
requested_max_uses: external_exports.number().int().positive().safe(),
|
|
4701
|
+
requested_max_uses: external_exports.number().int().positive().safe().nullable(),
|
|
4701
4702
|
granted_max_uses: external_exports.number().int().positive().safe().nullable(),
|
|
4702
4703
|
used_count: external_exports.number().int().nonnegative().safe(),
|
|
4703
4704
|
request_reason: external_exports.string().max(2e3).optional(),
|
|
4704
4705
|
decided_at: external_exports.string().datetime({ offset: true }).optional(),
|
|
4705
4706
|
decided_by: external_exports.string().max(200).optional(),
|
|
4706
4707
|
decided_on_behalf_of: external_exports.string().max(200).optional(),
|
|
4707
|
-
decision_reason: external_exports.string().max(2e3).optional(),
|
|
4708
4708
|
presentation: external_exports.object({
|
|
4709
4709
|
risk: external_exports.enum(["unknown", "low", "medium", "high", "critical"]),
|
|
4710
4710
|
title: external_exports.string().min(1).max(200),
|
|
@@ -4712,10 +4712,10 @@ var approvalSchema = external_exports.object({
|
|
|
4712
4712
|
facts: external_exports.array(displayFieldSchema).max(20).optional()
|
|
4713
4713
|
}).strict(),
|
|
4714
4714
|
presentation_unavailable: external_exports.boolean().optional(),
|
|
4715
|
-
allowed_actions: external_exports.array(external_exports.enum(["approve", "deny", "
|
|
4715
|
+
allowed_actions: external_exports.array(external_exports.enum(["approve", "deny", "revoke"])).max(3),
|
|
4716
4716
|
approval_bounds: external_exports.object({
|
|
4717
4717
|
max_duration_seconds: external_exports.number().int().positive().safe(),
|
|
4718
|
-
max_uses: external_exports.number().int().positive().safe()
|
|
4718
|
+
max_uses: external_exports.number().int().positive().safe().nullable()
|
|
4719
4719
|
}).strict().optional()
|
|
4720
4720
|
}).strict();
|
|
4721
4721
|
var approvalPageSchema = external_exports.object({
|
|
@@ -4808,11 +4808,10 @@ var BrokerOperatorClient = class {
|
|
|
4808
4808
|
expected_revision: decision.expectedRevision,
|
|
4809
4809
|
idempotency_key: decision.idempotencyKey,
|
|
4810
4810
|
on_behalf_of: decision.onBehalfOf,
|
|
4811
|
-
...decision.
|
|
4812
|
-
...decision.durationSeconds || decision.maxUses ? {
|
|
4811
|
+
...decision.durationSeconds !== void 0 || decision.maxUses !== void 0 ? {
|
|
4813
4812
|
constraints: {
|
|
4814
|
-
...decision.durationSeconds ? { duration_seconds: decision.durationSeconds } : {},
|
|
4815
|
-
...decision.maxUses ? { max_uses: decision.maxUses } : {}
|
|
4813
|
+
...decision.durationSeconds !== void 0 ? { duration_seconds: decision.durationSeconds } : {},
|
|
4814
|
+
...decision.maxUses !== void 0 ? { max_uses: decision.maxUses } : {}
|
|
4816
4815
|
}
|
|
4817
4816
|
} : {}
|
|
4818
4817
|
})
|
|
@@ -5105,7 +5104,9 @@ function loadConfig(env = process.env) {
|
|
|
5105
5104
|
routerToken: trim(env.MLCLAW_ROUTER_TOKEN ?? env.HF_ROUTER_TOKEN),
|
|
5106
5105
|
brokerAgentUrl: trim(env.MLCLAW_HF_BROKER_URL),
|
|
5107
5106
|
brokerAgentSecret: readOptionalSecret(trim(env.MLCLAW_HF_BROKER_AGENT_SECRET_FILE)),
|
|
5107
|
+
brokerAgentSecretFile: trim(env.MLCLAW_HF_BROKER_AGENT_SECRET_FILE),
|
|
5108
5108
|
operatorBrokers: loadOperatorBrokers(trim(env.MLCLAW_OPERATOR_BROKERS_FILE)),
|
|
5109
|
+
brokerKitPopoverDecisions: env.MLCLAW_BROKERKIT_POPOVER_DECISIONS === "1" || env.MLCLAW_BROKERKIT_POPOVER_DECISIONS === "true",
|
|
5109
5110
|
hubUrl: trim(env.HF_ENDPOINT) ?? "https://huggingface.co",
|
|
5110
5111
|
openaiCredentialFile: trim(env.MLCLAW_OPENAI_CREDENTIAL_FILE) ?? "/tmp/mlclaw-secrets/openai.env",
|
|
5111
5112
|
openaiCredentialStoreFile,
|
|
@@ -7360,7 +7361,81 @@ function signatureMatches(a, b) {
|
|
|
7360
7361
|
}
|
|
7361
7362
|
|
|
7362
7363
|
// src/mlclaw-space-runtime/delegated-brokerkit.ts
|
|
7363
|
-
import { createHash, createHmac as createHmac2, randomBytes as
|
|
7364
|
+
import { createHash, createHmac as createHmac2, randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
7365
|
+
|
|
7366
|
+
// src/mlclaw-space-runtime/delegated-revisions.ts
|
|
7367
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
7368
|
+
var DelegatedRevisions = class {
|
|
7369
|
+
epoch = randomBytes3(16).toString("base64url");
|
|
7370
|
+
revision = 0;
|
|
7371
|
+
material = "";
|
|
7372
|
+
current;
|
|
7373
|
+
waiters = /* @__PURE__ */ new Set();
|
|
7374
|
+
publish(material, value) {
|
|
7375
|
+
if (this.current && material === this.material) return this.current;
|
|
7376
|
+
this.material = material;
|
|
7377
|
+
this.revision += 1;
|
|
7378
|
+
this.current = value(this.cursor());
|
|
7379
|
+
for (const waiter of [...this.waiters])
|
|
7380
|
+
this.finish(waiter, { api_version: "brokerkit.io/operator-ui/v1", cursor: this.cursor(), changed: true });
|
|
7381
|
+
return this.current;
|
|
7382
|
+
}
|
|
7383
|
+
wait(cursor, waitSeconds, signal) {
|
|
7384
|
+
const observed = this.parse(cursor);
|
|
7385
|
+
if (observed === void 0) return Promise.reject(revisionError("cursor_expired"));
|
|
7386
|
+
if (observed !== this.revision) {
|
|
7387
|
+
return Promise.resolve({ api_version: "brokerkit.io/operator-ui/v1", cursor: this.cursor(), changed: true });
|
|
7388
|
+
}
|
|
7389
|
+
if (this.waiters.size >= 256) return Promise.reject(revisionError("source_unavailable"));
|
|
7390
|
+
if (signal?.aborted) return Promise.reject(abortError());
|
|
7391
|
+
return new Promise((resolve, reject) => {
|
|
7392
|
+
const waiter = {
|
|
7393
|
+
resolve,
|
|
7394
|
+
reject,
|
|
7395
|
+
timer: setTimeout(() => {
|
|
7396
|
+
this.finish(waiter, { api_version: "brokerkit.io/operator-ui/v1", cursor: this.cursor(), changed: false });
|
|
7397
|
+
}, waitSeconds * 1e3),
|
|
7398
|
+
...signal ? { signal } : {}
|
|
7399
|
+
};
|
|
7400
|
+
waiter.timer.unref();
|
|
7401
|
+
if (signal) {
|
|
7402
|
+
waiter.abort = () => this.fail(waiter, abortError());
|
|
7403
|
+
signal.addEventListener("abort", waiter.abort, { once: true });
|
|
7404
|
+
}
|
|
7405
|
+
this.waiters.add(waiter);
|
|
7406
|
+
});
|
|
7407
|
+
}
|
|
7408
|
+
cursor() {
|
|
7409
|
+
return `${this.epoch}.${this.revision.toString(36)}`;
|
|
7410
|
+
}
|
|
7411
|
+
parse(value) {
|
|
7412
|
+
const match2 = /^([A-Za-z0-9_-]{22})\.([0-9a-z]{1,13})$/u.exec(value);
|
|
7413
|
+
if (!match2 || match2[1] !== this.epoch) return void 0;
|
|
7414
|
+
const revision = Number.parseInt(match2[2] ?? "", 36);
|
|
7415
|
+
return Number.isSafeInteger(revision) && revision <= this.revision ? revision : void 0;
|
|
7416
|
+
}
|
|
7417
|
+
finish(waiter, value) {
|
|
7418
|
+
this.cleanup(waiter);
|
|
7419
|
+
waiter.resolve(value);
|
|
7420
|
+
}
|
|
7421
|
+
fail(waiter, error) {
|
|
7422
|
+
this.cleanup(waiter);
|
|
7423
|
+
waiter.reject(error);
|
|
7424
|
+
}
|
|
7425
|
+
cleanup(waiter) {
|
|
7426
|
+
if (!this.waiters.delete(waiter)) return;
|
|
7427
|
+
clearTimeout(waiter.timer);
|
|
7428
|
+
if (waiter.signal && waiter.abort) waiter.signal.removeEventListener("abort", waiter.abort);
|
|
7429
|
+
}
|
|
7430
|
+
};
|
|
7431
|
+
function revisionError(code) {
|
|
7432
|
+
return Object.assign(new Error(code), { code });
|
|
7433
|
+
}
|
|
7434
|
+
function abortError() {
|
|
7435
|
+
return new DOMException("The operation was aborted", "AbortError");
|
|
7436
|
+
}
|
|
7437
|
+
|
|
7438
|
+
// src/mlclaw-space-runtime/delegated-brokerkit.ts
|
|
7364
7439
|
var API_VERSION = "brokerkit.io/delegated-web/v1";
|
|
7365
7440
|
var TOKEN_LIFETIME_SECONDS = 4 * 60;
|
|
7366
7441
|
var MAX_PAGES_PER_SOURCE = 32;
|
|
@@ -7377,7 +7452,8 @@ var DelegatedBrokerKit = class {
|
|
|
7377
7452
|
handles = /* @__PURE__ */ new Map();
|
|
7378
7453
|
handlesByIdentity = /* @__PURE__ */ new Map();
|
|
7379
7454
|
snapshotInFlight;
|
|
7380
|
-
|
|
7455
|
+
revisions = new DelegatedRevisions();
|
|
7456
|
+
issueSession(actor, access) {
|
|
7381
7457
|
const issuedAt = Math.floor(this.now().getTime() / 1e3);
|
|
7382
7458
|
const expiresAt = issuedAt + TOKEN_LIFETIME_SECONDS;
|
|
7383
7459
|
const payload = {
|
|
@@ -7386,25 +7462,27 @@ var DelegatedBrokerKit = class {
|
|
|
7386
7462
|
subject: actor,
|
|
7387
7463
|
issuedAt,
|
|
7388
7464
|
expiresAt,
|
|
7389
|
-
nonce:
|
|
7465
|
+
nonce: randomBytes4(16).toString("base64url"),
|
|
7466
|
+
access
|
|
7390
7467
|
};
|
|
7391
7468
|
const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
7392
7469
|
const signature = this.sign(encoded);
|
|
7393
7470
|
return {
|
|
7394
7471
|
api_version: API_VERSION,
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7472
|
+
token: `${encoded}.${signature}`,
|
|
7473
|
+
expires_at: new Date(expiresAt * 1e3).toISOString(),
|
|
7474
|
+
access,
|
|
7475
|
+
renewal_transport: "direct"
|
|
7398
7476
|
};
|
|
7399
7477
|
}
|
|
7400
|
-
authorize(
|
|
7401
|
-
return this.authorizeSession(
|
|
7478
|
+
authorize(token) {
|
|
7479
|
+
return this.authorizeSession(token)?.actor;
|
|
7402
7480
|
}
|
|
7403
|
-
authorizeSession(
|
|
7404
|
-
const encoded = authenticatedTokenPayload(
|
|
7481
|
+
authorizeSession(token) {
|
|
7482
|
+
const encoded = authenticatedTokenPayload(token, (value) => this.sign(value));
|
|
7405
7483
|
if (!encoded) return void 0;
|
|
7406
7484
|
const payload = parseTokenPayload(encoded);
|
|
7407
|
-
return payload && tokenIsCurrent(payload, this.now()) ? { actor: payload.subject, sessionId: payload.nonce } : void 0;
|
|
7485
|
+
return payload && tokenIsCurrent(payload, this.now()) ? { actor: payload.subject, sessionId: payload.nonce, access: payload.access } : void 0;
|
|
7408
7486
|
}
|
|
7409
7487
|
async snapshot() {
|
|
7410
7488
|
if (this.snapshotInFlight) return this.snapshotInFlight;
|
|
@@ -7424,12 +7502,50 @@ var DelegatedBrokerKit = class {
|
|
|
7424
7502
|
);
|
|
7425
7503
|
const selected = selectSnapshotRequests(results, MAX_HANDLES);
|
|
7426
7504
|
const reservedHandles = this.selectedExistingHandles(selected);
|
|
7505
|
+
const sources = results.map((result) => result.source);
|
|
7506
|
+
const requests = selected.map(
|
|
7507
|
+
({ source, request }) => project(source, request, this.handle(source.id, request, reservedHandles))
|
|
7508
|
+
);
|
|
7509
|
+
const material = JSON.stringify({
|
|
7510
|
+
sources: sources.map((source) => ({
|
|
7511
|
+
id: source.id,
|
|
7512
|
+
label: source.label,
|
|
7513
|
+
healthy: source.healthy,
|
|
7514
|
+
...source.error ? { error: source.error } : {}
|
|
7515
|
+
})),
|
|
7516
|
+
requests
|
|
7517
|
+
});
|
|
7518
|
+
return this.revisions.publish(material, (cursor) => ({
|
|
7519
|
+
api_version: "brokerkit.io/operator-ui/v1",
|
|
7520
|
+
cursor,
|
|
7521
|
+
sources,
|
|
7522
|
+
requests,
|
|
7523
|
+
synchronized_at: synchronizedAt
|
|
7524
|
+
}));
|
|
7525
|
+
}
|
|
7526
|
+
async events(cursor, waitSeconds, signal) {
|
|
7527
|
+
await this.snapshot();
|
|
7528
|
+
const waiting = this.revisions.wait(cursor, waitSeconds, signal);
|
|
7529
|
+
const refresh = setInterval(() => void this.snapshot().catch(() => void 0), 1e3);
|
|
7530
|
+
refresh.unref();
|
|
7531
|
+
try {
|
|
7532
|
+
return await waiting;
|
|
7533
|
+
} catch (error) {
|
|
7534
|
+
if (error instanceof Error && "code" in error && (error.code === "cursor_expired" || error.code === "source_unavailable")) {
|
|
7535
|
+
throw delegatedError(error.code);
|
|
7536
|
+
}
|
|
7537
|
+
throw error;
|
|
7538
|
+
} finally {
|
|
7539
|
+
clearInterval(refresh);
|
|
7540
|
+
}
|
|
7541
|
+
}
|
|
7542
|
+
async summary() {
|
|
7543
|
+
const snapshot = await this.snapshot();
|
|
7427
7544
|
return {
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
)
|
|
7432
|
-
synchronizedAt
|
|
7545
|
+
api_version: snapshot.api_version,
|
|
7546
|
+
cursor: snapshot.cursor,
|
|
7547
|
+
pending: snapshot.requests.filter((value) => value.request.status === "pending").length,
|
|
7548
|
+
healthy: snapshot.sources.every((source) => source.healthy)
|
|
7433
7549
|
};
|
|
7434
7550
|
}
|
|
7435
7551
|
async detail(handle) {
|
|
@@ -7467,7 +7583,7 @@ var DelegatedBrokerKit = class {
|
|
|
7467
7583
|
]);
|
|
7468
7584
|
const requests = reconcileRequests(pages.map((page2) => page2.requests));
|
|
7469
7585
|
return {
|
|
7470
|
-
source: deadline.signal.aborted ? { ...summary, healthy: false, error: "broker_timeout" } : pages.some((page2) => page2.truncated) ? { ...summary, healthy: false, error: "source_truncated" } : { ...summary, healthy: true,
|
|
7586
|
+
source: deadline.signal.aborted ? { ...summary, healthy: false, error: "broker_timeout" } : pages.some((page2) => page2.truncated) ? { ...summary, healthy: false, error: "source_truncated" } : { ...summary, healthy: true, last_sync_at: synchronizedAt },
|
|
7471
7587
|
requests
|
|
7472
7588
|
};
|
|
7473
7589
|
} catch (error) {
|
|
@@ -7512,7 +7628,7 @@ var DelegatedBrokerKit = class {
|
|
|
7512
7628
|
if (this.handles.size >= MAX_HANDLES && !this.pruneOldestHandle(reservedHandles)) {
|
|
7513
7629
|
throw delegatedError("source_unavailable");
|
|
7514
7630
|
}
|
|
7515
|
-
const handle =
|
|
7631
|
+
const handle = randomBytes4(18).toString("base64url");
|
|
7516
7632
|
const requestExpiry = Date.parse(handleExpiry(request));
|
|
7517
7633
|
const expiresAtMs = Number.isFinite(requestExpiry) ? Math.min(requestExpiry, this.now().getTime() + 24 * 60 * 6e4) : this.now().getTime() + 5 * 6e4;
|
|
7518
7634
|
this.handles.set(handle, { sourceId, requestId: request.id, revision: request.revision, expiresAtMs });
|
|
@@ -7611,7 +7727,7 @@ function delegatedError(code) {
|
|
|
7611
7727
|
return new DelegatedBrokerKitError(code);
|
|
7612
7728
|
}
|
|
7613
7729
|
function project(source, request, handle) {
|
|
7614
|
-
return {
|
|
7730
|
+
return { source_id: source.id, source_label: source.label, handle, request };
|
|
7615
7731
|
}
|
|
7616
7732
|
function requestIdentity(sourceId, requestId, revision) {
|
|
7617
7733
|
return `${sourceId}\0${requestId}\0${revision}`;
|
|
@@ -7633,18 +7749,22 @@ function decisionOptions(record, action, expectedRevision, actor, options) {
|
|
|
7633
7749
|
expectedRevision,
|
|
7634
7750
|
idempotencyKey: decisionKey(record, action, actor),
|
|
7635
7751
|
onBehalfOf: `mlclaw:${actor}`,
|
|
7636
|
-
...options.reason ? { reason: options.reason } : {},
|
|
7637
7752
|
...options.durationSeconds ? { durationSeconds: options.durationSeconds } : {},
|
|
7638
|
-
...options.maxUses ? { maxUses: options.maxUses } : {}
|
|
7753
|
+
...options.maxUses !== void 0 ? { maxUses: options.maxUses } : {}
|
|
7639
7754
|
};
|
|
7640
7755
|
}
|
|
7641
7756
|
function decisionWithinBounds(action, request, options) {
|
|
7642
7757
|
if (options.durationSeconds === void 0 && options.maxUses === void 0) return true;
|
|
7643
7758
|
const bounds = request.approval_bounds;
|
|
7644
7759
|
return Boolean(
|
|
7645
|
-
action === "approve" && bounds && options.durationSeconds !== void 0 && options.durationSeconds <= bounds.max_duration_seconds && options.maxUses
|
|
7760
|
+
action === "approve" && bounds && options.durationSeconds !== void 0 && options.durationSeconds <= bounds.max_duration_seconds && useLimitWithinBounds(options.maxUses, bounds.max_uses)
|
|
7646
7761
|
);
|
|
7647
7762
|
}
|
|
7763
|
+
function useLimitWithinBounds(requested, maximum) {
|
|
7764
|
+
if (requested === void 0) return false;
|
|
7765
|
+
if (requested === null) return maximum === null;
|
|
7766
|
+
return maximum === null || requested <= maximum;
|
|
7767
|
+
}
|
|
7648
7768
|
function assertDecisionAllowed(request, record, action, expectedRevision, options) {
|
|
7649
7769
|
if (request.revision !== record.revision || request.revision !== expectedRevision) {
|
|
7650
7770
|
throw delegatedError("revision_stale");
|
|
@@ -7653,10 +7773,8 @@ function assertDecisionAllowed(request, record, action, expectedRevision, option
|
|
|
7653
7773
|
throw delegatedError("action_not_allowed");
|
|
7654
7774
|
}
|
|
7655
7775
|
}
|
|
7656
|
-
function authenticatedTokenPayload(
|
|
7657
|
-
if (!
|
|
7658
|
-
const token = header.slice("Bearer ".length);
|
|
7659
|
-
if (token.length > 4096) return void 0;
|
|
7776
|
+
function authenticatedTokenPayload(token, sign3) {
|
|
7777
|
+
if (!token || token.length > 4096 || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/u.test(token)) return void 0;
|
|
7660
7778
|
const [encoded, signature, extra] = token.split(".");
|
|
7661
7779
|
return encoded && signature && extra === void 0 && safeEqual(signature, sign3(encoded)) ? encoded : void 0;
|
|
7662
7780
|
}
|
|
@@ -7678,10 +7796,10 @@ function validTokenPayload(value) {
|
|
|
7678
7796
|
return hasExactTokenFields(record) && validTokenIdentity(record) && validTokenTimes(record) && validTokenNonce(record);
|
|
7679
7797
|
}
|
|
7680
7798
|
function hasExactTokenFields(record) {
|
|
7681
|
-
return Object.keys(record).sort().join(",") === "audience,expiresAt,issuedAt,nonce,subject,version";
|
|
7799
|
+
return Object.keys(record).sort().join(",") === "access,audience,expiresAt,issuedAt,nonce,subject,version";
|
|
7682
7800
|
}
|
|
7683
7801
|
function validTokenIdentity(record) {
|
|
7684
|
-
return record.version === 1 && record.audience === "brokerkit-delegated-web" && typeof record.subject === "string" && record.subject.length >= 1 && record.subject.length <= 200;
|
|
7802
|
+
return record.version === 1 && record.audience === "brokerkit-delegated-web" && (record.access === "read" || record.access === "decide") && typeof record.subject === "string" && record.subject.length >= 1 && record.subject.length <= 200;
|
|
7685
7803
|
}
|
|
7686
7804
|
function validTokenTimes(record) {
|
|
7687
7805
|
return typeof record.issuedAt === "number" && Number.isSafeInteger(record.issuedAt) && typeof record.expiresAt === "number" && Number.isSafeInteger(record.expiresAt);
|
|
@@ -8405,6 +8523,8 @@ function remainingUpstreamTimeout(deadline) {
|
|
|
8405
8523
|
}
|
|
8406
8524
|
|
|
8407
8525
|
// src/mlclaw-space-runtime/openclaw-config.ts
|
|
8526
|
+
var BROKER_MCP_CONNECTION_TIMEOUT_MS = 1e4;
|
|
8527
|
+
var BROKER_MCP_REQUEST_TIMEOUT_MS = 45e3;
|
|
8408
8528
|
async function configureOpenClawGateway(config2) {
|
|
8409
8529
|
const raw2 = await fs.readFile(config2.openclawConfigPath, "utf8");
|
|
8410
8530
|
const openclawConfig = JSON.parse(raw2);
|
|
@@ -8429,6 +8549,7 @@ async function configureOpenClawGateway(config2) {
|
|
|
8429
8549
|
};
|
|
8430
8550
|
configureOpenClawModels(openclawConfig, config2);
|
|
8431
8551
|
configureManagedMcpServers(openclawConfig, config2);
|
|
8552
|
+
configureBrokerMcpServer(openclawConfig, config2);
|
|
8432
8553
|
configureBrokerKitPlugin(openclawConfig, config2);
|
|
8433
8554
|
await fs.mkdir(path.dirname(config2.openclawConfigPath), { recursive: true });
|
|
8434
8555
|
await fs.writeFile(config2.openclawConfigPath, `${JSON.stringify(openclawConfig, null, 2)}
|
|
@@ -8438,6 +8559,60 @@ async function configureOpenClawGateway(config2) {
|
|
|
8438
8559
|
await fs.chown(config2.openclawConfigPath, config2.openclawUid, config2.openclawGid);
|
|
8439
8560
|
}
|
|
8440
8561
|
}
|
|
8562
|
+
function configureBrokerMcpServer(openclawConfig, config2) {
|
|
8563
|
+
const servers = object(object(openclawConfig, "mcp"), "servers");
|
|
8564
|
+
if (!config2.brokerAgentUrl || !config2.brokerAgentSecretFile) {
|
|
8565
|
+
delete servers["huggingface-broker"];
|
|
8566
|
+
return;
|
|
8567
|
+
}
|
|
8568
|
+
const existing = objectValue2(servers["huggingface-broker"]);
|
|
8569
|
+
servers["huggingface-broker"] = {
|
|
8570
|
+
...preservedBrokerMcpFields(existing),
|
|
8571
|
+
command: "/usr/local/bin/hf-broker",
|
|
8572
|
+
args: ["mcp"],
|
|
8573
|
+
connectionTimeoutMs: BROKER_MCP_CONNECTION_TIMEOUT_MS,
|
|
8574
|
+
requestTimeoutMs: BROKER_MCP_REQUEST_TIMEOUT_MS,
|
|
8575
|
+
env: {
|
|
8576
|
+
HF_BROKER_AGENT_ENDPOINT: brokerAgentEndpoint(config2.brokerAgentUrl),
|
|
8577
|
+
HF_BROKER_SHARED_SECRET_FILE: config2.brokerAgentSecretFile
|
|
8578
|
+
},
|
|
8579
|
+
...existing?.enabled === false ? { enabled: false } : { enabled: true }
|
|
8580
|
+
};
|
|
8581
|
+
}
|
|
8582
|
+
function brokerAgentEndpoint(agentUrl) {
|
|
8583
|
+
const parsed = new URL(agentUrl);
|
|
8584
|
+
if (parsed.protocol !== "http:" || !parsed.port || parsed.username || parsed.password) {
|
|
8585
|
+
throw new Error("HF Broker agent URL must be an unauthenticated HTTP URL with an explicit port");
|
|
8586
|
+
}
|
|
8587
|
+
return `tcp://${parsed.host}`;
|
|
8588
|
+
}
|
|
8589
|
+
function preservedBrokerMcpFields(existing) {
|
|
8590
|
+
const codex = preservedBrokerCodexConfig(objectValue2(existing?.codex));
|
|
8591
|
+
return {
|
|
8592
|
+
...existing?.toolFilter && typeof existing.toolFilter === "object" ? { toolFilter: existing.toolFilter } : {},
|
|
8593
|
+
...typeof existing?.supportsParallelToolCalls === "boolean" ? { supportsParallelToolCalls: existing.supportsParallelToolCalls } : {},
|
|
8594
|
+
...codex ? { codex } : {}
|
|
8595
|
+
};
|
|
8596
|
+
}
|
|
8597
|
+
function preservedBrokerCodexConfig(existing) {
|
|
8598
|
+
const agents = brokerAgentScope(existing?.agents);
|
|
8599
|
+
const defaultToolsApprovalMode = brokerApprovalMode(existing?.defaultToolsApprovalMode);
|
|
8600
|
+
const nativeApprovalMode = brokerApprovalMode(existing?.default_tools_approval_mode);
|
|
8601
|
+
const preserved = {
|
|
8602
|
+
...agents ? { agents } : {},
|
|
8603
|
+
...defaultToolsApprovalMode ? { defaultToolsApprovalMode } : {},
|
|
8604
|
+
...nativeApprovalMode ? { default_tools_approval_mode: nativeApprovalMode } : {}
|
|
8605
|
+
};
|
|
8606
|
+
return Object.keys(preserved).length > 0 ? preserved : void 0;
|
|
8607
|
+
}
|
|
8608
|
+
function brokerAgentScope(value) {
|
|
8609
|
+
if (!Array.isArray(value)) return void 0;
|
|
8610
|
+
const agents = value.filter((agent) => typeof agent === "string" && /^[a-z0-9][a-z0-9_-]{0,63}$/iu.test(agent.trim())).map((agent) => agent.trim());
|
|
8611
|
+
return agents.length > 0 ? agents : void 0;
|
|
8612
|
+
}
|
|
8613
|
+
function brokerApprovalMode(value) {
|
|
8614
|
+
return value === "auto" || value === "prompt" || value === "approve" ? value : void 0;
|
|
8615
|
+
}
|
|
8441
8616
|
function configureBrokerKitPlugin(openclawConfig, config2) {
|
|
8442
8617
|
const plugins = object(openclawConfig, "plugins");
|
|
8443
8618
|
const load = object(plugins, "load");
|
|
@@ -8576,7 +8751,7 @@ function uniqueStrings(value, required) {
|
|
|
8576
8751
|
}
|
|
8577
8752
|
|
|
8578
8753
|
// src/mlclaw-space-runtime/openai-credentials.ts
|
|
8579
|
-
import { createCipheriv, createDecipheriv, hkdfSync, randomBytes as
|
|
8754
|
+
import { createCipheriv, createDecipheriv, hkdfSync, randomBytes as randomBytes5 } from "node:crypto";
|
|
8580
8755
|
import fs2 from "node:fs/promises";
|
|
8581
8756
|
import path2 from "node:path";
|
|
8582
8757
|
function openAiConfigured(env = process.env) {
|
|
@@ -8645,7 +8820,7 @@ var OpenAiCredentialStore = class {
|
|
|
8645
8820
|
if (!normalized) {
|
|
8646
8821
|
throw new Error("valid OpenAI API key is required");
|
|
8647
8822
|
}
|
|
8648
|
-
const iv =
|
|
8823
|
+
const iv = randomBytes5(12);
|
|
8649
8824
|
const cipher = createCipheriv("aes-256-gcm", this.key, iv);
|
|
8650
8825
|
const ciphertext = Buffer.concat([cipher.update(normalized, "utf8"), cipher.final()]);
|
|
8651
8826
|
const envelope = {
|
|
@@ -8656,7 +8831,7 @@ var OpenAiCredentialStore = class {
|
|
|
8656
8831
|
ciphertext: ciphertext.toString("base64url")
|
|
8657
8832
|
};
|
|
8658
8833
|
const directory = path2.dirname(this.file);
|
|
8659
|
-
const temporary = `${this.file}.${process.pid}.${
|
|
8834
|
+
const temporary = `${this.file}.${process.pid}.${randomBytes5(6).toString("hex")}.tmp`;
|
|
8660
8835
|
await fs2.mkdir(directory, { recursive: true, mode: 448 });
|
|
8661
8836
|
try {
|
|
8662
8837
|
await fs2.writeFile(temporary, `${JSON.stringify(envelope)}
|
|
@@ -8942,7 +9117,7 @@ function positiveNumber2(value) {
|
|
|
8942
9117
|
}
|
|
8943
9118
|
|
|
8944
9119
|
// src/mlclaw-space-runtime/cookies.ts
|
|
8945
|
-
import { createHmac as createHmac4, randomBytes as
|
|
9120
|
+
import { createHmac as createHmac4, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
|
|
8946
9121
|
function createSignedCookie(options, payload) {
|
|
8947
9122
|
const body = Buffer.from(JSON.stringify({
|
|
8948
9123
|
...payload,
|
|
@@ -8991,7 +9166,7 @@ function clearCookie(name, secure) {
|
|
|
8991
9166
|
});
|
|
8992
9167
|
}
|
|
8993
9168
|
function randomState() {
|
|
8994
|
-
return
|
|
9169
|
+
return randomBytes6(24).toString("base64url");
|
|
8995
9170
|
}
|
|
8996
9171
|
function sign2(value, secret) {
|
|
8997
9172
|
return createHmac4("sha256", secret).update(value).digest("base64url");
|
|
@@ -9084,6 +9259,10 @@ function normalizeNext(value) {
|
|
|
9084
9259
|
var SHELL_MARKER = "data-mlclaw-shell";
|
|
9085
9260
|
var BRANDING_MARKER = "data-mlclaw-branding";
|
|
9086
9261
|
var CONTROL_BRANDING_MARKER = "data-mlclaw-control-branding";
|
|
9262
|
+
var BROKERKIT_DELEGATED_UI_BOOTSTRAP = Buffer.from(
|
|
9263
|
+
JSON.stringify({ version: 1, mode: "delegated-web", basePath: "/mlclaw/api/brokerkit" }),
|
|
9264
|
+
"utf8"
|
|
9265
|
+
).toString("base64url");
|
|
9087
9266
|
var CONTROL_BRANDING_SCRIPT_PATH = "/assets/mlclaw-control-branding.js";
|
|
9088
9267
|
var CONTROL_BRANDING_SCRIPT = `(function () {
|
|
9089
9268
|
var productName = "ML Claw";
|
|
@@ -9152,31 +9331,125 @@ var CONTROL_BRANDING_SCRIPT = `(function () {
|
|
|
9152
9331
|
}
|
|
9153
9332
|
});
|
|
9154
9333
|
}
|
|
9155
|
-
function
|
|
9156
|
-
|
|
9157
|
-
var
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9334
|
+
function installApprovals() {
|
|
9335
|
+
var shell = document.querySelector("[data-mlclaw-shell]");
|
|
9336
|
+
var button = document.querySelector("[data-mlclaw-approvals-button]");
|
|
9337
|
+
var popover = document.querySelector("[data-mlclaw-approvals-popover]");
|
|
9338
|
+
var frame = document.querySelector("[data-mlclaw-approvals-frame]");
|
|
9339
|
+
var badge = document.querySelector("[data-mlclaw-approvals-badge]");
|
|
9340
|
+
var close = document.querySelector("[data-mlclaw-approvals-close]");
|
|
9341
|
+
if (!shell || !button || !popover || !frame || button.getAttribute("data-ready") === "1") return;
|
|
9342
|
+
button.setAttribute("data-ready", "1");
|
|
9343
|
+
function invalidateFrame() {
|
|
9344
|
+
if (frame.contentWindow) {
|
|
9345
|
+
frame.contentWindow.postMessage({ type: "brokerkit.operator-ui.invalidate", version: 1 }, "*");
|
|
9346
|
+
}
|
|
9347
|
+
}
|
|
9348
|
+
function setOpen(open) {
|
|
9349
|
+
popover.hidden = !open;
|
|
9350
|
+
button.setAttribute("aria-expanded", open ? "true" : "false");
|
|
9351
|
+
if (!open) return;
|
|
9352
|
+
if (!frame.getAttribute("src")) frame.setAttribute("src", frame.getAttribute("data-src"));
|
|
9353
|
+
else invalidateFrame();
|
|
9354
|
+
}
|
|
9355
|
+
frame.addEventListener("load", function () { if (!popover.hidden) invalidateFrame(); });
|
|
9356
|
+
button.addEventListener("click", function () { setOpen(popover.hidden); });
|
|
9357
|
+
if (close) close.addEventListener("click", function () { setOpen(false); });
|
|
9358
|
+
document.addEventListener("click", function (event) {
|
|
9359
|
+
if (!popover.hidden && !shell.contains(event.target)) setOpen(false);
|
|
9360
|
+
});
|
|
9361
|
+
document.addEventListener("keydown", function (event) {
|
|
9362
|
+
if (event.key === "Escape") setOpen(false);
|
|
9363
|
+
});
|
|
9364
|
+
window.addEventListener("message", function (event) {
|
|
9365
|
+
var data = event.data;
|
|
9366
|
+
if (
|
|
9367
|
+
event.source !== frame.contentWindow ||
|
|
9368
|
+
!data ||
|
|
9369
|
+
typeof data !== "object" ||
|
|
9370
|
+
Object.keys(data).sort().join(",") !== "nonce,type,version" ||
|
|
9371
|
+
data.type !== "brokerkit.delegated-web.open" ||
|
|
9372
|
+
data.version !== 1 ||
|
|
9373
|
+
typeof data.nonce !== "string" ||
|
|
9374
|
+
!/^[a-f0-9]{32}$/.test(data.nonce)
|
|
9375
|
+
) return;
|
|
9376
|
+
window.location.assign("/plugins/brokerkit/ui/#${BROKERKIT_DELEGATED_UI_BOOTSTRAP}");
|
|
9377
|
+
});
|
|
9378
|
+
var summaryCursor = "";
|
|
9379
|
+
var stopped = false;
|
|
9380
|
+
function acceptSummary(summary) {
|
|
9381
|
+
if (
|
|
9382
|
+
!summary ||
|
|
9383
|
+
typeof summary !== "object" ||
|
|
9384
|
+
Object.keys(summary).sort().join(",") !== "api_version,cursor,healthy,pending" ||
|
|
9385
|
+
summary.api_version !== "brokerkit.io/operator-ui/v1" ||
|
|
9386
|
+
typeof summary.cursor !== "string" ||
|
|
9387
|
+
summary.cursor.length < 1 ||
|
|
9388
|
+
summary.cursor.length > 128 ||
|
|
9389
|
+
typeof summary.pending !== "number" ||
|
|
9390
|
+
!Number.isSafeInteger(summary.pending) ||
|
|
9391
|
+
summary.pending < 0 ||
|
|
9392
|
+
typeof summary.healthy !== "boolean"
|
|
9393
|
+
) return false;
|
|
9394
|
+
var changed = summaryCursor && summaryCursor !== summary.cursor;
|
|
9395
|
+
summaryCursor = summary.cursor;
|
|
9396
|
+
if (badge) {
|
|
9397
|
+
badge.textContent = summary.pending > 99 ? "99+" : String(summary.pending);
|
|
9398
|
+
badge.hidden = summary.pending < 1;
|
|
9399
|
+
}
|
|
9400
|
+
button.setAttribute("aria-label", summary.pending > 0 ? "Open approval requests (" + summary.pending + " pending)" : "Open approval requests");
|
|
9401
|
+
if (changed) invalidateFrame();
|
|
9402
|
+
return true;
|
|
9164
9403
|
}
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9404
|
+
function refresh() {
|
|
9405
|
+
return fetch("/mlclaw/api/brokerkit/summary", { credentials: "same-origin", cache: "no-store" })
|
|
9406
|
+
.then(function (response) { return response.ok ? response.json() : null; })
|
|
9407
|
+
.then(acceptSummary)
|
|
9408
|
+
.catch(function () { return false; });
|
|
9409
|
+
}
|
|
9410
|
+
function watch(delay) {
|
|
9411
|
+
if (stopped) return;
|
|
9412
|
+
if (!summaryCursor) {
|
|
9413
|
+
refresh().then(function () { window.setTimeout(function () { watch(250); }, delay); });
|
|
9414
|
+
return;
|
|
9170
9415
|
}
|
|
9416
|
+
fetch("/mlclaw/api/brokerkit/summary/events?cursor=" + encodeURIComponent(summaryCursor) + "&wait_seconds=25", {
|
|
9417
|
+
credentials: "same-origin",
|
|
9418
|
+
cache: "no-store"
|
|
9419
|
+
}).then(function (response) {
|
|
9420
|
+
if (response.status === 410) {
|
|
9421
|
+
summaryCursor = "";
|
|
9422
|
+
return null;
|
|
9423
|
+
}
|
|
9424
|
+
if (!response.ok) throw new Error("summary unavailable");
|
|
9425
|
+
return response.json();
|
|
9426
|
+
}).then(function (event) {
|
|
9427
|
+
if (
|
|
9428
|
+
event &&
|
|
9429
|
+
typeof event === "object" &&
|
|
9430
|
+
Object.keys(event).sort().join(",") === "api_version,changed,cursor" &&
|
|
9431
|
+
event.api_version === "brokerkit.io/operator-ui/v1" &&
|
|
9432
|
+
typeof event.cursor === "string" &&
|
|
9433
|
+
event.cursor.length >= 1 &&
|
|
9434
|
+
event.cursor.length <= 128 &&
|
|
9435
|
+
typeof event.changed === "boolean"
|
|
9436
|
+
) {
|
|
9437
|
+
summaryCursor = event.cursor;
|
|
9438
|
+
if (event.changed) invalidateFrame();
|
|
9439
|
+
return event.changed ? refresh() : true;
|
|
9440
|
+
}
|
|
9441
|
+
return false;
|
|
9442
|
+
}).then(function (ok) {
|
|
9443
|
+
window.setTimeout(function () { watch(ok ? 250 : Math.min(delay * 2, 30000)); }, ok ? 0 : delay);
|
|
9444
|
+
}).catch(function () {
|
|
9445
|
+
window.setTimeout(function () { watch(Math.min(delay * 2, 30000)); }, delay);
|
|
9446
|
+
});
|
|
9171
9447
|
}
|
|
9448
|
+
refresh().then(function () { watch(250); });
|
|
9449
|
+
window.setInterval(refresh, 300000);
|
|
9450
|
+
window.addEventListener("focus", function () { refresh(); });
|
|
9451
|
+
window.addEventListener("beforeunload", function () { stopped = true; });
|
|
9172
9452
|
}
|
|
9173
|
-
window.addEventListener("message", function (event) {
|
|
9174
|
-
var message = event.data;
|
|
9175
|
-
if (event.origin !== "null" || !message || message.type !== "brokerkit.delegated-web.open" ||
|
|
9176
|
-
message.version !== 1 || typeof message.nonce !== "string" || !/^[a-f0-9]{32}$/.test(message.nonce)) return;
|
|
9177
|
-
var frame = brokerKitFrameIn(document, event.source);
|
|
9178
|
-
if (frame) location.assign(frame.src);
|
|
9179
|
-
});
|
|
9180
9453
|
if (!document.documentElement.hasAttribute(marker)) {
|
|
9181
9454
|
document.documentElement.setAttribute(marker, "1");
|
|
9182
9455
|
var attachShadow = Element.prototype.attachShadow;
|
|
@@ -9190,6 +9463,7 @@ var CONTROL_BRANDING_SCRIPT = `(function () {
|
|
|
9190
9463
|
requestAnimationFrame(function () {
|
|
9191
9464
|
observeExistingShadowRoots(document);
|
|
9192
9465
|
scan(document);
|
|
9466
|
+
installApprovals();
|
|
9193
9467
|
});
|
|
9194
9468
|
}
|
|
9195
9469
|
})();
|
|
@@ -9222,6 +9496,13 @@ function rewriteOpenClawHtml(html, branding) {
|
|
|
9222
9496
|
function injectMlClawShell(html, branding) {
|
|
9223
9497
|
const shell = `
|
|
9224
9498
|
<div ${SHELL_MARKER} style="position:fixed;left:max(12px,env(safe-area-inset-left));bottom:max(12px,env(safe-area-inset-bottom));z-index:2147483647;">
|
|
9499
|
+
<section data-mlclaw-approvals-popover hidden aria-label="Approval requests" style="position:absolute;left:0;bottom:44px;box-sizing:border-box;width:min(420px,calc(100vw - 24px));height:min(620px,calc(100dvh - 72px));overflow:hidden;border:1px solid rgba(15,23,42,.16);border-radius:14px;background:white;box-shadow:0 18px 48px rgba(15,23,42,.24);">
|
|
9500
|
+
<header style="box-sizing:border-box;display:flex;height:42px;align-items:center;justify-content:space-between;padding:0 10px 0 14px;border-bottom:1px solid rgba(15,23,42,.1);color:#111827;font:600 14px system-ui;">
|
|
9501
|
+
<span>Approvals</span>
|
|
9502
|
+
<button data-mlclaw-approvals-close type="button" aria-label="Close approval requests" style="display:grid;width:30px;height:30px;place-items:center;border:0;border-radius:7px;background:transparent;color:#475569;cursor:pointer;font:20px/1 system-ui;">×</button>
|
|
9503
|
+
</header>
|
|
9504
|
+
<iframe data-mlclaw-approvals-frame data-src="/plugins/brokerkit/ui/?embed=popover#${BROKERKIT_DELEGATED_UI_BOOTSTRAP}" title="Approval requests" sandbox="allow-scripts" style="display:block;width:100%;height:calc(100% - 42px);border:0;background:white;"></iframe>
|
|
9505
|
+
</section>
|
|
9225
9506
|
<div style="display:flex;gap:8px;align-items:center;">
|
|
9226
9507
|
<a href="/mlclaw" aria-label="Open ${escapeHtml2(branding.name)} settings" title="${escapeHtml2(branding.name)}" style="box-sizing:border-box;display:flex;width:34px;height:34px;aspect-ratio:1/1;align-items:center;justify-content:center;border:1px solid rgba(15,23,42,.16);border-radius:8px;background:rgba(255,255,255,.94);box-shadow:0 8px 18px rgba(15,23,42,.14);color:#111827;text-decoration:none;">
|
|
9227
9508
|
<svg aria-hidden="true" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;width:18px;height:18px;">
|
|
@@ -9229,6 +9510,10 @@ function injectMlClawShell(html, branding) {
|
|
|
9229
9510
|
<circle cx="12" cy="12" r="3"></circle>
|
|
9230
9511
|
</svg>
|
|
9231
9512
|
</a>
|
|
9513
|
+
<button data-mlclaw-approvals-button type="button" aria-label="Open approval requests" aria-expanded="false" style="position:relative;box-sizing:border-box;display:grid;width:34px;height:34px;place-items:center;border:1px solid rgba(15,23,42,.16);border-radius:8px;background:rgba(255,255,255,.94);box-shadow:0 8px 18px rgba(15,23,42,.14);color:#111827;cursor:pointer;">
|
|
9514
|
+
<svg aria-hidden="true" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"></path><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"></path></svg>
|
|
9515
|
+
<span data-mlclaw-approvals-badge hidden style="position:absolute;place-items:center;min-width:17px;height:17px;right:-6px;top:-7px;padding:0 4px;border:2px solid white;border-radius:999px;background:#dc2626;color:white;font:700 9px system-ui;"></span>
|
|
9516
|
+
</button>
|
|
9232
9517
|
</div>
|
|
9233
9518
|
</div>
|
|
9234
9519
|
`;
|
|
@@ -9266,12 +9551,16 @@ function escapeHtml2(value) {
|
|
|
9266
9551
|
}
|
|
9267
9552
|
|
|
9268
9553
|
// src/mlclaw-space-runtime/app.ts
|
|
9554
|
+
var BROKERKIT_SESSION_HEADER = "brokerkit-session";
|
|
9269
9555
|
function createSpaceRuntimeApp(config2, controls) {
|
|
9270
9556
|
const app = new Hono2();
|
|
9271
9557
|
const operatorBrokers = new OperatorBrokerRegistry(config2.operatorBrokers);
|
|
9272
9558
|
const delegatedBrokerKit = new DelegatedBrokerKit(operatorBrokers, config2.sessionSecret);
|
|
9273
9559
|
const allowDelegatedSessionSnapshot = fixedWindowRateLimit(12, 6e4);
|
|
9274
9560
|
const allowDelegatedActorSnapshot = fixedWindowRateLimit(60, 6e4);
|
|
9561
|
+
const allowBrokerKitSummary = fixedWindowRateLimit(12, 6e4);
|
|
9562
|
+
const allowDelegatedEvents = fixedWindowRateLimit(60, 6e4);
|
|
9563
|
+
const allowSummaryEvents = fixedWindowRateLimit(60, 6e4);
|
|
9275
9564
|
const openAiCredentials = new OpenAiCredentialStore(config2.openaiCredentialStoreFile, config2.credentialKey);
|
|
9276
9565
|
app.get("/health", (c) => health(c, config2, controls));
|
|
9277
9566
|
app.get("/healthz", (c) => health(c, config2, controls));
|
|
@@ -9341,11 +9630,33 @@ function createSpaceRuntimeApp(config2, controls) {
|
|
|
9341
9630
|
}
|
|
9342
9631
|
return c.json(await statusPayload(config2, controls));
|
|
9343
9632
|
});
|
|
9633
|
+
app.get("/mlclaw/api/brokerkit/summary", async (c) => {
|
|
9634
|
+
const auth = requireAdmin(c, config2);
|
|
9635
|
+
if (auth instanceof Response) return auth;
|
|
9636
|
+
if (!allowBrokerKitSummary(auth.username)) return c.json({ ok: false, error: "rate limited" }, 429);
|
|
9637
|
+
try {
|
|
9638
|
+
return c.json(await delegatedBrokerKit.summary());
|
|
9639
|
+
} catch {
|
|
9640
|
+
return c.json({ ok: false, error: "operator inbox unavailable" }, 503);
|
|
9641
|
+
}
|
|
9642
|
+
});
|
|
9643
|
+
app.get("/mlclaw/api/brokerkit/summary/events", async (c) => {
|
|
9644
|
+
const auth = requireAdmin(c, config2);
|
|
9645
|
+
if (auth instanceof Response) return auth;
|
|
9646
|
+
if (!allowSummaryEvents(auth.username)) return c.json({ ok: false, error: "rate limited" }, 429);
|
|
9647
|
+
const input = delegatedEventQuery(c.req.url);
|
|
9648
|
+
if (!input) return c.json({ ok: false, error: "invalid request" }, 400);
|
|
9649
|
+
try {
|
|
9650
|
+
return c.json(await delegatedBrokerKit.events(input.cursor, input.waitSeconds, c.req.raw.signal));
|
|
9651
|
+
} catch (error) {
|
|
9652
|
+
return delegatedFailure(c, error);
|
|
9653
|
+
}
|
|
9654
|
+
});
|
|
9344
9655
|
app.options("/mlclaw/api/brokerkit/*", (c) => delegatedPreflight(c));
|
|
9345
9656
|
app.post("/mlclaw/api/brokerkit/session", (c) => {
|
|
9346
9657
|
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
9347
9658
|
if (!identity) return delegatedErrorResponse(c, "not_authorized", 401);
|
|
9348
|
-
return delegatedJson(c, delegatedBrokerKit.issueSession(identity.actor));
|
|
9659
|
+
return delegatedJson(c, delegatedBrokerKit.issueSession(identity.actor, identity.access));
|
|
9349
9660
|
});
|
|
9350
9661
|
app.get("/mlclaw/api/brokerkit/snapshot", async (c) => {
|
|
9351
9662
|
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
@@ -9359,6 +9670,18 @@ function createSpaceRuntimeApp(config2, controls) {
|
|
|
9359
9670
|
return delegatedFailure(c, error);
|
|
9360
9671
|
}
|
|
9361
9672
|
});
|
|
9673
|
+
app.get("/mlclaw/api/brokerkit/events", async (c) => {
|
|
9674
|
+
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
9675
|
+
if (!identity) return delegatedErrorResponse(c, "not_authorized", 401);
|
|
9676
|
+
if (!allowDelegatedEvents(identity.sessionId)) return delegatedErrorResponse(c, "rate_limited", 429);
|
|
9677
|
+
const input = delegatedEventQuery(c.req.url);
|
|
9678
|
+
if (!input) return delegatedErrorResponse(c, "invalid_input", 400);
|
|
9679
|
+
try {
|
|
9680
|
+
return delegatedJson(c, await delegatedBrokerKit.events(input.cursor, input.waitSeconds, c.req.raw.signal));
|
|
9681
|
+
} catch (error) {
|
|
9682
|
+
return delegatedFailure(c, error);
|
|
9683
|
+
}
|
|
9684
|
+
});
|
|
9362
9685
|
app.get("/mlclaw/api/brokerkit/requests/:handle", async (c) => {
|
|
9363
9686
|
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
9364
9687
|
if (!identity) return delegatedErrorResponse(c, "not_authorized", 401);
|
|
@@ -9368,28 +9691,27 @@ function createSpaceRuntimeApp(config2, controls) {
|
|
|
9368
9691
|
return delegatedFailure(c, error);
|
|
9369
9692
|
}
|
|
9370
9693
|
});
|
|
9371
|
-
for (const action of ["approve", "deny", "
|
|
9694
|
+
for (const action of ["approve", "deny", "revoke"]) {
|
|
9372
9695
|
app.post(`/mlclaw/api/brokerkit/requests/:handle/${action}`, async (c) => {
|
|
9373
9696
|
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
9374
|
-
if (!identity) return delegatedErrorResponse(c, "not_authorized", 401);
|
|
9697
|
+
if (!identity || identity.access !== "decide") return delegatedErrorResponse(c, "not_authorized", 401);
|
|
9375
9698
|
const body = await readBoundedJson(c, 16384);
|
|
9376
|
-
if (!body || Object.keys(body).some((key) => !["expectedRevision", "
|
|
9699
|
+
if (!body || Object.keys(body).some((key) => !["expectedRevision", "constraints"].includes(key))) {
|
|
9377
9700
|
return delegatedErrorResponse(c, "invalid_input", 400);
|
|
9378
9701
|
}
|
|
9379
9702
|
const constraints = recordValue(body.constraints);
|
|
9380
9703
|
const expectedRevision = positiveJsonInteger(body.expectedRevision);
|
|
9381
9704
|
const durationSeconds = optionalPositiveJsonInteger(constraints?.durationSeconds);
|
|
9382
|
-
const maxUses =
|
|
9383
|
-
if (!expectedRevision || body.
|
|
9705
|
+
const maxUses = optionalUseLimitJsonInteger(constraints?.maxUses);
|
|
9706
|
+
if (!expectedRevision || body.constraints !== void 0 && (!constraints || Object.keys(constraints).some((key) => !["durationSeconds", "maxUses"].includes(key)) || durationSeconds === void 0 || maxUses === void 0) || durationSeconds === "invalid" || maxUses === "invalid" || action !== "approve" && (durationSeconds !== void 0 || maxUses !== void 0)) {
|
|
9384
9707
|
return delegatedErrorResponse(c, "invalid_input", 400);
|
|
9385
9708
|
}
|
|
9386
9709
|
try {
|
|
9387
9710
|
return delegatedJson(
|
|
9388
9711
|
c,
|
|
9389
9712
|
await delegatedBrokerKit.decide(c.req.param("handle"), action, expectedRevision, identity.actor, {
|
|
9390
|
-
...typeof body.reason === "string" ? { reason: body.reason } : {},
|
|
9391
9713
|
...typeof durationSeconds === "number" ? { durationSeconds } : {},
|
|
9392
|
-
...typeof maxUses === "number" ? { maxUses } : {}
|
|
9714
|
+
...typeof maxUses === "number" || maxUses === null ? { maxUses } : {}
|
|
9393
9715
|
})
|
|
9394
9716
|
);
|
|
9395
9717
|
} catch (error) {
|
|
@@ -9397,6 +9719,7 @@ function createSpaceRuntimeApp(config2, controls) {
|
|
|
9397
9719
|
}
|
|
9398
9720
|
});
|
|
9399
9721
|
}
|
|
9722
|
+
app.all("/mlclaw/api/brokerkit/*", (c) => delegatedErrorResponse(c, "not_found", 404));
|
|
9400
9723
|
app.post("/mlclaw/api/integrations/huggingface/disconnect", async (c) => {
|
|
9401
9724
|
const auth = requireAdmin(c, config2);
|
|
9402
9725
|
if (auth instanceof Response) {
|
|
@@ -9668,16 +9991,28 @@ async function trustedBrokerKitUi(c, config2, delegatedBrokerKit) {
|
|
|
9668
9991
|
if (relative === "index.html") {
|
|
9669
9992
|
const destination = c.req.header("sec-fetch-dest");
|
|
9670
9993
|
if (destination !== "iframe" && destination !== "document") return c.text("not found\n", 404);
|
|
9994
|
+
const query = new URL(c.req.url).search;
|
|
9995
|
+
const embeddedPopover = destination === "iframe" && query === "?embed=popover";
|
|
9996
|
+
if (query && !embeddedPopover) return c.text("not found\n", 404);
|
|
9671
9997
|
const auth = requireAdmin(c, config2);
|
|
9672
9998
|
if (auth instanceof Response) return auth;
|
|
9673
9999
|
try {
|
|
9674
10000
|
const template = await fs3.readFile(file, "utf8");
|
|
9675
|
-
const
|
|
9676
|
-
|
|
10001
|
+
const delegatedSession = destination === "document" || embeddedPopover;
|
|
10002
|
+
const marker = !delegatedSession ? '<meta name="brokerkit-delegated-top-level">' : `<meta name="brokerkit-delegated-session" content="${Buffer.from(
|
|
10003
|
+
JSON.stringify(
|
|
10004
|
+
delegatedBrokerKit.issueSession(
|
|
10005
|
+
auth.username,
|
|
10006
|
+
embeddedPopover && !config2.brokerKitPopoverDecisions ? "read" : "decide"
|
|
10007
|
+
)
|
|
10008
|
+
),
|
|
9677
10009
|
"utf8"
|
|
9678
10010
|
).toString("base64url")}">`;
|
|
9679
10011
|
if (!template.includes("</head>")) return c.text("not found\n", 404);
|
|
9680
|
-
const headers2 = trustedBrokerKitHeaders(
|
|
10012
|
+
const headers2 = trustedBrokerKitHeaders(
|
|
10013
|
+
embeddedPopover ? "popover" : destination === "iframe" ? "launcher" : "top-level",
|
|
10014
|
+
new URL(c.req.url).origin
|
|
10015
|
+
);
|
|
9681
10016
|
headers2.set("content-type", "text/html; charset=utf-8");
|
|
9682
10017
|
return new Response(template.replace("</head>", `${marker}</head>`), { status: 200, headers: headers2 });
|
|
9683
10018
|
} catch {
|
|
@@ -9686,15 +10021,16 @@ async function trustedBrokerKitUi(c, config2, delegatedBrokerKit) {
|
|
|
9686
10021
|
}
|
|
9687
10022
|
const response = await serveFile(file, contentType(file), true);
|
|
9688
10023
|
if (response.status !== 200) return response;
|
|
9689
|
-
const headers = trustedBrokerKitHeaders("asset");
|
|
10024
|
+
const headers = trustedBrokerKitHeaders("asset", new URL(c.req.url).origin);
|
|
9690
10025
|
headers.set("content-type", response.headers.get("content-type") ?? "application/octet-stream");
|
|
9691
10026
|
return new Response(response.body, { status: response.status, headers });
|
|
9692
10027
|
}
|
|
9693
|
-
function trustedBrokerKitHeaders(mode) {
|
|
10028
|
+
function trustedBrokerKitHeaders(mode, origin) {
|
|
9694
10029
|
const asset = mode === "asset";
|
|
10030
|
+
const sandbox = mode === "top-level" || mode === "popover" ? "sandbox allow-scripts; " : "";
|
|
9695
10031
|
const headers = new Headers({
|
|
9696
10032
|
"cache-control": asset ? "public, max-age=31536000, immutable" : "no-store",
|
|
9697
|
-
"content-security-policy":
|
|
10033
|
+
"content-security-policy": `${sandbox}default-src 'self'; script-src 'self' ${origin}; style-src 'self' 'unsafe-inline' ${origin}; connect-src 'self' ${origin}; img-src 'self' data:; frame-ancestors ${mode === "top-level" ? "'none'" : "'self'"}`,
|
|
9698
10034
|
"cross-origin-resource-policy": asset ? "cross-origin" : "same-origin",
|
|
9699
10035
|
"referrer-policy": "no-referrer",
|
|
9700
10036
|
"x-content-type-options": "nosniff",
|
|
@@ -9747,14 +10083,24 @@ function requireCsrf(c, config2, username) {
|
|
|
9747
10083
|
function delegatedOriginAllowed(c) {
|
|
9748
10084
|
return c.req.header("origin") === "null";
|
|
9749
10085
|
}
|
|
10086
|
+
function delegatedEventQuery(urlValue) {
|
|
10087
|
+
const url = new URL(urlValue);
|
|
10088
|
+
if ([...url.searchParams.keys()].some((key) => key !== "cursor" && key !== "wait_seconds")) return void 0;
|
|
10089
|
+
const cursor = url.searchParams.get("cursor") ?? "";
|
|
10090
|
+
const wait = url.searchParams.get("wait_seconds") ?? "25";
|
|
10091
|
+
if (cursor.length < 1 || cursor.length > 128 || !/^[A-Za-z0-9_.-]+$/u.test(cursor) || !/^(?:[1-9]|1[0-9]|2[0-5])$/u.test(wait)) {
|
|
10092
|
+
return void 0;
|
|
10093
|
+
}
|
|
10094
|
+
return { cursor, waitSeconds: Number(wait) };
|
|
10095
|
+
}
|
|
9750
10096
|
function delegatedIdentity(c, delegated) {
|
|
9751
10097
|
if (!delegatedOriginAllowed(c)) return void 0;
|
|
9752
|
-
return delegated.authorizeSession(c.req.header(
|
|
10098
|
+
return delegated.authorizeSession(c.req.header(BROKERKIT_SESSION_HEADER));
|
|
9753
10099
|
}
|
|
9754
10100
|
function delegatedPreflight(c) {
|
|
9755
10101
|
if (!delegatedOriginAllowed(c)) return delegatedErrorResponse(c, "not_authorized", 403);
|
|
9756
10102
|
delegatedHeaders(c);
|
|
9757
|
-
c.header("access-control-allow-headers",
|
|
10103
|
+
c.header("access-control-allow-headers", `${BROKERKIT_SESSION_HEADER}, content-type`);
|
|
9758
10104
|
c.header("access-control-allow-methods", "GET, POST, OPTIONS");
|
|
9759
10105
|
c.header("access-control-max-age", "300");
|
|
9760
10106
|
return c.body(null, 204);
|
|
@@ -9769,7 +10115,7 @@ function delegatedErrorResponse(c, code, status) {
|
|
|
9769
10115
|
}
|
|
9770
10116
|
function delegatedFailure(c, error) {
|
|
9771
10117
|
if (error instanceof DelegatedBrokerKitError) {
|
|
9772
|
-
const status = error.code === "request_not_found" ? 404 : error.code === "revision_stale" || error.code === "action_not_allowed" ? 409 : 502;
|
|
10118
|
+
const status = error.code === "request_not_found" ? 404 : error.code === "cursor_expired" ? 410 : error.code === "revision_stale" || error.code === "action_not_allowed" ? 409 : 502;
|
|
9773
10119
|
return delegatedErrorResponse(c, error.code, status);
|
|
9774
10120
|
}
|
|
9775
10121
|
if (error instanceof BrokerOperatorError) {
|
|
@@ -9777,13 +10123,22 @@ function delegatedFailure(c, error) {
|
|
|
9777
10123
|
const status = error.status === 404 ? 404 : error.status === 409 ? 409 : 502;
|
|
9778
10124
|
return delegatedErrorResponse(c, code, status);
|
|
9779
10125
|
}
|
|
9780
|
-
process.stderr.write(
|
|
9781
|
-
`)
|
|
10126
|
+
process.stderr.write(
|
|
10127
|
+
`[mlclaw] delegated BrokerKit request failed: route=${delegatedRouteLabel(c)} status=502 class=${safeErrorClass(error)}
|
|
10128
|
+
`
|
|
10129
|
+
);
|
|
9782
10130
|
return delegatedErrorResponse(c, "source_unavailable", 502);
|
|
9783
10131
|
}
|
|
10132
|
+
function delegatedRouteLabel(c) {
|
|
10133
|
+
const pathLabel = c.req.path.replace(/\/requests\/[^/]+/u, "/requests/:handle");
|
|
10134
|
+
return `${c.req.method}:${pathLabel}`;
|
|
10135
|
+
}
|
|
10136
|
+
function safeErrorClass(error) {
|
|
10137
|
+
const name = error instanceof Error ? error.name : typeof error;
|
|
10138
|
+
return /^[A-Za-z][A-Za-z0-9]{0,79}$/u.test(name) ? name : "unknown";
|
|
10139
|
+
}
|
|
9784
10140
|
function delegatedHeaders(c) {
|
|
9785
10141
|
c.header("access-control-allow-origin", "null");
|
|
9786
|
-
c.header("access-control-allow-credentials", "true");
|
|
9787
10142
|
c.header("cache-control", "no-store");
|
|
9788
10143
|
c.header("vary", "origin");
|
|
9789
10144
|
c.header("x-content-type-options", "nosniff");
|
|
@@ -9960,6 +10315,10 @@ function optionalPositiveJsonInteger(value) {
|
|
|
9960
10315
|
if (value === void 0) return void 0;
|
|
9961
10316
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : "invalid";
|
|
9962
10317
|
}
|
|
10318
|
+
function optionalUseLimitJsonInteger(value) {
|
|
10319
|
+
if (value === null) return null;
|
|
10320
|
+
return optionalPositiveJsonInteger(value);
|
|
10321
|
+
}
|
|
9963
10322
|
function fixedWindowRateLimit(limit, windowMs) {
|
|
9964
10323
|
const windows = /* @__PURE__ */ new Map();
|
|
9965
10324
|
return (key) => {
|
|
@@ -10066,7 +10425,7 @@ function contentType(file) {
|
|
|
10066
10425
|
}
|
|
10067
10426
|
|
|
10068
10427
|
// src/mlclaw-space-runtime/mcp-credentials.ts
|
|
10069
|
-
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, hkdfSync as hkdfSync2, randomBytes as
|
|
10428
|
+
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, hkdfSync as hkdfSync2, randomBytes as randomBytes7 } from "node:crypto";
|
|
10070
10429
|
import fs4 from "node:fs/promises";
|
|
10071
10430
|
import path4 from "node:path";
|
|
10072
10431
|
var DEFAULT_REFRESH_TIMEOUT_MS = 3e4;
|
|
@@ -10199,7 +10558,7 @@ var McpCredentialStore = class {
|
|
|
10199
10558
|
async persist() {
|
|
10200
10559
|
const directory = path4.dirname(this.options.file);
|
|
10201
10560
|
await fs4.mkdir(directory, { recursive: true, mode: 448 });
|
|
10202
|
-
const temporary = `${this.options.file}.${process.pid}.${
|
|
10561
|
+
const temporary = `${this.options.file}.${process.pid}.${randomBytes7(6).toString("hex")}.tmp`;
|
|
10203
10562
|
const encrypted = encryptDocument(this.document, this.key);
|
|
10204
10563
|
try {
|
|
10205
10564
|
await fs4.writeFile(temporary, `${JSON.stringify(encrypted)}
|
|
@@ -10277,7 +10636,7 @@ var InvalidCredentialFileError = class extends Error {
|
|
|
10277
10636
|
}
|
|
10278
10637
|
};
|
|
10279
10638
|
function encryptDocument(document, key) {
|
|
10280
|
-
const iv =
|
|
10639
|
+
const iv = randomBytes7(12);
|
|
10281
10640
|
const cipher = createCipheriv2("aes-256-gcm", key, iv);
|
|
10282
10641
|
const plaintext = Buffer.from(JSON.stringify(document), "utf8");
|
|
10283
10642
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
@@ -10766,6 +11125,8 @@ var OPENCLAW_ENV_ALLOWLIST = [
|
|
|
10766
11125
|
"OPENCLAW_LIVE_DIR",
|
|
10767
11126
|
"OPENCLAW_STATE_DIR",
|
|
10768
11127
|
"OPENCLAW_WORKSPACE_DIR",
|
|
11128
|
+
"MLCLAW_HF_BROKER_URL",
|
|
11129
|
+
"MLCLAW_HF_BROKER_AGENT_SECRET_FILE",
|
|
10769
11130
|
"TELEGRAM_API_ROOT",
|
|
10770
11131
|
"TELEGRAM_ALLOWED_USERS",
|
|
10771
11132
|
"TELEGRAM_BOT_TOKEN",
|