mlclaw 0.4.0 → 0.4.1

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.
@@ -59,8 +59,9 @@ Collect or confirm:
59
59
 
60
60
  - Hugging Face token access: `HF_TOKEN`, `HF_TOKEN_PATH`, `$HF_HOME/token`, or
61
61
  `hf auth login`.
62
- - The active Hugging Face token is installed as the broker-owned
63
- `MLCLAW_BROKER_HF_TOKEN`; legacy dedicated Router tokens remain supported.
62
+ - A separate fine-grained Hugging Face token is installed as the broker-owned
63
+ `MLCLAW_BROKER_HF_TOKEN`; the active CLI login is used only to provision
64
+ resources. Legacy dedicated Router tokens remain supported for inference.
64
65
  - Agent name, unless a Telegram bot token is supplied and the user wants the
65
66
  name derived from the bot username.
66
67
  - Gateway mode:
package/Dockerfile CHANGED
@@ -1,8 +1,8 @@
1
1
  ARG OPENCLAW_VERSION=2026.7.1
2
2
  ARG OPENCLAW_BASE_IMAGE=ghcr.io/openclaw/openclaw:${OPENCLAW_VERSION}
3
3
  ARG BROKERKIT_PLUGIN_VERSION=0.2.1
4
- ARG BROKERKIT_VERSION=hf-broker/v0.1.0
5
- ARG MLCLAW_RUNTIME_IMAGE=ghcr.io/osolmaz/mlclaw:0.4.0-openclaw-2026.7.1
4
+ ARG BROKERKIT_VERSION=hf-broker/v0.2.0
5
+ ARG MLCLAW_RUNTIME_IMAGE=ghcr.io/osolmaz/mlclaw:0.4.1-openclaw-2026.7.1
6
6
 
7
7
  FROM golang:1.26.5-bookworm AS hf-broker-build
8
8
  ARG BROKERKIT_VERSION
package/README.md CHANGED
@@ -47,12 +47,23 @@ flow, and resumes after sign-in. Non-interactive runs never install software and
47
47
  still require a token up front. You never paste that token into someone else's
48
48
  app; the bootstrapper runs locally.
49
49
 
50
- ML Claw places the active Hugging Face token behind an in-container HF Broker.
51
- The broker owns the real token; OpenClaw receives only a separate agent
52
- credential that can call the broker's typed, policy-checked routes. It cannot
53
- read the token or use the admin-only operator API. Existing dedicated inference
54
- tokens remain supported through `MLCLAW_ROUTER_TOKEN`, `HF_ROUTER_TOKEN`, or
55
- `--router-token-file` during migration.
50
+ The `hf` CLI login is a provisioning credential: ML Claw uses it to create and
51
+ configure resources owned by your account. HF Broker uses a separate, durable
52
+ fine-grained credential whose required permissions are versioned by BrokerKit.
53
+ Interactive bootstrap opens Hugging Face's token form with those fields
54
+ preselected, then accepts the new token through a hidden local prompt. Creating
55
+ or replacing this credential never changes the active `hf` CLI login. For
56
+ automation, pass a `0600` file through `--broker-hf-token-file`; ML Claw does
57
+ not accept the token as a command-line value.
58
+
59
+ The broker owns the selected credential; OpenClaw receives only a separate
60
+ agent credential that can call the broker's typed, policy-checked routes. It
61
+ cannot read the token or use the admin-only operator API. Rerunning bootstrap
62
+ reuses a healthy saved broker credential without reopening the form. A missing,
63
+ invalid, wrong-account, or under-scoped credential must be repaired before ML
64
+ Claw continues; it never silently substitutes the active CLI login. Existing
65
+ dedicated inference tokens remain supported through `MLCLAW_ROUTER_TOKEN`,
66
+ `HF_ROUTER_TOKEN`, or `--router-token-file` during migration.
56
67
 
57
68
  ## Default Flow
58
69
 
@@ -254,9 +265,25 @@ It never reads secret values and never modifies bucket contents.
254
265
 
255
266
  `mlclaw update` also refreshes the generated Space Dockerfile and runtime
256
267
  metadata, so older Spaces can move to the current implementation without
257
- recreating their bucket. For legacy deployments, ML Claw installs the active
258
- local Hugging Face credential as `MLCLAW_BROKER_HF_TOKEN`, removes stale direct
259
- token secrets, and restarts the Space with the broker boundary enabled.
268
+ recreating their bucket. It does not promote the active Hugging Face CLI login
269
+ into a runtime secret. Recover or repair the dedicated broker credential first
270
+ when updating a deployment that predates this boundary.
271
+
272
+ Inspect or replace the dedicated credential with:
273
+
274
+ ```bash
275
+ mlclaw credentials status mlclaw
276
+ mlclaw credentials repair mlclaw
277
+ mlclaw credentials repair mlclaw --broker-hf-token-file ~/secrets/mlclaw-broker.env
278
+ ```
279
+
280
+ `status` performs a read-only verification and prints the account, BrokerKit
281
+ profile, verification time, and a short SHA-256 fingerprint, never the token.
282
+ `repair` validates the replacement before changing anything. Local running
283
+ gateways are replaced transactionally and rolled back on failure; stopped local
284
+ gateways stay stopped. Space repair uses the active CLI login only to install
285
+ the already-validated secret and restart the Space, restoring the previous
286
+ secret and local metadata if the operation fails.
260
287
 
261
288
  ## Browser Settings
262
289
 
@@ -8584,6 +8584,7 @@ function remainingUpstreamTimeout(deadline) {
8584
8584
  // src/mlclaw-space-runtime/openclaw-config.ts
8585
8585
  var BROKER_MCP_CONNECTION_TIMEOUT_MS = 1e4;
8586
8586
  var BROKER_MCP_REQUEST_TIMEOUT_MS = 45e3;
8587
+ var AUTOMATIC_SESSION_RESET_DISABLED_MINUTES = 2147483647;
8587
8588
  async function configureOpenClawGateway(config2) {
8588
8589
  const raw2 = await fs.readFile(config2.openclawConfigPath, "utf8");
8589
8590
  const openclawConfig = JSON.parse(raw2);
@@ -8607,6 +8608,7 @@ async function configureOpenClawGateway(config2) {
8607
8608
  embedSandbox: "scripts"
8608
8609
  };
8609
8610
  configureOpenClawModels(openclawConfig, config2);
8611
+ disableAutomaticSessionResets(openclawConfig);
8610
8612
  configureManagedMcpServers(openclawConfig, config2);
8611
8613
  configureBrokerMcpServer(openclawConfig, config2);
8612
8614
  configureBrokerKitPlugin(openclawConfig, config2);
@@ -8618,6 +8620,18 @@ async function configureOpenClawGateway(config2) {
8618
8620
  await fs.chown(config2.openclawConfigPath, config2.openclawUid, config2.openclawGid);
8619
8621
  }
8620
8622
  }
8623
+ function disableAutomaticSessionResets(openclawConfig) {
8624
+ const session = object(openclawConfig, "session");
8625
+ session.reset = {
8626
+ mode: "idle",
8627
+ idleMinutes: AUTOMATIC_SESSION_RESET_DISABLED_MINUTES
8628
+ };
8629
+ delete session.idleMinutes;
8630
+ delete session.resetByType;
8631
+ delete session.resetByChannel;
8632
+ const maintenance = object(session, "maintenance");
8633
+ maintenance.resetArchiveRetention = false;
8634
+ }
8621
8635
  function configureBrokerMcpServer(openclawConfig, config2) {
8622
8636
  const servers = object(object(openclawConfig, "mcp"), "servers");
8623
8637
  if (!config2.brokerAgentUrl || !config2.brokerAgentSecretFile) {
package/dist/mlclaw.mjs CHANGED
@@ -8956,7 +8956,7 @@ import fs16 from "node:fs/promises";
8956
8956
  import { realpathSync } from "node:fs";
8957
8957
  import os8 from "node:os";
8958
8958
  import process4 from "node:process";
8959
- import { createHash as createHash3, randomBytes, randomUUID as randomUUID2 } from "node:crypto";
8959
+ import { createHash as createHash4, randomBytes, randomUUID as randomUUID2 } from "node:crypto";
8960
8960
  import { pathToFileURL as pathToFileURL2 } from "node:url";
8961
8961
  import { setTimeout as delay2 } from "node:timers/promises";
8962
8962
 
@@ -10056,10 +10056,10 @@ function parsePodmanConnections(raw) {
10056
10056
  if (!value || typeof value !== "object") {
10057
10057
  return [];
10058
10058
  }
10059
- const record = value;
10060
- const name = record.Name ?? record.name;
10061
- const uri = record.URI ?? record.uri;
10062
- const isDefault = record.Default ?? record.default;
10059
+ const record2 = value;
10060
+ const name = record2.Name ?? record2.name;
10061
+ const uri = record2.URI ?? record2.uri;
10062
+ const isDefault = record2.Default ?? record2.default;
10063
10063
  if (typeof name !== "string" || !name.trim()) {
10064
10064
  return [];
10065
10065
  }
@@ -14900,7 +14900,7 @@ var HubApi = class {
14900
14900
  return new BucketClient({ bucket, accessToken: this.token, hubUrl: this.hubUrl, fetch: this.fetchImpl });
14901
14901
  }
14902
14902
  async whoami() {
14903
- return await this.requestJson("/api/whoami-v2");
14903
+ return parseHubIdentity(await this.requestJson("/api/whoami-v2"));
14904
14904
  }
14905
14905
  async createBucket(bucketId, privateBucket = true) {
14906
14906
  const [namespace, name] = splitRepoId(bucketId);
@@ -15285,6 +15285,55 @@ var HubApi = class {
15285
15285
  return response;
15286
15286
  }
15287
15287
  };
15288
+ function parseHubIdentity(value) {
15289
+ const root = record(value);
15290
+ const name = stringValue(root.name);
15291
+ if (!name) {
15292
+ throw new Error("Hugging Face identity response omitted the account name");
15293
+ }
15294
+ const organizations = Array.isArray(root.orgs) ? root.orgs.map((entry) => stringValue(record(entry).name)).filter((entry) => Boolean(entry)) : [];
15295
+ const auth = record(root.auth);
15296
+ const accessToken = record(auth.accessToken);
15297
+ const fineGrained = record(accessToken.fineGrained);
15298
+ const authType = stringValue(auth.type);
15299
+ const accessTokenRole = stringValue(accessToken.role);
15300
+ const scoped = Array.isArray(fineGrained.scoped) ? fineGrained.scoped.map(parseFineGrainedScope).filter((entry) => Boolean(entry)) : [];
15301
+ const global2 = stringArray(fineGrained.global);
15302
+ const canReadGatedRepos = fineGrained.canReadGatedRepos === true;
15303
+ const parsedAccessToken = {
15304
+ ...accessTokenRole ? { role: accessTokenRole } : {},
15305
+ ...Object.keys(fineGrained).length > 0 ? { fineGrained: { global: global2, scoped, canReadGatedRepos } } : {}
15306
+ };
15307
+ const parsedAuth = {
15308
+ ...authType ? { type: authType } : {},
15309
+ ...Object.keys(parsedAccessToken).length > 0 ? { accessToken: parsedAccessToken } : {}
15310
+ };
15311
+ return {
15312
+ name,
15313
+ organizations: [...new Set(organizations)].sort(),
15314
+ ...Object.keys(parsedAuth).length > 0 ? { auth: parsedAuth } : {}
15315
+ };
15316
+ }
15317
+ function parseFineGrainedScope(value) {
15318
+ const scope = record(value);
15319
+ const entity = record(scope.entity);
15320
+ const type = stringValue(entity.type);
15321
+ if (!type) return void 0;
15322
+ const name = stringValue(entity.name);
15323
+ return {
15324
+ entity: { type, ...name ? { name } : {} },
15325
+ permissions: stringArray(scope.permissions)
15326
+ };
15327
+ }
15328
+ function record(value) {
15329
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
15330
+ }
15331
+ function stringValue(value) {
15332
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
15333
+ }
15334
+ function stringArray(value) {
15335
+ return Array.isArray(value) ? [...new Set(value.filter((entry) => typeof entry === "string" && Boolean(entry.trim())))].map((entry) => entry.trim()).sort() : [];
15336
+ }
15288
15337
  function splitRepoId(repoId) {
15289
15338
  const parts = repoId.split("/");
15290
15339
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
@@ -15327,7 +15376,7 @@ import path13 from "node:path";
15327
15376
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15328
15377
  var DEFAULT_OPENCLAW_VERSION = "2026.7.1";
15329
15378
  var DEFAULT_BROKERKIT_PLUGIN_VERSION = "0.2.1";
15330
- var DEFAULT_BROKERKIT_VERSION = "hf-broker/v0.1.0";
15379
+ var DEFAULT_BROKERKIT_VERSION = "hf-broker/v0.2.0";
15331
15380
  var DEFAULT_RUNTIME_IMAGE_REPOSITORY = "ghcr.io/osolmaz/mlclaw";
15332
15381
  var PACKAGE_METADATA = readPackageMetadata();
15333
15382
  var PACKAGE_VERSION = packageString("version", "unknown");
@@ -16178,15 +16227,15 @@ var makeIssue = (params) => {
16178
16227
  message: issueData.message
16179
16228
  };
16180
16229
  }
16181
- let errorMessage = "";
16230
+ let errorMessage2 = "";
16182
16231
  const maps = errorMaps.filter((m2) => !!m2).slice().reverse();
16183
16232
  for (const map of maps) {
16184
- errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message;
16233
+ errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
16185
16234
  }
16186
16235
  return {
16187
16236
  ...issueData,
16188
16237
  path: fullPath,
16189
- message: errorMessage
16238
+ message: errorMessage2
16190
16239
  };
16191
16240
  };
16192
16241
  var EMPTY_PATH = [];
@@ -19791,6 +19840,12 @@ var manifestFields = {
19791
19840
  gatewayLocation: external_exports.enum(["local", "space"]),
19792
19841
  model: external_exports.string().min(1).max(512),
19793
19842
  runtimeImage: external_exports.string().min(1).max(1024),
19843
+ brokerCredential: external_exports.object({
19844
+ profileId: external_exports.literal("hf-broker-complete-v1"),
19845
+ account: external_exports.string().min(1).max(128),
19846
+ fingerprintSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
19847
+ verifiedAt: external_exports.string().datetime()
19848
+ }).strict().optional(),
19794
19849
  credentialKeySha256: external_exports.string().regex(/^[a-f0-9]{64}$/).optional(),
19795
19850
  tailscaleMode: external_exports.enum(["off", "direct", "serve"]).optional(),
19796
19851
  spaceVisibility: external_exports.enum(["private", "public"]).optional(),
@@ -20279,6 +20334,165 @@ function delay(ms) {
20279
20334
  return new Promise((resolve) => setTimeout(resolve, ms));
20280
20335
  }
20281
20336
 
20337
+ // src/mlclaw/hf-broker-credential.ts
20338
+ import { createHash as createHash3 } from "node:crypto";
20339
+
20340
+ // src/mlclaw/hf-broker-credential-requirements.json
20341
+ var hf_broker_credential_requirements_default = {
20342
+ version: 1,
20343
+ profile_id: "hf-broker-complete-v1",
20344
+ token_form_url: "https://huggingface.co/settings/tokens/new",
20345
+ token_type: "fineGrained",
20346
+ requires_gated_repositories: true,
20347
+ personal_permissions: [
20348
+ "collection.read",
20349
+ "collection.write",
20350
+ "discussion.write",
20351
+ "inference.endpoints.infer.write",
20352
+ "inference.endpoints.write",
20353
+ "inference.serverless.write",
20354
+ "job.write",
20355
+ "repo.access.read",
20356
+ "repo.content.read",
20357
+ "repo.write",
20358
+ "resourceGroup.write",
20359
+ "sql-console.embed.write",
20360
+ "user.billing.read",
20361
+ "user.mcp.read",
20362
+ "user.notifications.read",
20363
+ "user.notifications.write",
20364
+ "user.papers.write",
20365
+ "user.preferences.write",
20366
+ "user.settings.notifications.write",
20367
+ "user.social.likes.write",
20368
+ "user.webhooks.read",
20369
+ "user.webhooks.write"
20370
+ ],
20371
+ global_permissions: [
20372
+ "discussion.write",
20373
+ "post.write"
20374
+ ],
20375
+ organization_permissions: [
20376
+ "collection.read",
20377
+ "collection.write",
20378
+ "discussion.write",
20379
+ "inference.endpoints.infer.write",
20380
+ "inference.endpoints.write",
20381
+ "inference.serverless.write",
20382
+ "job.write",
20383
+ "org.auditLog.write",
20384
+ "org.billing.read",
20385
+ "org.members.read",
20386
+ "org.members.write",
20387
+ "org.networkSecurity.read",
20388
+ "org.networkSecurity.write",
20389
+ "org.read",
20390
+ "org.repos.read",
20391
+ "org.serviceAccounts.read",
20392
+ "org.serviceAccounts.write",
20393
+ "org.write",
20394
+ "repo.access.read",
20395
+ "repo.content.read",
20396
+ "repo.write",
20397
+ "resourceGroup.write",
20398
+ "sql-console.embed.write"
20399
+ ]
20400
+ };
20401
+
20402
+ // src/mlclaw/hf-broker-credential.ts
20403
+ var permissionListSchema = external_exports.array(external_exports.string().min(1)).min(1).superRefine((permissions, context) => {
20404
+ if (permissions.some((permission) => permission !== permission.trim())) {
20405
+ context.addIssue({ code: external_exports.ZodIssueCode.custom, message: "permissions must not contain outer whitespace" });
20406
+ }
20407
+ if (new Set(permissions).size !== permissions.length) {
20408
+ context.addIssue({ code: external_exports.ZodIssueCode.custom, message: "permissions must be unique" });
20409
+ }
20410
+ if (permissions.some((permission, index) => index > 0 && permission < permissions[index - 1])) {
20411
+ context.addIssue({ code: external_exports.ZodIssueCode.custom, message: "permissions must be sorted" });
20412
+ }
20413
+ });
20414
+ var profileSchema = external_exports.object({
20415
+ version: external_exports.literal(1),
20416
+ profile_id: external_exports.literal("hf-broker-complete-v1"),
20417
+ token_form_url: external_exports.literal("https://huggingface.co/settings/tokens/new"),
20418
+ token_type: external_exports.literal("fineGrained"),
20419
+ requires_gated_repositories: external_exports.literal(true),
20420
+ personal_permissions: permissionListSchema,
20421
+ global_permissions: permissionListSchema,
20422
+ organization_permissions: permissionListSchema
20423
+ }).strict();
20424
+ var BROKER_CREDENTIAL_PROFILE = Object.freeze(profileSchema.parse(hf_broker_credential_requirements_default));
20425
+ var HF_TOKEN_CREATE_URL = BROKER_CREDENTIAL_PROFILE.token_form_url;
20426
+ var BROKER_PERSONAL_PERMISSIONS = BROKER_CREDENTIAL_PROFILE.personal_permissions;
20427
+ var BROKER_GLOBAL_PERMISSIONS = BROKER_CREDENTIAL_PROFILE.global_permissions;
20428
+ var BROKER_ORGANIZATION_PERMISSIONS = BROKER_CREDENTIAL_PROFILE.organization_permissions;
20429
+ function buildBrokerTokenUrl(owner, accountName) {
20430
+ const url = new URL(HF_TOKEN_CREATE_URL);
20431
+ url.searchParams.set("tokenType", BROKER_CREDENTIAL_PROFILE.token_type);
20432
+ for (const permission of BROKER_PERSONAL_PERMISSIONS) {
20433
+ url.searchParams.append("ownUserPermissions", permission);
20434
+ }
20435
+ for (const permission of BROKER_GLOBAL_PERMISSIONS) {
20436
+ url.searchParams.append("globalPermissions", permission);
20437
+ }
20438
+ url.searchParams.set("canReadGatedRepos", String(BROKER_CREDENTIAL_PROFILE.requires_gated_repositories));
20439
+ if (owner !== accountName) {
20440
+ url.searchParams.append("orgs", owner);
20441
+ for (const permission of BROKER_ORGANIZATION_PERMISSIONS) {
20442
+ url.searchParams.append("orgPermissions", permission);
20443
+ }
20444
+ }
20445
+ return url.toString();
20446
+ }
20447
+ function assessBrokerCredential(identity, owner) {
20448
+ const accessToken = identity.auth?.accessToken;
20449
+ if (accessToken?.role !== BROKER_CREDENTIAL_PROFILE.token_type) {
20450
+ return {
20451
+ status: "unsupported",
20452
+ reason: "HF Broker requires a dedicated fine-grained Hugging Face token"
20453
+ };
20454
+ }
20455
+ if (!accessToken.fineGrained) {
20456
+ return {
20457
+ status: "unsupported",
20458
+ reason: "Hugging Face omitted this fine-grained token's permission details"
20459
+ };
20460
+ }
20461
+ const personalAvailable = new Set(scopedPermissions(accessToken.fineGrained.scoped, "user", identity.name));
20462
+ const globalAvailable = new Set(accessToken.fineGrained.global);
20463
+ const missing = BROKER_PERSONAL_PERMISSIONS.filter((permission) => !personalAvailable.has(permission));
20464
+ missing.push(
20465
+ ...BROKER_GLOBAL_PERMISSIONS.filter((permission) => !globalAvailable.has(permission)).map(
20466
+ (permission) => `global:${permission}`
20467
+ )
20468
+ );
20469
+ if (!accessToken.fineGrained.canReadGatedRepos) {
20470
+ missing.push("canReadGatedRepos");
20471
+ }
20472
+ if (owner !== identity.name) {
20473
+ const organizationAvailable = new Set(scopedPermissions(accessToken.fineGrained.scoped, "org", owner));
20474
+ missing.push(
20475
+ ...BROKER_ORGANIZATION_PERMISSIONS.filter((permission) => !organizationAvailable.has(permission)).map(
20476
+ (permission) => `org:${permission}`
20477
+ )
20478
+ );
20479
+ }
20480
+ missing.sort();
20481
+ return missing.length === 0 ? { status: "sufficient" } : { status: "insufficient", missing };
20482
+ }
20483
+ function brokerCredentialMetadata(token, identity, verifiedAt) {
20484
+ return {
20485
+ profileId: BROKER_CREDENTIAL_PROFILE.profile_id,
20486
+ account: identity.name,
20487
+ fingerprintSha256: createHash3("sha256").update(token).digest("hex"),
20488
+ verifiedAt: verifiedAt.toISOString()
20489
+ };
20490
+ }
20491
+ function scopedPermissions(scopes, type, name) {
20492
+ if (!Array.isArray(scopes)) return [];
20493
+ return scopes.filter((scope) => scope.entity.type === type && (!scope.entity.name || scope.entity.name === name)).flatMap((scope) => scope.permissions);
20494
+ }
20495
+
20282
20496
  // src/mlclaw/tailscale.ts
20283
20497
  import { execFile as execFile3 } from "node:child_process";
20284
20498
  import { promisify as promisify3 } from "node:util";
@@ -20351,7 +20565,7 @@ function parseTailscaleStatus(value) {
20351
20565
  return { ready: false, reason: "Tailscale returned an invalid status" };
20352
20566
  }
20353
20567
  if (status.BackendState !== "Running") {
20354
- return { ready: false, reason: `Tailscale is ${stringValue(status.BackendState) ?? "not running"}` };
20568
+ return { ready: false, reason: `Tailscale is ${stringValue2(status.BackendState) ?? "not running"}` };
20355
20569
  }
20356
20570
  const self = recordValue(status.Self);
20357
20571
  if (self?.Online === false) {
@@ -20361,8 +20575,8 @@ function parseTailscaleStatus(value) {
20361
20575
  (candidate) => typeof candidate === "string" && isTailscaleIpv4(candidate)
20362
20576
  );
20363
20577
  if (typeof ipv4 !== "string") return { ready: false, reason: "Tailscale IPv4 address is unavailable" };
20364
- const dnsName = normalizeTailscaleDnsName(stringValue(self?.DNSName));
20365
- const tailnet = stringValue(recordValue(status.CurrentTailnet)?.Name);
20578
+ const dnsName = normalizeTailscaleDnsName(stringValue2(self?.DNSName));
20579
+ const tailnet = stringValue2(recordValue(status.CurrentTailnet)?.Name);
20366
20580
  return { ready: true, ipv4, ...dnsName ? { dnsName } : {}, ...tailnet ? { tailnet } : {} };
20367
20581
  }
20368
20582
  var TailscaleApprovalRequiredError = class extends Error {
@@ -20469,7 +20683,7 @@ function commandErrorMessage(error) {
20469
20683
  function recordValue(value) {
20470
20684
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
20471
20685
  }
20472
- function stringValue(value) {
20686
+ function stringValue2(value) {
20473
20687
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
20474
20688
  }
20475
20689
  function arrayValue(value) {
@@ -20541,7 +20755,7 @@ function createProgram(runtimeOverrides = {}) {
20541
20755
  program2.command("bootstrap", { isDefault: true }).alias("configure").description("Create or update a Hugging Face OpenClaw deployment").option("--owner <owner>", "Hugging Face user or organization").option("--name <name>", "Agent and runtime resource base name").option("--bucket <owner/bucket>", "State bucket to create or adopt").option("--gateway <local|space>", "Where the live gateway runs").option("--telegram-token <token>", "Optional Telegram bot token").option("--telegram-token-file <path>", "File containing TELEGRAM_BOT_TOKEN=... or a raw token").option("--telegram-user-id <id>", "Allowed Telegram user ID").option("--telegram-api-root <url>", "Telegram API root override").option("--telegram-proxy <url>", "Telegram proxy URL override").option("--hardware <flavor>", "Hugging Face Space hardware flavor").option("--sleep-time <seconds>", "Space sleep timeout in seconds; -1 means never sleep", parseInteger).option("--model <model>", "OpenClaw model identifier").option("--runtime-image <image>", "ML Claw runtime image").option("--bundled-runtime", "Generate a bundled Space runtime instead of using the prebuilt ML Claw image", false).option("--public-space", "Create the Hugging Face Space as public instead of private", false).addOption(new Option("--gateway-token <token>").hideHelp()).option("--router-token <token>", "Hugging Face Router inference token for Space gateway model calls").option(
20542
20756
  "--router-token-file <path>",
20543
20757
  "File containing MLCLAW_ROUTER_TOKEN=..., HF_ROUTER_TOKEN=..., or a raw token"
20544
- ).option("--docker-context <name>", "Docker context for local gateway mode").option("--container-runtime <auto|docker|podman>", "Local container runtime", "auto").option("--local-port <port>", "Loopback port for a local gateway", parseLocalPort).option("--tailscale <off|direct|serve>", "Tailnet access mode", parseTailscaleMode).option("--tailscale-port <port>", "Tailnet listener or Serve HTTPS port", parseLocalPort).option(
20758
+ ).option("--broker-hf-token-file <path>", "File containing MLCLAW_BROKER_HF_TOKEN=... or a raw Hugging Face token").option("--docker-context <name>", "Docker context for local gateway mode").option("--container-runtime <auto|docker|podman>", "Local container runtime", "auto").option("--local-port <port>", "Loopback port for a local gateway", parseLocalPort).option("--tailscale <off|direct|serve>", "Tailnet access mode", parseTailscaleMode).option("--tailscale-port <port>", "Tailnet listener or Serve HTTPS port", parseLocalPort).option(
20545
20759
  "--allow-local-fallback",
20546
20760
  "Allow non-interactive Space bootstrap to fall back to a ready local runtime",
20547
20761
  false
@@ -20566,6 +20780,13 @@ function createProgram(runtimeOverrides = {}) {
20566
20780
  const hub = runtime.hubFactory(token);
20567
20781
  await settings(repoId, opts, hub, runtime);
20568
20782
  });
20783
+ const credentials = program2.command("credentials").description("Inspect or replace trusted broker credentials");
20784
+ credentials.command("status").description("Verify the dedicated HF Broker credential").argument("[agent]", "Agent name; inferred when exactly one deployment exists").action(async (agent) => {
20785
+ await credentialsStatus(agent, runtime);
20786
+ });
20787
+ credentials.command("repair").description("Replace and apply the dedicated HF Broker credential").argument("[agent]", "Agent name; inferred when exactly one deployment exists").option("--broker-hf-token-file <path>", "File containing MLCLAW_BROKER_HF_TOKEN=... or a raw token").action(async (agent, opts) => {
20788
+ await credentialsRepair(agent, opts, runtime);
20789
+ });
20569
20790
  const gateway = program2.command("gateway").description("Operate a ML Claw gateway");
20570
20791
  gateway.command("start").argument("<agent>", "Agent name").option("--docker-context <name>", "Set Docker context only when the deployment has no pinned context").option("--local-port <port>", "Loopback port for the local gateway", parseLocalPort).option("--tailscale <off|direct|serve>", "Tailnet access mode", parseTailscaleMode).option("--tailscale-port <port>", "Tailnet listener or Serve HTTPS port", parseLocalPort).option("--no-pull", "Do not docker pull before starting a local gateway").option("--takeover", "Start even if another live runtime lease is present", false).action(async (agent, opts) => {
20571
20792
  await gatewayStart(agent, opts, runtime);
@@ -20777,20 +20998,24 @@ async function bootstrap(opts, runtime) {
20777
20998
  const runtimeImage = resolveRuntimeImage(opts.runtimeImage, runtime.env);
20778
20999
  resolveSpaceRuntimeImage(opts, runtime.env);
20779
21000
  let plan;
21001
+ let reviewedBrokerHfToken;
20780
21002
  for (; ; ) {
20781
21003
  plan = await resolveBootstrapPlan({
20782
21004
  opts,
20783
21005
  owner,
20784
21006
  agentName,
20785
21007
  hfToken,
21008
+ hfIdentity: me2,
20786
21009
  model,
20787
21010
  runtimeImage,
20788
21011
  hub,
20789
21012
  runtime,
21013
+ ...reviewedBrokerHfToken ? { providedBrokerHfToken: reviewedBrokerHfToken } : {},
20790
21014
  ...requestedGatewayLocation ? { requestedGatewayLocation } : {},
20791
21015
  ...telegramToken ? { telegramToken } : {},
20792
21016
  ...telegramUserId ? { telegramUserId } : {}
20793
21017
  });
21018
+ reviewedBrokerHfToken = plan.secrets.MLCLAW_BROKER_HF_TOKEN;
20794
21019
  const alternative = await promptAlternativeBootstrapName({
20795
21020
  plan,
20796
21021
  explicitBucket: opts.bucket,
@@ -20866,6 +21091,8 @@ async function bootstrap(opts, runtime) {
20866
21091
  owner,
20867
21092
  agentName,
20868
21093
  hfToken,
21094
+ hfIdentity: me2,
21095
+ brokerHfToken: activePlan.secrets.MLCLAW_BROKER_HF_TOKEN ?? hfToken,
20869
21096
  model,
20870
21097
  runtimeImage,
20871
21098
  hub,
@@ -20923,7 +21150,7 @@ async function bootstrap(opts, runtime) {
20923
21150
  const secretsChanged = JSON.stringify(previousSecrets) !== JSON.stringify(activePlan.secrets);
20924
21151
  if (secretsChanged) {
20925
21152
  await assertLease();
20926
- await setSpaceGatewaySecrets(hub, activePlan.manifest.space, hfToken, activePlan.secrets, assertLease);
21153
+ await setSpaceGatewaySecrets(hub, activePlan.manifest.space, activePlan.secrets, assertLease);
20927
21154
  if (canDeleteBroadTokenSecrets({
20928
21155
  model: activePlan.manifest.model,
20929
21156
  routerTokenPresent: hasBrokerOrRouterTokenSecretRecord(activePlan.secrets)
@@ -21081,7 +21308,7 @@ function assertBootstrapIdentityMatches(plan, identity) {
21081
21308
  }
21082
21309
  async function reconcileManifest(params) {
21083
21310
  const { hub, runtime } = params;
21084
- const localLockKey = createHash3("sha256").update(`${params.manifest.owner}\0${params.manifest.agent}`).digest("hex");
21311
+ const localLockKey = deploymentLockKey(params.manifest);
21085
21312
  return await withDeploymentLock(runtime.configRoot, localLockKey, async () => {
21086
21313
  let requestedManifest = params.manifest;
21087
21314
  const client = hub.bucket(requestedManifest.bucket);
@@ -21120,7 +21347,7 @@ async function reconcileManifest(params) {
21120
21347
  const secrets = await ensureDeploymentCredentialKey(runtime, requestedManifest.agent);
21121
21348
  requestedManifest = {
21122
21349
  ...requestedManifest,
21123
- credentialKeySha256: createHash3("sha256").update(requiredSecret(secrets, "MLCLAW_CREDENTIAL_KEY")).digest("hex")
21350
+ credentialKeySha256: createHash4("sha256").update(requiredSecret(secrets, "MLCLAW_CREDENTIAL_KEY")).digest("hex")
21124
21351
  };
21125
21352
  }
21126
21353
  const currentDesired = await readDesiredState(client);
@@ -21240,6 +21467,8 @@ async function resolveBootstrapPlan(params) {
21240
21467
  agentName,
21241
21468
  requestedGatewayLocation,
21242
21469
  hfToken,
21470
+ hfIdentity,
21471
+ providedBrokerHfToken,
21243
21472
  telegramToken,
21244
21473
  telegramUserId,
21245
21474
  model,
@@ -21251,6 +21480,14 @@ async function resolveBootstrapPlan(params) {
21251
21480
  const now = runtime.now().toISOString();
21252
21481
  const existingManifest = await readManifest(runtime.configRoot, agentName).catch(() => null);
21253
21482
  const existingSecrets = await readSecretEnv(runtime.configRoot, agentName).catch(() => ({}));
21483
+ const brokerCredential = await resolveBrokerHfToken({
21484
+ opts,
21485
+ owner,
21486
+ hfIdentity,
21487
+ ...providedBrokerHfToken ? { preferredToken: providedBrokerHfToken } : {},
21488
+ existingSecrets,
21489
+ runtime
21490
+ });
21254
21491
  const sessionSecret = existingSecrets.MLCLAW_SESSION_SECRET ?? randomBytes(48).toString("base64url");
21255
21492
  const restoredCredentialKey = existingSecrets.MLCLAW_CREDENTIAL_KEY ?? runtime.env.MLCLAW_CREDENTIAL_KEY;
21256
21493
  if (existingManifest?.recoveredWithoutCredentialKey && !restoredCredentialKey) {
@@ -21259,7 +21496,7 @@ async function resolveBootstrapPlan(params) {
21259
21496
  );
21260
21497
  }
21261
21498
  const credentialKey = restoredCredentialKey ?? randomBytes(32).toString("base64url");
21262
- const credentialKeySha256 = createHash3("sha256").update(credentialKey).digest("hex");
21499
+ const credentialKeySha256 = createHash4("sha256").update(credentialKey).digest("hex");
21263
21500
  if (existingManifest?.credentialKeySha256 && existingManifest.credentialKeySha256 !== credentialKeySha256) {
21264
21501
  throw new Error("MLCLAW_CREDENTIAL_KEY does not match the recovered deployment");
21265
21502
  }
@@ -21325,6 +21562,7 @@ async function resolveBootstrapPlan(params) {
21325
21562
  gatewayLocation,
21326
21563
  model: effectiveModel,
21327
21564
  runtimeImage: effectiveRuntimeImage,
21565
+ brokerCredential: brokerCredentialMetadata(brokerCredential.token, brokerCredential.identity, runtime.now()),
21328
21566
  credentialKeySha256,
21329
21567
  ...existingManifest?.tailscaleMode ? { tailscaleMode: existingManifest.tailscaleMode } : {},
21330
21568
  ...existingManifest?.spaceVisibility ? { spaceVisibility: existingManifest.spaceVisibility } : {},
@@ -21342,7 +21580,7 @@ async function resolveBootstrapPlan(params) {
21342
21580
  const effectiveTelegramProxy = opts.telegramProxy ?? existingSecrets.TELEGRAM_PROXY;
21343
21581
  const effectiveTelegramApiRoot = opts.telegramApiRoot ?? existingSecrets.TELEGRAM_API_ROOT;
21344
21582
  const secrets = deploymentSecrets({
21345
- hfToken,
21583
+ hfToken: brokerCredential.token,
21346
21584
  ...effectiveTelegramToken ? { telegramToken: effectiveTelegramToken } : {},
21347
21585
  ...effectiveTelegramUserId ? { telegramUserId: effectiveTelegramUserId } : {},
21348
21586
  sessionSecret,
@@ -21601,6 +21839,8 @@ async function resolveHostedBootstrapFallback(params) {
21601
21839
  agentName: params.agentName,
21602
21840
  requestedGatewayLocation: "local",
21603
21841
  hfToken: params.hfToken,
21842
+ hfIdentity: params.hfIdentity,
21843
+ providedBrokerHfToken: params.brokerHfToken,
21604
21844
  model: params.model,
21605
21845
  runtimeImage: params.runtimeImage,
21606
21846
  hub: params.hub,
@@ -21978,35 +22218,43 @@ async function deployLocalBootstrap(plan, opts, runtime, desiredChanged = true,
21978
22218
  }
21979
22219
  try {
21980
22220
  if (networkAccessChanged && plan.manifest.networkAccess) {
22221
+ await assertLease();
21981
22222
  await disableNetworkAccess(plan.manifest, runtime);
21982
22223
  }
21983
22224
  if (previousSecrets) {
22225
+ await assertLease();
21984
22226
  await writeSecretEnv(runtime.configRoot, plan.agentName, previousSecrets);
21985
22227
  } else {
22228
+ await assertLease();
21986
22229
  await fs16.rm(secretEnvPath(runtime.configRoot, plan.agentName), { force: true });
21987
22230
  }
21988
22231
  if (previousContainer?.running && previousManifest) {
21989
- await startLocalGateway({ manifest: previousManifest, runtime, pull: false, refresh: true });
22232
+ await startLocalGateway({ manifest: previousManifest, runtime, pull: false, refresh: true, assertLease });
21990
22233
  runtime.stdout.log(`Previous local gateway restored: ${containerNameFor(previousManifest.agent)}`);
21991
22234
  } else if (previousContainer && previousManifest && startupAttempted) {
21992
- await startLocalGateway({ manifest: previousManifest, runtime, pull: false, refresh: true });
22235
+ await startLocalGateway({ manifest: previousManifest, runtime, pull: false, refresh: true, assertLease });
22236
+ await assertLease();
21993
22237
  await localRunnerFor(previousManifest, runtime).stop(
21994
22238
  containerNameFor(previousManifest.agent),
21995
22239
  localConnectionFor(previousManifest)
21996
22240
  );
21997
22241
  if (previousNetworkState !== "owned") {
22242
+ await assertLease();
21998
22243
  await disableNetworkAccess(previousManifest, runtime);
21999
22244
  }
22000
22245
  runtime.stdout.log(`Previous stopped local gateway restored: ${containerNameFor(previousManifest.agent)}`);
22001
22246
  } else {
22002
22247
  if (startupAttempted) {
22248
+ await assertLease();
22003
22249
  await removeFailedBootstrapContainer(plan.manifest, runtime, !previousManifest);
22004
22250
  }
22005
22251
  }
22006
22252
  if (previousNetworkState === "owned" && previousManifest?.networkAccess) {
22253
+ await assertLease();
22007
22254
  await runtime.tailscaleRunner.ensureMapping(networkAccessMapping(previousManifest.networkAccess));
22008
22255
  }
22009
22256
  if (!previousManifest) {
22257
+ await assertLease();
22010
22258
  await fs16.rm(manifestPath(runtime.configRoot, plan.agentName), { force: true });
22011
22259
  }
22012
22260
  } catch (rollbackError) {
@@ -22112,7 +22360,7 @@ async function deploySpaceGateway(params) {
22112
22360
  await assertLease();
22113
22361
  await clearSpaceGatewayDisabled(hub, manifest.space);
22114
22362
  await assertLease();
22115
- await setSpaceGatewaySecrets(hub, manifest.space, hfToken, secrets, assertLease);
22363
+ await setSpaceGatewaySecrets(hub, manifest.space, secrets, assertLease);
22116
22364
  if (canDeleteBroadTokenSecrets({
22117
22365
  model: manifest.model,
22118
22366
  routerTokenPresent: hasBrokerOrRouterTokenSecretRecord(secrets)
@@ -23219,7 +23467,7 @@ async function restoreMatchingDeploymentCredentialKey(runtime, agent, expectedSh
23219
23467
  const candidates = [suppliedCredentialKey, runtime.env.MLCLAW_CREDENTIAL_KEY, secrets.MLCLAW_CREDENTIAL_KEY].filter(
23220
23468
  (value) => Boolean(value)
23221
23469
  );
23222
- const credentialKey = candidates.find((value) => createHash3("sha256").update(value).digest("hex") === expectedSha256);
23470
+ const credentialKey = candidates.find((value) => createHash4("sha256").update(value).digest("hex") === expectedSha256);
23223
23471
  if (!credentialKey) {
23224
23472
  throw new Error("local MLCLAW_CREDENTIAL_KEY is missing or does not match the canonical deployment identity");
23225
23473
  }
@@ -23245,7 +23493,7 @@ async function update(repoId, opts, hub, hfToken, runtime) {
23245
23493
  const runtimeImage = resolveSpaceRuntimeImage(opts, runtime.env);
23246
23494
  const agentName = variables.get("OPENCLAW_AGENT_NAME")?.value?.trim() || repoId.split("/")[1] || "openclaw";
23247
23495
  if (!canonicalTemplate) {
23248
- await ensureUpdateRouterToken({
23496
+ await ensureUpdateCredentials({
23249
23497
  repoId,
23250
23498
  agentName,
23251
23499
  model: variables.get("OPENCLAW_MODEL")?.value ?? DEFAULT_MODEL2,
@@ -23282,39 +23530,39 @@ async function update(repoId, opts, hub, hfToken, runtime) {
23282
23530
  await doctor(repoId, { fix: true }, hub, runtime);
23283
23531
  runtime.stdout.log(`Space deployment triggered: ${repoId}`);
23284
23532
  }
23285
- async function ensureUpdateRouterToken(params) {
23286
- if (!isHuggingFaceRouterModel(params.model)) {
23287
- return;
23288
- }
23289
- const spaceSecrets = await params.hub.getSpaceSecrets(params.repoId);
23533
+ async function ensureUpdateCredentials(params) {
23290
23534
  const hasExplicitOverride = params.opts.routerToken !== void 0 || params.opts.routerTokenFile !== void 0;
23291
- if (hasBrokerOrRouterTokenSecretMap(spaceSecrets) && !hasExplicitOverride) {
23292
- return;
23293
- }
23294
23535
  const hasManifest = await manifestExists(params.runtime.configRoot, params.agentName);
23295
- const localSecrets = hasManifest ? await readSecretEnv(params.runtime.configRoot, params.agentName).catch(() => ({})) : {};
23536
+ if (!hasManifest) {
23537
+ throw new Error(
23538
+ `cannot verify the dedicated HF Broker credential; recover the deployment and run \`mlclaw credentials repair ${params.agentName}\``
23539
+ );
23540
+ }
23541
+ const localManifest = await readManifest(params.runtime.configRoot, params.agentName);
23542
+ if (localManifest.space !== params.repoId) {
23543
+ throw new Error(
23544
+ `local deployment ${params.agentName} belongs to ${localManifest.space}, not ${params.repoId}; specify the matching deployment before updating`
23545
+ );
23546
+ }
23547
+ const localSecrets = await readSecretEnv(params.runtime.configRoot, params.agentName).catch(
23548
+ () => ({})
23549
+ );
23550
+ const brokerCredential = await verifiedStoredBrokerCredential(localManifest, params.runtime);
23296
23551
  const routerToken = hasExplicitOverride ? await resolveRouterToken({
23297
23552
  opts: params.opts,
23298
23553
  runtime: params.runtime,
23299
23554
  existingSecrets: localSecrets,
23300
23555
  model: params.model
23301
23556
  }) : void 0;
23302
- const brokerToken = routerToken ? void 0 : await params.runtime.readToken(params.runtime.env);
23303
- const credential = routerToken ?? brokerToken;
23304
- if (!credential) {
23305
- throw new Error("Hugging Face broker credential is unavailable");
23306
- }
23307
- await params.hub.addSpaceSecret(
23308
- params.repoId,
23309
- routerToken ? "MLCLAW_ROUTER_TOKEN" : "MLCLAW_BROKER_HF_TOKEN",
23310
- credential
23311
- );
23312
- if (hasManifest) {
23313
- await writeSecretEnv(params.runtime.configRoot, params.agentName, {
23314
- ...localSecrets,
23315
- ...routerToken ? { MLCLAW_ROUTER_TOKEN: routerToken } : { MLCLAW_BROKER_HF_TOKEN: brokerToken }
23316
- });
23317
- }
23557
+ await params.hub.addSpaceSecret(params.repoId, "MLCLAW_BROKER_HF_TOKEN", brokerCredential.token);
23558
+ if (routerToken) {
23559
+ await params.hub.addSpaceSecret(params.repoId, "MLCLAW_ROUTER_TOKEN", routerToken);
23560
+ }
23561
+ await writeSecretEnv(params.runtime.configRoot, params.agentName, {
23562
+ ...localSecrets,
23563
+ MLCLAW_BROKER_HF_TOKEN: brokerCredential.token,
23564
+ ...routerToken ? { MLCLAW_ROUTER_TOKEN: routerToken } : {}
23565
+ });
23318
23566
  }
23319
23567
  async function doctor(repoId, opts, hub, runtime) {
23320
23568
  if (!repoId.includes("/") && await manifestExists(runtime.configRoot, repoId)) {
@@ -23409,14 +23657,20 @@ async function doctor(repoId, opts, hub, runtime) {
23409
23657
  }
23410
23658
  }
23411
23659
  }
23412
- if (!secrets.has("MLCLAW_BROKER_HF_TOKEN")) {
23413
- if (fix) {
23414
- await hub.addSpaceSecret(repoId, "MLCLAW_BROKER_HF_TOKEN", await runtime.readToken(runtime.env));
23415
- secrets.set("MLCLAW_BROKER_HF_TOKEN", { key: "MLCLAW_BROKER_HF_TOKEN" });
23416
- fixed.push("set secret MLCLAW_BROKER_HF_TOKEN");
23417
- } else {
23418
- issues.push("secret MLCLAW_BROKER_HF_TOKEN is missing");
23660
+ if (fix) {
23661
+ const manifests = (await listManifests(runtime.configRoot)).filter((manifest) => manifest.space === repoId);
23662
+ if (manifests.length !== 1) {
23663
+ throw new Error(
23664
+ `cannot verify MLCLAW_BROKER_HF_TOKEN without one matching local deployment; recover it with \`mlclaw bootstrap --name ${repoId.split("/")[1] ?? "agent"}\``
23665
+ );
23419
23666
  }
23667
+ const credential = await verifiedStoredBrokerCredential(manifests[0], runtime);
23668
+ const existed = secrets.has("MLCLAW_BROKER_HF_TOKEN");
23669
+ await hub.addSpaceSecret(repoId, "MLCLAW_BROKER_HF_TOKEN", credential.token);
23670
+ secrets.set("MLCLAW_BROKER_HF_TOKEN", { key: "MLCLAW_BROKER_HF_TOKEN" });
23671
+ fixed.push(`${existed ? "refreshed verified" : "set"} secret MLCLAW_BROKER_HF_TOKEN`);
23672
+ } else if (!secrets.has("MLCLAW_BROKER_HF_TOKEN")) {
23673
+ issues.push("secret MLCLAW_BROKER_HF_TOKEN is missing");
23420
23674
  }
23421
23675
  const staleTokenSecrets = ["HF_TOKEN", "HUGGINGFACE_HUB_TOKEN"].filter((key) => secrets.has(key));
23422
23676
  if (staleTokenSecrets.length > 0) {
@@ -23634,14 +23888,14 @@ async function setDeploymentSecrets(hub, repoId, secrets, assertMutation = async
23634
23888
  await hub.addSpaceSecret(repoId, key, value);
23635
23889
  }
23636
23890
  }
23637
- async function setSpaceGatewaySecrets(hub, repoId, hfToken, secrets, assertMutation = async () => void 0) {
23891
+ async function setSpaceGatewaySecrets(hub, repoId, secrets, assertMutation = async () => void 0) {
23638
23892
  await setDeploymentSecrets(
23639
23893
  hub,
23640
23894
  repoId,
23641
23895
  {
23642
23896
  MLCLAW_SESSION_SECRET: requiredSecret(secrets, "MLCLAW_SESSION_SECRET"),
23643
23897
  MLCLAW_CREDENTIAL_KEY: requiredSecret(secrets, "MLCLAW_CREDENTIAL_KEY"),
23644
- MLCLAW_BROKER_HF_TOKEN: hfToken,
23898
+ MLCLAW_BROKER_HF_TOKEN: requiredSecret(secrets, "MLCLAW_BROKER_HF_TOKEN"),
23645
23899
  ...secrets.MLCLAW_ROUTER_TOKEN ? { MLCLAW_ROUTER_TOKEN: secrets.MLCLAW_ROUTER_TOKEN } : {},
23646
23900
  ...secrets.TELEGRAM_BOT_TOKEN ? { TELEGRAM_BOT_TOKEN: secrets.TELEGRAM_BOT_TOKEN } : {},
23647
23901
  ...secrets.TELEGRAM_ALLOWED_USERS ? { TELEGRAM_ALLOWED_USERS: secrets.TELEGRAM_ALLOWED_USERS } : {},
@@ -23768,6 +24022,309 @@ async function readOptionalRouterTokenFile(file) {
23768
24022
  const parsed = parseSecretEnv(raw);
23769
24023
  return nonEmpty(parsed.MLCLAW_ROUTER_TOKEN) ?? nonEmpty(parsed.HF_ROUTER_TOKEN) ?? nonEmpty(raw);
23770
24024
  }
24025
+ async function credentialsStatus(requestedAgent, runtime) {
24026
+ const manifest = await credentialManifest(requestedAgent, runtime);
24027
+ await verifiedStoredBrokerCredential(manifest, runtime);
24028
+ const metadata = manifest.brokerCredential;
24029
+ if (!metadata) throw new Error("verified HF Broker credential metadata is missing");
24030
+ runtime.stdout.log(`Agent: ${manifest.agent}`);
24031
+ runtime.stdout.log(`Status: healthy`);
24032
+ runtime.stdout.log(`Profile: ${metadata.profileId}`);
24033
+ runtime.stdout.log(`Account: ${metadata.account}`);
24034
+ runtime.stdout.log(`Fingerprint: ${metadata.fingerprintSha256.slice(0, 12)}`);
24035
+ runtime.stdout.log(`Verified: ${metadata.verifiedAt}`);
24036
+ }
24037
+ async function verifiedStoredBrokerCredential(manifest, runtime) {
24038
+ const secrets = await readSecretEnv(runtime.configRoot, manifest.agent);
24039
+ const token = nonEmpty(secrets.MLCLAW_BROKER_HF_TOKEN);
24040
+ if (!token) throw new Error(`deployment ${manifest.agent} has no dedicated HF Broker credential`);
24041
+ if (!manifest.brokerCredential) {
24042
+ throw new Error(
24043
+ `HF Broker credential metadata is missing; run \`mlclaw credentials repair ${manifest.agent}\` to complete the cutover`
24044
+ );
24045
+ }
24046
+ const verified = await verifyBrokerHfToken(token, manifest.owner, manifest.brokerCredential.account, runtime);
24047
+ const observed = brokerCredentialMetadata(token, verified.identity, runtime.now());
24048
+ if (observed.fingerprintSha256 !== manifest.brokerCredential.fingerprintSha256) {
24049
+ throw new Error(
24050
+ `HF Broker credential fingerprint changed; run \`mlclaw credentials repair ${manifest.agent}\` to reconcile it`
24051
+ );
24052
+ }
24053
+ return verified;
24054
+ }
24055
+ async function credentialsRepair(requestedAgent, opts, runtime) {
24056
+ const selectedManifest = await credentialManifest(requestedAgent, runtime);
24057
+ await withDeploymentLock(runtime.configRoot, deploymentLockKey(selectedManifest), async () => {
24058
+ const manifest = await readManifest(runtime.configRoot, selectedManifest.agent);
24059
+ const secrets = await readSecretEnv(runtime.configRoot, manifest.agent).catch(() => ({}));
24060
+ const oldToken = nonEmpty(secrets.MLCLAW_BROKER_HF_TOKEN);
24061
+ const account = await credentialRepairAccount(manifest, oldToken, runtime);
24062
+ const fileToken = await readOptionalBrokerHfTokenFile(opts.brokerHfTokenFile);
24063
+ const suppliedToken = fileToken ?? nonEmpty(runtime.env.MLCLAW_BROKER_HF_TOKEN);
24064
+ let replacement;
24065
+ if (suppliedToken) {
24066
+ replacement = await verifyBrokerHfToken(suppliedToken, manifest.owner, account, runtime);
24067
+ } else {
24068
+ if (!runtime.prompt.isInteractive()) {
24069
+ throw new Error(
24070
+ "credential repair requires --broker-hf-token-file, MLCLAW_BROKER_HF_TOKEN, or an interactive terminal"
24071
+ );
24072
+ }
24073
+ replacement = await promptForBrokerHfToken(manifest.owner, account, runtime);
24074
+ }
24075
+ const updatedManifest = {
24076
+ ...manifest,
24077
+ brokerCredential: brokerCredentialMetadata(replacement.token, replacement.identity, runtime.now()),
24078
+ updatedAt: runtime.now().toISOString()
24079
+ };
24080
+ const updatedSecrets = { ...secrets, MLCLAW_BROKER_HF_TOKEN: replacement.token };
24081
+ const coordinationToken = manifest.gatewayLocation === "space" ? await runtime.readToken(runtime.env) : replacement.token;
24082
+ const coordinationHub = runtime.hubFactory(coordinationToken);
24083
+ const control = await coordinationHub.deploymentControlStore(manifest.owner, manifest.deploymentId);
24084
+ const operation = newOperation(manifest, runtime.now());
24085
+ let lease = await acquireControlLease(control, manifest, operation, runtime.now());
24086
+ let renewalError;
24087
+ let renewal = Promise.resolve();
24088
+ const renewalTimer = setInterval(() => {
24089
+ renewal = renewal.then(async () => {
24090
+ if (renewalError) return;
24091
+ try {
24092
+ lease = await renewControlLease(control, lease, runtime.now());
24093
+ } catch (error) {
24094
+ renewalError = error;
24095
+ }
24096
+ });
24097
+ }, 45e3);
24098
+ const assertLease = async () => {
24099
+ await renewal;
24100
+ if (renewalError) throw renewalError;
24101
+ await assertControlLease(control, lease, runtime.now());
24102
+ if (renewalError) throw renewalError;
24103
+ };
24104
+ try {
24105
+ await assertLease();
24106
+ if (manifest.gatewayLocation === "local") {
24107
+ await applyLocalCredentialRepair(manifest, updatedManifest, secrets, updatedSecrets, runtime, assertLease);
24108
+ } else {
24109
+ await applySpaceCredentialRepair(
24110
+ manifest,
24111
+ updatedManifest,
24112
+ secrets,
24113
+ updatedSecrets,
24114
+ oldToken,
24115
+ coordinationHub,
24116
+ runtime,
24117
+ assertLease
24118
+ );
24119
+ }
24120
+ await assertLease();
24121
+ } finally {
24122
+ clearInterval(renewalTimer);
24123
+ await renewal;
24124
+ await releaseControlLease(control, lease);
24125
+ }
24126
+ runtime.stdout.log(`HF Broker credential repaired: ${manifest.agent}`);
24127
+ });
24128
+ }
24129
+ async function credentialManifest(requestedAgent, runtime) {
24130
+ if (requestedAgent) return await readManifest(runtime.configRoot, requestedAgent);
24131
+ const manifests = await listManifests(runtime.configRoot);
24132
+ if (manifests.length === 1) return manifests[0];
24133
+ if (manifests.length === 0) throw new Error("no ML Claw deployment found");
24134
+ throw new Error("multiple ML Claw deployments found; specify an agent name");
24135
+ }
24136
+ async function credentialRepairAccount(manifest, oldToken, runtime) {
24137
+ if (manifest.brokerCredential) return manifest.brokerCredential.account;
24138
+ if (oldToken) {
24139
+ try {
24140
+ return (await runtime.hubFactory(oldToken).whoami()).name;
24141
+ } catch {
24142
+ }
24143
+ }
24144
+ const provisioningToken = await runtime.readToken(runtime.env);
24145
+ return (await runtime.hubFactory(provisioningToken).whoami()).name;
24146
+ }
24147
+ async function applyLocalCredentialRepair(previousManifest, updatedManifest, previousSecrets, updatedSecrets, runtime, assertLease) {
24148
+ const runner = localRunnerFor(previousManifest, runtime);
24149
+ const existing = await runner.inspect(containerNameFor(previousManifest.agent), localConnectionFor(previousManifest));
24150
+ if (!existing?.running) {
24151
+ await replaceLocalCredentialFiles(
24152
+ previousManifest,
24153
+ updatedManifest,
24154
+ previousSecrets,
24155
+ updatedSecrets,
24156
+ runtime,
24157
+ assertLease
24158
+ );
24159
+ return;
24160
+ }
24161
+ const bucketPrefix = await readDeploymentBucketPrefix(runtime, previousManifest.agent);
24162
+ await deployLocalBootstrap(
24163
+ {
24164
+ agentName: previousManifest.agent,
24165
+ names: namesFor(previousManifest.owner, previousManifest.agent),
24166
+ hasExistingManifest: true,
24167
+ previousManifest,
24168
+ gatewayLocation: "local",
24169
+ ...bucketPrefix ? { bucketPrefix } : {},
24170
+ bucketPlan: { bucket: previousManifest.bucket, exists: true, objectCount: 0 },
24171
+ bucket: previousManifest.bucket,
24172
+ manifest: updatedManifest,
24173
+ secrets: updatedSecrets
24174
+ },
24175
+ { pull: false },
24176
+ runtime,
24177
+ true,
24178
+ assertLease
24179
+ );
24180
+ }
24181
+ async function replaceLocalCredentialFiles(previousManifest, updatedManifest, previousSecrets, updatedSecrets, runtime, assertLease) {
24182
+ try {
24183
+ await assertLease();
24184
+ await writeSecretEnv(runtime.configRoot, updatedManifest.agent, updatedSecrets);
24185
+ await assertLease();
24186
+ await writeManifest(runtime.configRoot, updatedManifest);
24187
+ } catch (error) {
24188
+ await assertLease();
24189
+ await writeSecretEnv(runtime.configRoot, previousManifest.agent, previousSecrets);
24190
+ await assertLease();
24191
+ await writeManifest(runtime.configRoot, previousManifest);
24192
+ throw error;
24193
+ }
24194
+ }
24195
+ async function applySpaceCredentialRepair(previousManifest, updatedManifest, previousSecrets, updatedSecrets, oldToken, hub, runtime, assertLease) {
24196
+ try {
24197
+ await assertLease();
24198
+ await hub.addSpaceSecret(
24199
+ previousManifest.space,
24200
+ "MLCLAW_BROKER_HF_TOKEN",
24201
+ requiredSecret(updatedSecrets, "MLCLAW_BROKER_HF_TOKEN")
24202
+ );
24203
+ await assertLease();
24204
+ await hub.restartSpace(previousManifest.space, true);
24205
+ await replaceLocalCredentialFiles(
24206
+ previousManifest,
24207
+ updatedManifest,
24208
+ previousSecrets,
24209
+ updatedSecrets,
24210
+ runtime,
24211
+ assertLease
24212
+ );
24213
+ } catch (error) {
24214
+ try {
24215
+ await assertLease();
24216
+ if (oldToken) {
24217
+ await hub.addSpaceSecret(previousManifest.space, "MLCLAW_BROKER_HF_TOKEN", oldToken);
24218
+ } else {
24219
+ await hub.deleteSpaceSecret(previousManifest.space, "MLCLAW_BROKER_HF_TOKEN");
24220
+ }
24221
+ await assertLease();
24222
+ await hub.restartSpace(previousManifest.space, true);
24223
+ await assertLease();
24224
+ await writeSecretEnv(runtime.configRoot, previousManifest.agent, previousSecrets);
24225
+ await assertLease();
24226
+ await writeManifest(runtime.configRoot, previousManifest);
24227
+ } catch (rollbackError) {
24228
+ throw new Error(
24229
+ `credential repair failed (${errorMessage(error)}) and rollback failed (${errorMessage(rollbackError)})`
24230
+ );
24231
+ }
24232
+ throw error;
24233
+ }
24234
+ }
24235
+ function deploymentLockKey(manifest) {
24236
+ return createHash4("sha256").update(`${manifest.owner}\0${manifest.agent}`).digest("hex");
24237
+ }
24238
+ async function resolveBrokerHfToken(params) {
24239
+ const fileToken = await readOptionalBrokerHfTokenFile(params.opts.brokerHfTokenFile);
24240
+ const environmentToken = nonEmpty(params.runtime.env.MLCLAW_BROKER_HF_TOKEN);
24241
+ const configuredToken = fileToken ?? environmentToken ?? nonEmpty(params.preferredToken) ?? nonEmpty(params.existingSecrets.MLCLAW_BROKER_HF_TOKEN);
24242
+ if (configuredToken) {
24243
+ try {
24244
+ return await verifyBrokerHfToken(configuredToken, params.owner, params.hfIdentity.name, params.runtime);
24245
+ } catch (error) {
24246
+ if (fileToken || environmentToken || params.preferredToken) throw error;
24247
+ if (params.runtime.prompt.isInteractive()) {
24248
+ params.runtime.prompt.note(
24249
+ `The saved HF Broker credential could not be used (${errorMessage(error)}). It must be replaced before ML Claw can continue.`,
24250
+ "HF Broker credential"
24251
+ );
24252
+ } else {
24253
+ throw new Error(
24254
+ `the saved HF Broker credential is unusable (${errorMessage(error)}); run \`mlclaw credentials repair\` interactively`
24255
+ );
24256
+ }
24257
+ }
24258
+ }
24259
+ if (!params.runtime.prompt.isInteractive()) {
24260
+ throw new Error(
24261
+ "a dedicated HF Broker credential is required; set MLCLAW_BROKER_HF_TOKEN, pass --broker-hf-token-file, or run bootstrap interactively"
24262
+ );
24263
+ }
24264
+ return await promptForBrokerHfToken(params.owner, params.hfIdentity.name, params.runtime);
24265
+ }
24266
+ async function promptForBrokerHfToken(owner, account, runtime) {
24267
+ runtime.prompt.note(
24268
+ "ML Claw will open a Hugging Face token form with BrokerKit's permissions preselected. Create a dedicated token and paste it here. Your current HF CLI login will not be changed.",
24269
+ "HF Broker credential"
24270
+ );
24271
+ const url = buildBrokerTokenUrl(owner, account);
24272
+ const opened = await runtime.hfCli.openUrl(url);
24273
+ runtime.prompt.note(
24274
+ `${opened ? "The token form was opened in your browser." : "Open this token form in your browser."}
24275
+
24276
+ Name and create the token, then copy it. The URL contains permission names only; it contains no credential. The exact URL is printed below.`,
24277
+ "Create the broker token"
24278
+ );
24279
+ runtime.stdout.log(url);
24280
+ for (; ; ) {
24281
+ const replacement = readPromptValue(
24282
+ await runtime.prompt.password({ message: "Paste the new Hugging Face broker token" }),
24283
+ "Hugging Face broker token"
24284
+ );
24285
+ try {
24286
+ const verified = await verifyBrokerHfToken(replacement, owner, account, runtime);
24287
+ runtime.prompt.note(
24288
+ "The dedicated broker token was verified. It will be stored only in ML Claw's trusted broker configuration.",
24289
+ "HF Broker credential ready"
24290
+ );
24291
+ return verified;
24292
+ } catch (error) {
24293
+ runtime.prompt.note(errorMessage(error), "Broker token was not accepted");
24294
+ if (!await promptConfirm("Try another broker token?", true, runtime)) {
24295
+ throw new Error("a valid dedicated HF Broker credential is required");
24296
+ }
24297
+ }
24298
+ }
24299
+ }
24300
+ async function verifyBrokerHfToken(token, owner, expectedAccount, runtime) {
24301
+ const identity = await runtime.hubFactory(token).whoami();
24302
+ if (identity.name !== expectedAccount) {
24303
+ throw new Error(`broker token belongs to ${identity.name}, not ${expectedAccount}`);
24304
+ }
24305
+ const assessment = assessBrokerCredential(identity, owner);
24306
+ if (assessment.status !== "sufficient") {
24307
+ throw new Error(brokerCredentialAssessmentDetail(assessment));
24308
+ }
24309
+ return { token, identity };
24310
+ }
24311
+ async function readOptionalBrokerHfTokenFile(file) {
24312
+ if (!file) return void 0;
24313
+ const raw = await fs16.readFile(file, "utf8");
24314
+ const parsed = parseSecretEnv(raw);
24315
+ const token = nonEmpty(parsed.MLCLAW_BROKER_HF_TOKEN) ?? nonEmpty(raw);
24316
+ if (!token) throw new Error("HF Broker token file is empty");
24317
+ return token;
24318
+ }
24319
+ function brokerCredentialAssessmentDetail(assessment) {
24320
+ if (assessment.status === "unsupported") return assessment.reason;
24321
+ const shown = assessment.missing.slice(0, 8);
24322
+ const remaining = assessment.missing.length - shown.length;
24323
+ return `The HF Broker credential is missing ${assessment.missing.length} required permission${assessment.missing.length === 1 ? "" : "s"}: ${shown.join(", ")}${remaining > 0 ? `, and ${remaining} more` : ""}`;
24324
+ }
24325
+ function errorMessage(error) {
24326
+ return error instanceof Error ? error.message : String(error);
24327
+ }
23771
24328
  function isHuggingFaceRouterModel(model) {
23772
24329
  return model.trim().startsWith("huggingface/");
23773
24330
  }
package/entrypoint.sh CHANGED
@@ -15,7 +15,7 @@ PROTECTED_STATE_DIR="/var/lib/mlclaw-protected"
15
15
  HF_BROKER_STATE_DIR="$PROTECTED_STATE_DIR/hf-broker"
16
16
 
17
17
  prepare_hf_broker() {
18
- local broker_token="${MLCLAW_BROKER_HF_TOKEN:-${HF_TOKEN:-${HUGGINGFACE_HUB_TOKEN:-${MLCLAW_ROUTER_TOKEN:-${HF_ROUTER_TOKEN:-}}}}}"
18
+ local broker_token="${MLCLAW_BROKER_HF_TOKEN:-}"
19
19
  if [ -z "$broker_token" ]; then
20
20
  echo "[hf-broker] MLCLAW_BROKER_HF_TOKEN is not configured; broker disabled"
21
21
  return
@@ -45,7 +45,7 @@ prepare_hf_broker() {
45
45
  chmod 0600 "$token_file" "$agent_secret_file" "$operator_secret_file" "$broker_agent_secrets" "$broker_operator_secrets" "$operator_brokers_file"
46
46
 
47
47
  if [ -z "${MLCLAW_STATE_MOUNT_DIR:-}" ]; then
48
- STATE_HF_TOKEN="${MLCLAW_BROKER_HF_TOKEN:-${HF_TOKEN:-${HUGGINGFACE_HUB_TOKEN:-$broker_token}}}"
48
+ STATE_HF_TOKEN="$broker_token"
49
49
  fi
50
50
 
51
51
  export MLCLAW_HF_BROKER_URL="http://127.0.0.1:7863"
@@ -130,8 +130,8 @@ fi
130
130
  prepare_hf_broker
131
131
  export MLCLAW_PROTECTED_STATE_DIR="$PROTECTED_STATE_DIR"
132
132
  export MLCLAW_OPENAI_CREDENTIAL_STORE_FILE="$PROTECTED_STATE_DIR/control/openai-api-key.enc"
133
- # The broad token and legacy Router token must not enter the control plane or
134
- # OpenClaw. The token is already in the broker-owned runtime file before the
133
+ # The broker token and legacy token variables must not enter the control plane
134
+ # or OpenClaw. The broker token is already in its owned runtime file before the
135
135
  # environment is scrubbed; local bucket state sync receives a dedicated copy
136
136
  # only around trusted restore and supervisor execution.
137
137
  unset MLCLAW_BROKER_HF_TOKEN MLCLAW_ROUTER_TOKEN HF_ROUTER_TOKEN HF_TOKEN HUGGINGFACE_HUB_TOKEN
@@ -57,9 +57,11 @@
57
57
  "session": {
58
58
  "dmScope": "per-channel-peer",
59
59
  "reset": {
60
- "mode": "daily",
61
- "atHour": 4,
62
- "idleMinutes": 1440
60
+ "mode": "idle",
61
+ "idleMinutes": 2147483647
62
+ },
63
+ "maintenance": {
64
+ "resetArchiveRetention": false
63
65
  }
64
66
  },
65
67
  "cron": {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "mlclaw",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "license": "MIT",
5
5
  "config": {
6
6
  "openclawVersion": "2026.7.1",
7
- "brokerkitVersion": "hf-broker/v0.1.0",
7
+ "brokerkitVersion": "hf-broker/v0.2.0",
8
8
  "brokerkitPluginVersion": "0.2.1",
9
9
  "runtimeImageRepository": "ghcr.io/osolmaz/mlclaw"
10
10
  },
@@ -55,8 +55,8 @@
55
55
  "pack:check": "node scripts/check-package.mjs",
56
56
  "prepack": "npm run build",
57
57
  "check:secrets": "node scripts/check-secrets.mjs",
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/auth.ts src/mlclaw/cli.ts src/mlclaw/deployment-state.ts src/mlclaw/docker.ts src/mlclaw/hf-cli.ts src/mlclaw/hub-api.ts src/mlclaw/local-config.ts src/mlclaw/tailscale.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/local-access.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/proxy.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/session.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.auth.test.ts test/mlclaw.brokerkit-agent-tools.test.ts test/mlclaw.bundle.test.ts test/mlclaw.cli.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.deployment-state.test.ts test/mlclaw.docker.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.hub-api.test.ts test/mlclaw.local-config.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts test/mlclaw.tailscale.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/auth.ts src/mlclaw/cli.ts src/mlclaw/deployment-state.ts src/mlclaw/docker.ts src/mlclaw/hf-cli.ts src/mlclaw/hub-api.ts src/mlclaw/local-config.ts src/mlclaw/tailscale.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/local-access.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/proxy.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/session.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.auth.test.ts test/mlclaw.brokerkit-agent-tools.test.ts test/mlclaw.bundle.test.ts test/mlclaw.cli.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.deployment-state.test.ts test/mlclaw.docker.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.hub-api.test.ts test/mlclaw.local-config.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts test/mlclaw.tailscale.test.ts docs/operator-brokers-config.md",
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/auth.ts src/mlclaw/cli.ts src/mlclaw/deployment-state.ts src/mlclaw/docker.ts src/mlclaw/hf-broker-credential.ts src/mlclaw/hf-cli.ts src/mlclaw/hub-api.ts src/mlclaw/local-config.ts src/mlclaw/tailscale.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/local-access.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/proxy.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/session.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.auth.test.ts test/mlclaw.brokerkit-agent-tools.test.ts test/mlclaw.bundle.test.ts test/mlclaw.cli.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.deployment-state.test.ts test/mlclaw.docker.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.hf-broker-credential.test.ts test/mlclaw.hub-api.test.ts test/mlclaw.local-config.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts test/mlclaw.tailscale.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/auth.ts src/mlclaw/cli.ts src/mlclaw/deployment-state.ts src/mlclaw/docker.ts src/mlclaw/hf-broker-credential.ts src/mlclaw/hf-cli.ts src/mlclaw/hub-api.ts src/mlclaw/local-config.ts src/mlclaw/tailscale.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/local-access.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/proxy.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/session.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.auth.test.ts test/mlclaw.brokerkit-agent-tools.test.ts test/mlclaw.bundle.test.ts test/mlclaw.cli.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.deployment-state.test.ts test/mlclaw.docker.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.hf-broker-credential.test.ts test/mlclaw.hub-api.test.ts test/mlclaw.local-config.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts test/mlclaw.tailscale.test.ts docs/operator-brokers-config.md",
60
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",
61
61
  "coverage": "vitest run --coverage",
62
62
  "dry": "slophammer-ts dry .",
package/tsconfig.json CHANGED
@@ -3,6 +3,7 @@
3
3
  "target": "ES2023",
4
4
  "module": "NodeNext",
5
5
  "moduleResolution": "NodeNext",
6
+ "resolveJsonModule": true,
6
7
  "lib": ["ES2023", "DOM"],
7
8
  "types": ["node"],
8
9
  "jsx": "react-jsx",