/** * daemon-autostart.ts, starting a daemon that is installed but not running, * once, at boot. * * ── Why this exists ──────────────────────────────────────────────────────── * * A surface product used to solve "no daemon on the port" by BEING one: it * embedded a daemon server in its own process. That is gone, the daemon is its * own product and a surface adopts one or does without. * * Which leaves a case that must not become the user's problem: the daemon is * installed on this machine, the service is simply stopped, and the surface * boots to "no daemon" with a suggestion to go type something. So boot * discovery gets exactly one recovery step, ask the platform service manager * whether the daemon's service entry exists, start it if it does, wait a bounded * time, and re-probe. * * Both surface products grew this independently and arrived at the same policy, * with each carrying one thing the other lacked. This is the union of the two: * the terminal app's outcome renderer and its prefer-an-already-running-unit * choice, and the chat host's attempt-counted wait (which terminates even with * an injected no-op sleep), its `platform`/`unitPath` reporting, its * duplicate-name de-duplication, and its explicit refusal when the only * installed entry is one no service manager here can start. * * ── The boundaries, which stay strict ────────────────────────────────────── * * - A REACHABLE daemon is never restarted. Adopting is the whole point. * - A HELD port, `blocked` (an unverified process) or `incompatible` (a * GoodVibes daemon on a wire version this build refuses), is left alone. * Those are the closest states a probe has to "another owner is mid-update", * and stepping on either turns a transient state into an outage. * - A service the manager already reports ACTIVE gets a bounded wait, never a * second start underneath it. * - A daemon that is genuinely NOT installed gets honest guidance and nothing * else. A surface never spawns one. * - Every failure is reported and none of them break boot. Discovery failing is * a reason to say so, not a reason to refuse to start. */ import { type ManagedServiceActionResult, type ManagedServiceStatus } from '../../daemon/index.js'; import type { ConfigManager } from '../../config/manager.js'; /** The daemon's managed service name (what the installer registers). */ export declare const MANAGED_DAEMON_SERVICE_NAME = "goodvibes"; /** The unit name older installs registered. */ export declare const LEGACY_DAEMON_SERVICE_NAME = "goodvibes-daemon"; /** The service manager's action-runner result, under this module's name. */ export type DaemonServiceActionResult = ManagedServiceActionResult; /** One candidate service entry, as the platform service manager sees it. */ export interface DaemonServiceSnapshot { readonly serviceName: string; readonly platform: ManagedServiceStatus['platform'] | 'unknown'; readonly unitPath: string; readonly installed: boolean; readonly running: boolean; /** * False on the 'manual' platform: there the service manager would spawn its * own locally-resolved command, which a surface cannot honestly resolve for a * daemon it does not own, those stay on the guidance path. */ readonly startSupported: boolean; } export interface DaemonServiceStartResult { readonly ok: boolean; readonly error?: string | undefined; } /** The narrow detector/starter seam, so tests never touch the host's services. */ export interface DaemonServiceControl { snapshot(): readonly DaemonServiceSnapshot[]; start(serviceName: string): DaemonServiceStartResult; } export interface DaemonServiceControlOptions { readonly configManager: ConfigManager; readonly workingDirectory: string; readonly homeDirectory: string; /** Injectable systemctl/launchctl/schtasks runner. */ readonly actionRunner?: ((command: string, args: readonly string[]) => DaemonServiceActionResult) | undefined; } /** * Build the detector/starter over `PlatformServiceManager`. * * When `service.serviceName` is configured to something other than the managed * default, that is an explicit operator choice and is trusted exclusively; * otherwise the managed name AND the older unit name are both checked, so an * install that predates the rename is still found. */ export declare function createDaemonServiceControl(options: DaemonServiceControlOptions): DaemonServiceControl; /** Why there was nothing to do. */ export type DaemonAutostartInactionReason = 'daemon-active' | 'port-held' | 'daemon-disabled' /** A probe verdict this policy does not recognise, never treated as recoverable. */ | 'unrecognized-mode'; /** What the one boot-time recovery step did. */ export type DaemonAutostartOutcome = { readonly action: 'none'; readonly reason: DaemonAutostartInactionReason; } | { readonly action: 'not-installed'; } | { readonly action: 'started'; readonly serviceName: string; } | { readonly action: 'came-online'; readonly serviceName: string; } | { readonly action: 'start-failed'; readonly serviceName: string; readonly reason: string; }; export interface DaemonAutostartOptions { /** The probe's verdict for the configured daemon port. */ readonly daemonMode: string; readonly control: DaemonServiceControl; /** Is a daemon answering yet? Polled until the bounded wait runs out. */ readonly isReachable: () => Promise; readonly waitTimeoutMs?: number | undefined; readonly pollIntervalMs?: number | undefined; /** Injectable so tests drive the wait loop deterministically. */ readonly sleep?: ((ms: number) => Promise) | undefined; } /** * Start an installed-but-stopped daemon once, and wait a bounded time for it. * * Returns what happened rather than throwing: the caller renders it, and a * failure here never breaks boot. Pure over its seams, every effect goes * through `control` and `isReachable`. */ export declare function autostartInstalledDaemon(options: DaemonAutostartOptions): Promise; /** * Render one autostart outcome as the line the user reads. * * Kept beside the outcome type so the wording and the states it covers cannot * drift apart, and so a product's boot path stays a call rather than a switch. */ export declare function describeDaemonAutostart(outcome: DaemonAutostartOutcome, adoptedAfterwards: boolean, adoptionFailureReason?: string | undefined): { readonly level: 'low' | 'high'; readonly text: string; } | null; //# sourceMappingURL=daemon-autostart.d.ts.map