/** * ChannelLifecycleSupervisor — owns the start/stop/restart state machine for * every channel plugin. * * Concerns: * - `initialize()` — call each plugin's `init(...)` once and remember success * - Two-phase `start()` + `startDeferredConnects()` for channels whose * `meta.deferConnectUntilAfterListen` is true (HTTP listener has to be up * before they dial out) * - Per-channel manual stop / start (UI-driven; suppresses auto-restart) * - Exponential restart backoff via {@link CHANNEL_RESTART_POLICY} * - Soft restart (called from heartbeat) — stop + startPlugin, manual-stop guarded * * Heartbeat probes themselves live in {@link ChannelHeartbeatScheduler}; this * supervisor schedules them via `onPluginStarted` / `onPluginStopped` callbacks * passed in by the caller. * * Extracted from `ChannelManager` so the manager can be a thin composition root. */ import type { Config } from '../config/schema.js'; import type { MessageBus } from '../infra/bus/index.js'; import type { ChannelPlugin, ChannelPluginSessionModelHooks } from './plugin-types.js'; import type { ChannelPluginRegistry } from './plugin-registry.js'; export interface ChannelLifecycleSupervisorOptions { bus: MessageBus; registry: ChannelPluginRegistry; /** Effective config snapshot (per-channel `channels..*`). */ getConfig: () => Config; /** Hooks for sub-classes / external observers: heartbeat scheduling, etc. */ onPluginStarted?: (plugin: ChannelPlugin) => void; onPluginStopped?: (pluginId: string) => void; /** Optional session-model hooks forwarded into `plugin.init()`. */ getSessionModelHooks?: () => ChannelPluginSessionModelHooks | undefined; } export declare class ChannelLifecycleSupervisor { private readonly opts; /** Plugin ids whose `init()` completed (used by start/stop loops). */ private readonly initializedPluginIds; private readonly restartAttempts; /** When set, failed-start auto-restart is suppressed for that channel id. */ private readonly manuallyStopped; /** Plugins that skipped `start()` until `startDeferredConnects()`. */ private readonly deferredConnectPending; private initialized; private running; constructor(opts: ChannelLifecycleSupervisorOptions); isInitialized(pluginId: string): boolean; snapshot(): { initialized: boolean; running: boolean; initializedPluginIds: string[]; manuallyStopped: string[]; restartAttempts: Record; }; /** Channel ids that would run and declare `meta.deferConnectUntilAfterListen` (for logging / metrics). */ listDeferConnectChannelIds(cfg: Config): string[]; initialize(): Promise; initializeChannel(channelId: string): Promise; /** Phase 1: start every enabled channel except those in `deferConnectPluginIds`. */ start(options?: { deferConnectPluginIds?: ReadonlySet; }): Promise; /** Phase 2: `start()` for channels deferred at Phase 1. No-op if none pending. */ startDeferredConnects(): Promise; stop(): Promise; /** Stop one channel and suppress automatic restart until `startChannel` is called. */ stopChannel(channelId: string): Promise; /** Clear manual-stop and start one channel (requires prior `initialize()`). */ startChannel(channelId: string): Promise; /** Called by heartbeat: stop + start without clearing manualStop. No-op if user stopped it. */ softRestart(channelId: string): Promise; /** Forward a config snapshot to every initialised plugin's `onConfigUpdated` hook. */ forwardConfigUpdate(cfg: Config): Promise; /** * Builtin channels require `channels..enabled`. Extension-managed channels * run unless explicitly disabled. */ private shouldRunChannelPlugin; private initializePlugin; private startPlugin; private stopPlugin; }