/** * Self-update service — polls the npm registry for the version published on the * update channel and triggers a graceful update. Update rules: * - SAME channel: forward-only — update only when the published version is a * strictly newer semver (an equal/older version is ignored, never a * downgrade). * - Channel CHANGED (operator edited install.config.json): * · legacy/self-spawn mode — force-switch to the new channel's published * version even if it is an OLDER semver (the self-spawn respawn pins that * exact version, so the switch lands). * · broker mode — the switch is DEFERRED, not applied: the supervisor * respawns its own frozen launch channel and ignores install.config, so * a force-switch + /restart would restart-loop. The poller keeps the * launch channel forward-only until a full-stack restart (or the pending * supervisor-side change that passes targetVersion to /restart / re-reads * install.config) makes the switch honourable. * See decideUpdate. * * Single lifecycle owner (plan §4.5, Phase 4): this service no longer spawns * its own replacement. It quiesces the agents, then asks the supervisor * (POST /restart) to run the shared teardown/respawn choreography — the spawn * command re-resolves the channel version by itself (npx). The pre-Phase-4 * self-spawn path survives only as the legacy fallback for dev mode without a * supervisor, gated by the same SENSORIUM_BROKER_SPAWN cutover flag the spawn * services use (see brokerSpawnMode in spawn-common.ts). * * Entry point: startSelfUpdatePoller({ pkgVersion, httpPort }) * Call once after the HTTP server is listening (HTTP mode only). * * Environment variables: * SELF_UPDATE_ENABLED "false" to disable (default: enabled) * SELF_UPDATE_POLL_INTERVAL_MS Registry check interval (default: 60000) * SELF_UPDATE_MIN_UPTIME_MS Min uptime before first update (default: 600000) * SELF_UPDATE_GRACE_MS Grace period for agents to observe flag (default: 30000) * SENSORIUM_BROKER_SPAWN Two-state spawn switch (see brokerSpawnMode): * 1/required = supervisor-only, 0/off = legacy * self-spawn; unset → win32 required, else off * MCP_START_COMMAND Launch-time command; its "@" is the baseline * (currently-running) channel and the fallback when * install.config.json cannot be read. * * Update channel: * Resolved LIVE from ~/.remote-copilot-mcp/install.config.json (MCPStartCommand * → the npm spec tag in "sensorium-mcp@", e.g. @latest or @board) on every * poll cycle. In legacy/self-spawn mode, editing that file re-points the poller * with NO restart or Install-Sensorium.ps1 re-run. In broker/production mode a * file-only edit does NOT complete the switch (see above) — the supervisor is * frozen on its launch channel, so switching production needs a full-stack * restart / installer re-run (or the pending supervisor-side change). If the * config is missing/garbled/tag-less the poller falls back to the launch-time * MCP_START_COMMAND channel; the baseline it compares against is always what * THIS process was launched on. No config / no env / no tag = "latest". */ /** * Resolve the DESIRED release channel LIVE from install.config.json on every * call, so editing that file re-points the poller without any process restart * or installer re-run (behaviour 1). The channel is the "@" in the config's * MCPStartCommand — the same field Install-Sensorium.ps1 reads. * * Defensive by construction: any problem — file missing, unreadable, garbled * JSON, MCPStartCommand absent/non-string, or no parseable @tag — falls back to * the launch-time env channel (getUpdateChannel). A broken config can therefore * never crash the poller nor strand it on a bad channel; worst case it keeps * following the channel it was launched with. */ export declare function resolveDesiredChannel(): string; /** * True iff `a` is a strictly NEWER semver than `b`. Used to keep same-channel * updates forward-only. Defensive: if either version fails to parse, returns * false — an unparseable version must never justify a same-channel update, which * could otherwise be an accidental downgrade. */ export declare function isStrictlyNewer(a: string, b: string): boolean; /** The verdict of one poll cycle — kept pure (no I/O) so the decision logic is * unit-testable in isolation from the registry fetch and the restart path. */ export type UpdateDecision = { action: "none"; } | { action: "adopt-channel"; channel: string; } | { action: "defer-channel-switch"; channel: string; } | { action: "update"; targetVersion: string; reason: "forward" | "channel-switch"; }; /** * Decide what a single poll cycle should do, given the running version/channel, * the freshly-resolved desired channel, and whether we are in broker mode. * * `remoteVersion` is the published version of the channel this cycle actually * TRACKS: the desired channel in legacy/self-spawn mode, but the LAUNCH channel * (currentChannel) in broker mode — because the supervisor can only respawn its * own frozen launch channel (see the caller). * * - Legacy/self-spawn mode, channel CHANGED (behaviour 2): force-switch to the * desired channel's published version even if it is an OLDER semver — the * self-spawn path pins that exact version, so the switch lands. If that * version is already running, just adopt the new channel label. * - Broker mode, channel CHANGED: DO NOT force-switch. The supervisor respawns * its frozen launch channel and ignores install.config, so a force-switch + * /restart would loop forever (each child comes up on the launch channel and * re-detects the mismatch). We defer the switch and keep tracking the launch * channel forward-only, so a genuine forward move on the launch channel still * applies while the switch waits for a full-stack restart. * - SAME channel (either mode): forward-only — update only when the published * version is strictly newer (an equal/older version is ignored, so a poll can * never downgrade). */ export declare function decideUpdate(args: { currentVersion: string; currentChannel: string; desiredChannel: string; remoteVersion: string; brokerMode: boolean; }): UpdateDecision; /** * Fetch the published version of sensorium-mcp for the given update channel * (npm dist-tag) from the registry. Defaults to the live desired channel when no * channel is supplied, so external callers keep the previous behaviour. * Returns null on any error so the caller can silently skip this poll cycle. */ export declare function getRemoteVersion(channel?: string): Promise; /** * Run one check cycle. The desired channel is resolved LIVE from * install.config.json each cycle (behaviour 1). A change vs the running channel * force-switches to that channel's published version even if it is older * (behaviour 2); a same-channel poll updates only when the published version is * strictly newer. See decideUpdate for the full rule set. */ export declare function checkForUpdate(): Promise; /** * Start the self-update polling loop. * Call once after the HTTP server is listening (HTTP mode only). * * @param config.pkgVersion Current package version string (e.g. "3.0.30"). * @param config.httpPort HTTP port the server is bound to (for health polling). */ export declare function startSelfUpdatePoller(config: { pkgVersion: string; httpPort: number; onBeforeSpawn?: () => Promise | void; }): void; //# sourceMappingURL=self-update.service.d.ts.map