@blockrun/clawrouter 0.12.281 → 0.12.283

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/cli.js CHANGED
@@ -32315,6 +32315,11 @@ function getSharedSpendControl() {
32315
32315
  host.__clawrouterSharedSpendControl ??= new SpendControl();
32316
32316
  return host.__clawrouterSharedSpendControl;
32317
32317
  }
32318
+ function parseQuotedAmountMicros(selected) {
32319
+ const raw = selected.amount ?? selected.maxAmountRequired;
32320
+ if (typeof raw !== "string" || !CANONICAL_AMOUNT.test(raw)) return void 0;
32321
+ return BigInt(raw);
32322
+ }
32318
32323
  function parseQuotedAmountUsd(selected) {
32319
32324
  const raw = selected.amount ?? selected.maxAmountRequired;
32320
32325
  if (typeof raw !== "string" || !CANONICAL_AMOUNT.test(raw)) {
@@ -33080,6 +33085,16 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
33080
33085
  return est === void 0 ? void 0 : Number(est);
33081
33086
  };
33082
33087
  const needMicros = estimateMicros();
33088
+ const notifyDeclinedPayment = () => {
33089
+ if (!options?.onPaymentDeclined) return;
33090
+ try {
33091
+ options.onPaymentDeclined();
33092
+ } catch (error) {
33093
+ console.error(
33094
+ `[ClawRouter] onPaymentDeclined callback failed: ${error instanceof Error ? error.message : String(error)}`
33095
+ );
33096
+ }
33097
+ };
33083
33098
  const cached = !options?.skipPreAuth ? cache2.get(cacheKey2) : void 0;
33084
33099
  const preAuthCovers = cached !== void 0 && Date.now() - cached.cachedAt < ttlMs && cached.coverMicros !== void 0 && needMicros !== void 0 && needMicros <= cached.coverMicros;
33085
33100
  if (preAuthCovers) {
@@ -33098,6 +33113,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
33098
33113
  return response2;
33099
33114
  }
33100
33115
  paymentInFlight = false;
33116
+ notifyDeclinedPayment();
33101
33117
  cache2.delete(cacheKey2);
33102
33118
  } catch (err) {
33103
33119
  cache2.delete(cacheKey2);
@@ -33142,6 +33158,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
33142
33158
  clonedRequest.headers.set(key3, value);
33143
33159
  }
33144
33160
  const paidResponse = await baseFetch(clonedRequest);
33161
+ if (paidResponse.status === 402) notifyDeclinedPayment();
33145
33162
  notifyAcceptedPayment(paidResponse, payload);
33146
33163
  return paidResponse;
33147
33164
  };
@@ -64789,6 +64806,132 @@ var init_api_key = __esm({
64789
64806
  }
64790
64807
  });
64791
64808
 
64809
+ // src/session-spend.ts
64810
+ function parseMicros(raw) {
64811
+ if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
64812
+ return BigInt(raw);
64813
+ }
64814
+ function usdToMicros(usd2) {
64815
+ return BigInt(Math.round(usd2 * 1e6));
64816
+ }
64817
+ function microsToUsd(micros) {
64818
+ return Number(micros) / 1e6;
64819
+ }
64820
+ var SessionSpend, CostCapError;
64821
+ var init_session_spend = __esm({
64822
+ "src/session-spend.ts"() {
64823
+ "use strict";
64824
+ init_spend_control();
64825
+ SessionSpend = class _SessionSpend {
64826
+ constructor(store, sessionId, capMicros, holdMicros) {
64827
+ this.store = store;
64828
+ this.sessionId = sessionId;
64829
+ this.capMicros = capMicros;
64830
+ this.heldMicros = holdMicros;
64831
+ if (holdMicros > 0n) store.adjustSessionHold(sessionId, holdMicros);
64832
+ }
64833
+ store;
64834
+ sessionId;
64835
+ capMicros;
64836
+ /** Budget this request still holds against the session (strict mode only). */
64837
+ heldMicros;
64838
+ /** Sum of payments signed by this request that still count. */
64839
+ signedMicros = 0n;
64840
+ /** The most recent signature not yet settled or voided — the one a decline undoes. */
64841
+ lastSignedMicros = 0n;
64842
+ released = false;
64843
+ /**
64844
+ * Strict mode: admit the request only if the session's spend, plus what its
64845
+ * other in-flight requests hold, plus this request's estimate fits the cap —
64846
+ * and hold the estimate in the same step.
64847
+ */
64848
+ static admit(store, sessionId, capMicros, estimateMicros) {
64849
+ const spentMicros = store.getSessionCostMicros(sessionId);
64850
+ const heldMicros = store.getSessionHoldMicros(sessionId);
64851
+ if (spentMicros + heldMicros + estimateMicros > capMicros) {
64852
+ return { admitted: false, spentMicros, heldMicros, estimateMicros };
64853
+ }
64854
+ return {
64855
+ admitted: true,
64856
+ spend: new _SessionSpend(store, sessionId, capMicros, estimateMicros)
64857
+ };
64858
+ }
64859
+ /** Graceful mode: track signed spend, enforce nothing. */
64860
+ static track(store, sessionId) {
64861
+ return new _SessionSpend(store, sessionId, void 0, 0n);
64862
+ }
64863
+ get isStrict() {
64864
+ return this.capMicros !== void 0;
64865
+ }
64866
+ /**
64867
+ * Before a payment is created: may this request sign `quoteMicros`?
64868
+ * Spend already recorded (including this request's own earlier signatures)
64869
+ * plus what OTHER in-flight requests hold, plus the quote, must fit the cap.
64870
+ * Always true in graceful mode.
64871
+ */
64872
+ coverPayment(quoteMicros) {
64873
+ if (this.capMicros === void 0) return true;
64874
+ const spent = this.store.getSessionCostMicros(this.sessionId);
64875
+ const heldByOthers = this.store.getSessionHoldMicros(this.sessionId) - this.heldMicros;
64876
+ if (spent + heldByOthers + quoteMicros > this.capMicros) return false;
64877
+ if (quoteMicros > this.heldMicros) {
64878
+ this.store.adjustSessionHold(this.sessionId, quoteMicros - this.heldMicros);
64879
+ this.heldMicros = quoteMicros;
64880
+ }
64881
+ return true;
64882
+ }
64883
+ /** A payment was signed: it is spend now, and replaces part of the hold. */
64884
+ recordSigned(amountMicros) {
64885
+ if (amountMicros <= 0n) return;
64886
+ this.signedMicros += amountMicros;
64887
+ this.lastSignedMicros = amountMicros;
64888
+ this.store.addSessionCost(this.sessionId, amountMicros);
64889
+ this.shrinkHold(amountMicros);
64890
+ }
64891
+ /**
64892
+ * The gateway answered the payment just signed with a 402: nothing settled.
64893
+ * Payments within one request are signed and sent one at a time, so the
64894
+ * decline always belongs to the latest signature. Undone at most once.
64895
+ */
64896
+ voidLastSigned() {
64897
+ const undo = this.lastSignedMicros;
64898
+ this.lastSignedMicros = 0n;
64899
+ if (undo <= 0n) return;
64900
+ this.signedMicros -= undo;
64901
+ this.store.addSessionCost(this.sessionId, -undo);
64902
+ }
64903
+ /**
64904
+ * An attempt succeeded. When nothing was signed for this request (the API
64905
+ * key rail), the local estimate is the only record of the spend. When a
64906
+ * payment was signed, it is already recorded at its real amount.
64907
+ */
64908
+ recordSuccess(estimateMicros) {
64909
+ if (this.signedMicros > 0n || estimateMicros <= 0n) return;
64910
+ this.store.addSessionCost(this.sessionId, estimateMicros);
64911
+ this.shrinkHold(estimateMicros);
64912
+ }
64913
+ /** The request is over: return whatever it still holds. Idempotent. */
64914
+ release() {
64915
+ if (this.released) return;
64916
+ this.released = true;
64917
+ this.shrinkHold(this.heldMicros);
64918
+ }
64919
+ shrinkHold(byMicros) {
64920
+ const drop = byMicros < this.heldMicros ? byMicros : this.heldMicros;
64921
+ if (drop <= 0n) return;
64922
+ this.heldMicros -= drop;
64923
+ this.store.adjustSessionHold(this.sessionId, -drop);
64924
+ }
64925
+ };
64926
+ CostCapError = class extends SpendPolicyError {
64927
+ constructor(reason) {
64928
+ super(reason);
64929
+ this.name = "CostCapError";
64930
+ }
64931
+ };
64932
+ }
64933
+ });
64934
+
64792
64935
  // src/compression/types.ts
64793
64936
  var DEFAULT_COMPRESSION_CONFIG;
64794
64937
  var init_types2 = __esm({
@@ -65636,6 +65779,9 @@ var init_session = __esm({
65636
65779
  };
65637
65780
  SessionStore = class {
65638
65781
  sessions = /* @__PURE__ */ new Map();
65782
+ // Kept apart from `sessions`: a hold belongs to requests still running, so it
65783
+ // must not vanish when the idle-session sweep drops an entry mid-request.
65784
+ sessionHolds = /* @__PURE__ */ new Map();
65639
65785
  config;
65640
65786
  cleanupInterval = null;
65641
65787
  constructor(config = {}) {
@@ -65811,14 +65957,34 @@ var init_session = __esm({
65811
65957
  this.sessions.set(sessionId, entry);
65812
65958
  }
65813
65959
  entry.sessionCostMicros += additionalMicros;
65960
+ if (entry.sessionCostMicros < 0n) entry.sessionCostMicros = 0n;
65814
65961
  }
65815
65962
  /**
65816
65963
  * Get the total accumulated cost for a session in USD.
65817
65964
  */
65818
65965
  getSessionCostUsd(sessionId) {
65819
- const entry = this.sessions.get(sessionId);
65820
- if (!entry) return 0;
65821
- return Number(entry.sessionCostMicros) / 1e6;
65966
+ return Number(this.getSessionCostMicros(sessionId)) / 1e6;
65967
+ }
65968
+ /**
65969
+ * Get the total accumulated cost for a session in USDC micros.
65970
+ */
65971
+ getSessionCostMicros(sessionId) {
65972
+ return this.sessions.get(sessionId)?.sessionCostMicros ?? 0n;
65973
+ }
65974
+ /**
65975
+ * Budget held by a session's in-flight requests (USDC micros) — admitted
65976
+ * against the strict cap but not yet spent. See session-spend.ts.
65977
+ */
65978
+ getSessionHoldMicros(sessionId) {
65979
+ return this.sessionHolds.get(sessionId) ?? 0n;
65980
+ }
65981
+ /**
65982
+ * Grow (positive) or release (negative) a session's in-flight hold.
65983
+ */
65984
+ adjustSessionHold(sessionId, deltaMicros) {
65985
+ const next = (this.sessionHolds.get(sessionId) ?? 0n) + deltaMicros;
65986
+ if (next > 0n) this.sessionHolds.set(sessionId, next);
65987
+ else this.sessionHolds.delete(sessionId);
65822
65988
  }
65823
65989
  /**
65824
65990
  * Stop the cleanup interval.
@@ -66178,6 +66344,156 @@ ${lines.join("\n")}`;
66178
66344
  }
66179
66345
  });
66180
66346
 
66347
+ // src/local-guard.ts
66348
+ import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2, realpathSync } from "fs";
66349
+ import { homedir as homedir7 } from "os";
66350
+ import { join as join10 } from "path";
66351
+ function isLocalHostname(hostname) {
66352
+ const h = hostname.toLowerCase().replace(/\.$/, "");
66353
+ return h === "localhost" || h === "127.0.0.1" || h === "[::1]" || h === "::1";
66354
+ }
66355
+ function untrustedRequestReason(req) {
66356
+ const host = req.headers.host;
66357
+ if (host) {
66358
+ let hostname;
66359
+ try {
66360
+ hostname = new URL(`http://${host}`).hostname;
66361
+ } catch {
66362
+ return "malformed Host header";
66363
+ }
66364
+ if (!isLocalHostname(hostname)) return `non-local Host header (${hostname})`;
66365
+ }
66366
+ const origin2 = req.headers.origin;
66367
+ if (Array.isArray(origin2)) return "multiple Origin headers";
66368
+ if (origin2 !== void 0) {
66369
+ if (origin2 === "null") return "opaque Origin";
66370
+ let o3;
66371
+ try {
66372
+ o3 = new URL(origin2);
66373
+ } catch {
66374
+ return "malformed Origin header";
66375
+ }
66376
+ if (o3.protocol !== "http:" && o3.protocol !== "https:" || !isLocalHostname(o3.hostname)) {
66377
+ return `cross-site Origin (${origin2})`;
66378
+ }
66379
+ }
66380
+ const site = req.headers["sec-fetch-site"];
66381
+ if (site === "cross-site") return "cross-site browser request";
66382
+ return null;
66383
+ }
66384
+ function v4FromHex(hi, lo) {
66385
+ return `${hi >>> 8 & 255}.${hi & 255}.${lo >>> 8 & 255}.${lo & 255}`;
66386
+ }
66387
+ function isBlockedSsrfHost(hostname) {
66388
+ const h = hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "").replace(/\.$/, "");
66389
+ if (!h) return true;
66390
+ if (h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local")) return true;
66391
+ if (h.endsWith(".internal")) return true;
66392
+ if (METADATA_HOSTS.has(h)) return true;
66393
+ if (h.includes(":")) {
66394
+ if (h === "::1" || h === "::") return true;
66395
+ if (h.startsWith("fe80:") || h.startsWith("fc") || h.startsWith("fd")) return true;
66396
+ const sixToFour = h.match(/^2002:([0-9a-f]{1,4}):([0-9a-f]{1,4}):/);
66397
+ if (sixToFour) {
66398
+ return isBlockedSsrfHost(v4FromHex(parseInt(sixToFour[1], 16), parseInt(sixToFour[2], 16)));
66399
+ }
66400
+ const embedded = h.match(/^64:ff9b::(.+)$/) ?? h.match(/::ffff:(.+)$/);
66401
+ if (embedded) {
66402
+ const tail = embedded[1];
66403
+ if (tail.includes(".")) return isBlockedSsrfHost(tail);
66404
+ const seg = tail.split(":");
66405
+ if (seg.length === 2)
66406
+ return isBlockedSsrfHost(v4FromHex(parseInt(seg[0], 16), parseInt(seg[1], 16)));
66407
+ }
66408
+ return false;
66409
+ }
66410
+ const m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
66411
+ if (m) {
66412
+ const a = Number(m[1]);
66413
+ const b = Number(m[2]);
66414
+ const c = Number(m[3]);
66415
+ if (a === 127 || a === 0 || a === 10) return true;
66416
+ if (a === 169 && b === 254) return true;
66417
+ if (a === 172 && b >= 16 && b <= 31) return true;
66418
+ if (a === 192 && b === 168) return true;
66419
+ if (a === 100 && b >= 64 && b <= 127) return true;
66420
+ if (a === 192 && b === 0 && c === 0) return true;
66421
+ }
66422
+ return false;
66423
+ }
66424
+ async function ssrfSafeFetch(url2, init2 = {}, maxHops = 5) {
66425
+ const { allowPrivate, ...fetchInit } = init2;
66426
+ let current = url2;
66427
+ for (let hop = 0; hop <= maxHops; hop++) {
66428
+ const u = new URL(current);
66429
+ if (u.protocol !== "http:" && u.protocol !== "https:") {
66430
+ throw new Error(`refusing to fetch non-http(s) URL (${u.protocol})`);
66431
+ }
66432
+ if (!allowPrivate && isBlockedSsrfHost(u.hostname)) {
66433
+ throw new Error(`refusing to fetch a private/loopback/metadata address: ${u.hostname}`);
66434
+ }
66435
+ const res = await fetch(current, { ...fetchInit, redirect: "manual" });
66436
+ if (res.status >= 300 && res.status < 400) {
66437
+ const loc = res.headers.get("location");
66438
+ if (!loc) return res;
66439
+ current = new URL(loc, current).href;
66440
+ continue;
66441
+ }
66442
+ return res;
66443
+ }
66444
+ throw new Error("too many redirects");
66445
+ }
66446
+ function readLocalImageAsDataUri(filePath) {
66447
+ const expanded = filePath.startsWith("~/") ? join10(homedir7(), filePath.slice(2)) : filePath;
66448
+ const refuse = () => new Error(`Not a readable PNG, JPEG or WebP image: ${filePath}`);
66449
+ let fd;
66450
+ try {
66451
+ fd = openSync2(realpathSync(expanded), "r");
66452
+ } catch {
66453
+ throw refuse();
66454
+ }
66455
+ try {
66456
+ const st = fstatSync2(fd);
66457
+ if (!st.isFile() || st.size === 0 || st.size > MAX_LOCAL_IMAGE_BYTES) throw refuse();
66458
+ const head = Buffer.alloc(12);
66459
+ readSync2(fd, head, 0, 12, 0);
66460
+ const sig = IMAGE_SIGNATURES.find((s3) => s3.test(head));
66461
+ if (!sig) throw refuse();
66462
+ const data = Buffer.alloc(st.size);
66463
+ let off = 0;
66464
+ while (off < st.size) {
66465
+ const n = readSync2(fd, data, off, st.size - off, off);
66466
+ if (n === 0) break;
66467
+ off += n;
66468
+ }
66469
+ return `data:${sig.mime};base64,${data.subarray(0, off).toString("base64")}`;
66470
+ } finally {
66471
+ closeSync2(fd);
66472
+ }
66473
+ }
66474
+ var METADATA_HOSTS, MAX_LOCAL_IMAGE_BYTES, IMAGE_SIGNATURES;
66475
+ var init_local_guard = __esm({
66476
+ "src/local-guard.ts"() {
66477
+ "use strict";
66478
+ METADATA_HOSTS = /* @__PURE__ */ new Set(["metadata", "metadata.goog", "instance-data"]);
66479
+ MAX_LOCAL_IMAGE_BYTES = 25 * 1024 * 1024;
66480
+ IMAGE_SIGNATURES = [
66481
+ {
66482
+ mime: "image/png",
66483
+ test: (b) => b.length >= 8 && b.readUInt32BE(0) === 2303741511 && b.readUInt32BE(4) === 218765834
66484
+ },
66485
+ {
66486
+ mime: "image/jpeg",
66487
+ test: (b) => b.length >= 3 && b[0] === 255 && b[1] === 216 && b[2] === 255
66488
+ },
66489
+ {
66490
+ mime: "image/webp",
66491
+ test: (b) => b.length >= 12 && b.toString("ascii", 0, 4) === "RIFF" && b.toString("ascii", 8, 12) === "WEBP"
66492
+ }
66493
+ ];
66494
+ }
66495
+ });
66496
+
66181
66497
  // node_modules/undici/lib/core/symbols.js
66182
66498
  var require_symbols = __commonJS({
66183
66499
  "node_modules/undici/lib/core/symbols.js"(exports, module) {
@@ -93571,8 +93887,8 @@ var init_textual_tool_calls = __esm({
93571
93887
 
93572
93888
  // src/response-store.ts
93573
93889
  import { appendFile as appendFile2, mkdir as mkdir5, readFile, readdir as readdir2 } from "fs/promises";
93574
- import { homedir as homedir7 } from "os";
93575
- import { join as join10 } from "path";
93890
+ import { homedir as homedir8 } from "os";
93891
+ import { join as join11 } from "path";
93576
93892
  import { randomBytes as randomBytes7 } from "crypto";
93577
93893
  function isEnabled() {
93578
93894
  const v = process.env.BLOCKRUN_RESPONSE_STORE;
@@ -93585,7 +93901,7 @@ async function ensureDir2() {
93585
93901
  }
93586
93902
  function dailyFile(date = /* @__PURE__ */ new Date()) {
93587
93903
  const iso = date.toISOString().slice(0, 10);
93588
- return join10(STORE_DIR, `responses-${iso}.jsonl`);
93904
+ return join11(STORE_DIR, `responses-${iso}.jsonl`);
93589
93905
  }
93590
93906
  function genId() {
93591
93907
  return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
@@ -93635,7 +93951,7 @@ async function listRecent(limit = 20, daysBack = 7) {
93635
93951
  d.setUTCDate(d.getUTCDate() - i);
93636
93952
  const iso = d.toISOString().slice(0, 10);
93637
93953
  const name = `responses-${iso}.jsonl`;
93638
- if (files.includes(name)) candidateFiles.push(join10(STORE_DIR, name));
93954
+ if (files.includes(name)) candidateFiles.push(join11(STORE_DIR, name));
93639
93955
  }
93640
93956
  const all3 = [];
93641
93957
  for (const f of candidateFiles) {
@@ -93664,7 +93980,7 @@ var STORE_DIR, dirReady2;
93664
93980
  var init_response_store = __esm({
93665
93981
  "src/response-store.ts"() {
93666
93982
  "use strict";
93667
- STORE_DIR = join10(homedir7(), ".openclaw", "blockrun", "responses");
93983
+ STORE_DIR = join11(homedir8(), ".openclaw", "blockrun", "responses");
93668
93984
  dirReady2 = false;
93669
93985
  }
93670
93986
  });
@@ -95301,10 +95617,10 @@ import { createHmac } from "crypto";
95301
95617
  import { createServer } from "http";
95302
95618
  import { finished, Readable } from "stream";
95303
95619
  import { pipeline } from "stream/promises";
95304
- import { homedir as homedir8 } from "os";
95305
- import { join as join11 } from "path";
95620
+ import { homedir as homedir9 } from "os";
95621
+ import { join as join12 } from "path";
95306
95622
  import { mkdir as mkdir6, writeFile as writeFile4, readFile as readFile2, stat as fsStat } from "fs/promises";
95307
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
95623
+ import { readFileSync as readFileSync2 } from "fs";
95308
95624
  async function loadGatewayCatalog(apiBase, apiKey) {
95309
95625
  try {
95310
95626
  const controller = new AbortController();
@@ -96198,22 +96514,6 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
96198
96514
  }).catch(() => {
96199
96515
  });
96200
96516
  }
96201
- function readImageFileAsDataUri(filePath) {
96202
- const resolved = filePath.startsWith("~/") ? join11(homedir8(), filePath.slice(2)) : filePath;
96203
- if (!existsSync2(resolved)) {
96204
- throw new Error(`Image file not found: ${resolved}`);
96205
- }
96206
- const ext = resolved.split(".").pop()?.toLowerCase() ?? "png";
96207
- const mimeMap = {
96208
- png: "image/png",
96209
- jpg: "image/jpeg",
96210
- jpeg: "image/jpeg",
96211
- webp: "image/webp"
96212
- };
96213
- const mime = mimeMap[ext] ?? "image/png";
96214
- const data = readFileSync2(resolved);
96215
- return `data:${mime};base64,${data.toString("base64")}`;
96216
- }
96217
96517
  async function uploadDataUriToHost(dataUri) {
96218
96518
  const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
96219
96519
  if (!match) throw new Error("Invalid data URI format");
@@ -96387,6 +96687,19 @@ async function startProxy(options) {
96387
96687
  }
96388
96688
  const account = walletKey ? privateKeyToAccount(walletKey) : void 0;
96389
96689
  const x402 = authMode === "wallet" ? new x402Client() : void 0;
96690
+ x402?.onBeforePaymentCreation(async (context) => {
96691
+ const spend = paymentStore.getStore()?.sessionSpend;
96692
+ if (!spend?.isStrict) return;
96693
+ const quote = parseQuotedAmountMicros(context.selectedRequirements);
96694
+ if (quote === void 0) {
96695
+ throw new CostCapError("ClawRouter cost cap: unreadable payment amount in the 402 quote");
96696
+ }
96697
+ if (!spend.coverPayment(quote)) {
96698
+ throw new CostCapError(
96699
+ `ClawRouter cost cap exceeded: signing $${microsToUsd(quote).toFixed(6)} would push this session past its maxCostPerRun limit`
96700
+ );
96701
+ }
96702
+ });
96390
96703
  if (x402 && account) {
96391
96704
  const evmPublicClient = createPublicClient({ chain: base, transport: http() });
96392
96705
  const evmSigner = toClientEvmSigner(account, evmPublicClient);
@@ -96429,6 +96742,11 @@ async function startProxy(options) {
96429
96742
  if (store) store.amountUsd = amountUsd;
96430
96743
  console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
96431
96744
  });
96745
+ x402?.onAfterPaymentCreation(async (context) => {
96746
+ const spend = paymentStore.getStore()?.sessionSpend;
96747
+ const amount = parseQuotedAmountMicros(context.selectedRequirements);
96748
+ if (spend && amount !== void 0) spend.recordSigned(amount);
96749
+ });
96432
96750
  const payFetch = authMode === "api-key" ? withSpendPolicy(
96433
96751
  createApiKeyFetch(apiKey, fetch, apiBase),
96434
96752
  options.spendControl ?? getSharedSpendControl(),
@@ -96447,7 +96765,10 @@ async function startProxy(options) {
96447
96765
  // Only the wallet rail settles per call. The API-key rail is billed
96448
96766
  // server-side against account credit, so there is no per-request
96449
96767
  // settlement for an observer to report.
96450
- onPayment: options.onPayment
96768
+ onPayment: options.onPayment,
96769
+ // A 402 answer to a signed payment means the gateway declined it and
96770
+ // nothing settled — take it back off the session's budget.
96771
+ onPaymentDeclined: () => paymentStore.getStore()?.sessionSpend?.voidLastSigned()
96451
96772
  });
96452
96773
  let balanceMonitor;
96453
96774
  if (options._balanceMonitorOverride) {
@@ -96490,6 +96811,13 @@ async function startProxy(options) {
96490
96811
  console.error(`[ClawRouter] Request finished with error: ${err.message}`);
96491
96812
  }
96492
96813
  });
96814
+ const untrusted = untrustedRequestReason(req);
96815
+ if (untrusted) {
96816
+ console.warn(`[ClawRouter] Refused request to ${req.url}: ${untrusted}`);
96817
+ res.writeHead(403, { "Content-Type": "application/json" });
96818
+ res.end(JSON.stringify({ error: "forbidden", details: untrusted }));
96819
+ return;
96820
+ }
96493
96821
  if (req.url === "/health" || req.url?.startsWith("/health?")) {
96494
96822
  const url2 = new URL(req.url, "http://localhost");
96495
96823
  const full = url2.searchParams.get("full") === "true";
@@ -96718,7 +97046,7 @@ async function startProxy(options) {
96718
97046
  res.end("Bad request");
96719
97047
  return;
96720
97048
  }
96721
- const filePath = join11(IMAGE_DIR, filename);
97049
+ const filePath = join12(IMAGE_DIR, filename);
96722
97050
  try {
96723
97051
  const s3 = await fsStat(filePath);
96724
97052
  if (!s3.isFile()) throw new Error("not a file");
@@ -96749,7 +97077,7 @@ async function startProxy(options) {
96749
97077
  res.end("Bad request");
96750
97078
  return;
96751
97079
  }
96752
- const filePath = join11(AUDIO_DIR, filename);
97080
+ const filePath = join12(AUDIO_DIR, filename);
96753
97081
  try {
96754
97082
  const s3 = await fsStat(filePath);
96755
97083
  if (!s3.isFile()) throw new Error("not a file");
@@ -96779,7 +97107,7 @@ async function startProxy(options) {
96779
97107
  res.end("Bad request");
96780
97108
  return;
96781
97109
  }
96782
- const filePath = join11(VIDEO_DIR, filename);
97110
+ const filePath = join12(VIDEO_DIR, filename);
96783
97111
  try {
96784
97112
  const s3 = await fsStat(filePath);
96785
97113
  if (!s3.isFile()) throw new Error("not a file");
@@ -96921,7 +97249,7 @@ async function startProxy(options) {
96921
97249
  const [, mimeType, b64] = dataUriMatch;
96922
97250
  const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
96923
97251
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
96924
- await writeFile4(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
97252
+ await writeFile4(join12(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
96925
97253
  img.url = `http://localhost:${port2}/images/${filename}`;
96926
97254
  console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
96927
97255
  } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
@@ -96932,7 +97260,7 @@ async function startProxy(options) {
96932
97260
  const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
96933
97261
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
96934
97262
  const buf = Buffer.from(await imgResp.arrayBuffer());
96935
- await writeFile4(join11(IMAGE_DIR, filename), buf);
97263
+ await writeFile4(join12(IMAGE_DIR, filename), buf);
96936
97264
  img.url = `http://localhost:${port2}/images/${filename}`;
96937
97265
  console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
96938
97266
  }
@@ -96995,7 +97323,10 @@ async function startProxy(options) {
96995
97323
  if (typeof val !== "string" || !val) continue;
96996
97324
  if (val.startsWith("data:")) {
96997
97325
  } else if (val.startsWith("https://") || val.startsWith("http://")) {
96998
- const imgResp = await fetch(val, { signal: clientAbort.signal });
97326
+ const imgResp = await ssrfSafeFetch(val, {
97327
+ signal: clientAbort.signal,
97328
+ allowPrivate: process.env.CLAWROUTER_ALLOW_PRIVATE_FETCH === "1"
97329
+ });
96999
97330
  if (!imgResp.ok)
97000
97331
  throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`);
97001
97332
  const contentType = imgResp.headers.get("content-type") ?? "image/png";
@@ -97005,7 +97336,7 @@ async function startProxy(options) {
97005
97336
  `[ClawRouter] img2img: downloaded ${field} URL \u2192 data URI (${buf.length} bytes)`
97006
97337
  );
97007
97338
  } else {
97008
- parsed[field] = readImageFileAsDataUri(val);
97339
+ parsed[field] = readLocalImageAsDataUri(val);
97009
97340
  console.log(`[ClawRouter] img2img: read ${field} file \u2192 data URI`);
97010
97341
  }
97011
97342
  }
@@ -97054,7 +97385,7 @@ async function startProxy(options) {
97054
97385
  const [, mimeType, b64] = dataUriMatch;
97055
97386
  const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
97056
97387
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
97057
- await writeFile4(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
97388
+ await writeFile4(join12(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
97058
97389
  img.url = `http://localhost:${port2}/images/${filename}`;
97059
97390
  console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
97060
97391
  } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
@@ -97065,7 +97396,7 @@ async function startProxy(options) {
97065
97396
  const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
97066
97397
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
97067
97398
  const buf = Buffer.from(await imgResp.arrayBuffer());
97068
- await writeFile4(join11(IMAGE_DIR, filename), buf);
97399
+ await writeFile4(join12(IMAGE_DIR, filename), buf);
97069
97400
  img.url = `http://localhost:${port2}/images/${filename}`;
97070
97401
  console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
97071
97402
  }
@@ -97161,7 +97492,7 @@ async function startProxy(options) {
97161
97492
  const ext = contentType.includes("wav") ? "wav" : "mp3";
97162
97493
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
97163
97494
  const buf = Buffer.from(await audioResp.arrayBuffer());
97164
- await writeFile4(join11(AUDIO_DIR, filename), buf);
97495
+ await writeFile4(join12(AUDIO_DIR, filename), buf);
97165
97496
  track.url = `http://localhost:${port2}/audio/${filename}`;
97166
97497
  console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
97167
97498
  }
@@ -97328,7 +97659,7 @@ async function startProxy(options) {
97328
97659
  const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
97329
97660
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
97330
97661
  const buf = Buffer.from(await videoResp.arrayBuffer());
97331
- await writeFile4(join11(VIDEO_DIR, filename), buf);
97662
+ await writeFile4(join12(VIDEO_DIR, filename), buf);
97332
97663
  clip.url = `http://localhost:${port2}/videos/${filename}`;
97333
97664
  console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
97334
97665
  }
@@ -97721,6 +98052,17 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
97721
98052
  return { success: true, response };
97722
98053
  } catch (err) {
97723
98054
  const errorMsg = err instanceof Error ? err.message : String(err);
98055
+ if (err instanceof CostCapError) {
98056
+ return {
98057
+ success: false,
98058
+ errorBody: JSON.stringify({
98059
+ error: { message: errorMsg, type: "cost_cap_exceeded", code: "cost_cap_exceeded" }
98060
+ }),
98061
+ errorStatus: 429,
98062
+ isProviderError: false,
98063
+ errorCategory: "payment_error"
98064
+ };
98065
+ }
97724
98066
  if (err instanceof SpendPolicyError) {
97725
98067
  return {
97726
98068
  success: false,
@@ -98294,8 +98636,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
98294
98636
  let imageDataUri;
98295
98637
  let maskDataUri;
98296
98638
  try {
98297
- imageDataUri = readImageFileAsDataUri(imagePath);
98298
- if (maskPath) maskDataUri = readImageFileAsDataUri(maskPath);
98639
+ imageDataUri = readLocalImageAsDataUri(imagePath);
98640
+ if (maskPath) maskDataUri = readLocalImageAsDataUri(maskPath);
98299
98641
  } catch (fileErr) {
98300
98642
  const fileErrMsg = fileErr instanceof Error ? fileErr.message : String(fileErr);
98301
98643
  sendImg2ImgText(`Failed to read image file: ${fileErrMsg}`);
@@ -98800,14 +99142,23 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
98800
99142
  }
98801
99143
  }
98802
99144
  }
99145
+ let sessionSpend;
98803
99146
  if (options.maxCostPerRunUsd && effectiveSessionId && !isFreeModel && (options.maxCostPerRunMode ?? "graceful") === "strict") {
98804
- const runCostUsd = sessionStore.getSessionCostUsd(effectiveSessionId);
98805
99147
  const thisReqEstStr = estimatedCostMicros !== void 0 ? estimatedCostMicros.toString() : modelId ? estimateAmount(modelId, body.length, maxTokens) : void 0;
98806
- const thisReqEstUsd = thisReqEstStr ? Number(thisReqEstStr) / 1e6 : 0;
98807
- const projectedCostUsd = runCostUsd + thisReqEstUsd;
98808
- if (projectedCostUsd > options.maxCostPerRunUsd) {
99148
+ const admission = SessionSpend.admit(
99149
+ sessionStore,
99150
+ effectiveSessionId,
99151
+ usdToMicros(options.maxCostPerRunUsd),
99152
+ parseMicros(thisReqEstStr) ?? 0n
99153
+ );
99154
+ if (!admission.admitted) {
99155
+ const spentUsd = microsToUsd(admission.spentMicros);
99156
+ const heldUsd = microsToUsd(admission.heldMicros);
99157
+ const estUsd = microsToUsd(admission.estimateMicros);
99158
+ const projectedCostUsd = spentUsd + heldUsd + estUsd;
99159
+ const breakdown = `spent $${spentUsd.toFixed(4)}${heldUsd > 0 ? ` + in flight $${heldUsd.toFixed(4)}` : ""} + est $${estUsd.toFixed(4)}`;
98809
99160
  console.log(
98810
- `[ClawRouter] Cost cap exceeded for session ${effectiveSessionId.slice(0, 8)}...: projected $${projectedCostUsd.toFixed(4)} (spent $${runCostUsd.toFixed(4)} + est $${thisReqEstUsd.toFixed(4)}) > $${options.maxCostPerRunUsd} limit`
99161
+ `[ClawRouter] Cost cap exceeded for session ${effectiveSessionId.slice(0, 8)}...: projected $${projectedCostUsd.toFixed(4)} (${breakdown}) > $${options.maxCostPerRunUsd} limit`
98811
99162
  );
98812
99163
  res.writeHead(429, {
98813
99164
  "Content-Type": "application/json",
@@ -98816,7 +99167,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
98816
99167
  res.end(
98817
99168
  JSON.stringify({
98818
99169
  error: {
98819
- message: `ClawRouter cost cap exceeded: projected spend $${projectedCostUsd.toFixed(4)} (spent $${runCostUsd.toFixed(4)} + est $${thisReqEstUsd.toFixed(4)}) would exceed limit $${options.maxCostPerRunUsd}`,
99170
+ message: `ClawRouter cost cap exceeded: projected spend $${projectedCostUsd.toFixed(4)} (${breakdown}) would exceed limit $${options.maxCostPerRunUsd}`,
98820
99171
  type: "cost_cap_exceeded",
98821
99172
  code: "cost_cap_exceeded"
98822
99173
  }
@@ -98825,7 +99176,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
98825
99176
  deduplicator.removeInflight(dedupKey);
98826
99177
  return;
98827
99178
  }
99179
+ sessionSpend = admission.spend;
99180
+ } else if (options.maxCostPerRunUsd && effectiveSessionId) {
99181
+ sessionSpend = SessionSpend.track(sessionStore, effectiveSessionId);
98828
99182
  }
99183
+ const requestStore = paymentStore.getStore();
99184
+ if (requestStore) requestStore.sessionSpend = sessionSpend;
98829
99185
  if (options.maxCostPerRunUsd && effectiveSessionId && !isFreeModel && (options.maxCostPerRunMode ?? "graceful") === "graceful") {
98830
99186
  const runCostUsd = sessionStore.getSessionCostUsd(effectiveSessionId);
98831
99187
  const remainingUsd = options.maxCostPerRunUsd - runCostUsd;
@@ -99221,11 +99577,9 @@ data: [DONE]
99221
99577
  actualModelUsed = tryModel;
99222
99578
  recordOutcome(tryModel, requestKind, true);
99223
99579
  console.log(`[ClawRouter] Success with model: ${tryModel}`);
99224
- if (options.maxCostPerRunUsd && effectiveSessionId && !FREE_MODELS.has(tryModel)) {
99225
- const costEst = estimateAmount(tryModel, body.length, maxTokens);
99226
- if (costEst) {
99227
- sessionStore.addSessionCost(effectiveSessionId, BigInt(costEst));
99228
- }
99580
+ if (sessionSpend && !FREE_MODELS.has(tryModel)) {
99581
+ const costEst = parseMicros(estimateAmount(tryModel, body.length, maxTokens));
99582
+ if (costEst !== void 0) sessionSpend.recordSuccess(costEst);
99229
99583
  }
99230
99584
  break;
99231
99585
  }
@@ -99299,11 +99653,9 @@ data: [DONE]
99299
99653
  upstream = retryResult.response;
99300
99654
  actualModelUsed = tryModel;
99301
99655
  console.log(`[ClawRouter] Explicit-pin retry succeeded for: ${tryModel}`);
99302
- if (options.maxCostPerRunUsd && effectiveSessionId && !FREE_MODELS.has(tryModel)) {
99303
- const costEst = estimateAmount(tryModel, body.length, maxTokens);
99304
- if (costEst) {
99305
- sessionStore.addSessionCost(effectiveSessionId, BigInt(costEst));
99306
- }
99656
+ if (sessionSpend && !FREE_MODELS.has(tryModel)) {
99657
+ const costEst = parseMicros(estimateAmount(tryModel, body.length, maxTokens));
99658
+ if (costEst !== void 0) sessionSpend.recordSuccess(costEst);
99307
99659
  }
99308
99660
  break;
99309
99661
  }
@@ -99370,11 +99722,9 @@ data: [DONE]
99370
99722
  upstream = retryResult.response;
99371
99723
  actualModelUsed = tryModel;
99372
99724
  console.log(`[ClawRouter] Rate-limit retry succeeded for: ${tryModel}`);
99373
- if (options.maxCostPerRunUsd && effectiveSessionId && !FREE_MODELS.has(tryModel)) {
99374
- const costEst = estimateAmount(tryModel, body.length, maxTokens);
99375
- if (costEst) {
99376
- sessionStore.addSessionCost(effectiveSessionId, BigInt(costEst));
99377
- }
99725
+ if (sessionSpend && !FREE_MODELS.has(tryModel)) {
99726
+ const costEst = parseMicros(estimateAmount(tryModel, body.length, maxTokens));
99727
+ if (costEst !== void 0) sessionSpend.recordSuccess(costEst);
99378
99728
  }
99379
99729
  break;
99380
99730
  }
@@ -99468,6 +99818,7 @@ data: [DONE]
99468
99818
  );
99469
99819
  const rawErrBody = lastError?.body || structuredMessage;
99470
99820
  const errStatus = lastError?.status || 502;
99821
+ const costCapRefusal = errStatus === 429 && rawErrBody.includes('"cost_cap_exceeded"');
99471
99822
  const transformedErr = transformPaymentError(rawErrBody);
99472
99823
  requestHadError = true;
99473
99824
  if (headersSentEarly) {
@@ -99504,7 +99855,8 @@ data: [DONE]
99504
99855
  "Content-Type": "application/json",
99505
99856
  "x-context-used-kb": String(originalContextSizeKB),
99506
99857
  "x-context-limit-kb": String(CONTEXT_LIMIT_KB),
99507
- ...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {}
99858
+ ...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {},
99859
+ ...costCapRefusal ? { "X-ClawRouter-Cost-Cap-Exceeded": "1" } : {}
99508
99860
  });
99509
99861
  res.end(transformedErr);
99510
99862
  deduplicator.complete(dedupKey, {
@@ -99938,6 +100290,8 @@ data: [DONE]
99938
100290
  throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });
99939
100291
  }
99940
100292
  throw err;
100293
+ } finally {
100294
+ sessionSpend?.release();
99941
100295
  }
99942
100296
  const logModel = routingDecision?.model ?? modelId;
99943
100297
  if (logModel) {
@@ -100024,6 +100378,7 @@ var init_proxy = __esm({
100024
100378
  init_auth();
100025
100379
  init_api_key();
100026
100380
  init_spend_control();
100381
+ init_session_spend();
100027
100382
  init_compression();
100028
100383
  init_errors5();
100029
100384
  init_version4();
@@ -100033,6 +100388,7 @@ var init_proxy = __esm({
100033
100388
  init_outcome_memory();
100034
100389
  init_config();
100035
100390
  init_journal();
100391
+ init_local_guard();
100036
100392
  init_upstream_proxy();
100037
100393
  init_textual_tool_calls();
100038
100394
  init_response_store();
@@ -100041,10 +100397,10 @@ var init_proxy = __esm({
100041
100397
  paymentStore = new AsyncLocalStorage();
100042
100398
  BLOCKRUN_API = "https://blockrun.ai/api";
100043
100399
  BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
100044
- DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join11(homedir8(), ".clawrouter-desktop", "service-token");
100045
- IMAGE_DIR = join11(homedir8(), ".openclaw", "blockrun", "images");
100046
- AUDIO_DIR = join11(homedir8(), ".openclaw", "blockrun", "audio");
100047
- VIDEO_DIR = join11(homedir8(), ".openclaw", "blockrun", "videos");
100400
+ DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join12(homedir9(), ".clawrouter-desktop", "service-token");
100401
+ IMAGE_DIR = join12(homedir9(), ".openclaw", "blockrun", "images");
100402
+ AUDIO_DIR = join12(homedir9(), ".openclaw", "blockrun", "audio");
100403
+ VIDEO_DIR = join12(homedir9(), ".openclaw", "blockrun", "videos");
100048
100404
  AUTO_MODEL = "blockrun/auto";
100049
100405
  ROUTING_PROFILES = /* @__PURE__ */ new Set([
100050
100406
  "blockrun/eco",
@@ -101154,7 +101510,13 @@ function buildTool(service, proxyBaseUrl) {
101154
101510
  if (service.proxyPath === "/pm/__dynamic__") {
101155
101511
  const rawPath = typeof params.path === "string" ? params.path : "";
101156
101512
  const normalized = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
101157
- if (!normalized.startsWith("/pm/") || normalized.includes("..")) {
101513
+ let resolved;
101514
+ try {
101515
+ resolved = new URL(`/v1${normalized}`, "http://x").pathname;
101516
+ } catch {
101517
+ resolved = "";
101518
+ }
101519
+ if (!normalized.startsWith("/pm/") || normalized.includes("..") || /%2e|%2f|%5c|\\/i.test(normalized) || !resolved.startsWith("/v1/pm/")) {
101158
101520
  throw new Error(
101159
101521
  `predexon_endpoint_call: invalid path '${rawPath}' \u2014 must begin with '/pm/' and contain no '..'`
101160
101522
  );
@@ -101493,8 +101855,8 @@ __export(reconcile_exports, {
101493
101855
  reconcile: () => reconcile
101494
101856
  });
101495
101857
  import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
101496
- import { join as join12 } from "path";
101497
- import { homedir as homedir9 } from "os";
101858
+ import { join as join13 } from "path";
101859
+ import { homedir as homedir10 } from "os";
101498
101860
  async function loadLocalRows(since) {
101499
101861
  let files;
101500
101862
  try {
@@ -101507,7 +101869,7 @@ async function loadLocalRows(since) {
101507
101869
  for (const file of files) {
101508
101870
  let text;
101509
101871
  try {
101510
- text = await readFile3(join12(LOG_DIR3, file), "utf8");
101872
+ text = await readFile3(join13(LOG_DIR3, file), "utf8");
101511
101873
  } catch {
101512
101874
  continue;
101513
101875
  }
@@ -101665,7 +102027,7 @@ var init_reconcile = __esm({
101665
102027
  "src/reconcile.ts"() {
101666
102028
  "use strict";
101667
102029
  init_api_key();
101668
- LOG_DIR3 = join12(homedir9(), ".openclaw", "blockrun", "logs");
102030
+ LOG_DIR3 = join13(homedir10(), ".openclaw", "blockrun", "logs");
101669
102031
  CENT_EPSILON = 1e-9;
101670
102032
  usd = (n) => Math.abs(n) < 0.01 ? `$${n.toFixed(6)}` : `$${n.toFixed(2)}`;
101671
102033
  }
@@ -122657,8 +123019,8 @@ var init_constants3 = __esm({
122657
123019
 
122658
123020
  // src/polymarket/wallet-adapter.ts
122659
123021
  import fs2 from "fs";
122660
- import { homedir as homedir10 } from "os";
122661
- import { join as join13 } from "path";
123022
+ import { homedir as homedir11 } from "os";
123023
+ import { join as join14 } from "path";
122662
123024
  function getOrCreateWalletKey() {
122663
123025
  const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
122664
123026
  if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
@@ -122706,7 +123068,7 @@ var init_wallet_adapter = __esm({
122706
123068
  init_esm();
122707
123069
  init_chains();
122708
123070
  init_constants3();
122709
- WALLET_FILE2 = join13(homedir10(), ".openclaw", "blockrun", "wallet.key");
123071
+ WALLET_FILE2 = join14(homedir11(), ".openclaw", "blockrun", "wallet.key");
122710
123072
  BASE_RPC_URLS = [
122711
123073
  "https://mainnet.base.org",
122712
123074
  "https://base.llamarpc.com",
@@ -123110,7 +123472,7 @@ var init_client5 = __esm({
123110
123472
  function resolveSpendControl(deps) {
123111
123473
  return deps?.spendControl ?? getSharedSpendControl();
123112
123474
  }
123113
- function usdToMicros(usd2) {
123475
+ function usdToMicros2(usd2) {
123114
123476
  return String(Math.ceil(usd2 * 1e6));
123115
123477
  }
123116
123478
  async function signUnderSpendPolicy(deps, quote, action, sign4) {
@@ -123376,7 +123738,7 @@ Re-call with confirm:true to sign and submit.`,
123376
123738
  payTo: negRisk ? NEG_RISK_CTF_EXCHANGE_V2 : CTF_EXCHANGE_V2,
123377
123739
  network: `eip155:${POLYGON_CHAIN_ID}`,
123378
123740
  asset: PUSD_COLLATERAL,
123379
- amount: usdToMicros(notional)
123741
+ amount: usdToMicros2(notional)
123380
123742
  };
123381
123743
  let r2;
123382
123744
  try {
@@ -233099,7 +233461,7 @@ __export(index_exports, {
233099
233461
  });
233100
233462
  import {
233101
233463
  writeFileSync as writeFileSync3,
233102
- existsSync as existsSync4,
233464
+ existsSync as existsSync3,
233103
233465
  readdirSync,
233104
233466
  mkdirSync as mkdirSync3,
233105
233467
  copyFileSync,
@@ -233108,9 +233470,9 @@ import {
233108
233470
  unlinkSync
233109
233471
  } from "fs";
233110
233472
  import { readFile as readFileAsync } from "fs/promises";
233111
- import { homedir as homedir15 } from "os";
233473
+ import { homedir as homedir16 } from "os";
233112
233474
  import { randomUUID } from "crypto";
233113
- import { join as join18 } from "path";
233475
+ import { join as join19 } from "path";
233114
233476
  async function waitForProxyHealth(port, timeoutMs = 3e3) {
233115
233477
  const start = Date.now();
233116
233478
  while (Date.now() - start < timeoutMs) {
@@ -233130,12 +233492,12 @@ function writePrivateJsonSync(path5, value) {
233130
233492
  renameSync2(temporary, path5);
233131
233493
  chmodSync(path5, 384);
233132
233494
  } finally {
233133
- if (existsSync4(temporary)) unlinkSync(temporary);
233495
+ if (existsSync3(temporary)) unlinkSync(temporary);
233134
233496
  }
233135
233497
  }
233136
233498
  function hasConfiguredWallet() {
233137
233499
  return Boolean(
233138
- process["env"].BLOCKRUN_WALLET_KEY || existsSync4(CORE_WALLET_FILE) || existsSync4(WALLET_FILE)
233500
+ process["env"].BLOCKRUN_WALLET_KEY || existsSync3(CORE_WALLET_FILE) || existsSync3(WALLET_FILE)
233139
233501
  );
233140
233502
  }
233141
233503
  function isCompletionMode() {
@@ -233153,11 +233515,11 @@ function isBlockrunWebSearchDisabled(config) {
233153
233515
  }
233154
233516
  function legacyPackageIsBlockRun() {
233155
233517
  for (const dir of [
233156
- join18(homedir15(), ".openclaw", "extensions", "clawrouter"),
233157
- join18(homedir15(), ".openclaw", "npm", "node_modules", "@blockrun", "clawrouter")
233518
+ join19(homedir16(), ".openclaw", "extensions", "clawrouter"),
233519
+ join19(homedir16(), ".openclaw", "npm", "node_modules", "@blockrun", "clawrouter")
233158
233520
  ]) {
233159
233521
  try {
233160
- const metadata = JSON.parse(readTextFileSync(join18(dir, "package.json")));
233522
+ const metadata = JSON.parse(readTextFileSync(join19(dir, "package.json")));
233161
233523
  if (metadata.name === "@blockrun/clawrouter") return true;
233162
233524
  } catch {
233163
233525
  }
@@ -233165,11 +233527,11 @@ function legacyPackageIsBlockRun() {
233165
233527
  return false;
233166
233528
  }
233167
233529
  function injectModelsConfig(logger48, options = {}) {
233168
- const configDir = join18(homedir15(), ".openclaw");
233169
- const configPath = join18(configDir, "openclaw.json");
233530
+ const configDir = join19(homedir16(), ".openclaw");
233531
+ const configPath = join19(configDir, "openclaw.json");
233170
233532
  let config = {};
233171
233533
  let needsWrite = false;
233172
- if (!existsSync4(configDir)) {
233534
+ if (!existsSync3(configDir)) {
233173
233535
  try {
233174
233536
  mkdirSync3(configDir, { recursive: true });
233175
233537
  logger48.info("Created OpenClaw config directory");
@@ -233180,7 +233542,7 @@ function injectModelsConfig(logger48, options = {}) {
233180
233542
  return;
233181
233543
  }
233182
233544
  }
233183
- if (existsSync4(configPath)) {
233545
+ if (existsSync3(configPath)) {
233184
233546
  try {
233185
233547
  const content = readTextFileSync(configPath).trim();
233186
233548
  if (content) {
@@ -233387,8 +233749,8 @@ function injectModelsConfig(logger48, options = {}) {
233387
233749
  }
233388
233750
  function syncAgentModelCache(logger48, options = {}) {
233389
233751
  if (!isGatewayMode() && !options.forceWrite) return;
233390
- const agentsDir = join18(homedir15(), ".openclaw", "agents");
233391
- if (!existsSync4(agentsDir)) return;
233752
+ const agentsDir = join19(homedir16(), ".openclaw", "agents");
233753
+ if (!existsSync3(agentsDir)) return;
233392
233754
  let agentDirs;
233393
233755
  try {
233394
233756
  agentDirs = readdirSync(agentsDir);
@@ -233397,8 +233759,8 @@ function syncAgentModelCache(logger48, options = {}) {
233397
233759
  }
233398
233760
  const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
233399
233761
  for (const agent of agentDirs) {
233400
- const cachePath = join18(agentsDir, agent, "agent", "models.json");
233401
- if (!existsSync4(cachePath)) continue;
233762
+ const cachePath = join19(agentsDir, agent, "agent", "models.json");
233763
+ if (!existsSync3(cachePath)) continue;
233402
233764
  try {
233403
233765
  const raw = readTextFileSync(cachePath).trim();
233404
233766
  if (!raw) continue;
@@ -233426,8 +233788,8 @@ function syncAgentModelCache(logger48, options = {}) {
233426
233788
  }
233427
233789
  }
233428
233790
  function injectAuthProfile(logger48) {
233429
- const agentsDir = join18(homedir15(), ".openclaw", "agents");
233430
- if (!existsSync4(agentsDir)) {
233791
+ const agentsDir = join19(homedir16(), ".openclaw", "agents");
233792
+ if (!existsSync3(agentsDir)) {
233431
233793
  try {
233432
233794
  mkdirSync3(agentsDir, { recursive: true });
233433
233795
  } catch (err) {
@@ -233443,10 +233805,10 @@ function injectAuthProfile(logger48) {
233443
233805
  agents = ["main", ...agents];
233444
233806
  }
233445
233807
  for (const agentId of agents) {
233446
- const authDir = join18(agentsDir, agentId, "agent");
233447
- const authPath = join18(authDir, "auth-profiles.json");
233448
- const sqlitePath = join18(authDir, "openclaw-agent.sqlite");
233449
- if (existsSync4(sqlitePath)) {
233808
+ const authDir = join19(agentsDir, agentId, "agent");
233809
+ const authPath = join19(authDir, "auth-profiles.json");
233810
+ const sqlitePath = join19(authDir, "openclaw-agent.sqlite");
233811
+ if (existsSync3(sqlitePath)) {
233450
233812
  removeInjectedAuthPlaceholder(authPath, logger48, agentId);
233451
233813
  continue;
233452
233814
  }
@@ -233454,7 +233816,7 @@ function injectAuthProfile(logger48) {
233454
233816
  removeInjectedAuthPlaceholder(authPath, logger48, agentId);
233455
233817
  continue;
233456
233818
  }
233457
- if (!existsSync4(authDir)) {
233819
+ if (!existsSync3(authDir)) {
233458
233820
  try {
233459
233821
  mkdirSync3(authDir, { recursive: true });
233460
233822
  } catch {
@@ -233465,7 +233827,7 @@ function injectAuthProfile(logger48) {
233465
233827
  version: 1,
233466
233828
  profiles: {}
233467
233829
  };
233468
- if (existsSync4(authPath)) {
233830
+ if (existsSync3(authPath)) {
233469
233831
  try {
233470
233832
  const existing = JSON.parse(readTextFileSync(authPath));
233471
233833
  if (existing.version && existing.profiles) {
@@ -233498,7 +233860,7 @@ function injectAuthProfile(logger48) {
233498
233860
  }
233499
233861
  function removeInjectedAuthPlaceholder(authPath, logger48, agentId) {
233500
233862
  try {
233501
- if (!existsSync4(authPath)) return;
233863
+ if (!existsSync3(authPath)) return;
233502
233864
  const parsed = JSON.parse(readTextFileSync(authPath));
233503
233865
  const profiles = parsed?.profiles;
233504
233866
  if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) return;
@@ -233953,7 +234315,7 @@ function buildImageGenerationProvider() {
233953
234315
  (result.data ?? []).map(async (img) => {
233954
234316
  const filename = img.url?.split("/images/").pop();
233955
234317
  if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
233956
- const filePath = join18(IMAGE_DIR2, filename);
234318
+ const filePath = join19(IMAGE_DIR2, filename);
233957
234319
  const buffer2 = await readFileAsync(filePath);
233958
234320
  const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
233959
234321
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
@@ -234005,7 +234367,7 @@ function buildMusicGenerationProvider() {
234005
234367
  (result.data ?? []).map(async (track) => {
234006
234368
  const filename = track.url?.split("/audio/").pop();
234007
234369
  if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
234008
- const filePath = join18(AUDIO_DIR2, filename);
234370
+ const filePath = join19(AUDIO_DIR2, filename);
234009
234371
  const buffer2 = await readFileAsync(filePath);
234010
234372
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
234011
234373
  const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
@@ -234081,7 +234443,7 @@ function buildVideoGenerationProvider() {
234081
234443
  (result.data ?? []).map(async (clip) => {
234082
234444
  const filename = clip.url?.split("/videos/").pop();
234083
234445
  if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
234084
- const filePath = join18(VIDEO_DIR2, filename);
234446
+ const filePath = join19(VIDEO_DIR2, filename);
234085
234447
  const buffer2 = await readFileAsync(filePath);
234086
234448
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
234087
234449
  const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
@@ -234182,7 +234544,7 @@ function createWalletCommand(api) {
234182
234544
  ];
234183
234545
  let hasMnemonic3 = false;
234184
234546
  try {
234185
- if (existsSync4(MNEMONIC_FILE)) {
234547
+ if (existsSync3(MNEMONIC_FILE)) {
234186
234548
  const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
234187
234549
  if (mnemonic) {
234188
234550
  hasMnemonic3 = true;
@@ -234253,7 +234615,7 @@ function createWalletCommand(api) {
234253
234615
  ].join("\n")
234254
234616
  };
234255
234617
  }
234256
- if (existsSync4(MNEMONIC_FILE)) {
234618
+ if (existsSync3(MNEMONIC_FILE)) {
234257
234619
  const existingMnemonic = readTextFileSync(MNEMONIC_FILE).trim();
234258
234620
  if (existingMnemonic) {
234259
234621
  await savePaymentChain("solana");
@@ -234446,9 +234808,9 @@ var init_index = __esm({
234446
234808
  init_partners();
234447
234809
  activeProxyHandle = null;
234448
234810
  pendingConfiguredStartupApi = null;
234449
- IMAGE_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "images");
234450
- AUDIO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "audio");
234451
- VIDEO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "videos");
234811
+ IMAGE_DIR2 = join19(homedir16(), ".openclaw", "blockrun", "images");
234812
+ AUDIO_DIR2 = join19(homedir16(), ".openclaw", "blockrun", "audio");
234813
+ VIDEO_DIR2 = join19(homedir16(), ".openclaw", "blockrun", "videos");
234452
234814
  plugin = {
234453
234815
  // NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
234454
234816
  // 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
@@ -234588,7 +234950,8 @@ var init_index = __esm({
234588
234950
  name: "cr-imagegen",
234589
234951
  description: "Generate an image (BlockRun image models, paid via wallet)",
234590
234952
  acceptsArgs: true,
234591
- requireAuth: false,
234953
+ // Paid from the wallet: only authorized senders.
234954
+ requireAuth: true,
234592
234955
  handler: async (ctx) => {
234593
234956
  const parsed = parseGenArgs(ctx.args ?? "");
234594
234957
  if (!parsed.prompt) {
@@ -234640,7 +235003,8 @@ ${errText}`
234640
235003
  name: "videogen",
234641
235004
  description: "Generate a short video (Grok Imagine / Seedance, paid via wallet)",
234642
235005
  acceptsArgs: true,
234643
- requireAuth: false,
235006
+ // Paid from the wallet: only authorized senders.
235007
+ requireAuth: true,
234644
235008
  handler: async (ctx) => {
234645
235009
  const parsed = parseGenArgs(ctx.args ?? "");
234646
235010
  if (!parsed.prompt) {
@@ -234689,7 +235053,8 @@ ${errText}`
234689
235053
  name: "cr-call",
234690
235054
  description: "Place an AI voice call via BlockRun + Bland.ai (paid from wallet, $0.54/call)",
234691
235055
  acceptsArgs: true,
234692
- requireAuth: false,
235056
+ // Paid from the wallet: only authorized senders.
235057
+ requireAuth: true,
234693
235058
  handler: async (ctx) => {
234694
235059
  const parsed = parseCallArgs(ctx.args ?? "");
234695
235060
  if (!parsed.to || !parsed.task) {
@@ -234886,8 +235251,8 @@ ${errText}`
234886
235251
  }
234887
235252
  resetProxyStartupState();
234888
235253
  try {
234889
- const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
234890
- if (existsSync4(configPath)) {
235254
+ const configPath = join19(homedir16(), ".openclaw", "openclaw.json");
235255
+ if (existsSync3(configPath)) {
234891
235256
  const config = JSON.parse(readTextFileSync(configPath));
234892
235257
  if (config.models?.providers?.blockrun) {
234893
235258
  delete config.models.providers.blockrun;
@@ -234928,12 +235293,12 @@ ${errText}`
234928
235293
  api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
234929
235294
  }
234930
235295
  try {
234931
- const agentsDir = join18(homedir15(), ".openclaw", "agents");
234932
- if (existsSync4(agentsDir)) {
235296
+ const agentsDir = join19(homedir16(), ".openclaw", "agents");
235297
+ if (existsSync3(agentsDir)) {
234933
235298
  for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
234934
235299
  if (!entry.isDirectory()) continue;
234935
- const authPath = join18(agentsDir, entry.name, "agent", "auth-profiles.json");
234936
- if (!existsSync4(authPath)) continue;
235300
+ const authPath = join19(agentsDir, entry.name, "agent", "auth-profiles.json");
235301
+ if (!existsSync3(authPath)) continue;
234937
235302
  try {
234938
235303
  const store = JSON.parse(readTextFileSync(authPath));
234939
235304
  if (store.profiles?.["blockrun:default"]) {
@@ -235018,7 +235383,7 @@ init_proxy();
235018
235383
  init_spend_control();
235019
235384
  init_version4();
235020
235385
  import { platform, arch, freemem, totalmem } from "os";
235021
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
235386
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
235022
235387
  function formatBytes(bytes) {
235023
235388
  const gb = bytes / (1024 * 1024 * 1024);
235024
235389
  return `${gb.toFixed(1)}GB`;
@@ -235388,7 +235753,7 @@ async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
235388
235753
  const paymentChain = diagnostics.wallet.paymentChain;
235389
235754
  if (paymentChain === "solana") {
235390
235755
  try {
235391
- if (!existsSync3(MNEMONIC_FILE)) {
235756
+ if (!existsSync2(MNEMONIC_FILE)) {
235392
235757
  throw new Error(`mnemonic file missing at ${MNEMONIC_FILE}`);
235393
235758
  }
235394
235759
  const mnemonic = readFileSync3(MNEMONIC_FILE, "utf8").trim();
@@ -236037,7 +236402,7 @@ async function cmdSetup() {
236037
236402
  const {
236038
236403
  copyFileSync: copyFileSync2,
236039
236404
  cpSync,
236040
- existsSync: existsSync5,
236405
+ existsSync: existsSync4,
236041
236406
  mkdirSync: mkdirSync4,
236042
236407
  readFileSync: readFileSync4,
236043
236408
  readdirSync: readdirSync2,
@@ -236046,8 +236411,8 @@ async function cmdSetup() {
236046
236411
  unlinkSync: unlinkSync2,
236047
236412
  writeFileSync: writeFileSync4
236048
236413
  } = await import("fs");
236049
- const { dirname: dirname5, join: join19 } = await import("path");
236050
- const { homedir: homedir16 } = await import("os");
236414
+ const { dirname: dirname5, join: join20 } = await import("path");
236415
+ const { homedir: homedir17 } = await import("os");
236051
236416
  const { injectModelsConfig: injectModelsConfig2, injectAuthProfile: injectAuthProfile2, syncAgentModelCache: syncAgentModelCache2, VISIBLE_OPENCLAW_MODELS: VISIBLE_OPENCLAW_MODELS2 } = await Promise.resolve().then(() => (init_index(), index_exports));
236052
236417
  const { BLOCKRUN_PLUGIN_ID: BLOCKRUN_PLUGIN_ID2, prepareBlockRunPluginConfig: prepareBlockRunPluginConfig2 } = await Promise.resolve().then(() => (init_openclaw_plugin_config(), openclaw_plugin_config_exports));
236053
236418
  console.log("\u{1F99E} ClawRouter setup\n");
@@ -236062,22 +236427,22 @@ async function cmdSetup() {
236062
236427
  } catch {
236063
236428
  }
236064
236429
  if (process.env.npm_config_prefix) {
236065
- candidates.push(join19(process.env.npm_config_prefix, "bin", "openclaw"));
236430
+ candidates.push(join20(process.env.npm_config_prefix, "bin", "openclaw"));
236066
236431
  }
236067
236432
  try {
236068
236433
  const npmRoot = execSync("npm root -g", {
236069
236434
  encoding: "utf8",
236070
236435
  stdio: ["ignore", "pipe", "ignore"]
236071
236436
  }).trim();
236072
- if (npmRoot) candidates.push(join19(dirname5(dirname5(npmRoot)), "bin", "openclaw"));
236437
+ if (npmRoot) candidates.push(join20(dirname5(dirname5(npmRoot)), "bin", "openclaw"));
236073
236438
  } catch {
236074
236439
  }
236075
236440
  candidates.push(
236076
- join19(homedir16(), ".npm-global", "bin", "openclaw"),
236077
- join19(homedir16(), ".local", "bin", "openclaw"),
236441
+ join20(homedir17(), ".npm-global", "bin", "openclaw"),
236442
+ join20(homedir17(), ".local", "bin", "openclaw"),
236078
236443
  "/usr/local/bin/openclaw"
236079
236444
  );
236080
- return candidates.find((candidate) => existsSync5(candidate)) ?? "";
236445
+ return candidates.find((candidate) => existsSync4(candidate)) ?? "";
236081
236446
  })();
236082
236447
  if (!openclawPath) {
236083
236448
  console.error(" \u2717 openclaw not found on PATH");
@@ -236086,31 +236451,31 @@ async function cmdSetup() {
236086
236451
  process.exit(1);
236087
236452
  }
236088
236453
  console.log(` \u2713 Found openclaw at ${openclawPath}`);
236089
- const configPath = join19(homedir16(), ".openclaw", "openclaw.json");
236090
- const configInitiallyExisted = existsSync5(configPath);
236454
+ const configPath = join20(homedir17(), ".openclaw", "openclaw.json");
236455
+ const configInitiallyExisted = existsSync4(configPath);
236091
236456
  const configRollbackBackup = configInitiallyExisted ? `${configPath}.before-${BLOCKRUN_PLUGIN_ID2}.${Date.now()}` : void 0;
236092
236457
  if (configRollbackBackup) copyFileSync2(configPath, configRollbackBackup);
236093
236458
  const ensureConfigRollbackBackup = () => {
236094
236459
  return configRollbackBackup;
236095
236460
  };
236096
236461
  let installAttempted = false;
236097
- const openclawRoot = join19(homedir16(), ".openclaw");
236462
+ const openclawRoot = join20(homedir17(), ".openclaw");
236098
236463
  const findManagedBlockRunInstalls = () => {
236099
236464
  const candidates = [
236100
- join19(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
236101
- join19(openclawRoot, "extensions", "clawrouter"),
236102
- join19(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
236465
+ join20(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
236466
+ join20(openclawRoot, "extensions", "clawrouter"),
236467
+ join20(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
236103
236468
  ];
236104
- const projectsDir = join19(openclawRoot, "npm", "projects");
236469
+ const projectsDir = join20(openclawRoot, "npm", "projects");
236105
236470
  try {
236106
236471
  for (const project of readdirSync2(projectsDir)) {
236107
- candidates.push(join19(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
236472
+ candidates.push(join20(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
236108
236473
  }
236109
236474
  } catch {
236110
236475
  }
236111
236476
  return [...new Set(candidates)].filter((candidate) => {
236112
236477
  try {
236113
- const metadata = JSON.parse(readFileSync4(join19(candidate, "package.json"), "utf8"));
236478
+ const metadata = JSON.parse(readFileSync4(join20(candidate, "package.json"), "utf8"));
236114
236479
  return metadata.name === "@blockrun/clawrouter";
236115
236480
  } catch {
236116
236481
  return false;
@@ -236118,7 +236483,7 @@ async function cmdSetup() {
236118
236483
  });
236119
236484
  };
236120
236485
  const initialPluginDirs = findManagedBlockRunInstalls();
236121
- const setupRollbackRoot = join19(
236486
+ const setupRollbackRoot = join20(
236122
236487
  openclawRoot,
236123
236488
  "blockrun",
236124
236489
  "setup-rollbacks",
@@ -236128,16 +236493,16 @@ async function cmdSetup() {
236128
236493
  if (initialPluginDirs.length > 0) {
236129
236494
  mkdirSync4(setupRollbackRoot, { recursive: true, mode: 448 });
236130
236495
  for (const [index2, pluginDir] of initialPluginDirs.entries()) {
236131
- const backup = join19(setupRollbackRoot, String(index2));
236496
+ const backup = join20(setupRollbackRoot, String(index2));
236132
236497
  cpSync(pluginDir, backup, { recursive: true });
236133
236498
  pluginRollbackBackups.set(pluginDir, backup);
236134
236499
  }
236135
236500
  }
236136
236501
  const restoreSetupConfig = () => {
236137
- if (configRollbackBackup && existsSync5(configRollbackBackup)) {
236502
+ if (configRollbackBackup && existsSync4(configRollbackBackup)) {
236138
236503
  copyFileSync2(configRollbackBackup, configPath);
236139
236504
  console.error(` \u2713 Restored original OpenClaw config from ${configRollbackBackup}`);
236140
- } else if (!configInitiallyExisted && existsSync5(configPath)) {
236505
+ } else if (!configInitiallyExisted && existsSync4(configPath)) {
236141
236506
  rmSync(configPath);
236142
236507
  console.error(" \u2713 Removed the partially created OpenClaw config");
236143
236508
  }
@@ -236159,7 +236524,7 @@ async function cmdSetup() {
236159
236524
  }
236160
236525
  }
236161
236526
  for (const [pluginDir, backup] of pluginRollbackBackups) {
236162
- if (!existsSync5(backup)) continue;
236527
+ if (!existsSync4(backup)) continue;
236163
236528
  rmSync(pluginDir, { recursive: true, force: true });
236164
236529
  mkdirSync4(dirname5(pluginDir), { recursive: true });
236165
236530
  cpSync(backup, pluginDir, { recursive: true });
@@ -236170,17 +236535,17 @@ async function cmdSetup() {
236170
236535
  };
236171
236536
  let legacyBlockRunInstall = false;
236172
236537
  let stagedLegacyEntry;
236173
- if (existsSync5(configPath)) {
236538
+ if (existsSync4(configPath)) {
236174
236539
  try {
236175
236540
  const config = JSON.parse(readFileSync4(configPath, "utf8"));
236176
- const legacyPackagePath = join19(
236177
- homedir16(),
236541
+ const legacyPackagePath = join20(
236542
+ homedir17(),
236178
236543
  ".openclaw",
236179
236544
  "extensions",
236180
236545
  "clawrouter",
236181
236546
  "package.json"
236182
236547
  );
236183
- if (existsSync5(legacyPackagePath)) {
236548
+ if (existsSync4(legacyPackagePath)) {
236184
236549
  try {
236185
236550
  const legacyPackage = JSON.parse(readFileSync4(legacyPackagePath, "utf8"));
236186
236551
  legacyBlockRunInstall = legacyPackage.name === "@blockrun/clawrouter";
@@ -236261,7 +236626,7 @@ async function cmdSetup() {
236261
236626
  );
236262
236627
  process.exit(1);
236263
236628
  }
236264
- if (existsSync5(configPath)) {
236629
+ if (existsSync4(configPath)) {
236265
236630
  try {
236266
236631
  const config = JSON.parse(readFileSync4(configPath, "utf8"));
236267
236632
  if (stagedLegacyEntry) {
@@ -236303,7 +236668,7 @@ async function cmdSetup() {
236303
236668
  info: (msg) => console.log(` ${msg}`)
236304
236669
  };
236305
236670
  try {
236306
- if (existsSync5(configPath)) ensureConfigRollbackBackup();
236671
+ if (existsSync4(configPath)) ensureConfigRollbackBackup();
236307
236672
  injectModelsConfig2(setupLogger, { forceWrite: true });
236308
236673
  syncAgentModelCache2(setupLogger, { forceWrite: true });
236309
236674
  injectAuthProfile2(setupLogger);
@@ -236314,7 +236679,7 @@ async function cmdSetup() {
236314
236679
  }
236315
236680
  console.log("\n\u2713 Setup complete.");
236316
236681
  rmSync(setupRollbackRoot, { recursive: true, force: true });
236317
- if (configRollbackBackup && existsSync5(configRollbackBackup)) {
236682
+ if (configRollbackBackup && existsSync4(configRollbackBackup)) {
236318
236683
  unlinkSync2(configRollbackBackup);
236319
236684
  }
236320
236685
  console.log("\nNext: restart the gateway to load ClawRouter:");