import type { PipelineSlot } from '../types/pipeline.js';
import type { ConfigUISchemaWithValues, FieldProbeResult } from './config-ui.js';
import type { IScopedLogger } from './logging.js';
import type { IEventBus, ReadinessScope } from './event-bus.js';
import type { ICapabilityHandle } from './readiness.js';
import type { CapabilityDeclaration } from './capability.js';
import type { ICapabilityRegistry } from './kernel-abstractions.js';
import type { CapabilityDefinition, InferProvider } from '../capabilities/capability-definition.js';
import type { CustomActionsSpec } from '../capabilities/custom-actions.js';
import type { z } from 'zod';
/** Sub-process info (inlined from deleted worker-protocol.ts) */
export interface SubProcessInfo {
    readonly pid: number;
    readonly name: string;
    readonly command: string;
    readonly state: 'running' | 'stopped' | 'crashed';
    readonly cpuPercent: number;
    readonly memoryRss: number;
    readonly uptimeSeconds: number;
}
/** Worker process stats (inlined from deleted worker-protocol.ts) */
export interface WorkerProcessStats {
    readonly pid: number;
    readonly cpuPercent: number;
    readonly memoryRss: number;
    readonly heapUsed?: number;
    readonly uptimeSeconds: number;
    readonly restartCount: number;
    readonly state: 'starting' | 'running' | 'stopping' | 'stopped' | 'crashed';
}
/** Storage interface for addon file operations */
export interface IAddonFileStorage {
    readFile(path: string): Promise<Buffer>;
    writeFile(path: string, data: Buffer): Promise<void>;
    deleteFile(path: string): Promise<void>;
    listFiles(prefix?: string): Promise<readonly string[]>;
    exists(path: string): Promise<boolean>;
}
export interface RequiredStep {
    readonly slot: PipelineSlot;
    readonly outputClasses: readonly string[];
    readonly description: string;
}
/**
 * Minimal model-management API exposed to addons via AddonContext.
 */
export interface IAddonModelManager {
    /** Ensure a model is downloaded. Returns the local file path. */
    ensure(modelId: string, format?: import('../types/models.js').ModelFormat): Promise<string>;
    /** Ensure extra files for a model are downloaded. Returns paths. */
    ensureExtraFiles(modelId: string): Promise<readonly string[]>;
    /** Absolute path to the shared models directory. */
    getModelsDir(): string;
    /** Check if a model is already downloaded. */
    isDownloaded(modelId: string, format?: import('../types/models.js').ModelFormat): boolean;
}
/** Process management for addon sub-processes (ffmpeg, python, etc.) */
export interface IAddonProcessManager {
    /** Spawn a child process managed by this addon's worker */
    spawn(config: SubProcessConfig): Promise<IManagedSubProcess>;
    /** List all sub-processes spawned by this addon */
    listProcesses(): readonly SubProcessInfo[];
    /** Get worker stats (this addon's worker process) */
    getWorkerStats(): WorkerProcessStats;
}
export interface SubProcessConfig {
    readonly name: string;
    readonly command: string;
    readonly args?: readonly string[];
    readonly cwd?: string;
    readonly env?: Readonly<Record<string, string>>;
    readonly autoRestart?: boolean;
    readonly maxRestarts?: number;
}
export interface IManagedSubProcess {
    readonly pid: number;
    readonly name: string;
    getStats(): WorkerProcessStats;
    write(data: Buffer): void;
    readonly stdout: AsyncIterable<Buffer>;
    readonly stderr: AsyncIterable<Buffer>;
    kill(signal?: NodeJS.Signals): void;
    wait(): Promise<{
        code: number | null;
        signal: string | null;
    }>;
    onExit(handler: (code: number | null) => void): void;
    onError(handler: (error: Error) => void): void;
}
/** Dependency manager — ensures binaries/tools are available for addons */
export interface IAddonDepsManager {
    /** Ensure a named binary is available (check local -> PATH -> download) */
    ensureBinary(opts: {
        readonly name: string;
        readonly downloadUrl: string;
        readonly isArchive?: boolean;
        readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz';
        readonly archiveInnerPath?: string;
    }): Promise<string>;
    /** Ensure ffmpeg is available */
    ensureFfmpeg(): Promise<string>;
    /** Ensure Python is available, returns path or null if unavailable */
    ensurePython(): Promise<string | null>;
    /** Install Python packages in the managed Python environment */
    installPythonPackages(packages: readonly string[]): Promise<void>;
    /**
     * Install a pip requirements file into the embedded Python.
     * Idempotent: keyed on (file basename + sha256 of contents). Marker
     * lives under the python install dir so re-downloading python wipes
     * everything together.
     */
    installPythonRequirements(requirementsFile: string): Promise<void>;
    /** Get the deps directory path */
    getDepsDir(): string;
}
/**
 * AddonContext -- injected into every addon at initialize().
 *
 * The context includes both the CamstackContext fields (id, logger, eventBus, storage, config)
 * and addon-specific extras (addonConfig, models, locationPaths, router, network).
 */
/**
 * AddonContext -- injected into every addon at initialize().
 *
 * The `api` field is typed as `AddonApi` — a generated interface that
 * mirrors the server's tRPC router namespaces. This gives addons type-safe
 * access to server procedures without depending on the server package.
 *
 * To regenerate after changing routers: npx tsx scripts/generate-api-types.ts
 */
/**
 * A capability provider binding returned from `ICamstackAddon.initialize()`.
 * The kernel registers each entry in the CapabilityRegistry after init completes.
 */
export interface ProviderRegistration<TCap extends CapabilityDefinition = CapabilityDefinition> {
    /** Capability definition this provider implements. Read `capability.name` to look up by name. */
    readonly capability: TCap;
    /**
     * The provider object implementing the capability interface.
     *
     * - With a specific `TCap` (e.g. `ProviderRegistration<typeof metricsProviderCapability>`)
     *   the provider is strictly checked against `InferProvider<TCap>`.
     * - With the default `TCap = CapabilityDefinition` (used by heterogeneous arrays like
     *   `ProviderRegistration[]` returned from `onInitialize()`) the provider is typed as
     *   `object`: a mapped type over the default `Record<string, CapabilityMethodSchema>`
     *   collapses to an index signature that class instances cannot satisfy structurally.
     *   The runtime capability router enforces the contract; the strict compile-time check
     *   is applied at the specific-cap boundary (ProviderRegistration<typeof xxxCap>) instead.
     */
    readonly provider: CapabilityDefinition extends TCap ? object : InferProvider<TCap>;
    /** 'native' (default) — absolute provider.
     *  'wrapper' — user-toggleable decorator that delegates to a native via `ctx.getNativeProvider`. */
    readonly kind?: 'native' | 'wrapper';
    /** Wrapper only — active by default for every compatible device. */
    readonly defaultActive?: boolean;
}
/**
 * Return value of `ICamstackAddon.initialize()`. Both sections are optional —
 * addons that provide only capabilities omit `customActions`; addons that only
 * expose actions omit `providers`; pure consumers may return void.
 */
export interface AddonInitResult<TCatalog extends CustomActionsSpec = CustomActionsSpec> {
    readonly providers?: readonly ProviderRegistration[];
    readonly customActions?: TCatalog;
    /**
     * Required iff `customActions` is set. One async handler per catalog entry —
     * a typed dispatch map rather than a single generic function so that each
     * handler's input/output types collapse to the concrete `z.infer<...>` of
     * its schema at the provider site (TypeScript cannot unify the output of a
     * generic `<K>(action: K, input) => Promise<...>` signature with a concrete
     * per-action return type, so the map form is strictly more typeable).
     */
    readonly actionHandlers?: {
        readonly [K in keyof TCatalog & string]: (input: z.infer<TCatalog[K]['input']>) => Promise<z.infer<TCatalog[K]['output']>>;
    };
}
/**
 * Infrastructure services injected by the kernel at boot time.
 * Grouped under `ctx.kernel` to separate kernel plumbing from
 * addon-level concerns (logger, settings, devices, api).
 */
export interface IKernelServices {
    /**
     * The Moleculer nodeID for this process (e.g. `'hub'` on the hub, or the
     * agent's own name on remote nodes). Addons use this to distinguish local
     * dispatch from remote dispatch without a direct Moleculer dependency.
     */
    readonly localNodeId?: string;
    /**
     * Local storage provider — direct filesystem access with zero serialization.
     * Use for file I/O (write/read with Buffer data) that can't go through tRPC.
     * Always co-located: storage is a kernel-level service injected at boot.
     */
    readonly storage?: import('./storage.js').IStorageProvider;
    /**
     * Per-process system-readiness tracker. One `ReadinessRegistry` is
     * created per broker by the kernel at boot and shared across every
     * addon on that broker — consumers that need `awaitReady` /
     * `onReadyState` should use this singleton rather than constructing
     * their own so subscriptions on the event bus stay de-duplicated and
     * the snapshot is consistent across the process.
     *
     * Tests that run without a real kernel (no `ctx.kernel`) should
     * either inject a mock or fall back to creating a local
     * `ReadinessRegistry`; the production code in kernel always
     * populates this field.
     */
    readonly readinessRegistry?: import('../capabilities/index.js').ITypedReadinessRegistry;
    /**
     * Global device registry — read + mutate devices across all addons.
     * Hub-only: workers don't have a local registry (use `ctx.devices`
     * for per-addon scoped management, or `ctx.api.deviceManager.*` for
     * cross-process access).
     */
    readonly deviceRegistry?: import('../device/device-context.js').IDeviceRegistry;
    /**
     * Per-addon scoped device management — create, register, remove, list devices.
     * Used by device provider addons (rtsp, onvif, frigate) to manage cameras.
     */
    readonly devices: import('../device/device-context.js').DeviceManagerApi;
    /**
     * Cluster runtime — exposes the underlying message broker for addons that
     * need to participate in cluster-wide RPC outside the capability/tRPC
     * surface (e.g. log forwarding, hub readiness probes).
     *
     * Optional: only the agent and hub processes populate this. Forked workers
     * receive their broker through their own runner harness — they do not see
     * `cluster.broker` on ctx.kernel.
     */
    readonly cluster?: {
        readonly broker: IClusterBroker;
    };
    /**
     * Kernel stream-probe bundle. Both entrypoints land on the same
     * hub-side `StreamProbeService` (ffprobe + HTTP reachability check
     * with a 1h cache). Hub resolves them directly; forked workers +
     * remote agents route through the `$stream-probe.*` Moleculer
     * broker service. Providers that call
     * `ctx.kernel.streamProbe.probe(url)` don't need to know where
     * they run.
     */
    readonly streamProbe?: IKernelStreamProbe;
    /**
     * Kernel hardware-accel resolver. Platform-probe that picks an
     * ordered preference list of hw decode backends for the **local
     * host**. Every node (hub + forked worker + remote agent) runs its
     * own probe because hwaccel is inherently hardware-bound: decoder
     * addons on an NVIDIA agent get `['cuda', 'nvdec']`, the same addon
     * on a Mac hub gets `['videotoolbox']`. Cross-node queries for the
     * admin UI go via the `$hwaccel.resolve` Moleculer service
     * registered on every node.
     */
    readonly hwaccel?: IKernelHwAccel;
    /**
     * Capability registry — provider lookups, native-cap resolution, wrapper listing.
     * Hub-only: forked workers don't see this (they use `ctx.api` for cross-process calls).
     */
    readonly capabilityRegistry?: ICapabilityRegistry;
}
/**
 * Canonical hwaccel backend names. Accepted by both the `ffmpeg` CLI
 * (`-hwaccel <name>`) and node-av `HardwareDeviceContext.create()`
 * (`AV_HWDEVICE_TYPE_<upper>`), so a single string drives both layers.
 */
export type HwAccelBackend = 'videotoolbox' | 'cuda' | 'nvdec' | 'vaapi' | 'qsv' | 'd3d11va' | 'dxva2' | 'amf' | 'vdpau' | 'drm';
/** Resolution output — preference list + rationale for observability. */
export interface HwAccelResolution {
    readonly preferred: readonly HwAccelBackend[];
    readonly rationale: string;
}
export interface IKernelHwAccel {
    /**
     * Probe the local host and return the preferred backends.
     *
     * `prefer` accepts:
     *   - `null` / `undefined` → auto, use platform probe
     *   - `'none'` → hwaccel disabled; returns empty list
     *   - an explicit backend name → single-entry list (skip probe)
     *
     * Implementations are pure functions over the host — safe to call
     * repeatedly; results are deterministic per host.
     */
    readonly resolve: (prefer?: HwAccelBackend | 'none' | null) => Promise<HwAccelResolution>;
}
export interface IKernelStreamProbe {
    /**
     * Run ffprobe on an RTSP / HTTP stream URL and return the raw
     * metadata. Use this when you need the width/height/codec/fps data
     * for profile classification. `classifyStream` / `classifyStreams`
     * in `@camstack/types` turn the metadata into a `StreamQuality`.
     */
    readonly probe: (url: string, options?: {
        force?: boolean;
    }) => Promise<import('./device-capabilities/camera.js').StreamMetadata>;
    /**
     * Generic field probe dispatcher — keys starting with `stream` run
     * ffprobe, others run an HTTP GET that aborts at response headers.
     * Returns a `FieldProbeResult` suitable for the admin UI's "Test"
     * button on any `probe`-typed form field.
     */
    readonly probeField: (key: string, value: unknown) => Promise<FieldProbeResult>;
}
/**
 * Minimal structural shape of a cluster-message-broker (Moleculer in the
 * current implementation). Captured in @camstack/types so cluster-aware
 * addons can consume it without pulling a moleculer dependency.
 *
 * Add new methods sparingly — every addition becomes a contract the broker
 * implementation must satisfy.
 */
export interface IClusterBroker {
    readonly nodeID: string;
    call<TParams = unknown, TResult = unknown>(action: string, params?: TParams, opts?: {
        nodeID?: string;
        timeout?: number;
    }): Promise<TResult>;
    waitForServices(service: string | readonly string[], timeout?: number): Promise<void>;
    readonly localBus: {
        on(event: string, handler: (payload: {
            node: {
                id: string;
            };
        }) => void): void;
    };
}
export interface AddonContext<TConfig = Record<string, unknown>> {
    /** Immutable progressive ID */
    readonly id: string;
    /** Pre-scoped logger */
    readonly logger: IScopedLogger;
    /** System event bus */
    readonly eventBus: IEventBus;
    /** Bootstrap configuration from server settings (read-only) */
    readonly addonConfig: Readonly<TConfig>;
    /**
     * Private data directory for this addon.
     * Resolved via storageProvider.resolve('addons-data', '{addonId}/').
     * The addon owns this directory exclusively.
     */
    readonly dataDir: string;
    /** Infrastructure services provided by the kernel. */
    readonly kernel: IKernelServices;
    /**
     * Capability settings API — 3-level resolution (defaults → global → per-device).
     * Replaces context.config (IElementConfig) and context.addonConfig.
     */
    /**
     * Three-level settings store API.
     *
     * Raw, non-merging accessors for the addon's `addon_settings` row and
     * per-device overrides, plus a `(getSection, setSection)` pair for the
     * cluster-wide yml-backed sections consumed by the built-in
     * `system-config` addon.
     *
     * The addon combines these raw reads with its own schema via
     * `hydrateSchema()` inside `getAddonSettings / getGlobalSettings /
     * getDeviceSettings` on `ICamstackAddon`. There is no defaults
     * injection or cross-level merge at this layer.
     *
     * Cleanup note: the earlier `getGlobal / getDevice / updateGlobal /
     * updateDevice` trio (with a polymorphic `getGlobal({ section })`
     * overload) was removed after every caller migrated. The only
     * consumer of the yml-section path was `system-config`, which now
     * goes through the explicit `getSection / setSection` pair below.
     */
    readonly settings?: {
        /**
         * Read the raw addon store for this addon. Holds both `addon`-level
         * and `global`-level values; they're disjoint by schema invariant so
         * they can share one physical store. No defaults merged in. Returns
         * an empty object if the addon has never written anything.
         */
        readAddonStore(): Promise<Record<string, unknown>>;
        /**
         * Write a patch into the addon store. Keys not in the patch are
         * preserved. Keys explicitly set to `null` are preserved as-is (they
         * do NOT fall back to schema default on subsequent reads — that
         * layering happens only at `hydrateSchema` time).
         */
        writeAddonStore(patch: Record<string, unknown>): Promise<void>;
        /**
         * Read the raw per-device store for this addon + device. No defaults,
         * no merge with the addon store. Returns `{}` when no overrides exist.
         */
        readDeviceStore(deviceId: number): Promise<Record<string, unknown>>;
        /**
         * Write a patch into the per-device store. Does NOT touch the
         * addon-level store. Keys preserved as in `writeAddonStore`.
         */
        writeDeviceStore(deviceId: number, patch: Record<string, unknown>): Promise<void>;
        /**
         * Delete all keys for a device from the per-device store.
         * Use on device removal to guarantee no orphaned settings rows remain.
         */
        clearDeviceStore(deviceId: number): Promise<void>;
        /**
         * Read a device's runtime-state blob — separate namespace from
         * `readDeviceStore` (which is the addon's config blob). Holds
         * mutable runtime signals discovered after boot (battery
         * snapshot, sleep flag, last-seen) that survive restart. Stored
         * under the kernel's reserved `__device-state` namespace so the
         * addon's config schema never sees it.
         */
        readDeviceRuntimeState(deviceId: number): Promise<Record<string, unknown>>;
        /**
         * Replace the runtime-state blob for a device. The kernel's
         * `DeviceRuntimeState` always hands over the FULL persistent
         * slice — no shallow merge here, the latest blob wins.
         */
        writeDeviceRuntimeState(deviceId: number, data: Record<string, unknown>): Promise<void>;
        /**
         * Delete the device's runtime-state blob. Use on device removal
         * so a recreated device with the same id starts clean instead
         * of inheriting stale runtime state from the previous lifetime.
         */
        clearDeviceRuntimeState(deviceId: number): Promise<void>;
        /**
         * Read a cluster-wide yml-backed section (server, auth, ffmpeg,
         * logging, recording, retention — whatever
         * `ConfigManager.getSection` recognises). Returns the raw section
         * record with no schema merging. Used by the built-in
         * `system-config` addon to surface the legacy `config.yaml`
         * sections through its `getGlobalSettings()` method.
         */
        getSection(section: string): Promise<Record<string, unknown>>;
        /**
         * Write a cluster-wide yml-backed section. Shallow-merges the patch
         * into the existing section so unrelated keys are preserved.
         * Hub-only — agents have no concept of yml sections and will
         * throw. Used by the `system-config` addon's
         * `updateGlobalSettings()` implementation.
         */
        setSection(section: string, patch: Record<string, unknown>): Promise<void>;
    };
    /**
     * Full tRPC client — same API as the frontend.
     * Transport is transparent: direct caller (in-process), WSS (worker), or WSS (remote agent).
     * Use this for all server interactions: storage, events, devices, streaming, etc.
     */
    readonly api: import('../generated/addon-api.js').AddonApi;
    /**
     * Binary / tool dependency manager. Addons call
     * `ctx.deps.ensureBinary(...)` / `ensureFfmpeg()` / `ensurePython()` to
     * materialise per-addon runtime deps under the shared `data/deps` tree.
     * Injected by the addon host so addons never import the concrete
     * implementation from `@camstack/core` directly.
     */
    readonly deps: IAddonDepsManager;
    /**
     * Typed handle to the native provider of `cap` for `deviceId`. Bypasses the
     * currently-active wrapper — call this ONLY from the wrapper that owns
     * `cap`. Every other consumer should use `fetchDevice(id).<cap>.<method>()`
     * instead, which routes through the cap-router and respects the wrapper
     * chain.
     *
     * Wrapper-vs-consumer rule (memorise this):
     *   - "I am the wrapper for this cap and I need to delegate to the camera
     *     driver" → `getNativeProvider(cap, id)` (bypasses self, no recursion).
     *   - "I am consuming someone else's cap" → `fetchDevice(id).<cap>...`
     *     (uses the wrapper chain — cache, fallback, validation, etc.).
     *
     * Calling `fetchDevice` from inside the cap's own wrapper would re-enter
     * the wrapper and produce infinite recursion through the cap-router.
     *
     * Returns `null` when no addon has published a native provider for this
     * `(cap, deviceId)` — e.g. a camera driver that doesn't implement the cap
     * natively (RtspCamera without `snapshotUrl` does not register snapshot).
     * Wrappers use this to decide between native delegation and their own
     * fallback strategy; they MUST check for null.
     *
     * When the native is registered in this process, returns the local object
     * directly (zero serialization). When it lives in a forked worker, returns
     * a Moleculer-backed proxy constructed by the hub's native-cap fallback
     * (see `CapabilityRegistry.setNativeFallback`) — standard
     * `<addonId>.native-provider.<capName>.<method>` RPC path.
     */
    getNativeProvider<TCap extends CapabilityDefinition>(cap: TCap, deviceId: number): InferProvider<TCap> | null;
    /**
     * Fetch a typed proxy for a device. THE canonical entry point for any
     * consumer that wants to read state, subscribe to changes, or dispatch
     * cap methods on a device — equivalent to `BackendClient.fetchDevice(id)`
     * on the client side.
     *
     * What you can do with the returned proxy:
     *   - Read live state slices: `dev.state.battery.value` (typed
     *     `BatteryStatus | undefined`, no provider call — reads the kernel's
     *     mirrored snapshot).
     *   - Subscribe to slice changes: `dev.state.battery.subscribe(cb)` —
     *     refcounted, auto-seeds the callback with the current value.
     *   - Invoke cap methods through the wrapper chain:
     *     `await dev.snapshot.getSnapshot({...})`. Methods are typed against
     *     each cap's definition; absent caps appear as `undefined`.
     *
     * Routing semantics: every method call on the returned proxy goes through
     * the tRPC cap-router → the active wrapper (if any) → the native provider.
     * That means a wrapper consuming its OWN cap via `fetchDevice` would
     * recurse — wrappers must use `getNativeProvider` instead. See the rule
     * documented on `getNativeProvider` above.
     *
     * Bindings are cached per-addon-context: the first call resolves bindings
     * via `deviceManager.getBindings.query`, subsequent calls reuse the cache.
     * The cache is invalidated implicitly when the device is removed.
     */
    fetchDevice(deviceId: number): Promise<import('../generated/device-proxy.js').DeviceProxy>;
    /**
     * Runtime accessor for the capability registry — exposed to addons that
     * need to enumerate peers of a collection cap (e.g. `turn-provider`) or
     * grab the active singleton for a system cap. Injected by the kernel at
     * addon instantiation; absent when the addon is loaded in isolation
     * (tests, tooling), which is why the field is optional.
     */
    readonly capabilities?: CapabilitiesAccess;
    /**
     * Returns a capability handle immediately (non-blocking). The handle tracks
     * ready/down state via the ReadinessRegistry and gates calls accordingly.
     * Repeated calls with the same `(capName, scope)` return the same cached instance.
     *
     * Use for runtime cap consumption where the cap may not be ready yet and
     * you want calls to block transparently via `handle.call(fn)`.
     */
    useCapability<T = unknown>(capName: string, scope?: ReadinessScope): ICapabilityHandle<T>;
    /**
     * Blocks until the capability is ready, then returns the cached handle.
     * Throws `CapabilityUnavailableError` if the cap does not become ready
     * within `timeoutMs` (default 15 000ms).
     *
     * Use for boot sequencing where downstream work must not start until
     * the cap is confirmed available.
     */
    acquireCapability<T = unknown>(capName: string, scope?: ReadinessScope, opts?: {
        timeoutMs?: number;
    }): Promise<ICapabilityHandle<T>>;
    /**
     * Subscribe to capability state changes (ready / down).
     * Returns an unsubscribe function. Thin wrapper over `ReadinessRegistry.onReadyState`.
     */
    onCapabilityStateChange(capName: string, scope: ReadinessScope, handler: (state: 'ready' | 'down') => void): () => void;
    /**
     * Register a teardown callback that runs automatically when this
     * addon is shut down or restarted (e.g. by `restartAddon` after a
     * runtime update from the admin UI). Disposers run in LIFO order so
     * later registrations clean up before the resources they depend on.
     *
     * Use this for any side effect that survives across function calls
     * — `setInterval`, native socket listeners, watchers, file handles.
     * Capability/event subscriptions returned by `onCapabilityStateChange`
     * / `eventBus.subscribe` are unsubscribe functions you can pass
     * straight to `addDisposer`. Returns a manual unsubscribe so the
     * addon can drop the disposer early if needed.
     *
     *   const stopTimer = setInterval(...);
     *   ctx.addDisposer(() => clearInterval(stopTimer));
     *
     *   const unsub = ctx.eventBus.subscribe('motion', cb);
     *   ctx.addDisposer(unsub);
     *
     * The kernel calls each disposer once during `addon.shutdown()`. A
     * disposer that throws is logged and skipped — subsequent disposers
     * still run.
     */
    addDisposer(fn: () => void | Promise<void>): () => void;
}
/**
 * Runtime-only capability registry surface — kept minimal on purpose.
 * `getCollection` returns every registered provider for a collection cap
 * (order-unstable); `get` returns the active provider for a singleton.
 * Both return `undefined` when the cap isn't declared or has no provider.
 */
export interface CapabilitiesAccess {
    /** All providers registered on a collection capability (by cap name). */
    getCollection<T = unknown>(name: string): readonly T[] | undefined;
    /**
     * Like `getCollection` but returns `[addonId, provider]` tuples so
     * callers can attribute work back to the contributing addon. The
     * `addonId` for cap-routed remote providers is `<addonName>@<workerNodeId>`
     * (e.g. `decoder-nodeav@dev-agent-1/decoder-nodeav`); local providers
     * are just `<addonName>` (no `@`). Used by stream-broker to filter the
     * decoder collection by the orchestrator's per-camera placement
     * decision (`getDecoderAssignment.decoderNodeId`).
     */
    getCollectionEntries<T = unknown>(name: string): readonly (readonly [string, T])[] | undefined;
    /** The active provider of a singleton capability (by cap name). */
    get<T = unknown>(name: string): T | undefined;
}
export interface ICamstackAddon {
    readonly id?: string;
    /** Addon declaration — injected by the framework from package.json. Do not declare in addon class. */
    readonly manifest?: AddonDeclaration;
    /**
     * Initialize the addon. Return capability provider bindings for the kernel
     * to register in the CapabilityRegistry. Returning `void` or an empty array
     * is valid for addons that don't provide capabilities (e.g. pure consumers).
     *
     * The kernel validates that every capability declared in `package.json`
     * has a corresponding entry in the returned array.
     *
     * @example
     * ```ts
     * async initialize(ctx: AddonContext): Promise<AddonInitResult> {
     *   this.detector = new WasmMotionDetector()
     *   await this.detector.load()
     *   return { providers: [{ capability: motionDetectionCapability, provider: this }] }
     * }
     * ```
     */
    initialize(context: AddonContext): Promise<ProviderRegistration[] | AddonInitResult | void>;
    /**
     * Called by the isolated-process runner after `broker.start()`.
     * In-process addons never need this — the hub broker is already running
     * when `initialize()` fires. Forked children emit readiness inside
     * `initialize()` before the broker is live; this re-emits it once the
     * transport is ready so consumers actually receive the event.
     */
    postBrokerStart(): void;
    /**
     * Optional hook. Fires on forked workers / remote agents once the
     * broker has started AND the hub node has been discovered via the
     * Moleculer `$node.connected` event — the moment at which
     * `ctx.api.*` calls to hub-provided capabilities become safe.
     *
     * Use this instead of `initialize()` for any bootstrap work that
     * needs to query the hub (e.g. restoring persisted state, reading
     * hub-owned settings). Calling `ctx.api.*` from `initialize()` on a
     * worker is a documented foot-gun: the broker is not connected yet
     * and requests either time out or throw "Service not found".
     *
     * On the hub itself this never fires (the hub is its own node; there
     * is no separate hub to wait for).
     */
    onHubReachable?(): Promise<void> | void;
    shutdown(): Promise<void>;
    /** Level 1 — node-global settings (schema + values). Appears in
     *  Cluster → Settings tab. `overlay` is an optional preview merge
     *  consumed by override-mode UIs (benchmark) so cascade-aware
     *  addons can re-derive dependent options without touching the
     *  persisted store. */
    getGlobalSettings?(overlay?: Record<string, unknown>): Promise<ConfigUISchemaWithValues>;
    updateGlobalSettings?(patch: Record<string, unknown>): Promise<void>;
    /** Level 2 — per-device settings (schema + values). Appears in
     *  Device Overrides. */
    getDeviceSettings?(deviceId: number): Promise<ConfigUISchemaWithValues>;
    updateDeviceSettings?(deviceId: number, patch: Record<string, unknown>): Promise<void>;
    /**
     * Called at boot after initialize() with all previously persisted devices.
     * The addon should re-instantiate device objects and register them via context.devices.register().
     */
    restoreDevices?(savedDevices: readonly import('../device/device-management.js').SavedDevice[]): Promise<void>;
    getModelCatalog?(): unknown[];
    /** Load/prepare a model engine for a given modelId. Called before process()/classifyAudio(). */
    ensureEngine?(modelId: string): Promise<void>;
    /** Unified vision inference — handles detection, cropping, classification, refinement.
     * Input is FrameInput (root) or CropInput (child node). Output shape determines slot. */
    process?(input: unknown): Promise<unknown>;
    /** Audio classification — separate from vision pipeline */
    classifyAudio?(input: unknown): Promise<unknown>;
}
/** Declaration of a UI page provided by an addon */
export interface AddonPageDeclaration {
    /** Unique page ID (scoped to addon) */
    readonly id: string;
    /** Display label for sidebar */
    readonly label: string;
    /** Lucide icon name for sidebar */
    readonly icon: string;
    /** Route path (e.g., '/benchmark') */
    readonly path: string;
    /**
     * Module Federation remote name — must match the `name` field on the
     * page addon's `federation()` plugin config. Used by admin-ui's
     * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
     * Conventionally `addon_<id>_page` (snake_case; MF names cannot
     * contain hyphens).
     */
    readonly remoteName: string;
    /**
     * Bundle filename inside the addon's `dist/` dir served at
     * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
     * is always `'remoteEntry.js'`.
     */
    readonly bundle: string;
}
/** Provider interface for addons that expose UI pages */
export interface IAddonPageProvider {
    readonly id: string;
    listPages(): readonly AddonPageDeclaration[];
}
/** Provider interface for the admin UI shell (singleton capability) */
export interface IAdminUI {
    getStaticDir(): string;
    getVersion(): string;
}
/**
 * Full addon declaration — lives in package.json `camstack.addons[]`.
 * This is the SINGLE SOURCE OF TRUTH for addon identity and metadata.
 * Addon classes no longer declare a `manifest` property.
 */
export interface AddonDeclaration {
    readonly id: string;
    /** JS entry point relative to package root (e.g., "./dist/addon.js") */
    readonly entry?: string;
    /** Human-readable name */
    readonly name?: string;
    /** Semver version */
    readonly version?: string;
    /** Short description */
    readonly description?: string;
    /** Relative path to SVG icon asset within the addon package */
    readonly icon?: string;
    /** Brand hex color for UI display (e.g., "#3b82f6") */
    readonly color?: string;
    readonly slot?: PipelineSlot | null;
    /** Instance mode: 'unique' or 'multiple'. Default: 'multiple' */
    readonly instanceMode?: 'unique' | 'multiple';
    /** Whether this addon is protected (required by the system, cannot be uninstalled) */
    readonly protected?: boolean;
    /**
     * Group-runner execution model.
     *
     * Every addon lives in a forked subprocess; the question is which one.
     * Addons sharing the same `group` value share one subprocess and one
     * Moleculer broker, so cap calls between them resolve in-process via
     * `localProviderLink` (zero IPC). Different groups speak via Moleculer
     * TCP.
     *
     * Defaults (when `execution` is omitted entirely):
     *   - `placement: 'hub-only'` — addon does NOT deploy to agents.
     *   - `group: 'main-group'`   — joins the catch-all subprocess.
     *
     * Examples:
     *   ```jsonc
     *   // hub-only addon, joins main-group (most common)
     *   "execution": {}
     *
     *   // deployable to any node (cluster scenario)
     *   "execution": { "placement": "any-node" }
     *
     *   // bundle detection on a dedicated subprocess
     *   "execution": { "placement": "any-node", "group": "detection" }
     *
     *   // hardware-bound, only runs on agents (no hub instance)
     *   "execution": { "placement": "agent-only" }
     *
     *   // crash-isolated (private subprocess; unique group per addon)
     *   "execution": { "group": "untrusted-foo" }
     *   ```
     */
    readonly execution?: AddonExecution;
    /** Capabilities this addon provides */
    readonly capabilities?: readonly CapabilityDeclaration[];
    /** UI pages provided by this addon */
    readonly pages?: readonly AddonPageDeclaration[];
    readonly requiredFeatures?: readonly string[];
    readonly inputClasses?: readonly string[];
    readonly outputClasses?: readonly string[];
    readonly requiredSteps?: readonly RequiredStep[];
    readonly supportsCustomModels?: boolean;
    readonly mayRequirePython?: boolean;
    /** Whether this addon performs active inference. Passive addons are excluded from inference pipeline. Default: true. */
    readonly passive?: boolean;
    /**
     * Label output type for classifier/recognizer addons.
     *
     * `classification` — generic class name (e.g. `'bird'`, `'labrador'`)
     * `face`           — recognised face identity
     * `plate`          — recognised licence plate OCR
     * `recognition`    — generic recognition result (embedding gallery)
     */
    readonly labelOutputType?: 'classification' | 'face' | 'plate' | 'recognition';
    /**
     * Optional Python runtime dependencies. Declares the pip requirements
     * file the kernel must install into the embedded portable Python
     * before this addon's `onInitialize()` runs. Resolved relative to the
     * addon's package root.
     *
     * Idempotent: install is keyed on (file basename + sha256 of contents)
     * via the marker dir under the python install — back-to-back boots
     * skip the pip subprocess after the first install.
     *
     * Example:
     *   ```jsonc
     *   "camstack": {
     *     "addons": [{
     *       "id": "detection-pipeline",
     *       "python": { "requirements": "./python/requirements.txt" }
     *     }]
     *   }
     *   ```
     *
     * Backend / feature-specific deps that depend on user settings should
     * NOT be listed here — addons install those lazily via
     * `ctx.deps.installPythonRequirements(absolutePath)`.
     */
    readonly python?: AddonPythonRequirements;
    /** Default configuration values */
    readonly defaultConfig?: Readonly<Record<string, unknown>>;
}
/**
 * Manifest declaration for an addon's Python runtime dependencies.
 * See `AddonDeclaration.python`.
 */
export interface AddonPythonRequirements {
    /**
     * Path to the pip requirements file the kernel installs into the
     * embedded portable Python before `onInitialize()` runs. Resolved
     * relative to the addon package root.
     */
    readonly requirements: string;
}
export interface AddonPackageManifest {
    /** Human-readable package name for UI display (e.g., "CamStack Vision") */
    readonly displayName?: string;
    readonly addons: readonly AddonDeclaration[];
    /**
     * Optional bundle metadata for npm packages that ship multiple addon
     * entries (e.g., `@camstack/addon-pipeline` with 7 entries,
     * `@camstack/addon-remote-storage` with 3 entries). When set, the
     * Addons admin UI renders the entries as collapsible children under a
     * single bundle card. The npm package boundary IS the install/update
     * unit — operator-facing actions (Install, Update, Uninstall) act on
     * the bundle, not on individual children. Per-child enable/disable +
     * settings are still surfaced individually.
     *
     * Single-entry packages omit this field; their UI stays a standalone
     * card. Multi-entry packages WITHOUT this field fall back to deriving
     * displayName from `package.json:name`.
     */
    readonly bundle?: AddonBundleManifest;
    /**
     * Native node modules that the addon needs at runtime but cannot be
     * bundled (`.node` binary files require ABI-matched compilation).
     * Phase E of the bundles + builder modernization spec — mirror of
     * the Python `requirements.txt` pattern, but for native Node deps.
     *
     * Format: package name → semver range, exactly like
     * `package.json#dependencies`. NOT listed in the package's regular
     * `dependencies` field (so workspace `npm install` skips them), but
     * the addon installer reads this field at install time and runs:
     *
     *   npm install --prefix <addonDir> <name>@<range>
     *   electron-rebuild -m <addonDir> -w <name>   (for Electron host)
     *   npm rebuild --prefix <addonDir> <name>     (for plain Node host)
     *
     * The host's runtime ABI (Electron 35 = Node ABI 127, plain Node 22 =
     * ABI 133) is detected at install time. Both targets are first-class.
     *
     * Example:
     * ```jsonc
     * "camstack": {
     *   "nativeDependencies": {
     *     "better-sqlite3": "^12.8.0",
     *     "ssh2": "^1.16.0"
     *   }
     * }
     * ```
     *
     * Phase E will also produce prebuilt `.node` binaries for both ABIs
     * during `npm publish`, shipped in the tarball under
     * `dist/native-prebuilds/<runtime>-<abi>-<arch>/`. The installer
     * tries the matching prebuild first; falls back to source rebuild
     * only when no prebuild exists for the host triple.
     */
    readonly nativeDependencies?: Readonly<Record<string, string>>;
}
export interface AddonBundleManifest {
    /** Display name shown on the bundle card header (e.g., "Pipeline"). */
    readonly displayName: string;
    /** Optional 1-2 sentence description for the operator. */
    readonly description?: string;
    /** Optional icon path relative to the package root. */
    readonly icon?: string;
}
/**
 * Where an addon may be deployed. The kernel honours this when picking
 * which node hosts the group-runner that will load the addon.
 *
 * - `'hub-only'` — runs only on the hub process. Default.
 * - `'agent-only'` — runs only on remote agent processes.
 * - `'any-node'` — eligible to run on either hub or agents.
 */
export type AddonPlacement = 'hub-only' | 'agent-only' | 'any-node';
/**
 * Effective execution model for an addon. See `AddonDeclaration.execution`
 * for the full semantics.
 */
export interface AddonExecution {
    readonly placement?: AddonPlacement;
    /**
     * Subprocess group label. Addons sharing the same value share one
     * forked subprocess + one Moleculer broker. Defaults to `'main-group'`
     * — the catch-all bucket for every addon that doesn't opt out.
     */
    readonly group?: string;
}
export declare const DEFAULT_ADDON_GROUP = "main-group";
export declare const DEFAULT_ADDON_PLACEMENT: AddonPlacement;
/**
 * Resolve the effective `AddonExecution` for an addon. Returns a fully
 * populated value (placement + group always set) so callers can read
 * either field without a separate fallback.
 */
export declare function resolveAddonExecution(decl: Pick<AddonDeclaration, 'execution'>): Required<AddonExecution>;
/** True when the addon may run on a remote agent (deployable from hub). */
export declare function isDeployableToAgent(decl: Pick<AddonDeclaration, 'execution'>): boolean;
/** True when the addon is skipped on the hub (agent-only deployment). */
export declare function isAgentOnlyPlacement(decl: Pick<AddonDeclaration, 'execution'>): boolean;
/** Convenience accessor — the resolved subprocess group label. */
export declare function resolveAddonGroup(decl: Pick<AddonDeclaration, 'execution'>): string;
/** Convenience accessor — the resolved placement (defaults to `hub-only`). */
export declare function resolveAddonPlacement(decl: Pick<AddonDeclaration, 'execution'>): AddonPlacement;
/**
 * Extended AddonContext used exclusively by the kernel (hub + worker boot
 * sequences) to wire providers into the CapabilityRegistry after
 * `initialize()` returns. Addon code never sees this — it only receives
 * the public `AddonContext`.
 */
export interface InternalAddonContext extends AddonContext {
    /** Register a provider in the local capability registry. Kernel-only. */
    registerProvider(capabilityName: string, provider: unknown): void;
    /** Resolve the preferred provider for a capability. Kernel-only. */
    resolveProvider<T = unknown>(capabilityName: string): T | null;
}
//# sourceMappingURL=addon.d.ts.map