mlclaw 0.2.3 → 0.3.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.
package/dist/mlclaw.mjs CHANGED
@@ -14629,6 +14629,20 @@ var HubApi = class {
14629
14629
  const raw = await this.requestJson(`/api/spaces/${repoId}/secrets`);
14630
14630
  return new Map(Object.entries(raw));
14631
14631
  }
14632
+ async deleteSpaceSecret(repoId, key) {
14633
+ try {
14634
+ await this.requestJson(`/api/spaces/${repoId}/secrets`, {
14635
+ method: "DELETE",
14636
+ body: JSON.stringify({ key }),
14637
+ headers: { "Content-Type": "application/json" }
14638
+ });
14639
+ } catch (err) {
14640
+ if (err instanceof HubApiError2 && err.status === 404) {
14641
+ return;
14642
+ }
14643
+ throw err;
14644
+ }
14645
+ }
14632
14646
  async restartSpace(repoId, factoryReboot = false) {
14633
14647
  await this.requestJson(`/api/spaces/${repoId}/restart`, {
14634
14648
  method: "POST",
@@ -14660,7 +14674,25 @@ var HubApi = class {
14660
14674
  });
14661
14675
  }
14662
14676
  async getSpaceRuntime(repoId) {
14663
- return await this.requestJson(`/api/spaces/${repoId}/runtime`);
14677
+ const runtime = await this.requestJson(`/api/spaces/${repoId}/runtime`);
14678
+ if (Array.isArray(runtime.volumes)) {
14679
+ return runtime;
14680
+ }
14681
+ try {
14682
+ const info = await this.requestJson(`/api/spaces/${repoId}`);
14683
+ if (Array.isArray(info.runtime?.volumes)) {
14684
+ return { ...runtime, volumes: info.runtime.volumes };
14685
+ }
14686
+ } catch {
14687
+ }
14688
+ return runtime;
14689
+ }
14690
+ async setSpaceVolumes(repoId, volumes) {
14691
+ await this.requestJson(`/api/spaces/${repoId}/volumes`, {
14692
+ method: "PUT",
14693
+ body: JSON.stringify({ volumes }),
14694
+ headers: { "Content-Type": "application/json" }
14695
+ });
14664
14696
  }
14665
14697
  async fetchSpaceLogs(repoId, kind = "run") {
14666
14698
  const url = `${this.hubUrl}/api/spaces/${repoId}/logs/${kind}`;
@@ -14804,12 +14836,19 @@ function sseDataToText(raw) {
14804
14836
  import fs11 from "node:fs";
14805
14837
  import path12 from "node:path";
14806
14838
  import { fileURLToPath as fileURLToPath2 } from "node:url";
14807
- var DEFAULT_OPENCLAW_VERSION = "2026.7.1-beta.2";
14839
+ var DEFAULT_OPENCLAW_VERSION = "2026.7.1-beta.5";
14840
+ var DEFAULT_BROKERKIT_PLUGIN_VERSION = "0.1.0";
14841
+ var DEFAULT_BROKERKIT_VERSION = "fcfc5f2304436d952b18e7c9583bb378d1952776";
14808
14842
  var DEFAULT_RUNTIME_IMAGE_REPOSITORY = "ghcr.io/osolmaz/mlclaw";
14809
14843
  var PACKAGE_METADATA = readPackageMetadata();
14810
14844
  var PACKAGE_VERSION = packageString("version", "unknown");
14811
14845
  var OPENCLAW_VERSION = packageConfigString("openclawVersion", DEFAULT_OPENCLAW_VERSION);
14812
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
+ );
14851
+ var BROKERKIT_VERSION = packageConfigString("brokerkitVersion", DEFAULT_BROKERKIT_VERSION);
14813
14852
  var RUNTIME_IMAGE_REPOSITORY = packageConfigString("runtimeImageRepository", DEFAULT_RUNTIME_IMAGE_REPOSITORY);
14814
14853
  var DEFAULT_RUNTIME_IMAGE_TAG = `${PACKAGE_VERSION}-openclaw-${OPENCLAW_VERSION}`;
14815
14854
  var DEFAULT_RUNTIME_IMAGE = `${RUNTIME_IMAGE_REPOSITORY}:${DEFAULT_RUNTIME_IMAGE_TAG}`;
@@ -14916,6 +14955,7 @@ async function generateSpaceRepo(sourceDir, outDir, options = {}) {
14916
14955
  ["dist/hf-tooling-seed.js", "runtime/hf-tooling-seed.js"],
14917
14956
  ["dist/mlclaw-space-runtime.js", "runtime/mlclaw-space-runtime.js"],
14918
14957
  ["entrypoint.sh", "runtime/entrypoint.sh"],
14958
+ ["hf-broker.scope.json", "runtime/hf-broker.scope.json"],
14919
14959
  ["openclaw.default.json", "runtime/openclaw.default.json"],
14920
14960
  ["scripts/configure-huggingface-model.mjs", "runtime/scripts/configure-huggingface-model.mjs"],
14921
14961
  ["scripts/configure-telegram.mjs", "runtime/scripts/configure-telegram.mjs"],
@@ -14936,14 +14976,33 @@ function imageDockerfile(runtimeImage) {
14936
14976
  `;
14937
14977
  }
14938
14978
  function bundledDockerfile() {
14939
- return `FROM ${OPENCLAW_BASE_IMAGE}
14979
+ return `ARG BROKERKIT_PLUGIN_VERSION=${BROKERKIT_PLUGIN_VERSION}
14980
+ ARG BROKERKIT_VERSION=${BROKERKIT_VERSION}
14981
+
14982
+ FROM golang:1.26.5-bookworm AS hf-broker-build
14983
+ ARG BROKERKIT_VERSION
14984
+ RUN git init /src \\
14985
+ && git -C /src fetch --depth=1 https://github.com/osolmaz/brokerkit.git "$BROKERKIT_VERSION" \\
14986
+ && git -C /src checkout --detach FETCH_HEAD \\
14987
+ && test "$(git -C /src rev-parse HEAD)" = "$BROKERKIT_VERSION" \\
14988
+ && cd /src \\
14989
+ && GOWORK=off go build -trimpath -o /out/hf-broker ./brokers/huggingface/cmd/hf-broker
14990
+
14991
+ FROM node:24-bookworm-slim AS brokerkit-plugin-build
14992
+ ARG BROKERKIT_VERSION
14993
+ RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates git && rm -rf /var/lib/apt/lists/* && git init /src && git -C /src fetch --depth=1 https://github.com/osolmaz/brokerkit.git "$BROKERKIT_VERSION" && git -C /src checkout --detach FETCH_HEAD && test "$(git -C /src rev-parse HEAD)" = "$BROKERKIT_VERSION"
14994
+ WORKDIR /src
14995
+ RUN corepack enable && pnpm install --frozen-lockfile && pnpm --filter openclaw-brokerkit build && pnpm --filter openclaw-brokerkit pack --pack-destination /out
14996
+
14997
+ FROM ${OPENCLAW_BASE_IMAGE}
14940
14998
 
14941
14999
  LABEL org.opencontainers.image.source="https://github.com/osolmaz/mlclaw"
14942
15000
  LABEL org.opencontainers.image.description="ML Claw runtime for OpenClaw on Hugging Face"
14943
15001
 
14944
15002
  USER root
14945
15003
  RUN apt-get update \\
14946
- && apt-get install -y --no-install-recommends gosu python3 python3-pip python3-venv zstd \\
15004
+ && apt-get install -y --no-install-recommends ca-certificates gosu python3 python3-pip python3-venv zstd \\
15005
+ && useradd --system --home-dir /var/lib/hf-broker --create-home --shell /usr/sbin/nologin hf-broker \\
14947
15006
  && rm -rf /var/lib/apt/lists/*
14948
15007
  RUN python3 -m pip install --break-system-packages --no-cache-dir \\
14949
15008
  "huggingface_hub==1.19.0" \\
@@ -14957,10 +15016,15 @@ RUN python3 -m pip install --break-system-packages --no-cache-dir \\
14957
15016
  "uvicorn==0.49.0" \\
14958
15017
  "uv==0.11.28" \\
14959
15018
  "hf-discover==1.3.7"
15019
+ ARG BROKERKIT_PLUGIN_VERSION
15020
+ COPY --from=brokerkit-plugin-build /out/openclaw-brokerkit-\${BROKERKIT_PLUGIN_VERSION}.tgz /tmp/openclaw-brokerkit.tgz
15021
+ RUN npm install --omit=dev --omit=peer --no-audit --no-fund --prefix /opt/openclaw-plugins /tmp/openclaw-brokerkit.tgz && rm /tmp/openclaw-brokerkit.tgz && test -f /opt/openclaw-plugins/node_modules/openclaw-brokerkit/openclaw.plugin.json
14960
15022
 
14961
15023
  COPY --chown=node:node runtime/hf-state-sync.js /app/hf-state-sync.js
14962
15024
  COPY --chown=node:node runtime/hf-tooling-seed.js /app/hf-tooling-seed.js
14963
15025
  COPY --chown=node:node runtime/mlclaw-space-runtime.js /app/mlclaw-space-runtime.js
15026
+ 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
14964
15028
  COPY --chown=node:node runtime/openclaw.default.json /app/openclaw.default.json
14965
15029
  COPY --chown=node:node runtime/entrypoint.sh /app/entrypoint.sh
14966
15030
  COPY --chown=node:node runtime/scripts/ /app/scripts/
@@ -14970,11 +15034,12 @@ RUN chmod +x /app/entrypoint.sh
14970
15034
  ENV PORT=7860
14971
15035
  ENV MLCLAW_OPENCLAW_PORT=7861
14972
15036
  ENV OPENCLAW_GATEWAY_PORT=7861
14973
- ENV OPENCLAW_LIVE_DIR=/tmp/openclaw-live
14974
- ENV OPENCLAW_STATE_DIR=/tmp/openclaw-live/.openclaw
14975
- ENV OPENCLAW_WORKSPACE_DIR=/tmp/openclaw-live/workspace
14976
- ENV OPENCLAW_CONFIG_PATH=/tmp/openclaw-live/.openclaw/openclaw.json
15037
+ ENV OPENCLAW_LIVE_DIR=/home/node/.local/share/mlclaw/live
15038
+ ENV OPENCLAW_STATE_DIR=/home/node/.local/share/mlclaw/live/.openclaw
15039
+ ENV OPENCLAW_WORKSPACE_DIR=/home/node/.local/share/mlclaw/live/workspace
15040
+ ENV OPENCLAW_CONFIG_PATH=/home/node/.local/share/mlclaw/live/.openclaw/openclaw.json
14977
15041
  ENV OPENCLAW_DISABLE_BONJOUR=1
15042
+ ENV MLCLAW_BROKERKIT_PLUGIN_PATH=/opt/openclaw-plugins/node_modules/openclaw-brokerkit
14978
15043
 
14979
15044
  EXPOSE 7860
14980
15045
 
@@ -15259,6 +15324,8 @@ var DEFAULT_LOCAL_PORT = 7860;
15259
15324
  var DEFAULT_SPACE_OPENCLAW_PORT = 7861;
15260
15325
  var LOCAL_VOLUME_MOUNT_PATH = "/tmp/mlclaw-local";
15261
15326
  var LOCAL_LIVE_DIR = `${LOCAL_VOLUME_MOUNT_PATH}/openclaw-live`;
15327
+ var SPACE_STATE_MOUNT_DIR = "/data/mlclaw-state";
15328
+ var SPACE_LIVE_DIR = "/home/node/.local/share/mlclaw/live";
15262
15329
  var SPACE_HANDOFF_TIMEOUT_MS = 12e4;
15263
15330
  var SPACE_HANDOFF_POLL_MS = 5e3;
15264
15331
  var STALE_PATH_VARS = ["OPENCLAW_STATE_DIR", "OPENCLAW_WORKSPACE_DIR", "OPENCLAW_CONFIG_PATH"];
@@ -15296,10 +15363,10 @@ function createProgram(runtimeOverrides = {}) {
15296
15363
  program2.name("mlclaw").description("Deploy OpenClaw to a Hugging Face Space and private bucket").showHelpAfterError().exitOverride((err) => {
15297
15364
  throw err;
15298
15365
  });
15299
- program2.command("bootstrap", { isDefault: true }).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", DEFAULT_MODEL).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("--docker-context <name>", "Docker context for local gateway mode").option("--no-pull", "Do not docker pull before starting a local gateway").option("--takeover", "Start even if a stale runtime lease is present", false).option("--yes", "Confirm paid hardware prompts for automation", false).action(async (opts) => {
15366
+ program2.command("bootstrap", { isDefault: true }).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", DEFAULT_MODEL).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("--router-token-file <path>", "File containing MLCLAW_ROUTER_TOKEN=..., HF_ROUTER_TOKEN=..., or a raw token").option("--docker-context <name>", "Docker context for local gateway mode").option("--no-pull", "Do not docker pull before starting a local gateway").option("--takeover", "Start even if a stale runtime lease is present", false).option("--yes", "Confirm paid hardware prompts for automation", false).action(async (opts) => {
15300
15367
  await bootstrap(opts, runtime);
15301
15368
  });
15302
- program2.command("update").description("Regenerate and upload current ML Claw Space files").argument("<owner/space>", "Hugging Face Space repo ID").option("--runtime-image <image>", "Runtime image to write into the generated Space Dockerfile").option("--bundled-runtime", "Generate a bundled Space runtime instead of using the prebuilt ML Claw image", false).option("--force", "Update even if the Space does not look like ML Claw", false).action(async (repoId, opts) => {
15369
+ program2.command("update").description("Regenerate and upload current ML Claw Space files").argument("<owner/space>", "Hugging Face Space repo ID").option("--runtime-image <image>", "Runtime image to write into the generated Space Dockerfile").option("--bundled-runtime", "Generate a bundled Space runtime instead of using the prebuilt ML Claw image", false).option("--router-token <token>", "Dedicated Hugging Face Router inference token").option("--router-token-file <path>", "File containing MLCLAW_ROUTER_TOKEN=..., HF_ROUTER_TOKEN=..., or a raw token").option("--force", "Update even if the Space does not look like ML Claw", false).action(async (repoId, opts) => {
15303
15370
  const token = await runtime.readToken(runtime.env);
15304
15371
  const hub = runtime.hubFactory(token);
15305
15372
  await update(repoId, opts, hub, token, runtime);
@@ -15322,8 +15389,7 @@ function createProgram(runtimeOverrides = {}) {
15322
15389
  await gatewayStop(agent, runtime);
15323
15390
  });
15324
15391
  gateway.command("restart").argument("<agent>", "Agent name").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) => {
15325
- await gatewayStop(agent, runtime);
15326
- await gatewayStart(agent, opts, runtime);
15392
+ await gatewayRestart(agent, opts, runtime);
15327
15393
  });
15328
15394
  gateway.command("status").argument("<agent>", "Agent name").action(async (agent) => {
15329
15395
  await gatewayStatus(agent, runtime);
@@ -15331,7 +15397,7 @@ function createProgram(runtimeOverrides = {}) {
15331
15397
  gateway.command("logs").argument("<agent>", "Agent name").option("--tail <lines>", "Number of log lines", parseInteger, 200).action(async (agent, opts) => {
15332
15398
  await gatewayLogs(agent, opts, runtime);
15333
15399
  });
15334
- gateway.command("migrate").argument("<agent>", "Agent name").requiredOption("--to <local|space>", "Target gateway location").option("--hardware <flavor>", "Hugging Face Space hardware flavor").option("--sleep-time <seconds>", "Space sleep timeout in seconds; -1 means never sleep", parseInteger).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).option("--docker-context <name>", "Docker context for local gateway startup when migrating to local").option("--no-pull", "Do not docker pull before starting a local gateway").option("--takeover", "Start even if another live runtime lease is present", false).option("--yes", "Confirm paid hardware prompts for automation", false).action(async (agent, opts) => {
15400
+ gateway.command("migrate").argument("<agent>", "Agent name").requiredOption("--to <local|space>", "Target gateway location").option("--hardware <flavor>", "Hugging Face Space hardware flavor").option("--sleep-time <seconds>", "Space sleep timeout in seconds; -1 means never sleep", parseInteger).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).option("--router-token <token>", "Hugging Face Router inference token for Space gateway model calls").option("--router-token-file <path>", "File containing MLCLAW_ROUTER_TOKEN=..., HF_ROUTER_TOKEN=..., or a raw token").option("--docker-context <name>", "Docker context for local gateway startup when migrating to local").option("--no-pull", "Do not docker pull before starting a local gateway").option("--takeover", "Start even if another live runtime lease is present", false).option("--yes", "Confirm paid hardware prompts for automation", false).action(async (agent, opts) => {
15335
15401
  await gatewayMigrate(agent, opts, runtime);
15336
15402
  });
15337
15403
  gateway.command("rebind").argument("<agent>", "Agent name").requiredOption("--docker-context <name>", "Target Docker context").option("--no-pull", "Do not docker pull before starting the rebound local gateway").option("--takeover", "Rebind even if the old Docker context is unavailable", false).action(async (agent, opts) => {
@@ -15404,8 +15470,9 @@ async function bootstrap(opts, runtime) {
15404
15470
  throw new Error("internal error: Space plan was not resolved");
15405
15471
  }
15406
15472
  const paidHardware = await resolveHardware({
15407
- requestedHardware: opts.hardware ?? (telegramToken ? TELEGRAM_HARDWARE : DEFAULT_HARDWARE),
15473
+ ...opts.hardware ? { requestedHardware: opts.hardware } : {},
15408
15474
  ...typeof opts.sleepTime === "number" ? { requestedSleepTime: opts.sleepTime } : telegramToken ? { requestedSleepTime: TELEGRAM_SLEEP_TIME } : {},
15475
+ defaultLabel: spacePlan.exists ? "unchanged Space hardware" : "default free CPU",
15409
15476
  requiresMessagingEgress: Boolean(telegramToken),
15410
15477
  yes: Boolean(opts.yes),
15411
15478
  runtime
@@ -15415,7 +15482,7 @@ async function bootstrap(opts, runtime) {
15415
15482
  bucketPlan,
15416
15483
  spacePlan,
15417
15484
  hasExistingManifest: plan.hasExistingManifest,
15418
- hardware: paidHardware.hardware,
15485
+ hardware: paidHardware.label,
15419
15486
  ...typeof paidHardware.sleepTime === "number" ? { sleepTime: paidHardware.sleepTime } : {},
15420
15487
  yes: Boolean(opts.yes),
15421
15488
  runtime
@@ -15435,9 +15502,9 @@ async function bootstrap(opts, runtime) {
15435
15502
  manifest,
15436
15503
  secrets,
15437
15504
  allowedUsers: me2.name,
15438
- hardware: paidHardware.hardware,
15439
15505
  spaceExists: spacePlan.exists,
15440
15506
  publicSpace: Boolean(opts.publicSpace),
15507
+ ...paidHardware.kind === "explicit" ? { hardware: paidHardware.hardware } : {},
15441
15508
  ...typeof paidHardware.sleepTime === "number" ? { sleepTime: paidHardware.sleepTime } : {},
15442
15509
  ...templateRuntimeImage ? { templateRuntimeImage } : {}
15443
15510
  });
@@ -15493,10 +15560,11 @@ function spacePublicUrl(repoId) {
15493
15560
  async function resolveBootstrapPlan(params) {
15494
15561
  const { opts, owner, agentName, requestedGatewayLocation, hfToken, telegramToken, telegramUserId, model, runtimeImage, hub, runtime } = params;
15495
15562
  const names = namesFor(owner, agentName);
15496
- const sessionSecret = randomBytes(48).toString("base64url");
15497
15563
  const now = runtime.now().toISOString();
15498
15564
  const existingManifest = await readManifest(runtime.configRoot, agentName).catch(() => null);
15499
15565
  const existingSecrets = await readSecretEnv(runtime.configRoot, agentName).catch(() => ({}));
15566
+ const sessionSecret = existingSecrets.MLCLAW_SESSION_SECRET ?? randomBytes(48).toString("base64url");
15567
+ const credentialKey = existingSecrets.MLCLAW_CREDENTIAL_KEY ?? randomBytes(32).toString("base64url");
15500
15568
  const gatewayLocation = requestedGatewayLocation ?? existingManifest?.gatewayLocation ?? DEFAULT_GATEWAY_LOCATION;
15501
15569
  if (opts.dockerContext && gatewayLocation !== "local") {
15502
15570
  throw new Error("--docker-context only applies to local gateway mode");
@@ -15517,6 +15585,12 @@ async function resolveBootstrapPlan(params) {
15517
15585
  hub
15518
15586
  });
15519
15587
  const bucket = bucketPlan.bucket;
15588
+ const routerToken = await resolveRouterToken({
15589
+ opts,
15590
+ runtime,
15591
+ existingSecrets,
15592
+ model
15593
+ });
15520
15594
  const spacePlan = gatewayLocation === "space" ? {
15521
15595
  space: names.space,
15522
15596
  exists: await hub.spaceExists(names.space),
@@ -15541,6 +15615,7 @@ async function resolveBootstrapPlan(params) {
15541
15615
  ...telegramToken ? { telegramToken } : {},
15542
15616
  ...telegramUserId ? { telegramUserId } : {},
15543
15617
  sessionSecret,
15618
+ credentialKey,
15544
15619
  bucket,
15545
15620
  model,
15546
15621
  agentName,
@@ -15549,7 +15624,8 @@ async function resolveBootstrapPlan(params) {
15549
15624
  runtimeId: gatewayLocation === "local" ? manifest.localRuntimeId : spaceRuntimeId(agentName),
15550
15625
  ...bucketPrefix ? { bucketPrefix } : {},
15551
15626
  ...opts.telegramProxy ? { telegramProxy: opts.telegramProxy } : {},
15552
- ...opts.telegramApiRoot ? { telegramApiRoot: opts.telegramApiRoot } : {}
15627
+ ...opts.telegramApiRoot ? { telegramApiRoot: opts.telegramApiRoot } : {},
15628
+ ...routerToken ? { routerToken } : {}
15553
15629
  });
15554
15630
  return {
15555
15631
  agentName,
@@ -15611,6 +15687,8 @@ async function confirmBootstrapPlan(params) {
15611
15687
  if (params.spacePlan) {
15612
15688
  lines.push(`Space: ${params.spacePlan.space} (${params.spacePlan.exists ? "exists; files, variables, secrets, and runtime will be updated" : `will be created as ${params.spacePlan.visibility}`})`);
15613
15689
  lines.push(`Hardware: ${params.hardware}`);
15690
+ lines.push(`Bucket mount: ${SPACE_STATE_MOUNT_DIR}`);
15691
+ lines.push(`Live state: ${SPACE_LIVE_DIR}`);
15614
15692
  if (typeof params.sleepTime === "number") {
15615
15693
  lines.push(`Sleep time: ${params.sleepTime}`);
15616
15694
  }
@@ -15650,6 +15728,10 @@ async function stateAdopt(agent, opts, runtime) {
15650
15728
  const hub = runtime.hubFactory(token);
15651
15729
  const secrets = await readSecretEnv(runtime.configRoot, agent);
15652
15730
  const bucketPrefix = persistedBucketPrefix(secrets);
15731
+ const bucketChanged = current.bucket !== bucket;
15732
+ if (bucketChanged && current.gatewayLocation === "local") {
15733
+ assertDedicatedRouterToken(current.model, secrets);
15734
+ }
15653
15735
  runtime.stdout.log(`Creating or adopting private bucket ${bucket}`);
15654
15736
  await hub.createBucket(bucket, true);
15655
15737
  await inspectStateBucket(hub, bucket, bucketPrefix);
@@ -15660,7 +15742,6 @@ async function stateAdopt(agent, opts, runtime) {
15660
15742
  runtimeId: runtimeIdFor(current),
15661
15743
  takeover: Boolean(opts.takeover)
15662
15744
  });
15663
- const bucketChanged = current.bucket !== bucket;
15664
15745
  if (bucketChanged) {
15665
15746
  await confirmBucketChange({
15666
15747
  message: `Adopt state bucket ${bucket} for ${agent}, replacing ${current.bucket}?`,
@@ -15711,9 +15792,16 @@ async function stateAdopt(agent, opts, runtime) {
15711
15792
  } else {
15712
15793
  await setDeploymentVariables(hub, updated.space, {
15713
15794
  OPENCLAW_HF_STATE_BUCKET: bucket,
15795
+ MLCLAW_STATE_MOUNT_DIR: SPACE_STATE_MOUNT_DIR,
15796
+ OPENCLAW_LIVE_DIR: SPACE_LIVE_DIR,
15797
+ MLCLAW_RUNTIME_SETTINGS_FILE: `${SPACE_LIVE_DIR}/.mlclaw/settings.json`,
15714
15798
  MLCLAW_GATEWAY_LOCATION: "space",
15715
15799
  MLCLAW_RUNTIME_ID: spaceRuntimeId(updated.agent)
15716
15800
  });
15801
+ await ensureSpaceStateVolume(hub, updated.space, bucket);
15802
+ if (canDeleteBroadTokenSecrets({ model: updated.model, routerTokenPresent: hasBrokerOrRouterTokenSecretRecord(secrets) })) {
15803
+ await deleteStaleSpaceTokenSecrets(hub, updated.space);
15804
+ }
15717
15805
  await clearSpaceGatewayDisabled(hub, updated.space);
15718
15806
  if (bucketChanged) {
15719
15807
  await hub.restartSpace(updated.space, true);
@@ -15788,8 +15876,8 @@ function parseBucketId(raw) {
15788
15876
  }
15789
15877
  function deploymentSecrets(params) {
15790
15878
  return {
15791
- HF_TOKEN: params.hfToken,
15792
- HUGGINGFACE_HUB_TOKEN: params.hfToken,
15879
+ MLCLAW_BROKER_HF_TOKEN: params.hfToken,
15880
+ ...params.routerToken ? { MLCLAW_ROUTER_TOKEN: params.routerToken } : {},
15793
15881
  OPENCLAW_HF_STATE_BUCKET: params.bucket,
15794
15882
  OPENCLAW_MODEL: params.model,
15795
15883
  OPENCLAW_AGENT_NAME: params.agentName,
@@ -15797,6 +15885,7 @@ function deploymentSecrets(params) {
15797
15885
  MLCLAW_RUNTIME_IMAGE: params.runtimeImage,
15798
15886
  MLCLAW_RUNTIME_ID: params.runtimeId,
15799
15887
  MLCLAW_SESSION_SECRET: params.sessionSecret,
15888
+ MLCLAW_CREDENTIAL_KEY: params.credentialKey,
15800
15889
  MLCLAW_OPENCLAW_PORT: String(DEFAULT_SPACE_OPENCLAW_PORT),
15801
15890
  OPENCLAW_GATEWAY_PORT: String(DEFAULT_SPACE_OPENCLAW_PORT),
15802
15891
  ...params.telegramToken ? { TELEGRAM_BOT_TOKEN: params.telegramToken } : {},
@@ -15815,10 +15904,14 @@ async function deploySpaceGateway(params) {
15815
15904
  runtime.stdout.log(params.spaceExists ? `Updating existing Space ${manifest.space}` : `Creating ${params.publicSpace ? "public" : "private"} Space ${manifest.space}`);
15816
15905
  await hub.createDockerSpace(manifest.space, {
15817
15906
  private: !params.publicSpace,
15818
- hardware: params.hardware,
15907
+ ...params.hardware && !params.spaceExists ? { hardware: params.hardware } : {},
15819
15908
  ...typeof params.sleepTime === "number" ? { sleepTimeSeconds: params.sleepTime } : {}
15820
15909
  });
15821
- await hub.requestSpaceHardware(manifest.space, params.hardware, params.sleepTime);
15910
+ if (params.hardware && params.spaceExists) {
15911
+ await hub.requestSpaceHardware(manifest.space, params.hardware, params.sleepTime);
15912
+ } else if (!params.hardware && params.spaceExists && typeof params.sleepTime === "number") {
15913
+ await hub.setSpaceSleepTime(manifest.space, params.sleepTime);
15914
+ }
15822
15915
  runtime.stdout.log(params.templateRuntimeImage ? "Generating Space files from prebuilt runtime image" : "Generating bundled Space runtime files");
15823
15916
  const { templateRev } = await runtime.pushTemplateToSpace({
15824
15917
  targetRepo: manifest.space,
@@ -15828,6 +15921,9 @@ async function deploySpaceGateway(params) {
15828
15921
  const spaceRuntimeRef = params.templateRuntimeImage ?? bundledSpaceRuntimeRef(templateRev);
15829
15922
  await setDeploymentVariables(hub, manifest.space, {
15830
15923
  OPENCLAW_HF_STATE_BUCKET: manifest.bucket,
15924
+ MLCLAW_STATE_MOUNT_DIR: SPACE_STATE_MOUNT_DIR,
15925
+ OPENCLAW_LIVE_DIR: SPACE_LIVE_DIR,
15926
+ MLCLAW_RUNTIME_SETTINGS_FILE: `${SPACE_LIVE_DIR}/.mlclaw/settings.json`,
15831
15927
  ...secrets.OPENCLAW_HF_STATE_PREFIX ? { OPENCLAW_HF_STATE_PREFIX: secrets.OPENCLAW_HF_STATE_PREFIX } : {},
15832
15928
  MLCLAW_TEMPLATE_REV: templateRev,
15833
15929
  OPENCLAW_MODEL: manifest.model,
@@ -15841,21 +15937,30 @@ async function deploySpaceGateway(params) {
15841
15937
  MLCLAW_OPENCLAW_PORT: String(DEFAULT_SPACE_OPENCLAW_PORT),
15842
15938
  OPENCLAW_GATEWAY_PORT: String(DEFAULT_SPACE_OPENCLAW_PORT)
15843
15939
  });
15940
+ await ensureSpaceStateVolume(hub, manifest.space, manifest.bucket, { allowMissingVolumes: !params.spaceExists });
15844
15941
  await clearSpaceGatewayDisabled(hub, manifest.space);
15845
15942
  await setDeploymentSecrets(hub, manifest.space, {
15846
- HF_TOKEN: requiredSecret(secrets, "HF_TOKEN"),
15847
- HUGGINGFACE_HUB_TOKEN: requiredSecret(secrets, "HF_TOKEN"),
15848
15943
  MLCLAW_SESSION_SECRET: requiredSecret(secrets, "MLCLAW_SESSION_SECRET"),
15944
+ MLCLAW_CREDENTIAL_KEY: requiredSecret(secrets, "MLCLAW_CREDENTIAL_KEY"),
15945
+ MLCLAW_BROKER_HF_TOKEN: hfToken,
15946
+ ...secrets.MLCLAW_ROUTER_TOKEN ? { MLCLAW_ROUTER_TOKEN: secrets.MLCLAW_ROUTER_TOKEN } : {},
15849
15947
  ...secrets.TELEGRAM_BOT_TOKEN ? { TELEGRAM_BOT_TOKEN: secrets.TELEGRAM_BOT_TOKEN } : {},
15850
15948
  ...secrets.TELEGRAM_ALLOWED_USERS ? { TELEGRAM_ALLOWED_USERS: secrets.TELEGRAM_ALLOWED_USERS } : {},
15851
15949
  ...secrets.TELEGRAM_PROXY ? { TELEGRAM_PROXY: secrets.TELEGRAM_PROXY } : {},
15852
15950
  ...secrets.TELEGRAM_API_ROOT ? { TELEGRAM_API_ROOT: secrets.TELEGRAM_API_ROOT } : {}
15853
15951
  });
15952
+ if (canDeleteBroadTokenSecrets({ model: manifest.model, routerTokenPresent: hasBrokerOrRouterTokenSecretRecord(secrets) })) {
15953
+ await deleteStaleSpaceTokenSecrets(hub, manifest.space);
15954
+ } else {
15955
+ runtime.stdout.log("Keeping legacy broad Hub token secrets until an HF Broker or Router credential is configured");
15956
+ }
15854
15957
  await hub.restartSpace(manifest.space, true);
15855
15958
  return { runtimeImage: spaceRuntimeRef };
15856
15959
  }
15857
15960
  async function startLocalGateway(params) {
15858
15961
  const { manifest, runtime } = params;
15962
+ const secrets = await ensureDeploymentCredentialKey(runtime, manifest.agent);
15963
+ assertDedicatedRouterToken(manifest.model, secrets);
15859
15964
  const containerName = containerNameFor(manifest.agent);
15860
15965
  const volumeName = volumeNameFor(manifest.agent);
15861
15966
  const dockerContext = dockerContextFor(manifest);
@@ -15928,6 +16033,17 @@ async function gatewayStart(agent, opts, runtime) {
15928
16033
  runtime.stdout.log(`Space gateway restart requested: ${manifest.space}`);
15929
16034
  }
15930
16035
  }
16036
+ async function gatewayRestart(agent, opts, runtime) {
16037
+ const manifest = await readDeploymentManifest(runtime, agent, { requestedDockerContext: opts.dockerContext });
16038
+ if (manifest.gatewayLocation === "local") {
16039
+ assertDedicatedRouterToken(
16040
+ manifest.model,
16041
+ await readSecretEnv(runtime.configRoot, agent).catch(() => ({}))
16042
+ );
16043
+ }
16044
+ await gatewayStop(agent, runtime);
16045
+ await gatewayStart(agent, opts, runtime);
16046
+ }
15931
16047
  async function gatewayStop(agent, runtime) {
15932
16048
  const manifest = await readDeploymentManifest(runtime, agent);
15933
16049
  const bucketPrefix = await readDeploymentBucketPrefix(runtime, agent);
@@ -15995,7 +16111,7 @@ async function gatewayMigrate(agent, opts, runtime) {
15995
16111
  }
15996
16112
  const token = await runtime.readToken(runtime.env);
15997
16113
  const hub = runtime.hubFactory(token);
15998
- const secrets = await readSecretEnv(runtime.configRoot, agent);
16114
+ const secrets = await ensureDeploymentCredentialKey(runtime, agent);
15999
16115
  const bucketPrefix = persistedBucketPrefix(secrets);
16000
16116
  const updated = {
16001
16117
  ...current,
@@ -16003,10 +16119,23 @@ async function gatewayMigrate(agent, opts, runtime) {
16003
16119
  runtimeImage: resolveRuntimeImage(opts.runtimeImage ?? current.runtimeImage, runtime.env),
16004
16120
  updatedAt: runtime.now().toISOString()
16005
16121
  };
16122
+ const routerToken = await resolveRouterToken({
16123
+ opts,
16124
+ runtime,
16125
+ existingSecrets: secrets,
16126
+ model: updated.model
16127
+ });
16006
16128
  if (target === "space") {
16129
+ const deploymentSecrets2 = {
16130
+ ...secrets,
16131
+ MLCLAW_GATEWAY_LOCATION: "space",
16132
+ MLCLAW_RUNTIME_IMAGE: updated.runtimeImage,
16133
+ ...routerToken ? { MLCLAW_ROUTER_TOKEN: routerToken } : {}
16134
+ };
16007
16135
  const paidHardware = await resolveHardware({
16008
- requestedHardware: opts.hardware ?? (secrets.TELEGRAM_BOT_TOKEN ? TELEGRAM_HARDWARE : DEFAULT_HARDWARE),
16136
+ ...opts.hardware ? { requestedHardware: opts.hardware } : {},
16009
16137
  ...typeof opts.sleepTime === "number" ? { requestedSleepTime: opts.sleepTime } : secrets.TELEGRAM_BOT_TOKEN ? { requestedSleepTime: TELEGRAM_SLEEP_TIME } : {},
16138
+ defaultLabel: "unchanged Space hardware",
16010
16139
  requiresMessagingEgress: Boolean(secrets.TELEGRAM_BOT_TOKEN),
16011
16140
  yes: Boolean(opts.yes),
16012
16141
  runtime
@@ -16021,24 +16150,22 @@ async function gatewayMigrate(agent, opts, runtime) {
16021
16150
  await handoffAndStopLocalGateway({ manifest: current, hub, runtime, bucketPrefix });
16022
16151
  const me2 = await hub.whoami();
16023
16152
  const templateRuntimeImage = resolveSpaceRuntimeImage(opts, runtime.env);
16153
+ const spaceExists = await hub.spaceExists(updated.space);
16024
16154
  await deploySpaceGateway({
16025
16155
  hub,
16026
16156
  runtime,
16027
16157
  hfToken: token,
16028
16158
  manifest: updated,
16029
- secrets: {
16030
- ...secrets,
16031
- MLCLAW_GATEWAY_LOCATION: "space",
16032
- MLCLAW_RUNTIME_IMAGE: updated.runtimeImage
16033
- },
16159
+ secrets: deploymentSecrets2,
16034
16160
  allowedUsers: me2.name,
16035
- hardware: paidHardware.hardware,
16036
16161
  publicSpace: Boolean(opts.publicSpace),
16162
+ spaceExists,
16163
+ ...paidHardware.kind === "explicit" ? { hardware: paidHardware.hardware } : {},
16037
16164
  ...typeof paidHardware.sleepTime === "number" ? { sleepTime: paidHardware.sleepTime } : {},
16038
16165
  ...templateRuntimeImage ? { templateRuntimeImage } : {}
16039
16166
  });
16040
16167
  await writeSecretEnv(runtime.configRoot, agent, {
16041
- ...secrets,
16168
+ ...deploymentSecrets2,
16042
16169
  MLCLAW_GATEWAY_LOCATION: "space",
16043
16170
  MLCLAW_RUNTIME_IMAGE: updated.runtimeImage,
16044
16171
  MLCLAW_RUNTIME_ID: spaceRuntimeId(agent)
@@ -16070,6 +16197,7 @@ async function gatewayMigrate(agent, opts, runtime) {
16070
16197
  });
16071
16198
  await writeSecretEnv(runtime.configRoot, agent, {
16072
16199
  ...secrets,
16200
+ ...routerToken ? { MLCLAW_ROUTER_TOKEN: routerToken } : {},
16073
16201
  MLCLAW_GATEWAY_LOCATION: "local",
16074
16202
  MLCLAW_RUNTIME_IMAGE: updated.runtimeImage,
16075
16203
  MLCLAW_RUNTIME_ID: updated.localRuntimeId
@@ -16085,6 +16213,10 @@ async function gatewayRebind(agent, opts, runtime) {
16085
16213
  if (current.gatewayLocation !== "local") {
16086
16214
  throw new Error("Docker context rebind only applies to local gateway deployments");
16087
16215
  }
16216
+ assertDedicatedRouterToken(
16217
+ current.model,
16218
+ await readSecretEnv(runtime.configRoot, agent).catch(() => ({}))
16219
+ );
16088
16220
  const targetBinding = await resolveLocalGatewayBinding({
16089
16221
  manifest: void 0,
16090
16222
  requestedContext: targetContext,
@@ -16358,6 +16490,18 @@ function requiredSecret(secrets, key) {
16358
16490
  }
16359
16491
  return value;
16360
16492
  }
16493
+ async function ensureDeploymentCredentialKey(runtime, agent, existing) {
16494
+ const secrets = existing ?? await readSecretEnv(runtime.configRoot, agent).catch(() => ({}));
16495
+ if (secrets.MLCLAW_CREDENTIAL_KEY) {
16496
+ return secrets;
16497
+ }
16498
+ const updated = {
16499
+ ...secrets,
16500
+ MLCLAW_CREDENTIAL_KEY: randomBytes(32).toString("base64url")
16501
+ };
16502
+ await writeSecretEnv(runtime.configRoot, agent, updated);
16503
+ return updated;
16504
+ }
16361
16505
  function requiredOption(value, label) {
16362
16506
  if (!value) {
16363
16507
  throw new Error(`${label} is required`);
@@ -16375,6 +16519,16 @@ async function update(repoId, opts, hub, hfToken, runtime) {
16375
16519
  }
16376
16520
  const runtimeImage = resolveSpaceRuntimeImage(opts, runtime.env);
16377
16521
  const agentName = variables.get("OPENCLAW_AGENT_NAME")?.value?.trim() || repoId.split("/")[1] || "openclaw";
16522
+ if (!canonicalTemplate) {
16523
+ await ensureUpdateRouterToken({
16524
+ repoId,
16525
+ agentName,
16526
+ model: variables.get("OPENCLAW_MODEL")?.value ?? DEFAULT_MODEL,
16527
+ opts,
16528
+ hub,
16529
+ runtime
16530
+ });
16531
+ }
16378
16532
  runtime.stdout.log(`Generating current Space files into ${repoId}`);
16379
16533
  const { templateRev } = await runtime.pushTemplateToSpace({
16380
16534
  targetRepo: repoId,
@@ -16393,9 +16547,50 @@ async function update(repoId, opts, hub, hfToken, runtime) {
16393
16547
  await hub.addSpaceVariable(repoId, "MLCLAW_RUNTIME_ID", spaceRuntimeId(agentName));
16394
16548
  await hub.addSpaceVariable(repoId, "MLCLAW_OPENCLAW_PORT", String(DEFAULT_SPACE_OPENCLAW_PORT));
16395
16549
  await hub.addSpaceVariable(repoId, "OPENCLAW_GATEWAY_PORT", String(DEFAULT_SPACE_OPENCLAW_PORT));
16550
+ const bucket = variables.get("OPENCLAW_HF_STATE_BUCKET")?.value;
16551
+ if (bucket) {
16552
+ await hub.addSpaceVariable(repoId, "MLCLAW_STATE_MOUNT_DIR", SPACE_STATE_MOUNT_DIR);
16553
+ await hub.addSpaceVariable(repoId, "OPENCLAW_LIVE_DIR", SPACE_LIVE_DIR);
16554
+ await hub.addSpaceVariable(repoId, "MLCLAW_RUNTIME_SETTINGS_FILE", `${SPACE_LIVE_DIR}/.mlclaw/settings.json`);
16555
+ await ensureSpaceStateVolume(hub, repoId, bucket);
16556
+ }
16396
16557
  await doctor(repoId, { fix: true }, hub, runtime);
16397
16558
  await hub.restartSpace(repoId, true);
16398
16559
  }
16560
+ async function ensureUpdateRouterToken(params) {
16561
+ if (!isHuggingFaceRouterModel(params.model)) {
16562
+ return;
16563
+ }
16564
+ const spaceSecrets = await params.hub.getSpaceSecrets(params.repoId);
16565
+ const hasExplicitOverride = params.opts.routerToken !== void 0 || params.opts.routerTokenFile !== void 0;
16566
+ if (hasBrokerOrRouterTokenSecretMap(spaceSecrets) && !hasExplicitOverride) {
16567
+ return;
16568
+ }
16569
+ const hasManifest = await manifestExists(params.runtime.configRoot, params.agentName);
16570
+ const localSecrets = hasManifest ? await readSecretEnv(params.runtime.configRoot, params.agentName).catch(() => ({})) : {};
16571
+ const routerToken = hasExplicitOverride ? await resolveRouterToken({
16572
+ opts: params.opts,
16573
+ runtime: params.runtime,
16574
+ existingSecrets: localSecrets,
16575
+ model: params.model
16576
+ }) : void 0;
16577
+ const brokerToken = routerToken ? void 0 : await params.runtime.readToken(params.runtime.env);
16578
+ const credential = routerToken ?? brokerToken;
16579
+ if (!credential) {
16580
+ throw new Error("Hugging Face broker credential is unavailable");
16581
+ }
16582
+ await params.hub.addSpaceSecret(
16583
+ params.repoId,
16584
+ routerToken ? "MLCLAW_ROUTER_TOKEN" : "MLCLAW_BROKER_HF_TOKEN",
16585
+ credential
16586
+ );
16587
+ if (hasManifest) {
16588
+ await writeSecretEnv(params.runtime.configRoot, params.agentName, {
16589
+ ...localSecrets,
16590
+ ...routerToken ? { MLCLAW_ROUTER_TOKEN: routerToken } : { MLCLAW_BROKER_HF_TOKEN: brokerToken }
16591
+ });
16592
+ }
16593
+ }
16399
16594
  async function doctor(repoId, opts, hub, runtime) {
16400
16595
  if (!repoId.includes("/") && await manifestExists(runtime.configRoot, repoId)) {
16401
16596
  await gatewayStatus(repoId, runtime);
@@ -16454,6 +16649,31 @@ async function doctor(repoId, opts, hub, runtime) {
16454
16649
  await hub.addSpaceVariable(repoId, "OPENCLAW_HF_STATE_BUCKET", bucket);
16455
16650
  fixed.push("set OPENCLAW_HF_STATE_BUCKET");
16456
16651
  }
16652
+ if ((variables.get("MLCLAW_STATE_MOUNT_DIR")?.value ?? "") !== SPACE_STATE_MOUNT_DIR) {
16653
+ if (fix) {
16654
+ await hub.addSpaceVariable(repoId, "MLCLAW_STATE_MOUNT_DIR", SPACE_STATE_MOUNT_DIR);
16655
+ fixed.push("set MLCLAW_STATE_MOUNT_DIR");
16656
+ } else {
16657
+ issues.push(`MLCLAW_STATE_MOUNT_DIR is not ${SPACE_STATE_MOUNT_DIR}`);
16658
+ }
16659
+ }
16660
+ if ((variables.get("OPENCLAW_LIVE_DIR")?.value ?? "") !== SPACE_LIVE_DIR) {
16661
+ if (fix) {
16662
+ await hub.addSpaceVariable(repoId, "OPENCLAW_LIVE_DIR", SPACE_LIVE_DIR);
16663
+ fixed.push("set OPENCLAW_LIVE_DIR");
16664
+ } else {
16665
+ issues.push(`OPENCLAW_LIVE_DIR is not ${SPACE_LIVE_DIR}`);
16666
+ }
16667
+ }
16668
+ const expectedRuntimeSettingsFile = `${SPACE_LIVE_DIR}/.mlclaw/settings.json`;
16669
+ if ((variables.get("MLCLAW_RUNTIME_SETTINGS_FILE")?.value ?? "") !== expectedRuntimeSettingsFile) {
16670
+ if (fix) {
16671
+ await hub.addSpaceVariable(repoId, "MLCLAW_RUNTIME_SETTINGS_FILE", expectedRuntimeSettingsFile);
16672
+ fixed.push("set MLCLAW_RUNTIME_SETTINGS_FILE");
16673
+ } else {
16674
+ issues.push(`MLCLAW_RUNTIME_SETTINGS_FILE is not ${expectedRuntimeSettingsFile}`);
16675
+ }
16676
+ }
16457
16677
  for (const key of STALE_PATH_VARS) {
16458
16678
  if (variables.has(key)) {
16459
16679
  if (fix) {
@@ -16464,8 +16684,30 @@ async function doctor(repoId, opts, hub, runtime) {
16464
16684
  }
16465
16685
  }
16466
16686
  }
16467
- if (!secrets.has("HF_TOKEN")) {
16468
- issues.push("secret HF_TOKEN is missing");
16687
+ if (!secrets.has("MLCLAW_BROKER_HF_TOKEN")) {
16688
+ if (fix) {
16689
+ await hub.addSpaceSecret(repoId, "MLCLAW_BROKER_HF_TOKEN", await runtime.readToken(runtime.env));
16690
+ secrets.set("MLCLAW_BROKER_HF_TOKEN", { key: "MLCLAW_BROKER_HF_TOKEN" });
16691
+ fixed.push("set secret MLCLAW_BROKER_HF_TOKEN");
16692
+ } else {
16693
+ issues.push("secret MLCLAW_BROKER_HF_TOKEN is missing");
16694
+ }
16695
+ }
16696
+ const staleTokenSecrets = ["HF_TOKEN", "HUGGINGFACE_HUB_TOKEN"].filter((key) => secrets.has(key));
16697
+ if (staleTokenSecrets.length > 0) {
16698
+ const model = variables.get("OPENCLAW_MODEL")?.value ?? DEFAULT_MODEL;
16699
+ const canDelete = canDeleteBroadTokenSecrets({
16700
+ model,
16701
+ routerTokenPresent: hasBrokerOrRouterTokenSecretMap(secrets)
16702
+ });
16703
+ if (fix && canDelete) {
16704
+ await deleteStaleSpaceTokenSecrets(hub, repoId);
16705
+ fixed.push(`deleted stale secret${staleTokenSecrets.length === 1 ? "" : "s"} ${staleTokenSecrets.join(", ")}`);
16706
+ } else if (fix) {
16707
+ issues.push(`stale broad Hub token secret${staleTokenSecrets.length === 1 ? "" : "s"} present: ${staleTokenSecrets.join(", ")}; add MLCLAW_BROKER_HF_TOKEN before removing`);
16708
+ } else {
16709
+ issues.push(`stale broad Hub token secret${staleTokenSecrets.length === 1 ? "" : "s"} present: ${staleTokenSecrets.join(", ")}`);
16710
+ }
16469
16711
  }
16470
16712
  if (!secrets.has("MLCLAW_SESSION_SECRET")) {
16471
16713
  if (fix) {
@@ -16475,6 +16717,16 @@ async function doctor(repoId, opts, hub, runtime) {
16475
16717
  issues.push("secret MLCLAW_SESSION_SECRET is missing");
16476
16718
  }
16477
16719
  }
16720
+ if (!secrets.has("MLCLAW_CREDENTIAL_KEY")) {
16721
+ if (fix) {
16722
+ const agent = variables.get("OPENCLAW_AGENT_NAME")?.value?.trim() || repoId.split("/")[1] || "openclaw";
16723
+ const credentialKey = await manifestExists(runtime.configRoot, agent) ? requiredSecret(await ensureDeploymentCredentialKey(runtime, agent), "MLCLAW_CREDENTIAL_KEY") : randomBytes(32).toString("base64url");
16724
+ await hub.addSpaceSecret(repoId, "MLCLAW_CREDENTIAL_KEY", credentialKey);
16725
+ fixed.push("set secret MLCLAW_CREDENTIAL_KEY");
16726
+ } else {
16727
+ issues.push("secret MLCLAW_CREDENTIAL_KEY is missing");
16728
+ }
16729
+ }
16478
16730
  if (!variables.has("MLCLAW_TEMPLATE_REV") && !variables.has("OPENCLAW_HF_TEMPLATE_REV")) {
16479
16731
  issues.push("MLCLAW_TEMPLATE_REV is missing; updates cannot verify template lineage");
16480
16732
  }
@@ -16514,6 +16766,14 @@ async function doctor(repoId, opts, hub, runtime) {
16514
16766
  await hub.assertBucketAccessible(bucket);
16515
16767
  }
16516
16768
  const runtimeInfo = await hub.getSpaceRuntime(repoId);
16769
+ if (bucket && !hasStateVolume(runtimeInfo.volumes, bucket)) {
16770
+ if (fix) {
16771
+ await hub.setSpaceVolumes(repoId, mergeStateVolume(requireRuntimeVolumes(runtimeInfo, repoId), bucket));
16772
+ fixed.push(`mounted bucket ${bucket} at ${SPACE_STATE_MOUNT_DIR}`);
16773
+ } else {
16774
+ issues.push(`bucket ${bucket} is not mounted read-write at ${SPACE_STATE_MOUNT_DIR}`);
16775
+ }
16776
+ }
16517
16777
  let logs = "";
16518
16778
  try {
16519
16779
  logs = await hub.fetchSpaceLogs(repoId, "run");
@@ -16614,6 +16874,79 @@ async function setDeploymentSecrets(hub, repoId, secrets) {
16614
16874
  await hub.addSpaceSecret(repoId, key, value);
16615
16875
  }
16616
16876
  }
16877
+ async function deleteStaleSpaceTokenSecrets(hub, repoId) {
16878
+ await Promise.all([
16879
+ hub.deleteSpaceSecret(repoId, "HF_TOKEN"),
16880
+ hub.deleteSpaceSecret(repoId, "HUGGINGFACE_HUB_TOKEN")
16881
+ ]);
16882
+ }
16883
+ function canDeleteBroadTokenSecrets(params) {
16884
+ return params.routerTokenPresent || !isHuggingFaceRouterModel(params.model);
16885
+ }
16886
+ function hasRouterTokenSecretRecord(secrets) {
16887
+ return Boolean(secrets.MLCLAW_ROUTER_TOKEN || secrets.HF_ROUTER_TOKEN);
16888
+ }
16889
+ function hasRouterTokenSecretMap(secrets) {
16890
+ return secrets.has("MLCLAW_ROUTER_TOKEN") || secrets.has("HF_ROUTER_TOKEN");
16891
+ }
16892
+ function hasBrokerOrRouterTokenSecretRecord(secrets) {
16893
+ return Boolean(secrets.MLCLAW_BROKER_HF_TOKEN) || hasRouterTokenSecretRecord(secrets);
16894
+ }
16895
+ function hasBrokerOrRouterTokenSecretMap(secrets) {
16896
+ return secrets.has("MLCLAW_BROKER_HF_TOKEN") || hasRouterTokenSecretMap(secrets);
16897
+ }
16898
+ function assertDedicatedRouterToken(model, secrets) {
16899
+ if (isHuggingFaceRouterModel(model) && !hasBrokerOrRouterTokenSecretRecord(secrets)) {
16900
+ throw new Error(
16901
+ "Hugging Face Router models require MLCLAW_BROKER_HF_TOKEN or a dedicated inference token"
16902
+ );
16903
+ }
16904
+ }
16905
+ async function ensureSpaceStateVolume(hub, repoId, bucket, opts = {}) {
16906
+ const runtime = await hub.getSpaceRuntime(repoId);
16907
+ const volumes = Array.isArray(runtime.volumes) ? runtime.volumes : opts.allowMissingVolumes ? [] : requireRuntimeVolumes(runtime, repoId);
16908
+ await hub.setSpaceVolumes(repoId, mergeStateVolume(volumes, bucket));
16909
+ }
16910
+ function requireRuntimeVolumes(runtime, repoId) {
16911
+ if (!Array.isArray(runtime.volumes)) {
16912
+ throw new Error(`Space runtime metadata for ${repoId} did not include volumes; refusing to replace mounts`);
16913
+ }
16914
+ return runtime.volumes;
16915
+ }
16916
+ function mergeStateVolume(existing, bucket) {
16917
+ return [
16918
+ ...existing.filter((volume) => volumeMountPath(volume) !== SPACE_STATE_MOUNT_DIR).map(normalizeSpaceVolume),
16919
+ {
16920
+ type: "bucket",
16921
+ source: bucket,
16922
+ mountPath: SPACE_STATE_MOUNT_DIR,
16923
+ readOnly: false
16924
+ }
16925
+ ];
16926
+ }
16927
+ function hasStateVolume(volumes, bucket) {
16928
+ return Boolean(volumes?.some(
16929
+ (volume) => volume.type === "bucket" && volume.source === bucket && volumeMountPath(volume) === SPACE_STATE_MOUNT_DIR && volumeReadOnly(volume) !== true
16930
+ ));
16931
+ }
16932
+ function normalizeSpaceVolume(volume) {
16933
+ const normalized = { ...volume };
16934
+ const mountPath = volumeMountPath(volume);
16935
+ if (mountPath) {
16936
+ normalized.mountPath = mountPath;
16937
+ }
16938
+ const readOnly = volumeReadOnly(volume);
16939
+ if (typeof readOnly === "boolean") {
16940
+ normalized.readOnly = readOnly;
16941
+ }
16942
+ return normalized;
16943
+ }
16944
+ function volumeMountPath(volume) {
16945
+ return volume.mountPath ?? volume.mount_path;
16946
+ }
16947
+ function volumeReadOnly(volume) {
16948
+ return volume.readOnly ?? volume.read_only;
16949
+ }
16617
16950
  async function clearSpaceGatewayDisabled(hub, repoId) {
16618
16951
  try {
16619
16952
  await hub.deleteSpaceVariable(repoId, "MLCLAW_GATEWAY_DISABLED");
@@ -16636,6 +16969,29 @@ async function readOptionalTelegramToken(opts, runtime) {
16636
16969
  }
16637
16970
  return void 0;
16638
16971
  }
16972
+ async function resolveRouterToken(params) {
16973
+ const explicit = nonEmpty(params.opts.routerToken) ?? await readOptionalRouterTokenFile(params.opts.routerTokenFile);
16974
+ const direct = explicit ?? params.runtime.env.MLCLAW_ROUTER_TOKEN ?? params.runtime.env.HF_ROUTER_TOKEN ?? params.existingSecrets?.MLCLAW_ROUTER_TOKEN ?? params.existingSecrets?.HF_ROUTER_TOKEN;
16975
+ const existing = nonEmpty(direct);
16976
+ if (existing) {
16977
+ return existing;
16978
+ }
16979
+ if (!isHuggingFaceRouterModel(params.model)) {
16980
+ return void 0;
16981
+ }
16982
+ return void 0;
16983
+ }
16984
+ async function readOptionalRouterTokenFile(file) {
16985
+ if (!file) {
16986
+ return void 0;
16987
+ }
16988
+ const raw = await fs14.readFile(file, "utf8");
16989
+ const parsed = parseSecretEnv(raw);
16990
+ return nonEmpty(parsed.MLCLAW_ROUTER_TOKEN) ?? nonEmpty(parsed.HF_ROUTER_TOKEN) ?? nonEmpty(raw);
16991
+ }
16992
+ function isHuggingFaceRouterModel(model) {
16993
+ return model.trim().startsWith("huggingface/");
16994
+ }
16639
16995
  async function promptAgentName(runtime) {
16640
16996
  if (!runtime.prompt.isInteractive()) {
16641
16997
  return "mlclaw";
@@ -16648,21 +17004,26 @@ async function promptAgentName(runtime) {
16648
17004
  return readPromptValue(value, "Agent name");
16649
17005
  }
16650
17006
  async function resolveHardware(params) {
16651
- const hardware = params.requestedHardware;
16652
- const sleepTime = params.requestedSleepTime ?? TELEGRAM_SLEEP_TIME;
17007
+ const hardware = params.requestedHardware ?? (params.requiresMessagingEgress ? TELEGRAM_HARDWARE : void 0);
17008
+ if (!hardware) {
17009
+ const label = params.defaultLabel ?? "default free CPU";
17010
+ return typeof params.requestedSleepTime === "number" ? { kind: "default", label, sleepTime: params.requestedSleepTime } : { kind: "default", label };
17011
+ }
17012
+ const sleepTime = isPaidHardware(hardware) ? params.requestedSleepTime ?? TELEGRAM_SLEEP_TIME : params.requestedSleepTime;
16653
17013
  if (params.requiresMessagingEgress && !isPaidHardware(hardware)) {
16654
17014
  throw new Error(`Telegram requires upgraded paid Space hardware today; use --hardware ${TELEGRAM_HARDWARE} or --gateway local`);
16655
17015
  }
16656
17016
  if (isPaidHardware(hardware)) {
17017
+ const paidSleepTime = params.requestedSleepTime ?? TELEGRAM_SLEEP_TIME;
16657
17018
  await confirmPaidHardware({
16658
17019
  hardware,
16659
- sleepTime,
17020
+ sleepTime: paidSleepTime,
16660
17021
  yes: params.yes,
16661
17022
  runtime: params.runtime
16662
17023
  });
16663
- return { hardware, sleepTime };
17024
+ return { kind: "explicit", hardware, label: hardware, sleepTime: paidSleepTime };
16664
17025
  }
16665
- return typeof params.requestedSleepTime === "number" ? { hardware, sleepTime: params.requestedSleepTime } : { hardware };
17026
+ return typeof sleepTime === "number" ? { kind: "explicit", hardware, label: hardware, sleepTime } : { kind: "explicit", hardware, label: hardware };
16666
17027
  }
16667
17028
  async function confirmPaidHardware(params) {
16668
17029
  if (params.yes) {
@@ -16746,8 +17107,11 @@ export {
16746
17107
  LOCAL_VOLUME_MOUNT_PATH,
16747
17108
  SPACE_HANDOFF_POLL_MS,
16748
17109
  SPACE_HANDOFF_TIMEOUT_MS,
17110
+ SPACE_LIVE_DIR,
17111
+ SPACE_STATE_MOUNT_DIR,
16749
17112
  TELEGRAM_HARDWARE,
16750
17113
  TELEGRAM_SLEEP_TIME,
16751
17114
  createProgram,
16752
- main
17115
+ main,
17116
+ mergeStateVolume
16753
17117
  };