@khanglvm/llm-router 2.3.6 → 2.3.7

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.
@@ -11,6 +11,7 @@ import {
11
11
  } from "./local-server-settings.js";
12
12
  import { resolveListenPort } from "./listen-port.js";
13
13
  import { startLocalRouteServer } from "./local-server.js";
14
+ import { startRouterSupervisor } from "./router-supervisor.js";
14
15
  import { reclaimPort, stopStartupManagedListener } from "./port-reclaim.js";
15
16
  import { installStartup, startupStatus } from "./startup-manager.js";
16
17
  import { configHasProvider, sanitizeConfigForDisplay } from "../runtime/config.js";
@@ -134,10 +135,21 @@ function snapshotCliVersionState(cliPath) {
134
135
  return { cliPath, realpath, packageJsonPath, version };
135
136
  }
136
137
 
137
- function buildStartArgs({ configPath, watchConfig, watchBinary, requireAuth, useConfigDefaults = false }) {
138
+ function buildStartArgs({
139
+ configPath,
140
+ host = FIXED_LOCAL_ROUTER_HOST,
141
+ port = FIXED_LOCAL_ROUTER_PORT,
142
+ watchConfig,
143
+ watchBinary,
144
+ requireAuth,
145
+ useConfigDefaults = false,
146
+ command = "start"
147
+ }) {
138
148
  const args = [
139
- "start",
140
- `--config=${configPath}`
149
+ command,
150
+ `--config=${configPath}`,
151
+ `--host=${host}`,
152
+ `--port=${port}`
141
153
  ];
142
154
  if (useConfigDefaults) return args;
143
155
  args.push(
@@ -361,8 +373,9 @@ async function attemptServerStartAfterStartupStop(buildLocalServerOptions, deps
361
373
  return { ok: false, error: lastError };
362
374
  }
363
375
 
364
- export async function runStartCommand(options = {}) {
376
+ async function runRouterRuntimeCommand(options = {}) {
365
377
  const configPath = options.configPath || getDefaultConfigPath();
378
+ const backendMode = options.backendMode === true;
366
379
  const requestedWatchConfig = options.watchConfig;
367
380
  const requestedWatchBinary = options.watchBinary;
368
381
  const binaryWatchIntervalMs = Math.max(
@@ -373,6 +386,7 @@ export async function runStartCommand(options = {}) {
373
386
  const onStartupConflict = typeof options.onStartupConflict === "function" ? options.onStartupConflict : null;
374
387
  const managedByStartup = options.managedByStartup === true || process.env.LLM_ROUTER_MANAGED_BY_STARTUP === "1";
375
388
  const cliPathForWatch = String(options.cliPathForWatch || process.env.LLM_ROUTER_CLI_PATH || process.argv[1] || "");
389
+ const startCommand = String(options.startCommand || (backendMode ? "start-runtime" : "start")).trim() || "start";
376
390
  const line = typeof options.onLine === "function" ? options.onLine : console.log;
377
391
  const error = typeof options.onError === "function" ? options.onError : console.error;
378
392
  const startLocalRouteServerFn = typeof options.startLocalRouteServer === "function" ? options.startLocalRouteServer : startLocalRouteServer;
@@ -421,8 +435,12 @@ export async function runStartCommand(options = {}) {
421
435
 
422
436
  let config = configState.config;
423
437
  const persistedLocalServer = readLocalServerSettings(config);
424
- const host = FIXED_LOCAL_ROUTER_HOST;
425
- const port = resolveListenPort({ explicitPort: persistedLocalServer.port });
438
+ const host = backendMode
439
+ ? String(options.host || FIXED_LOCAL_ROUTER_HOST).trim() || FIXED_LOCAL_ROUTER_HOST
440
+ : FIXED_LOCAL_ROUTER_HOST;
441
+ const port = backendMode
442
+ ? Math.max(1, Number(options.port || FIXED_LOCAL_ROUTER_PORT))
443
+ : resolveListenPort({ explicitPort: persistedLocalServer.port });
426
444
  const watchConfig = requestedWatchConfig === undefined ? persistedLocalServer.watchConfig : toBoolean(requestedWatchConfig, persistedLocalServer.watchConfig);
427
445
  const watchBinary = requestedWatchBinary === undefined ? persistedLocalServer.watchBinary : toBoolean(requestedWatchBinary, persistedLocalServer.watchBinary);
428
446
  const requireAuth = requestedRequireAuth === undefined ? persistedLocalServer.requireAuth : toBoolean(requestedRequireAuth, persistedLocalServer.requireAuth);
@@ -464,8 +482,8 @@ export async function runStartCommand(options = {}) {
464
482
  requireAuth
465
483
  };
466
484
 
467
- const startup = await startupStatusFn().catch(() => null);
468
- if (!managedByStartup && startup?.installed) {
485
+ const startup = backendMode ? null : await startupStatusFn().catch(() => null);
486
+ if (!backendMode && !managedByStartup && startup?.installed) {
469
487
  const handoff = await handoffToStartupManagedWithLatest({
470
488
  runtimeState: null,
471
489
  fallbackStartArgs: requestedStartArgs,
@@ -502,6 +520,7 @@ export async function runStartCommand(options = {}) {
502
520
  }
503
521
 
504
522
  let restartRequestedByConfig = false;
523
+ let requestGracefulRelaunch = async () => {};
505
524
 
506
525
  const buildLocalServerOptions = () => ({
507
526
  port,
@@ -524,26 +543,12 @@ export async function runStartCommand(options = {}) {
524
543
  if (!areLocalServerSettingsEqual(nextLocalServer, resolvedLocalServer)) {
525
544
  if (restartRequestedByConfig) return;
526
545
  restartRequestedByConfig = true;
527
- line(`Local server settings changed in config (${reason}). Restarting to apply local router settings...`);
528
- void (async () => {
529
- if (managedByStartup) {
530
- await shutdown();
531
- process.exit(0);
532
- return;
533
- }
534
-
535
- await shutdown();
536
- const launch = await spawnReplacementCli({
537
- cliPath: cliPathForWatch || process.argv[1],
538
- startArgs: buildStartArgs({ configPath, ...nextLocalServer })
539
- });
540
- if (!launch.ok) {
541
- error(`Failed to relaunch LLM Router after the config runtime change: ${launch.error instanceof Error ? launch.error.message : String(launch.error)}`);
542
- process.exit(1);
543
- return;
544
- }
545
- process.exit(0);
546
- })();
546
+ void requestGracefulRelaunch({
547
+ reasonMessage: `Local server settings changed in config (${reason}). Restarting to apply local router settings...`,
548
+ manualRestartMessage: "Local server settings changed, but this process cannot resolve its CLI path. Restart `llr start` manually to apply them.",
549
+ configPath,
550
+ ...nextLocalServer
551
+ });
547
552
  return;
548
553
  }
549
554
 
@@ -702,7 +707,7 @@ export async function runStartCommand(options = {}) {
702
707
  let binaryWatchTimer = null;
703
708
  let binaryState = watchBinary && cliPathForWatch ? snapshotCliVersionState(cliPathForWatch) : null;
704
709
  let binaryNoticeSent = false;
705
- let binaryRelaunching = false;
710
+ let relaunchInProgress = false;
706
711
  const runtimeVersion = binaryState?.version || readPackageVersion(resolvePackageJsonPathFromCliPath(safeRealpath(cliPathForWatch)));
707
712
 
708
713
  try {
@@ -748,9 +753,62 @@ export async function runStartCommand(options = {}) {
748
753
  resolveDone();
749
754
  };
750
755
 
756
+ requestGracefulRelaunch = async ({
757
+ reasonMessage = "",
758
+ manualRestartMessage = "",
759
+ cliPath = cliPathForWatch || process.argv[1],
760
+ configPath: nextConfigPath = configPath,
761
+ host: nextHost = host,
762
+ port: nextPort = port,
763
+ watchConfig: nextWatchConfig = watchConfig,
764
+ watchBinary: nextWatchBinary = watchBinary,
765
+ requireAuth: nextRequireAuth = requireAuth
766
+ } = {}) => {
767
+ if (shuttingDown || relaunchInProgress) return;
768
+ relaunchInProgress = true;
769
+
770
+ if (reasonMessage) {
771
+ line(reasonMessage);
772
+ }
773
+
774
+ if (managedByStartup) {
775
+ await shutdown();
776
+ process.exit(0);
777
+ return;
778
+ }
779
+
780
+ if (!cliPath) {
781
+ relaunchInProgress = false;
782
+ error(manualRestartMessage || "LLM Router needs a manual restart because its CLI path cannot be resolved.");
783
+ return;
784
+ }
785
+
786
+ await shutdown();
787
+ const launch = await spawnReplacementCli({
788
+ cliPath,
789
+ startArgs: buildStartArgs({
790
+ command: startCommand,
791
+ configPath: nextConfigPath,
792
+ host: nextHost,
793
+ port: nextPort,
794
+ watchConfig: nextWatchConfig,
795
+ watchBinary: nextWatchBinary,
796
+ requireAuth: nextRequireAuth
797
+ })
798
+ });
799
+ if (!launch.ok) {
800
+ error(`Failed to relaunch LLM Router: ${launch.error instanceof Error ? launch.error.message : String(launch.error)}`);
801
+ process.exit(1);
802
+ return;
803
+ }
804
+
805
+ line(`Started the replacement LLM Router process (pid ${launch.pid || "unknown"}).`);
806
+ process.exit(0);
807
+ };
808
+
751
809
  if (watchBinary && binaryState) {
752
810
  binaryWatchTimer = setInterval(() => {
753
- if (shuttingDown || binaryRelaunching) return;
811
+ if (shuttingDown || relaunchInProgress) return;
754
812
  const nextState = snapshotCliVersionState(binaryState.cliPath);
755
813
  const changed =
756
814
  nextState.realpath !== binaryState.realpath ||
@@ -762,16 +820,8 @@ export async function runStartCommand(options = {}) {
762
820
  const to = nextState.version || nextState.realpath || "(unknown)";
763
821
  binaryState = nextState;
764
822
 
765
- if (managedByStartup) {
766
- line(`Detected LLM Router update (${from} -> ${to}). Exiting for the startup manager to relaunch the latest version.`);
767
- void shutdown().then(() => {
768
- process.exit(0);
769
- });
770
- return;
771
- }
772
-
773
823
  const cliPath = nextState.cliPath || cliPathForWatch || process.argv[1];
774
- if (!cliPath) {
824
+ if (!managedByStartup && !cliPath) {
775
825
  if (!binaryNoticeSent) {
776
826
  binaryNoticeSent = true;
777
827
  line(`Detected LLM Router update (${from} -> ${to}). Restart this process to run the new version.`);
@@ -779,35 +829,309 @@ export async function runStartCommand(options = {}) {
779
829
  return;
780
830
  }
781
831
 
782
- binaryRelaunching = true;
783
- void (async () => {
784
- try {
785
- line(`Detected LLM Router update (${from} -> ${to}). Relaunching the latest version...`);
786
- await shutdown();
787
- const launch = await spawnReplacementCli({
788
- cliPath,
789
- startArgs: buildStartArgs({ configPath, host, port, watchConfig, watchBinary, requireAuth })
790
- });
791
- if (!launch.ok) {
792
- error(`Failed to relaunch the updated LLM Router process: ${launch.error instanceof Error ? launch.error.message : String(launch.error)}`);
793
- process.exit(1);
794
- return;
795
- }
796
-
797
- line(`Started the updated LLM Router process (pid ${launch.pid || "unknown"}).`);
798
- process.exit(0);
799
- } catch (relaunchError) {
800
- error(`Failed during LLM Router auto-relaunch: ${relaunchError instanceof Error ? relaunchError.message : String(relaunchError)}`);
801
- process.exit(1);
802
- }
803
- })();
832
+ void requestGracefulRelaunch({
833
+ reasonMessage: managedByStartup
834
+ ? `Detected LLM Router update (${from} -> ${to}). Draining current requests before the startup manager relaunches the latest version.`
835
+ : `Detected LLM Router update (${from} -> ${to}). Gracefully relaunching the latest version...`,
836
+ manualRestartMessage: "Detected an updated LLM Router binary, but this process cannot resolve its CLI path. Restart it manually to run the new version.",
837
+ cliPath
838
+ }).catch((relaunchError) => {
839
+ error(`Failed during LLM Router auto-relaunch: ${relaunchError instanceof Error ? relaunchError.message : String(relaunchError)}`);
840
+ process.exit(1);
841
+ });
804
842
  }, binaryWatchIntervalMs);
805
843
  }
806
844
 
807
845
  process.once("SIGINT", () => { void shutdown(); });
808
846
  process.once("SIGTERM", () => { void shutdown(); });
847
+ process.once("SIGUSR2", () => {
848
+ void requestGracefulRelaunch({
849
+ reasonMessage: managedByStartup
850
+ ? "Received runtime upgrade signal. Draining current requests before the startup manager relaunches the latest version..."
851
+ : "Received runtime upgrade signal. Gracefully restarting to activate the newly installed version...",
852
+ manualRestartMessage: "Received a runtime upgrade signal, but this process cannot resolve its CLI path. Restart it manually to activate the new version."
853
+ }).catch((relaunchError) => {
854
+ error(`Failed during the runtime upgrade relaunch: ${relaunchError instanceof Error ? relaunchError.message : String(relaunchError)}`);
855
+ process.exit(1);
856
+ });
857
+ });
858
+
859
+ await donePromise;
860
+
861
+ return {
862
+ ok: true,
863
+ exitCode: 0,
864
+ data: "Server stopped."
865
+ };
866
+ }
867
+
868
+ async function runRouterSupervisorCommand(options = {}) {
869
+ const configPath = options.configPath || getDefaultConfigPath();
870
+ const requestedWatchConfig = options.watchConfig;
871
+ const requestedWatchBinary = options.watchBinary;
872
+ const requestedRequireAuth = options.requireAuth;
873
+ const managedByStartup = options.managedByStartup === true || process.env.LLM_ROUTER_MANAGED_BY_STARTUP === "1";
874
+ const cliPathForWatch = String(options.cliPathForWatch || process.env.LLM_ROUTER_CLI_PATH || process.argv[1] || "");
875
+ const line = typeof options.onLine === "function" ? options.onLine : console.log;
876
+ const error = typeof options.onError === "function" ? options.onError : console.error;
877
+ const getActiveRuntimeStateFn = typeof options.getActiveRuntimeState === "function" ? options.getActiveRuntimeState : getActiveRuntimeState;
878
+ const stopProcessByPidFn = typeof options.stopProcessByPid === "function" ? options.stopProcessByPid : stopProcessByPid;
879
+ const clearRuntimeStateFn = typeof options.clearRuntimeState === "function" ? options.clearRuntimeState : clearRuntimeState;
880
+ const installStartupFn = typeof options.installStartup === "function" ? options.installStartup : installStartup;
881
+ const startupStatusFn = typeof options.startupStatus === "function" ? options.startupStatus : startupStatus;
882
+ const reclaimPortFn = typeof options.reclaimPort === "function"
883
+ ? options.reclaimPort
884
+ : (args) => reclaimPort(args, options);
885
+ const waitForRuntimeMatchFn = typeof options.waitForRuntimeMatch === "function"
886
+ ? options.waitForRuntimeMatch
887
+ : (startOptions, waitOptions = {}) => waitForRuntimeMatch(startOptions, waitOptions);
888
+ const startRouterSupervisorFn = typeof options.startRouterSupervisor === "function"
889
+ ? options.startRouterSupervisor
890
+ : (startOptions) => startRouterSupervisor(startOptions, options);
891
+
892
+ if (!(await configFileExists(configPath))) {
893
+ return {
894
+ ok: false,
895
+ exitCode: 2,
896
+ errorMessage: [
897
+ `Config file not found: ${configPath}`,
898
+ "Run 'llr config' to create provider config or 'llr -h' for help."
899
+ ].join("\n")
900
+ };
901
+ }
902
+
903
+ let configState;
904
+ try {
905
+ configState = await readConfigFileState(configPath);
906
+ } catch (readConfigError) {
907
+ return {
908
+ ok: false,
909
+ exitCode: 2,
910
+ errorMessage: `Failed to load config from ${configPath}: ${readConfigError instanceof Error ? readConfigError.message : String(readConfigError)}`
911
+ };
912
+ }
913
+
914
+ const configMigrationMessage = formatStartupConfigMigrationMessage(configState, configPath);
915
+ if (configMigrationMessage) {
916
+ if (configState.persistError) {
917
+ error(configMigrationMessage);
918
+ } else {
919
+ line(configMigrationMessage);
920
+ }
921
+ }
922
+
923
+ let config = configState.config;
924
+ const persistedLocalServer = readLocalServerSettings(config);
925
+ const host = FIXED_LOCAL_ROUTER_HOST;
926
+ const port = resolveListenPort({ explicitPort: persistedLocalServer.port });
927
+ const watchConfig = requestedWatchConfig === undefined ? persistedLocalServer.watchConfig : toBoolean(requestedWatchConfig, persistedLocalServer.watchConfig);
928
+ const watchBinary = requestedWatchBinary === undefined ? persistedLocalServer.watchBinary : toBoolean(requestedWatchBinary, persistedLocalServer.watchBinary);
929
+ const requireAuth = requestedRequireAuth === undefined ? persistedLocalServer.requireAuth : toBoolean(requestedRequireAuth, persistedLocalServer.requireAuth);
930
+ const resolvedLocalServer = { host, port, watchConfig, watchBinary, requireAuth };
931
+
932
+ if (!areLocalServerSettingsEqual(persistedLocalServer, resolvedLocalServer)) {
933
+ config = await readConfigFile(configPath, { persistMigrated: false });
934
+ config = applyLocalServerSettings(config, resolvedLocalServer);
935
+ config = await writeConfigFile(config, configPath);
936
+ }
937
+ if (!configHasProvider(config)) {
938
+ return {
939
+ ok: false,
940
+ exitCode: 2,
941
+ errorMessage: [
942
+ `No providers configured in ${configPath}`,
943
+ "Run 'llr config' to add a provider or 'llr -h' for help."
944
+ ].join("\n")
945
+ };
946
+ }
947
+
948
+ if (requireAuth && !config.masterKey) {
949
+ return {
950
+ ok: false,
951
+ exitCode: 2,
952
+ errorMessage: [
953
+ `Local auth requires masterKey in ${configPath}.`,
954
+ "Run 'llr config --operation=set-master-key --master-key=...' or start without --require-auth."
955
+ ].join("\n")
956
+ };
957
+ }
958
+
959
+ const requestedStartArgs = {
960
+ configPath,
961
+ host,
962
+ port,
963
+ watchConfig,
964
+ watchBinary,
965
+ requireAuth
966
+ };
967
+
968
+ const startup = await startupStatusFn().catch(() => null);
969
+ if (!managedByStartup && startup?.installed) {
970
+ const handoff = await handoffToStartupManagedWithLatest({
971
+ runtimeState: null,
972
+ fallbackStartArgs: requestedStartArgs,
973
+ cliPath: cliPathForWatch,
974
+ line,
975
+ error
976
+ }, {
977
+ getActiveRuntimeState: getActiveRuntimeStateFn,
978
+ stopProcessByPid: stopProcessByPidFn,
979
+ clearRuntimeState: clearRuntimeStateFn,
980
+ reclaimPort: reclaimPortFn,
981
+ installStartup: installStartupFn,
982
+ waitForRuntimeMatch: waitForRuntimeMatchFn,
983
+ startupStatus: startupStatusFn,
984
+ onLine: line,
985
+ onError: error
986
+ });
987
+ if (!handoff.ok) {
988
+ return {
989
+ ok: false,
990
+ exitCode: 1,
991
+ errorMessage: handoff.errorMessage
992
+ };
993
+ }
994
+ return {
995
+ ok: true,
996
+ exitCode: 0,
997
+ data: [
998
+ `Startup-managed LLM Router is active on http://${handoff.runtime.host}:${handoff.runtime.port}.`,
999
+ `manager=${handoff.detail?.manager || startup.manager || "unknown"}`,
1000
+ `service=${handoff.detail?.serviceId || startup.serviceId || "unknown"}`
1001
+ ].join("\n")
1002
+ };
1003
+ }
1004
+
1005
+ const activeRuntime = await getActiveRuntimeStateFn().catch(() => null);
1006
+ if (activeRuntime && Number(activeRuntime.pid) !== Number(process.pid)) {
1007
+ return {
1008
+ ok: false,
1009
+ exitCode: 1,
1010
+ errorMessage: `Another LLM Router instance is already running at http://${activeRuntime.host}:${activeRuntime.port}. Stop it before starting a new one.`
1011
+ };
1012
+ }
1013
+
1014
+ let server;
1015
+ try {
1016
+ server = await startRouterSupervisorFn({
1017
+ host,
1018
+ port,
1019
+ configPath,
1020
+ watchConfig,
1021
+ watchBinary,
1022
+ requireAuth,
1023
+ cliPath: cliPathForWatch,
1024
+ onLine: line,
1025
+ onError: error
1026
+ });
1027
+ } catch (startError) {
1028
+ if (startError?.code !== "EADDRINUSE") {
1029
+ return {
1030
+ ok: false,
1031
+ exitCode: 1,
1032
+ errorMessage: `Failed to start LLM Router on http://${host}:${port}: ${startError instanceof Error ? startError.message : String(startError)}`
1033
+ };
1034
+ }
1035
+
1036
+ const reclaimed = await reclaimPortFn({ port, line, error });
1037
+ if (!reclaimed.ok) {
1038
+ return {
1039
+ ok: false,
1040
+ exitCode: 1,
1041
+ errorMessage: reclaimed.errorMessage
1042
+ };
1043
+ }
1044
+
1045
+ try {
1046
+ server = await startRouterSupervisorFn({
1047
+ host,
1048
+ port,
1049
+ configPath,
1050
+ watchConfig,
1051
+ watchBinary,
1052
+ requireAuth,
1053
+ cliPath: cliPathForWatch,
1054
+ onLine: line,
1055
+ onError: error
1056
+ });
1057
+ line(`Port ${port} reclaimed successfully.`);
1058
+ } catch (retryError) {
1059
+ return {
1060
+ ok: false,
1061
+ exitCode: 1,
1062
+ errorMessage: `Failed to start LLM Router after reclaiming port ${port}: ${retryError instanceof Error ? retryError.message : String(retryError)}`
1063
+ };
1064
+ }
1065
+ }
1066
+
1067
+ const runtimeVersion = readPackageVersion(resolvePackageJsonPathFromCliPath(safeRealpath(cliPathForWatch)));
1068
+ try {
1069
+ await writeRuntimeState({
1070
+ pid: process.pid,
1071
+ host,
1072
+ port,
1073
+ configPath,
1074
+ watchConfig,
1075
+ watchBinary,
1076
+ requireAuth,
1077
+ managedByStartup,
1078
+ cliPath: cliPathForWatch,
1079
+ startedAt: new Date().toISOString(),
1080
+ version: runtimeVersion
1081
+ });
1082
+ } catch (stateError) {
1083
+ error(`Failed to write runtime state file: ${stateError instanceof Error ? stateError.message : String(stateError)}`);
1084
+ }
1085
+
1086
+ line(`LLM Router started on http://${host}:${port}`);
1087
+ line(`Anthropic base URL: http://${host}:${port}/anthropic`);
1088
+ line(`OpenAI base URL: http://${host}:${port}/openai`);
1089
+ for (const row of summarizeConfig(config, configPath)) {
1090
+ line(row);
1091
+ }
1092
+ line(`Local auth: ${requireAuth ? "required (masterKey)" : "disabled"}`);
1093
+ line(`Config hot reload: ${watchConfig ? "enabled" : "disabled"} (backend hot reload via supervisor)`);
1094
+ line(`Binary update watch: ${watchBinary ? "enabled" : "disabled"} (backend hot-swap via supervisor)`);
1095
+ line("Press Ctrl+C to stop.");
1096
+
1097
+ let shuttingDown = false;
1098
+ let shutdownPromise = null;
1099
+ const donePromise = new Promise((resolve) => {
1100
+ server.once("close", resolve);
1101
+ });
1102
+ const shutdown = async () => {
1103
+ if (shutdownPromise) return shutdownPromise;
1104
+ shuttingDown = true;
1105
+ shutdownPromise = (async () => {
1106
+ await new Promise((resolve) => server.close(() => resolve()));
1107
+ await clearRuntimeStateFn({ pid: process.pid });
1108
+ })();
1109
+ return shutdownPromise;
1110
+ };
1111
+
1112
+ const handleSigInt = () => { void shutdown(); };
1113
+ const handleSigTerm = () => { void shutdown(); };
1114
+ const handleSigUsr2 = () => {
1115
+ void server.requestBackendUpgrade("SIGUSR2").then((result) => {
1116
+ if (!result?.ok) {
1117
+ error(`Failed forwarding runtime upgrade signal to router backend: ${result?.reason || "unknown error"}`);
1118
+ }
1119
+ }).catch((upgradeError) => {
1120
+ error(`Failed forwarding runtime upgrade signal to router backend: ${upgradeError instanceof Error ? upgradeError.message : String(upgradeError)}`);
1121
+ });
1122
+ };
1123
+ process.once("SIGINT", handleSigInt);
1124
+ process.once("SIGTERM", handleSigTerm);
1125
+ process.on("SIGUSR2", handleSigUsr2);
809
1126
 
810
1127
  await donePromise;
1128
+ if (shutdownPromise) {
1129
+ await shutdownPromise;
1130
+ }
1131
+
1132
+ process.removeListener("SIGINT", handleSigInt);
1133
+ process.removeListener("SIGTERM", handleSigTerm);
1134
+ process.removeListener("SIGUSR2", handleSigUsr2);
811
1135
 
812
1136
  return {
813
1137
  ok: true,
@@ -815,3 +1139,10 @@ export async function runStartCommand(options = {}) {
815
1139
  data: "Server stopped."
816
1140
  };
817
1141
  }
1142
+
1143
+ export async function runStartCommand(options = {}) {
1144
+ if (options.backendMode === true) {
1145
+ return runRouterRuntimeCommand(options);
1146
+ }
1147
+ return runRouterSupervisorCommand(options);
1148
+ }