@monotykamary/localterm-server 2.76.8 → 2.76.9
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/dist/cdp/cdp-client.d.ts +10 -0
- package/dist/cdp/cdp-client.d.ts.map +1 -1
- package/dist/cdp/cdp-client.js +42 -0
- package/dist/cdp/cdp-client.js.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +15 -0
- package/dist/constants.js.map +1 -1
- package/dist/daemon-config-store.d.ts +2 -0
- package/dist/daemon-config-store.d.ts.map +1 -1
- package/dist/daemon-config-store.js +15 -0
- package/dist/daemon-config-store.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +132 -3
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +14 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +20 -3
- package/dist/schemas.js.map +1 -1
- package/dist/session-client-hub.d.ts +7 -0
- package/dist/session-client-hub.d.ts.map +1 -1
- package/dist/session-client-hub.js +60 -0
- package/dist/session-client-hub.js.map +1 -1
- package/dist/session-manager.d.ts +7 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +9 -0
- package/dist/session-manager.js.map +1 -1
- 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, 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, 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";
|
|
@@ -40,6 +40,7 @@ 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
42
|
import { HibernateStore } from "./hibernate-store.js";
|
|
43
|
+
import { WorkspaceStore } from "./workspace-store.js";
|
|
43
44
|
import { createDefaultSecretBackend } from "./secret-backend.js";
|
|
44
45
|
import { SecretStore } from "./secret-store.js";
|
|
45
46
|
import { ProcessStore } from "./process-store.js";
|
|
@@ -193,7 +194,7 @@ const normalizeMouseAction = (input) => {
|
|
|
193
194
|
};
|
|
194
195
|
const buildApiRoutes = (ctx) => {
|
|
195
196
|
const api = new Hono();
|
|
196
|
-
const { registry, ownerFor, cdpClient, secretBackend, secretExportScryptWorkFactor, secretStore, shimsDir, processStore, themeStore, fontStore, syncSecretShims, automationStore, broadcastAutomations, syncFolderWatchers, syncSessionEventListeners, webhookTriggerManager, worktreeConfigStore, portsSnapshotProcesses, portsSnapshotListeners, toAutomationWithNextRun, listAutomationsWithNextRun, tryLaunch, getCdpPort, applyCdpPort, getGraceSeconds, applyGraceSeconds, connectCdpNow, buildTabUrl, mintViewerCookie, stateDirectory, broadcastThemes, broadcastFonts, updateCheckStore, } = ctx;
|
|
197
|
+
const { registry, ownerFor, cdpClient, secretBackend, secretExportScryptWorkFactor, secretStore, shimsDir, processStore, themeStore, fontStore, syncSecretShims, automationStore, broadcastAutomations, syncFolderWatchers, syncSessionEventListeners, webhookTriggerManager, worktreeConfigStore, portsSnapshotProcesses, portsSnapshotListeners, toAutomationWithNextRun, listAutomationsWithNextRun, tryLaunch, getCdpPort, applyCdpPort, getGraceSeconds, applyGraceSeconds, getWorkspaceRestore, applyWorkspaceRestore, connectCdpNow, buildTabUrl, mintViewerCookie, stateDirectory, broadcastThemes, broadcastFonts, updateCheckStore, } = ctx;
|
|
197
198
|
// Headless SGR-1006 fallback for `mouse` when no CDP tab is reachable:
|
|
198
199
|
// encode the gesture as SGR bytes and write them straight to the PTY. Closes
|
|
199
200
|
// over `registry` so the automation layer stays CDP-agnostic. Coords arrive
|
|
@@ -1583,6 +1584,7 @@ const buildApiRoutes = (ctx) => {
|
|
|
1583
1584
|
api.get("/config", (context) => context.json({
|
|
1584
1585
|
cdpPort: getCdpPort(),
|
|
1585
1586
|
graceSeconds: getGraceSeconds(),
|
|
1587
|
+
workspaceRestore: getWorkspaceRestore(),
|
|
1586
1588
|
defaultShell: getDefaultShell(),
|
|
1587
1589
|
shells: listKnownShells(),
|
|
1588
1590
|
}));
|
|
@@ -1595,6 +1597,9 @@ const buildApiRoutes = (ctx) => {
|
|
|
1595
1597
|
graceSeconds: parsed.data.graceSeconds === undefined
|
|
1596
1598
|
? getGraceSeconds()
|
|
1597
1599
|
: applyGraceSeconds(parsed.data.graceSeconds),
|
|
1600
|
+
workspaceRestore: parsed.data.workspaceRestore === undefined
|
|
1601
|
+
? getWorkspaceRestore()
|
|
1602
|
+
: applyWorkspaceRestore(parsed.data.workspaceRestore),
|
|
1598
1603
|
defaultShell: getDefaultShell(),
|
|
1599
1604
|
shells: listKnownShells(),
|
|
1600
1605
|
});
|
|
@@ -1672,10 +1677,15 @@ export const createServer = async (options = {}) => {
|
|
|
1672
1677
|
onOutputActivity: () => caffeinateManager.noteOutputActivity(),
|
|
1673
1678
|
onSessionActivity: () => {
|
|
1674
1679
|
caffeinateManager.pokeAuto();
|
|
1680
|
+
scheduleWorkspaceSnapshot();
|
|
1675
1681
|
refreshHerdrThemeSyncState();
|
|
1676
1682
|
},
|
|
1677
1683
|
onSessionEvent: (event, cwd) => {
|
|
1678
1684
|
sessionEventManager.onSessionEvent(event, cwd);
|
|
1685
|
+
// A cwd change reshapes the workspace manifest (the tab's respawn cwd
|
|
1686
|
+
// moves), so re-snapshot alongside the attach/detach-driven snapshot.
|
|
1687
|
+
if (event === "cwd")
|
|
1688
|
+
scheduleWorkspaceSnapshot();
|
|
1679
1689
|
},
|
|
1680
1690
|
onAutomationExit: (automationId, runId, exitCode, log) => {
|
|
1681
1691
|
automationStore.updateRun(automationId, runId, {
|
|
@@ -1760,9 +1770,10 @@ export const createServer = async (options = {}) => {
|
|
|
1760
1770
|
const caffeinateController = options.caffeinateController ?? new CaffeinateController();
|
|
1761
1771
|
const caffeinatePreferencesStore = new CaffeinatePreferencesStore(path.join(stateDirectory, "caffeinate.json"));
|
|
1762
1772
|
const worktreeConfigStore = new WorktreeConfigStore(stateDirectory);
|
|
1773
|
+
const workspaceStore = new WorkspaceStore(path.join(stateDirectory, WORKSPACE_FILENAME));
|
|
1763
1774
|
const hibernateStore = new HibernateStore(path.join(stateDirectory, HIBERNATE_FILENAME));
|
|
1764
1775
|
// Immutable shutdown snapshot for this daemon lifetime. Exact old session ids
|
|
1765
|
-
// let surviving browser tabs recover only their own text.
|
|
1776
|
+
// let surviving or CDP-restored browser tabs recover only their own text.
|
|
1766
1777
|
const startupHibernateEntries = hibernateStore.read();
|
|
1767
1778
|
// Per-process secret injection: a backend (macOS Keychain on darwin) holds
|
|
1768
1779
|
// secret values; a secret is an identity + the env var it exports
|
|
@@ -2291,6 +2302,11 @@ export const createServer = async (options = {}) => {
|
|
|
2291
2302
|
registry.rearmGrace();
|
|
2292
2303
|
return next;
|
|
2293
2304
|
};
|
|
2305
|
+
// Persist + read the workspace-restore toggle. Read at restore time, so a
|
|
2306
|
+
// `PUT /api/config` change takes effect on the next start (the restore runs
|
|
2307
|
+
// once at startup, not live-reactively).
|
|
2308
|
+
const getWorkspaceRestore = () => daemonConfigStore.getWorkspaceRestore();
|
|
2309
|
+
const applyWorkspaceRestore = (enabled) => daemonConfigStore.setWorkspaceRestore(enabled);
|
|
2294
2310
|
// Explicit "Connect now" (Settings → Automation browser → Connect): drop any
|
|
2295
2311
|
// live socket and await a fresh connect so the caller learns the outcome —
|
|
2296
2312
|
// connected + which browser, or the error that explains a failure (e.g. a
|
|
@@ -2315,6 +2331,45 @@ export const createServer = async (options = {}) => {
|
|
|
2315
2331
|
};
|
|
2316
2332
|
}
|
|
2317
2333
|
};
|
|
2334
|
+
// Workspace restore: reopen the user's last tabs via CDP on start (a
|
|
2335
|
+
// tmux-resurrect/herdr-style restore of the *layout*; the shells themselves
|
|
2336
|
+
// don't survive a stop). The manifest is snapshotted to disk — debounced on
|
|
2337
|
+
// attach/detach churn (scheduleWorkspaceSnapshot, hooked into onSessionActivity)
|
|
2338
|
+
// and flushed on graceful stop — so a restart, graceful or a crash, can reopen
|
|
2339
|
+
// the same tabs. Triggered once per (owner, windowId) after the first desktop
|
|
2340
|
+
// tab pairs with CDP, on a quiet window so surviving tabs (a daemon restart
|
|
2341
|
+
// with the browser left open) reattach and are counted before the deficit is
|
|
2342
|
+
// opened. Excludes automation-run tabs and dormant shells (filtered in
|
|
2343
|
+
// SessionManager.workspaceEntries), so only tabs that were actively open
|
|
2344
|
+
// come back. Opt out via Settings → Sessions (config.json workspaceRestore).
|
|
2345
|
+
let workspaceSnapshotTimer = null;
|
|
2346
|
+
const scheduleWorkspaceSnapshot = () => {
|
|
2347
|
+
if (workspaceSnapshotTimer !== null)
|
|
2348
|
+
clearTimeout(workspaceSnapshotTimer);
|
|
2349
|
+
const timer = setTimeout(() => {
|
|
2350
|
+
workspaceSnapshotTimer = null;
|
|
2351
|
+
workspaceStore.write(registry.workspaceEntries());
|
|
2352
|
+
}, WORKSPACE_SNAPSHOT_DEBOUNCE_MS);
|
|
2353
|
+
timer.unref?.();
|
|
2354
|
+
workspaceSnapshotTimer = timer;
|
|
2355
|
+
};
|
|
2356
|
+
const flushWorkspaceSnapshot = () => {
|
|
2357
|
+
if (workspaceSnapshotTimer !== null) {
|
|
2358
|
+
clearTimeout(workspaceSnapshotTimer);
|
|
2359
|
+
workspaceSnapshotTimer = null;
|
|
2360
|
+
}
|
|
2361
|
+
workspaceStore.write(registry.workspaceEntries());
|
|
2362
|
+
};
|
|
2363
|
+
const buildSpawnTabUrl = (cwd, shell, sessionId) => {
|
|
2364
|
+
const url = new URL(localOrigin ?? publicOrigin ?? `http://${FRIENDLY_HOSTNAME}:${actualPort}`);
|
|
2365
|
+
if (cwd)
|
|
2366
|
+
url.searchParams.set("cwd", cwd);
|
|
2367
|
+
if (shell)
|
|
2368
|
+
url.searchParams.set("shell", shell);
|
|
2369
|
+
if (sessionId)
|
|
2370
|
+
url.searchParams.set(SESSION_ID_QUERY_PARAM, sessionId);
|
|
2371
|
+
return url.toString();
|
|
2372
|
+
};
|
|
2318
2373
|
const hibernatedTabFor = (owner, windowId, sessionId) => {
|
|
2319
2374
|
if (!windowId || !sessionId)
|
|
2320
2375
|
return undefined;
|
|
@@ -2322,6 +2377,70 @@ export const createServer = async (options = {}) => {
|
|
|
2322
2377
|
.find((entry) => entry.owner === owner && entry.windowId === windowId)
|
|
2323
2378
|
?.tabs.find((tab) => tab.sessionId === sessionId);
|
|
2324
2379
|
};
|
|
2380
|
+
const restoredWorkspaceKeys = new Set();
|
|
2381
|
+
const workspaceRestoreTimers = new Map();
|
|
2382
|
+
const scheduleWorkspaceRestore = (owner, windowId) => {
|
|
2383
|
+
if (!windowId)
|
|
2384
|
+
return;
|
|
2385
|
+
const key = `${owner ?? ""}\u0000${windowId}`;
|
|
2386
|
+
if (restoredWorkspaceKeys.has(key))
|
|
2387
|
+
return;
|
|
2388
|
+
const existing = workspaceRestoreTimers.get(key);
|
|
2389
|
+
if (existing)
|
|
2390
|
+
clearTimeout(existing);
|
|
2391
|
+
const timer = setTimeout(() => {
|
|
2392
|
+
workspaceRestoreTimers.delete(key);
|
|
2393
|
+
if (restoredWorkspaceKeys.has(key))
|
|
2394
|
+
return;
|
|
2395
|
+
restoredWorkspaceKeys.add(key);
|
|
2396
|
+
void restoreWorkspace(owner, windowId);
|
|
2397
|
+
}, WORKSPACE_RESTORE_SETTLE_MS);
|
|
2398
|
+
timer.unref?.();
|
|
2399
|
+
workspaceRestoreTimers.set(key, timer);
|
|
2400
|
+
};
|
|
2401
|
+
const restoreWorkspace = async (owner, windowId) => {
|
|
2402
|
+
if (!daemonConfigStore.getWorkspaceRestore())
|
|
2403
|
+
return;
|
|
2404
|
+
if (!cdpClient || !cdpClient.isConnected())
|
|
2405
|
+
return;
|
|
2406
|
+
const hibernatedEntry = startupHibernateEntries.find((candidate) => candidate.owner === owner && candidate.windowId === windowId);
|
|
2407
|
+
const workspaceEntry = workspaceStore
|
|
2408
|
+
.read()
|
|
2409
|
+
.find((candidate) => candidate.owner === owner && candidate.windowId === windowId);
|
|
2410
|
+
const manifest = hibernatedEntry?.tabs ?? workspaceEntry?.tabs ?? [];
|
|
2411
|
+
if (manifest.length === 0)
|
|
2412
|
+
return;
|
|
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
|
+
};
|
|
2418
|
+
const openCount = registry.attachedClientCount(owner, windowId);
|
|
2419
|
+
if (openCount >= manifest.length)
|
|
2420
|
+
return;
|
|
2421
|
+
if (openCount === 0) {
|
|
2422
|
+
for (let index = 0; index < manifest.length; index += 1) {
|
|
2423
|
+
await cdpClient.openBackgroundTab(urlFor(index));
|
|
2424
|
+
}
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
if (openCount === 1) {
|
|
2428
|
+
// Browser was fully closed; repoint the lone bootstrap to the first tab.
|
|
2429
|
+
const firstTargetId = [...wsToTargetId.entries()].find(([ws]) => {
|
|
2430
|
+
const profile = registry.clientProfile(ws);
|
|
2431
|
+
return profile !== null && profile.owner === owner && profile.windowId === windowId;
|
|
2432
|
+
})?.[1];
|
|
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));
|
|
2437
|
+
}
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
for (let index = openCount; index < manifest.length; index += 1) {
|
|
2441
|
+
await cdpClient.openBackgroundTab(urlFor(index));
|
|
2442
|
+
}
|
|
2443
|
+
};
|
|
2325
2444
|
const updateCheckStore = new UpdateCheckStore({
|
|
2326
2445
|
filePath: path.join(stateDirectory, UPDATE_CHECK_FILENAME),
|
|
2327
2446
|
currentVersion: options.currentVersion ?? readServerVersion(),
|
|
@@ -2357,6 +2476,8 @@ export const createServer = async (options = {}) => {
|
|
|
2357
2476
|
applyCdpPort,
|
|
2358
2477
|
getGraceSeconds,
|
|
2359
2478
|
applyGraceSeconds,
|
|
2479
|
+
getWorkspaceRestore,
|
|
2480
|
+
applyWorkspaceRestore,
|
|
2360
2481
|
broadcastThemes,
|
|
2361
2482
|
broadcastFonts,
|
|
2362
2483
|
connectCdpNow,
|
|
@@ -2658,6 +2779,13 @@ export const createServer = async (options = {}) => {
|
|
|
2658
2779
|
const targetId = cdpClient?.findTargetIdForToken(token);
|
|
2659
2780
|
if (targetId) {
|
|
2660
2781
|
wsToTargetId.set(ws, targetId);
|
|
2782
|
+
// A desktop tab just paired with CDP — arm a one-shot restore
|
|
2783
|
+
// for its (owner, windowId) so missing workspace tabs reopen
|
|
2784
|
+
// once the reconnection burst settles. Phone PWA tabs never pair
|
|
2785
|
+
// (no debug port), so restore stays scoped to the desktop.
|
|
2786
|
+
const profile = registry.clientProfile(ws);
|
|
2787
|
+
if (profile)
|
|
2788
|
+
scheduleWorkspaceRestore(profile.owner, profile.windowId);
|
|
2661
2789
|
}
|
|
2662
2790
|
}
|
|
2663
2791
|
safeSend(ws, {
|
|
@@ -2797,6 +2925,7 @@ export const createServer = async (options = {}) => {
|
|
|
2797
2925
|
if (stopPromise)
|
|
2798
2926
|
return stopPromise;
|
|
2799
2927
|
stopPromise = (async () => {
|
|
2928
|
+
flushWorkspaceSnapshot();
|
|
2800
2929
|
hibernateStore.write(await registry.hibernateEntries());
|
|
2801
2930
|
automationScheduler.dispose();
|
|
2802
2931
|
folderWatchManager.dispose();
|