@monotykamary/localterm-server 2.74.1 → 2.76.0

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 (91) hide show
  1. package/dist/capture-renderer.d.ts +1 -0
  2. package/dist/capture-renderer.d.ts.map +1 -1
  3. package/dist/capture-renderer.js +25 -1
  4. package/dist/capture-renderer.js.map +1 -1
  5. package/dist/constants.d.ts +4 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +6 -0
  8. package/dist/constants.js.map +1 -1
  9. package/dist/hibernate-ledger.d.ts +16 -0
  10. package/dist/hibernate-ledger.d.ts.map +1 -0
  11. package/dist/hibernate-ledger.js +37 -0
  12. package/dist/hibernate-ledger.js.map +1 -0
  13. package/dist/hibernate-store.d.ts +18 -0
  14. package/dist/hibernate-store.d.ts.map +1 -0
  15. package/dist/hibernate-store.js +56 -0
  16. package/dist/hibernate-store.js.map +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +94 -72
  19. package/dist/index.js.map +1 -1
  20. package/dist/schemas.d.ts +9 -0
  21. package/dist/schemas.d.ts.map +1 -1
  22. package/dist/schemas.js +1 -1
  23. package/dist/schemas.js.map +1 -1
  24. package/dist/session-client-hub.d.ts.map +1 -1
  25. package/dist/session-client-hub.js +1 -0
  26. package/dist/session-client-hub.js.map +1 -1
  27. package/dist/session-manager.d.ts +4 -1
  28. package/dist/session-manager.d.ts.map +1 -1
  29. package/dist/session-manager.js +36 -1
  30. package/dist/session-manager.js.map +1 -1
  31. package/dist/session-output-coordinator.d.ts.map +1 -1
  32. package/dist/session-output-coordinator.js +2 -1
  33. package/dist/session-output-coordinator.js.map +1 -1
  34. package/dist/session.d.ts.map +1 -1
  35. package/dist/session.js +8 -0
  36. package/dist/session.js.map +1 -1
  37. package/dist/types.d.ts +1 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/utils/append-to-bounded-utf8-ring.d.ts +2 -0
  40. package/dist/utils/append-to-bounded-utf8-ring.d.ts.map +1 -0
  41. package/dist/utils/append-to-bounded-utf8-ring.js +32 -0
  42. package/dist/utils/append-to-bounded-utf8-ring.js.map +1 -0
  43. package/dist/utils/create-bounded-debounce.d.ts +13 -0
  44. package/dist/utils/create-bounded-debounce.d.ts.map +1 -0
  45. package/dist/utils/create-bounded-debounce.js +43 -0
  46. package/dist/utils/create-bounded-debounce.js.map +1 -0
  47. package/dist/utils/find-terminal-safe-trim-offset.d.ts +2 -0
  48. package/dist/utils/find-terminal-safe-trim-offset.d.ts.map +1 -0
  49. package/dist/utils/find-terminal-safe-trim-offset.js +96 -0
  50. package/dist/utils/find-terminal-safe-trim-offset.js.map +1 -0
  51. package/dist/utils/main-screen-output-filter.d.ts +17 -0
  52. package/dist/utils/main-screen-output-filter.d.ts.map +1 -0
  53. package/dist/utils/main-screen-output-filter.js +185 -0
  54. package/dist/utils/main-screen-output-filter.js.map +1 -0
  55. package/dist/utils/merge-workspace-restore-entries.d.ts +9 -0
  56. package/dist/utils/merge-workspace-restore-entries.d.ts.map +1 -0
  57. package/dist/utils/merge-workspace-restore-entries.js +79 -0
  58. package/dist/utils/merge-workspace-restore-entries.js.map +1 -0
  59. package/dist/utils/normalize-hibernate-scrollback.d.ts +2 -0
  60. package/dist/utils/normalize-hibernate-scrollback.d.ts.map +1 -0
  61. package/dist/utils/normalize-hibernate-scrollback.js +23 -0
  62. package/dist/utils/normalize-hibernate-scrollback.js.map +1 -0
  63. package/dist/utils/pending-workspace-restore-tabs.d.ts +4 -0
  64. package/dist/utils/pending-workspace-restore-tabs.d.ts.map +1 -0
  65. package/dist/utils/pending-workspace-restore-tabs.js +21 -0
  66. package/dist/utils/pending-workspace-restore-tabs.js.map +1 -0
  67. package/dist/utils/plan-workspace-restore.d.ts +14 -0
  68. package/dist/utils/plan-workspace-restore.d.ts.map +1 -0
  69. package/dist/utils/plan-workspace-restore.js +12 -0
  70. package/dist/utils/plan-workspace-restore.js.map +1 -0
  71. package/dist/utils/render-buffer-line-with-sgr.d.ts +3 -0
  72. package/dist/utils/render-buffer-line-with-sgr.d.ts.map +1 -0
  73. package/dist/utils/render-buffer-line-with-sgr.js +100 -0
  74. package/dist/utils/render-buffer-line-with-sgr.js.map +1 -0
  75. package/dist/utils/render-legacy-hibernate-scrollback.d.ts +2 -0
  76. package/dist/utils/render-legacy-hibernate-scrollback.d.ts.map +1 -0
  77. package/dist/utils/render-legacy-hibernate-scrollback.js +56 -0
  78. package/dist/utils/render-legacy-hibernate-scrollback.js.map +1 -0
  79. package/dist/utils/terminal-mode-state.d.ts +0 -1
  80. package/dist/utils/terminal-mode-state.d.ts.map +1 -1
  81. package/dist/utils/terminal-mode-state.js +1 -1
  82. package/dist/utils/terminal-mode-state.js.map +1 -1
  83. package/dist/utils/workspace-session-ids.d.ts +3 -0
  84. package/dist/utils/workspace-session-ids.d.ts.map +1 -0
  85. package/dist/utils/workspace-session-ids.js +11 -0
  86. package/dist/utils/workspace-session-ids.js.map +1 -0
  87. package/dist/utils/write-file-atomically.d.ts +2 -0
  88. package/dist/utils/write-file-atomically.d.ts.map +1 -0
  89. package/dist/utils/write-file-atomically.js +38 -0
  90. package/dist/utils/write-file-atomically.js.map +1 -0
  91. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import { detectWithExplicitPort } from "./cdp/discover-explicit-endpoint.js";
22
22
  import { DaemonConfigStore } from "./daemon-config-store.js";
23
23
  import { formatCandidates, resolveCandidates, resolveCompletionContext, } from "./completion/index.js";
24
24
  import { z } from "zod";
25
- import { ACTIVITY_DIRNAME, ACTIVITY_REFRESH_DEBOUNCE_MS, ACTIVITY_WATCHED_PROGRAMS, AUTOMATION_EVENT_DEBOUNCE_MS, AUTOMATION_RECONCILE_MIN_DOWNTIME_MS, AUTOMATION_RUN_QUERY_PARAM, AUTOMATION_WATCH_DEBOUNCE_MS, AUTOMATION_WATCH_POST_RUN_GRACE_MS, AUTOMATION_WEBHOOK_DEBOUNCE_MS, DEFAULT_HOST, DEFAULT_PORT, FRIENDLY_HOSTNAME, GIT_DIRTY_THROTTLE_MS, GIT_MAX_REF_LENGTH, HERDR_THEME_SYNC_DEBOUNCE_MS, HTTP_STATUS_ACCEPTED, HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_CONFLICT, HTTP_STATUS_CREATED, HTTP_STATUS_NOT_FOUND, HTTP_STATUS_PAYLOAD_TOO_LARGE, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, MAX_AUTOMATIONS, MAX_IMAGE_UPLOAD_BYTES, MAX_IMAGE_UPLOAD_REQUEST_BYTES, MAX_PROCESSES, MAX_SECRETS, MS_PER_MINUTE, PROCESSES_FILENAME, SECRETS_FILENAME, THEMES_FILENAME, FONTS_FILENAME, SECRETS_SHIMS_DIRNAME, SECRET_EXPORT_SCRYPT_WORK_FACTOR, AUTOMATION_AGENT_SESSIONS_DIRNAME, SERVER_STOP_GRACE_MS, SESSION_ID_QUERY_PARAM, WORKSPACE_FILENAME, WORKSPACE_RESTORE_SETTLE_MS, WORKSPACE_SNAPSHOT_DEBOUNCE_MS, SESSION_ACTIVITY_WINDOW_MS, WINDOW_ID_QUERY_PARAM, WAIT_DEFAULT_TIMEOUT_MS, WS_BACKPRESSURE_THRESHOLD_BYTES, WS_CLOSE_BACKPRESSURE, WS_CLOSE_CAPACITY_REACHED, WS_CLOSE_POLICY_VIOLATION, WS_HEARTBEAT_GRACE_MS, WS_HEARTBEAT_INTERVAL_MS, WS_HEARTBEAT_TIMEOUT_MS, WS_MAX_INBOUND_PAYLOAD_BYTES, WS_READY_STATE_OPEN, AUTH_SECRET_FILENAME, AUTH_COOKIE_NAME, UPDATE_CHECK_FILENAME, } from "./constants.js";
25
+ import { ACTIVITY_DIRNAME, ACTIVITY_REFRESH_DEBOUNCE_MS, ACTIVITY_WATCHED_PROGRAMS, AUTOMATION_EVENT_DEBOUNCE_MS, AUTOMATION_RECONCILE_MIN_DOWNTIME_MS, AUTOMATION_RUN_QUERY_PARAM, AUTOMATION_WATCH_DEBOUNCE_MS, AUTOMATION_WATCH_POST_RUN_GRACE_MS, AUTOMATION_WEBHOOK_DEBOUNCE_MS, DEFAULT_HOST, DEFAULT_PORT, FRIENDLY_HOSTNAME, GIT_DIRTY_THROTTLE_MS, GIT_MAX_REF_LENGTH, HERDR_THEME_SYNC_DEBOUNCE_MS, HIBERNATE_FILENAME, HTTP_STATUS_ACCEPTED, HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_CONFLICT, HTTP_STATUS_CREATED, HTTP_STATUS_NOT_FOUND, HTTP_STATUS_PAYLOAD_TOO_LARGE, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, MAX_AUTOMATIONS, MAX_IMAGE_UPLOAD_BYTES, MAX_IMAGE_UPLOAD_REQUEST_BYTES, MAX_PROCESSES, MAX_SECRETS, MS_PER_MINUTE, PROCESSES_FILENAME, SECRETS_FILENAME, THEMES_FILENAME, FONTS_FILENAME, SECRETS_SHIMS_DIRNAME, SECRET_EXPORT_SCRYPT_WORK_FACTOR, AUTOMATION_AGENT_SESSIONS_DIRNAME, SERVER_STOP_GRACE_MS, SESSION_ID_QUERY_PARAM, WORKSPACE_FILENAME, WORKSPACE_RESTORE_SETTLE_MS, WORKSPACE_SNAPSHOT_DEBOUNCE_MS, SESSION_ACTIVITY_WINDOW_MS, WINDOW_ID_QUERY_PARAM, WAIT_DEFAULT_TIMEOUT_MS, WS_BACKPRESSURE_THRESHOLD_BYTES, WS_CLOSE_BACKPRESSURE, WS_CLOSE_CAPACITY_REACHED, WS_CLOSE_POLICY_VIOLATION, WS_HEARTBEAT_GRACE_MS, WS_HEARTBEAT_INTERVAL_MS, WS_HEARTBEAT_TIMEOUT_MS, WS_MAX_INBOUND_PAYLOAD_BYTES, WS_READY_STATE_OPEN, AUTH_SECRET_FILENAME, AUTH_COOKIE_NAME, UPDATE_CHECK_FILENAME, } from "./constants.js";
26
26
  import { getDefaultShell, listKnownShells, resolveShellOverride } from "./default-shell.js";
27
27
  import { resolveWindowId } from "./utils/resolve-window-id.js";
28
28
  import { resolveTermBackspaceSequence } from "./utils/resolve-term-backspace-sequence.js";
@@ -39,6 +39,7 @@ import { SessionEventManager } from "./session-event-manager.js";
39
39
  import { WebhookTriggerManager } from "./webhook-trigger-manager.js";
40
40
  import { getGitBranchInfo, getGitBranchPr, getGitDiff, getGitDiffFileContents, getGitDiffFilePatch, getGitDiffFiles, getGitDiffSummary, } from "./git-diff.js";
41
41
  import { HeartbeatStore } from "./heartbeat-store.js";
42
+ import { HibernateStore } from "./hibernate-store.js";
42
43
  import { WorkspaceStore } from "./workspace-store.js";
43
44
  import { createDefaultSecretBackend } from "./secret-backend.js";
44
45
  import { SecretStore } from "./secret-store.js";
@@ -1770,6 +1771,10 @@ export const createServer = async (options = {}) => {
1770
1771
  const caffeinatePreferencesStore = new CaffeinatePreferencesStore(path.join(stateDirectory, "caffeinate.json"));
1771
1772
  const worktreeConfigStore = new WorktreeConfigStore(stateDirectory);
1772
1773
  const workspaceStore = new WorkspaceStore(path.join(stateDirectory, WORKSPACE_FILENAME));
1774
+ const hibernateStore = new HibernateStore(path.join(stateDirectory, HIBERNATE_FILENAME));
1775
+ // Immutable shutdown snapshot for this daemon lifetime. Exact old session ids
1776
+ // let surviving or CDP-restored browser tabs recover only their own text.
1777
+ const startupHibernateEntries = hibernateStore.read();
1773
1778
  // Per-process secret injection: a backend (macOS Keychain on darwin) holds
1774
1779
  // secret values; a secret is an identity + the env var it exports
1775
1780
  // (~/.localterm/secrets.json, names + env var only — never values), and a
@@ -2355,14 +2360,23 @@ export const createServer = async (options = {}) => {
2355
2360
  }
2356
2361
  workspaceStore.write(registry.workspaceEntries());
2357
2362
  };
2358
- const buildSpawnTabUrl = (cwd, shell) => {
2363
+ const buildSpawnTabUrl = (cwd, shell, sessionId) => {
2359
2364
  const url = new URL(localOrigin ?? publicOrigin ?? `http://${FRIENDLY_HOSTNAME}:${actualPort}`);
2360
2365
  if (cwd)
2361
2366
  url.searchParams.set("cwd", cwd);
2362
2367
  if (shell)
2363
2368
  url.searchParams.set("shell", shell);
2369
+ if (sessionId)
2370
+ url.searchParams.set(SESSION_ID_QUERY_PARAM, sessionId);
2364
2371
  return url.toString();
2365
2372
  };
2373
+ const hibernatedTabFor = (owner, windowId, sessionId) => {
2374
+ if (!windowId || !sessionId)
2375
+ return undefined;
2376
+ return startupHibernateEntries
2377
+ .find((entry) => entry.owner === owner && entry.windowId === windowId)
2378
+ ?.tabs.find((tab) => tab.sessionId === sessionId);
2379
+ };
2366
2380
  const restoredWorkspaceKeys = new Set();
2367
2381
  const workspaceRestoreTimers = new Map();
2368
2382
  const scheduleWorkspaceRestore = (owner, windowId) => {
@@ -2389,45 +2403,42 @@ export const createServer = async (options = {}) => {
2389
2403
  return;
2390
2404
  if (!cdpClient || !cdpClient.isConnected())
2391
2405
  return;
2392
- const entry = workspaceStore
2406
+ const hibernatedEntry = startupHibernateEntries.find((candidate) => candidate.owner === owner && candidate.windowId === windowId);
2407
+ const workspaceEntry = workspaceStore
2393
2408
  .read()
2394
2409
  .find((candidate) => candidate.owner === owner && candidate.windowId === windowId);
2395
- if (!entry || entry.tabs.length === 0)
2410
+ const manifest = hibernatedEntry?.tabs ?? workspaceEntry?.tabs ?? [];
2411
+ if (manifest.length === 0)
2396
2412
  return;
2397
- const manifest = entry.tabs;
2413
+ const urlFor = (index) => {
2414
+ const tab = manifest[index];
2415
+ const sessionId = "sessionId" in tab && typeof tab.sessionId === "string" ? tab.sessionId : undefined;
2416
+ return buildSpawnTabUrl(tab.cwd, tab.shell, sessionId);
2417
+ };
2398
2418
  const openCount = registry.attachedClientCount(owner, windowId);
2399
2419
  if (openCount >= manifest.length)
2400
2420
  return;
2401
2421
  if (openCount === 0) {
2402
- for (const tab of manifest) {
2403
- await cdpClient.openBackgroundTab(buildSpawnTabUrl(tab.cwd, tab.shell));
2422
+ for (let index = 0; index < manifest.length; index += 1) {
2423
+ await cdpClient.openBackgroundTab(urlFor(index));
2404
2424
  }
2405
2425
  return;
2406
2426
  }
2407
2427
  if (openCount === 1) {
2408
- // Browser was fully closed; the lone bootstrap tab is repointed to the
2409
- // first restored shell (navigate-the-bootstrap) so the reopen lands
2410
- // exactly N tabs in the manifest's cwds instead of N−1 + one stray in
2411
- // the default directory.
2428
+ // Browser was fully closed; repoint the lone bootstrap to the first tab.
2412
2429
  const firstTargetId = [...wsToTargetId.entries()].find(([ws]) => {
2413
2430
  const profile = registry.clientProfile(ws);
2414
2431
  return profile !== null && profile.owner === owner && profile.windowId === windowId;
2415
2432
  })?.[1];
2416
- if (firstTargetId) {
2417
- await cdpClient.navigateTab(firstTargetId, buildSpawnTabUrl(manifest[0].cwd, manifest[0].shell));
2418
- }
2419
- for (let index = 1; index < manifest.length; index++) {
2420
- await cdpClient.openBackgroundTab(buildSpawnTabUrl(manifest[index].cwd, manifest[index].shell));
2433
+ if (firstTargetId)
2434
+ await cdpClient.navigateTab(firstTargetId, urlFor(0));
2435
+ for (let index = 1; index < manifest.length; index += 1) {
2436
+ await cdpClient.openBackgroundTab(urlFor(index));
2421
2437
  }
2422
2438
  return;
2423
2439
  }
2424
- // Partial survival (a daemon restart with some tabs left open): surviving
2425
- // tabs self-healed to their live cwds, so only the deficit is opened. Which
2426
- // manifest cwds the survivors cover can't be matched (their URLs carry the
2427
- // spawn cwd, stale after a `cd`), so the tail is opened as a best-effort
2428
- // placement — the count is exact, the cwd placement is approximate.
2429
- for (let index = openCount; index < manifest.length; index++) {
2430
- await cdpClient.openBackgroundTab(buildSpawnTabUrl(manifest[index].cwd, manifest[index].shell));
2440
+ for (let index = openCount; index < manifest.length; index += 1) {
2441
+ await cdpClient.openBackgroundTab(urlFor(index));
2431
2442
  }
2432
2443
  };
2433
2444
  const updateCheckStore = new UpdateCheckStore({
@@ -2603,6 +2614,9 @@ export const createServer = async (options = {}) => {
2603
2614
  /* automation cwd vanished since creation; fall back to default */
2604
2615
  }
2605
2616
  }
2617
+ const hibernatedTab = claimedRun || requestedInitialCommand
2618
+ ? undefined
2619
+ : hibernatedTabFor(owner, requestedWindowId, requestedSid);
2606
2620
  const automation = claimedRun
2607
2621
  ? {
2608
2622
  automationId: claimedRun.automationId,
@@ -2611,8 +2625,9 @@ export const createServer = async (options = {}) => {
2611
2625
  }
2612
2626
  : undefined;
2613
2627
  const spawned = registry.spawnAndAttach(ws, {
2614
- cwd: sessionCwd,
2615
- shell: requestedShell,
2628
+ cwd: hibernatedTab?.cwd ?? sessionCwd,
2629
+ shell: hibernatedTab?.shell ?? requestedShell,
2630
+ replaySeed: hibernatedTab?.scrollback,
2616
2631
  initialCommand: claimedRun && claimedRun.runner.kind === "shell"
2617
2632
  ? claimedRun.runner.command
2618
2633
  : requestedInitialCommand,
@@ -2905,58 +2920,65 @@ export const createServer = async (options = {}) => {
2905
2920
  // No debug-enabled Chromium browser reachable — runs use `open -g`.
2906
2921
  });
2907
2922
  }
2908
- const stop = async () => {
2909
- flushWorkspaceSnapshot();
2910
- automationScheduler.dispose();
2911
- folderWatchManager.dispose();
2912
- automationGitWatcher.dispose();
2913
- updateCheckStore.dispose();
2914
- webhookTriggerManager.dispose();
2915
- caffeinateManager.dispose();
2916
- processActivityWatcher?.dispose();
2917
- herdrThemeSync?.dispose();
2918
- cdpClient?.close();
2919
- registry.disposeAll();
2920
- // Forcibly tear down every WS first. node-pty + ws upgraded sockets
2921
- // aren't tracked in http.Server's keep-alive set, so target.close() would
2922
- // otherwise wait forever for them and the CLI's force-exit fallback would
2923
- // fire on every shutdown.
2924
- for (const client of wss.clients) {
2923
+ let stopPromise = null;
2924
+ const stop = () => {
2925
+ if (stopPromise)
2926
+ return stopPromise;
2927
+ stopPromise = (async () => {
2928
+ flushWorkspaceSnapshot();
2929
+ hibernateStore.write(await registry.hibernateEntries());
2930
+ automationScheduler.dispose();
2931
+ folderWatchManager.dispose();
2932
+ automationGitWatcher.dispose();
2933
+ updateCheckStore.dispose();
2934
+ webhookTriggerManager.dispose();
2935
+ caffeinateManager.dispose();
2936
+ processActivityWatcher?.dispose();
2937
+ herdrThemeSync?.dispose();
2938
+ cdpClient?.close();
2939
+ registry.disposeAll();
2940
+ // Forcibly tear down every WS first. node-pty + ws upgraded sockets
2941
+ // aren't tracked in http.Server's keep-alive set, so target.close() would
2942
+ // otherwise wait forever for them and the CLI's force-exit fallback would
2943
+ // fire on every shutdown.
2944
+ for (const client of wss.clients) {
2945
+ try {
2946
+ client.terminate();
2947
+ }
2948
+ catch {
2949
+ /* socket already torn down */
2950
+ }
2951
+ }
2925
2952
  try {
2926
- client.terminate();
2953
+ wss.close();
2927
2954
  }
2928
2955
  catch {
2929
- /* socket already torn down */
2956
+ /* idempotent close — wss may already be closed */
2930
2957
  }
2931
- }
2932
- try {
2933
- wss.close();
2934
- }
2935
- catch {
2936
- /* idempotent close — wss may already be closed */
2937
- }
2938
- if (!httpServer)
2939
- return;
2940
- const target = httpServer;
2941
- const closeAllConnections = Reflect.get(target, "closeAllConnections");
2942
- if (typeof closeAllConnections === "function") {
2943
- closeAllConnections.call(target);
2944
- }
2945
- await new Promise((resolve) => {
2946
- let settled = false;
2947
- const settle = () => {
2948
- if (settled)
2949
- return;
2950
- settled = true;
2951
- resolve();
2952
- };
2953
- const grace = setTimeout(settle, SERVER_STOP_GRACE_MS);
2954
- grace.unref?.();
2955
- target.close(() => {
2956
- clearTimeout(grace);
2957
- settle();
2958
+ if (!httpServer)
2959
+ return;
2960
+ const target = httpServer;
2961
+ const closeAllConnections = Reflect.get(target, "closeAllConnections");
2962
+ if (typeof closeAllConnections === "function") {
2963
+ closeAllConnections.call(target);
2964
+ }
2965
+ await new Promise((resolve) => {
2966
+ let settled = false;
2967
+ const settle = () => {
2968
+ if (settled)
2969
+ return;
2970
+ settled = true;
2971
+ resolve();
2972
+ };
2973
+ const grace = setTimeout(settle, SERVER_STOP_GRACE_MS);
2974
+ grace.unref?.();
2975
+ target.close(() => {
2976
+ clearTimeout(grace);
2977
+ settle();
2978
+ });
2958
2979
  });
2959
- });
2980
+ })();
2981
+ return stopPromise;
2960
2982
  };
2961
2983
  return { port: actualPort, host, registry, automationStore, setPublicUrl, setLocalUrl, stop };
2962
2984
  };