dexbot 1.4.14 → 1.4.16

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.
Files changed (74) hide show
  1. package/README.md +50 -59
  2. package/claw/data/positions.json +2939 -1
  3. package/claw/modules/chain_broadcast.ts +4 -5
  4. package/claw/modules/credit_runtime_adapter.ts +6 -7
  5. package/claw/modules/dexbot_profiles.ts +4 -1
  6. package/claw/modules/launcher_paths.ts +4 -1
  7. package/claw/package.json +1 -1
  8. package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
  9. package/claw/runtimes/openclaw-plugin/package.json +1 -1
  10. package/claw/tests/test_claw_mcp_transport.ts +2 -2
  11. package/dist/credential-daemon.js +7 -7
  12. package/dist/credential-daemon.js.map +1 -1
  13. package/dist/dexbot.d.ts.map +1 -1
  14. package/dist/dexbot.js +17 -1
  15. package/dist/dexbot.js.map +1 -1
  16. package/dist/market_adapter/core/market_adapter_service.d.ts.map +1 -1
  17. package/dist/market_adapter/core/market_adapter_service.js +7 -1
  18. package/dist/market_adapter/core/market_adapter_service.js.map +1 -1
  19. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +1 -2
  20. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js.map +1 -1
  21. package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
  22. package/dist/market_adapter/inputs/fetch_lp_data.js +2 -3
  23. package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
  24. package/dist/market_adapter/market_adapter.d.ts.map +1 -1
  25. package/dist/market_adapter/market_adapter.js +1 -0
  26. package/dist/market_adapter/market_adapter.js.map +1 -1
  27. package/dist/market_adapter/utils/data_discovery.js +2 -2
  28. package/dist/market_adapter/utils/data_discovery.js.map +1 -1
  29. package/dist/modules/chain_keys.d.ts.map +1 -1
  30. package/dist/modules/chain_keys.js +7 -1
  31. package/dist/modules/chain_keys.js.map +1 -1
  32. package/dist/modules/config.d.ts +3 -0
  33. package/dist/modules/config.d.ts.map +1 -1
  34. package/dist/modules/config.js +3 -0
  35. package/dist/modules/config.js.map +1 -1
  36. package/dist/modules/credential_policy.js +1 -1
  37. package/dist/modules/credential_policy.js.map +1 -1
  38. package/dist/modules/credential_runtime.d.ts +0 -3
  39. package/dist/modules/credential_runtime.d.ts.map +1 -1
  40. package/dist/modules/credential_runtime.js +5 -27
  41. package/dist/modules/credential_runtime.js.map +1 -1
  42. package/dist/modules/key_store.js +1 -1
  43. package/dist/modules/key_store.js.map +1 -1
  44. package/dist/modules/launcher/child_env.d.ts.map +1 -1
  45. package/dist/modules/launcher/child_env.js +3 -0
  46. package/dist/modules/launcher/child_env.js.map +1 -1
  47. package/dist/modules/launcher/credential_daemon.d.ts.map +1 -1
  48. package/dist/modules/launcher/credential_daemon.js +3 -3
  49. package/dist/modules/launcher/credential_daemon.js.map +1 -1
  50. package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
  51. package/dist/modules/launcher/monolithic_runtime.js +2 -2
  52. package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
  53. package/dist/modules/order/utils/system.d.ts.map +1 -1
  54. package/dist/modules/order/utils/system.js +11 -1
  55. package/dist/modules/order/utils/system.js.map +1 -1
  56. package/dist/modules/paths.d.ts +72 -2
  57. package/dist/modules/paths.d.ts.map +1 -1
  58. package/dist/modules/paths.js +199 -29
  59. package/dist/modules/paths.js.map +1 -1
  60. package/dist/pm2.js +3 -3
  61. package/dist/pm2.js.map +1 -1
  62. package/dist/scripts/update.d.ts +14 -5
  63. package/dist/scripts/update.d.ts.map +1 -1
  64. package/dist/scripts/update.js +335 -172
  65. package/dist/scripts/update.js.map +1 -1
  66. package/package.json +4 -3
  67. package/scripts/README.md +20 -4
  68. package/scripts/clear-all.sh +39 -12
  69. package/scripts/clear-logs.sh +1 -1
  70. package/scripts/clear-market-adapter.sh +3 -3
  71. package/scripts/clear-orders.sh +1 -1
  72. package/scripts/create-bot-symlinks.sh +4 -2
  73. package/scripts/lib/dexbot-paths.sh +114 -0
  74. package/scripts/reset-settings.sh +1 -1
@@ -2,11 +2,20 @@
2
2
  /**
3
3
  * DEXBot2 Auto-Update Script
4
4
  *
5
- * Manages pulling latest code from git repository with smart logic:
6
- * - Fetches from configured remote repository
7
- * - Detects if updates are available
8
- * - Handles branch switching if needed
9
- * - Reinstalls npm dependencies
5
+ * Supports two installation layouts:
6
+ *
7
+ * 1. Git checkout:
8
+ * - Fetches from configured remote repository
9
+ * - Detects if updates are available
10
+ * - Handles branch switching if needed
11
+ * - Reinstalls npm dependencies and rebuilds TypeScript sources
12
+ *
13
+ * 2. npm package install (e.g. `npm install -g dexbot`):
14
+ * - Compares the installed version against the npm registry
15
+ * - Runs `npm install -g <pkg>@<latest>` to fetch the newest release
16
+ * - Skips the git and local build steps (published packages are pre-built)
17
+ *
18
+ * Shared tail for both layouts:
10
19
  * - Selectively restarts active runtime processes
11
20
  * - Gracefully handles missing files or PM2
12
21
  *
@@ -23,11 +32,12 @@
23
32
  import { execSync } from 'node:child_process';
24
33
  import path from 'node:path';
25
34
  import fs from 'node:fs';
35
+ import { homedir } from 'node:os';
26
36
  import { sendControlCommand } from '../modules/launcher/supervisor_control.js';
27
37
  // Import update configuration from constants
28
38
  // Contains: REPOSITORY_URL, BRANCH, BUILD_DIR settings
29
39
  import { UPDATER, BUILD_DIR } from '../modules/constants.js';
30
- import { PATHS } from '../modules/paths.js';
40
+ import { PATHS, isGlobalNpmPackageDir } from '../modules/paths.js';
31
41
  import { Config } from '../modules/config.js';
32
42
  import { getStorage } from '../modules/storage/index.js';
33
43
  const { readJSON } = getStorage();
@@ -60,24 +70,101 @@ function updateError(msg) {
60
70
  return colorUpdateOutput(msg, UPDATE_COLORS.error, process.stderr);
61
71
  }
62
72
  /**
63
- * run: Execute shell command with error handling
73
+ * runIn: Execute shell command in a given working directory with error handling
64
74
  *
65
75
  * Runs command with inherited stdio so user sees full output.
66
76
  * Throws error if command fails, breaking update process.
67
77
  *
78
+ * @param {string} cwd - Working directory for the command
68
79
  * @param {string} cmd - Shell command to execute
69
80
  * @throws {Error} If command exits with non-zero status
70
81
  */
71
- function run(cmd) {
82
+ function runIn(cwd, cmd) {
72
83
  log(`Executing: ${cmd}`);
73
84
  try {
74
- execSync(cmd, { stdio: 'inherit', cwd: PATHS.PROJECT_ROOT });
85
+ execSync(cmd, { stdio: 'inherit', cwd });
75
86
  }
76
87
  catch (err) {
77
88
  console.error(updateError(`[ERROR] Command failed: ${cmd}`));
78
89
  throw err;
79
90
  }
80
91
  }
92
+ /**
93
+ * run: Execute shell command in the project root with error handling
94
+ *
95
+ * Runs command with inherited stdio so user sees full output.
96
+ * Throws error if command fails, breaking update process.
97
+ *
98
+ * @param {string} cmd - Shell command to execute
99
+ * @throws {Error} If command exits with non-zero status
100
+ */
101
+ function run(cmd) {
102
+ runIn(PATHS.PROJECT_ROOT, cmd);
103
+ }
104
+ // ── npm-install helpers ──────────────────────────────────────────────
105
+ function readPackageJson(root) {
106
+ try {
107
+ return readJSON(path.join(root, 'package.json'));
108
+ }
109
+ catch (_) {
110
+ return null;
111
+ }
112
+ }
113
+ function readPackageVersion(root) {
114
+ const pkg = readPackageJson(root);
115
+ return typeof pkg?.version === 'string' ? pkg.version : '';
116
+ }
117
+ function readPackageName(root) {
118
+ const pkg = readPackageJson(root);
119
+ return typeof pkg?.name === 'string' && pkg.name ? pkg.name : 'dexbot';
120
+ }
121
+ /**
122
+ * compareVersions: Minimal semver comparison for major.minor.patch strings.
123
+ *
124
+ * Returns a negative number when a < b, zero when equal, positive when a > b.
125
+ * Prerelease/build suffixes are ignored (sufficient for update detection).
126
+ */
127
+ function compareVersions(a, b) {
128
+ const na = a.split('.').map((n) => parseInt(n, 10) || 0);
129
+ const nb = b.split('.').map((n) => parseInt(n, 10) || 0);
130
+ const len = Math.max(na.length, nb.length);
131
+ for (let i = 0; i < len; i++) {
132
+ const va = na[i] ?? 0;
133
+ const vb = nb[i] ?? 0;
134
+ if (va !== vb)
135
+ return va < vb ? -1 : 1;
136
+ }
137
+ return 0;
138
+ }
139
+ /**
140
+ * getNpmLatestVersion: Query the npm registry for the latest published version.
141
+ *
142
+ * Throws when the registry is unreachable so the update is treated as failed
143
+ * (exit 1) and the scheduler does not restart the daemon for a no-op.
144
+ */
145
+ function getNpmLatestVersion(pkgName) {
146
+ try {
147
+ const version = execSync(`npm view ${pkgName} version`, { stdio: 'pipe' }).toString().trim();
148
+ if (!version)
149
+ throw new Error('npm returned an empty version.');
150
+ return version;
151
+ }
152
+ catch (err) {
153
+ throw new Error(`Could not reach the npm registry to check for updates (${getErrorMessage(err)}).`);
154
+ }
155
+ }
156
+ /**
157
+ * getGlobalNpmRoot: Resolve the npm global install root (e.g. <prefix>/lib/node_modules).
158
+ * Returns an empty string when npm is unavailable.
159
+ */
160
+ function getGlobalNpmRoot() {
161
+ try {
162
+ return execSync('npm root -g', { stdio: 'pipe' }).toString().trim();
163
+ }
164
+ catch (_) {
165
+ return '';
166
+ }
167
+ }
81
168
  function readLivePidFile(filePath) {
82
169
  if (!fs.existsSync(filePath))
83
170
  return 0;
@@ -236,21 +323,244 @@ async function restartActiveIsolatedProcesses() {
236
323
  await sendControlCommand({ cmd: 'restart-running' });
237
324
  return true;
238
325
  }
326
+ // ── Shared update tail (both git and npm layouts) ────────────────────
327
+ /**
328
+ * Snapshot pre-update runtime state BEFORE touching code (git pull or
329
+ * `npm install -g`). The daemon may shut down during the operation (e.g.
330
+ * a prepare hook or build), erasing its PID file. By capturing state up
331
+ * front, we can still restart it after the update completes.
332
+ */
333
+ function snapshotMonolithicState() {
334
+ const monolithicWasRunning = !!detectMonolithicRuntime();
335
+ const hadMonolithicFiles = detectAnyMonolithicFiles();
336
+ if (monolithicWasRunning) {
337
+ log('Monolithic daemon detected running before update. Will restart after update.');
338
+ }
339
+ else if (hadMonolithicFiles) {
340
+ log('Monolithic PID/file artifacts found but daemon is not alive. Will attempt restart after update.');
341
+ }
342
+ return { monolithicWasRunning, hadMonolithicFiles };
343
+ }
344
+ /**
345
+ * Regenerate the PM2 ecosystem config so profiles/ecosystem.config.cjs
346
+ * reflects the current bots.json state (including dexbot-adapter and
347
+ * dexbot-update service apps). Uses the freshly compiled dist/pm2.js.
348
+ */
349
+ async function regenerateEcosystemConfig() {
350
+ log('Regenerating PM2 ecosystem config...');
351
+ try {
352
+ // Try loading from compiled dist/ first, then fall back to source dir
353
+ const distPath = path.join(PATHS.PROJECT_ROOT, BUILD_DIR, 'pm2.js');
354
+ const pm2Module = fs.existsSync(distPath)
355
+ ? await import(distPath)
356
+ : await import(path.join(PATHS.PROJECT_ROOT, 'pm2.js'));
357
+ pm2Module.generateEcosystemConfig({ clawOnly: false, exitOnError: false });
358
+ log('Ecosystem config regenerated successfully.');
359
+ }
360
+ catch (err) {
361
+ log(`Warning: Ecosystem config regeneration failed (${getErrorMessage(err)}). Continuing with existing config.`);
362
+ }
363
+ }
364
+ /**
365
+ * Restart active runtime processes after an update so the new code is picked
366
+ * up. Handles, in order of preference:
367
+ * - Monolithic daemon (SIGUSR2 restart)
368
+ * - Isolated supervisor (control-command restart)
369
+ * - PM2 selective restart from bots.json
370
+ * - Fallback auto-start of a monolithic daemon that died during the update
371
+ * Never touches dexbot-cred through bulk PM2 actions.
372
+ */
373
+ async function restartActiveRuntimes({ monolithicWasRunning, hadMonolithicFiles }) {
374
+ let restarted = false;
375
+ log('Restarting active runtime processes...');
376
+ try {
377
+ if (Config.DEXBOT_UPDATE_SKIP_RELOAD) {
378
+ log('Restart skipped (managed by launcher).');
379
+ restarted = true;
380
+ }
381
+ else {
382
+ const monolithic = detectMonolithicRuntime();
383
+ if (monolithic) {
384
+ restartMonolithicRuntime(monolithic);
385
+ restarted = true;
386
+ }
387
+ else if (await restartActiveIsolatedProcesses()) {
388
+ log('Isolated supervisor runtime restarted.');
389
+ restarted = true;
390
+ }
391
+ else {
392
+ const BOTS_FILE = PATHS.PROFILES.BOTS_JSON;
393
+ if (fs.existsSync(BOTS_FILE)) {
394
+ const raw = fs.readFileSync(BOTS_FILE, 'utf8');
395
+ const stripped = raw.replace(/\/\*(?:.|[\r\n])*?\*\//g, '').replace(/(^|\s*)\/\/.*$/gm, '');
396
+ const config = JSON.parse(stripped);
397
+ const activeInConfig = (config.bots || [])
398
+ .filter((b) => b.active !== false)
399
+ .map((b) => b.name)
400
+ .filter((name) => !!name);
401
+ if (activeInConfig.length > 0) {
402
+ let runningProcesses = [];
403
+ try {
404
+ const output = execSync('pm2 jlist').toString().trim();
405
+ const jsonStart = output.indexOf('[');
406
+ if (jsonStart !== -1) {
407
+ const jsonPart = output.substring(jsonStart);
408
+ const parsed = JSON.parse(jsonPart);
409
+ runningProcesses = parsed.map((p) => p.name);
410
+ }
411
+ else {
412
+ log('Warning: PM2 jlist output did not contain JSON array.');
413
+ }
414
+ }
415
+ catch (e) {
416
+ log('Warning: Could not fetch PM2 process list. Falling back to config-only detection.');
417
+ runningProcesses = activeInConfig;
418
+ }
419
+ const botsToRestart = activeInConfig.filter((name) => runningProcesses.includes(name));
420
+ const activeBots = (config.bots || []).filter((b) => b.active !== false);
421
+ const runningActiveBots = activeBots.filter((b) => runningProcesses.includes(b.name));
422
+ const maPath = path.join(PATHS.PROJECT_ROOT, BUILD_DIR, 'pm2.js');
423
+ const pm2Module = fs.existsSync(maPath)
424
+ ? await import(maPath)
425
+ : await import(path.join(PATHS.PROJECT_ROOT, 'pm2.js'));
426
+ const marketAdapterRequired = pm2Module.needsMarketAdapter(runningActiveBots);
427
+ const serviceAppsToRestart = marketAdapterRequired ? ['dexbot-adapter'] : [];
428
+ const servicesToRestart = serviceAppsToRestart.filter((name) => runningProcesses.includes(name));
429
+ const allToRestart = [...botsToRestart, ...servicesToRestart];
430
+ if (allToRestart.length > 0) {
431
+ log(`Active processes detected: ${allToRestart.join(', ')}`);
432
+ for (const name of allToRestart) {
433
+ try {
434
+ run(`pm2 restart "${name}"`);
435
+ }
436
+ catch (e) {
437
+ log(`Warning: Failed to restart process "${name}" (it might not be running).`);
438
+ }
439
+ }
440
+ restarted = true;
441
+ }
442
+ else {
443
+ log('No active processes currently running in PM2. Skipping restart.');
444
+ }
445
+ if (marketAdapterRequired && !runningProcesses.includes('dexbot-adapter')) {
446
+ log('dexbot-adapter is required by an AMA-grid bot but not running. Starting from ecosystem...');
447
+ try {
448
+ run(`pm2 start "${PATHS.PROFILES.ECOSYSTEM_CONFIG_JS}" --only dexbot-adapter`);
449
+ }
450
+ catch (e) {
451
+ log('Warning: Failed to start dexbot-adapter from ecosystem config.');
452
+ }
453
+ }
454
+ }
455
+ else {
456
+ log('No active bots found in config.');
457
+ }
458
+ }
459
+ else {
460
+ log('Warning: profiles/bots.json not found, skipping selective restart.');
461
+ }
462
+ }
463
+ }
464
+ }
465
+ catch (err) {
466
+ log(`Warning: runtime restart logic failed (${getErrorMessage(err)}). Skipping bulk restart to avoid touching dexbot-cred.`);
467
+ }
468
+ // Fallback restart for monolithic daemon that died during the update
469
+ if (!restarted && !Config.DEXBOT_UPDATE_SKIP_RELOAD) {
470
+ if (monolithicWasRunning || hadMonolithicFiles) {
471
+ log('Monolithic daemon was detected before update but is no longer running. Auto-starting...');
472
+ if (startMonolithicRuntime()) {
473
+ restarted = true;
474
+ }
475
+ else {
476
+ log('Auto-start did not succeed; the manual-start instructions below apply.');
477
+ }
478
+ }
479
+ }
480
+ if (!restarted) {
481
+ console.log(colorUpdateOutput('\n⚠️ No active runtime was restarted.\n' +
482
+ ' If the daemon was running before, start it manually from a terminal:\n' +
483
+ ' dexbot start\n' +
484
+ ' (will prompt for master password; add --foreground for interactive mode)\n' +
485
+ ' For non-interactive automation:\n' +
486
+ ' dexbot start --headless --password-file <path>\n', UPDATE_COLORS.warn));
487
+ }
488
+ }
489
+ // ── npm-install update flow ──────────────────────────────────────────
490
+ /**
491
+ * Update a globally installed npm package to the latest published version.
492
+ *
493
+ * Steps:
494
+ * 1. Compare installed package.json version against the npm registry.
495
+ * 2. Exit cleanly (0) when already up to date — no restart needed.
496
+ * 3. Verify this is a global install (only that layout can be replaced in place).
497
+ * 4. Snapshot runtime state, then run `npm install -g <pkg>@<latest>` from a
498
+ * neutral cwd (home dir) so npm can freely swap the package directory the
499
+ * currently running process executes from.
500
+ * 5. Regenerate the ecosystem config and restart active runtimes using the
501
+ * new code — the published package ships a pre-built dist/, so there is
502
+ * no local TypeScript build step.
503
+ */
504
+ async function runNpmUpdateFlow() {
505
+ log('Detected npm package installation. Checking npm registry for updates...');
506
+ const pkgName = readPackageName(PATHS.PROJECT_ROOT);
507
+ const currentVersion = readPackageVersion(PATHS.PROJECT_ROOT);
508
+ if (!currentVersion) {
509
+ throw new Error(`Could not read a version from package.json at ${PATHS.PROJECT_ROOT}.`);
510
+ }
511
+ log(`Current installed version: ${currentVersion}`);
512
+ const latestVersion = getNpmLatestVersion(pkgName);
513
+ log(`Latest published version: ${latestVersion}`);
514
+ if (compareVersions(currentVersion, latestVersion) >= 0) {
515
+ log('DEXBot2 is already up to date (installed version matches or exceeds the latest published version).');
516
+ process.exit(0);
517
+ }
518
+ const globalRoot = getGlobalNpmRoot();
519
+ if (!globalRoot || !PATHS.PROJECT_ROOT.startsWith(globalRoot)) {
520
+ throw new Error('DEXBot2 is installed as a local (non-global) npm dependency; auto-update cannot replace the ' +
521
+ 'package in place. Run `npm update ' + pkgName + '` in the parent project directory and restart manually.');
522
+ }
523
+ log(`${currentVersion} -> ${latestVersion} update available. Proceeding with npm update...`);
524
+ // Snapshot pre-update runtime state BEFORE npm replaces the package files.
525
+ const snapshot = snapshotMonolithicState();
526
+ // Run from a neutral cwd: npm's reify swaps the package directory this
527
+ // process is executing from, so the working directory may briefly not exist.
528
+ runIn(homedir(), `npm install -g ${pkgName}@${latestVersion}`);
529
+ const installedAfter = readPackageVersion(PATHS.PROJECT_ROOT);
530
+ log(`Installed version after update: ${installedAfter || 'unknown'}`);
531
+ await regenerateEcosystemConfig();
532
+ await restartActiveRuntimes(snapshot);
533
+ }
239
534
  (async () => {
240
535
  try {
241
536
  // Change to project root for all git operations
242
537
  process.chdir(PATHS.PROJECT_ROOT);
243
538
  log('Starting DEXBot2 update process...');
244
- // Get configured repository URL and target branch
245
- const repoUrl = UPDATER.REPOSITORY_URL;
246
- let branch = UPDATER.BRANCH;
247
539
  /**
248
- * STEP 1: Validate Git Repository
249
- * Ensures .git directory exists so we can perform git operations
540
+ * STEP 0: Detect Installation Layout
541
+ *
542
+ * Two supported layouts:
543
+ * 1. Git checkout (has a .git dir) -> git pull + npm install + build
544
+ * 2. Global npm package install (path under a node_modules tree) ->
545
+ * `npm install -g <pkg>@<latest>` (pre-built dist, no local build)
546
+ *
547
+ * A git checkout wins over the npm path so a repo that was npm-linked is
548
+ * still updated through git. Neither layout -> fail with a clear message.
250
549
  */
251
- if (!fs.existsSync(path.join(PATHS.PROJECT_ROOT, '.git'))) {
252
- throw new Error('Not a git repository. If installed from npm, run: npm update -g dexbot');
550
+ const isGitRepo = fs.existsSync(path.join(PATHS.PROJECT_ROOT, '.git'));
551
+ const isNpmInstall = isGlobalNpmPackageDir(PATHS.PROJECT_ROOT);
552
+ if (!isGitRepo && isNpmInstall) {
553
+ await runNpmUpdateFlow();
554
+ logSuccess('DEXBot2 update completed successfully.');
555
+ process.exit(0);
253
556
  }
557
+ if (!isGitRepo) {
558
+ throw new Error('DEXBot2 is neither a git checkout nor an npm package install. ' +
559
+ 'Auto-update is not available for this installation.');
560
+ }
561
+ // Get configured repository URL and target branch
562
+ const repoUrl = UPDATER.REPOSITORY_URL;
563
+ let branch = UPDATER.BRANCH;
254
564
  /**
255
565
  * STEP 2: Detect Current Branch
256
566
  * Gets the current checked-out branch name
@@ -350,14 +660,9 @@ async function restartActiveIsolatedProcesses() {
350
660
  * hook or build), erasing its PID file. By capturing state up front,
351
661
  * we can still restart it after the build completes.
352
662
  */
353
- const monolithicWasRunning = !!detectMonolithicRuntime();
354
- const hadMonolithicFiles = detectAnyMonolithicFiles();
355
- if (monolithicWasRunning) {
356
- log('Monolithic daemon detected running before update. Will restart after build.');
357
- }
358
- else if (hadMonolithicFiles) {
359
- log('Monolithic PID/file artifacts found but daemon is not alive. Will attempt restart after build.');
360
- }
663
+ const snapshot = snapshotMonolithicState();
664
+ const monolithicWasRunning = snapshot.monolithicWasRunning;
665
+ const hadMonolithicFiles = snapshot.hadMonolithicFiles;
361
666
  /**
362
667
  * STEP 6b: Prepare Working Directory
363
668
  * Stashes local changes to ensure a clean pull.
@@ -489,157 +794,15 @@ async function restartActiveIsolatedProcesses() {
489
794
  * state, including service apps like dexbot-adapter and dexbot-update.
490
795
  * Uses the compiled dist/pm2.js (after TS build) for correct dist/ paths.
491
796
  */
492
- log('Regenerating PM2 ecosystem config...');
493
- try {
494
- // Try loading from compiled dist/ first, then fall back to source dir
495
- const distPath = path.join(PATHS.PROJECT_ROOT, BUILD_DIR, 'pm2.js');
496
- const pm2Module = fs.existsSync(distPath)
497
- ? await import(distPath)
498
- : await import(path.join(PATHS.PROJECT_ROOT, 'pm2.js'));
499
- pm2Module.generateEcosystemConfig({ clawOnly: false, exitOnError: false });
500
- log('Ecosystem config regenerated successfully.');
501
- }
502
- catch (err) {
503
- log(`Warning: Ecosystem config regeneration failed (${getErrorMessage(err)}). Continuing with existing config.`);
504
- }
797
+ await regenerateEcosystemConfig();
505
798
  /**
506
- * STEP 9: Restart Active Runtime Processes
799
+ * STEP 9/9b: Restart Active Runtime Processes
507
800
  * Intelligently restarts only the bots that were active before update
508
- * This approach:
509
- * - Preserves PM2 state if not running
510
- * - Restarts active bots to pick up code changes
511
- * - Handles missing bots.json gracefully
512
- * - Never restarts dexbot-cred through bulk PM2 actions
513
- *
514
- * Uses the pre-update snapshot (monolithicWasRunning) as a fallback:
515
- * if the daemon shut down during git/npm ops and its PID file is gone,
516
- * we still attempt to restart it.
801
+ * (monolithic, isolated supervisor, or PM2 selective restart), with a
802
+ * fallback auto-start for a monolithic daemon that died during the update.
803
+ * Never restarts dexbot-cred through bulk PM2 actions.
517
804
  */
518
- let restarted = false;
519
- log('Restarting active runtime processes...');
520
- try {
521
- if (Config.DEXBOT_UPDATE_SKIP_RELOAD) {
522
- log('Restart skipped (managed by launcher).');
523
- restarted = true;
524
- }
525
- else {
526
- const monolithic = detectMonolithicRuntime();
527
- if (monolithic) {
528
- restartMonolithicRuntime(monolithic);
529
- restarted = true;
530
- }
531
- else if (await restartActiveIsolatedProcesses()) {
532
- log('Isolated supervisor runtime restarted.');
533
- restarted = true;
534
- }
535
- else {
536
- const BOTS_FILE = PATHS.PROFILES.BOTS_JSON;
537
- if (fs.existsSync(BOTS_FILE)) {
538
- const raw = fs.readFileSync(BOTS_FILE, 'utf8');
539
- const stripped = raw.replace(/\/\*(?:.|[\r\n])*?\*\//g, '').replace(/(^|\s*)\/\/.*$/gm, '');
540
- const config = JSON.parse(stripped);
541
- const activeInConfig = (config.bots || [])
542
- .filter((b) => b.active !== false)
543
- .map((b) => b.name)
544
- .filter((name) => !!name);
545
- if (activeInConfig.length > 0) {
546
- let runningProcesses = [];
547
- try {
548
- const output = execSync('pm2 jlist').toString().trim();
549
- const jsonStart = output.indexOf('[');
550
- if (jsonStart !== -1) {
551
- const jsonPart = output.substring(jsonStart);
552
- const parsed = JSON.parse(jsonPart);
553
- runningProcesses = parsed.map((p) => p.name);
554
- }
555
- else {
556
- log('Warning: PM2 jlist output did not contain JSON array.');
557
- }
558
- }
559
- catch (e) {
560
- log('Warning: Could not fetch PM2 process list. Falling back to config-only detection.');
561
- runningProcesses = activeInConfig;
562
- }
563
- const botsToRestart = activeInConfig.filter((name) => runningProcesses.includes(name));
564
- const activeBots = (config.bots || []).filter((b) => b.active !== false);
565
- const runningActiveBots = activeBots.filter((b) => runningProcesses.includes(b.name));
566
- const maPath = path.join(PATHS.PROJECT_ROOT, BUILD_DIR, 'pm2.js');
567
- const pm2Module = fs.existsSync(maPath)
568
- ? await import(maPath)
569
- : await import(path.join(PATHS.PROJECT_ROOT, 'pm2.js'));
570
- const marketAdapterRequired = pm2Module.needsMarketAdapter(runningActiveBots);
571
- const serviceAppsToRestart = marketAdapterRequired ? ['dexbot-adapter'] : [];
572
- const servicesToRestart = serviceAppsToRestart.filter((name) => runningProcesses.includes(name));
573
- const allToRestart = [...botsToRestart, ...servicesToRestart];
574
- if (allToRestart.length > 0) {
575
- log(`Active processes detected: ${allToRestart.join(', ')}`);
576
- for (const name of allToRestart) {
577
- try {
578
- run(`pm2 restart "${name}"`);
579
- }
580
- catch (e) {
581
- log(`Warning: Failed to restart process "${name}" (it might not be running).`);
582
- }
583
- }
584
- restarted = true;
585
- }
586
- else {
587
- log('No active processes currently running in PM2. Skipping restart.');
588
- }
589
- if (marketAdapterRequired && !runningProcesses.includes('dexbot-adapter')) {
590
- log('dexbot-adapter is required by an AMA-grid bot but not running. Starting from ecosystem...');
591
- try {
592
- run('pm2 start profiles/ecosystem.config.cjs --only dexbot-adapter');
593
- }
594
- catch (e) {
595
- log('Warning: Failed to start dexbot-adapter from ecosystem config.');
596
- }
597
- }
598
- }
599
- else {
600
- log('No active bots found in config.');
601
- }
602
- }
603
- else {
604
- log('Warning: profiles/bots.json not found, skipping selective restart.');
605
- }
606
- }
607
- }
608
- }
609
- catch (err) {
610
- log(`Warning: runtime restart logic failed (${getErrorMessage(err)}). Skipping bulk restart to avoid touching dexbot-cred.`);
611
- }
612
- /**
613
- * STEP 9b: Fallback restart for monolithic daemon
614
- *
615
- * If the monolithic daemon was running (or had state files) before the
616
- * update but is no longer alive (common when the daemon is killed during
617
- * git/npm operations like the prepare hook), try to auto-start it.
618
- * This prevents the "bot updated but is not running" failure mode.
619
- *
620
- * Only mark `restarted` true when startMonolithicRuntime reports
621
- * success — otherwise fall through to the manual-start instructions
622
- * below so the operator knows the daemon did not actually come back.
623
- */
624
- if (!restarted && !Config.DEXBOT_UPDATE_SKIP_RELOAD) {
625
- if (monolithicWasRunning || hadMonolithicFiles) {
626
- log('Monolithic daemon was detected before update but is no longer running. Auto-starting...');
627
- if (startMonolithicRuntime()) {
628
- restarted = true;
629
- }
630
- else {
631
- log('Auto-start did not succeed; the manual-start instructions below apply.');
632
- }
633
- }
634
- }
635
- if (!restarted) {
636
- console.log(colorUpdateOutput('\n⚠️ No active runtime was restarted.\n' +
637
- ' If the daemon was running before, start it manually from a terminal:\n' +
638
- ' dexbot start\n' +
639
- ' (will prompt for master password; add --foreground for interactive mode)\n' +
640
- ' For non-interactive automation:\n' +
641
- ' dexbot start --headless --password-file <path>\n', UPDATE_COLORS.warn));
642
- }
805
+ await restartActiveRuntimes({ monolithicWasRunning, hadMonolithicFiles });
643
806
  logSuccess('DEXBot2 update completed successfully.');
644
807
  process.exit(0);
645
808
  }