@blockrun/clawrouter 0.12.265 → 0.12.267
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 +15 -1
- package/dist/cli.js +1389 -417
- package/dist/index.d.ts +52 -23
- package/dist/index.js +1107 -388
- package/docs/clawrouter-cuts-llm-api-costs-500x.md +1 -1
- package/docs/configuration.md +26 -24
- package/docs/troubleshooting.md +6 -2
- package/openclaw.plugin.json +2 -1
- package/package.json +3 -2
- package/scripts/reinstall.sh +181 -77
- package/scripts/uninstall.sh +53 -8
- package/scripts/update.ps1 +215 -33
- package/scripts/update.sh +223 -86
package/scripts/update.sh
CHANGED
|
@@ -10,13 +10,42 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
10
10
|
# restores it if the update process somehow wiped it.
|
|
11
11
|
# ─────────────────────────────────────────────────────────────
|
|
12
12
|
|
|
13
|
-
PLUGIN_DIR="$HOME/.openclaw/extensions/clawrouter"
|
|
13
|
+
PLUGIN_DIR="$HOME/.openclaw/extensions/blockrun-clawrouter"
|
|
14
|
+
LEGACY_PLUGIN_DIR="$HOME/.openclaw/extensions/clawrouter"
|
|
14
15
|
CONFIG_PATH="$HOME/.openclaw/openclaw.json"
|
|
15
16
|
WALLET_FILE="$HOME/.openclaw/blockrun/wallet.key"
|
|
16
17
|
WALLET_BACKUP=""
|
|
18
|
+
WALLET_EXISTED=0
|
|
17
19
|
PLUGIN_BACKUP=""
|
|
20
|
+
LEGACY_PLUGIN_BACKUP=""
|
|
21
|
+
LEGACY_BLOCKRUN_INSTALL=0
|
|
18
22
|
CONFIG_BACKUP=""
|
|
23
|
+
CONFIG_EXISTED=0
|
|
24
|
+
CREDS_DIR="$HOME/.openclaw/credentials"
|
|
25
|
+
CREDS_BACKUP=""
|
|
26
|
+
CREDS_EXISTED=0
|
|
19
27
|
ACTIVE_PACKAGE_DIR=""
|
|
28
|
+
INSTALL_SPEC="${BLOCKRUN_CLAWROUTER_INSTALL_SPEC:-@blockrun/clawrouter}"
|
|
29
|
+
|
|
30
|
+
[ -f "$WALLET_FILE" ] && WALLET_EXISTED=1
|
|
31
|
+
[ -f "$CONFIG_PATH" ] && CONFIG_EXISTED=1
|
|
32
|
+
[ -d "$CREDS_DIR" ] && CREDS_EXISTED=1
|
|
33
|
+
|
|
34
|
+
is_blockrun_plugin_dir() {
|
|
35
|
+
local candidate_dir="$1"
|
|
36
|
+
[ -d "$candidate_dir" ] || return 1
|
|
37
|
+
node -e '
|
|
38
|
+
const fs = require("fs");
|
|
39
|
+
const path = require("path");
|
|
40
|
+
const dir = process.argv[1];
|
|
41
|
+
for (const candidate of [path.join(dir, "package.json"), path.join(dir, "package", "package.json")]) {
|
|
42
|
+
try {
|
|
43
|
+
if (JSON.parse(fs.readFileSync(candidate, "utf8")).name === "@blockrun/clawrouter") process.exit(0);
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
process.exit(1);
|
|
47
|
+
' "$candidate_dir"
|
|
48
|
+
}
|
|
20
49
|
|
|
21
50
|
NPM_PREFIX="$(npm config get prefix 2>/dev/null || echo "")"
|
|
22
51
|
if [ -n "$NPM_PREFIX" ] && [ -d "$NPM_PREFIX/bin" ]; then
|
|
@@ -32,7 +61,8 @@ const path = require('path');
|
|
|
32
61
|
|
|
33
62
|
const home = os.homedir();
|
|
34
63
|
const candidates = [
|
|
35
|
-
{ packagePath: path.join(home, '.openclaw', 'extensions', 'clawrouter', 'package.json'), priority: 0 },
|
|
64
|
+
{ packagePath: path.join(home, '.openclaw', 'extensions', 'blockrun-clawrouter', 'package.json'), priority: 0 },
|
|
65
|
+
{ packagePath: path.join(home, '.openclaw', 'extensions', 'clawrouter', 'package.json'), priority: 3 },
|
|
36
66
|
{ packagePath: path.join(home, '.openclaw', 'npm', 'node_modules', '@blockrun', 'clawrouter', 'package.json'), priority: 2 },
|
|
37
67
|
];
|
|
38
68
|
|
|
@@ -111,9 +141,15 @@ cleanup_backups() {
|
|
|
111
141
|
if [ -n "$PLUGIN_BACKUP" ] && [ -d "$PLUGIN_BACKUP" ]; then
|
|
112
142
|
rm -rf "$PLUGIN_BACKUP"
|
|
113
143
|
fi
|
|
144
|
+
if [ -n "$LEGACY_PLUGIN_BACKUP" ] && [ -d "$LEGACY_PLUGIN_BACKUP" ]; then
|
|
145
|
+
rm -rf "$LEGACY_PLUGIN_BACKUP"
|
|
146
|
+
fi
|
|
114
147
|
if [ -n "$CONFIG_BACKUP" ] && [ -f "$CONFIG_BACKUP" ]; then
|
|
115
148
|
rm -f "$CONFIG_BACKUP"
|
|
116
149
|
fi
|
|
150
|
+
if [ -n "$CREDS_BACKUP" ] && [ -d "$CREDS_BACKUP" ]; then
|
|
151
|
+
rm -rf "$(dirname "$CREDS_BACKUP")"
|
|
152
|
+
fi
|
|
117
153
|
}
|
|
118
154
|
|
|
119
155
|
restore_previous_install() {
|
|
@@ -132,9 +168,34 @@ restore_previous_install() {
|
|
|
132
168
|
echo " ✓ Restored previous plugin files"
|
|
133
169
|
fi
|
|
134
170
|
|
|
171
|
+
if [ -n "$LEGACY_PLUGIN_BACKUP" ] && [ -d "$LEGACY_PLUGIN_BACKUP" ]; then
|
|
172
|
+
mv "$LEGACY_PLUGIN_BACKUP" "$LEGACY_PLUGIN_DIR"
|
|
173
|
+
echo " ✓ Restored previous legacy BlockRun plugin files"
|
|
174
|
+
fi
|
|
175
|
+
|
|
135
176
|
if [ -n "$CONFIG_BACKUP" ] && [ -f "$CONFIG_BACKUP" ]; then
|
|
136
177
|
cp "$CONFIG_BACKUP" "$CONFIG_PATH"
|
|
137
178
|
echo " ✓ Restored previous OpenClaw config"
|
|
179
|
+
elif [ "$CONFIG_EXISTED" = "0" ]; then
|
|
180
|
+
rm -f "$CONFIG_PATH"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
if [ "$WALLET_EXISTED" = "1" ] && [ -n "$WALLET_BACKUP" ] && [ -f "$WALLET_BACKUP" ]; then
|
|
184
|
+
mkdir -p "$(dirname "$WALLET_FILE")"
|
|
185
|
+
cp "$WALLET_BACKUP" "$WALLET_FILE"
|
|
186
|
+
chmod 600 "$WALLET_FILE"
|
|
187
|
+
echo " ✓ Restored previous wallet"
|
|
188
|
+
elif [ "$WALLET_EXISTED" = "0" ]; then
|
|
189
|
+
rm -f "$WALLET_FILE"
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
if [ "$CREDS_EXISTED" = "1" ] && [ -n "$CREDS_BACKUP" ] && [ -d "$CREDS_BACKUP" ]; then
|
|
193
|
+
rm -rf "$CREDS_DIR"
|
|
194
|
+
mkdir -p "$CREDS_DIR"
|
|
195
|
+
cp -a "$CREDS_BACKUP/." "$CREDS_DIR/"
|
|
196
|
+
echo " ✓ Restored OpenClaw credentials"
|
|
197
|
+
elif [ "$CREDS_EXISTED" = "0" ]; then
|
|
198
|
+
rm -rf "$CREDS_DIR"
|
|
138
199
|
fi
|
|
139
200
|
fi
|
|
140
201
|
|
|
@@ -188,6 +249,7 @@ ACTIVE_PACKAGE_DIR=$(find_clawrouter_package_dir)
|
|
|
188
249
|
echo "→ Checking wallet..."
|
|
189
250
|
|
|
190
251
|
if [ -f "$WALLET_FILE" ]; then
|
|
252
|
+
WALLET_EXISTED=1
|
|
191
253
|
# Validate the key looks correct before backing up
|
|
192
254
|
WALLET_KEY=$(cat "$WALLET_FILE" | tr -d '[:space:]')
|
|
193
255
|
KEY_LEN=${#WALLET_KEY}
|
|
@@ -205,7 +267,7 @@ if [ -f "$WALLET_FILE" ]; then
|
|
|
205
267
|
}
|
|
206
268
|
" 2>/dev/null || echo "(address check skipped)")
|
|
207
269
|
|
|
208
|
-
WALLET_BACKUP="$HOME/.openclaw/blockrun/wallet.key.bak
|
|
270
|
+
WALLET_BACKUP="$(mktemp "$HOME/.openclaw/blockrun/wallet.key.bak.XXXXXX")"
|
|
209
271
|
cp "$WALLET_FILE" "$WALLET_BACKUP"
|
|
210
272
|
chmod 600 "$WALLET_BACKUP"
|
|
211
273
|
|
|
@@ -230,7 +292,17 @@ else
|
|
|
230
292
|
echo " ℹ No existing plugin files found"
|
|
231
293
|
fi
|
|
232
294
|
|
|
295
|
+
# Stage a pre-rename directory only when its package metadata proves ownership.
|
|
296
|
+
# The same path may otherwise belong to OpenClaw's official bundled router.
|
|
297
|
+
if is_blockrun_plugin_dir "$LEGACY_PLUGIN_DIR"; then
|
|
298
|
+
LEGACY_BLOCKRUN_INSTALL=1
|
|
299
|
+
LEGACY_PLUGIN_BACKUP="$HOME/.openclaw/blockrun/clawrouter.legacy.backup.$(date +%s)"
|
|
300
|
+
mv "$LEGACY_PLUGIN_DIR" "$LEGACY_PLUGIN_BACKUP"
|
|
301
|
+
echo " ✓ Legacy BlockRun plugin staged at: $LEGACY_PLUGIN_BACKUP"
|
|
302
|
+
fi
|
|
303
|
+
|
|
233
304
|
if [ -f "$CONFIG_PATH" ]; then
|
|
305
|
+
CONFIG_EXISTED=1
|
|
234
306
|
CONFIG_BACKUP="$CONFIG_PATH.clawrouter-update.$(date +%s).bak"
|
|
235
307
|
cp "$CONFIG_PATH" "$CONFIG_BACKUP"
|
|
236
308
|
echo " ✓ Config backed up to: $CONFIG_BACKUP"
|
|
@@ -238,44 +310,9 @@ fi
|
|
|
238
310
|
|
|
239
311
|
echo ""
|
|
240
312
|
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
echo "→ Removing Crossmint/lobster extension..."
|
|
245
|
-
LOBSTER_DIR="$HOME/.openclaw/extensions/lobster.cash"
|
|
246
|
-
if [ -d "$LOBSTER_DIR" ]; then
|
|
247
|
-
rm -rf "$LOBSTER_DIR"
|
|
248
|
-
echo " ✓ Removed $LOBSTER_DIR"
|
|
249
|
-
else
|
|
250
|
-
echo " ✓ Not installed"
|
|
251
|
-
fi
|
|
252
|
-
# Clean crossmint/lobster from openclaw.json
|
|
253
|
-
node -e "
|
|
254
|
-
const fs = require('fs');
|
|
255
|
-
const configPath = '$CONFIG_PATH';
|
|
256
|
-
if (!fs.existsSync(configPath)) process.exit(0);
|
|
257
|
-
try {
|
|
258
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
259
|
-
let changed = false;
|
|
260
|
-
// Remove from plugins.entries
|
|
261
|
-
for (const key of ['lobster.cash', 'lobster', 'crossmint']) {
|
|
262
|
-
if (config?.plugins?.entries?.[key]) { delete config.plugins.entries[key]; changed = true; console.log(' Removed plugins.entries.' + key); }
|
|
263
|
-
if (config?.plugins?.installs?.[key]) { delete config.plugins.installs[key]; changed = true; }
|
|
264
|
-
}
|
|
265
|
-
// Remove from plugins.allow
|
|
266
|
-
if (Array.isArray(config?.plugins?.allow)) {
|
|
267
|
-
const before = config.plugins.allow.length;
|
|
268
|
-
config.plugins.allow = config.plugins.allow.filter(p => !['lobster.cash','lobster','crossmint'].includes(p));
|
|
269
|
-
if (config.plugins.allow.length !== before) { changed = true; console.log(' Removed lobster/crossmint from plugins.allow'); }
|
|
270
|
-
}
|
|
271
|
-
if (changed) {
|
|
272
|
-
const tmp = configPath + '.tmp.' + process.pid;
|
|
273
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
|
|
274
|
-
fs.renameSync(tmp, configPath);
|
|
275
|
-
} else { console.log(' Config clean'); }
|
|
276
|
-
} catch (e) { console.log(' Skipped: ' + e.message); }
|
|
277
|
-
"
|
|
278
|
-
echo ""
|
|
313
|
+
# Third-party plugins are never removed by a ClawRouter update. If another
|
|
314
|
+
# plugin owns the same slash command, OpenClaw reports the conflict and the
|
|
315
|
+
# user can choose which plugin to disable.
|
|
279
316
|
|
|
280
317
|
# ── Step 2: Kill old proxy ──────────────────────────────────────
|
|
281
318
|
echo "→ Stopping old proxy..."
|
|
@@ -297,7 +334,7 @@ kill_port_processes 8402
|
|
|
297
334
|
# The old plugin dir is staged in a backup above. Remove the stale
|
|
298
335
|
# plugin entry so a fresh install can proceed, and restore it on error.
|
|
299
336
|
echo "→ Cleaning config..."
|
|
300
|
-
node -e "
|
|
337
|
+
LEGACY_BLOCKRUN_INSTALL="$LEGACY_BLOCKRUN_INSTALL" node -e "
|
|
301
338
|
const fs = require('fs');
|
|
302
339
|
const path = require('path');
|
|
303
340
|
const configPath = '$CONFIG_PATH';
|
|
@@ -306,21 +343,34 @@ try {
|
|
|
306
343
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
307
344
|
let changed = false;
|
|
308
345
|
|
|
309
|
-
//
|
|
346
|
+
// Never remove lowercase clawrouter: OpenClaw's official router owns it.
|
|
310
347
|
const entries = config?.plugins?.entries;
|
|
311
348
|
const installs = config?.plugins?.installs;
|
|
312
|
-
for (const key of ['clawrouter', 'ClawRouter', '@blockrun/clawrouter']) {
|
|
349
|
+
for (const key of ['blockrun-clawrouter', 'ClawRouter', '@blockrun/clawrouter']) {
|
|
313
350
|
if (entries?.[key]) { delete entries[key]; changed = true; console.log(' Removed plugins.entries.' + key); }
|
|
314
351
|
if (installs?.[key]) { delete installs[key]; changed = true; console.log(' Removed plugins.installs.' + key); }
|
|
315
352
|
}
|
|
353
|
+
if (process.env.LEGACY_BLOCKRUN_INSTALL === '1' && entries?.clawrouter) {
|
|
354
|
+
const legacy = entries.clawrouter;
|
|
355
|
+
for (const key of ['walletKey', 'routing']) {
|
|
356
|
+
if (Object.prototype.hasOwnProperty.call(legacy, key)) { delete legacy[key]; changed = true; }
|
|
357
|
+
if (legacy.config && Object.prototype.hasOwnProperty.call(legacy.config, key)) {
|
|
358
|
+
delete legacy.config[key];
|
|
359
|
+
changed = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (legacy.config && Object.keys(legacy.config).length === 0) delete legacy.config;
|
|
363
|
+
}
|
|
316
364
|
|
|
317
365
|
// Clean plugins.allow — remove only ClawRouter entries; preserve every other
|
|
318
366
|
// plugin the user has allowed, including bare local/custom plugin IDs.
|
|
319
367
|
if (Array.isArray(config?.plugins?.allow)) {
|
|
320
368
|
const before = config.plugins.allow.length;
|
|
321
|
-
config.plugins.allow = config.plugins.allow.filter(
|
|
369
|
+
config.plugins.allow = config.plugins.allow.filter(
|
|
370
|
+
p => p !== 'blockrun-clawrouter' && p !== 'ClawRouter' && p !== '@blockrun/clawrouter'
|
|
371
|
+
);
|
|
322
372
|
const removed = before - config.plugins.allow.length;
|
|
323
|
-
if (removed > 0) { changed = true; console.log('
|
|
373
|
+
if (removed > 0) { changed = true; console.log(' Staged BlockRun plugin config for reinstall'); }
|
|
324
374
|
}
|
|
325
375
|
|
|
326
376
|
// OpenClaw 2026.5.2+ validates tools.web.search.provider at config-load
|
|
@@ -336,8 +386,9 @@ try {
|
|
|
336
386
|
|
|
337
387
|
if (changed) {
|
|
338
388
|
const tmp = configPath + '.tmp.' + process.pid;
|
|
339
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
|
|
389
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
340
390
|
fs.renameSync(tmp, configPath);
|
|
391
|
+
fs.chmodSync(configPath, 0o600);
|
|
341
392
|
} else {
|
|
342
393
|
console.log(' Config clean');
|
|
343
394
|
}
|
|
@@ -361,17 +412,17 @@ try {
|
|
|
361
412
|
if (!provider.apiKey) { provider.apiKey = 'x402-proxy-handles-auth'; changed = true; console.log(' Fixed missing apiKey'); }
|
|
362
413
|
if (changed) {
|
|
363
414
|
const tmp = configPath + '.tmp.' + process.pid;
|
|
364
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
|
|
415
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
365
416
|
fs.renameSync(tmp, configPath);
|
|
417
|
+
fs.chmodSync(configPath, 0o600);
|
|
366
418
|
} else { console.log(' Provider config OK'); }
|
|
367
419
|
} catch (err) { console.log(' Skipped: ' + err.message); }
|
|
368
420
|
"
|
|
369
421
|
|
|
370
422
|
# ── Step 4: Install latest version ─────────────────────────────
|
|
371
423
|
# Back up OpenClaw credentials (channels, WhatsApp/Telegram state) before plugin install
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if [ -d "$CREDS_DIR" ] && [ "$(ls -A "$CREDS_DIR" 2>/dev/null)" ]; then
|
|
424
|
+
if [ -d "$CREDS_DIR" ]; then
|
|
425
|
+
CREDS_EXISTED=1
|
|
375
426
|
CREDS_BACKUP="$(mktemp -d)/openclaw-credentials-backup"
|
|
376
427
|
cp -a "$CREDS_DIR" "$CREDS_BACKUP"
|
|
377
428
|
echo " ✓ Backed up OpenClaw credentials"
|
|
@@ -400,7 +451,9 @@ fi
|
|
|
400
451
|
# openclaw plugins install scans the directory. If they exist during install,
|
|
401
452
|
# OpenClaw writes them into config as duplicate plugins.
|
|
402
453
|
for stale in "$HOME/.openclaw/extensions/clawrouter.backup."* "$HOME/.openclaw/extensions/.openclaw-install-stage-"*; do
|
|
403
|
-
|
|
454
|
+
if is_blockrun_plugin_dir "$stale"; then
|
|
455
|
+
rm -rf "$stale"
|
|
456
|
+
fi
|
|
404
457
|
done
|
|
405
458
|
|
|
406
459
|
apply_scoped_model_trim() {
|
|
@@ -495,8 +548,9 @@ if (totalDrop <= 0 || modelListDrop / totalDrop < 0.65) {
|
|
|
495
548
|
|
|
496
549
|
active.models.providers.blockrun.models = rejectedModels;
|
|
497
550
|
const tmpPath = `${activePath}.tmp.${process.pid}`;
|
|
498
|
-
fs.writeFileSync(tmpPath, JSON.stringify(active, null, 2));
|
|
551
|
+
fs.writeFileSync(tmpPath, JSON.stringify(active, null, 2), { mode: 0o600 });
|
|
499
552
|
fs.renameSync(tmpPath, activePath);
|
|
553
|
+
fs.chmodSync(activePath, 0o600);
|
|
500
554
|
|
|
501
555
|
console.log(
|
|
502
556
|
` ✓ Applied scoped BlockRun model-list trim (${activeModels.length} -> ${rejectedModels.length})`,
|
|
@@ -534,6 +588,10 @@ handle_openclaw_install_failure() {
|
|
|
534
588
|
}
|
|
535
589
|
|
|
536
590
|
echo "→ Installing latest ClawRouter..."
|
|
591
|
+
OPENCLAW_CAPABILITY_ARGS=()
|
|
592
|
+
if openclaw plugins install --help 2>&1 | grep -q -- '--accept-capabilities'; then
|
|
593
|
+
OPENCLAW_CAPABILITY_ARGS=(--accept-capabilities)
|
|
594
|
+
fi
|
|
537
595
|
# `--force` is required when the plugin is already installed at the same path
|
|
538
596
|
# (which is always true on update). Without it, OpenClaw exits non-zero with
|
|
539
597
|
# "plugin already exists" and our EXIT trap rolls back, stranding the user on
|
|
@@ -549,14 +607,14 @@ OPENCLAW_INSTALL_LOG="$(mktemp)"
|
|
|
549
607
|
# "Installed plugin: clawrouter" in OpenClaw v2026.4.5 (parallel plugin loading).
|
|
550
608
|
# 120s is enough for slow connections; the install itself completes in ~30s.
|
|
551
609
|
if command -v timeout >/dev/null 2>&1; then
|
|
552
|
-
timeout 120 openclaw plugins install --force @
|
|
610
|
+
timeout 120 openclaw plugins install --force "${OPENCLAW_CAPABILITY_ARGS[@]}" "$INSTALL_SPEC" 2>&1 | tee "$OPENCLAW_INSTALL_LOG" || {
|
|
553
611
|
exit_code=$?
|
|
554
612
|
handle_openclaw_install_failure "$exit_code" || {
|
|
555
613
|
[ "$OPENCLAW_INSTALL_RECOVERABLE" = "1" ] || exit $exit_code
|
|
556
614
|
}
|
|
557
615
|
}
|
|
558
616
|
else
|
|
559
|
-
openclaw plugins install --force @
|
|
617
|
+
openclaw plugins install --force "${OPENCLAW_CAPABILITY_ARGS[@]}" "$INSTALL_SPEC" 2>&1 | tee "$OPENCLAW_INSTALL_LOG" || {
|
|
560
618
|
exit_code=$?
|
|
561
619
|
handle_openclaw_install_failure "$exit_code" || {
|
|
562
620
|
[ "$OPENCLAW_INSTALL_RECOVERABLE" = "1" ] || exit $exit_code
|
|
@@ -565,16 +623,64 @@ else
|
|
|
565
623
|
fi
|
|
566
624
|
rm -f "$OPENCLAW_INSTALL_LOG"
|
|
567
625
|
|
|
568
|
-
#
|
|
569
|
-
#
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
626
|
+
# Enable only after the package exists so OpenClaw does not report a stale id
|
|
627
|
+
# while the previous install is staged outside extensions/.
|
|
628
|
+
node - "$CONFIG_PATH" "$CONFIG_BACKUP" "$LEGACY_BLOCKRUN_INSTALL" <<'NODE'
|
|
629
|
+
const fs = require('fs');
|
|
630
|
+
const [configPath, backupPath, legacyOwned] = process.argv.slice(2);
|
|
631
|
+
if (!fs.existsSync(configPath)) process.exit(0);
|
|
632
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
633
|
+
const preserved = backupPath && fs.existsSync(backupPath)
|
|
634
|
+
? JSON.parse(fs.readFileSync(backupPath, 'utf8'))
|
|
635
|
+
: {};
|
|
636
|
+
config.plugins ??= {};
|
|
637
|
+
config.plugins.entries ??= {};
|
|
638
|
+
const current = config.plugins.entries['blockrun-clawrouter'] || {};
|
|
639
|
+
const preservedCurrent = preserved?.plugins?.entries?.['blockrun-clawrouter'];
|
|
640
|
+
const moveOwnedFields = (source, destination, removeFromSource = false) => {
|
|
641
|
+
if (!source || typeof source !== 'object') return;
|
|
642
|
+
for (const key of ['walletKey', 'routing']) {
|
|
643
|
+
if (!(key in destination) && key in source) destination[key] = source[key];
|
|
644
|
+
if (removeFromSource) delete source[key];
|
|
645
|
+
}
|
|
646
|
+
if (source.config && typeof source.config === 'object') {
|
|
647
|
+
destination.config ??= {};
|
|
648
|
+
for (const key of ['walletKey', 'routing']) {
|
|
649
|
+
if (!(key in destination.config) && key in source.config) destination.config[key] = source.config[key];
|
|
650
|
+
if (removeFromSource) delete source.config[key];
|
|
651
|
+
}
|
|
652
|
+
if (Object.keys(destination.config).length === 0) delete destination.config;
|
|
653
|
+
if (removeFromSource && Object.keys(source.config).length === 0) delete source.config;
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
moveOwnedFields(preservedCurrent, current);
|
|
657
|
+
if (legacyOwned === '1') {
|
|
658
|
+
const legacy = config.plugins.entries.clawrouter;
|
|
659
|
+
const preservedLegacy = preserved?.plugins?.entries?.clawrouter;
|
|
660
|
+
if (typeof preservedLegacy?.enabled === 'boolean' && typeof current.enabled !== 'boolean') {
|
|
661
|
+
current.enabled = preservedLegacy.enabled;
|
|
662
|
+
}
|
|
663
|
+
moveOwnedFields(preservedLegacy, current);
|
|
664
|
+
moveOwnedFields(legacy, current, true);
|
|
665
|
+
}
|
|
666
|
+
current.enabled = true;
|
|
667
|
+
config.plugins.entries['blockrun-clawrouter'] = current;
|
|
668
|
+
if (Array.isArray(config.plugins.allow) && !config.plugins.allow.includes('blockrun-clawrouter')) {
|
|
669
|
+
config.plugins.allow.push('blockrun-clawrouter');
|
|
670
|
+
}
|
|
671
|
+
if (Array.isArray(config.plugins.deny)) {
|
|
672
|
+
config.plugins.deny = config.plugins.deny.filter(id => id !== 'blockrun-clawrouter');
|
|
673
|
+
}
|
|
674
|
+
const tmp = configPath + '.tmp.' + process.pid;
|
|
675
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
676
|
+
fs.renameSync(tmp, configPath);
|
|
677
|
+
fs.chmodSync(configPath, 0o600);
|
|
678
|
+
NODE
|
|
573
679
|
|
|
574
680
|
# Restore credentials after plugin install (always restore to preserve user's channels)
|
|
575
681
|
if [ -n "$CREDS_BACKUP" ] && [ -d "$CREDS_BACKUP" ]; then
|
|
576
682
|
mkdir -p "$CREDS_DIR"
|
|
577
|
-
cp -a "$CREDS_BACKUP
|
|
683
|
+
cp -a "$CREDS_BACKUP/." "$CREDS_DIR/"
|
|
578
684
|
echo " ✓ Restored OpenClaw credentials (channels preserved)"
|
|
579
685
|
rm -rf "$(dirname "$CREDS_BACKUP")"
|
|
580
686
|
fi
|
|
@@ -585,8 +691,9 @@ if [ -n "$CHANNEL_CONFIG_BACKUP" ] && [ -f "$CHANNEL_CONFIG_BACKUP" ] && [ -f "$
|
|
|
585
691
|
const fs = require('fs');
|
|
586
692
|
function atomicWrite(filePath, data) {
|
|
587
693
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
588
|
-
fs.writeFileSync(tmpPath, data);
|
|
694
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
589
695
|
fs.renameSync(tmpPath, filePath);
|
|
696
|
+
fs.chmodSync(filePath, 0o600);
|
|
590
697
|
}
|
|
591
698
|
try {
|
|
592
699
|
const config = JSON.parse(fs.readFileSync('$CONFIG_PATH', 'utf8'));
|
|
@@ -670,8 +777,6 @@ fi
|
|
|
670
777
|
|
|
671
778
|
echo " ✓ ClawRouter v${INSTALLED_VER} installed at $ACTIVE_PACKAGE_DIR"
|
|
672
779
|
sync_cli_shim "$ACTIVE_PACKAGE_DIR"
|
|
673
|
-
trap - EXIT INT TERM
|
|
674
|
-
|
|
675
780
|
# ── Step 4c: Ensure all dependencies are installed ────────────
|
|
676
781
|
# openclaw's plugin installer may skip native/optional deps like @solana/kit.
|
|
677
782
|
# Run npm install in the plugin directory to fill any gaps.
|
|
@@ -682,12 +787,8 @@ fi
|
|
|
682
787
|
|
|
683
788
|
# ── Step 5: Verify wallet survived ─────────────────────────────
|
|
684
789
|
# ── Step 4d: Post-install duplicate cleanup ──────────────────────
|
|
685
|
-
#
|
|
686
|
-
#
|
|
687
|
-
# The extensions/ directory scan also discovers the plugin by id.
|
|
688
|
-
# Having both entries + directory causes "duplicate plugin" warnings.
|
|
689
|
-
# Fix: keep only the installs record (used by OpenClaw's matchInstalledPlugin),
|
|
690
|
-
# remove the entries record that duplicates it.
|
|
790
|
+
# Remove only obsolete BlockRun aliases. Preserve both the current
|
|
791
|
+
# blockrun-clawrouter entry and OpenClaw's official clawrouter entry.
|
|
691
792
|
echo "→ Cleaning duplicate plugin entries..."
|
|
692
793
|
node -e "
|
|
693
794
|
const fs = require('fs');
|
|
@@ -697,7 +798,7 @@ try {
|
|
|
697
798
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
698
799
|
let changed = false;
|
|
699
800
|
// Remove entries that duplicate the installs record
|
|
700
|
-
for (const key of ['
|
|
801
|
+
for (const key of ['ClawRouter', '@blockrun/clawrouter']) {
|
|
701
802
|
if (config?.plugins?.entries?.[key]) {
|
|
702
803
|
delete config.plugins.entries[key];
|
|
703
804
|
changed = true;
|
|
@@ -706,8 +807,9 @@ try {
|
|
|
706
807
|
}
|
|
707
808
|
if (changed) {
|
|
708
809
|
const tmp = configPath + '.tmp.' + process.pid;
|
|
709
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
|
|
810
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
710
811
|
fs.renameSync(tmp, configPath);
|
|
812
|
+
fs.chmodSync(configPath, 0o600);
|
|
711
813
|
console.log(' ✓ Duplicate entries cleaned');
|
|
712
814
|
} else {
|
|
713
815
|
console.log(' ✓ No duplicates found');
|
|
@@ -733,8 +835,9 @@ try {
|
|
|
733
835
|
if (!provider.apiKey) { provider.apiKey = 'x402-proxy-handles-auth'; changed = true; console.log(' Fixed missing apiKey'); }
|
|
734
836
|
if (changed) {
|
|
735
837
|
const tmp = configPath + '.tmp.' + process.pid;
|
|
736
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
|
|
838
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
737
839
|
fs.renameSync(tmp, configPath);
|
|
840
|
+
fs.chmodSync(configPath, 0o600);
|
|
738
841
|
} else { console.log(' ✓ Provider config OK'); }
|
|
739
842
|
} catch (err) { console.log(' Skipped: ' + err.message); }
|
|
740
843
|
"
|
|
@@ -781,8 +884,9 @@ const authDir = path.join(os.homedir(), '.openclaw', 'agents', 'main', 'agent');
|
|
|
781
884
|
const authPath = path.join(authDir, 'auth-profiles.json');
|
|
782
885
|
function atomicWrite(filePath, data) {
|
|
783
886
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
784
|
-
fs.writeFileSync(tmpPath, data);
|
|
887
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
785
888
|
fs.renameSync(tmpPath, filePath);
|
|
889
|
+
fs.chmodSync(filePath, 0o600);
|
|
786
890
|
}
|
|
787
891
|
|
|
788
892
|
fs.mkdirSync(authDir, { recursive: true });
|
|
@@ -860,8 +964,9 @@ try {
|
|
|
860
964
|
|
|
861
965
|
// Atomic write
|
|
862
966
|
const tmpPath = configPath + '.tmp.' + process.pid;
|
|
863
|
-
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
|
|
967
|
+
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
864
968
|
fs.renameSync(tmpPath, configPath);
|
|
969
|
+
fs.chmodSync(configPath, 0o600);
|
|
865
970
|
|
|
866
971
|
if (removed > 0) {
|
|
867
972
|
console.log(' Removed ' + removed + ' deprecated models from allowlist');
|
|
@@ -886,8 +991,9 @@ const path = require('path');
|
|
|
886
991
|
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
887
992
|
function atomicWrite(filePath, data) {
|
|
888
993
|
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
889
|
-
fs.writeFileSync(tmpPath, data);
|
|
994
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
890
995
|
fs.renameSync(tmpPath, filePath);
|
|
996
|
+
fs.chmodSync(filePath, 0o600);
|
|
891
997
|
}
|
|
892
998
|
|
|
893
999
|
if (fs.existsSync(configPath)) {
|
|
@@ -916,7 +1022,7 @@ if (fs.existsSync(configPath)) {
|
|
|
916
1022
|
echo "→ Cleaning up stale install stages..."
|
|
917
1023
|
CLEANED=0
|
|
918
1024
|
for stage_dir in "$HOME/.openclaw/extensions/.openclaw-install-stage-"*; do
|
|
919
|
-
if
|
|
1025
|
+
if is_blockrun_plugin_dir "$stage_dir"; then
|
|
920
1026
|
rm -rf "$stage_dir"
|
|
921
1027
|
CLEANED=$((CLEANED + 1))
|
|
922
1028
|
fi
|
|
@@ -931,7 +1037,13 @@ fi
|
|
|
931
1037
|
# plugin detection), new ones live in blockrun/. Clean both locations.
|
|
932
1038
|
echo "→ Cleaning up stale plugin backups..."
|
|
933
1039
|
CLEANED=0
|
|
934
|
-
for backup_dir in "$HOME/.openclaw/extensions/clawrouter.backup."
|
|
1040
|
+
for backup_dir in "$HOME/.openclaw/extensions/clawrouter.backup."*; do
|
|
1041
|
+
if is_blockrun_plugin_dir "$backup_dir"; then
|
|
1042
|
+
rm -rf "$backup_dir"
|
|
1043
|
+
CLEANED=$((CLEANED + 1))
|
|
1044
|
+
fi
|
|
1045
|
+
done
|
|
1046
|
+
for backup_dir in "$HOME/.openclaw/blockrun/clawrouter.backup."*; do
|
|
935
1047
|
if [ -d "$backup_dir" ]; then
|
|
936
1048
|
rm -rf "$backup_dir"
|
|
937
1049
|
CLEANED=$((CLEANED + 1))
|
|
@@ -953,11 +1065,12 @@ try {
|
|
|
953
1065
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
954
1066
|
let changed = false;
|
|
955
1067
|
const isStale = (p) => p.includes('.openclaw-install-stage-') || p.includes('clawrouter.backup.');
|
|
1068
|
+
const blockrunIds = new Set(['blockrun-clawrouter', 'ClawRouter', '@blockrun/clawrouter']);
|
|
956
1069
|
// Remove plugins.entries pointing to stale directories
|
|
957
1070
|
if (config?.plugins?.entries) {
|
|
958
1071
|
for (const [key, val] of Object.entries(config.plugins.entries)) {
|
|
959
1072
|
const path = typeof val === 'string' ? val : val?.path || val?.main || '';
|
|
960
|
-
if (isStale(path)) {
|
|
1073
|
+
if (blockrunIds.has(key) && isStale(path)) {
|
|
961
1074
|
delete config.plugins.entries[key];
|
|
962
1075
|
changed = true;
|
|
963
1076
|
console.log(' Removed plugins.entries.' + key + ' (stale)');
|
|
@@ -968,7 +1081,7 @@ try {
|
|
|
968
1081
|
if (config?.plugins?.installs) {
|
|
969
1082
|
for (const [key, val] of Object.entries(config.plugins.installs)) {
|
|
970
1083
|
const path = typeof val === 'string' ? val : val?.path || val?.main || '';
|
|
971
|
-
if (isStale(path)) {
|
|
1084
|
+
if (blockrunIds.has(key) && isStale(path)) {
|
|
972
1085
|
delete config.plugins.installs[key];
|
|
973
1086
|
changed = true;
|
|
974
1087
|
console.log(' Removed plugins.installs.' + key + ' (stale)');
|
|
@@ -977,8 +1090,9 @@ try {
|
|
|
977
1090
|
}
|
|
978
1091
|
if (changed) {
|
|
979
1092
|
const tmp = configPath + '.tmp.' + process.pid;
|
|
980
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
|
|
1093
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
981
1094
|
fs.renameSync(tmp, configPath);
|
|
1095
|
+
fs.chmodSync(configPath, 0o600);
|
|
982
1096
|
console.log(' ✓ Registry cleaned');
|
|
983
1097
|
} else {
|
|
984
1098
|
console.log(' ✓ Registry clean');
|
|
@@ -1001,7 +1115,8 @@ const legacyPath = path.join(home, '.openclaw', 'plugins', 'installs.json');
|
|
|
1001
1115
|
|
|
1002
1116
|
function currentPackageCandidates() {
|
|
1003
1117
|
const candidates = [
|
|
1004
|
-
{ packagePath: path.join(home, '.openclaw', 'extensions', 'clawrouter', 'package.json'), priority: 0 },
|
|
1118
|
+
{ packagePath: path.join(home, '.openclaw', 'extensions', 'blockrun-clawrouter', 'package.json'), priority: 0 },
|
|
1119
|
+
{ packagePath: path.join(home, '.openclaw', 'extensions', 'clawrouter', 'package.json'), priority: 3 },
|
|
1005
1120
|
{
|
|
1006
1121
|
packagePath: path.join(home, '.openclaw', 'npm', 'node_modules', '@blockrun', 'clawrouter', 'package.json'),
|
|
1007
1122
|
priority: 2,
|
|
@@ -1058,9 +1173,10 @@ function newestCurrentPackage() {
|
|
|
1058
1173
|
}
|
|
1059
1174
|
|
|
1060
1175
|
function atomicWrite(filePath, data) {
|
|
1061
|
-
const
|
|
1062
|
-
fs.writeFileSync(
|
|
1063
|
-
fs.renameSync(
|
|
1176
|
+
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
1177
|
+
fs.writeFileSync(tmpPath, data, { mode: 0o600 });
|
|
1178
|
+
fs.renameSync(tmpPath, filePath);
|
|
1179
|
+
fs.chmodSync(filePath, 0o600);
|
|
1064
1180
|
}
|
|
1065
1181
|
|
|
1066
1182
|
try {
|
|
@@ -1083,6 +1199,19 @@ try {
|
|
|
1083
1199
|
process.exit(0);
|
|
1084
1200
|
}
|
|
1085
1201
|
|
|
1202
|
+
const recordText = JSON.stringify(legacyRecord);
|
|
1203
|
+
let provenBlockRun = recordText.includes('@blockrun/clawrouter');
|
|
1204
|
+
if (!provenBlockRun) {
|
|
1205
|
+
try {
|
|
1206
|
+
const packagePath = path.join(String(legacyRecord.installPath || ''), 'package.json');
|
|
1207
|
+
provenBlockRun = JSON.parse(fs.readFileSync(packagePath, 'utf8')).name === '@blockrun/clawrouter';
|
|
1208
|
+
} catch {}
|
|
1209
|
+
}
|
|
1210
|
+
if (!provenBlockRun) {
|
|
1211
|
+
console.log(' Preserved ambiguous official clawrouter install metadata');
|
|
1212
|
+
process.exit(0);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1086
1215
|
const currentVersion = String(currentPkg.version || '');
|
|
1087
1216
|
const legacyVersion = String(legacyRecord.version || legacyRecord.resolvedVersion || '');
|
|
1088
1217
|
const legacyPathValue = String(legacyRecord.installPath || '');
|
|
@@ -1096,7 +1225,13 @@ try {
|
|
|
1096
1225
|
|
|
1097
1226
|
delete index.installRecords.clawrouter;
|
|
1098
1227
|
if (Array.isArray(index.plugins)) {
|
|
1099
|
-
index.plugins = index.plugins.filter((plugin) =>
|
|
1228
|
+
index.plugins = index.plugins.filter((plugin) => {
|
|
1229
|
+
if (plugin?.pluginId !== 'clawrouter') return true;
|
|
1230
|
+
const text = JSON.stringify(plugin);
|
|
1231
|
+
if (text.includes('@blockrun/clawrouter')) return false;
|
|
1232
|
+
const pluginPath = String(plugin?.installPath || plugin?.path || '');
|
|
1233
|
+
return !pluginPath || path.resolve(pluginPath) !== path.resolve(legacyPathValue);
|
|
1234
|
+
});
|
|
1100
1235
|
}
|
|
1101
1236
|
index.refreshReason = 'clawrouter-stale-metadata-cleanup';
|
|
1102
1237
|
index.generatedAtMs = Date.now();
|
|
@@ -1108,6 +1243,8 @@ try {
|
|
|
1108
1243
|
"
|
|
1109
1244
|
|
|
1110
1245
|
# ── Summary ─────────────────────────────────────────────────────
|
|
1246
|
+
cleanup_backups
|
|
1247
|
+
trap - EXIT INT TERM
|
|
1111
1248
|
echo ""
|
|
1112
1249
|
echo "✓ ClawRouter updated successfully!"
|
|
1113
1250
|
echo ""
|