@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/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.$(date +%s)"
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
- # ── Step 1b: Remove Crossmint/lobster extension ───────────────
242
- # lobster.cash is a third-party plugin that conflicts with /wallet command.
243
- # Remove it so ClawRouter owns /wallet without conflict.
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
- // Remove stale plugin entries (check all case variants)
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(p => p !== 'clawrouter' && p !== 'ClawRouter' && p !== '@blockrun/clawrouter');
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(' Removed ClawRouter from plugins.allow (re-added after install)'); }
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
- CREDS_DIR="$HOME/.openclaw/credentials"
373
- CREDS_BACKUP=""
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
- [ -d "$stale" ] && rm -rf "$stale"
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 @blockrun/clawrouter 2>&1 | tee "$OPENCLAW_INSTALL_LOG" || {
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 @blockrun/clawrouter 2>&1 | tee "$OPENCLAW_INSTALL_LOG" || {
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
- # Install is complete — clear the rollback trap immediately.
569
- # From this point on, Ctrl+C or errors should NOT roll back the install.
570
- if [ "$OPENCLAW_INSTALL_RECOVERABLE" != "1" ]; then
571
- trap - EXIT INT TERM
572
- fi
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/"* "$CREDS_DIR/"
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
- # openclaw plugins install writes plugins.entries.ClawRouter (PascalCase, from
686
- # plugin name) AND plugins.installs.clawrouter (lowercase, from plugin id).
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 ['clawrouter', 'ClawRouter', '@blockrun/clawrouter']) {
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 [ -d "$stage_dir" ]; then
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."* "$HOME/.openclaw/blockrun/clawrouter.backup."*; do
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 tmp = filePath + '.tmp.' + process.pid;
1062
- fs.writeFileSync(tmp, data);
1063
- fs.renameSync(tmp, filePath);
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) => plugin?.pluginId !== 'clawrouter');
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 ""