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 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=fcfc5f2304436d952b18e7c9583bb378d1952776
5
- ARG MLCLAW_RUNTIME_IMAGE=ghcr.io/osolmaz/mlclaw:0.3.1-openclaw-2026.7.1-beta.5
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 launcher inside the Gateway. Decisions open in an unframeable protected
213
- document with a renewable, short-lived, admin-bound browser token. Broker
214
- operator tokens remain in backend-only files and are never sent to the browser
215
- or OpenClaw. See
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.svg
244
+ MLCLAW_BRAND_FAVICON_32=hf-logo.png
243
245
  MLCLAW_BRAND_FAVICON_ICO=hf-logo.svg
244
- MLCLAW_BRAND_APPLE_TOUCH_ICON=assistant-avatar.svg
246
+ MLCLAW_BRAND_APPLE_TOUCH_ICON=hf-logo.png
245
247
  MLCLAW_BRAND_ASSISTANT_AVATAR=assistant-avatar.svg
246
248
  ```
247
249
 
Binary file
@@ -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 DEFAULT_ASSISTANT_AVATAR_ASSET = "assistant-avatar.svg";
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
- DEFAULT_HUGGING_FACE_ASSET
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
- name: branding.name,
67
- short_name: branding.shortName,
68
- description: `${branding.name} browser gateway`,
69
- start_url: "./",
70
- display: "standalone",
71
- theme_color: branding.themeColor,
72
- background_color: branding.themeColor,
73
- icons: [
74
- {
75
- src: "./favicon.svg",
76
- sizes: "any",
77
- type: "image/svg+xml",
78
- purpose: "any"
79
- },
80
- {
81
- src: "./favicon-32.png",
82
- sizes: "32x32",
83
- type: "image/png"
84
- },
85
- {
86
- src: "./apple-touch-icon.png",
87
- sizes: "180x180",
88
- type: "image/png"
89
- }
90
- ]
91
- }, null, 2)}
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 randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
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
- issueSession(actor) {
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: randomBytes3(16).toString("base64url")
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
- actor,
7396
- decision_token: `${encoded}.${signature}`,
7397
- expires_at: new Date(expiresAt * 1e3).toISOString()
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
- sources: results.map((result) => result.source),
7429
- requests: selected.map(
7430
- ({ source, request }) => project(source, request, this.handle(source.id, request, reservedHandles))
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, lastSyncAt: synchronizedAt },
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 = randomBytes3(18).toString("base64url");
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 { ...request, sourceId: source.id, sourceLabel: source.label, handle };
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 randomBytes4 } from "node:crypto";
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 = randomBytes4(12);
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}.${randomBytes4(6).toString("hex")}.tmp`;
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 randomBytes5, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
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 randomBytes5(24).toString("base64url");
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 brokerKitFrameIn(root, source) {
9156
- if (!root.querySelectorAll) return;
9157
- var frames = root.querySelectorAll("iframe");
9158
- for (var i = 0; i < frames.length; i++) {
9159
- try {
9160
- var frameUrl = new URL(frames[i].src, location.href);
9161
- if (frames[i].contentWindow === source && frameUrl.origin === location.origin &&
9162
- frameUrl.pathname === "/plugins/brokerkit/ui/" && !frameUrl.search) return frames[i];
9163
- } catch (_) {}
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
- var elements = root.querySelectorAll("*");
9166
- for (var j = 0; j < elements.length; j++) {
9167
- if (elements[j].shadowRoot) {
9168
- var nested = brokerKitFrameIn(elements[j].shadowRoot, source);
9169
- if (nested) return nested;
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;">&times;</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", "reason", "constraints"].includes(key))) {
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.reason !== void 0 && (typeof body.reason !== "string" || body.reason.length > 2e3) || 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)) {
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 marker = destination === "iframe" ? '<meta name="brokerkit-delegated-top-level">' : `<meta name="brokerkit-delegated-session" content="${Buffer.from(
9676
- JSON.stringify(delegatedBrokerKit.issueSession(auth.username)),
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(destination === "iframe" ? "launcher" : "top-level");
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": `sandbox allow-scripts; default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; frame-ancestors ${mode === "top-level" ? "'none'" : "'self'"}`,
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 randomBytes6 } from "node:crypto";
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}.${randomBytes6(6).toString("hex")}.tmp`;
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 = randomBytes6(12);
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 = "fcfc5f2304436d952b18e7c9583bb378d1952776";
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 runtime/hf-broker.scope.json /app/hf-broker.scope.json
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
- install -d -m 0750 -o root -g hf-broker "$HF_BROKER_RUN_DIR"
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.1",
3
+ "version": "0.3.2",
4
4
  "license": "MIT",
5
5
  "config": {
6
6
  "openclawVersion": "2026.7.1-beta.5",
7
- "brokerkitVersion": "fcfc5f2304436d952b18e7c9583bb378d1952776",
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",
@@ -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
- }