mlclaw 0.3.1 → 0.3.2
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 +16 -4
- package/README.md +8 -6
- package/assets/hf-logo.png +0 -0
- package/dist/hf-tooling-seed.js +12 -0
- package/dist/mlclaw-space-runtime.js +399 -94
- package/dist/mlclaw.mjs +20 -9
- package/entrypoint.sh +11 -7
- package/package.json +5 -6
- package/hf-broker.scope.json +0 -38
package/Dockerfile
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
ARG OPENCLAW_VERSION=2026.7.1-beta.5
|
|
2
2
|
ARG OPENCLAW_BASE_IMAGE=ghcr.io/openclaw/openclaw:${OPENCLAW_VERSION}
|
|
3
3
|
ARG BROKERKIT_PLUGIN_VERSION=0.1.0
|
|
4
|
-
ARG BROKERKIT_VERSION=
|
|
5
|
-
ARG MLCLAW_RUNTIME_IMAGE=ghcr.io/osolmaz/mlclaw:0.3.
|
|
4
|
+
ARG BROKERKIT_VERSION=a2adf6e626bbd509605bb7c72a7f3a8b1c00f045
|
|
5
|
+
ARG MLCLAW_RUNTIME_IMAGE=ghcr.io/osolmaz/mlclaw:0.3.2-openclaw-2026.7.1-beta.5
|
|
6
6
|
|
|
7
7
|
FROM golang:1.26.5-bookworm AS hf-broker-build
|
|
8
8
|
ARG BROKERKIT_VERSION
|
|
@@ -11,7 +11,17 @@ RUN git init /src \
|
|
|
11
11
|
&& git -C /src checkout --detach FETCH_HEAD \
|
|
12
12
|
&& test "$(git -C /src rev-parse HEAD)" = "$BROKERKIT_VERSION" \
|
|
13
13
|
&& cd /src \
|
|
14
|
-
&& GOWORK=off go build -trimpath -o /out/hf-broker ./brokers/huggingface/cmd/hf-broker
|
|
14
|
+
&& GOWORK=off go build -trimpath -o /out/hf-broker ./brokers/huggingface/cmd/hf-broker \
|
|
15
|
+
&& /out/hf-broker policy render \
|
|
16
|
+
--preset request-all-agent-operations \
|
|
17
|
+
--client default \
|
|
18
|
+
--profile-out /out/hf-broker.policy-profile.json \
|
|
19
|
+
--output /out/hf-broker.scope.json \
|
|
20
|
+
--manifest-out /out/hf-broker.policy-manifest.json \
|
|
21
|
+
&& /out/hf-broker doctor policy \
|
|
22
|
+
--profile /out/hf-broker.policy-profile.json \
|
|
23
|
+
--scope /out/hf-broker.scope.json \
|
|
24
|
+
--manifest /out/hf-broker.policy-manifest.json
|
|
15
25
|
|
|
16
26
|
FROM node:24-bookworm-slim AS brokerkit-plugin-build
|
|
17
27
|
ARG BROKERKIT_VERSION
|
|
@@ -69,7 +79,9 @@ COPY --from=sync-build /build/dist/hf-state-sync.js /app/hf-state-sync.js
|
|
|
69
79
|
COPY --from=sync-build /build/dist/hf-tooling-seed.js /app/hf-tooling-seed.js
|
|
70
80
|
COPY --from=sync-build /build/dist/mlclaw-space-runtime.js /app/mlclaw-space-runtime.js
|
|
71
81
|
COPY --from=hf-broker-build /out/hf-broker /usr/local/bin/hf-broker
|
|
72
|
-
COPY hf-broker.scope.json /app/hf-broker.scope.json
|
|
82
|
+
COPY --from=hf-broker-build /out/hf-broker.scope.json /app/hf-broker.scope.json
|
|
83
|
+
COPY --from=hf-broker-build /out/hf-broker.policy-profile.json /app/hf-broker.policy-profile.json
|
|
84
|
+
COPY --from=hf-broker-build /out/hf-broker.policy-manifest.json /app/hf-broker.policy-manifest.json
|
|
73
85
|
COPY --chown=node:node openclaw.default.json /app/openclaw.default.json
|
|
74
86
|
COPY --chown=node:node entrypoint.sh /app/entrypoint.sh
|
|
75
87
|
COPY --chown=node:node assets/ /app/assets/
|
package/README.md
CHANGED
|
@@ -209,10 +209,12 @@ merges every BrokerKit-compatible backend configured in
|
|
|
209
209
|
`MLCLAW_OPERATOR_BROKERS_FILE` and lets administrators inspect, approve, deny,
|
|
210
210
|
cancel, or revoke requests. The plugin registers the Gateway tab, while ML Claw
|
|
211
211
|
serves the immutable packaged UI from its trusted HTTP boundary and gives it a
|
|
212
|
-
small
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
small popover inside the Gateway. Administrators decide requests directly in
|
|
213
|
+
the sandboxed popover when `MLCLAW_BROKERKIT_POPOVER_DECISIONS=true`; otherwise
|
|
214
|
+
the popover remains read-only and decisions use the standalone protected page.
|
|
215
|
+
Both flows use a renewable, short-lived, admin-bound browser token.
|
|
216
|
+
Broker operator tokens remain in backend-only files and are never sent to the
|
|
217
|
+
browser or OpenClaw. See
|
|
216
218
|
[Operator Broker Configuration](docs/operator-brokers-config.md).
|
|
217
219
|
|
|
218
220
|
- `/mlclaw/credentials`: connect or disconnect Hugging Face MCP and Research
|
|
@@ -239,9 +241,9 @@ MLCLAW_BRAND_SHORT_NAME=Bob
|
|
|
239
241
|
MLCLAW_BRAND_THEME_COLOR=#111827
|
|
240
242
|
MLCLAW_BRAND_LOGO=mlclaw.svg
|
|
241
243
|
MLCLAW_BRAND_FAVICON=hf-logo.svg
|
|
242
|
-
MLCLAW_BRAND_FAVICON_32=hf-logo.
|
|
244
|
+
MLCLAW_BRAND_FAVICON_32=hf-logo.png
|
|
243
245
|
MLCLAW_BRAND_FAVICON_ICO=hf-logo.svg
|
|
244
|
-
MLCLAW_BRAND_APPLE_TOUCH_ICON=
|
|
246
|
+
MLCLAW_BRAND_APPLE_TOUCH_ICON=hf-logo.png
|
|
245
247
|
MLCLAW_BRAND_ASSISTANT_AVATAR=assistant-avatar.svg
|
|
246
248
|
```
|
|
247
249
|
|
|
Binary file
|
package/dist/hf-tooling-seed.js
CHANGED
|
@@ -126,6 +126,18 @@ This workspace has Hugging Face tooling preinstalled. Use the Hugging Face CLI
|
|
|
126
126
|
\`hf\`, \`hf-discover\`, and \`uv\` for Hub, dataset, model, Space, and bucket
|
|
127
127
|
work.
|
|
128
128
|
|
|
129
|
+
Protected Hugging Face mutations use the preconfigured HF Broker. Never ask
|
|
130
|
+
the user for a Hugging Face token and never run \`hf auth login\`. Use the
|
|
131
|
+
\`hf_repo_create\`, \`hf_operation_get\`, and \`hf_operation_wait\` MCP tools,
|
|
132
|
+
or run \`hf-broker client repo create OWNER/NAME --type dataset\`. A protected
|
|
133
|
+
action creates an approval in the ML Claw BrokerKit control and may also be
|
|
134
|
+
sent through configured OpenClaw channels. The operation ID is resumable with
|
|
135
|
+
\`hf-broker client operation wait ID\`.
|
|
136
|
+
|
|
137
|
+
After repository creation, use the brokered Git transport for repository
|
|
138
|
+
contents. The limited broker credential is not a Hugging Face Hub token; do
|
|
139
|
+
not report it as missing Hub access.
|
|
140
|
+
|
|
129
141
|
Hugging Face agent skills are available in both \`.agents/skills\` and
|
|
130
142
|
\`skills\`. Prefer these skills when the task involves Hugging Face:
|
|
131
143
|
|
|
@@ -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) {
|
|
@@ -4704,7 +4705,6 @@ var approvalSchema = external_exports.object({
|
|
|
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),
|
|
@@ -4808,7 +4808,6 @@ var BrokerOperatorClient = class {
|
|
|
4808
4808
|
expected_revision: decision.expectedRevision,
|
|
4809
4809
|
idempotency_key: decision.idempotencyKey,
|
|
4810
4810
|
on_behalf_of: decision.onBehalfOf,
|
|
4811
|
-
...decision.reason ? { decision_reason: decision.reason } : {},
|
|
4812
4811
|
...decision.durationSeconds || decision.maxUses ? {
|
|
4813
4812
|
constraints: {
|
|
4814
4813
|
...decision.durationSeconds ? { duration_seconds: decision.durationSeconds } : {},
|
|
@@ -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,15 +7462,17 @@ 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
7478
|
authorize(header) {
|
|
@@ -7404,7 +7482,7 @@ var DelegatedBrokerKit = class {
|
|
|
7404
7482
|
const encoded = authenticatedTokenPayload(header, (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,7 +7749,6 @@ 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
7753
|
...options.maxUses ? { maxUses: options.maxUses } : {}
|
|
7639
7754
|
};
|
|
@@ -7678,10 +7793,10 @@ function validTokenPayload(value) {
|
|
|
7678
7793
|
return hasExactTokenFields(record) && validTokenIdentity(record) && validTokenTimes(record) && validTokenNonce(record);
|
|
7679
7794
|
}
|
|
7680
7795
|
function hasExactTokenFields(record) {
|
|
7681
|
-
return Object.keys(record).sort().join(",") === "audience,expiresAt,issuedAt,nonce,subject,version";
|
|
7796
|
+
return Object.keys(record).sort().join(",") === "access,audience,expiresAt,issuedAt,nonce,subject,version";
|
|
7682
7797
|
}
|
|
7683
7798
|
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;
|
|
7799
|
+
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
7800
|
}
|
|
7686
7801
|
function validTokenTimes(record) {
|
|
7687
7802
|
return typeof record.issuedAt === "number" && Number.isSafeInteger(record.issuedAt) && typeof record.expiresAt === "number" && Number.isSafeInteger(record.expiresAt);
|
|
@@ -8429,6 +8544,7 @@ async function configureOpenClawGateway(config2) {
|
|
|
8429
8544
|
};
|
|
8430
8545
|
configureOpenClawModels(openclawConfig, config2);
|
|
8431
8546
|
configureManagedMcpServers(openclawConfig, config2);
|
|
8547
|
+
configureBrokerMcpServer(openclawConfig, config2);
|
|
8432
8548
|
configureBrokerKitPlugin(openclawConfig, config2);
|
|
8433
8549
|
await fs.mkdir(path.dirname(config2.openclawConfigPath), { recursive: true });
|
|
8434
8550
|
await fs.writeFile(config2.openclawConfigPath, `${JSON.stringify(openclawConfig, null, 2)}
|
|
@@ -8438,6 +8554,24 @@ async function configureOpenClawGateway(config2) {
|
|
|
8438
8554
|
await fs.chown(config2.openclawConfigPath, config2.openclawUid, config2.openclawGid);
|
|
8439
8555
|
}
|
|
8440
8556
|
}
|
|
8557
|
+
function configureBrokerMcpServer(openclawConfig, config2) {
|
|
8558
|
+
const servers = object(object(openclawConfig, "mcp"), "servers");
|
|
8559
|
+
if (!config2.brokerAgentUrl || !config2.brokerAgentSecretFile) {
|
|
8560
|
+
delete servers["huggingface-broker"];
|
|
8561
|
+
return;
|
|
8562
|
+
}
|
|
8563
|
+
const existing = objectValue2(servers["huggingface-broker"]);
|
|
8564
|
+
servers["huggingface-broker"] = {
|
|
8565
|
+
...existing,
|
|
8566
|
+
command: "/usr/local/bin/hf-broker",
|
|
8567
|
+
args: ["mcp"],
|
|
8568
|
+
env: {
|
|
8569
|
+
MLCLAW_HF_BROKER_URL: config2.brokerAgentUrl,
|
|
8570
|
+
MLCLAW_HF_BROKER_AGENT_SECRET_FILE: config2.brokerAgentSecretFile
|
|
8571
|
+
},
|
|
8572
|
+
...existing?.enabled === false ? { enabled: false } : { enabled: true }
|
|
8573
|
+
};
|
|
8574
|
+
}
|
|
8441
8575
|
function configureBrokerKitPlugin(openclawConfig, config2) {
|
|
8442
8576
|
const plugins = object(openclawConfig, "plugins");
|
|
8443
8577
|
const load = object(plugins, "load");
|
|
@@ -8576,7 +8710,7 @@ function uniqueStrings(value, required) {
|
|
|
8576
8710
|
}
|
|
8577
8711
|
|
|
8578
8712
|
// src/mlclaw-space-runtime/openai-credentials.ts
|
|
8579
|
-
import { createCipheriv, createDecipheriv, hkdfSync, randomBytes as
|
|
8713
|
+
import { createCipheriv, createDecipheriv, hkdfSync, randomBytes as randomBytes5 } from "node:crypto";
|
|
8580
8714
|
import fs2 from "node:fs/promises";
|
|
8581
8715
|
import path2 from "node:path";
|
|
8582
8716
|
function openAiConfigured(env = process.env) {
|
|
@@ -8645,7 +8779,7 @@ var OpenAiCredentialStore = class {
|
|
|
8645
8779
|
if (!normalized) {
|
|
8646
8780
|
throw new Error("valid OpenAI API key is required");
|
|
8647
8781
|
}
|
|
8648
|
-
const iv =
|
|
8782
|
+
const iv = randomBytes5(12);
|
|
8649
8783
|
const cipher = createCipheriv("aes-256-gcm", this.key, iv);
|
|
8650
8784
|
const ciphertext = Buffer.concat([cipher.update(normalized, "utf8"), cipher.final()]);
|
|
8651
8785
|
const envelope = {
|
|
@@ -8656,7 +8790,7 @@ var OpenAiCredentialStore = class {
|
|
|
8656
8790
|
ciphertext: ciphertext.toString("base64url")
|
|
8657
8791
|
};
|
|
8658
8792
|
const directory = path2.dirname(this.file);
|
|
8659
|
-
const temporary = `${this.file}.${process.pid}.${
|
|
8793
|
+
const temporary = `${this.file}.${process.pid}.${randomBytes5(6).toString("hex")}.tmp`;
|
|
8660
8794
|
await fs2.mkdir(directory, { recursive: true, mode: 448 });
|
|
8661
8795
|
try {
|
|
8662
8796
|
await fs2.writeFile(temporary, `${JSON.stringify(envelope)}
|
|
@@ -8942,7 +9076,7 @@ function positiveNumber2(value) {
|
|
|
8942
9076
|
}
|
|
8943
9077
|
|
|
8944
9078
|
// src/mlclaw-space-runtime/cookies.ts
|
|
8945
|
-
import { createHmac as createHmac4, randomBytes as
|
|
9079
|
+
import { createHmac as createHmac4, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
|
|
8946
9080
|
function createSignedCookie(options, payload) {
|
|
8947
9081
|
const body = Buffer.from(JSON.stringify({
|
|
8948
9082
|
...payload,
|
|
@@ -8991,7 +9125,7 @@ function clearCookie(name, secure) {
|
|
|
8991
9125
|
});
|
|
8992
9126
|
}
|
|
8993
9127
|
function randomState() {
|
|
8994
|
-
return
|
|
9128
|
+
return randomBytes6(24).toString("base64url");
|
|
8995
9129
|
}
|
|
8996
9130
|
function sign2(value, secret) {
|
|
8997
9131
|
return createHmac4("sha256", secret).update(value).digest("base64url");
|
|
@@ -9084,6 +9218,10 @@ function normalizeNext(value) {
|
|
|
9084
9218
|
var SHELL_MARKER = "data-mlclaw-shell";
|
|
9085
9219
|
var BRANDING_MARKER = "data-mlclaw-branding";
|
|
9086
9220
|
var CONTROL_BRANDING_MARKER = "data-mlclaw-control-branding";
|
|
9221
|
+
var BROKERKIT_DELEGATED_UI_BOOTSTRAP = Buffer.from(
|
|
9222
|
+
JSON.stringify({ version: 1, mode: "delegated-web", basePath: "/mlclaw/api/brokerkit" }),
|
|
9223
|
+
"utf8"
|
|
9224
|
+
).toString("base64url");
|
|
9087
9225
|
var CONTROL_BRANDING_SCRIPT_PATH = "/assets/mlclaw-control-branding.js";
|
|
9088
9226
|
var CONTROL_BRANDING_SCRIPT = `(function () {
|
|
9089
9227
|
var productName = "ML Claw";
|
|
@@ -9152,31 +9290,125 @@ var CONTROL_BRANDING_SCRIPT = `(function () {
|
|
|
9152
9290
|
}
|
|
9153
9291
|
});
|
|
9154
9292
|
}
|
|
9155
|
-
function
|
|
9156
|
-
|
|
9157
|
-
var
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9293
|
+
function installApprovals() {
|
|
9294
|
+
var shell = document.querySelector("[data-mlclaw-shell]");
|
|
9295
|
+
var button = document.querySelector("[data-mlclaw-approvals-button]");
|
|
9296
|
+
var popover = document.querySelector("[data-mlclaw-approvals-popover]");
|
|
9297
|
+
var frame = document.querySelector("[data-mlclaw-approvals-frame]");
|
|
9298
|
+
var badge = document.querySelector("[data-mlclaw-approvals-badge]");
|
|
9299
|
+
var close = document.querySelector("[data-mlclaw-approvals-close]");
|
|
9300
|
+
if (!shell || !button || !popover || !frame || button.getAttribute("data-ready") === "1") return;
|
|
9301
|
+
button.setAttribute("data-ready", "1");
|
|
9302
|
+
function invalidateFrame() {
|
|
9303
|
+
if (frame.contentWindow) {
|
|
9304
|
+
frame.contentWindow.postMessage({ type: "brokerkit.operator-ui.invalidate", version: 1 }, "*");
|
|
9305
|
+
}
|
|
9306
|
+
}
|
|
9307
|
+
function setOpen(open) {
|
|
9308
|
+
popover.hidden = !open;
|
|
9309
|
+
button.setAttribute("aria-expanded", open ? "true" : "false");
|
|
9310
|
+
if (!open) return;
|
|
9311
|
+
if (!frame.getAttribute("src")) frame.setAttribute("src", frame.getAttribute("data-src"));
|
|
9312
|
+
else invalidateFrame();
|
|
9313
|
+
}
|
|
9314
|
+
frame.addEventListener("load", function () { if (!popover.hidden) invalidateFrame(); });
|
|
9315
|
+
button.addEventListener("click", function () { setOpen(popover.hidden); });
|
|
9316
|
+
if (close) close.addEventListener("click", function () { setOpen(false); });
|
|
9317
|
+
document.addEventListener("click", function (event) {
|
|
9318
|
+
if (!popover.hidden && !shell.contains(event.target)) setOpen(false);
|
|
9319
|
+
});
|
|
9320
|
+
document.addEventListener("keydown", function (event) {
|
|
9321
|
+
if (event.key === "Escape") setOpen(false);
|
|
9322
|
+
});
|
|
9323
|
+
window.addEventListener("message", function (event) {
|
|
9324
|
+
var data = event.data;
|
|
9325
|
+
if (
|
|
9326
|
+
event.source !== frame.contentWindow ||
|
|
9327
|
+
!data ||
|
|
9328
|
+
typeof data !== "object" ||
|
|
9329
|
+
Object.keys(data).sort().join(",") !== "nonce,type,version" ||
|
|
9330
|
+
data.type !== "brokerkit.delegated-web.open" ||
|
|
9331
|
+
data.version !== 1 ||
|
|
9332
|
+
typeof data.nonce !== "string" ||
|
|
9333
|
+
!/^[a-f0-9]{32}$/.test(data.nonce)
|
|
9334
|
+
) return;
|
|
9335
|
+
window.location.assign("/plugins/brokerkit/ui/#${BROKERKIT_DELEGATED_UI_BOOTSTRAP}");
|
|
9336
|
+
});
|
|
9337
|
+
var summaryCursor = "";
|
|
9338
|
+
var stopped = false;
|
|
9339
|
+
function acceptSummary(summary) {
|
|
9340
|
+
if (
|
|
9341
|
+
!summary ||
|
|
9342
|
+
typeof summary !== "object" ||
|
|
9343
|
+
Object.keys(summary).sort().join(",") !== "api_version,cursor,healthy,pending" ||
|
|
9344
|
+
summary.api_version !== "brokerkit.io/operator-ui/v1" ||
|
|
9345
|
+
typeof summary.cursor !== "string" ||
|
|
9346
|
+
summary.cursor.length < 1 ||
|
|
9347
|
+
summary.cursor.length > 128 ||
|
|
9348
|
+
typeof summary.pending !== "number" ||
|
|
9349
|
+
!Number.isSafeInteger(summary.pending) ||
|
|
9350
|
+
summary.pending < 0 ||
|
|
9351
|
+
typeof summary.healthy !== "boolean"
|
|
9352
|
+
) return false;
|
|
9353
|
+
var changed = summaryCursor && summaryCursor !== summary.cursor;
|
|
9354
|
+
summaryCursor = summary.cursor;
|
|
9355
|
+
if (badge) {
|
|
9356
|
+
badge.textContent = summary.pending > 99 ? "99+" : String(summary.pending);
|
|
9357
|
+
badge.hidden = summary.pending < 1;
|
|
9358
|
+
}
|
|
9359
|
+
button.setAttribute("aria-label", summary.pending > 0 ? "Open approval requests (" + summary.pending + " pending)" : "Open approval requests");
|
|
9360
|
+
if (changed) invalidateFrame();
|
|
9361
|
+
return true;
|
|
9362
|
+
}
|
|
9363
|
+
function refresh() {
|
|
9364
|
+
return fetch("/mlclaw/api/brokerkit/summary", { credentials: "same-origin", cache: "no-store" })
|
|
9365
|
+
.then(function (response) { return response.ok ? response.json() : null; })
|
|
9366
|
+
.then(acceptSummary)
|
|
9367
|
+
.catch(function () { return false; });
|
|
9164
9368
|
}
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
if (
|
|
9168
|
-
|
|
9169
|
-
|
|
9369
|
+
function watch(delay) {
|
|
9370
|
+
if (stopped) return;
|
|
9371
|
+
if (!summaryCursor) {
|
|
9372
|
+
refresh().then(function () { window.setTimeout(function () { watch(250); }, delay); });
|
|
9373
|
+
return;
|
|
9170
9374
|
}
|
|
9375
|
+
fetch("/mlclaw/api/brokerkit/summary/events?cursor=" + encodeURIComponent(summaryCursor) + "&wait_seconds=25", {
|
|
9376
|
+
credentials: "same-origin",
|
|
9377
|
+
cache: "no-store"
|
|
9378
|
+
}).then(function (response) {
|
|
9379
|
+
if (response.status === 410) {
|
|
9380
|
+
summaryCursor = "";
|
|
9381
|
+
return null;
|
|
9382
|
+
}
|
|
9383
|
+
if (!response.ok) throw new Error("summary unavailable");
|
|
9384
|
+
return response.json();
|
|
9385
|
+
}).then(function (event) {
|
|
9386
|
+
if (
|
|
9387
|
+
event &&
|
|
9388
|
+
typeof event === "object" &&
|
|
9389
|
+
Object.keys(event).sort().join(",") === "api_version,changed,cursor" &&
|
|
9390
|
+
event.api_version === "brokerkit.io/operator-ui/v1" &&
|
|
9391
|
+
typeof event.cursor === "string" &&
|
|
9392
|
+
event.cursor.length >= 1 &&
|
|
9393
|
+
event.cursor.length <= 128 &&
|
|
9394
|
+
typeof event.changed === "boolean"
|
|
9395
|
+
) {
|
|
9396
|
+
summaryCursor = event.cursor;
|
|
9397
|
+
if (event.changed) invalidateFrame();
|
|
9398
|
+
return event.changed ? refresh() : true;
|
|
9399
|
+
}
|
|
9400
|
+
return false;
|
|
9401
|
+
}).then(function (ok) {
|
|
9402
|
+
window.setTimeout(function () { watch(ok ? 250 : Math.min(delay * 2, 30000)); }, ok ? 0 : delay);
|
|
9403
|
+
}).catch(function () {
|
|
9404
|
+
window.setTimeout(function () { watch(Math.min(delay * 2, 30000)); }, delay);
|
|
9405
|
+
});
|
|
9171
9406
|
}
|
|
9407
|
+
refresh().then(function () { watch(250); });
|
|
9408
|
+
window.setInterval(refresh, 300000);
|
|
9409
|
+
window.addEventListener("focus", function () { refresh(); });
|
|
9410
|
+
window.addEventListener("beforeunload", function () { stopped = true; });
|
|
9172
9411
|
}
|
|
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
9412
|
if (!document.documentElement.hasAttribute(marker)) {
|
|
9181
9413
|
document.documentElement.setAttribute(marker, "1");
|
|
9182
9414
|
var attachShadow = Element.prototype.attachShadow;
|
|
@@ -9190,6 +9422,7 @@ var CONTROL_BRANDING_SCRIPT = `(function () {
|
|
|
9190
9422
|
requestAnimationFrame(function () {
|
|
9191
9423
|
observeExistingShadowRoots(document);
|
|
9192
9424
|
scan(document);
|
|
9425
|
+
installApprovals();
|
|
9193
9426
|
});
|
|
9194
9427
|
}
|
|
9195
9428
|
})();
|
|
@@ -9222,6 +9455,13 @@ function rewriteOpenClawHtml(html, branding) {
|
|
|
9222
9455
|
function injectMlClawShell(html, branding) {
|
|
9223
9456
|
const shell = `
|
|
9224
9457
|
<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;">
|
|
9458
|
+
<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);">
|
|
9459
|
+
<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;">
|
|
9460
|
+
<span>Approvals</span>
|
|
9461
|
+
<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>
|
|
9462
|
+
</header>
|
|
9463
|
+
<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>
|
|
9464
|
+
</section>
|
|
9225
9465
|
<div style="display:flex;gap:8px;align-items:center;">
|
|
9226
9466
|
<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
9467
|
<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 +9469,10 @@ function injectMlClawShell(html, branding) {
|
|
|
9229
9469
|
<circle cx="12" cy="12" r="3"></circle>
|
|
9230
9470
|
</svg>
|
|
9231
9471
|
</a>
|
|
9472
|
+
<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;">
|
|
9473
|
+
<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>
|
|
9474
|
+
<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>
|
|
9475
|
+
</button>
|
|
9232
9476
|
</div>
|
|
9233
9477
|
</div>
|
|
9234
9478
|
`;
|
|
@@ -9272,6 +9516,9 @@ function createSpaceRuntimeApp(config2, controls) {
|
|
|
9272
9516
|
const delegatedBrokerKit = new DelegatedBrokerKit(operatorBrokers, config2.sessionSecret);
|
|
9273
9517
|
const allowDelegatedSessionSnapshot = fixedWindowRateLimit(12, 6e4);
|
|
9274
9518
|
const allowDelegatedActorSnapshot = fixedWindowRateLimit(60, 6e4);
|
|
9519
|
+
const allowBrokerKitSummary = fixedWindowRateLimit(12, 6e4);
|
|
9520
|
+
const allowDelegatedEvents = fixedWindowRateLimit(60, 6e4);
|
|
9521
|
+
const allowSummaryEvents = fixedWindowRateLimit(60, 6e4);
|
|
9275
9522
|
const openAiCredentials = new OpenAiCredentialStore(config2.openaiCredentialStoreFile, config2.credentialKey);
|
|
9276
9523
|
app.get("/health", (c) => health(c, config2, controls));
|
|
9277
9524
|
app.get("/healthz", (c) => health(c, config2, controls));
|
|
@@ -9341,11 +9588,33 @@ function createSpaceRuntimeApp(config2, controls) {
|
|
|
9341
9588
|
}
|
|
9342
9589
|
return c.json(await statusPayload(config2, controls));
|
|
9343
9590
|
});
|
|
9591
|
+
app.get("/mlclaw/api/brokerkit/summary", async (c) => {
|
|
9592
|
+
const auth = requireAdmin(c, config2);
|
|
9593
|
+
if (auth instanceof Response) return auth;
|
|
9594
|
+
if (!allowBrokerKitSummary(auth.username)) return c.json({ ok: false, error: "rate limited" }, 429);
|
|
9595
|
+
try {
|
|
9596
|
+
return c.json(await delegatedBrokerKit.summary());
|
|
9597
|
+
} catch {
|
|
9598
|
+
return c.json({ ok: false, error: "operator inbox unavailable" }, 503);
|
|
9599
|
+
}
|
|
9600
|
+
});
|
|
9601
|
+
app.get("/mlclaw/api/brokerkit/summary/events", async (c) => {
|
|
9602
|
+
const auth = requireAdmin(c, config2);
|
|
9603
|
+
if (auth instanceof Response) return auth;
|
|
9604
|
+
if (!allowSummaryEvents(auth.username)) return c.json({ ok: false, error: "rate limited" }, 429);
|
|
9605
|
+
const input = delegatedEventQuery(c.req.url);
|
|
9606
|
+
if (!input) return c.json({ ok: false, error: "invalid request" }, 400);
|
|
9607
|
+
try {
|
|
9608
|
+
return c.json(await delegatedBrokerKit.events(input.cursor, input.waitSeconds, c.req.raw.signal));
|
|
9609
|
+
} catch (error) {
|
|
9610
|
+
return delegatedFailure(c, error);
|
|
9611
|
+
}
|
|
9612
|
+
});
|
|
9344
9613
|
app.options("/mlclaw/api/brokerkit/*", (c) => delegatedPreflight(c));
|
|
9345
9614
|
app.post("/mlclaw/api/brokerkit/session", (c) => {
|
|
9346
9615
|
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
9347
9616
|
if (!identity) return delegatedErrorResponse(c, "not_authorized", 401);
|
|
9348
|
-
return delegatedJson(c, delegatedBrokerKit.issueSession(identity.actor));
|
|
9617
|
+
return delegatedJson(c, delegatedBrokerKit.issueSession(identity.actor, identity.access));
|
|
9349
9618
|
});
|
|
9350
9619
|
app.get("/mlclaw/api/brokerkit/snapshot", async (c) => {
|
|
9351
9620
|
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
@@ -9359,6 +9628,18 @@ function createSpaceRuntimeApp(config2, controls) {
|
|
|
9359
9628
|
return delegatedFailure(c, error);
|
|
9360
9629
|
}
|
|
9361
9630
|
});
|
|
9631
|
+
app.get("/mlclaw/api/brokerkit/events", async (c) => {
|
|
9632
|
+
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
9633
|
+
if (!identity) return delegatedErrorResponse(c, "not_authorized", 401);
|
|
9634
|
+
if (!allowDelegatedEvents(identity.sessionId)) return delegatedErrorResponse(c, "rate_limited", 429);
|
|
9635
|
+
const input = delegatedEventQuery(c.req.url);
|
|
9636
|
+
if (!input) return delegatedErrorResponse(c, "invalid_input", 400);
|
|
9637
|
+
try {
|
|
9638
|
+
return delegatedJson(c, await delegatedBrokerKit.events(input.cursor, input.waitSeconds, c.req.raw.signal));
|
|
9639
|
+
} catch (error) {
|
|
9640
|
+
return delegatedFailure(c, error);
|
|
9641
|
+
}
|
|
9642
|
+
});
|
|
9362
9643
|
app.get("/mlclaw/api/brokerkit/requests/:handle", async (c) => {
|
|
9363
9644
|
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
9364
9645
|
if (!identity) return delegatedErrorResponse(c, "not_authorized", 401);
|
|
@@ -9371,23 +9652,22 @@ function createSpaceRuntimeApp(config2, controls) {
|
|
|
9371
9652
|
for (const action of ["approve", "deny", "cancel", "revoke"]) {
|
|
9372
9653
|
app.post(`/mlclaw/api/brokerkit/requests/:handle/${action}`, async (c) => {
|
|
9373
9654
|
const identity = delegatedIdentity(c, delegatedBrokerKit);
|
|
9374
|
-
if (!identity) return delegatedErrorResponse(c, "not_authorized", 401);
|
|
9655
|
+
if (!identity || identity.access !== "decide") return delegatedErrorResponse(c, "not_authorized", 401);
|
|
9375
9656
|
const body = await readBoundedJson(c, 16384);
|
|
9376
|
-
if (!body || Object.keys(body).some((key) => !["expectedRevision", "
|
|
9657
|
+
if (!body || Object.keys(body).some((key) => !["expectedRevision", "constraints"].includes(key))) {
|
|
9377
9658
|
return delegatedErrorResponse(c, "invalid_input", 400);
|
|
9378
9659
|
}
|
|
9379
9660
|
const constraints = recordValue(body.constraints);
|
|
9380
9661
|
const expectedRevision = positiveJsonInteger(body.expectedRevision);
|
|
9381
9662
|
const durationSeconds = optionalPositiveJsonInteger(constraints?.durationSeconds);
|
|
9382
9663
|
const maxUses = optionalPositiveJsonInteger(constraints?.maxUses);
|
|
9383
|
-
if (!expectedRevision || body.
|
|
9664
|
+
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
9665
|
return delegatedErrorResponse(c, "invalid_input", 400);
|
|
9385
9666
|
}
|
|
9386
9667
|
try {
|
|
9387
9668
|
return delegatedJson(
|
|
9388
9669
|
c,
|
|
9389
9670
|
await delegatedBrokerKit.decide(c.req.param("handle"), action, expectedRevision, identity.actor, {
|
|
9390
|
-
...typeof body.reason === "string" ? { reason: body.reason } : {},
|
|
9391
9671
|
...typeof durationSeconds === "number" ? { durationSeconds } : {},
|
|
9392
9672
|
...typeof maxUses === "number" ? { maxUses } : {}
|
|
9393
9673
|
})
|
|
@@ -9668,16 +9948,28 @@ async function trustedBrokerKitUi(c, config2, delegatedBrokerKit) {
|
|
|
9668
9948
|
if (relative === "index.html") {
|
|
9669
9949
|
const destination = c.req.header("sec-fetch-dest");
|
|
9670
9950
|
if (destination !== "iframe" && destination !== "document") return c.text("not found\n", 404);
|
|
9951
|
+
const query = new URL(c.req.url).search;
|
|
9952
|
+
const embeddedPopover = destination === "iframe" && query === "?embed=popover";
|
|
9953
|
+
if (query && !embeddedPopover) return c.text("not found\n", 404);
|
|
9671
9954
|
const auth = requireAdmin(c, config2);
|
|
9672
9955
|
if (auth instanceof Response) return auth;
|
|
9673
9956
|
try {
|
|
9674
9957
|
const template = await fs3.readFile(file, "utf8");
|
|
9675
|
-
const
|
|
9676
|
-
|
|
9958
|
+
const delegatedSession = destination === "document" || embeddedPopover;
|
|
9959
|
+
const marker = !delegatedSession ? '<meta name="brokerkit-delegated-top-level">' : `<meta name="brokerkit-delegated-session" content="${Buffer.from(
|
|
9960
|
+
JSON.stringify(
|
|
9961
|
+
delegatedBrokerKit.issueSession(
|
|
9962
|
+
auth.username,
|
|
9963
|
+
embeddedPopover && !config2.brokerKitPopoverDecisions ? "read" : "decide"
|
|
9964
|
+
)
|
|
9965
|
+
),
|
|
9677
9966
|
"utf8"
|
|
9678
9967
|
).toString("base64url")}">`;
|
|
9679
9968
|
if (!template.includes("</head>")) return c.text("not found\n", 404);
|
|
9680
|
-
const headers2 = trustedBrokerKitHeaders(
|
|
9969
|
+
const headers2 = trustedBrokerKitHeaders(
|
|
9970
|
+
embeddedPopover ? "popover" : destination === "iframe" ? "launcher" : "top-level",
|
|
9971
|
+
new URL(c.req.url).origin
|
|
9972
|
+
);
|
|
9681
9973
|
headers2.set("content-type", "text/html; charset=utf-8");
|
|
9682
9974
|
return new Response(template.replace("</head>", `${marker}</head>`), { status: 200, headers: headers2 });
|
|
9683
9975
|
} catch {
|
|
@@ -9686,15 +9978,16 @@ async function trustedBrokerKitUi(c, config2, delegatedBrokerKit) {
|
|
|
9686
9978
|
}
|
|
9687
9979
|
const response = await serveFile(file, contentType(file), true);
|
|
9688
9980
|
if (response.status !== 200) return response;
|
|
9689
|
-
const headers = trustedBrokerKitHeaders("asset");
|
|
9981
|
+
const headers = trustedBrokerKitHeaders("asset", new URL(c.req.url).origin);
|
|
9690
9982
|
headers.set("content-type", response.headers.get("content-type") ?? "application/octet-stream");
|
|
9691
9983
|
return new Response(response.body, { status: response.status, headers });
|
|
9692
9984
|
}
|
|
9693
|
-
function trustedBrokerKitHeaders(mode) {
|
|
9985
|
+
function trustedBrokerKitHeaders(mode, origin) {
|
|
9694
9986
|
const asset = mode === "asset";
|
|
9987
|
+
const sandbox = mode === "top-level" || mode === "popover" ? "sandbox allow-scripts; " : "";
|
|
9695
9988
|
const headers = new Headers({
|
|
9696
9989
|
"cache-control": asset ? "public, max-age=31536000, immutable" : "no-store",
|
|
9697
|
-
"content-security-policy":
|
|
9990
|
+
"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
9991
|
"cross-origin-resource-policy": asset ? "cross-origin" : "same-origin",
|
|
9699
9992
|
"referrer-policy": "no-referrer",
|
|
9700
9993
|
"x-content-type-options": "nosniff",
|
|
@@ -9747,6 +10040,16 @@ function requireCsrf(c, config2, username) {
|
|
|
9747
10040
|
function delegatedOriginAllowed(c) {
|
|
9748
10041
|
return c.req.header("origin") === "null";
|
|
9749
10042
|
}
|
|
10043
|
+
function delegatedEventQuery(urlValue) {
|
|
10044
|
+
const url = new URL(urlValue);
|
|
10045
|
+
if ([...url.searchParams.keys()].some((key) => key !== "cursor" && key !== "wait_seconds")) return void 0;
|
|
10046
|
+
const cursor = url.searchParams.get("cursor") ?? "";
|
|
10047
|
+
const wait = url.searchParams.get("wait_seconds") ?? "25";
|
|
10048
|
+
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)) {
|
|
10049
|
+
return void 0;
|
|
10050
|
+
}
|
|
10051
|
+
return { cursor, waitSeconds: Number(wait) };
|
|
10052
|
+
}
|
|
9750
10053
|
function delegatedIdentity(c, delegated) {
|
|
9751
10054
|
if (!delegatedOriginAllowed(c)) return void 0;
|
|
9752
10055
|
return delegated.authorizeSession(c.req.header("authorization"));
|
|
@@ -9769,7 +10072,7 @@ function delegatedErrorResponse(c, code, status) {
|
|
|
9769
10072
|
}
|
|
9770
10073
|
function delegatedFailure(c, error) {
|
|
9771
10074
|
if (error instanceof DelegatedBrokerKitError) {
|
|
9772
|
-
const status = error.code === "request_not_found" ? 404 : error.code === "revision_stale" || error.code === "action_not_allowed" ? 409 : 502;
|
|
10075
|
+
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
10076
|
return delegatedErrorResponse(c, error.code, status);
|
|
9774
10077
|
}
|
|
9775
10078
|
if (error instanceof BrokerOperatorError) {
|
|
@@ -10066,7 +10369,7 @@ function contentType(file) {
|
|
|
10066
10369
|
}
|
|
10067
10370
|
|
|
10068
10371
|
// src/mlclaw-space-runtime/mcp-credentials.ts
|
|
10069
|
-
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, hkdfSync as hkdfSync2, randomBytes as
|
|
10372
|
+
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, hkdfSync as hkdfSync2, randomBytes as randomBytes7 } from "node:crypto";
|
|
10070
10373
|
import fs4 from "node:fs/promises";
|
|
10071
10374
|
import path4 from "node:path";
|
|
10072
10375
|
var DEFAULT_REFRESH_TIMEOUT_MS = 3e4;
|
|
@@ -10199,7 +10502,7 @@ var McpCredentialStore = class {
|
|
|
10199
10502
|
async persist() {
|
|
10200
10503
|
const directory = path4.dirname(this.options.file);
|
|
10201
10504
|
await fs4.mkdir(directory, { recursive: true, mode: 448 });
|
|
10202
|
-
const temporary = `${this.options.file}.${process.pid}.${
|
|
10505
|
+
const temporary = `${this.options.file}.${process.pid}.${randomBytes7(6).toString("hex")}.tmp`;
|
|
10203
10506
|
const encrypted = encryptDocument(this.document, this.key);
|
|
10204
10507
|
try {
|
|
10205
10508
|
await fs4.writeFile(temporary, `${JSON.stringify(encrypted)}
|
|
@@ -10277,7 +10580,7 @@ var InvalidCredentialFileError = class extends Error {
|
|
|
10277
10580
|
}
|
|
10278
10581
|
};
|
|
10279
10582
|
function encryptDocument(document, key) {
|
|
10280
|
-
const iv =
|
|
10583
|
+
const iv = randomBytes7(12);
|
|
10281
10584
|
const cipher = createCipheriv2("aes-256-gcm", key, iv);
|
|
10282
10585
|
const plaintext = Buffer.from(JSON.stringify(document), "utf8");
|
|
10283
10586
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
@@ -10766,6 +11069,8 @@ var OPENCLAW_ENV_ALLOWLIST = [
|
|
|
10766
11069
|
"OPENCLAW_LIVE_DIR",
|
|
10767
11070
|
"OPENCLAW_STATE_DIR",
|
|
10768
11071
|
"OPENCLAW_WORKSPACE_DIR",
|
|
11072
|
+
"MLCLAW_HF_BROKER_URL",
|
|
11073
|
+
"MLCLAW_HF_BROKER_AGENT_SECRET_FILE",
|
|
10769
11074
|
"TELEGRAM_API_ROOT",
|
|
10770
11075
|
"TELEGRAM_ALLOWED_USERS",
|
|
10771
11076
|
"TELEGRAM_BOT_TOKEN",
|
package/dist/mlclaw.mjs
CHANGED
|
@@ -14838,16 +14838,13 @@ import path12 from "node:path";
|
|
|
14838
14838
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
14839
14839
|
var DEFAULT_OPENCLAW_VERSION = "2026.7.1-beta.5";
|
|
14840
14840
|
var DEFAULT_BROKERKIT_PLUGIN_VERSION = "0.1.0";
|
|
14841
|
-
var DEFAULT_BROKERKIT_VERSION = "
|
|
14841
|
+
var DEFAULT_BROKERKIT_VERSION = "a2adf6e626bbd509605bb7c72a7f3a8b1c00f045";
|
|
14842
14842
|
var DEFAULT_RUNTIME_IMAGE_REPOSITORY = "ghcr.io/osolmaz/mlclaw";
|
|
14843
14843
|
var PACKAGE_METADATA = readPackageMetadata();
|
|
14844
14844
|
var PACKAGE_VERSION = packageString("version", "unknown");
|
|
14845
14845
|
var OPENCLAW_VERSION = packageConfigString("openclawVersion", DEFAULT_OPENCLAW_VERSION);
|
|
14846
14846
|
var OPENCLAW_BASE_IMAGE = `ghcr.io/openclaw/openclaw:${OPENCLAW_VERSION}`;
|
|
14847
|
-
var BROKERKIT_PLUGIN_VERSION = packageConfigString(
|
|
14848
|
-
"brokerkitPluginVersion",
|
|
14849
|
-
DEFAULT_BROKERKIT_PLUGIN_VERSION
|
|
14850
|
-
);
|
|
14847
|
+
var BROKERKIT_PLUGIN_VERSION = packageConfigString("brokerkitPluginVersion", DEFAULT_BROKERKIT_PLUGIN_VERSION);
|
|
14851
14848
|
var BROKERKIT_VERSION = packageConfigString("brokerkitVersion", DEFAULT_BROKERKIT_VERSION);
|
|
14852
14849
|
var RUNTIME_IMAGE_REPOSITORY = packageConfigString("runtimeImageRepository", DEFAULT_RUNTIME_IMAGE_REPOSITORY);
|
|
14853
14850
|
var DEFAULT_RUNTIME_IMAGE_TAG = `${PACKAGE_VERSION}-openclaw-${OPENCLAW_VERSION}`;
|
|
@@ -14955,7 +14952,6 @@ async function generateSpaceRepo(sourceDir, outDir, options = {}) {
|
|
|
14955
14952
|
["dist/hf-tooling-seed.js", "runtime/hf-tooling-seed.js"],
|
|
14956
14953
|
["dist/mlclaw-space-runtime.js", "runtime/mlclaw-space-runtime.js"],
|
|
14957
14954
|
["entrypoint.sh", "runtime/entrypoint.sh"],
|
|
14958
|
-
["hf-broker.scope.json", "runtime/hf-broker.scope.json"],
|
|
14959
14955
|
["openclaw.default.json", "runtime/openclaw.default.json"],
|
|
14960
14956
|
["scripts/configure-huggingface-model.mjs", "runtime/scripts/configure-huggingface-model.mjs"],
|
|
14961
14957
|
["scripts/configure-telegram.mjs", "runtime/scripts/configure-telegram.mjs"],
|
|
@@ -14965,6 +14961,9 @@ async function generateSpaceRepo(sourceDir, outDir, options = {}) {
|
|
|
14965
14961
|
for (const [from, to] of copies) {
|
|
14966
14962
|
await copyExisting(path13.join(sourceDir, from), path13.join(outDir, to));
|
|
14967
14963
|
}
|
|
14964
|
+
const hfLogoPng = await fs12.readFile(path13.join(sourceDir, "assets/hf-logo.png"));
|
|
14965
|
+
await fs12.writeFile(path13.join(outDir, "assets/hf-logo.png.base64"), `${hfLogoPng.toString("base64")}
|
|
14966
|
+
`, "utf8");
|
|
14968
14967
|
await fs12.writeFile(
|
|
14969
14968
|
path13.join(outDir, "Dockerfile"),
|
|
14970
14969
|
options.runtimeImage ? imageDockerfile(options.runtimeImage) : bundledDockerfile(),
|
|
@@ -14986,7 +14985,17 @@ RUN git init /src \\
|
|
|
14986
14985
|
&& git -C /src checkout --detach FETCH_HEAD \\
|
|
14987
14986
|
&& test "$(git -C /src rev-parse HEAD)" = "$BROKERKIT_VERSION" \\
|
|
14988
14987
|
&& cd /src \\
|
|
14989
|
-
&& GOWORK=off go build -trimpath -o /out/hf-broker ./brokers/huggingface/cmd/hf-broker
|
|
14988
|
+
&& GOWORK=off go build -trimpath -o /out/hf-broker ./brokers/huggingface/cmd/hf-broker \\
|
|
14989
|
+
&& /out/hf-broker policy render \\
|
|
14990
|
+
--preset request-all-agent-operations \\
|
|
14991
|
+
--client default \\
|
|
14992
|
+
--profile-out /out/hf-broker.policy-profile.json \\
|
|
14993
|
+
--output /out/hf-broker.scope.json \\
|
|
14994
|
+
--manifest-out /out/hf-broker.policy-manifest.json \\
|
|
14995
|
+
&& /out/hf-broker doctor policy \\
|
|
14996
|
+
--profile /out/hf-broker.policy-profile.json \\
|
|
14997
|
+
--scope /out/hf-broker.scope.json \\
|
|
14998
|
+
--manifest /out/hf-broker.policy-manifest.json
|
|
14990
14999
|
|
|
14991
15000
|
FROM node:24-bookworm-slim AS brokerkit-plugin-build
|
|
14992
15001
|
ARG BROKERKIT_VERSION
|
|
@@ -15024,12 +15033,14 @@ COPY --chown=node:node runtime/hf-state-sync.js /app/hf-state-sync.js
|
|
|
15024
15033
|
COPY --chown=node:node runtime/hf-tooling-seed.js /app/hf-tooling-seed.js
|
|
15025
15034
|
COPY --chown=node:node runtime/mlclaw-space-runtime.js /app/mlclaw-space-runtime.js
|
|
15026
15035
|
COPY --from=hf-broker-build /out/hf-broker /usr/local/bin/hf-broker
|
|
15027
|
-
COPY
|
|
15036
|
+
COPY --from=hf-broker-build /out/hf-broker.scope.json /app/hf-broker.scope.json
|
|
15037
|
+
COPY --from=hf-broker-build /out/hf-broker.policy-profile.json /app/hf-broker.policy-profile.json
|
|
15038
|
+
COPY --from=hf-broker-build /out/hf-broker.policy-manifest.json /app/hf-broker.policy-manifest.json
|
|
15028
15039
|
COPY --chown=node:node runtime/openclaw.default.json /app/openclaw.default.json
|
|
15029
15040
|
COPY --chown=node:node runtime/entrypoint.sh /app/entrypoint.sh
|
|
15030
15041
|
COPY --chown=node:node runtime/scripts/ /app/scripts/
|
|
15031
15042
|
COPY --chown=node:node assets/ /app/assets/
|
|
15032
|
-
RUN chmod +x /app/entrypoint.sh
|
|
15043
|
+
RUN base64 -d /app/assets/hf-logo.png.base64 > /app/assets/hf-logo.png && rm /app/assets/hf-logo.png.base64 && chown node:node /app/assets/hf-logo.png && chmod +x /app/entrypoint.sh
|
|
15033
15044
|
|
|
15034
15045
|
ENV PORT=7860
|
|
15035
15046
|
ENV MLCLAW_OPENCLAW_PORT=7861
|
package/entrypoint.sh
CHANGED
|
@@ -29,7 +29,9 @@ prepare_hf_broker() {
|
|
|
29
29
|
local operator_brokers_file="$HF_BROKER_RUN_DIR/operator-brokers.json"
|
|
30
30
|
local agent_secret operator_secret
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
# OpenClaw may traverse this directory only to its own 0600 credential file.
|
|
33
|
+
# It cannot list the directory or read any broker-owned credential.
|
|
34
|
+
install -d -m 0711 -o root -g hf-broker "$HF_BROKER_RUN_DIR"
|
|
33
35
|
agent_secret="$(od -An -N48 -tx1 /dev/urandom | tr -d ' \n')"
|
|
34
36
|
operator_secret="$(od -An -N48 -tx1 /dev/urandom | tr -d ' \n')"
|
|
35
37
|
printf '%s\n' "$broker_token" > "$token_file"
|
|
@@ -39,6 +41,7 @@ prepare_hf_broker() {
|
|
|
39
41
|
printf 'mlclaw-control = %s\n' "$operator_secret" > "$broker_operator_secrets"
|
|
40
42
|
printf '{"version":1,"brokers":[{"id":"hf-broker","label":"Hugging Face","url":"http://127.0.0.1:7864","token_file":"%s"}]}\n' "$operator_secret_file" > "$operator_brokers_file"
|
|
41
43
|
chown hf-broker:hf-broker "$token_file" "$broker_agent_secrets" "$broker_operator_secrets"
|
|
44
|
+
chown "$OPENCLAW_IDENTITY" "$agent_secret_file"
|
|
42
45
|
chmod 0600 "$token_file" "$agent_secret_file" "$operator_secret_file" "$broker_agent_secrets" "$broker_operator_secrets" "$operator_brokers_file"
|
|
43
46
|
|
|
44
47
|
if [ -z "${MLCLAW_STATE_MOUNT_DIR:-}" ]; then
|
|
@@ -65,6 +68,13 @@ restore_protected_state() {
|
|
|
65
68
|
fi
|
|
66
69
|
install -d -m 0700 -o root -g root "$PROTECTED_STATE_DIR/control"
|
|
67
70
|
install -d -m 0700 -o hf-broker -g hf-broker "$HF_BROKER_STATE_DIR"
|
|
71
|
+
rm -rf -- \
|
|
72
|
+
"$HF_BROKER_STATE_DIR/grants" \
|
|
73
|
+
"$HF_BROKER_STATE_DIR/operations" \
|
|
74
|
+
"$HF_BROKER_STATE_DIR/plans"
|
|
75
|
+
rm -f -- \
|
|
76
|
+
"$HF_BROKER_STATE_DIR/grants.json" \
|
|
77
|
+
"$HF_BROKER_STATE_DIR/operations.json"
|
|
68
78
|
chown -R root:root "$PROTECTED_STATE_DIR/control"
|
|
69
79
|
chown -R hf-broker:hf-broker "$HF_BROKER_STATE_DIR"
|
|
70
80
|
chmod 0710 "$PROTECTED_STATE_DIR"
|
|
@@ -77,12 +87,6 @@ start_hf_broker() {
|
|
|
77
87
|
fi
|
|
78
88
|
|
|
79
89
|
install -d -m 0700 -o hf-broker -g hf-broker "$HF_BROKER_STATE_DIR"
|
|
80
|
-
install -d -m 0700 -o hf-broker -g hf-broker "$HF_BROKER_STATE_DIR/grants"
|
|
81
|
-
if [ ! -e "$HF_BROKER_STATE_DIR/grants/grants.json" ]; then
|
|
82
|
-
printf '{"grants":[]}\n' > "$HF_BROKER_STATE_DIR/grants/grants.json"
|
|
83
|
-
chown hf-broker:hf-broker "$HF_BROKER_STATE_DIR/grants/grants.json"
|
|
84
|
-
chmod 0600 "$HF_BROKER_STATE_DIR/grants/grants.json"
|
|
85
|
-
fi
|
|
86
90
|
chown -R hf-broker:hf-broker "$HF_BROKER_STATE_DIR"
|
|
87
91
|
chmod 0700 "$HF_BROKER_STATE_DIR"
|
|
88
92
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mlclaw",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"config": {
|
|
6
6
|
"openclawVersion": "2026.7.1-beta.5",
|
|
7
|
-
"brokerkitVersion": "
|
|
7
|
+
"brokerkitVersion": "a2adf6e626bbd509605bb7c72a7f3a8b1c00f045",
|
|
8
8
|
"brokerkitPluginVersion": "0.1.0",
|
|
9
9
|
"runtimeImageRepository": "ghcr.io/osolmaz/mlclaw"
|
|
10
10
|
},
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
"dist/mlclaw-space-runtime.js",
|
|
31
31
|
"Dockerfile",
|
|
32
32
|
"entrypoint.sh",
|
|
33
|
-
"hf-broker.scope.json",
|
|
34
33
|
"mlclaw.ps1",
|
|
35
34
|
"mlclaw.sh",
|
|
36
35
|
"LICENSE",
|
|
@@ -56,9 +55,9 @@
|
|
|
56
55
|
"pack:check": "node scripts/check-package.mjs",
|
|
57
56
|
"prepack": "npm run build",
|
|
58
57
|
"check:secrets": "node scripts/check-secrets.mjs",
|
|
59
|
-
"format": "prettier --check eslint.config.mjs vitest.config.ts vitest.stryker.config.ts package.json README.md src/hf-state-sync/archive.ts src/hf-state-sync/cli.ts src/hf-state-sync/paths.ts src/hf-state-sync/prepare.ts src/hf-state-sync/stage-worker.ts src/hf-state-sync/supervise.ts src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts src/mlclaw-space-runtime/app.ts src/mlclaw-space-runtime/config.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/shell.ts src/mlclaw-control-ui/src/main.tsx src/mlclaw-control-ui/src/styles.css src/mlclaw-control-ui/src/components/ui/utils.ts src/mlclaw/git.ts test/hf-state-sync.archive.test.ts test/hf-state-sync.roundtrip.test.ts test/hf-state-sync.supervise.test.ts test/mlclaw.bundle.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts docs/operator-brokers-config.md",
|
|
60
|
-
"format:write": "prettier --write eslint.config.mjs vitest.config.ts vitest.stryker.config.ts package.json README.md src/hf-state-sync/archive.ts src/hf-state-sync/cli.ts src/hf-state-sync/paths.ts src/hf-state-sync/prepare.ts src/hf-state-sync/stage-worker.ts src/hf-state-sync/supervise.ts src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts src/mlclaw-space-runtime/app.ts src/mlclaw-space-runtime/config.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/shell.ts src/mlclaw-control-ui/src/main.tsx src/mlclaw-control-ui/src/styles.css src/mlclaw-control-ui/src/components/ui/utils.ts src/mlclaw/git.ts test/hf-state-sync.archive.test.ts test/hf-state-sync.roundtrip.test.ts test/hf-state-sync.supervise.test.ts test/mlclaw.bundle.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts docs/operator-brokers-config.md",
|
|
61
|
-
"lint": "eslint --max-warnings 0 src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts",
|
|
58
|
+
"format": "prettier --check eslint.config.mjs vitest.config.ts vitest.stryker.config.ts package.json README.md src/hf-state-sync/archive.ts src/hf-state-sync/cli.ts src/hf-state-sync/paths.ts src/hf-state-sync/prepare.ts src/hf-state-sync/stage-worker.ts src/hf-state-sync/supervise.ts src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/delegated-revisions.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts src/mlclaw-space-runtime/app.ts src/mlclaw-space-runtime/config.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/shell.ts src/mlclaw-control-ui/src/main.tsx src/mlclaw-control-ui/src/styles.css src/mlclaw-control-ui/src/components/ui/utils.ts src/mlclaw/git.ts test/hf-state-sync.archive.test.ts test/hf-state-sync.roundtrip.test.ts test/hf-state-sync.supervise.test.ts test/mlclaw.bundle.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts docs/operator-brokers-config.md",
|
|
59
|
+
"format:write": "prettier --write eslint.config.mjs vitest.config.ts vitest.stryker.config.ts package.json README.md src/hf-state-sync/archive.ts src/hf-state-sync/cli.ts src/hf-state-sync/paths.ts src/hf-state-sync/prepare.ts src/hf-state-sync/stage-worker.ts src/hf-state-sync/supervise.ts src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/delegated-revisions.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts src/mlclaw-space-runtime/app.ts src/mlclaw-space-runtime/config.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/shell.ts src/mlclaw-control-ui/src/main.tsx src/mlclaw-control-ui/src/styles.css src/mlclaw-control-ui/src/components/ui/utils.ts src/mlclaw/git.ts test/hf-state-sync.archive.test.ts test/hf-state-sync.roundtrip.test.ts test/hf-state-sync.supervise.test.ts test/mlclaw.bundle.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts docs/operator-brokers-config.md",
|
|
60
|
+
"lint": "eslint --max-warnings 0 src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/delegated-revisions.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts",
|
|
62
61
|
"coverage": "vitest run --coverage",
|
|
63
62
|
"dry": "slophammer-ts dry .",
|
|
64
63
|
"mutate": "stryker run --dryRunOnly",
|
package/hf-broker.scope.json
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"rules": [
|
|
3
|
-
{
|
|
4
|
-
"id": "mlclaw-read-repositories",
|
|
5
|
-
"effect": "allow",
|
|
6
|
-
"clients": ["default"],
|
|
7
|
-
"operations": ["repo.list", "repo.metadata.read", "repo.contents.read", "git.fetch"],
|
|
8
|
-
"targets": [
|
|
9
|
-
{"kind": "repo", "type": "*", "owner": "*", "name": "*"}
|
|
10
|
-
]
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"id": "mlclaw-append-repositories",
|
|
14
|
-
"effect": "allow",
|
|
15
|
-
"clients": ["default"],
|
|
16
|
-
"operations": ["git.push.append"],
|
|
17
|
-
"targets": [
|
|
18
|
-
{"kind": "repo", "type": "*", "owner": "*", "name": "*"}
|
|
19
|
-
]
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"id": "mlclaw-request-protected-repository-writes",
|
|
23
|
-
"effect": "request",
|
|
24
|
-
"clients": ["default"],
|
|
25
|
-
"operations": ["git.push.force", "git.ref.delete", "git.tag.update"],
|
|
26
|
-
"targets": [
|
|
27
|
-
{"kind": "repo", "type": "*", "owner": "*", "name": "*"}
|
|
28
|
-
],
|
|
29
|
-
"grant_policy": {
|
|
30
|
-
"default_minutes": 5,
|
|
31
|
-
"max_minutes": 60,
|
|
32
|
-
"request_ttl_minutes": 15,
|
|
33
|
-
"default_max_uses": 1,
|
|
34
|
-
"max_uses": 3
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
]
|
|
38
|
-
}
|