diff --git a/docs/architecture.md b/docs/architecture.md index 354e0a77c..012e47083 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -382,5 +382,5 @@ $PASEO_HOME/ ## Deployment models 1. **Local daemon** (default): `paseo daemon start` on `127.0.0.1:6767` -2. **Managed desktop**: Electron app spawns daemon as subprocess +2. **Managed desktop**: Electron app spawns daemon as subprocess, and stops it again on quit so that "restart the app" is a complete reset. Settings > Host > "Keep daemon running after quit" opts out. Only a daemon the desktop started is stopped — a daemon you started yourself with `paseo daemon start` is left alone (`paseo.pid` records `desktopManaged`). 3. **Remote + relay**: Daemon behind firewall, relay bridges with E2E encryption diff --git a/docs/product.md b/docs/product.md index 2eeeb0b84..e8a7a553b 100644 --- a/docs/product.md +++ b/docs/product.md @@ -43,7 +43,8 @@ This architecture means: - The daemon can run on any machine: laptop, VM, remote server - Multiple clients can connect simultaneously -- Agents keep running when you close the app +- Agents keep running when a client disconnects — the daemon owns them, not the client +- Quitting the desktop app stops the daemon it started, so "restart the app" is a real fix; a daemon you run yourself is unaffected ## Target user diff --git a/packages/app/src/desktop/settings/desktop-settings.ts b/packages/app/src/desktop/settings/desktop-settings.ts index cbda11e02..3ff857652 100644 --- a/packages/app/src/desktop/settings/desktop-settings.ts +++ b/packages/app/src/desktop/settings/desktop-settings.ts @@ -28,7 +28,7 @@ export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = { releaseChannel: "stable", daemon: { manageBuiltInDaemon: true, - keepRunningAfterQuit: true, + keepRunningAfterQuit: false, }, }; diff --git a/packages/app/src/hooks/use-settings/fakes.ts b/packages/app/src/hooks/use-settings/fakes.ts index 95d46ad94..b13163e6d 100644 --- a/packages/app/src/hooks/use-settings/fakes.ts +++ b/packages/app/src/hooks/use-settings/fakes.ts @@ -31,7 +31,7 @@ const DEFAULT_DESKTOP: DesktopSettings = { releaseChannel: "stable", daemon: { manageBuiltInDaemon: true, - keepRunningAfterQuit: true, + keepRunningAfterQuit: false, }, }; diff --git a/packages/desktop/src/daemon/quit-lifecycle.test.ts b/packages/desktop/src/daemon/quit-lifecycle.test.ts index 1d636bd95..5dd1e6680 100644 --- a/packages/desktop/src/daemon/quit-lifecycle.test.ts +++ b/packages/desktop/src/daemon/quit-lifecycle.test.ts @@ -7,12 +7,12 @@ import { stopDesktopManagedDaemonOnQuitIfNeeded, } from "./quit-lifecycle"; -const SETTINGS_KEEP_RUNNING = DEFAULT_DESKTOP_SETTINGS; -const SETTINGS_STOP_ON_QUIT = { +const SETTINGS_STOP_ON_QUIT = DEFAULT_DESKTOP_SETTINGS; +const SETTINGS_KEEP_RUNNING = { ...DEFAULT_DESKTOP_SETTINGS, daemon: { ...DEFAULT_DESKTOP_SETTINGS.daemon, - keepRunningAfterQuit: false, + keepRunningAfterQuit: true, }, }; @@ -29,7 +29,7 @@ function waitForQuitLifecycle(): Promise { } describe("quit-lifecycle", () => { - it("only stops when keepRunningAfterQuit is explicitly disabled", () => { + it("stops by default and only keeps running when keepRunningAfterQuit is enabled", () => { expect(shouldStopDesktopManagedDaemonOnQuit(SETTINGS_STOP_ON_QUIT)).toBe(true); expect(shouldStopDesktopManagedDaemonOnQuit(SETTINGS_KEEP_RUNNING)).toBe(false); }); diff --git a/packages/desktop/src/settings/desktop-settings.test.ts b/packages/desktop/src/settings/desktop-settings.test.ts index 0291f220e..445f5b089 100644 --- a/packages/desktop/src/settings/desktop-settings.test.ts +++ b/packages/desktop/src/settings/desktop-settings.test.ts @@ -162,6 +162,50 @@ describe("desktop-settings", () => { expect(persisted).toBe(raw); }); + it("resets the pre-existing keep-running default so the daemon stops with the app", async () => { + const userDataPath = await createTempUserDataDir(); + directories.add(userDataPath); + await writeFile( + settingsFilePath(userDataPath), + JSON.stringify({ + version: 1, + settings: { + releaseChannel: "stable", + daemon: { manageBuiltInDaemon: true, keepRunningAfterQuit: true }, + }, + migrations: { legacyRendererSettingsImported: true }, + }), + ); + const store = createDesktopSettingsStore({ userDataPath }); + + const settings = await store.get(); + + expect(settings.daemon.keepRunningAfterQuit).toBe(false); + }); + + it("keeps an explicit keep-running choice across restarts", async () => { + const userDataPath = await createTempUserDataDir(); + directories.add(userDataPath); + await writeFile( + settingsFilePath(userDataPath), + JSON.stringify({ + version: 1, + settings: { + releaseChannel: "stable", + daemon: { manageBuiltInDaemon: true, keepRunningAfterQuit: true }, + }, + migrations: { legacyRendererSettingsImported: true }, + }), + ); + await createDesktopSettingsStore({ userDataPath }).patch({ + daemon: { keepRunningAfterQuit: true }, + }); + + const settings = await createDesktopSettingsStore({ userDataPath }).get(); + + expect(settings.daemon.keepRunningAfterQuit).toBe(true); + }); + it("migrates desktop-owned values from legacy renderer settings once", async () => { const userDataPath = await createTempUserDataDir(); directories.add(userDataPath); diff --git a/packages/desktop/src/settings/desktop-settings.ts b/packages/desktop/src/settings/desktop-settings.ts index cfadc1f6f..c8fe23d3e 100644 --- a/packages/desktop/src/settings/desktop-settings.ts +++ b/packages/desktop/src/settings/desktop-settings.ts @@ -22,6 +22,11 @@ interface PersistedDesktopSettingsDocument { settings: DesktopSettings; migrations: { legacyRendererSettingsImported: boolean; + // Installs created before the stop-on-quit default persisted the old + // `keepRunningAfterQuit: true` default to disk, so the new default alone + // would only reach fresh installs. Reset it once; a later explicit toggle + // persists this flag and is never overridden again. + daemonStopOnQuitDefaultApplied: boolean; }; } @@ -35,7 +40,7 @@ export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = { releaseChannel: "stable", daemon: { manageBuiltInDaemon: true, - keepRunningAfterQuit: true, + keepRunningAfterQuit: false, }, }; @@ -72,6 +77,7 @@ function buildDefaultDocument(): PersistedDesktopSettingsDocument { }, migrations: { legacyRendererSettingsImported: false, + daemonStopOnQuitDefaultApplied: true, }, }; } @@ -180,11 +186,18 @@ function coerceDocument(input: unknown): PersistedDesktopSettingsDocument { const migrations = isRecord(input.migrations) ? { legacyRendererSettingsImported: input.migrations.legacyRendererSettingsImported === true, + daemonStopOnQuitDefaultApplied: input.migrations.daemonStopOnQuitDefaultApplied === true, } : { legacyRendererSettingsImported: false, + daemonStopOnQuitDefaultApplied: false, }; + if (!migrations.daemonStopOnQuitDefaultApplied) { + settings.daemon.keepRunningAfterQuit = DEFAULT_DESKTOP_SETTINGS.daemon.keepRunningAfterQuit; + migrations.daemonStopOnQuitDefaultApplied = true; + } + return { version: 1, settings,