@blockrun/clawrouter 0.12.267 → 0.12.269
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/README.md +168 -21
- package/dist/cli.js +804 -204
- package/dist/index.d.ts +86 -12
- package/dist/index.js +465 -136
- package/docs/configuration.md +102 -9
- package/openclaw.plugin.json +9 -0
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -32310,6 +32310,11 @@ function canonicalLimits(limits) {
|
|
|
32310
32310
|
Object.fromEntries(Object.entries(limits).sort(([a], [b]) => a < b ? -1 : 1))
|
|
32311
32311
|
);
|
|
32312
32312
|
}
|
|
32313
|
+
function getSharedSpendControl() {
|
|
32314
|
+
const host = sharedControlHost();
|
|
32315
|
+
host.__clawrouterSharedSpendControl ??= new SpendControl();
|
|
32316
|
+
return host.__clawrouterSharedSpendControl;
|
|
32317
|
+
}
|
|
32313
32318
|
function parseQuotedAmountUsd(selected) {
|
|
32314
32319
|
const raw = selected.amount ?? selected.maxAmountRequired;
|
|
32315
32320
|
if (typeof raw !== "string" || !CANONICAL_AMOUNT.test(raw)) {
|
|
@@ -32385,7 +32390,7 @@ function formatDuration(seconds) {
|
|
|
32385
32390
|
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
32386
32391
|
}
|
|
32387
32392
|
}
|
|
32388
|
-
var WALLET_DIR, HOUR_MS, DAY_MS, CAIP2_BASE, CAIP2_SOLANA_MAINNET, PAYABLE_NETWORKS, POLICY_LISTS, ADDRESS_LISTS, EVM_ADDRESS, MalformedSpendPolicyError, SpendPolicyConflictError, FileSpendControlStorage, InMemorySpendControlStorage, RESERVATION_TTL_MS, SpendControl, SpendPolicyError, CANONICAL_AMOUNT;
|
|
32393
|
+
var WALLET_DIR, HOUR_MS, DAY_MS, CAIP2_BASE, CAIP2_SOLANA_MAINNET, PAYABLE_NETWORKS, POLICY_LISTS, ADDRESS_LISTS, EVM_ADDRESS, UnreadableSpendPolicyError, MalformedSpendPolicyError, SpendPolicyConflictError, FileSpendControlStorage, InMemorySpendControlStorage, RESERVATION_TTL_MS, SpendControl, sharedControlHost, SpendPolicyError, CANONICAL_AMOUNT;
|
|
32389
32394
|
var init_spend_control = __esm({
|
|
32390
32395
|
"src/spend-control.ts"() {
|
|
32391
32396
|
"use strict";
|
|
@@ -32404,6 +32409,12 @@ var init_spend_control = __esm({
|
|
|
32404
32409
|
];
|
|
32405
32410
|
ADDRESS_LISTS = ["allowedPayees", "blockedPayees", "allowedAssets"];
|
|
32406
32411
|
EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
32412
|
+
UnreadableSpendPolicyError = class extends Error {
|
|
32413
|
+
constructor(cause) {
|
|
32414
|
+
super(`[ClawRouter] Failed to load spending data: ${cause}`);
|
|
32415
|
+
this.name = "UnreadableSpendPolicyError";
|
|
32416
|
+
}
|
|
32417
|
+
};
|
|
32407
32418
|
MalformedSpendPolicyError = class extends Error {
|
|
32408
32419
|
constructor(key2) {
|
|
32409
32420
|
super(
|
|
@@ -32464,9 +32475,7 @@ var init_spend_control = __esm({
|
|
|
32464
32475
|
if (err instanceof MalformedSpendPolicyError) {
|
|
32465
32476
|
throw err;
|
|
32466
32477
|
}
|
|
32467
|
-
|
|
32468
|
-
`[ClawRouter] Failed to load spending data, starting fresh (any configured spend policy is NOT in effect until this file is repaired): ${err}`
|
|
32469
|
-
);
|
|
32478
|
+
throw new UnreadableSpendPolicyError(err);
|
|
32470
32479
|
}
|
|
32471
32480
|
return null;
|
|
32472
32481
|
}
|
|
@@ -32607,6 +32616,33 @@ var init_spend_control = __esm({
|
|
|
32607
32616
|
getPolicyFileError() {
|
|
32608
32617
|
return this.policyFileBroken;
|
|
32609
32618
|
}
|
|
32619
|
+
/**
|
|
32620
|
+
* Re-read limits from storage, keeping this instance's history and open
|
|
32621
|
+
* reservations. Used on an in-process proxy restart so a hand-edit to
|
|
32622
|
+
* spending.json made while the process was running still applies, without
|
|
32623
|
+
* resetting the rolling windows. Fails closed exactly like the constructor:
|
|
32624
|
+
* a malformed file refuses every payment until repaired, and a repaired
|
|
32625
|
+
* file clears that refusal.
|
|
32626
|
+
*/
|
|
32627
|
+
reloadLimits() {
|
|
32628
|
+
let data;
|
|
32629
|
+
try {
|
|
32630
|
+
data = this.storage.load();
|
|
32631
|
+
} catch (err) {
|
|
32632
|
+
if (err instanceof UnreadableSpendPolicyError) {
|
|
32633
|
+
console.error(`${err.message} \u2014 keeping the limits already in effect`);
|
|
32634
|
+
return;
|
|
32635
|
+
}
|
|
32636
|
+
if (!(err instanceof MalformedSpendPolicyError)) throw err;
|
|
32637
|
+
this.policyFileBroken = err.message;
|
|
32638
|
+
console.error(`[ClawRouter] ${err.message}`);
|
|
32639
|
+
return;
|
|
32640
|
+
}
|
|
32641
|
+
this.policyFileBroken = void 0;
|
|
32642
|
+
this.limits = data ? cloneLimits(data.limits) : {};
|
|
32643
|
+
this.diskLimits = cloneLimits(this.limits);
|
|
32644
|
+
this.limitsDirty = false;
|
|
32645
|
+
}
|
|
32610
32646
|
check(estimatedCost2, counterparty) {
|
|
32611
32647
|
if (this.policyFileBroken !== void 0) {
|
|
32612
32648
|
return {
|
|
@@ -32851,6 +32887,16 @@ var init_spend_control = __esm({
|
|
|
32851
32887
|
const records = [...this.history].reverse();
|
|
32852
32888
|
return limit ? records.slice(0, limit) : records;
|
|
32853
32889
|
}
|
|
32890
|
+
/**
|
|
32891
|
+
* Reset the session window. `sessionSpent`/`sessionCalls` are instance state
|
|
32892
|
+
* that is never persisted, so this used to happen implicitly: every
|
|
32893
|
+
* startProxy() built a fresh SpendControl. The process-wide ledger outlives
|
|
32894
|
+
* an in-process proxy restart, which would silently redefine `session` as
|
|
32895
|
+
* "since the gateway booted" — docs/configuration.md and the /policy help
|
|
32896
|
+
* both promise "session resets on restart". The restart path in index.ts
|
|
32897
|
+
* calls this to keep that promise. History and the rolling hourly/daily
|
|
32898
|
+
* windows are deliberately untouched.
|
|
32899
|
+
*/
|
|
32854
32900
|
resetSession() {
|
|
32855
32901
|
this.sessionSpent = 0;
|
|
32856
32902
|
this.sessionCalls = 0;
|
|
@@ -32872,10 +32918,13 @@ var init_spend_control = __esm({
|
|
|
32872
32918
|
this.storage.saveLimits(cloneLimits(this.limits), cloneLimits(this.diskLimits));
|
|
32873
32919
|
} catch (err) {
|
|
32874
32920
|
if (err instanceof SpendPolicyConflictError) {
|
|
32875
|
-
|
|
32876
|
-
|
|
32877
|
-
|
|
32878
|
-
|
|
32921
|
+
try {
|
|
32922
|
+
const current = this.storage.load();
|
|
32923
|
+
this.limits = cloneLimits(current?.limits ?? {});
|
|
32924
|
+
this.diskLimits = cloneLimits(this.limits);
|
|
32925
|
+
this.limitsDirty = false;
|
|
32926
|
+
} catch {
|
|
32927
|
+
}
|
|
32879
32928
|
}
|
|
32880
32929
|
throw err;
|
|
32881
32930
|
}
|
|
@@ -32893,6 +32942,12 @@ var init_spend_control = __esm({
|
|
|
32893
32942
|
try {
|
|
32894
32943
|
data = this.storage.load();
|
|
32895
32944
|
} catch (err) {
|
|
32945
|
+
if (err instanceof UnreadableSpendPolicyError) {
|
|
32946
|
+
console.error(
|
|
32947
|
+
`${err.message} \u2014 starting fresh (any configured spend policy is NOT in effect until this file is repaired)`
|
|
32948
|
+
);
|
|
32949
|
+
return;
|
|
32950
|
+
}
|
|
32896
32951
|
if (!(err instanceof MalformedSpendPolicyError)) throw err;
|
|
32897
32952
|
this.policyFileBroken = err.message;
|
|
32898
32953
|
console.error(`[ClawRouter] ${err.message}`);
|
|
@@ -32909,6 +32964,7 @@ var init_spend_control = __esm({
|
|
|
32909
32964
|
}
|
|
32910
32965
|
}
|
|
32911
32966
|
};
|
|
32967
|
+
sharedControlHost = () => process;
|
|
32912
32968
|
SpendPolicyError = class extends Error {
|
|
32913
32969
|
blockedBy;
|
|
32914
32970
|
blockedByPolicy;
|
|
@@ -32946,6 +33002,21 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
32946
33002
|
}
|
|
32947
33003
|
}
|
|
32948
33004
|
const cacheKey2 = `${urlPath}:${requestModel}`;
|
|
33005
|
+
const notifyAcceptedPayment = (response2, payload2) => {
|
|
33006
|
+
const settled = response2.headers.has("payment-response") || response2.headers.has("x-payment-response");
|
|
33007
|
+
if (!settled || !options?.onPayment) return;
|
|
33008
|
+
try {
|
|
33009
|
+
options.onPayment({
|
|
33010
|
+
model: requestModel,
|
|
33011
|
+
amount: payload2.accepted.amount,
|
|
33012
|
+
network: payload2.accepted.network
|
|
33013
|
+
});
|
|
33014
|
+
} catch (error) {
|
|
33015
|
+
console.error(
|
|
33016
|
+
`[ClawRouter] onPayment callback failed: ${error instanceof Error ? error.message : String(error)}`
|
|
33017
|
+
);
|
|
33018
|
+
}
|
|
33019
|
+
};
|
|
32949
33020
|
const estimateMicros = () => {
|
|
32950
33021
|
if (!options?.estimateAmount || !requestModel) return void 0;
|
|
32951
33022
|
const est = options.estimateAmount(requestModel, bodyLength, maxTokens);
|
|
@@ -32966,6 +33037,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
32966
33037
|
paymentInFlight = true;
|
|
32967
33038
|
const response2 = await baseFetch(preAuthRequest);
|
|
32968
33039
|
if (response2.status !== 402) {
|
|
33040
|
+
notifyAcceptedPayment(response2, payload2);
|
|
32969
33041
|
return response2;
|
|
32970
33042
|
}
|
|
32971
33043
|
paymentInFlight = false;
|
|
@@ -33012,7 +33084,9 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
33012
33084
|
for (const [key2, value] of Object.entries(paymentHeaders)) {
|
|
33013
33085
|
clonedRequest.headers.set(key2, value);
|
|
33014
33086
|
}
|
|
33015
|
-
|
|
33087
|
+
const paidResponse = await baseFetch(clonedRequest);
|
|
33088
|
+
notifyAcceptedPayment(paidResponse, payload);
|
|
33089
|
+
return paidResponse;
|
|
33016
33090
|
};
|
|
33017
33091
|
}
|
|
33018
33092
|
var DEFAULT_TTL_MS;
|
|
@@ -40691,7 +40765,7 @@ var init_errors5 = __esm({
|
|
|
40691
40765
|
});
|
|
40692
40766
|
|
|
40693
40767
|
// src/balance.ts
|
|
40694
|
-
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor;
|
|
40768
|
+
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor, ApiKeyBalanceMonitor;
|
|
40695
40769
|
var init_balance = __esm({
|
|
40696
40770
|
"src/balance.ts"() {
|
|
40697
40771
|
"use strict";
|
|
@@ -40820,6 +40894,38 @@ var init_balance = __esm({
|
|
|
40820
40894
|
};
|
|
40821
40895
|
}
|
|
40822
40896
|
};
|
|
40897
|
+
ApiKeyBalanceMonitor = class _ApiKeyBalanceMonitor {
|
|
40898
|
+
/** Large enough that every `balance >= estimatedCost` comparison passes. */
|
|
40899
|
+
static UNMETERED = BigInt(Number.MAX_SAFE_INTEGER);
|
|
40900
|
+
async checkBalance() {
|
|
40901
|
+
return {
|
|
40902
|
+
balance: _ApiKeyBalanceMonitor.UNMETERED,
|
|
40903
|
+
// Never a dollar figure: no caller may print this as if it were one.
|
|
40904
|
+
balanceUSD: "account credit",
|
|
40905
|
+
isLow: false,
|
|
40906
|
+
isEmpty: false,
|
|
40907
|
+
walletAddress: ""
|
|
40908
|
+
};
|
|
40909
|
+
}
|
|
40910
|
+
async checkSufficient() {
|
|
40911
|
+
return { sufficient: true, info: await this.checkBalance() };
|
|
40912
|
+
}
|
|
40913
|
+
/** No local cache to adjust — the server keeps the books. */
|
|
40914
|
+
deductEstimated() {
|
|
40915
|
+
}
|
|
40916
|
+
invalidate() {
|
|
40917
|
+
}
|
|
40918
|
+
async refresh() {
|
|
40919
|
+
return this.checkBalance();
|
|
40920
|
+
}
|
|
40921
|
+
formatUSDC(amountMicros) {
|
|
40922
|
+
return `$${(Number(amountMicros) / 1e6).toFixed(2)}`;
|
|
40923
|
+
}
|
|
40924
|
+
/** No wallet in this mode; callers render the account instead. */
|
|
40925
|
+
getWalletAddress() {
|
|
40926
|
+
return "";
|
|
40927
|
+
}
|
|
40928
|
+
};
|
|
40823
40929
|
}
|
|
40824
40930
|
});
|
|
40825
40931
|
|
|
@@ -59980,6 +60086,119 @@ var init_auth = __esm({
|
|
|
59980
60086
|
}
|
|
59981
60087
|
});
|
|
59982
60088
|
|
|
60089
|
+
// src/api-key.ts
|
|
60090
|
+
import { writeFile as writeFile2, mkdir as mkdir3, rm } from "fs/promises";
|
|
60091
|
+
import { join as join8 } from "path";
|
|
60092
|
+
import { homedir as homedir5 } from "os";
|
|
60093
|
+
function isValidApiKey(value) {
|
|
60094
|
+
if (typeof value !== "string") return false;
|
|
60095
|
+
return /^brk_[A-Za-z0-9_-]{8,}$/.test(value.trim());
|
|
60096
|
+
}
|
|
60097
|
+
function maskApiKey(key2) {
|
|
60098
|
+
const trimmed = key2.trim();
|
|
60099
|
+
if (trimmed.length <= 18) return `${trimmed.slice(0, 8)}\u2026`;
|
|
60100
|
+
return `${trimmed.slice(0, 14)}\u2026${trimmed.slice(-4)}`;
|
|
60101
|
+
}
|
|
60102
|
+
async function readOptional2(path5) {
|
|
60103
|
+
try {
|
|
60104
|
+
return (await readTextFile(path5)).trim() || void 0;
|
|
60105
|
+
} catch {
|
|
60106
|
+
return void 0;
|
|
60107
|
+
}
|
|
60108
|
+
}
|
|
60109
|
+
async function resolveApiKey() {
|
|
60110
|
+
const envKey = process["env"].BLOCKRUN_API_KEY?.trim();
|
|
60111
|
+
if (envKey) {
|
|
60112
|
+
if (isValidApiKey(envKey)) return { key: envKey, source: "env" };
|
|
60113
|
+
console.warn(
|
|
60114
|
+
`[ClawRouter] \u26A0 BLOCKRUN_API_KEY is set but does not look like a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
|
|
60115
|
+
);
|
|
60116
|
+
}
|
|
60117
|
+
for (const [path5, source] of [
|
|
60118
|
+
[CORE_API_KEY_FILE, "core"],
|
|
60119
|
+
[API_KEY_FILE, "saved"]
|
|
60120
|
+
]) {
|
|
60121
|
+
const stored = await readOptional2(path5);
|
|
60122
|
+
if (stored === void 0) continue;
|
|
60123
|
+
if (isValidApiKey(stored)) return { key: stored, source };
|
|
60124
|
+
console.warn(
|
|
60125
|
+
`[ClawRouter] \u26A0 ${path5} does not contain a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
|
|
60126
|
+
);
|
|
60127
|
+
}
|
|
60128
|
+
return void 0;
|
|
60129
|
+
}
|
|
60130
|
+
async function saveApiKey(key2) {
|
|
60131
|
+
const trimmed = key2.trim();
|
|
60132
|
+
if (!isValidApiKey(trimmed)) {
|
|
60133
|
+
throw new Error(
|
|
60134
|
+
`Not a BlockRun API key: expected it to start with "brk_". Mint one at ${PORTAL_KEYS_URL}`
|
|
60135
|
+
);
|
|
60136
|
+
}
|
|
60137
|
+
await mkdir3(join8(homedir5(), ".blockrun"), { recursive: true });
|
|
60138
|
+
await writeFile2(CORE_API_KEY_FILE, trimmed + "\n", { mode: 384 });
|
|
60139
|
+
return CORE_API_KEY_FILE;
|
|
60140
|
+
}
|
|
60141
|
+
async function clearApiKey() {
|
|
60142
|
+
const removed = [];
|
|
60143
|
+
for (const path5 of [CORE_API_KEY_FILE, API_KEY_FILE]) {
|
|
60144
|
+
if (await readOptional2(path5) === void 0) continue;
|
|
60145
|
+
await rm(path5, { force: true });
|
|
60146
|
+
removed.push(path5);
|
|
60147
|
+
}
|
|
60148
|
+
return { removed, envStillSet: Boolean(process["env"].BLOCKRUN_API_KEY?.trim()) };
|
|
60149
|
+
}
|
|
60150
|
+
function createApiKeyFetch(apiKey, baseFetch = fetch) {
|
|
60151
|
+
return async (input, init2) => {
|
|
60152
|
+
const headers = new Headers(init2?.headers);
|
|
60153
|
+
headers.set("authorization", `Bearer ${apiKey}`);
|
|
60154
|
+
headers.delete("x-payment");
|
|
60155
|
+
headers.delete("x-api-key");
|
|
60156
|
+
const response = await baseFetch(input, { ...init2, headers });
|
|
60157
|
+
return response.ok ? response : explainApiKeyFailure(response);
|
|
60158
|
+
};
|
|
60159
|
+
}
|
|
60160
|
+
async function explainApiKeyFailure(response) {
|
|
60161
|
+
const hint = HINTS[response.status];
|
|
60162
|
+
if (!hint) return response;
|
|
60163
|
+
if (!(response.headers.get("content-type") ?? "").includes("json")) return response;
|
|
60164
|
+
let parsed;
|
|
60165
|
+
try {
|
|
60166
|
+
parsed = await response.clone().json();
|
|
60167
|
+
} catch {
|
|
60168
|
+
return response;
|
|
60169
|
+
}
|
|
60170
|
+
if (!parsed?.error || typeof parsed.error.message !== "string") return response;
|
|
60171
|
+
if (response.status === 404 && !/unsupported endpoint/i.test(parsed.error.message)) {
|
|
60172
|
+
return response;
|
|
60173
|
+
}
|
|
60174
|
+
parsed.error.message = `${parsed.error.message} ${hint}`;
|
|
60175
|
+
const headers = new Headers(response.headers);
|
|
60176
|
+
headers.delete("content-length");
|
|
60177
|
+
return new Response(JSON.stringify(parsed), {
|
|
60178
|
+
status: response.status,
|
|
60179
|
+
statusText: response.statusText,
|
|
60180
|
+
headers
|
|
60181
|
+
});
|
|
60182
|
+
}
|
|
60183
|
+
var API_KEY_FILE, CORE_API_KEY_FILE, PORTAL_URL, PORTAL_KEYS_URL, PORTAL_CREDITS_URL, BLOCKRUN_API_KEY_API, HINTS;
|
|
60184
|
+
var init_api_key = __esm({
|
|
60185
|
+
"src/api-key.ts"() {
|
|
60186
|
+
"use strict";
|
|
60187
|
+
init_fs_read();
|
|
60188
|
+
API_KEY_FILE = join8(homedir5(), ".openclaw", "blockrun", "api-key");
|
|
60189
|
+
CORE_API_KEY_FILE = join8(homedir5(), ".blockrun", ".api-key");
|
|
60190
|
+
PORTAL_URL = "https://user.blockrun.ai";
|
|
60191
|
+
PORTAL_KEYS_URL = `${PORTAL_URL}/dashboard/keys`;
|
|
60192
|
+
PORTAL_CREDITS_URL = `${PORTAL_URL}/dashboard/credits`;
|
|
60193
|
+
BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "") || "https://api.blockrun.ai";
|
|
60194
|
+
HINTS = {
|
|
60195
|
+
401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
|
|
60196
|
+
402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
|
|
60197
|
+
404: `api.blockrun.ai does not serve this endpoint yet \u2014 it currently carries chat and text completions. For media and partner APIs, run ClawRouter in wallet mode (unset BLOCKRUN_API_KEY and run "clawrouter logout").`
|
|
60198
|
+
};
|
|
60199
|
+
}
|
|
60200
|
+
});
|
|
60201
|
+
|
|
59983
60202
|
// src/compression/types.ts
|
|
59984
60203
|
var DEFAULT_COMPRESSION_CONFIG;
|
|
59985
60204
|
var init_types2 = __esm({
|
|
@@ -61069,8 +61288,8 @@ var init_updater = __esm({
|
|
|
61069
61288
|
|
|
61070
61289
|
// src/exclude-models.ts
|
|
61071
61290
|
import { readFileSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
61072
|
-
import { join as
|
|
61073
|
-
import { homedir as
|
|
61291
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
61292
|
+
import { homedir as homedir6 } from "os";
|
|
61074
61293
|
function loadExcludeList(filePath = DEFAULT_FILE_PATH) {
|
|
61075
61294
|
try {
|
|
61076
61295
|
const mtimeMs = statSync(filePath).mtimeMs;
|
|
@@ -61119,7 +61338,7 @@ var init_exclude_models = __esm({
|
|
|
61119
61338
|
"src/exclude-models.ts"() {
|
|
61120
61339
|
"use strict";
|
|
61121
61340
|
init_models();
|
|
61122
|
-
DEFAULT_FILE_PATH =
|
|
61341
|
+
DEFAULT_FILE_PATH = join9(homedir6(), ".openclaw", "blockrun", "exclude-models.json");
|
|
61123
61342
|
loadCache = /* @__PURE__ */ new Map();
|
|
61124
61343
|
}
|
|
61125
61344
|
});
|
|
@@ -76128,7 +76347,7 @@ var require_snapshot_utils = __commonJS({
|
|
|
76128
76347
|
var require_snapshot_recorder = __commonJS({
|
|
76129
76348
|
"node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
76130
76349
|
"use strict";
|
|
76131
|
-
var { writeFile:
|
|
76350
|
+
var { writeFile: writeFile4, readFile: readFile3, mkdir: mkdir6 } = __require("fs/promises");
|
|
76132
76351
|
var { dirname: dirname4, resolve } = __require("path");
|
|
76133
76352
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
|
|
76134
76353
|
var { InvalidArgumentError, UndiciError } = require_errors();
|
|
@@ -76375,12 +76594,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
76375
76594
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
76376
76595
|
}
|
|
76377
76596
|
const resolvedPath = resolve(path5);
|
|
76378
|
-
await
|
|
76597
|
+
await mkdir6(dirname4(resolvedPath), { recursive: true });
|
|
76379
76598
|
const data = Array.from(this.#snapshots.entries()).map(([hash5, snapshot]) => ({
|
|
76380
76599
|
hash: hash5,
|
|
76381
76600
|
snapshot
|
|
76382
76601
|
}));
|
|
76383
|
-
await
|
|
76602
|
+
await writeFile4(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
76384
76603
|
}
|
|
76385
76604
|
/**
|
|
76386
76605
|
* Clears all recorded snapshots
|
|
@@ -88709,9 +88928,9 @@ var init_textual_tool_calls = __esm({
|
|
|
88709
88928
|
});
|
|
88710
88929
|
|
|
88711
88930
|
// src/response-store.ts
|
|
88712
|
-
import { appendFile as appendFile2, mkdir as
|
|
88713
|
-
import { homedir as
|
|
88714
|
-
import { join as
|
|
88931
|
+
import { appendFile as appendFile2, mkdir as mkdir4, readFile, readdir as readdir2 } from "fs/promises";
|
|
88932
|
+
import { homedir as homedir7 } from "os";
|
|
88933
|
+
import { join as join10 } from "path";
|
|
88715
88934
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
88716
88935
|
function isEnabled() {
|
|
88717
88936
|
const v = process.env.BLOCKRUN_RESPONSE_STORE;
|
|
@@ -88719,12 +88938,12 @@ function isEnabled() {
|
|
|
88719
88938
|
}
|
|
88720
88939
|
async function ensureDir2() {
|
|
88721
88940
|
if (dirReady2) return;
|
|
88722
|
-
await
|
|
88941
|
+
await mkdir4(STORE_DIR, { recursive: true });
|
|
88723
88942
|
dirReady2 = true;
|
|
88724
88943
|
}
|
|
88725
88944
|
function dailyFile(date = /* @__PURE__ */ new Date()) {
|
|
88726
88945
|
const iso = date.toISOString().slice(0, 10);
|
|
88727
|
-
return
|
|
88946
|
+
return join10(STORE_DIR, `responses-${iso}.jsonl`);
|
|
88728
88947
|
}
|
|
88729
88948
|
function genId() {
|
|
88730
88949
|
return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
|
|
@@ -88774,7 +88993,7 @@ async function listRecent(limit = 20, daysBack = 7) {
|
|
|
88774
88993
|
d.setUTCDate(d.getUTCDate() - i);
|
|
88775
88994
|
const iso = d.toISOString().slice(0, 10);
|
|
88776
88995
|
const name = `responses-${iso}.jsonl`;
|
|
88777
|
-
if (files.includes(name)) candidateFiles.push(
|
|
88996
|
+
if (files.includes(name)) candidateFiles.push(join10(STORE_DIR, name));
|
|
88778
88997
|
}
|
|
88779
88998
|
const all3 = [];
|
|
88780
88999
|
for (const f of candidateFiles) {
|
|
@@ -88803,7 +89022,7 @@ var STORE_DIR, dirReady2;
|
|
|
88803
89022
|
var init_response_store = __esm({
|
|
88804
89023
|
"src/response-store.ts"() {
|
|
88805
89024
|
"use strict";
|
|
88806
|
-
STORE_DIR =
|
|
89025
|
+
STORE_DIR = join10(homedir7(), ".openclaw", "blockrun", "responses");
|
|
88807
89026
|
dirReady2 = false;
|
|
88808
89027
|
}
|
|
88809
89028
|
});
|
|
@@ -90454,15 +90673,18 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
90454
90673
|
import { createHmac } from "crypto";
|
|
90455
90674
|
import { createServer } from "http";
|
|
90456
90675
|
import { finished } from "stream";
|
|
90457
|
-
import { homedir as
|
|
90458
|
-
import { join as
|
|
90459
|
-
import { mkdir as
|
|
90676
|
+
import { homedir as homedir8 } from "os";
|
|
90677
|
+
import { join as join11 } from "path";
|
|
90678
|
+
import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile2, stat as fsStat } from "fs/promises";
|
|
90460
90679
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
90461
|
-
async function loadGatewayCatalog(apiBase) {
|
|
90680
|
+
async function loadGatewayCatalog(apiBase, apiKey) {
|
|
90462
90681
|
try {
|
|
90463
90682
|
const controller = new AbortController();
|
|
90464
90683
|
const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
|
|
90465
|
-
const res = await fetch(`${apiBase}/v1/models`, {
|
|
90684
|
+
const res = await fetch(`${apiBase}/v1/models`, {
|
|
90685
|
+
signal: controller.signal,
|
|
90686
|
+
...apiKey ? { headers: { authorization: `Bearer ${apiKey}` } } : {}
|
|
90687
|
+
});
|
|
90466
90688
|
clearTimeout(timer2);
|
|
90467
90689
|
if (!res.ok) return;
|
|
90468
90690
|
const body = await res.json();
|
|
@@ -90761,8 +90983,9 @@ async function checkExistingProxy(port) {
|
|
|
90761
90983
|
clearTimeout(timeoutId);
|
|
90762
90984
|
if (response.ok) {
|
|
90763
90985
|
const data = await response.json();
|
|
90764
|
-
|
|
90765
|
-
|
|
90986
|
+
const authMode = data.authMode === "api-key" ? "api-key" : "wallet";
|
|
90987
|
+
if (data.status === "ok" && (data.wallet || authMode === "api-key")) {
|
|
90988
|
+
return { wallet: data.wallet ?? "", paymentChain: data.paymentChain, authMode };
|
|
90766
90989
|
}
|
|
90767
90990
|
}
|
|
90768
90991
|
return void 0;
|
|
@@ -91270,7 +91493,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91270
91493
|
});
|
|
91271
91494
|
}
|
|
91272
91495
|
function readImageFileAsDataUri(filePath) {
|
|
91273
|
-
const resolved = filePath.startsWith("~/") ?
|
|
91496
|
+
const resolved = filePath.startsWith("~/") ? join11(homedir8(), filePath.slice(2)) : filePath;
|
|
91274
91497
|
if (!existsSync2(resolved)) {
|
|
91275
91498
|
throw new Error(`Image file not found: ${resolved}`);
|
|
91276
91499
|
}
|
|
@@ -91313,16 +91536,48 @@ async function uploadDataUriToHost(dataUri) {
|
|
|
91313
91536
|
clearTimeout(uploadTimeout);
|
|
91314
91537
|
}
|
|
91315
91538
|
}
|
|
91539
|
+
function warnIfSpendLimitsUnenforced(spendControl) {
|
|
91540
|
+
let configured;
|
|
91541
|
+
try {
|
|
91542
|
+
const limits = (spendControl ?? new SpendControl()).getStatus().limits;
|
|
91543
|
+
configured = ["perRequest", "hourly", "daily", "session"].filter((window2) => typeof limits[window2] === "number").map((window2) => `${window2}=$${limits[window2].toFixed(2)}`);
|
|
91544
|
+
} catch {
|
|
91545
|
+
return;
|
|
91546
|
+
}
|
|
91547
|
+
if (configured.length === 0) return;
|
|
91548
|
+
console.warn(
|
|
91549
|
+
`[ClawRouter] \u26A0 Spend limits (${configured.join(", ")}) are NOT enforced in API-key mode \u2014 they gate x402 signing, and nothing is signed here.`
|
|
91550
|
+
);
|
|
91551
|
+
console.warn(
|
|
91552
|
+
`[ClawRouter] Your BlockRun account balance is the cap instead. Use maxCostPerRun for a per-session limit that still applies.`
|
|
91553
|
+
);
|
|
91554
|
+
}
|
|
91316
91555
|
async function startProxy(options) {
|
|
91317
91556
|
const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
|
|
91318
91557
|
if (upstreamProxy) {
|
|
91319
91558
|
console.log(`[ClawRouter] Upstream proxy: ${upstreamProxy}`);
|
|
91320
91559
|
}
|
|
91321
|
-
const
|
|
91322
|
-
|
|
91560
|
+
const apiKey = options.apiKey?.trim() || void 0;
|
|
91561
|
+
if (apiKey && !isValidApiKey(apiKey)) {
|
|
91562
|
+
throw new Error(
|
|
91563
|
+
`BlockRun API key is malformed (expected it to start with "brk_"). Mint one at https://user.blockrun.ai/dashboard/keys`
|
|
91564
|
+
);
|
|
91565
|
+
}
|
|
91566
|
+
const authMode = apiKey ? "api-key" : "wallet";
|
|
91567
|
+
if (!apiKey && !options.wallet) {
|
|
91568
|
+
throw new Error(
|
|
91569
|
+
`startProxy needs a credential: pass either a wallet (x402) or an apiKey (brk_\u2026, from https://user.blockrun.ai/dashboard/keys).`
|
|
91570
|
+
);
|
|
91571
|
+
}
|
|
91572
|
+
const walletKey = options.wallet === void 0 ? void 0 : typeof options.wallet === "string" ? options.wallet : options.wallet.key;
|
|
91573
|
+
const solanaPrivateKeyBytes = options.wallet === void 0 || typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
|
|
91323
91574
|
const paymentChain = options.paymentChain ?? await resolvePaymentChain();
|
|
91324
|
-
const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
91325
|
-
if (
|
|
91575
|
+
const apiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
91576
|
+
if (authMode === "api-key") {
|
|
91577
|
+
console.log(`[ClawRouter] Auth: BlockRun API key ${maskApiKey(apiKey)} (${apiBase})`);
|
|
91578
|
+
console.log(`[ClawRouter] Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
91579
|
+
warnIfSpendLimitsUnenforced(options.spendControl);
|
|
91580
|
+
} else if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
|
|
91326
91581
|
console.warn(
|
|
91327
91582
|
`[ClawRouter] \u26A0 Payment chain is Solana but no mnemonic found \u2014 falling back to Base (EVM).`
|
|
91328
91583
|
);
|
|
@@ -91333,12 +91588,31 @@ async function startProxy(options) {
|
|
|
91333
91588
|
} else if (paymentChain === "solana") {
|
|
91334
91589
|
console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
|
|
91335
91590
|
}
|
|
91336
|
-
void loadGatewayCatalog(apiBase);
|
|
91591
|
+
void loadGatewayCatalog(apiBase, apiKey);
|
|
91337
91592
|
const listenPort = options.port ?? getProxyPort();
|
|
91338
91593
|
const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
|
|
91339
91594
|
if (existingProxy) {
|
|
91340
|
-
const account2 = privateKeyToAccount(walletKey);
|
|
91341
91595
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
91596
|
+
if (existingProxy.authMode !== authMode) {
|
|
91597
|
+
throw new Error(
|
|
91598
|
+
`Existing proxy on port ${listenPort} is authenticating with ${existingProxy.authMode === "api-key" ? "a BlockRun API key" : "a wallet"} but ${authMode === "api-key" ? "an API key" : "a wallet"} was requested. Stop the existing proxy first or use a different port.`
|
|
91599
|
+
);
|
|
91600
|
+
}
|
|
91601
|
+
if (authMode === "api-key") {
|
|
91602
|
+
options.onReady?.(listenPort);
|
|
91603
|
+
return {
|
|
91604
|
+
port: listenPort,
|
|
91605
|
+
baseUrl: baseUrl2,
|
|
91606
|
+
walletAddress: "",
|
|
91607
|
+
authMode,
|
|
91608
|
+
apiKeyLabel: maskApiKey(apiKey),
|
|
91609
|
+
balanceMonitor: new ApiKeyBalanceMonitor(),
|
|
91610
|
+
// No-op: we didn't start this proxy, so we shouldn't close it
|
|
91611
|
+
close: async () => {
|
|
91612
|
+
}
|
|
91613
|
+
};
|
|
91614
|
+
}
|
|
91615
|
+
const account2 = privateKeyToAccount(walletKey);
|
|
91342
91616
|
if (existingProxy.wallet !== account2.address) {
|
|
91343
91617
|
console.warn(
|
|
91344
91618
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
@@ -91377,20 +91651,23 @@ async function startProxy(options) {
|
|
|
91377
91651
|
baseUrl: baseUrl2,
|
|
91378
91652
|
walletAddress: existingProxy.wallet,
|
|
91379
91653
|
solanaAddress: reuseSolanaAddress,
|
|
91654
|
+
authMode,
|
|
91380
91655
|
balanceMonitor: balanceMonitor2,
|
|
91381
91656
|
close: async () => {
|
|
91382
91657
|
}
|
|
91383
91658
|
};
|
|
91384
91659
|
}
|
|
91385
|
-
const account = privateKeyToAccount(walletKey);
|
|
91386
|
-
const
|
|
91387
|
-
|
|
91388
|
-
|
|
91389
|
-
|
|
91390
|
-
|
|
91391
|
-
|
|
91660
|
+
const account = walletKey ? privateKeyToAccount(walletKey) : void 0;
|
|
91661
|
+
const x402 = authMode === "wallet" ? new x402Client() : void 0;
|
|
91662
|
+
if (x402 && account) {
|
|
91663
|
+
const evmPublicClient = createPublicClient({ chain: base, transport: http() });
|
|
91664
|
+
const evmSigner = toClientEvmSigner(account, evmPublicClient);
|
|
91665
|
+
const spendControl = options.spendControl ?? getSharedSpendControl();
|
|
91666
|
+
registerSpendPolicyHook(x402, spendControl);
|
|
91667
|
+
registerExactEvmScheme(x402, { signer: evmSigner });
|
|
91668
|
+
}
|
|
91392
91669
|
let solanaAddress;
|
|
91393
|
-
if (solanaPrivateKeyBytes) {
|
|
91670
|
+
if (x402 && solanaPrivateKeyBytes) {
|
|
91394
91671
|
const { registerExactSvmScheme: registerExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
91395
91672
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91396
91673
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
@@ -91398,12 +91675,12 @@ async function startProxy(options) {
|
|
|
91398
91675
|
registerExactSvmScheme2(x402, { signer: solanaSigner });
|
|
91399
91676
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91400
91677
|
}
|
|
91401
|
-
x402
|
|
91678
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91402
91679
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
91403
91680
|
const payload = context.paymentPayload;
|
|
91404
91681
|
payload.extensions = withBuilderCodeServiceCode(payload.extensions);
|
|
91405
91682
|
});
|
|
91406
|
-
x402
|
|
91683
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91407
91684
|
const network = context.selectedRequirements.network;
|
|
91408
91685
|
const chain3 = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
|
|
91409
91686
|
const amountMicros = parseInt(context.selectedRequirements.amount || "0", 10);
|
|
@@ -91412,16 +91689,22 @@ async function startProxy(options) {
|
|
|
91412
91689
|
if (store) store.amountUsd = amountUsd;
|
|
91413
91690
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91414
91691
|
});
|
|
91415
|
-
const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91692
|
+
const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91416
91693
|
skipPreAuth: paymentChain === "solana",
|
|
91417
91694
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91418
91695
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
91419
91696
|
// token, so one model can cost different amounts across requests.
|
|
91420
|
-
estimateAmount
|
|
91697
|
+
estimateAmount,
|
|
91698
|
+
// Only the wallet rail settles per call. The API-key rail is billed
|
|
91699
|
+
// server-side against account credit, so there is no per-request
|
|
91700
|
+
// settlement for an observer to report.
|
|
91701
|
+
onPayment: options.onPayment
|
|
91421
91702
|
});
|
|
91422
91703
|
let balanceMonitor;
|
|
91423
91704
|
if (options._balanceMonitorOverride) {
|
|
91424
91705
|
balanceMonitor = options._balanceMonitorOverride;
|
|
91706
|
+
} else if (authMode === "api-key") {
|
|
91707
|
+
balanceMonitor = new ApiKeyBalanceMonitor();
|
|
91425
91708
|
} else if (paymentChain === "solana" && solanaAddress) {
|
|
91426
91709
|
const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
|
|
91427
91710
|
balanceMonitor = new SolanaBalanceMonitor2(solanaAddress);
|
|
@@ -91463,11 +91746,17 @@ async function startProxy(options) {
|
|
|
91463
91746
|
const full = url2.searchParams.get("full") === "true";
|
|
91464
91747
|
const response = {
|
|
91465
91748
|
status: "ok",
|
|
91466
|
-
|
|
91467
|
-
paymentChain
|
|
91749
|
+
authMode
|
|
91468
91750
|
};
|
|
91469
|
-
if (
|
|
91470
|
-
response.
|
|
91751
|
+
if (authMode === "api-key") {
|
|
91752
|
+
response.apiKey = maskApiKey(apiKey);
|
|
91753
|
+
response.gateway = apiBase;
|
|
91754
|
+
} else {
|
|
91755
|
+
response.wallet = account.address;
|
|
91756
|
+
response.paymentChain = paymentChain;
|
|
91757
|
+
if (solanaAddress) {
|
|
91758
|
+
response.solana = solanaAddress;
|
|
91759
|
+
}
|
|
91471
91760
|
}
|
|
91472
91761
|
if (upstreamProxy) {
|
|
91473
91762
|
response.upstreamProxy = upstreamProxy;
|
|
@@ -91483,9 +91772,15 @@ async function startProxy(options) {
|
|
|
91483
91772
|
).unref()
|
|
91484
91773
|
)
|
|
91485
91774
|
]);
|
|
91486
|
-
|
|
91487
|
-
|
|
91488
|
-
|
|
91775
|
+
if (authMode === "api-key") {
|
|
91776
|
+
response.balance = null;
|
|
91777
|
+
response.billing = "BlockRun account credit";
|
|
91778
|
+
response.topUpUrl = PORTAL_CREDITS_URL;
|
|
91779
|
+
} else {
|
|
91780
|
+
response.balance = balanceInfo.balanceUSD;
|
|
91781
|
+
response.isLow = balanceInfo.isLow;
|
|
91782
|
+
response.isEmpty = balanceInfo.isEmpty;
|
|
91783
|
+
}
|
|
91489
91784
|
} catch {
|
|
91490
91785
|
response.balanceError = "Could not fetch balance";
|
|
91491
91786
|
}
|
|
@@ -91665,7 +91960,7 @@ async function startProxy(options) {
|
|
|
91665
91960
|
res.end("Bad request");
|
|
91666
91961
|
return;
|
|
91667
91962
|
}
|
|
91668
|
-
const filePath =
|
|
91963
|
+
const filePath = join11(IMAGE_DIR, filename);
|
|
91669
91964
|
try {
|
|
91670
91965
|
const s3 = await fsStat(filePath);
|
|
91671
91966
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91696,7 +91991,7 @@ async function startProxy(options) {
|
|
|
91696
91991
|
res.end("Bad request");
|
|
91697
91992
|
return;
|
|
91698
91993
|
}
|
|
91699
|
-
const filePath =
|
|
91994
|
+
const filePath = join11(AUDIO_DIR, filename);
|
|
91700
91995
|
try {
|
|
91701
91996
|
const s3 = await fsStat(filePath);
|
|
91702
91997
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91726,7 +92021,7 @@ async function startProxy(options) {
|
|
|
91726
92021
|
res.end("Bad request");
|
|
91727
92022
|
return;
|
|
91728
92023
|
}
|
|
91729
|
-
const filePath =
|
|
92024
|
+
const filePath = join11(VIDEO_DIR, filename);
|
|
91730
92025
|
try {
|
|
91731
92026
|
const s3 = await fsStat(filePath);
|
|
91732
92027
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91859,7 +92154,7 @@ async function startProxy(options) {
|
|
|
91859
92154
|
}
|
|
91860
92155
|
}
|
|
91861
92156
|
if (result.data?.length) {
|
|
91862
|
-
await
|
|
92157
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91863
92158
|
const port2 = server.address()?.port ?? 8402;
|
|
91864
92159
|
for (const img of result.data) {
|
|
91865
92160
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91867,7 +92162,7 @@ async function startProxy(options) {
|
|
|
91867
92162
|
const [, mimeType, b64] = dataUriMatch;
|
|
91868
92163
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91869
92164
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91870
|
-
await
|
|
92165
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91871
92166
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91872
92167
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91873
92168
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -91878,7 +92173,7 @@ async function startProxy(options) {
|
|
|
91878
92173
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
91879
92174
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91880
92175
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
91881
|
-
await
|
|
92176
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
91882
92177
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91883
92178
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
91884
92179
|
}
|
|
@@ -91982,7 +92277,7 @@ async function startProxy(options) {
|
|
|
91982
92277
|
return;
|
|
91983
92278
|
}
|
|
91984
92279
|
if (result.data?.length) {
|
|
91985
|
-
await
|
|
92280
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91986
92281
|
const port2 = server.address()?.port ?? 8402;
|
|
91987
92282
|
for (const img of result.data) {
|
|
91988
92283
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91990,7 +92285,7 @@ async function startProxy(options) {
|
|
|
91990
92285
|
const [, mimeType, b64] = dataUriMatch;
|
|
91991
92286
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91992
92287
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91993
|
-
await
|
|
92288
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91994
92289
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91995
92290
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91996
92291
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -92001,7 +92296,7 @@ async function startProxy(options) {
|
|
|
92001
92296
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
92002
92297
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92003
92298
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
92004
|
-
await
|
|
92299
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
92005
92300
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
92006
92301
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
92007
92302
|
}
|
|
@@ -92076,7 +92371,7 @@ async function startProxy(options) {
|
|
|
92076
92371
|
return;
|
|
92077
92372
|
}
|
|
92078
92373
|
if (result.data?.length) {
|
|
92079
|
-
await
|
|
92374
|
+
await mkdir5(AUDIO_DIR, { recursive: true });
|
|
92080
92375
|
const port2 = server.address()?.port ?? 8402;
|
|
92081
92376
|
for (const track of result.data) {
|
|
92082
92377
|
if (track.url?.startsWith("https://") || track.url?.startsWith("http://")) {
|
|
@@ -92087,7 +92382,7 @@ async function startProxy(options) {
|
|
|
92087
92382
|
const ext = contentType.includes("wav") ? "wav" : "mp3";
|
|
92088
92383
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92089
92384
|
const buf = Buffer.from(await audioResp.arrayBuffer());
|
|
92090
|
-
await
|
|
92385
|
+
await writeFile3(join11(AUDIO_DIR, filename), buf);
|
|
92091
92386
|
track.url = `http://localhost:${port2}/audio/${filename}`;
|
|
92092
92387
|
console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
|
|
92093
92388
|
}
|
|
@@ -92236,7 +92531,7 @@ async function startProxy(options) {
|
|
|
92236
92531
|
}
|
|
92237
92532
|
}
|
|
92238
92533
|
if (finalResult.data?.length) {
|
|
92239
|
-
await
|
|
92534
|
+
await mkdir5(VIDEO_DIR, { recursive: true });
|
|
92240
92535
|
const port2 = server.address()?.port ?? 8402;
|
|
92241
92536
|
for (const clip of finalResult.data) {
|
|
92242
92537
|
if (clip.url?.startsWith("https://") || clip.url?.startsWith("http://")) {
|
|
@@ -92247,7 +92542,7 @@ async function startProxy(options) {
|
|
|
92247
92542
|
const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
|
|
92248
92543
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92249
92544
|
const buf = Buffer.from(await videoResp.arrayBuffer());
|
|
92250
|
-
await
|
|
92545
|
+
await writeFile3(join11(VIDEO_DIR, filename), buf);
|
|
92251
92546
|
clip.url = `http://localhost:${port2}/videos/${filename}`;
|
|
92252
92547
|
console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
|
|
92253
92548
|
}
|
|
@@ -92430,6 +92725,8 @@ async function startProxy(options) {
|
|
|
92430
92725
|
port: listenPort,
|
|
92431
92726
|
baseUrl: baseUrl2,
|
|
92432
92727
|
walletAddress: error.wallet,
|
|
92728
|
+
authMode,
|
|
92729
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92433
92730
|
balanceMonitor,
|
|
92434
92731
|
close: async () => {
|
|
92435
92732
|
}
|
|
@@ -92480,8 +92777,10 @@ async function startProxy(options) {
|
|
|
92480
92777
|
return {
|
|
92481
92778
|
port,
|
|
92482
92779
|
baseUrl,
|
|
92483
|
-
walletAddress: account
|
|
92780
|
+
walletAddress: account?.address ?? "",
|
|
92484
92781
|
solanaAddress,
|
|
92782
|
+
authMode,
|
|
92783
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92485
92784
|
balanceMonitor,
|
|
92486
92785
|
close: () => new Promise((res, rej) => {
|
|
92487
92786
|
const timeout = setTimeout(() => {
|
|
@@ -94804,6 +95103,7 @@ var init_proxy = __esm({
|
|
|
94804
95103
|
init_response_cache();
|
|
94805
95104
|
init_balance();
|
|
94806
95105
|
init_auth();
|
|
95106
|
+
init_api_key();
|
|
94807
95107
|
init_spend_control();
|
|
94808
95108
|
init_compression();
|
|
94809
95109
|
init_version4();
|
|
@@ -94820,10 +95120,10 @@ var init_proxy = __esm({
|
|
|
94820
95120
|
paymentStore = new AsyncLocalStorage();
|
|
94821
95121
|
BLOCKRUN_API = "https://blockrun.ai/api";
|
|
94822
95122
|
BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
|
|
94823
|
-
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ??
|
|
94824
|
-
IMAGE_DIR =
|
|
94825
|
-
AUDIO_DIR =
|
|
94826
|
-
VIDEO_DIR =
|
|
95123
|
+
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join11(homedir8(), ".clawrouter-desktop", "service-token");
|
|
95124
|
+
IMAGE_DIR = join11(homedir8(), ".openclaw", "blockrun", "images");
|
|
95125
|
+
AUDIO_DIR = join11(homedir8(), ".openclaw", "blockrun", "audio");
|
|
95126
|
+
VIDEO_DIR = join11(homedir8(), ".openclaw", "blockrun", "videos");
|
|
94827
95127
|
AUTO_MODEL = "blockrun/auto";
|
|
94828
95128
|
ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
94829
95129
|
"blockrun/eco",
|
|
@@ -117219,8 +117519,8 @@ var init_constants3 = __esm({
|
|
|
117219
117519
|
|
|
117220
117520
|
// src/polymarket/wallet-adapter.ts
|
|
117221
117521
|
import fs2 from "fs";
|
|
117222
|
-
import { homedir as
|
|
117223
|
-
import { join as
|
|
117522
|
+
import { homedir as homedir9 } from "os";
|
|
117523
|
+
import { join as join12 } from "path";
|
|
117224
117524
|
function getOrCreateWalletKey() {
|
|
117225
117525
|
const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
|
|
117226
117526
|
if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
|
|
@@ -117268,7 +117568,7 @@ var init_wallet_adapter = __esm({
|
|
|
117268
117568
|
init_esm();
|
|
117269
117569
|
init_chains();
|
|
117270
117570
|
init_constants3();
|
|
117271
|
-
WALLET_FILE2 =
|
|
117571
|
+
WALLET_FILE2 = join12(homedir9(), ".openclaw", "blockrun", "wallet.key");
|
|
117272
117572
|
BASE_RPC_URLS = [
|
|
117273
117573
|
"https://mainnet.base.org",
|
|
117274
117574
|
"https://base.llamarpc.com",
|
|
@@ -117670,9 +117970,7 @@ var init_client5 = __esm({
|
|
|
117670
117970
|
|
|
117671
117971
|
// src/polymarket/spend-policy.ts
|
|
117672
117972
|
function resolveSpendControl(deps) {
|
|
117673
|
-
|
|
117674
|
-
defaultControl ??= new SpendControl();
|
|
117675
|
-
return defaultControl;
|
|
117973
|
+
return deps?.spendControl ?? getSharedSpendControl();
|
|
117676
117974
|
}
|
|
117677
117975
|
function usdToMicros(usd) {
|
|
117678
117976
|
return String(Math.ceil(usd * 1e6));
|
|
@@ -117689,7 +117987,6 @@ async function signUnderSpendPolicy(deps, quote, action, sign4) {
|
|
|
117689
117987
|
throw err;
|
|
117690
117988
|
}
|
|
117691
117989
|
}
|
|
117692
|
-
var defaultControl;
|
|
117693
117990
|
var init_spend_policy = __esm({
|
|
117694
117991
|
"src/polymarket/spend-policy.ts"() {
|
|
117695
117992
|
"use strict";
|
|
@@ -227054,7 +227351,7 @@ var init_fund = __esm({
|
|
|
227054
227351
|
});
|
|
227055
227352
|
|
|
227056
227353
|
// src/polymarket/tool.ts
|
|
227057
|
-
function buildPolymarketTool() {
|
|
227354
|
+
function buildPolymarketTool(deps) {
|
|
227058
227355
|
return {
|
|
227059
227356
|
name: "blockrun_polymarket",
|
|
227060
227357
|
description: DESCRIPTION,
|
|
@@ -227139,26 +227436,32 @@ function buildPolymarketTool() {
|
|
|
227139
227436
|
result = await runSetup({ confirm });
|
|
227140
227437
|
break;
|
|
227141
227438
|
case "fund":
|
|
227142
|
-
result = await fundVault(
|
|
227143
|
-
|
|
227144
|
-
|
|
227145
|
-
|
|
227439
|
+
result = await fundVault(
|
|
227440
|
+
{
|
|
227441
|
+
amount_usd: params.amount_usd,
|
|
227442
|
+
confirm
|
|
227443
|
+
},
|
|
227444
|
+
deps
|
|
227445
|
+
);
|
|
227146
227446
|
break;
|
|
227147
227447
|
case "buy":
|
|
227148
227448
|
case "sell":
|
|
227149
|
-
result = await executeTrade(
|
|
227150
|
-
|
|
227151
|
-
|
|
227152
|
-
|
|
227153
|
-
|
|
227154
|
-
|
|
227155
|
-
|
|
227156
|
-
|
|
227157
|
-
|
|
227158
|
-
|
|
227159
|
-
|
|
227160
|
-
|
|
227161
|
-
|
|
227449
|
+
result = await executeTrade(
|
|
227450
|
+
{
|
|
227451
|
+
action,
|
|
227452
|
+
token_id: params.token_id,
|
|
227453
|
+
condition_id: params.condition_id,
|
|
227454
|
+
outcome: params.outcome,
|
|
227455
|
+
price: params.price,
|
|
227456
|
+
size: params.size,
|
|
227457
|
+
amount_usd: params.amount_usd,
|
|
227458
|
+
order_type: params.order_type,
|
|
227459
|
+
expires_at: params.expires_at,
|
|
227460
|
+
post_only: params.post_only,
|
|
227461
|
+
confirm
|
|
227462
|
+
},
|
|
227463
|
+
deps
|
|
227464
|
+
);
|
|
227162
227465
|
break;
|
|
227163
227466
|
case "orders":
|
|
227164
227467
|
result = await listOpenOrders({
|
|
@@ -227175,17 +227478,23 @@ function buildPolymarketTool() {
|
|
|
227175
227478
|
result = await listPositions();
|
|
227176
227479
|
break;
|
|
227177
227480
|
case "redeem":
|
|
227178
|
-
result = await redeemPosition(
|
|
227179
|
-
|
|
227180
|
-
|
|
227181
|
-
|
|
227481
|
+
result = await redeemPosition(
|
|
227482
|
+
{
|
|
227483
|
+
condition_id: params.condition_id,
|
|
227484
|
+
confirm
|
|
227485
|
+
},
|
|
227486
|
+
deps
|
|
227487
|
+
);
|
|
227182
227488
|
break;
|
|
227183
227489
|
case "withdraw":
|
|
227184
|
-
result = await withdrawFunds(
|
|
227185
|
-
|
|
227186
|
-
|
|
227187
|
-
|
|
227188
|
-
|
|
227490
|
+
result = await withdrawFunds(
|
|
227491
|
+
{
|
|
227492
|
+
amount_usd: params.amount_usd,
|
|
227493
|
+
to_address: params.to_address,
|
|
227494
|
+
confirm
|
|
227495
|
+
},
|
|
227496
|
+
deps
|
|
227497
|
+
);
|
|
227189
227498
|
break;
|
|
227190
227499
|
default:
|
|
227191
227500
|
throw new Error(`Unknown blockrun_polymarket action: ${action}`);
|
|
@@ -227601,6 +227910,7 @@ __export(index_exports, {
|
|
|
227601
227910
|
SolanaBalanceMonitor: () => SolanaBalanceMonitor,
|
|
227602
227911
|
SpendControl: () => SpendControl,
|
|
227603
227912
|
SpendPolicyError: () => SpendPolicyError,
|
|
227913
|
+
UnreadableSpendPolicyError: () => UnreadableSpendPolicyError,
|
|
227604
227914
|
VISIBLE_OPENCLAW_MODELS: () => VISIBLE_OPENCLAW_MODELS,
|
|
227605
227915
|
blockrunProvider: () => blockrunProvider,
|
|
227606
227916
|
buildImageGenerationProvider: () => buildImageGenerationProvider,
|
|
@@ -227660,9 +227970,9 @@ import {
|
|
|
227660
227970
|
unlinkSync
|
|
227661
227971
|
} from "fs";
|
|
227662
227972
|
import { readFile as readFileAsync } from "fs/promises";
|
|
227663
|
-
import { homedir as
|
|
227973
|
+
import { homedir as homedir14 } from "os";
|
|
227664
227974
|
import { randomUUID } from "crypto";
|
|
227665
|
-
import { join as
|
|
227975
|
+
import { join as join17 } from "path";
|
|
227666
227976
|
async function waitForProxyHealth(port, timeoutMs = 3e3) {
|
|
227667
227977
|
const start = Date.now();
|
|
227668
227978
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -227704,8 +228014,8 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
227704
228014
|
return cfg.tools?.web?.search?.enabled === false;
|
|
227705
228015
|
}
|
|
227706
228016
|
function injectModelsConfig(logger48, options = {}) {
|
|
227707
|
-
const configDir =
|
|
227708
|
-
const configPath =
|
|
228017
|
+
const configDir = join17(homedir14(), ".openclaw");
|
|
228018
|
+
const configPath = join17(configDir, "openclaw.json");
|
|
227709
228019
|
let config = {};
|
|
227710
228020
|
let needsWrite = false;
|
|
227711
228021
|
if (!existsSync4(configDir)) {
|
|
@@ -227926,7 +228236,7 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
227926
228236
|
}
|
|
227927
228237
|
function syncAgentModelCache(logger48, options = {}) {
|
|
227928
228238
|
if (!isGatewayMode() && !options.forceWrite) return;
|
|
227929
|
-
const agentsDir =
|
|
228239
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227930
228240
|
if (!existsSync4(agentsDir)) return;
|
|
227931
228241
|
let agentDirs;
|
|
227932
228242
|
try {
|
|
@@ -227936,7 +228246,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227936
228246
|
}
|
|
227937
228247
|
const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
|
|
227938
228248
|
for (const agent of agentDirs) {
|
|
227939
|
-
const cachePath =
|
|
228249
|
+
const cachePath = join17(agentsDir, agent, "agent", "models.json");
|
|
227940
228250
|
if (!existsSync4(cachePath)) continue;
|
|
227941
228251
|
try {
|
|
227942
228252
|
const raw = readTextFileSync(cachePath).trim();
|
|
@@ -227965,7 +228275,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227965
228275
|
}
|
|
227966
228276
|
}
|
|
227967
228277
|
function injectAuthProfile(logger48) {
|
|
227968
|
-
const agentsDir =
|
|
228278
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227969
228279
|
if (!existsSync4(agentsDir)) {
|
|
227970
228280
|
try {
|
|
227971
228281
|
mkdirSync3(agentsDir, { recursive: true });
|
|
@@ -227982,9 +228292,9 @@ function injectAuthProfile(logger48) {
|
|
|
227982
228292
|
agents = ["main", ...agents];
|
|
227983
228293
|
}
|
|
227984
228294
|
for (const agentId of agents) {
|
|
227985
|
-
const authDir =
|
|
227986
|
-
const authPath =
|
|
227987
|
-
const sqlitePath =
|
|
228295
|
+
const authDir = join17(agentsDir, agentId, "agent");
|
|
228296
|
+
const authPath = join17(authDir, "auth-profiles.json");
|
|
228297
|
+
const sqlitePath = join17(authDir, "openclaw-agent.sqlite");
|
|
227988
228298
|
if (existsSync4(sqlitePath)) {
|
|
227989
228299
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
227990
228300
|
continue;
|
|
@@ -228131,9 +228441,21 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228131
228441
|
if (startupGeneration !== void 0 && isProxyStartupCurrent(startupGeneration, proc)) {
|
|
228132
228442
|
proc.__clawrouterStartupPhase = "starting";
|
|
228133
228443
|
}
|
|
228444
|
+
const pluginApiKey = api.pluginConfig?.apiKey;
|
|
228445
|
+
const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
|
|
228446
|
+
if (typeof pluginApiKey === "string" && !isValidApiKey(pluginApiKey)) {
|
|
228447
|
+
api.logger.warn(
|
|
228448
|
+
`pluginConfig.apiKey is set but invalid (expected brk_...) \u2014 ignoring it and looking elsewhere`
|
|
228449
|
+
);
|
|
228450
|
+
}
|
|
228451
|
+
const apiKey = resolvedApiKey?.key;
|
|
228134
228452
|
const configKey = api.pluginConfig?.walletKey;
|
|
228135
228453
|
let wallet;
|
|
228136
|
-
if (
|
|
228454
|
+
if (apiKey) {
|
|
228455
|
+
api.logger.info(
|
|
228456
|
+
`Using BlockRun API key ${maskApiKey(apiKey)} (from ${resolvedApiKey.source}) \u2014 billing account credit, no wallet`
|
|
228457
|
+
);
|
|
228458
|
+
} else if (typeof configKey === "string" && /^0x[0-9a-fA-F]{64}$/.test(configKey)) {
|
|
228137
228459
|
const account = privateKeyToAccount(configKey);
|
|
228138
228460
|
wallet = { key: configKey, address: account.address, source: "config" };
|
|
228139
228461
|
} else {
|
|
@@ -228144,7 +228466,8 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228144
228466
|
}
|
|
228145
228467
|
wallet = await resolveOrGenerateWalletKey();
|
|
228146
228468
|
}
|
|
228147
|
-
if (wallet
|
|
228469
|
+
if (!wallet) {
|
|
228470
|
+
} else if (wallet.source === "generated") {
|
|
228148
228471
|
api.logger.warn(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
228149
228472
|
api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
|
|
228150
228473
|
api.logger.warn(` Address : ${wallet.address}`);
|
|
@@ -228168,14 +228491,20 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228168
228491
|
`Cost cap: $${maxCostPerRunUsd.toFixed(2)} per session (mode: ${maxCostPerRunMode})`
|
|
228169
228492
|
);
|
|
228170
228493
|
}
|
|
228494
|
+
const sharedControl = getSharedSpendControl();
|
|
228495
|
+
sharedControl.reloadLimits();
|
|
228496
|
+
sharedControl.resetSession();
|
|
228171
228497
|
const proxy = await startProxy({
|
|
228172
|
-
wallet,
|
|
228498
|
+
...apiKey ? { apiKey } : { wallet },
|
|
228173
228499
|
routingConfig,
|
|
228174
228500
|
maxCostPerRunUsd,
|
|
228175
228501
|
maxCostPerRunMode,
|
|
228176
|
-
|
|
228502
|
+
// The process-wide ledger: the polymarket tool and the /policy command
|
|
228503
|
+
// registered below use the same instance, so hourly/daily/session windows
|
|
228504
|
+
// cover every surface and a /policy write reaches the live signer.
|
|
228505
|
+
spendControl: sharedControl,
|
|
228177
228506
|
onReady: (port) => {
|
|
228178
|
-
api.logger.info(`BlockRun x402 proxy listening on port ${port}`);
|
|
228507
|
+
api.logger.info(`BlockRun ${apiKey ? "API-key" : "x402"} proxy listening on port ${port}`);
|
|
228179
228508
|
},
|
|
228180
228509
|
onError: (error) => {
|
|
228181
228510
|
api.logger.error(`BlockRun proxy error: ${error.message}`);
|
|
@@ -228221,6 +228550,10 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228221
228550
|
}
|
|
228222
228551
|
api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
|
|
228223
228552
|
api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
|
|
228553
|
+
if (apiKey) {
|
|
228554
|
+
api.logger.info(`Billing: BlockRun account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
228555
|
+
return true;
|
|
228556
|
+
}
|
|
228224
228557
|
const currentChain = await resolvePaymentChain();
|
|
228225
228558
|
const displayAddress = currentChain === "solana" && proxy.solanaAddress ? proxy.solanaAddress : wallet.address;
|
|
228226
228559
|
const network = currentChain === "solana" ? "Solana" : "Base";
|
|
@@ -228464,7 +228797,7 @@ function buildImageGenerationProvider() {
|
|
|
228464
228797
|
(result.data ?? []).map(async (img) => {
|
|
228465
228798
|
const filename = img.url?.split("/images/").pop();
|
|
228466
228799
|
if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
|
|
228467
|
-
const filePath =
|
|
228800
|
+
const filePath = join17(IMAGE_DIR2, filename);
|
|
228468
228801
|
const buffer2 = await readFileAsync(filePath);
|
|
228469
228802
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
|
|
228470
228803
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
@@ -228516,7 +228849,7 @@ function buildMusicGenerationProvider() {
|
|
|
228516
228849
|
(result.data ?? []).map(async (track) => {
|
|
228517
228850
|
const filename = track.url?.split("/audio/").pop();
|
|
228518
228851
|
if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
|
|
228519
|
-
const filePath =
|
|
228852
|
+
const filePath = join17(AUDIO_DIR2, filename);
|
|
228520
228853
|
const buffer2 = await readFileAsync(filePath);
|
|
228521
228854
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
|
|
228522
228855
|
const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
|
|
@@ -228592,7 +228925,7 @@ function buildVideoGenerationProvider() {
|
|
|
228592
228925
|
(result.data ?? []).map(async (clip) => {
|
|
228593
228926
|
const filename = clip.url?.split("/videos/").pop();
|
|
228594
228927
|
if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
|
|
228595
|
-
const filePath =
|
|
228928
|
+
const filePath = join17(VIDEO_DIR2, filename);
|
|
228596
228929
|
const buffer2 = await readFileAsync(filePath);
|
|
228597
228930
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
|
|
228598
228931
|
const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
|
|
@@ -228645,6 +228978,22 @@ function createWalletCommand(api) {
|
|
|
228645
228978
|
requireAuth: true,
|
|
228646
228979
|
handler: async (ctx) => {
|
|
228647
228980
|
const subcommand = ctx.args?.trim().toLowerCase() || "status";
|
|
228981
|
+
const activeApiKey = (await resolveApiKey())?.key;
|
|
228982
|
+
if (activeApiKey) {
|
|
228983
|
+
return {
|
|
228984
|
+
text: [
|
|
228985
|
+
"**BlockRun account** (API key)",
|
|
228986
|
+
"",
|
|
228987
|
+
`**Key:** \`${maskApiKey(activeApiKey)}\``,
|
|
228988
|
+
"**Billing:** account credit \u2014 no wallet, no payment chain, no gas.",
|
|
228989
|
+
"",
|
|
228990
|
+
`**Add credit:** ${PORTAL_CREDITS_URL}`,
|
|
228991
|
+
`**Usage & keys:** ${PORTAL_KEYS_URL}`,
|
|
228992
|
+
"",
|
|
228993
|
+
"To pay with a USDC wallet over x402 instead, run `clawrouter logout` and restart."
|
|
228994
|
+
].join("\n")
|
|
228995
|
+
};
|
|
228996
|
+
}
|
|
228648
228997
|
let wallet;
|
|
228649
228998
|
try {
|
|
228650
228999
|
wallet = subcommand === "status" || subcommand === "export" ? await resolveExistingWalletKey() : await resolveOrGenerateWalletKey();
|
|
@@ -228896,12 +229245,13 @@ function createWalletCommand(api) {
|
|
|
228896
229245
|
}
|
|
228897
229246
|
};
|
|
228898
229247
|
}
|
|
228899
|
-
var activeProxyHandle,
|
|
229248
|
+
var activeProxyHandle, pendingConfiguredStartupApi, IMAGE_DIR2, AUDIO_DIR2, VIDEO_DIR2, plugin, index_default2;
|
|
228900
229249
|
var init_index = __esm({
|
|
228901
229250
|
"src/index.ts"() {
|
|
228902
229251
|
"use strict";
|
|
228903
229252
|
init_provider();
|
|
228904
229253
|
init_proxy();
|
|
229254
|
+
init_api_key();
|
|
228905
229255
|
init_web_search_provider();
|
|
228906
229256
|
init_auth();
|
|
228907
229257
|
init_balance();
|
|
@@ -228914,10 +229264,10 @@ var init_index = __esm({
|
|
|
228914
229264
|
init_stats();
|
|
228915
229265
|
init_partners();
|
|
228916
229266
|
init_tool();
|
|
229267
|
+
init_spend_control();
|
|
228917
229268
|
init_stats2();
|
|
228918
229269
|
init_exclude();
|
|
228919
229270
|
init_policy();
|
|
228920
|
-
init_spend_control();
|
|
228921
229271
|
init_mcp_config();
|
|
228922
229272
|
init_openclaw_plugin_config();
|
|
228923
229273
|
init_openclaw_plugin_config();
|
|
@@ -228939,11 +229289,10 @@ var init_index = __esm({
|
|
|
228939
229289
|
init_response_cache();
|
|
228940
229290
|
init_partners();
|
|
228941
229291
|
activeProxyHandle = null;
|
|
228942
|
-
liveSpendControl = null;
|
|
228943
229292
|
pendingConfiguredStartupApi = null;
|
|
228944
|
-
IMAGE_DIR2 =
|
|
228945
|
-
AUDIO_DIR2 =
|
|
228946
|
-
VIDEO_DIR2 =
|
|
229293
|
+
IMAGE_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "images");
|
|
229294
|
+
AUDIO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "audio");
|
|
229295
|
+
VIDEO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "videos");
|
|
228947
229296
|
plugin = {
|
|
228948
229297
|
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
228949
229298
|
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
@@ -229255,7 +229604,7 @@ ${errText}`
|
|
|
229255
229604
|
api.registerCommand(createExcludeCommand());
|
|
229256
229605
|
api.registerCommand(
|
|
229257
229606
|
createPolicyCommand({
|
|
229258
|
-
liveControl: () =>
|
|
229607
|
+
liveControl: () => process.__clawrouterProxyStarted ? getSharedSpendControl() : void 0
|
|
229259
229608
|
})
|
|
229260
229609
|
);
|
|
229261
229610
|
if (shouldLogRegistration) {
|
|
@@ -229360,7 +229709,7 @@ ${errText}`
|
|
|
229360
229709
|
}
|
|
229361
229710
|
resetProxyStartupState();
|
|
229362
229711
|
try {
|
|
229363
|
-
const configPath =
|
|
229712
|
+
const configPath = join17(homedir14(), ".openclaw", "openclaw.json");
|
|
229364
229713
|
if (existsSync4(configPath)) {
|
|
229365
229714
|
const config = JSON.parse(readTextFileSync(configPath));
|
|
229366
229715
|
if (config.models?.providers?.blockrun) {
|
|
@@ -229402,11 +229751,11 @@ ${errText}`
|
|
|
229402
229751
|
api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
229403
229752
|
}
|
|
229404
229753
|
try {
|
|
229405
|
-
const agentsDir =
|
|
229754
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
229406
229755
|
if (existsSync4(agentsDir)) {
|
|
229407
229756
|
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
229408
229757
|
if (!entry.isDirectory()) continue;
|
|
229409
|
-
const authPath =
|
|
229758
|
+
const authPath = join17(agentsDir, entry.name, "agent", "auth-profiles.json");
|
|
229410
229759
|
if (!existsSync4(authPath)) continue;
|
|
229411
229760
|
try {
|
|
229412
229761
|
const store = JSON.parse(readTextFileSync(authPath));
|
|
@@ -229485,6 +229834,7 @@ init_client2();
|
|
|
229485
229834
|
init_esm5();
|
|
229486
229835
|
init_auth();
|
|
229487
229836
|
init_balance();
|
|
229837
|
+
init_api_key();
|
|
229488
229838
|
init_wallet2();
|
|
229489
229839
|
init_stats();
|
|
229490
229840
|
init_proxy();
|
|
@@ -229526,7 +229876,48 @@ function collectSystemInfo() {
|
|
|
229526
229876
|
memoryTotal: formatBytes(totalmem())
|
|
229527
229877
|
};
|
|
229528
229878
|
}
|
|
229529
|
-
async function
|
|
229879
|
+
async function collectApiKeyInfo() {
|
|
229880
|
+
const resolved = await resolveApiKey();
|
|
229881
|
+
if (!resolved) {
|
|
229882
|
+
return {
|
|
229883
|
+
configured: false,
|
|
229884
|
+
masked: null,
|
|
229885
|
+
source: null,
|
|
229886
|
+
gateway: BLOCKRUN_API_KEY_API,
|
|
229887
|
+
accepted: null
|
|
229888
|
+
};
|
|
229889
|
+
}
|
|
229890
|
+
let accepted = null;
|
|
229891
|
+
try {
|
|
229892
|
+
const response = await fetch(`${BLOCKRUN_API_KEY_API}/v1/models`, {
|
|
229893
|
+
headers: { authorization: `Bearer ${resolved.key}` },
|
|
229894
|
+
signal: AbortSignal.timeout(1e4)
|
|
229895
|
+
});
|
|
229896
|
+
if (response.status === 401) accepted = false;
|
|
229897
|
+
else if (response.ok) accepted = true;
|
|
229898
|
+
} catch {
|
|
229899
|
+
}
|
|
229900
|
+
return {
|
|
229901
|
+
configured: true,
|
|
229902
|
+
masked: maskApiKey(resolved.key),
|
|
229903
|
+
source: resolved.source,
|
|
229904
|
+
gateway: BLOCKRUN_API_KEY_API,
|
|
229905
|
+
accepted
|
|
229906
|
+
};
|
|
229907
|
+
}
|
|
229908
|
+
async function collectWalletInfo(apiKeyConfigured) {
|
|
229909
|
+
const empty = {
|
|
229910
|
+
exists: false,
|
|
229911
|
+
valid: false,
|
|
229912
|
+
address: null,
|
|
229913
|
+
solanaAddress: null,
|
|
229914
|
+
balance: null,
|
|
229915
|
+
isLow: false,
|
|
229916
|
+
isEmpty: true,
|
|
229917
|
+
source: null,
|
|
229918
|
+
paymentChain: "base"
|
|
229919
|
+
};
|
|
229920
|
+
if (apiKeyConfigured) return { ...empty, isEmpty: false };
|
|
229530
229921
|
try {
|
|
229531
229922
|
const { key: key2, address: address2, source, solanaPrivateKeyBytes } = await resolveOrGenerateWalletKey();
|
|
229532
229923
|
if (!key2 || !address2) {
|
|
@@ -229645,6 +230036,14 @@ async function collectLogInfo() {
|
|
|
229645
230036
|
}
|
|
229646
230037
|
function identifyIssues(result) {
|
|
229647
230038
|
const issues = [];
|
|
230039
|
+
if (result.apiKey.configured) {
|
|
230040
|
+
if (result.apiKey.accepted === false) {
|
|
230041
|
+
issues.push(
|
|
230042
|
+
`BlockRun rejected the API key (${result.apiKey.masked}) \u2014 it may be revoked or mistyped. Mint a new one at ${PORTAL_KEYS_URL}`
|
|
230043
|
+
);
|
|
230044
|
+
}
|
|
230045
|
+
return finishIssues(result, issues);
|
|
230046
|
+
}
|
|
229648
230047
|
if (!result.wallet.exists) {
|
|
229649
230048
|
issues.push("No wallet found");
|
|
229650
230049
|
}
|
|
@@ -229657,6 +230056,9 @@ function identifyIssues(result) {
|
|
|
229657
230056
|
} else if (result.wallet.isLow) {
|
|
229658
230057
|
issues.push("Wallet balance is low (< $1.00)");
|
|
229659
230058
|
}
|
|
230059
|
+
return finishIssues(result, issues);
|
|
230060
|
+
}
|
|
230061
|
+
function finishIssues(result, issues) {
|
|
229660
230062
|
if (!result.network.blockrunApi.reachable) {
|
|
229661
230063
|
issues.push("Cannot reach BlockRun API - check internet connection");
|
|
229662
230064
|
}
|
|
@@ -229690,6 +230092,22 @@ function printDiagnostics(result) {
|
|
|
229690
230092
|
console.log(
|
|
229691
230093
|
` ${green(`Memory: ${result.system.memoryFree} free / ${result.system.memoryTotal}`)}`
|
|
229692
230094
|
);
|
|
230095
|
+
if (result.apiKey.configured) {
|
|
230096
|
+
console.log("\nBlockRun account (API key)");
|
|
230097
|
+
console.log(` ${green(`Key: ${result.apiKey.masked} (from ${result.apiKey.source})`)}`);
|
|
230098
|
+
console.log(` ${green(`Gateway: ${result.apiKey.gateway}`)}`);
|
|
230099
|
+
if (result.apiKey.accepted === true) {
|
|
230100
|
+
console.log(` ${green("Key accepted by BlockRun")}`);
|
|
230101
|
+
} else if (result.apiKey.accepted === false) {
|
|
230102
|
+
console.log(` ${red("Key REJECTED by BlockRun (401) \u2014 revoked or mistyped")}`);
|
|
230103
|
+
console.log(` ${yellow(`Mint a new key: ${PORTAL_KEYS_URL}`)}`);
|
|
230104
|
+
} else {
|
|
230105
|
+
console.log(` ${yellow("Could not verify the key (gateway unreachable)")}`);
|
|
230106
|
+
}
|
|
230107
|
+
console.log(` ${green(`Credit: billed server-side \u2014 top up at ${PORTAL_CREDITS_URL}`)}`);
|
|
230108
|
+
printRestOfDiagnostics(result);
|
|
230109
|
+
return;
|
|
230110
|
+
}
|
|
229693
230111
|
console.log("\nWallet");
|
|
229694
230112
|
if (result.wallet.exists && result.wallet.valid) {
|
|
229695
230113
|
const walletPath = result.wallet.source === "core" ? CORE_WALLET_FILE : WALLET_FILE;
|
|
@@ -229717,6 +230135,9 @@ function printDiagnostics(result) {
|
|
|
229717
230135
|
} else {
|
|
229718
230136
|
console.log(` ${red("No wallet found")}`);
|
|
229719
230137
|
}
|
|
230138
|
+
printRestOfDiagnostics(result);
|
|
230139
|
+
}
|
|
230140
|
+
function printRestOfDiagnostics(result) {
|
|
229720
230141
|
console.log("\nNetwork");
|
|
229721
230142
|
if (result.network.blockrunApi.reachable) {
|
|
229722
230143
|
console.log(
|
|
@@ -229761,11 +230182,15 @@ function createDoctorX402Client(opts) {
|
|
|
229761
230182
|
const publicClient = createPublicClient({ chain: base, transport: http() });
|
|
229762
230183
|
const evmSigner = toClientEvmSigner(account, publicClient);
|
|
229763
230184
|
const x402 = new x402Client();
|
|
229764
|
-
registerSpendPolicyHook(x402, opts.spendControl ??
|
|
230185
|
+
registerSpendPolicyHook(x402, opts.spendControl ?? getSharedSpendControl());
|
|
229765
230186
|
registerExactEvmScheme(x402, { signer: evmSigner });
|
|
229766
230187
|
return x402;
|
|
229767
230188
|
}
|
|
229768
230189
|
async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
230190
|
+
if (diagnostics.apiKey.configured) {
|
|
230191
|
+
await analyzeWithApiKey(diagnostics, userQuestion, model);
|
|
230192
|
+
return;
|
|
230193
|
+
}
|
|
229769
230194
|
if (diagnostics.wallet.isEmpty) {
|
|
229770
230195
|
console.log("\n\u{1F4B3} Wallet is empty - cannot call AI for analysis.");
|
|
229771
230196
|
console.log(` Fund your EVM wallet with USDC on Base: ${diagnostics.wallet.address}`);
|
|
@@ -229813,30 +230238,7 @@ async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
|
229813
230238
|
body: JSON.stringify({
|
|
229814
230239
|
model: modelConfig.id,
|
|
229815
230240
|
stream: false,
|
|
229816
|
-
messages:
|
|
229817
|
-
{
|
|
229818
|
-
role: "system",
|
|
229819
|
-
content: `You are a technical support expert for BlockRun and ClawRouter.
|
|
229820
|
-
Analyze the diagnostics and:
|
|
229821
|
-
1. Identify the root cause of any issues
|
|
229822
|
-
2. Provide specific, actionable fix commands (bash)
|
|
229823
|
-
3. Explain why the issue occurred briefly
|
|
229824
|
-
4. Be concise but thorough
|
|
229825
|
-
5. Format commands in code blocks`
|
|
229826
|
-
},
|
|
229827
|
-
{
|
|
229828
|
-
role: "user",
|
|
229829
|
-
content: userQuestion ? `Here are my system diagnostics:
|
|
229830
|
-
|
|
229831
|
-
${JSON.stringify(diagnostics, null, 2)}
|
|
229832
|
-
|
|
229833
|
-
User's question: ${userQuestion}` : `Here are my system diagnostics:
|
|
229834
|
-
|
|
229835
|
-
${JSON.stringify(diagnostics, null, 2)}
|
|
229836
|
-
|
|
229837
|
-
Please analyze and help me fix any issues.`
|
|
229838
|
-
}
|
|
229839
|
-
],
|
|
230241
|
+
messages: doctorMessages(diagnostics, userQuestion),
|
|
229840
230242
|
max_tokens: 1e3
|
|
229841
230243
|
})
|
|
229842
230244
|
});
|
|
@@ -229860,13 +230262,91 @@ Error calling AI: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
229860
230262
|
console.log("Try again or check your wallet balance.\n");
|
|
229861
230263
|
}
|
|
229862
230264
|
}
|
|
230265
|
+
async function analyzeWithApiKey(diagnostics, userQuestion, model) {
|
|
230266
|
+
const resolved = await resolveApiKey();
|
|
230267
|
+
if (!resolved) return;
|
|
230268
|
+
const modelConfig = DOCTOR_MODELS[model];
|
|
230269
|
+
console.log(`
|
|
230270
|
+
\u{1F4E4} Sending to ${modelConfig.name} (${modelConfig.cost})...
|
|
230271
|
+
`);
|
|
230272
|
+
try {
|
|
230273
|
+
const response = await fetch(`${BLOCKRUN_API_KEY_API}/v1/chat/completions`, {
|
|
230274
|
+
method: "POST",
|
|
230275
|
+
headers: {
|
|
230276
|
+
"Content-Type": "application/json",
|
|
230277
|
+
authorization: `Bearer ${resolved.key}`
|
|
230278
|
+
},
|
|
230279
|
+
body: JSON.stringify({
|
|
230280
|
+
model: modelConfig.id,
|
|
230281
|
+
stream: false,
|
|
230282
|
+
messages: doctorMessages(diagnostics, userQuestion),
|
|
230283
|
+
max_tokens: 1e3
|
|
230284
|
+
})
|
|
230285
|
+
});
|
|
230286
|
+
if (!response.ok) {
|
|
230287
|
+
const text = await response.text();
|
|
230288
|
+
console.log(`Error: ${response.status} - ${text}`);
|
|
230289
|
+
if (response.status === 402) {
|
|
230290
|
+
console.log(`
|
|
230291
|
+
Your BlockRun credit is exhausted. Top up: ${PORTAL_CREDITS_URL}
|
|
230292
|
+
`);
|
|
230293
|
+
} else if (response.status === 401) {
|
|
230294
|
+
console.log(`
|
|
230295
|
+
BlockRun rejected the key. Mint a new one: ${PORTAL_KEYS_URL}
|
|
230296
|
+
`);
|
|
230297
|
+
}
|
|
230298
|
+
return;
|
|
230299
|
+
}
|
|
230300
|
+
const data = await response.json();
|
|
230301
|
+
const content = data.choices?.[0]?.message?.content;
|
|
230302
|
+
if (content) {
|
|
230303
|
+
console.log("\u{1F916} AI Analysis:\n");
|
|
230304
|
+
console.log(content);
|
|
230305
|
+
console.log();
|
|
230306
|
+
} else {
|
|
230307
|
+
console.log("Error: No response from AI");
|
|
230308
|
+
}
|
|
230309
|
+
} catch (err) {
|
|
230310
|
+
console.log(`
|
|
230311
|
+
Error calling AI: ${err instanceof Error ? err.message : String(err)}`);
|
|
230312
|
+
console.log(`Try again, or check your credit at ${PORTAL_CREDITS_URL}
|
|
230313
|
+
`);
|
|
230314
|
+
}
|
|
230315
|
+
}
|
|
230316
|
+
function doctorMessages(diagnostics, userQuestion) {
|
|
230317
|
+
return [
|
|
230318
|
+
{
|
|
230319
|
+
role: "system",
|
|
230320
|
+
content: `You are a technical support expert for BlockRun and ClawRouter.
|
|
230321
|
+
Analyze the diagnostics and:
|
|
230322
|
+
1. Identify the root cause of any issues
|
|
230323
|
+
2. Provide specific, actionable fix commands (bash)
|
|
230324
|
+
3. Explain why the issue occurred briefly
|
|
230325
|
+
4. Be concise but thorough
|
|
230326
|
+
5. Format commands in code blocks`
|
|
230327
|
+
},
|
|
230328
|
+
{
|
|
230329
|
+
role: "user",
|
|
230330
|
+
content: userQuestion ? `Here are my system diagnostics:
|
|
230331
|
+
|
|
230332
|
+
${JSON.stringify(diagnostics, null, 2)}
|
|
230333
|
+
|
|
230334
|
+
User's question: ${userQuestion}` : `Here are my system diagnostics:
|
|
230335
|
+
|
|
230336
|
+
${JSON.stringify(diagnostics, null, 2)}
|
|
230337
|
+
|
|
230338
|
+
Please analyze and help me fix any issues.`
|
|
230339
|
+
}
|
|
230340
|
+
];
|
|
230341
|
+
}
|
|
229863
230342
|
async function runDoctor(userQuestion, model = "sonnet") {
|
|
229864
230343
|
console.log(`
|
|
229865
230344
|
\u{1FA7A} BlockRun Doctor v${VERSION}
|
|
229866
230345
|
`);
|
|
230346
|
+
const apiKey = await collectApiKeyInfo();
|
|
229867
230347
|
const [system, wallet, network, logs, latestVersion] = await Promise.all([
|
|
229868
230348
|
collectSystemInfo(),
|
|
229869
|
-
collectWalletInfo(),
|
|
230349
|
+
collectWalletInfo(apiKey.configured),
|
|
229870
230350
|
collectNetworkInfo(),
|
|
229871
230351
|
collectLogInfo(),
|
|
229872
230352
|
fetchLatestVersion()
|
|
@@ -229876,6 +230356,7 @@ async function runDoctor(userQuestion, model = "sonnet") {
|
|
|
229876
230356
|
latestVersion,
|
|
229877
230357
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
229878
230358
|
system,
|
|
230359
|
+
apiKey,
|
|
229879
230360
|
wallet,
|
|
229880
230361
|
network,
|
|
229881
230362
|
logs,
|
|
@@ -229889,12 +230370,15 @@ async function runDoctor(userQuestion, model = "sonnet") {
|
|
|
229889
230370
|
// src/cli.ts
|
|
229890
230371
|
init_partners();
|
|
229891
230372
|
init_policy();
|
|
230373
|
+
init_api_key();
|
|
229892
230374
|
function printHelp() {
|
|
229893
230375
|
console.log(`
|
|
229894
230376
|
ClawRouter v${VERSION} - Smart LLM Router
|
|
229895
230377
|
|
|
229896
230378
|
Usage:
|
|
229897
230379
|
clawrouter [options]
|
|
230380
|
+
clawrouter login [brk_...] # Use a BlockRun API key (card-funded credit)
|
|
230381
|
+
clawrouter logout # Forget the API key and go back to the wallet
|
|
229898
230382
|
clawrouter status # Live proxy status (wallet, balance, chain)
|
|
229899
230383
|
clawrouter wallet # Wallet details + balance
|
|
229900
230384
|
clawrouter models # List available models
|
|
@@ -229945,11 +230429,20 @@ Management Commands:
|
|
|
229945
230429
|
partners List available partner APIs with pricing
|
|
229946
230430
|
partners test Test partner API endpoints (expect 402 = alive)
|
|
229947
230431
|
wallet recover Restore wallet.key from mnemonic (if generated by ClawRouter)
|
|
230432
|
+
login Store a BlockRun API key in ~/.blockrun/.api-key (persists)
|
|
230433
|
+
logout Delete the stored API key and fall back to the wallet
|
|
229948
230434
|
chain solana Switch to Solana (persists)
|
|
229949
230435
|
chain base Switch to Base EVM (persists)
|
|
229950
230436
|
policy Edit spend limits and counterparty lists (fail-closed; restart proxy to apply)
|
|
229951
230437
|
|
|
230438
|
+
Paying for calls \u2014 two ways, and ClawRouter prefers the API key when both exist:
|
|
230439
|
+
API key Sign up at ${PORTAL_URL}, top up with a card, mint a key, then run
|
|
230440
|
+
"clawrouter login brk_live_...". Calls go to api.blockrun.ai.
|
|
230441
|
+
Wallet The default. A USDC wallet is generated on first run and pays for
|
|
230442
|
+
each call over x402 \u2014 Solana for new installs, Base for older ones.
|
|
230443
|
+
|
|
229952
230444
|
Environment Variables:
|
|
230445
|
+
BLOCKRUN_API_KEY BlockRun API key (brk_...) \u2014 wins over the wallet when set
|
|
229953
230446
|
BLOCKRUN_WALLET_KEY Private key for x402 payments (auto-generated if not set)
|
|
229954
230447
|
BLOCKRUN_PROXY_PORT Default proxy port (default: 8402)
|
|
229955
230448
|
|
|
@@ -230032,7 +230525,7 @@ Use: clawrouter share <id> --as=<feishu|slack|discord|telegram|whatsapp|plain>
|
|
|
230032
230525
|
const idOrLast = tgt === "last" ? "last" : tgt;
|
|
230033
230526
|
if (all3) {
|
|
230034
230527
|
try {
|
|
230035
|
-
const { writeFile:
|
|
230528
|
+
const { writeFile: writeFile4 } = await import("fs/promises");
|
|
230036
230529
|
const { tmpdir } = await import("os");
|
|
230037
230530
|
const { join: pjoin } = await import("path");
|
|
230038
230531
|
const paths = [];
|
|
@@ -230040,7 +230533,7 @@ Use: clawrouter share <id> --as=<feishu|slack|discord|telegram|whatsapp|plain>
|
|
|
230040
230533
|
const path5 = idOrLast === "last" ? `/share/last?as=${p}` : `/share/${idOrLast}/render?as=${p}`;
|
|
230041
230534
|
const result = await queryProxy(path5, port);
|
|
230042
230535
|
const file = pjoin(tmpdir(), `claw-share-${result.id}-${p}.txt`);
|
|
230043
|
-
await
|
|
230536
|
+
await writeFile4(file, result.rendered, "utf8");
|
|
230044
230537
|
paths.push(file);
|
|
230045
230538
|
}
|
|
230046
230539
|
console.log("\n\u2713 Wrote all 6 preset variants:");
|
|
@@ -230085,12 +230578,19 @@ async function cmdStatus(port) {
|
|
|
230085
230578
|
ClawRouter Status (port ${port})
|
|
230086
230579
|
`);
|
|
230087
230580
|
console.log(` Status: ${data.status}`);
|
|
230088
|
-
|
|
230089
|
-
|
|
230090
|
-
|
|
230091
|
-
|
|
230092
|
-
|
|
230093
|
-
|
|
230581
|
+
if (data.authMode === "api-key") {
|
|
230582
|
+
console.log(` Auth: BlockRun API key ${data.apiKey}`);
|
|
230583
|
+
console.log(` Gateway: ${data.gateway}`);
|
|
230584
|
+
console.log(` Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
230585
|
+
} else {
|
|
230586
|
+
console.log(` Auth: wallet (x402)`);
|
|
230587
|
+
console.log(` Payment Chain: ${data.paymentChain}`);
|
|
230588
|
+
console.log(` EVM Wallet: ${data.wallet}`);
|
|
230589
|
+
if (data.solana) console.log(` Solana Wallet: ${data.solana}`);
|
|
230590
|
+
console.log(` Balance: ${data.balance ?? "unknown"}`);
|
|
230591
|
+
if (data.isLow) console.log(` \u26A0 Balance is low`);
|
|
230592
|
+
if (data.isEmpty) console.log(` \u26A0 Balance is empty \u2014 using free models`);
|
|
230593
|
+
}
|
|
230094
230594
|
if (data.upstreamProxy) console.log(` Upstream Proxy: ${data.upstreamProxy}`);
|
|
230095
230595
|
console.log();
|
|
230096
230596
|
} catch {
|
|
@@ -230102,6 +230602,20 @@ ClawRouter Status (port ${port})
|
|
|
230102
230602
|
async function cmdWallet(port) {
|
|
230103
230603
|
try {
|
|
230104
230604
|
const data = await queryProxy("/health?full=true", port);
|
|
230605
|
+
if (data.authMode === "api-key") {
|
|
230606
|
+
console.log(`
|
|
230607
|
+
ClawRouter Account
|
|
230608
|
+
`);
|
|
230609
|
+
console.log(` Auth: BlockRun API key ${data.apiKey}`);
|
|
230610
|
+
console.log(` Gateway: ${data.gateway}`);
|
|
230611
|
+
console.log(` Billing: account credit (no wallet, no chain)`);
|
|
230612
|
+
console.log(` Top up: ${PORTAL_CREDITS_URL}`);
|
|
230613
|
+
console.log(` Usage: ${PORTAL_URL}/dashboard`);
|
|
230614
|
+
console.log();
|
|
230615
|
+
console.log(` To pay with a USDC wallet instead: clawrouter logout`);
|
|
230616
|
+
console.log();
|
|
230617
|
+
return;
|
|
230618
|
+
}
|
|
230105
230619
|
console.log(`
|
|
230106
230620
|
ClawRouter Wallet
|
|
230107
230621
|
`);
|
|
@@ -230327,8 +230841,8 @@ async function cmdSetup() {
|
|
|
230327
230841
|
unlinkSync: unlinkSync2,
|
|
230328
230842
|
writeFileSync: writeFileSync4
|
|
230329
230843
|
} = await import("fs");
|
|
230330
|
-
const { dirname: dirname4, join:
|
|
230331
|
-
const { homedir:
|
|
230844
|
+
const { dirname: dirname4, join: join18 } = await import("path");
|
|
230845
|
+
const { homedir: homedir15 } = await import("os");
|
|
230332
230846
|
const { injectModelsConfig: injectModelsConfig2, injectAuthProfile: injectAuthProfile2, syncAgentModelCache: syncAgentModelCache2, VISIBLE_OPENCLAW_MODELS: VISIBLE_OPENCLAW_MODELS2 } = await Promise.resolve().then(() => (init_index(), index_exports));
|
|
230333
230847
|
const { BLOCKRUN_PLUGIN_ID: BLOCKRUN_PLUGIN_ID2, prepareBlockRunPluginConfig: prepareBlockRunPluginConfig2 } = await Promise.resolve().then(() => (init_openclaw_plugin_config(), openclaw_plugin_config_exports));
|
|
230334
230848
|
console.log("\u{1F99E} ClawRouter setup\n");
|
|
@@ -230343,19 +230857,19 @@ async function cmdSetup() {
|
|
|
230343
230857
|
} catch {
|
|
230344
230858
|
}
|
|
230345
230859
|
if (process.env.npm_config_prefix) {
|
|
230346
|
-
candidates.push(
|
|
230860
|
+
candidates.push(join18(process.env.npm_config_prefix, "bin", "openclaw"));
|
|
230347
230861
|
}
|
|
230348
230862
|
try {
|
|
230349
230863
|
const npmRoot = execSync("npm root -g", {
|
|
230350
230864
|
encoding: "utf8",
|
|
230351
230865
|
stdio: ["ignore", "pipe", "ignore"]
|
|
230352
230866
|
}).trim();
|
|
230353
|
-
if (npmRoot) candidates.push(
|
|
230867
|
+
if (npmRoot) candidates.push(join18(dirname4(dirname4(npmRoot)), "bin", "openclaw"));
|
|
230354
230868
|
} catch {
|
|
230355
230869
|
}
|
|
230356
230870
|
candidates.push(
|
|
230357
|
-
|
|
230358
|
-
|
|
230871
|
+
join18(homedir15(), ".npm-global", "bin", "openclaw"),
|
|
230872
|
+
join18(homedir15(), ".local", "bin", "openclaw"),
|
|
230359
230873
|
"/usr/local/bin/openclaw"
|
|
230360
230874
|
);
|
|
230361
230875
|
return candidates.find((candidate) => existsSync5(candidate)) ?? "";
|
|
@@ -230367,7 +230881,7 @@ async function cmdSetup() {
|
|
|
230367
230881
|
process.exit(1);
|
|
230368
230882
|
}
|
|
230369
230883
|
console.log(` \u2713 Found openclaw at ${openclawPath}`);
|
|
230370
|
-
const configPath =
|
|
230884
|
+
const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
|
|
230371
230885
|
const configInitiallyExisted = existsSync5(configPath);
|
|
230372
230886
|
const configRollbackBackup = configInitiallyExisted ? `${configPath}.before-${BLOCKRUN_PLUGIN_ID2}.${Date.now()}` : void 0;
|
|
230373
230887
|
if (configRollbackBackup) copyFileSync2(configPath, configRollbackBackup);
|
|
@@ -230375,23 +230889,23 @@ async function cmdSetup() {
|
|
|
230375
230889
|
return configRollbackBackup;
|
|
230376
230890
|
};
|
|
230377
230891
|
let installAttempted = false;
|
|
230378
|
-
const openclawRoot =
|
|
230892
|
+
const openclawRoot = join18(homedir15(), ".openclaw");
|
|
230379
230893
|
const findManagedBlockRunInstalls = () => {
|
|
230380
230894
|
const candidates = [
|
|
230381
|
-
|
|
230382
|
-
|
|
230383
|
-
|
|
230895
|
+
join18(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
|
|
230896
|
+
join18(openclawRoot, "extensions", "clawrouter"),
|
|
230897
|
+
join18(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
|
|
230384
230898
|
];
|
|
230385
|
-
const projectsDir =
|
|
230899
|
+
const projectsDir = join18(openclawRoot, "npm", "projects");
|
|
230386
230900
|
try {
|
|
230387
230901
|
for (const project of readdirSync2(projectsDir)) {
|
|
230388
|
-
candidates.push(
|
|
230902
|
+
candidates.push(join18(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
|
|
230389
230903
|
}
|
|
230390
230904
|
} catch {
|
|
230391
230905
|
}
|
|
230392
230906
|
return [...new Set(candidates)].filter((candidate) => {
|
|
230393
230907
|
try {
|
|
230394
|
-
const metadata = JSON.parse(readFileSync4(
|
|
230908
|
+
const metadata = JSON.parse(readFileSync4(join18(candidate, "package.json"), "utf8"));
|
|
230395
230909
|
return metadata.name === "@blockrun/clawrouter";
|
|
230396
230910
|
} catch {
|
|
230397
230911
|
return false;
|
|
@@ -230399,7 +230913,7 @@ async function cmdSetup() {
|
|
|
230399
230913
|
});
|
|
230400
230914
|
};
|
|
230401
230915
|
const initialPluginDirs = findManagedBlockRunInstalls();
|
|
230402
|
-
const setupRollbackRoot =
|
|
230916
|
+
const setupRollbackRoot = join18(
|
|
230403
230917
|
openclawRoot,
|
|
230404
230918
|
"blockrun",
|
|
230405
230919
|
"setup-rollbacks",
|
|
@@ -230409,7 +230923,7 @@ async function cmdSetup() {
|
|
|
230409
230923
|
if (initialPluginDirs.length > 0) {
|
|
230410
230924
|
mkdirSync4(setupRollbackRoot, { recursive: true, mode: 448 });
|
|
230411
230925
|
for (const [index2, pluginDir] of initialPluginDirs.entries()) {
|
|
230412
|
-
const backup =
|
|
230926
|
+
const backup = join18(setupRollbackRoot, String(index2));
|
|
230413
230927
|
cpSync(pluginDir, backup, { recursive: true });
|
|
230414
230928
|
pluginRollbackBackups.set(pluginDir, backup);
|
|
230415
230929
|
}
|
|
@@ -230454,8 +230968,8 @@ async function cmdSetup() {
|
|
|
230454
230968
|
if (existsSync5(configPath)) {
|
|
230455
230969
|
try {
|
|
230456
230970
|
const config = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
230457
|
-
const legacyPackagePath =
|
|
230458
|
-
|
|
230971
|
+
const legacyPackagePath = join18(
|
|
230972
|
+
homedir15(),
|
|
230459
230973
|
".openclaw",
|
|
230460
230974
|
"extensions",
|
|
230461
230975
|
"clawrouter",
|
|
@@ -230639,7 +231153,10 @@ function parseArgs(args) {
|
|
|
230639
231153
|
phoneArg: void 0,
|
|
230640
231154
|
phoneAreaCode: void 0,
|
|
230641
231155
|
policy: false,
|
|
230642
|
-
policyArgs: []
|
|
231156
|
+
policyArgs: [],
|
|
231157
|
+
login: false,
|
|
231158
|
+
loginKey: void 0,
|
|
231159
|
+
logout: false
|
|
230643
231160
|
};
|
|
230644
231161
|
for (let i = 0; i < args.length; i++) {
|
|
230645
231162
|
const arg = args[i];
|
|
@@ -230700,6 +231217,14 @@ function parseArgs(args) {
|
|
|
230700
231217
|
} else if ((arg === "chain" || arg === "/wallet") && (args[i + 1] === "solana" || args[i + 1] === "base")) {
|
|
230701
231218
|
result.chain = args[i + 1];
|
|
230702
231219
|
i++;
|
|
231220
|
+
} else if (arg === "login") {
|
|
231221
|
+
result.login = true;
|
|
231222
|
+
if (args[i + 1] && !args[i + 1].startsWith("-")) {
|
|
231223
|
+
result.loginKey = args[i + 1];
|
|
231224
|
+
i++;
|
|
231225
|
+
}
|
|
231226
|
+
} else if (arg === "logout") {
|
|
231227
|
+
result.logout = true;
|
|
230703
231228
|
} else if (arg === "policy") {
|
|
230704
231229
|
result.policy = true;
|
|
230705
231230
|
result.policyArgs = args.slice(i + 1);
|
|
@@ -230782,6 +231307,58 @@ function parseArgs(args) {
|
|
|
230782
231307
|
}
|
|
230783
231308
|
return result;
|
|
230784
231309
|
}
|
|
231310
|
+
async function promptForApiKey() {
|
|
231311
|
+
const { createInterface } = await import("readline/promises");
|
|
231312
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
231313
|
+
try {
|
|
231314
|
+
console.log(`Sign up, add credit with a card, and mint a key at ${PORTAL_KEYS_URL}`);
|
|
231315
|
+
return (await rl.question("Paste your BlockRun API key (brk_...): ")).trim();
|
|
231316
|
+
} finally {
|
|
231317
|
+
rl.close();
|
|
231318
|
+
}
|
|
231319
|
+
}
|
|
231320
|
+
function installShutdownHandlers(proxy) {
|
|
231321
|
+
const shutdown = async (signal) => {
|
|
231322
|
+
console.log(`
|
|
231323
|
+
[ClawRouter] Received ${signal}, shutting down...`);
|
|
231324
|
+
try {
|
|
231325
|
+
await proxy.close();
|
|
231326
|
+
console.log(`[ClawRouter] Proxy closed`);
|
|
231327
|
+
process.exit(0);
|
|
231328
|
+
} catch (err) {
|
|
231329
|
+
console.error(`[ClawRouter] Error during shutdown: ${err}`);
|
|
231330
|
+
process.exit(1);
|
|
231331
|
+
}
|
|
231332
|
+
};
|
|
231333
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
231334
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
231335
|
+
}
|
|
231336
|
+
async function startWithApiKey(apiKey, source, args) {
|
|
231337
|
+
const origin2 = source === "env" ? "BLOCKRUN_API_KEY" : source === "core" ? "~/.blockrun/.api-key" : source;
|
|
231338
|
+
console.log(`[ClawRouter] Using BlockRun API key ${maskApiKey(apiKey)} (from ${origin2})`);
|
|
231339
|
+
const proxy = await startProxy({
|
|
231340
|
+
apiKey,
|
|
231341
|
+
port: args.port,
|
|
231342
|
+
allowExistingProxy: !args.noReuse,
|
|
231343
|
+
onReady: (port) => {
|
|
231344
|
+
console.log(`[ClawRouter] v${VERSION} | Proxy listening on http://127.0.0.1:${port}`);
|
|
231345
|
+
console.log(`[ClawRouter] Health check: http://127.0.0.1:${port}/health`);
|
|
231346
|
+
},
|
|
231347
|
+
onError: (error) => {
|
|
231348
|
+
console.error(`[ClawRouter] Error: ${error.message}`);
|
|
231349
|
+
},
|
|
231350
|
+
onRouted: (decision) => {
|
|
231351
|
+
const cost = decision.costEstimate.toFixed(4);
|
|
231352
|
+
const saved = (decision.savings * 100).toFixed(0);
|
|
231353
|
+
console.log(`[ClawRouter] [${decision.tier}] ${decision.model} $${cost} (saved ${saved}%)`);
|
|
231354
|
+
}
|
|
231355
|
+
});
|
|
231356
|
+
console.log(`[ClawRouter] Billing: BlockRun account credit \u2014 ${PORTAL_CREDITS_URL}`);
|
|
231357
|
+
console.log(`[ClawRouter] Ready - Ctrl+C to stop`);
|
|
231358
|
+
installShutdownHandlers(proxy);
|
|
231359
|
+
await new Promise(() => {
|
|
231360
|
+
});
|
|
231361
|
+
}
|
|
230785
231362
|
async function main() {
|
|
230786
231363
|
const args = parseArgs(process.argv.slice(2));
|
|
230787
231364
|
if (args.version) {
|
|
@@ -230919,6 +231496,37 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
230919
231496
|
await recoverWalletFromMnemonic();
|
|
230920
231497
|
process.exit(0);
|
|
230921
231498
|
}
|
|
231499
|
+
if (args.login) {
|
|
231500
|
+
const key2 = args.loginKey ?? await promptForApiKey();
|
|
231501
|
+
if (!isValidApiKey(key2)) {
|
|
231502
|
+
console.error(`[ClawRouter] That does not look like a BlockRun API key (expected brk_...).`);
|
|
231503
|
+
console.error(`[ClawRouter] Sign up and mint one at ${PORTAL_KEYS_URL}`);
|
|
231504
|
+
process.exit(1);
|
|
231505
|
+
}
|
|
231506
|
+
const path5 = await saveApiKey(key2);
|
|
231507
|
+
console.log(`[ClawRouter] Signed in with ${maskApiKey(key2)}`);
|
|
231508
|
+
console.log(`[ClawRouter] Key saved to ${path5} (readable only by you)`);
|
|
231509
|
+
console.log(`[ClawRouter] Calls now go to api.blockrun.ai and bill your account credit.`);
|
|
231510
|
+
console.log(`[ClawRouter] Top up: ${PORTAL_CREDITS_URL}`);
|
|
231511
|
+
console.log(`[ClawRouter] Restart the proxy to apply.`);
|
|
231512
|
+
process.exit(0);
|
|
231513
|
+
}
|
|
231514
|
+
if (args.logout) {
|
|
231515
|
+
const { removed, envStillSet } = await clearApiKey();
|
|
231516
|
+
if (removed.length === 0) {
|
|
231517
|
+
console.log(`[ClawRouter] No stored API key \u2014 already using the wallet.`);
|
|
231518
|
+
} else {
|
|
231519
|
+
for (const path5 of removed) console.log(`[ClawRouter] Removed ${path5}`);
|
|
231520
|
+
console.log(`[ClawRouter] Back to wallet mode (x402). Restart the proxy to apply.`);
|
|
231521
|
+
}
|
|
231522
|
+
if (envStillSet) {
|
|
231523
|
+
console.warn(
|
|
231524
|
+
`[ClawRouter] \u26A0 BLOCKRUN_API_KEY is still set in this environment and overrides the wallet.`
|
|
231525
|
+
);
|
|
231526
|
+
console.warn(`[ClawRouter] Unset it too: unset BLOCKRUN_API_KEY`);
|
|
231527
|
+
}
|
|
231528
|
+
process.exit(0);
|
|
231529
|
+
}
|
|
230922
231530
|
if (args.chain) {
|
|
230923
231531
|
const targetChain = args.chain;
|
|
230924
231532
|
if (targetChain === "solana") {
|
|
@@ -230955,6 +231563,11 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
230955
231563
|
console.log(report);
|
|
230956
231564
|
process.exit(0);
|
|
230957
231565
|
}
|
|
231566
|
+
const apiKeyResolution = await resolveApiKey();
|
|
231567
|
+
if (apiKeyResolution) {
|
|
231568
|
+
await startWithApiKey(apiKeyResolution.key, apiKeyResolution.source, args);
|
|
231569
|
+
return;
|
|
231570
|
+
}
|
|
230958
231571
|
const wallet = await resolveOrGenerateWalletKey();
|
|
230959
231572
|
if (wallet.source === "generated") {
|
|
230960
231573
|
console.log(`[ClawRouter] Generated new wallet: ${wallet.address}`);
|
|
@@ -231017,20 +231630,7 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
231017
231630
|
console.log(`[ClawRouter] Wallet: ${displayAddress} (balance check pending)`);
|
|
231018
231631
|
}
|
|
231019
231632
|
console.log(`[ClawRouter] Ready - Ctrl+C to stop`);
|
|
231020
|
-
|
|
231021
|
-
console.log(`
|
|
231022
|
-
[ClawRouter] Received ${signal}, shutting down...`);
|
|
231023
|
-
try {
|
|
231024
|
-
await proxy.close();
|
|
231025
|
-
console.log(`[ClawRouter] Proxy closed`);
|
|
231026
|
-
process.exit(0);
|
|
231027
|
-
} catch (err) {
|
|
231028
|
-
console.error(`[ClawRouter] Error during shutdown: ${err}`);
|
|
231029
|
-
process.exit(1);
|
|
231030
|
-
}
|
|
231031
|
-
};
|
|
231032
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
231033
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
231633
|
+
installShutdownHandlers(proxy);
|
|
231034
231634
|
await new Promise(() => {
|
|
231035
231635
|
});
|
|
231036
231636
|
}
|