/** * Maintenance timer: scheduled auto-update / auto-restart. Runs only on the * primary daemon (bot-0) — restart is a host-wide operation (it takes down all * per-bot daemons), so exactly one process must own it. * * At the scheduled local time (Asia/Shanghai, once/day) it: * - checks the cross-daemon busy gate (anyDaemonBusy) — a session mid-CLI-turn * anywhere defers the run to the next day (no retry); * - auto-update (npm/pnpm/Bun global): update the package with its owning package * manager, then restart * to apply iff the version actually changed; * - auto-restart: just restart. * Before triggering a restart it drops a restart-intent breadcrumb so the fresh * daemon knows to DM the owner (vs. staying silent on a crash-restart). * * runMaintenanceTick is pure over its injected deps (unit tested); the rest is * production wiring. */ import { spawn } from 'node:child_process'; import { type MaintenanceConfig } from '../global-config.js'; import { type RestartIntent } from '../services/restart-intent-store.js'; import { type GlobalInstallPlan } from '../utils/global-install.js'; export interface MaintenanceState { /** Local date the auto-update run was last handled (fired or skipped). */ autoUpdate?: { lastDate: string; }; } export interface MaintenanceDeps { now: () => number; readConfig: () => MaintenanceConfig | undefined; readState: () => MaintenanceState; writeState: (s: MaintenanceState) => void; anyBusy: () => boolean; isLocalDev: () => boolean; /** Serialize the complete install → optional restart handoff. */ withUpdateLock: (fn: () => void) => void; /** Current on-disk botmux version (read fresh — changes after runUpdate). */ currentVersion: () => string; /** Updates the owning npm/pnpm/Bun global install (download/install only). */ runUpdate: () => void; writeIntent: (intent: RestartIntent) => void; /** Spawn a detached `botmux restart` (this process is then killed by pm2). */ triggerRestart: () => void; log?: (msg: string) => void; } /** * One maintenance tick. The schedule is driven solely by auto-update's time * (once/day). At that time: install the latest version (download only), and * — only if a newer version was actually installed AND the auto-restart toggle * is on — restart to apply it. A busy session anywhere skips the whole run to * the next day; auto-restart off ⇒ install only (applied on the next restart). * Pure orchestration over injected deps. */ export declare function runMaintenanceTick(deps: MaintenanceDeps): void; export declare function maintenanceStatePathIn(dir: string): string; export declare function readMaintenanceStateTo(dir: string): MaintenanceState; export declare function writeMaintenanceStateTo(dir: string, s: MaintenanceState): void; /** How often to evaluate the schedule. Sub-minute so an HH:MM target fires * within the same minute it's reached. */ export declare const MAINTENANCE_TICK_MS = 60000; /** Where the auto-restart driver's stdout/stderr is captured, so a failed * restart-to-apply is diagnosable (previously stdio was 'ignore'). */ export declare function maintenanceRestartLogPath(): string; /** * Stable cwd (HOME) for spawns that must not inherit a possibly-deleted cwd. * A global package update replaces the botmux package dir, so any process whose cwd * points there (notably the dashboard, started by pm2 with `cwd: PKG_ROOT`) is * left holding a deleted directory. Both the package-manager child and the * detached restart driver spawned afterwards would then die at startup reading * cwd (`uv_cwd`/ENOENT). Pinning them to HOME sidesteps that entirely. */ export declare function globalInstallUpdateCwd(): string; /** Run the ownership-aware update synchronously. */ export declare function installLatestBotmuxSync(plan?: GlobalInstallPlan): void; /** * Cross-process lock target that serializes global botmux updates * between the scheduled auto-update (this daemon process) and a * dashboard-triggered manual update (the separate `botmux-dashboard` process), * so the two never write the active global install concurrently. Both sides acquire * `withFileLock(Sync)` on this path. */ export declare function globalInstallUpdateLockTargetIn(dataDir: string): string; export declare function globalInstallUpdateLockTarget(): string; /** * Build the command to launch `botmux restart` for applying an auto-update. * * The restart driver must NOT remain a descendant of the daemon it's about to * tear down: `botmux restart` deletes botmux-0 (the very daemon that spawned * this), and when PM2 kills botmux-0 a child in its process tree gets * interrupted — so the restart aborts after deleting botmux-0 and never * restarts the rest (the 2026-06-11 incident). `setsid` starts it in a brand * new session, reparented to init, immune to botmux-0's teardown. Without * setsid we fall back to a plain spawn (still detached by the caller). */ export declare function buildRestartLauncher(node: string, cliEntry: string, hasSetsid: boolean): { cmd: string; args: string[]; }; export declare function detachedRestartEnv(inheritedEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv; /** * Spawn a detached `botmux restart`, immune to this process's own teardown * (setsid → a new session reparented to init, so PM2 killing the current * process doesn't interrupt the restart driver). Output is appended to the * maintenance-restart log so a failed restart stays diagnosable. Shared by the * maintenance timer (auto-update) and the dashboard's manual update/restart. * * @param reason short tag written to the log (e.g. 'auto-update', 'dashboard'). */ export declare function spawnDetachedRestart(reason: string, activePackageRoot?: string, restartLeaseId?: string): ReturnType; /** Start the maintenance loop. Call only on the primary daemon (bot-0). */ export declare function startMaintenance(): void; export declare function stopMaintenance(): void; //# sourceMappingURL=maintenance.d.ts.map