@camstack/core 0.1.38 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/auth-manager.d.ts +12 -1
- package/dist/auth/auth-manager.d.ts.map +1 -1
- package/dist/auth/scope-matcher.d.ts +8 -0
- package/dist/auth/scope-matcher.d.ts.map +1 -0
- package/dist/auth/totp-manager.d.ts +0 -1
- package/dist/auth/totp-manager.d.ts.map +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts +15 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts.map +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +27 -6
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +27 -6
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -1
- package/dist/builtins/device-manager/device-config-contribution.d.ts +33 -0
- package/dist/builtins/device-manager/device-config-contribution.d.ts.map +1 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +52 -17
- package/dist/builtins/device-manager/device-manager.addon.d.ts.map +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +285 -161
- package/dist/builtins/device-manager/device-manager.addon.js.map +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +286 -162
- package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -1
- package/dist/builtins/local-auth/auth-schema.d.ts +1 -0
- package/dist/builtins/local-auth/auth-schema.d.ts.map +1 -1
- package/dist/builtins/local-auth/local-auth.addon.d.ts +1 -0
- package/dist/builtins/local-auth/local-auth.addon.d.ts.map +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +354 -3
- package/dist/builtins/local-auth/local-auth.addon.js.map +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +355 -3
- package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -1
- package/dist/builtins/local-auth/oauth-grants.d.ts +46 -0
- package/dist/builtins/local-auth/oauth-grants.d.ts.map +1 -0
- package/dist/builtins/local-auth/oauth-session-manager.d.ts +51 -0
- package/dist/builtins/local-auth/oauth-session-manager.d.ts.map +1 -0
- package/dist/builtins/remote-access-orchestrator/enabled-providers-reconcile.d.ts +97 -0
- package/dist/builtins/remote-access-orchestrator/enabled-providers-reconcile.d.ts.map +1 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts +17 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts.map +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +95 -5
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js.map +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +95 -5
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs.map +1 -1
- package/dist/builtins/snapshot/index.js +1 -3
- package/dist/builtins/snapshot/index.js.map +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -3
- package/dist/builtins/snapshot/index.mjs.map +1 -1
- package/dist/builtins/snapshot/snapshot.addon.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +419 -97
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +419 -98
- package/dist/index.mjs.map +1 -1
- package/package.json +19 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-manager.addon.js","names":[],"sources":["../../../src/builtins/device-manager/device-event-propagator.ts","../../../src/builtins/device-manager/device-manager.addon.ts"],"sourcesContent":["import type { IEventBus, EventSource, SystemEvent } from '@camstack/types'\n\n/**\n * Walks the parent chain for every device-sourced event and re-emits a\n * copy on each ancestor scope with `via[]` populated.\n *\n * Design goals:\n * - Transparent: drivers emit once on their own device scope; the\n * framework handles fan-out. Zero provider boilerplate.\n * - Anti-loop: events that already carry `via[]` are skipped (we only\n * propagate ORIGINAL emissions).\n * - Anti-cycle: the parent chain is bounded — if the device registry\n * is corrupt and has a cycle, the walker caps at `MAX_CHAIN_DEPTH`\n * and logs a warning.\n * - Lazy: parent chain is resolved on-demand per event (no cached\n * topology). The lookup is O(depth) which is ≤2 in practice.\n *\n * `via` contract (from SystemEvent.via JSDoc):\n * - `via[0]` is the originating source (the device that produced the\n * event). Subsequent entries walk up the parent chain.\n * - On the re-emission, `source` is the ancestor at that level and\n * `via[0..i]` is the prefix of the chain up to and including the\n * first N ancestors below the current one.\n *\n * Example (grandchild → parent → grandparent):\n * Original: { source: {id: 7}, data: {...}, via: undefined }\n * Re-emit 1: { source: {id: 4}, data: {...}, via: [{id: 7}] }\n * Re-emit 2: { source: {id: 1}, data: {...}, via: [{id: 7}, {id: 4}] }\n *\n * A consumer listening at `source.id === 1` receives re-emit 2 (with\n * `via` showing the chain). A consumer listening at `source.id === 7`\n * with `via === undefined` receives the original only.\n */\n\n/** Bounded walk — paranoia against corrupt device registries with cycles. */\nconst MAX_CHAIN_DEPTH = 16\n\nexport interface DeviceEventPropagatorOptions {\n readonly eventBus: IEventBus\n /**\n * Resolve the numeric parent id of a device, or `null` when the\n * device has no parent (or was deleted). The propagator injects a\n * closure over the hub's `DeviceRegistry` so the lookup is in-\n * process and O(1) per call.\n */\n readonly getParentOf: (deviceId: number) => number | null\n /** Structured logger for anti-cycle + anti-loop diagnostics. */\n readonly logger: {\n warn: (msg: string, meta?: Record<string, unknown>) => void\n debug: (msg: string, meta?: Record<string, unknown>) => void\n }\n}\n\nexport class DeviceEventPropagator {\n private unsubscribe: (() => void) | null = null\n\n constructor(private readonly opts: DeviceEventPropagatorOptions) {}\n\n start(): void {\n if (this.unsubscribe) return\n // Subscribe to ALL events — the filter is applied inline per event\n // so we can short-circuit non-device sources without walking the\n // event bus's glob engine every time.\n const unsub = this.opts.eventBus.subscribe({}, (ev) => this.handle(ev))\n this.unsubscribe = unsub\n }\n\n stop(): void {\n if (!this.unsubscribe) return\n this.unsubscribe()\n this.unsubscribe = null\n }\n\n /** Exposed for tests — lets them inject events without the full bus. */\n handle(ev: SystemEvent): void {\n if (ev.via !== undefined) return // skip propagated\n if (ev.source.type !== 'device') return // skip non-device scopes\n const rawId = ev.source.id\n const deviceId = typeof rawId === 'number' ? rawId : Number(rawId)\n if (!Number.isFinite(deviceId)) return\n\n const chain = this.resolveParentChain(deviceId)\n if (chain.length === 0) return\n\n // Build `via[]` incrementally: for the first ancestor, via = [origin];\n // for the second, via = [origin, firstAncestor]; etc.\n const origin: EventSource = ev.source\n const via: EventSource[] = [origin]\n for (const ancestorId of chain) {\n const reEmission: SystemEvent = {\n ...ev,\n source: { type: 'device', id: ancestorId },\n via: [...via],\n }\n this.opts.eventBus.emit(reEmission)\n via.push({ type: 'device', id: ancestorId })\n }\n }\n\n private resolveParentChain(deviceId: number): readonly number[] {\n const chain: number[] = []\n const seen = new Set<number>([deviceId])\n let current = this.opts.getParentOf(deviceId)\n while (current != null) {\n if (seen.has(current)) {\n this.opts.logger.warn(\n 'device-event-propagator: cycle detected in parent chain — aborting propagation',\n { tags: { deviceId }, meta: { cycleAt: current, chainSoFar: [...chain] } },\n )\n return chain\n }\n seen.add(current)\n chain.push(current)\n if (chain.length >= MAX_CHAIN_DEPTH) {\n this.opts.logger.warn(\n 'device-event-propagator: chain depth limit hit — truncating',\n { tags: { deviceId }, meta: { depth: chain.length, max: MAX_CHAIN_DEPTH } },\n )\n break\n }\n current = this.opts.getParentOf(current)\n }\n return chain\n }\n}\n","/**\n * Device Manager addon — hub-side singleton that unifies device persistence,\n * live registry queries, and all management operations into a single\n * tRPC-routable capability.\n *\n * Persistence strategy: all device data is stored via `ctx.settings`, the same\n * settings API every other addon uses. No raw SQLite access.\n *\n * Addon store layout:\n * deviceIndex → Record<addonId, stableId[]> (which devices exist per addon)\n * deviceMeta → Record<\"addonId:stableId\", DeviceMeta> (type, name, parentDeviceId, id)\n *\n * Device store (per-device config):\n * readDeviceStore(numericDeviceId) → config blob\n * writeDeviceStore(numericDeviceId, patch)\n *\n * Live registry: resolved from the kernel capability registry after Phase 2.\n * This gives direct access to in-memory IDevice instances registered by provider addons.\n * The DeviceManagerAddon is the single owner of the live device operations API.\n *\n * Replaces:\n * - `device-persistence` capability (absorbed here)\n * - live operations previously served by `device-management.router.ts`\n */\nimport { randomUUID } from 'node:crypto'\nimport { EventCategory,\n ProviderRegistration,\n BaseAddon,\n DeviceType,\n DeviceFeature,\n errMsg,\n} from '@camstack/types'\nimport { DeviceEventPropagator } from './device-event-propagator.js'\nimport { CAP_NAMES_WITH_STATUS } from '@camstack/types'\nimport { WELL_KNOWN_TAB_MAP } from '@camstack/types'\nimport type {\n IDevice,\n ICameraDevice,\n StreamSourceEntry,\n DeviceSettingsContribution,\n ConfigUISchemaWithValues,\n} from '@camstack/types'\n\n/**\n * Wire shape matching `z.infer<typeof SettingsSchemaWithValuesSchema>` —\n * duplicated as a plain interface because importing the Zod schema across\n * package boundaries confuses `tsc` when the types package pins a\n * different `zod` minor than the core package. Keeping the shape local\n * keeps the addon decoupled from the schema's internal type encoding.\n */\ninterface ContributionShape {\n tabs?: Array<{ id: string; label: string; icon: string; order?: number }>\n sections: Array<{\n id: string\n title: string\n description?: string\n style?: 'card' | 'accordion'\n defaultCollapsed?: boolean\n columns?: 1 | 2 | 3 | 4\n tab?: string\n /** Where the section renders. Default 'settings' (Config tab); 'top-tab' hoists into the device-detail tab bar via DeviceDetail discovery. */\n location?: 'settings' | 'top-tab'\n order?: number\n fields: unknown[]\n }>\n}\nimport { deviceManagerCapability, deviceProviderCapability, deviceStateCapability } from '@camstack/types'\nimport type { InferProvider, IDeviceOpsProvider, ICapabilityRegistry } from '@camstack/types'\nimport type { IDeviceRegistry } from '@camstack/types'\nimport type { DeviceBindingEntry } from '@camstack/types'\n\ntype IDeviceManagerProvider = InferProvider<typeof deviceManagerCapability>\ntype IDeviceProviderCap = InferProvider<typeof deviceProviderCapability>\n\n// ── Bindings store types ─────────────────────────────────────────────────────\n\ninterface DeviceBindingsStore {\n readonly deviceBindings: Record<string /* deviceId */, Record<string /* capName */, { wrapperAddonId: string | null }>>\n}\n\ninterface PersistedDeviceMeta {\n type: string\n name: string\n /**\n * Operator-organisational label (room / area / zone). `null` when\n * unset. Mutable through `setLocation` action; surfaced on\n * `IDevice.location` via the kernel-built `DeviceContext.deviceMeta`\n * snapshot. Free-text — providers don't interpret it.\n */\n location: string | null\n /**\n * Soft-disabled flag. When `true`, the device class is still\n * instantiated and visible in the UI (so the operator can flip\n * back on without re-adding) but lifecycle hooks are gated by\n * `BaseDevice.disabled`. Mutable through `setDisabled` action.\n */\n disabled: boolean\n /** Numeric parent id (or null for standalone). The hub resolves the\n * parent's stableId to its numeric id at registration time, and from\n * there everything internal references the parent by `parentDeviceId`\n * — `stableId` only crosses the external boundary (autodiscovery and\n * `(addonId, stableId)` row lookups). */\n parentDeviceId: number | null\n /** Progressive, system-wide unique id. Allocated through\n * `allocateDeviceId` before `registerDevice` runs; backfilled at boot\n * for any legacy meta entry missing it (see init migration below).\n * Post-init invariant: every persisted device has a numeric id. */\n id: number\n /** Snapshot of `device.features` from the most recent `registerDevice`\n * call. Used by `getDevice` when the live `IDevice` instance lives\n * on a forked-worker / remote agent and isn't reachable from the\n * hub registry. Optional for legacy rows that predate the field —\n * callers fall back to an empty array. */\n features?: readonly string[]\n}\n\ninterface AddonStore {\n deviceIndex?: Record<string, string[]>\n deviceMeta?: Record<string, PersistedDeviceMeta>\n /** Hardware-identity metadata blob keyed by `<addonId>:<stableId>`.\n * Stored in a separate key from `deviceMeta` so the lifecycle\n * writers (registerDevice / setName / setLocation / setDisabled)\n * never need to read or preserve the metadata field — single-writer\n * per row eliminates the \"writer X clobbers writer Y's field\" bug\n * class. The `metadata` field on `PersistedDeviceMeta` is a legacy\n * fallback consulted only at read time during the lazy migration —\n * new writes always go to `deviceMetadata`. */\n deviceMetadata?: Record<string, Record<string, unknown>>\n /** Monotonic counter that feeds `PersistedDeviceMeta.id` for every\n * new device. Incremented exactly once per registerDevice; never\n * decremented. */\n nextDeviceId?: number\n /**\n * Operator-curated location registry. Each entry is a free-form room/\n * area label (e.g. \"Cucina\", \"Garage\") that drives the\n * autocomplete in the Device Info location editor. Devices reference\n * these as plain strings via `PersistedDeviceMeta.location` — the\n * registry is purely a suggestion list, NOT a foreign key. Removing a\n * location from the registry leaves devices that still reference it\n * intact; `listLocations` returns the union of registered labels +\n * labels currently in use, so nothing disappears from the UI even if\n * the operator forgot to register a label before assigning it.\n */\n locations?: readonly string[]\n}\n\nfunction shallowEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {\n const ak = Object.keys(a)\n const bk = Object.keys(b)\n if (ak.length !== bk.length) return false\n for (const k of ak) {\n if (a[k] !== b[k]) return false\n }\n return true\n}\n\nfunction deviceKey(addonId: string, stableId: string): string {\n return `${addonId}:${stableId}`\n}\n\n// ── Type guards ──────────────────────────────────────────────────────────────\n\nfunction isCameraDevice(device: IDevice): device is ICameraDevice {\n return 'getStreamSources' in device && typeof (device as ICameraDevice).getStreamSources === 'function'\n}\n\nconst DEVICE_FEATURE_VALUES = new Set<string>(Object.values(DeviceFeature) as readonly string[])\n\n/**\n * Validate persisted feature strings against the `DeviceFeature` enum\n * — workers serialise the live `device.features` array (so every entry\n * is a valid enum value at write time) but the persisted blob is loose\n * `string[]` on the wire. The narrow keeps unknown values out of the\n * `getDevice` response without losing the enum-typed contract.\n */\nfunction persistedFeatures(features: readonly string[] | undefined): DeviceFeature[] {\n if (!features) return []\n const out: DeviceFeature[] = []\n for (const f of features) {\n if (DEVICE_FEATURE_VALUES.has(f)) out.push(f as DeviceFeature)\n }\n return out\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction toDeviceInfo(\n addonId: string,\n device: IDevice,\n metadata: Record<string, unknown> | null = null,\n metaRow: PersistedDeviceMeta | null = null,\n) {\n const configValues: Record<string, unknown> = {}\n for (const entry of device.config.entries()) {\n configValues[entry.key] = entry.value\n }\n // Operator-organisational fields (`name` / `location` / `disabled`)\n // are mutated through `setName` / `setLocation` / `setDisabled`,\n // which write to the persisted `deviceMeta` row and emit\n // `DeviceMetaChanged`. The live `BaseDevice` instance reads these\n // from `ctx.deviceMeta` at CONSTRUCTION ONLY (see\n // `base-device.ts:140-142`), so its `device.name` / `device.location`\n // / `device.disabled` fields are stale until the device class is\n // re-instantiated. Reading from `metaRow` here guarantees the wire\n // response reflects the latest persisted value within one settings\n // round-trip — the UI's enable/disable toggle (and rename) flips\n // immediately after the mutation completes. Falls back to the live\n // device fields when no meta row was supplied (test fixtures, edge\n // paths) so existing behaviour is preserved.\n const name = metaRow?.name ?? device.name\n const location = metaRow?.location !== undefined ? metaRow.location : device.location\n const disabled = metaRow?.disabled ?? device.disabled\n return {\n // Progressive system-wide id — bound on the live `IDevice` at\n // construction time (see `DeviceContext.id` / `BaseDevice.id`). The\n // hub allocates via `allocateDeviceId` BEFORE the device is\n // instantiated, so every live device exposes one.\n id: device.id,\n stableId: device.stableId,\n addonId,\n type: device.type,\n name,\n location,\n disabled,\n parentDeviceId: device.parentDeviceId,\n // `role` is set by the provider on accessory children (synthesized\n // via `getAccessoryChildren()` — Reolink siren/floodlight/PIR,\n // Hikvision supplemental light/siren). Top-level devices and\n // first-class hub-adopted children leave it `undefined`. Surfacing\n // it on `DeviceInfo` lets the UI distinguish accessory rows from\n // adopted-camera rows without re-deriving the heuristic from the\n // provider's `deviceClasses` map.\n role: device.role ?? null,\n online: device.online,\n features: [...device.features],\n isCamera: isCameraDevice(device),\n config: configValues,\n metadata,\n }\n}\n\nfunction resolveDeviceById(\n registry: IDeviceRegistry,\n deviceId: number,\n): { addonId: string; device: IDevice } | null {\n const device = registry.getById(deviceId)\n if (!device) return null\n const addonId = registry.getAddonId(deviceId)\n if (!addonId) return null\n return { addonId, device }\n}\n\n// ── Aggregator merge ─────────────────────────────────────────────────────────\n//\n// Concatenate several contributions into a single wire-shape object matching\n// `z.infer<typeof SettingsSchemaWithValuesSchema>` (plain mutable arrays).\n// Tabs with the same `id` collapse into one; sections within a tab are kept\n// in contribution order (contributors self-sort via `order`).\n//\n// No field-level merge — if two providers target the same `(tab, section)`\n// they're concatenated in discovery order. This is intentional: letting the\n// aggregator pick a winner would mask accidental collisions and silently\n// drop fields.\n//\n// `ContributionShape` / `AggregateShape` mirror the cap output exactly so no\n// assertion is needed when returning from the device-manager handlers.\n\ntype AggregateTabDecl = NonNullable<ContributionShape['tabs']>[number]\ntype AggregateSection = ContributionShape['sections'][number]\n\n/**\n * Walk the sections/fields of a contribution and inject `writerCapName` +\n * `writerAddonId` + `source` on each editable field. Readonly fields and\n * structural fields (separator/info/button) pass through untouched. The\n * aggregator is the single place that knows provenance — provider schemas\n * stay clean, UI-bound metadata is attached once at the boundary.\n */\nfunction tagContribution(\n contribution: ContributionShape,\n capName: string,\n addonId: string,\n kind: 'settings' | 'live',\n): ContributionShape {\n const source = kind === 'settings' ? 'settings' : 'live'\n return {\n ...(contribution.tabs ? { tabs: [...contribution.tabs] } : {}),\n sections: contribution.sections.map(section => ({\n ...section,\n fields: section.fields.map(field => tagField(field, capName, addonId, source, kind)),\n })),\n }\n}\n\nfunction isFieldRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\n/**\n * Convert a strict `ConfigUISchemaWithValues` (readonly arrays, typed\n * field union) into the cap wire shape `ContributionShape` (mutable\n * arrays, opaque field records). Required because the cap method z.infer\n * uses mutable arrays — readonly arrays are not assignable to mutable\n * even when structurally identical, so a structural copy bridges the gap\n * without disabling the type checker.\n */\nfunction toWireShape(input: ConfigUISchemaWithValues): ContributionShape {\n const out: ContributionShape = {\n sections: input.sections.map(s => ({\n id: s.id,\n title: s.title,\n ...(s.description !== undefined ? { description: s.description } : {}),\n ...(s.style !== undefined ? { style: s.style } : {}),\n ...(s.defaultCollapsed !== undefined ? { defaultCollapsed: s.defaultCollapsed } : {}),\n ...(s.columns !== undefined ? { columns: s.columns } : {}),\n ...(s.tab !== undefined ? { tab: s.tab } : {}),\n // `location: 'top-tab'` hoists the section into a discovered top-\n // level device tab in DeviceDetail. Forgetting to forward it here\n // strips the marker before the client sees it, so the tab never\n // shows up — see `DeviceDetail.tsx` aggregate-driven tab discovery.\n ...(s.location !== undefined ? { location: s.location } : {}),\n ...(s.order !== undefined ? { order: s.order } : {}),\n fields: [...s.fields],\n })),\n }\n if (input.tabs) out.tabs = [...input.tabs]\n return out\n}\n\nfunction tagField(\n field: unknown,\n capName: string,\n addonId: string,\n source: 'settings' | 'live',\n kind: 'settings' | 'live',\n): unknown {\n if (!isFieldRecord(field)) return field\n const f = field\n const structuralTypes = new Set(['separator', 'info', 'button'])\n if (typeof f.type === 'string' && structuralTypes.has(f.type)) return field\n\n const tagged: Record<string, unknown> = { ...f, source }\n if (kind === 'live' || f.readonlyField === true) {\n tagged.readonlyField = true\n } else {\n tagged.writerCapName = capName\n tagged.writerAddonId = addonId\n }\n\n // Recurse into composite layouts so nested leaf fields carry the\n // writer routing too. Without this, fields buried inside `'group'`\n // or `'sub-tabs'` containers are surfaced to the UI without a\n // writerCapName/writerAddonId — `findWriter` then can't route the\n // patch back to the cap that produced the field. Records are\n // typed `Record<string, unknown>`, so each step needs an explicit\n // `unknown[]` narrow before `.map` to keep the no-`any` rule happy.\n if (f.type === 'group') {\n const children: readonly unknown[] = Array.isArray(f.fields) ? f.fields : []\n if (children.length > 0) {\n tagged.fields = children.map((child) => tagField(child, capName, addonId, source, kind))\n }\n } else if (f.type === 'sub-tabs') {\n const rawTabs: readonly unknown[] = Array.isArray(f.tabs) ? f.tabs : []\n if (rawTabs.length > 0) {\n tagged.tabs = rawTabs.map((tab) => {\n if (!isFieldRecord(tab)) return tab\n const tabChildren: readonly unknown[] = Array.isArray(tab.fields) ? tab.fields : []\n return {\n ...tab,\n fields: tabChildren.map((child) => tagField(child, capName, addonId, source, kind)),\n }\n })\n }\n }\n return tagged\n}\n\nfunction mergeAggregates(parts: readonly ContributionShape[]): ContributionShape {\n const tabDecls = new Map<string, AggregateTabDecl>()\n const sections: AggregateSection[] = []\n\n for (const part of parts) {\n if (part.tabs) {\n for (const t of part.tabs) {\n if (!tabDecls.has(t.id)) tabDecls.set(t.id, t)\n }\n }\n for (const s of part.sections) {\n sections.push(s)\n }\n }\n\n // Auto-derive tab declarations from section references when contributors\n // don't emit one explicitly. Every tab id referenced in a section's `tab`\n // field gets a declaration — if the id matches a well-known tab\n // (general/detection/streaming/recording/…) the canonical label+icon+order\n // is used; otherwise we fall back to the raw id as label with a generic\n // icon. Ensures the UI has everything it needs to render the tab bar\n // from a single payload — no extra lookup, no duplicate source of truth.\n for (const s of sections) {\n const tabId = s.tab ?? 'general'\n if (tabDecls.has(tabId)) continue\n const known = WELL_KNOWN_TAB_MAP[tabId as keyof typeof WELL_KNOWN_TAB_MAP]\n if (known) {\n tabDecls.set(tabId, { id: known.id, label: known.label, icon: known.icon, order: known.order })\n } else {\n tabDecls.set(tabId, { id: tabId, label: tabId, icon: 'wrench', order: 100 })\n }\n }\n\n sections.sort((a, b) => {\n const tabA = a.tab ?? 'general'\n const tabB = b.tab ?? 'general'\n if (tabA !== tabB) {\n const orderA = tabDecls.get(tabA)?.order ?? 100\n const orderB = tabDecls.get(tabB)?.order ?? 100\n if (orderA !== orderB) return orderA - orderB\n return tabA.localeCompare(tabB)\n }\n return (a.order ?? 0) - (b.order ?? 0)\n })\n\n const sortedTabs = [...tabDecls.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100))\n const out: ContributionShape = { sections }\n if (sortedTabs.length > 0) out.tabs = sortedTabs\n return out\n}\n\nexport class DeviceManagerAddon extends BaseAddon {\n constructor() { super({}) }\n\n /** Shorthand for the kernel-injected capability registry. */\n private get capabilityRegistry(): ICapabilityRegistry | undefined {\n return this.ctx.kernel.capabilityRegistry\n }\n\n /**\n * Parent-chain event propagator. Started in `onInitialize` once the\n * hub's `deviceRegistry` is available; listens to every device-sourced\n * event and re-emits a copy on each ancestor scope with `via[]`\n * populated. Stopped in `onShutdown`.\n */\n private propagator: DeviceEventPropagator | null = null\n /**\n * Hub-side mirror of every device's cap-keyed runtime state.\n * Populated whenever any caller writes via `deviceState.setCapSlice`\n * (the canonical cross-layer write entrypoint) and on first load\n * via `loadRuntimeState`. Cross-process consumers reach the mirror\n * through the `deviceState` cap router; per-cap event subscribers\n * (e.g. `battery.onStatusChanged`) get the same data via\n * cap-specific events still emitted by the owning device.\n *\n * Key: deviceId. Value: per-cap slice map. Empty by default —\n * slices show up as `setCapSlice` calls trickle in.\n */\n private readonly stateMirror = new Map<number, Map<string, Record<string, unknown>>>()\n\n /**\n * Per-device disk-write debouncer for runtime-state. `setCapSlice`\n * updates the in-memory mirror synchronously and emits the change\n * event immediately, but the disk write is coalesced — frequent\n * back-to-back writes (motion phase transitions, battery pushes,\n * etc.) collapse to one `writeDeviceRuntimeState` per\n * `RUNTIME_STATE_DEBOUNCE_MS` window. `flushRuntimeStateWrites`\n * awaits any in-flight write + scheduled flush so shutdown is\n * lossless.\n */\n private readonly runtimeStateDebounce = new Map<number, {\n timer: ReturnType<typeof setTimeout> | null\n inFlight: Promise<void> | null\n }>()\n private static readonly RUNTIME_STATE_DEBOUNCE_MS = 1000\n\n /**\n * Cross-process native-provider cache: deviceId (numeric) → capName → { addonId, nodeId }.\n * Kept in sync with `device.bindings-changed` events emitted by forked\n * workers. Union'd into `getBindings` so hub-side consumers see every\n * native cap regardless of which process owns the IDevice. No persistence\n * — entries re-register when the worker restarts. Purged on\n * `$node.disconnected` to avoid stale routing after a worker crash.\n */\n private remoteNativeCaps: Map<number, Map<string, { addonId: string; nodeId: string }>> = new Map()\n\n /** Wait for a device-provider by addonId, returning null on timeout. */\n private async waitDeviceProvider(addonId: string, timeoutMs = 5_000): Promise<IDeviceProviderCap | null> {\n const provider = await this.capabilityRegistry?.waitForProvider('device-provider', addonId, timeoutMs)\n return provider ? (provider as IDeviceProviderCap) : null\n }\n\n /** Require a device-provider by addonId — throws if not found. */\n private async requireDeviceProvider(addonId: string): Promise<IDeviceProviderCap> {\n const dp = await this.waitDeviceProvider(addonId)\n if (!dp) {\n throw new Error(`Device provider \"${addonId}\" not found or not registered`)\n }\n return dp\n }\n\n // ── Binding store (persisted wrapper activation) ───────────────────────────\n //\n // These methods are public so the device-manager capability router can\n // expose them as cap methods. They read/write through\n // ctx.settings.readAddonStore/writeAddonStore — the same persistence channel\n // the rest of the addon uses.\n\n private async readBindingsStore(): Promise<DeviceBindingsStore> {\n const raw = (await this.ctx.settings!.readAddonStore()) as Partial<DeviceBindingsStore>\n return { deviceBindings: raw.deviceBindings ?? {} }\n }\n\n private async writeBindingsStore(next: DeviceBindingsStore): Promise<void> {\n await this.ctx.settings!.writeAddonStore({ deviceBindings: next.deviceBindings })\n }\n\n private resolveWrapperNodeId(_wrapperAddonId: string): string {\n // TODO(phase-7): read from cluster view so cluster-deployed wrappers\n // report their actual node. For now assume hub-co-located.\n return 'hub'\n }\n\n /**\n * Active discovery of native caps registered on a worker node.\n * Called from the `$node.connected` handler to recover from lost\n * `DeviceBindingsChanged` broadcasts after worker restart.\n *\n * Iterates the broker's service registry, picks\n * `<addonId>.native-provider.<capName>` services owned by the\n * connected node, calls each service's `$listDeviceIds` action to\n * fetch the deviceIds the worker has registered that cap on, then\n * writes entries into `remoteNativeCaps`.\n *\n * Best-effort: per-service failures are logged and swallowed so a\n * single bad service doesn't abort the whole rebuild.\n */\n private async discoverWorkerNativeCaps(connectedNodeId: string, _connectedAddonId: string): Promise<void> {\n const cluster = this.ctx.kernel.cluster\n if (!cluster) return\n const broker = cluster.broker as unknown as {\n registry?: {\n getServiceList?: (opts: Record<string, boolean>) => readonly { name: string; nodeID: string; metadata?: Record<string, unknown> }[]\n }\n call?: (action: string, params: unknown, opts?: { nodeID?: string }) => Promise<unknown>\n }\n const services = broker.registry?.getServiceList?.({ onlyAvailable: true, withActions: false }) ?? []\n const NATIVE_INFIX = '.native-provider.'\n // Filter on nodeID + the `.native-provider.` infix only. The\n // service-name addonId prefix may differ from the addonId we'd\n // derive from the nodeId (groupId suffixes like `-isolated`), so\n // we extract addonId + capName straight from the service name.\n const matched = services.filter((s) =>\n s.nodeID === connectedNodeId && s.name.includes(NATIVE_INFIX),\n )\n if (matched.length === 0) return\n for (const svc of matched) {\n const idx = svc.name.indexOf(NATIVE_INFIX)\n if (idx <= 0) continue\n const addonId = svc.name.slice(0, idx)\n const capName = svc.name.slice(idx + NATIVE_INFIX.length)\n if (!addonId || !capName) continue\n try {\n const action = `${svc.name}.$listDeviceIds`\n const deviceIds = await broker.call?.(action, {}, { nodeID: connectedNodeId }) as readonly number[] | undefined\n if (!deviceIds || deviceIds.length === 0) continue\n for (const deviceId of deviceIds) {\n if (this.capabilityRegistry?.getNativeAddonId(capName, deviceId)) continue\n let perDevice = this.remoteNativeCaps.get(deviceId)\n if (!perDevice) {\n perDevice = new Map()\n this.remoteNativeCaps.set(deviceId, perDevice)\n }\n perDevice.set(capName, { addonId, nodeId: connectedNodeId })\n }\n this.ctx.logger.debug('worker native-cap discovered', {\n meta: { nodeId: connectedNodeId, addonId, capName, deviceIds },\n })\n } catch (err) {\n this.ctx.logger.debug('worker native-cap $listDeviceIds failed', {\n meta: { service: svc.name, nodeId: connectedNodeId, error: errMsg(err) },\n })\n }\n }\n this.ctx.logger.info('worker native-cap discovery completed', {\n meta: { nodeId: connectedNodeId, services: matched.length },\n })\n }\n\n async getBindings(input: { deviceId: number }): Promise<{ deviceId: number; entries: DeviceBindingEntry[] }> {\n // Every map (bindings store, native-cap registry, remoteNativeCaps) is\n // keyed by numeric deviceId now that Phase 2 finished the migration.\n const storeKey = String(input.deviceId)\n const store = await this.readBindingsStore()\n const perDevice = store.deviceBindings[storeKey] ?? {}\n\n const entries: DeviceBindingEntry[] = []\n const seenCaps = new Set<string>()\n\n // 1. Entries from persisted wrapper activations\n for (const [capName, { wrapperAddonId }] of Object.entries(perDevice)) {\n const hubLocalNative = this.capabilityRegistry?.getNativeAddonId(capName, input.deviceId) ?? null\n const remoteNative = this.remoteNativeCaps.get(input.deviceId)?.get(capName) ?? null\n const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? ''\n const nativeNodeId = hubLocalNative\n ? (this.ctx.kernel.localNodeId ?? 'hub')\n : (remoteNative?.nodeId ?? (this.ctx.kernel.localNodeId ?? 'hub'))\n // Operator explicitly disabled the wrapper AND no native is available\n // → cap is genuinely off for this device. Mark it seen so step 2's\n // defaultActive auto-bind doesn't silently re-bind the wrapper.\n if (wrapperAddonId === null && !nativeAddonId) {\n seenCaps.add(capName)\n continue\n }\n entries.push({\n capName,\n kind: wrapperAddonId ? 'wrapped' : 'native',\n providerAddonId: wrapperAddonId ?? nativeAddonId,\n providerNodeId: wrapperAddonId ? this.resolveWrapperNodeId(wrapperAddonId) : nativeNodeId,\n nativeAddonId,\n })\n seenCaps.add(capName)\n }\n\n const remote = this.remoteNativeCaps.get(input.deviceId)\n\n // 2. Auto-activated wrappers — any cap a wrapper addon declared with\n // `defaultActive: true` claims the binding HERE, ahead of native\n // discovery. The wrapper is the active provider; it delegates to\n // the native via `ctx.getNativeProvider`, so \"wrapper decorates\n // native\" is the intended runtime shape and the cap router resolves\n // locally on the wrapper's node instead of trying to forward to the\n // native's worker (cap routers like `snapshot` are mounted without\n // a `createRemoteProxy` because the wrapper handles cross-process\n // delegation internally).\n //\n // Step 1 (persisted activations) still wins: an operator-toggled\n // deactivation marks the cap seen with no entry, blocking this\n // branch from re-binding the wrapper.\n //\n // Pure-wrapper caps (motion-detection, audio-analysis,\n // detection-pipeline) have NO native — the wrapper is the only\n // implementation. They still bind here with an empty `nativeAddonId`\n // so consumers (UI bindings table, pipeline-orchestrator's\n // `isCapActiveForDevice` gate) see the wrapper as active by default.\n // Mirrors the fallback semantics of step 1 (`?? ''`).\n if (this.capabilityRegistry) {\n for (const capName of this.capabilityRegistry.getCapsWithDefaultWrapper()) {\n if (seenCaps.has(capName)) continue\n const defaultWrapperAddonId = this.capabilityRegistry.getDefaultWrapperForCap(capName)\n if (!defaultWrapperAddonId) continue\n const hubLocalNative = this.capabilityRegistry.getNativeAddonId(capName, input.deviceId) ?? null\n const remoteNative = remote?.get(capName) ?? null\n const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? ''\n entries.push({\n capName,\n kind: 'wrapped',\n providerAddonId: defaultWrapperAddonId,\n providerNodeId: this.resolveWrapperNodeId(defaultWrapperAddonId),\n nativeAddonId,\n })\n seenCaps.add(capName)\n }\n }\n\n // 3. Entries from hub-local registered natives (no wrapper configured)\n if (this.capabilityRegistry) {\n for (const capName of this.capabilityRegistry.getNativeCapsForDevice(input.deviceId)) {\n if (seenCaps.has(capName)) continue\n const nativeAddonId = this.capabilityRegistry.getNativeAddonId(capName, input.deviceId) ?? ''\n entries.push({\n capName,\n kind: 'native',\n providerAddonId: nativeAddonId,\n providerNodeId: this.ctx.kernel.localNodeId ?? 'hub',\n nativeAddonId,\n })\n seenCaps.add(capName)\n }\n }\n\n // 4. Entries from remote (forked-worker) natives learned via\n // `device.bindings-changed` broadcasts. Union'd here so consumers see\n // every cap for the device regardless of owning process.\n if (remote) {\n for (const [capName, info] of remote) {\n if (seenCaps.has(capName)) continue\n entries.push({\n capName,\n kind: 'native',\n providerAddonId: info.addonId,\n providerNodeId: info.nodeId,\n nativeAddonId: info.addonId,\n })\n seenCaps.add(capName)\n }\n }\n\n return { deviceId: input.deviceId, entries }\n }\n\n /**\n * Whole-fleet binding dump. Iterates every device known to the\n * deviceRegistry and reuses the per-device `getBindings` resolver\n * for each — same routing rules, single round-trip. Used by\n * `SystemManager.init()` for warm-boot.\n *\n * Bindings change rarely (wrapper toggle, device add/remove) so\n * clients invalidate via the existing\n * `capability.binding-changed` event rather than re-fetching this\n * payload periodically.\n */\n async getAllBindings(): Promise<Array<{ deviceId: number; entries: DeviceBindingEntry[] }>> {\n const hubRegistry = this.ctx.kernel?.deviceRegistry\n if (!hubRegistry) return []\n const out: Array<{ deviceId: number; entries: DeviceBindingEntry[] }> = []\n for (const device of hubRegistry.getAll()) {\n out.push(await this.getBindings({ deviceId: device.id }))\n }\n return out\n }\n\n /**\n * Resolve a numeric deviceId to a stableId via persisted meta.\n * Used only by the device-identity section of the device-details\n * aggregator (see `buildBaseDeviceSection`) to surface the stableId as\n * a readonly display field. All runtime/registry lookups are keyed by\n * numeric deviceId; this helper is display-only.\n */\n private async lookupPersistedStableId(deviceId: number): Promise<string | undefined> {\n const store = (await this.ctx.settings!.readAddonStore()) as AddonStore\n const meta = store.deviceMeta ?? {}\n for (const [key, m] of Object.entries(meta)) {\n if (m.id === deviceId) {\n const sep = key.indexOf(':')\n if (sep < 0) continue\n return key.slice(sep + 1)\n }\n }\n return undefined\n }\n\n\n // ── Device-details aggregator ──────────────────────────────────────────────\n //\n // Walks the bindings for `deviceId` (native + wrapped), picks every\n // provider whose cap declares `exposesDeviceSettings: true`, and asks\n // each for its per-device contribution. Contributions are concatenated\n // into a single `ConfigUISchemaWithValues`; editable fields are tagged\n // with `writerCapName` + `writerAddonId` so `updateDeviceField` can\n // route the mutation back to the exact provider that produced them.\n //\n // `kind` selects settings vs live-info. The two aggregators share the\n // enumeration + tagging pipeline; only the method invoked on each\n // contributing provider differs.\n async getDeviceAggregate(\n deviceId: number,\n kind: 'settings' | 'live',\n ): Promise<ContributionShape | null> {\n const registry = this.capabilityRegistry\n if (!registry) {\n this.ctx.logger.debug('capability registry unavailable — aggregate empty', { meta: { kind } })\n return null\n }\n\n const method = kind === 'settings' ? 'getDeviceSettingsContribution' : 'getDeviceLiveContribution'\n\n // Enumerate EVERY capability with `exposesDeviceSettings: true`, not\n // just the caps for which this device has a per-device binding. The\n // system-wide singletons that hold per-device state (pipeline-\n // orchestrator assignments, motion-detection tuning, stream-broker\n // RTSP restream toggles, audio-analyzer VAD thresholds, …) are\n // registered once per system — they don't map to `(deviceId → cap)`\n // bindings. Walking bindings alone left their sections off the\n // aggregate, producing the \"5 empty fields\" device-details page.\n //\n // Each provider decides whether `deviceId` is relevant and returns\n // either a contribution or `null`. The aggregator flattens results\n // and tags each section with `(capName, addonId)` so the write path\n // can route a patch back to the provider that produced the field.\n const contributors: Array<{ capName: string; addonId: string }> = []\n for (const info of registry.listCapabilities()) {\n const def = registry.getDefinition(info.name)\n if (!def?.exposesDeviceSettings) continue\n // Deduplicate by base addon id — remote agents register with\n // \"addonId@nodeId\" keys but device settings are hub-centric:\n // only one contribution per logical addon, preferring the local\n // instance (shortest key = no \"@\" suffix).\n //\n // Skip synthetic addonIds (`<addonId>::native-<capName>`) created\n // for native-cap proxy services in `mountNativeCapService`. Those\n // services exist only to expose the cap's actual methods over\n // Moleculer; they don't implement contribution methods, so calling\n // `getDeviceSettingsContribution` on them throws and floods the\n // log. The owning addon (without the suffix) is the legitimate\n // contributor — it's listed separately and handled normally.\n const seen = new Set<string>()\n const sorted = [...info.providers].sort((a, b) => a.length - b.length)\n for (const addonId of sorted) {\n if (addonId.includes('::native-')) continue\n const baseId = addonId.includes('@') ? addonId.slice(0, addonId.indexOf('@')) : addonId\n if (seen.has(baseId)) continue\n seen.add(baseId)\n contributors.push({ capName: info.name, addonId })\n }\n }\n\n const results = await Promise.all(contributors.map(async ({ capName, addonId }) => {\n const provider = registry.getProviderByAddon<DeviceSettingsContribution>(capName, addonId)\n if (!provider) {\n // `listCapabilities()` returned this pair — it must resolve. A\n // null here means the registry's internal state diverged from\n // its public listing, which is always a bug (not a silent skip).\n throw new Error(\n `[device-manager] capability \"${capName}\" lists provider \"${addonId}\" but getProviderByAddon returned null — registry inconsistency`,\n )\n }\n // No typeof-function gate: `registerProvider` enforces that every\n // provider of an `exposesDeviceSettings` cap implements the three\n // contribution methods. Missing-method failures happen at register\n // time, not here.\n try {\n const contribution = await provider[method]({ deviceId })\n if (!contribution) return null\n return {\n capName,\n addonId,\n contribution: tagContribution(toWireShape(contribution), capName, addonId, kind),\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n this.ctx.logger.warn('contribution method failed', { tags: { deviceId, addonId }, meta: { capName, method, error: msg } })\n return null\n }\n }))\n\n // Prepend the device-manager's own base section (only for settings —\n // live contribution from device-manager is currently empty). The base\n // section holds the device's identity + driver-specific config, tagged\n // with writer = 'device-manager' so updateDeviceField can route its\n // mutations back to `device.config.setAll`.\n const base = kind === 'settings' ? await this.buildBaseDeviceSection(deviceId) : null\n const parts: ContributionShape[] = [\n ...(base ? [tagContribution(base, 'device-manager', 'device-manager', kind)] : []),\n ...results.filter((r): r is { capName: string; addonId: string; contribution: ContributionShape } => r !== null).map(r => r.contribution),\n ]\n if (parts.length === 0) return null\n return mergeAggregates(parts)\n }\n\n /**\n * Build the device-manager's own contribution to the aggregator — the\n * device identity (id, stableId, addonId, type, online) + the\n * driver-specific config exposed by the device class via\n * `zodEntriesToConfigUI`.\n *\n * Two paths, deliberately symmetric with `getSettingsSchema`:\n *\n * - Hub-local: device's IDevice instance lives in this process'\n * DeviceRegistry, we read config + schema directly by reference.\n * - Cross-process: device lives in a forked worker (RtspCamera on\n * provider-rtsp, ONVIF on provider-onvif, …). We ask the worker's\n * `device-ops.getSettingsSchema` native provider for a wire-\n * serializable ConfigUISchema and merge it in under the same\n * \"Driver Config\" section, so the UI sees the same shape regardless\n * of where the IDevice physically runs.\n *\n * Returns `null` only when the device genuinely doesn't exist anywhere\n * (no hub-local, no persisted ownership, no device-ops native). The\n * aggregator falls back to contributor sections only in that case.\n */\n private async buildBaseDeviceSection(deviceId: number): Promise<ContributionShape | null> {\n const hubRegistry = this.ctx.kernel?.deviceRegistry\n const hubLocal = hubRegistry ? resolveDeviceById(hubRegistry, deviceId) : null\n const stableId = hubLocal?.device.stableId ?? await this.lookupPersistedStableId(deviceId)\n\n // Resolve identity fields regardless of where the device lives. The\n // capability-registry's native fallback populates remoteNativeCaps\n // from DeviceBindingsChanged events (so every worker-owned device\n // that registered device-ops shows up here with its addonId/nodeId).\n const nativeOwner = this.resolveNativeDeviceOwner(deviceId)\n const addonId = hubLocal?.addonId ?? nativeOwner?.addonId ?? null\n if (!hubLocal && !nativeOwner) return null\n\n const identityFields: unknown[] = [\n {\n type: 'text',\n key: '_deviceId',\n label: 'Device ID',\n readonlyField: true,\n value: String(deviceId),\n },\n {\n type: 'text',\n key: '_stableId',\n label: 'Stable ID',\n readonlyField: true,\n value: stableId ?? '',\n },\n {\n type: 'text',\n key: '_addonId',\n label: 'Driver',\n readonlyField: true,\n value: addonId ?? 'unknown',\n },\n ...(hubLocal\n ? [\n {\n type: 'text',\n key: '_type',\n label: 'Type',\n readonlyField: true,\n value: hubLocal.device.type,\n },\n {\n type: 'text',\n key: '_online',\n label: 'Online',\n readonlyField: true,\n value: hubLocal.device.online ? 'yes' : 'no',\n },\n ]\n : []),\n ]\n\n const sections: ContributionShape['sections'] = [\n {\n id: 'device-identity',\n title: 'Identity',\n tab: 'general',\n order: 0,\n fields: identityFields,\n },\n ]\n // Hardware-info metadata is exposed read-only via `getDevice` and\n // rendered by a dedicated UI tab — it's facts, not preferences,\n // so it doesn't belong in the editable settings aggregate.\n\n // Driver config — either built in-process from the live IDevice's Zod\n // schema (hub-local path) or obtained as a pre-built ConfigUISchema\n // from the worker that owns the device (cross-process path).\n const driverSchema = await this.resolveDriverConfigSchema(deviceId, hubLocal)\n if (driverSchema) {\n for (const section of driverSchema.sections) {\n sections.push({\n id: section.id,\n title: section.title,\n tab: section.tab ?? 'general',\n order: section.order ?? 1,\n fields: [...section.fields],\n ...(section.description !== undefined ? { description: section.description } : {}),\n ...(section.columns !== undefined ? { columns: section.columns } : {}),\n })\n }\n }\n\n // Propagate driver-declared tab metadata (label + icon + order).\n // Without this the merge step in `mergeAggregates` falls back to\n // auto-derived tabs (raw id as label, generic icon) — UI then shows\n // lowercase `sessions` / `streams` instead of the driver's\n // `Sessions` / `Streams`. First-wins inside mergeAggregates so the\n // driver's declarations beat any later contributor.\n return {\n sections,\n ...(driverSchema?.tabs ? { tabs: [...driverSchema.tabs] } : {}),\n }\n }\n\n /**\n * Lookup the native owner for `device-ops` on `deviceId` — the native-cap\n * registry (hub-local and remote) is keyed by numeric id.\n */\n private resolveNativeDeviceOwner(deviceId: number): { addonId: string; nodeId: string } | null {\n const local = this.capabilityRegistry?.getNativeAddonId('device-ops', deviceId) ?? null\n if (local) return { addonId: local, nodeId: this.ctx.kernel.localNodeId ?? 'hub' }\n const remote = this.remoteNativeCaps.get(deviceId)?.get('device-ops') ?? null\n return remote ? { addonId: remote.addonId, nodeId: remote.nodeId } : null\n }\n\n /**\n * Aggregate `status` across every registered cap for a device.\n *\n * Walks the supplied cap list (or `CAP_NAMES_WITH_STATUS` when\n * omitted), looks up a native provider per cap via the capability\n * registry, calls `provider.getStatus({ deviceId })`, and validates\n * the return against the cap's own `status.schema`. Validation\n * failures log a warning and yield `null` for that cap so the\n * overall aggregate stays usable — a single misbehaving provider\n * must not blank out a device's entire status view.\n *\n * Returned shape is `Record<capName, unknown | null>`; the client-\n * side hook tightens this to `CapStatusTypeMap` via the generated\n * `cap-status-types.ts`.\n */\n private async getDeviceStatusAggregate(input: {\n readonly deviceId: number\n readonly caps?: readonly string[]\n }): Promise<Record<string, unknown | null>> {\n const capNames: readonly string[] = input.caps ?? CAP_NAMES_WITH_STATUS\n const registry = this.capabilityRegistry\n const out: Record<string, unknown | null> = {}\n if (!registry) {\n for (const name of capNames) out[name] = null\n return out\n }\n\n await Promise.all(capNames.map(async (capName) => {\n try {\n const def = registry.getDefinition(capName)\n if (!def?.status) { out[capName] = null; return }\n\n const provider = registry.getNativeProvider<{\n getStatus?: (input: { deviceId: number }) => Promise<unknown>\n }>(capName, input.deviceId)\n if (!provider || typeof provider.getStatus !== 'function') {\n out[capName] = null\n return\n }\n\n const raw = await provider.getStatus({ deviceId: input.deviceId })\n if (raw == null) { out[capName] = null; return }\n\n // Validate against the cap's own schema — single misbehaving\n // provider must not poison the rest of the aggregate.\n const parsed = def.status.schema.safeParse(raw)\n if (!parsed.success) {\n this.ctx.logger.warn('getDeviceStatusAggregate: provider returned invalid status, dropping', {\n tags: { deviceId: input.deviceId },\n meta: { capName, issues: parsed.error.issues.slice(0, 3) },\n })\n out[capName] = null\n return\n }\n out[capName] = parsed.data\n } catch (err) {\n this.ctx.logger.warn('getDeviceStatusAggregate: provider threw, dropping', {\n tags: { deviceId: input.deviceId },\n meta: { capName, error: errMsg(err) },\n })\n out[capName] = null\n }\n }))\n\n return out\n }\n\n /**\n * Return the driver-specific device-settings contribution. Hub-local\n * devices call `getSettingsUISchema()` directly; forked-worker devices\n * go through the `device-ops.getSettingsSchema` cap method on the\n * numeric-id-keyed native registry.\n */\n private async resolveDriverConfigSchema(\n deviceId: number,\n hubLocal: { addonId: string; device: IDevice } | null,\n ): Promise<ContributionShape | null> {\n if (hubLocal) {\n const schema = hubLocal.device.getSettingsUISchema()\n return schema.sections.length === 0 ? null : toWireShape(schema)\n }\n const ops = this.capabilityRegistry?.getNativeProvider<IDeviceOpsProvider>('device-ops', deviceId)\n if (!ops) return null\n try {\n const schema = await ops.getSettingsSchema({ deviceId })\n if (!schema) return null\n const wire = schema as ConfigUISchemaWithValues\n return wire.sections.length === 0 ? null : toWireShape(wire)\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n this.ctx.logger.warn('cross-process getSettingsSchema failed', { tags: { deviceId }, meta: { error: msg } })\n return null\n }\n }\n\n async updateDeviceField(input: {\n deviceId: number\n writerCapName: string\n writerAddonId: string\n key: string\n value: unknown\n }): Promise<{ success: true }> {\n // Special-case: the device-manager's own base section tags fields with\n // writer='device-manager'. Route those to `device.config.setAll` via the\n // live registry — the device-manager cap itself does not participate in\n // the binding-enumerated aggregator (it's a hub singleton), so it would\n // otherwise fail the capability lookup below.\n if (input.writerCapName === 'device-manager') {\n // Hub-local fast path — the device instance is in this process.\n const hubRegistry = this.ctx.kernel?.deviceRegistry\n const found = hubRegistry ? resolveDeviceById(hubRegistry, input.deviceId) : null\n if (found) {\n // Always go through `applySettingsPatch` — drivers whose UI shape\n // differs from storage (RtspCamera's flat mainStreamUrl projecting\n // onto a `streams: [{label,url}]` array) reshape here. `BaseDevice`\n // defaults the method to `config.setAll` so drivers that don't\n // reshape keep the old behaviour for free.\n await found.device.applySettingsPatch({ [input.key]: input.value })\n return { success: true as const }\n }\n // Cross-process fallback — device lives in a forked worker. Native\n // registry is numeric-keyed; dispatch through device-ops directly.\n const ops = this.capabilityRegistry?.getNativeProvider<IDeviceOpsProvider>('device-ops', input.deviceId)\n if (!ops) {\n throw new Error(`[device-manager] device \"${input.deviceId}\" not found (no hub-local entry, no device-ops native provider)`)\n }\n await ops.setConfig({ deviceId: input.deviceId, values: { [input.key]: input.value } })\n return { success: true as const }\n }\n\n const registry = this.capabilityRegistry\n if (!registry) {\n throw new Error('[device-manager] updateDeviceField requires capability registry — unavailable on this node')\n }\n\n const def = registry.getDefinition(input.writerCapName)\n if (!def?.exposesDeviceSettings) {\n throw new Error(`[device-manager] cap \"${input.writerCapName}\" does not expose device settings`)\n }\n\n const provider = registry.getProviderByAddon<DeviceSettingsContribution>(input.writerCapName, input.writerAddonId)\n if (!provider) {\n throw new Error(`[device-manager] provider \"${input.writerAddonId}\" not registered for cap \"${input.writerCapName}\"`)\n }\n\n await provider.applyDeviceSettingsPatch({\n deviceId: input.deviceId,\n patch: { [input.key]: input.value },\n })\n return { success: true as const }\n }\n\n /**\n * Batched counterpart of `updateDeviceField`. Groups changes by\n * `(writerCapName, writerAddonId)` so each contributor receives a\n * single `applyDeviceSettingsPatch` with all of its updates merged —\n * avoids N round-trips for simultaneous edits in the same save.\n *\n * Per-provider failures are captured in the `failures[]` output so the\n * admin UI can highlight which sections didn't persist; a failure on\n * one provider does NOT abort the others.\n */\n async updateDeviceFieldsBatch(input: {\n deviceId: number\n changes: ReadonlyArray<{\n writerCapName: string\n writerAddonId: string\n key: string\n value: unknown\n }>\n }): Promise<{ success: true; failures: { writerCapName: string; writerAddonId: string; error: string }[] }> {\n // Group by `(writerCapName, writerAddonId)` so we fire at most one\n // applyDeviceSettingsPatch per contributing provider.\n const groups = new Map<string, { writerCapName: string; writerAddonId: string; patch: Record<string, unknown> }>()\n for (const change of input.changes) {\n const key = `${change.writerCapName}::${change.writerAddonId}`\n const existing = groups.get(key)\n if (existing) {\n existing.patch[change.key] = change.value\n } else {\n groups.set(key, {\n writerCapName: change.writerCapName,\n writerAddonId: change.writerAddonId,\n patch: { [change.key]: change.value },\n })\n }\n }\n\n const failures: { writerCapName: string; writerAddonId: string; error: string }[] = []\n for (const group of groups.values()) {\n try {\n await this.applyGroupPatch(input.deviceId, group)\n } catch (err: unknown) {\n failures.push({\n writerCapName: group.writerCapName,\n writerAddonId: group.writerAddonId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n return { success: true as const, failures }\n }\n\n /** Apply a single grouped patch to the appropriate provider. Mirrors\n * `updateDeviceField` routing (special-case device-manager, else\n * registry lookup). Used by `updateDeviceFieldsBatch`. */\n private async applyGroupPatch(\n deviceId: number,\n group: { writerCapName: string; writerAddonId: string; patch: Record<string, unknown> },\n ): Promise<void> {\n if (group.writerCapName === 'device-manager') {\n // Hub-local fast path (same reshape-aware path used by `updateDeviceField`).\n const hubRegistry = this.ctx.kernel?.deviceRegistry\n const found = hubRegistry ? resolveDeviceById(hubRegistry, deviceId) : null\n if (found) {\n await found.device.applySettingsPatch(group.patch)\n return\n }\n // Cross-process fallback via `device-ops.setConfig` — native registry\n // is numeric-keyed.\n const ops = this.capabilityRegistry?.getNativeProvider<IDeviceOpsProvider>('device-ops', deviceId)\n if (!ops) {\n throw new Error(`[device-manager] device \"${deviceId}\" not found (no hub-local entry, no device-ops native provider)`)\n }\n await ops.setConfig({ deviceId, values: group.patch })\n return\n }\n\n const registry = this.capabilityRegistry\n if (!registry) throw new Error('[device-manager] capability registry unavailable')\n const def = registry.getDefinition(group.writerCapName)\n if (!def?.exposesDeviceSettings) {\n throw new Error(`[device-manager] cap \"${group.writerCapName}\" does not expose device settings`)\n }\n const provider = registry.getProviderByAddon<DeviceSettingsContribution>(group.writerCapName, group.writerAddonId)\n if (!provider) {\n throw new Error(`[device-manager] provider \"${group.writerAddonId}\" not registered for cap \"${group.writerCapName}\"`)\n }\n await provider.applyDeviceSettingsPatch({ deviceId, patch: group.patch })\n }\n\n async listWrappersForCap(input: { capName: string }): Promise<string[]> {\n return [...(this.capabilityRegistry?.getWrappersForCap(input.capName) ?? [])]\n }\n\n async listBindableCapsForDeviceType(input: { deviceType: string }): Promise<Array<{ capName: string; wrappers: string[] }>> {\n const registry = this.capabilityRegistry\n if (!registry) return []\n const capNames = registry.listDeviceScopedCapsForType(input.deviceType)\n return capNames.map(capName => ({\n capName,\n wrappers: [...registry.getWrappersForCap(capName)],\n }))\n }\n\n async setWrapperActive(input: {\n deviceId: number\n capName: string\n wrapperAddonId: string\n active: boolean\n }): Promise<void> {\n const storeKey = String(input.deviceId)\n const store = await this.readBindingsStore()\n const perDevice = { ...(store.deviceBindings[storeKey] ?? {}) }\n if (input.active) {\n perDevice[input.capName] = { wrapperAddonId: input.wrapperAddonId }\n } else {\n // Explicit \"operator disabled\" marker. Persisting {wrapperAddonId:null}\n // (instead of deleting) prevents getBindings step 4 from re-binding\n // the defaultActive wrapper after a toggle-off. When a native provider\n // exists, the null marker still surfaces as `kind:'native'` (the revert\n // path the old delete branch was optimising for); when no native is\n // registered, the cap stays truly unbound for this device.\n perDevice[input.capName] = { wrapperAddonId: null }\n }\n const nextDeviceBindings = Object.keys(perDevice).length > 0\n ? { ...store.deviceBindings, [storeKey]: perDevice }\n : (() => { const { [storeKey]: _drop, ...rest } = store.deviceBindings; return rest })()\n await this.writeBindingsStore({ deviceBindings: nextDeviceBindings })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: this.ctx.id },\n category: EventCategory.DeviceBindingsChanged,\n data: {\n deviceId: input.deviceId,\n capName: input.capName,\n reason: input.active ? 'wrapper-activated' : 'wrapper-deactivated',\n addonId: input.wrapperAddonId,\n nodeId: this.resolveWrapperNodeId(input.wrapperAddonId),\n },\n })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[] | void> {\n const settings = this.ctx.settings\n if (!settings) {\n this.ctx.logger.warn('ctx.settings not available — device persistence unavailable')\n return\n }\n\n const registry = this.ctx.kernel.deviceRegistry ?? null\n\n if (!registry) {\n this.ctx.logger.warn('device-registry not available — live operations will use persisted data only')\n }\n\n // Cross-process device-ops dispatch. Forked-worker addons (e.g.\n // provider-rtsp) register devices whose live IDevice instance lives in\n // the worker's own DeviceRegistry — invisible to this hub-side\n // `registry`. Instead of a bespoke Moleculer bridge, every worker\n // auto-registers a `device-ops` NATIVE capability provider per device\n // (see createBrokerDeviceManagerApi.register). Hub consumers reach it\n // via `capabilityRegistry.getNativeProvider('device-ops', deviceId)`:\n // the CapabilityRegistry's native-fallback builds a typed proxy that\n // routes the call to the owning worker through the standard native-cap\n // bridge — same plumbing every other per-device cap (snapshot, events,\n // ptz, …) already uses.\n\n // Cross-process native-cap discovery. Workers emit `device.bindings-changed`\n // on `registerNativeCap` / device removal; we mirror those into\n // `remoteNativeCaps` so `getBindings` returns the full cluster view.\n // Events from the local node are ignored: hub-local natives live in\n // `capabilityRegistry` and are folded in directly by getBindings.\n const localNodeId = this.ctx.kernel.localNodeId ?? 'hub'\n this.ctx.eventBus.subscribe(\n { category: EventCategory.DeviceBindingsChanged },\n (event) => {\n const { deviceId, capName, reason, addonId, nodeId } = event.data\n if (nodeId === localNodeId) return\n if (reason === 'native-registered') {\n let perDevice = this.remoteNativeCaps.get(deviceId)\n if (!perDevice) {\n perDevice = new Map()\n this.remoteNativeCaps.set(deviceId, perDevice)\n }\n perDevice.set(capName, { addonId, nodeId })\n } else if (reason === 'native-unregistered') {\n const perDevice = this.remoteNativeCaps.get(deviceId)\n if (!perDevice) return\n perDevice.delete(capName)\n if (perDevice.size === 0) this.remoteNativeCaps.delete(deviceId)\n }\n },\n )\n\n // Purge every remote native-cap entry owned by a node that just dropped.\n // The worker will re-emit `native-registered` on its next boot — no\n // persistence needed here.\n const cluster = this.ctx.kernel.cluster\n if (cluster) {\n cluster.broker.localBus.on('$node.disconnected', (payload) => {\n const gone = payload.node.id\n const emptyDevices: number[] = []\n for (const [deviceId, perDevice] of this.remoteNativeCaps) {\n const toDelete: string[] = []\n for (const [capName, entry] of perDevice) {\n if (entry.nodeId === gone) toDelete.push(capName)\n }\n for (const capName of toDelete) perDevice.delete(capName)\n if (perDevice.size === 0) emptyDevices.push(deviceId)\n }\n for (const deviceId of emptyDevices) this.remoteNativeCaps.delete(deviceId)\n })\n }\n // Resolve the per-device `device-ops` native provider. Works hub-local\n // (direct CapabilityRegistry lookup) and cross-process (the native-cap\n // fallback builds a proxy that routes to the owning worker). Consumers\n // that previously spoke the `<addonId>.device-ops.device.<method>`\n // Moleculer action go through this path instead — no bridge, no\n // parallel transport, just the standard native-cap dispatch.\n const requireDeviceOps = (deviceId: number): IDeviceOpsProvider => {\n const ops = this.capabilityRegistry?.getNativeProvider<IDeviceOpsProvider>('device-ops', deviceId)\n if (!ops) {\n throw new Error(`[device-manager] device-ops native provider not found for '${deviceId}'`)\n }\n return ops\n }\n\n // ── Helpers ──────────────────────────────────────────────────────────────\n\n const readStore = async (): Promise<AddonStore> => {\n return (await settings.readAddonStore()) as AddonStore\n }\n\n const readIndex = async (): Promise<Record<string, string[]>> => {\n const store = await readStore()\n return store.deviceIndex ?? {}\n }\n\n const readMeta = async (): Promise<Record<string, PersistedDeviceMeta>> => {\n const store = await readStore()\n return store.deviceMeta ?? {}\n }\n\n /** Hardware-identity metadata map. Lives in a sibling key on the\n * device-manager addon store so its writers (`setMetadata`) never\n * collide with the lifecycle writers on `deviceMeta`\n * (`registerDevice` / `setName` / `setLocation` / `setDisabled`).\n * Single-writer per row eliminates the \"writer X clobbers writer\n * Y's field\" bug class — `setMetadata` is the only producer. */\n const readMetadataMap = async (): Promise<Record<string, Record<string, unknown>>> => {\n const store = await readStore()\n return store.deviceMetadata ?? {}\n }\n\n // Serialize every read-modify-write of the deviceMeta / deviceIndex\n // blob through a single promise chain. Without this, concurrent\n // setMetadata / setName / setLocation / setDisabled callers race:\n // each reads the same `allMeta` snapshot, patches its own row,\n // then writes back the FULL blob — last writer wins, prior patches\n // are silently lost. The race fired on every addon restart when 5\n // Reolink cameras populate metadata in parallel and only the last\n // write survives. The mutex queues writes; reads in between still\n // hit `readStore()` directly (no penalty for hot read paths).\n let metaWriteChain: Promise<unknown> = Promise.resolve()\n const withMetaWriteLock = async <T>(fn: () => Promise<T>): Promise<T> => {\n const previous = metaWriteChain\n let release: () => void = () => {}\n const next = new Promise<void>((resolve) => { release = resolve })\n metaWriteChain = next\n try {\n await previous.catch(() => { /* swallow — keep chain alive */ })\n return await fn()\n } finally {\n release()\n }\n }\n\n /**\n * Resolve a numeric deviceId to the owning `(addonId, stableId)` pair.\n * Scans persisted meta — live IDevice lookup (hub registry) is handled\n * separately per call site so callers can decide whether to route to\n * an in-process driver or to the cross-process `device-ops` bridge.\n * Returns null when no device with that id is known to the hub.\n */\n const resolvePersistedById = async (\n deviceId: number,\n ): Promise<{ addonId: string; stableId: string; meta: PersistedDeviceMeta } | null> => {\n const meta = await readMeta()\n for (const [key, m] of Object.entries(meta)) {\n if (m.id === deviceId) {\n const sep = key.indexOf(':')\n if (sep < 0) continue\n return { addonId: key.slice(0, sep), stableId: key.slice(sep + 1), meta: m }\n }\n }\n return null\n }\n\n // ── Provider implementation ───────────────────────────────────────────────\n\n // Synchronous ownership cache, keyed by NUMERIC deviceId now that the\n // hub allocates ids before construction. The persisted meta store is\n // authoritative but reads are async; hub-side callers (e.g.\n // `CapabilityRegistry.getNativeProvider` fallback) need ownership\n // without awaiting. We keep this map in sync with every\n // registerDevice/removeDevice the provider serves, and warm it from\n // persistence on boot.\n const idToAddonId = new Map<number, string>()\n {\n const meta = await readMeta()\n for (const [key, m] of Object.entries(meta)) {\n const sep = key.indexOf(':')\n if (sep < 0) continue\n idToAddonId.set(m.id, key.slice(0, sep))\n }\n }\n\n // `$node.connected` bootstrap seed for cross-process `device-ops`. When a\n // forked worker reconnects after a hub restart, its `registerNativeCap`\n // broadcast for the auto-registered `device-ops` cap can be lost if the\n // Moleculer transport is still mid-handshake at the moment of emit — the\n // `broker.broadcast($cluster-events, …)` reaches no peer. `remoteNativeCaps`\n // then stays empty for that device forever, and every `getBindings` /\n // `requireDeviceOps` call on the hub throws \"device-ops native provider\n // not found\", which in turn blocks `stream-broker` from wiring the\n // camera and every tRPC `getStreamSources` from succeeding.\n //\n // Since `device-ops` is auto-registered for every device by contract\n // (see `createBrokerDeviceManagerApi.register`), ownership alone is\n // enough to synthesize a valid entry: if addon X owns deviceId N (per\n // persisted meta), then X's worker MUST host `device-ops` for N.\n // Routing through `buildNativeCapProxy` keys on `addonId` only —\n // Moleculer resolves the node — so the synthetic entry is always\n // correct once the worker's service is mounted.\n //\n // Idempotent: a late-arriving real `DeviceBindingsChanged` event\n // overwrites the entry with the authoritative payload. Hub-local\n // natives are skipped: those sit in `capabilityRegistry` and are\n // folded in directly by `getBindings` / `getNativeProvider`.\n if (cluster) {\n cluster.broker.localBus.on('$node.connected', (payload: { node: { id: string } }) => {\n const connectedNodeId = payload.node.id\n const lastSlash = connectedNodeId.lastIndexOf('/')\n if (lastSlash < 0) return\n const connectedAddonId = connectedNodeId.slice(lastSlash + 1)\n if (connectedAddonId.length === 0) return\n for (const [deviceId, ownerAddonId] of idToAddonId) {\n if (ownerAddonId !== connectedAddonId) continue\n if (this.capabilityRegistry?.getNativeAddonId('device-ops', deviceId)) continue\n let perDevice = this.remoteNativeCaps.get(deviceId)\n if (!perDevice) {\n perDevice = new Map()\n this.remoteNativeCaps.set(deviceId, perDevice)\n }\n if (!perDevice.has('device-ops')) {\n perDevice.set('device-ops', { addonId: connectedAddonId, nodeId: connectedNodeId })\n }\n }\n // Active discovery for ALL native caps the worker hosts. The\n // `device-ops` seed above only covers one cap because device\n // ownership trivially implies it; for other caps (intercom,\n // ptz, motion, osd, …) we need to ASK the worker which devices\n // it has registered each cap on. Without this poll, an\n // `addon-restart` of a forked worker leaves `remoteNativeCaps`\n // empty for every cap whose `DeviceBindingsChanged` broadcast\n // got lost in the transport handshake window — the symptom is\n // bindings UI dropping from 13→7 entries until full server\n // restart.\n //\n // The poll is deferred to the next tick so service discovery\n // has time to register the worker's `*.native-provider.*`\n // services. We then iterate the broker's service registry,\n // pick services that match this connected node, and call\n // `$listDeviceIds` on each (ports the worker-side\n // `workerNativeCaps` map). Per-device entries are written\n // straight into `remoteNativeCaps`; a late real\n // `DeviceBindingsChanged` event idempotently overwrites them.\n setTimeout(() => {\n this.discoverWorkerNativeCaps(connectedNodeId, connectedAddonId).catch((err) => {\n this.ctx.logger.warn('worker native-cap discovery failed', {\n meta: { nodeId: connectedNodeId, addonId: connectedAddonId, error: errMsg(err) },\n })\n })\n }, 500)\n })\n }\n\n const allocateNextDeviceId = async (): Promise<number> => {\n // Read-modify-write under the same settings writer the rest of the\n // addon uses — sqlite-settings serializes writes per addon, so two\n // concurrent registerDevice calls are ordered and each gets a unique\n // id. No in-memory counter: the store is the single source of truth.\n const store = await readStore()\n const current = store.nextDeviceId ?? 1\n await settings.writeAddonStore({ nextDeviceId: current + 1 })\n return current\n }\n\n const provider: IDeviceManagerProvider & {\n resolveDeviceOwnerSync: (deviceId: number) => string | null\n resolveNativeCapOwnerSync: (capName: string, deviceId: number) => { addonId: string; nodeId: string } | null\n } = {\n\n /** Sync ownership lookup backing persistence fallbacks (e.g. remove()\n * when the owning worker is offline). Ownership is keyed by numeric\n * deviceId → owning addonId as recorded in the persisted meta. NOT\n * a native-cap lookup: an addon can own a device without registering\n * every possible cap natively (e.g. RtspCamera without snapshotUrl\n * doesn't register the snapshot cap). Use\n * `resolveNativeCapOwnerSync` for cap-resolution paths.\n */\n resolveDeviceOwnerSync: (deviceId: number): string | null => {\n return idToAddonId.get(deviceId) ?? null\n },\n\n /** Sync lookup for the addon that registered a native provider for\n * `(capName, deviceId)`. Backs `CapabilityRegistry`'s native fallback\n * so the hub only synthesizes a cross-process proxy when the cap is\n * actually published — never on speculative device ownership.\n *\n * Consults hub-local registrations first (in-process natives),\n * then the `remoteNativeCaps` map populated from\n * `DeviceBindingsChanged` events emitted by forked-worker\n * `registerNativeCap` calls. Both are generic: any addon that hosts\n * devices and registers caps via the standard context API shows up\n * here without per-addon branching.\n */\n resolveNativeCapOwnerSync: (capName: string, deviceId: number): { addonId: string; nodeId: string } | null => {\n const localAddonId = this.capabilityRegistry?.getNativeAddonId(capName, deviceId) ?? null\n if (localAddonId) {\n return { addonId: localAddonId, nodeId: this.ctx.kernel.localNodeId ?? 'hub' }\n }\n const remote = this.remoteNativeCaps.get(deviceId)?.get(capName) ?? null\n if (remote) return { addonId: remote.addonId, nodeId: remote.nodeId }\n return null\n },\n\n // ── Persistence ────────────────────────────────────────────────────────\n\n /** Idempotent numeric-id reservation. Callers invoke this before\n * constructing the owning `IDevice` so `DeviceContext.id` is bound\n * at construction time. A repeat call for the same `(addonId,\n * stableId)` returns the already-persisted id — same physical\n * device reconnecting after a driver restart keeps its original\n * number. Fresh pairs burn one slot from the monotonic\n * `nextDeviceId` counter and seed a meta placeholder so the\n * `deviceMeta` → `id` invariant holds even before\n * `registerDevice` completes. */\n allocateDeviceId: async (input) => {\n const { addonId, stableId } = input\n const key = deviceKey(addonId, stableId)\n return await withMetaWriteLock(async () => {\n const meta = await readMeta()\n const existing = meta[key]\n if (existing) return { id: existing.id }\n const id = await allocateNextDeviceId()\n await settings.writeAddonStore({\n deviceMeta: {\n ...meta,\n [key]: {\n type: 'generic',\n name: stableId,\n location: null,\n disabled: false,\n parentDeviceId: null,\n id,\n },\n },\n })\n return { id }\n })\n },\n\n registerDevice: async (input) => {\n const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input\n const key = deviceKey(addonId, stableId)\n\n const featuresArr = Array.isArray(features) ? [...features] : []\n // Index update + meta write together under the lock so a\n // concurrent registerDevice for a different (addonId, stableId)\n // can't read/clobber our deviceMeta snapshot.\n const { isFirstRegistration } = await withMetaWriteLock(async () => {\n const index = await readIndex()\n const existing = index[addonId] ?? []\n const wasInIndex = existing.includes(stableId)\n if (!wasInIndex) {\n await settings.writeAddonStore({\n deviceIndex: { ...index, [addonId]: [...existing, stableId] },\n })\n }\n\n // Persist the full meta record. The kernel's `create()`\n // shortcut writes operator-supplied `name` + `location`\n // BEFORE construction via `setName` / `setLocation` actions,\n // so by the time we land here the placeholder name is already\n // overwritten. Preserve every operator-edited field; the\n // `name` arg coming through this action is the device's\n // construction-time `device.name` which already reflects\n // whatever the meta row carries (`BaseDevice` reads from\n // `ctx.deviceMeta.name`).\n const meta = await readMeta()\n const existingMeta = meta[key]\n // First registration vs subsequent meta refresh (e.g. post-probe\n // features update). Drives whether we emit a `DeviceRegistered`\n // event below — listeners (ReolinkHub discovery refresh, etc)\n // must not re-fire on every meta update or downstream work\n // multiplies.\n const isFirst = !existingMeta || !wasInIndex\n await settings.writeAddonStore({\n deviceMeta: {\n ...meta,\n [key]: {\n type,\n // Prefer the persisted name (set by `setName` pre-construct)\n // over the construction-time fallback. Only when the meta\n // row literally still carries `name === stableId` (the\n // placeholder seeded by `allocateDeviceId`) do we fall\n // back to the action arg.\n name: existingMeta && existingMeta.name !== stableId ? existingMeta.name : name,\n location: existingMeta?.location ?? null,\n disabled: existingMeta?.disabled ?? false,\n parentDeviceId,\n id,\n features: featuresArr,\n },\n },\n })\n return { isFirstRegistration: isFirst }\n })\n\n // Config is pre-persisted by the kernel's `create()` shortcut\n // via `persistConfig` BEFORE construction. We still accept a\n // non-empty `config` payload here for back-compat with paths\n // that haven't migrated, and write it through — but the\n // canonical CREATE path passes `config: {}` and the persisted\n // row stays untouched.\n if (Object.keys(config).length > 0) {\n await settings.writeDeviceStore(id, config)\n }\n\n idToAddonId.set(id, addonId)\n\n // Emit `DeviceRegistered` ONLY on first registration. Subsequent\n // calls (driver re-publishing post-probe features, accessory\n // count update, …) are silent meta refreshes — listeners that\n // care about the device existing already saw the first event.\n // Skipping the duplicate prevents listeners like ReolinkHub from\n // burning a discovery-refresh roundtrip per re-register.\n if (isFirstRegistration) {\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id },\n category: EventCategory.DeviceRegistered,\n data: {\n deviceId: id,\n name: (name.length > 0 ? name : stableId),\n providerId: addonId,\n parentDeviceId: parentDeviceId ?? null,\n },\n })\n } else {\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id },\n category: EventCategory.DeviceMetaChanged,\n data: {\n deviceId: id,\n name: (name.length > 0 ? name : stableId),\n providerId: addonId,\n parentDeviceId: parentDeviceId ?? null,\n features: featuresArr,\n },\n })\n }\n },\n\n removeDevice: async (input) => {\n const { deviceId } = input\n // Resolve `(addonId, stableId)` from the meta store. The cap input\n // only carries the numeric id now — internal cleanup still needs\n // the composite key for index/meta storage rows (those are the\n // external-linkage rows we deliberately keep stableId in).\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) {\n // Removal of an unknown device is a no-op (idempotent) — the\n // caller may be racing teardown after a crash. No throw: the\n // hub-level `remove()` already error-checks first.\n return\n }\n const { addonId, stableId, meta: persistedMeta } = persisted\n const key = deviceKey(addonId, stableId)\n const deviceName = persistedMeta.name\n\n await withMetaWriteLock(async () => {\n // Update index + meta + hardware-metadata together under the\n // lock so we can't lose a concurrent register's freshly-persisted\n // row by reading a pre-write snapshot of `meta`.\n const index = await readIndex()\n const remaining = (index[addonId] ?? []).filter(sid => sid !== stableId)\n const updatedIndex = remaining.length > 0\n ? { ...index, [addonId]: remaining }\n : (() => { const { [addonId]: _removed, ...rest } = index; return rest })()\n await settings.writeAddonStore({ deviceIndex: updatedIndex })\n\n const meta = await readMeta()\n const { [key]: _removedMeta, ...restMeta } = meta\n await settings.writeAddonStore({ deviceMeta: restMeta })\n\n const map = await readMetadataMap()\n if (key in map) {\n const { [key]: _removedMetadata, ...restMap } = map\n await settings.writeAddonStore({ deviceMetadata: restMap })\n }\n })\n\n // Delete all per-device config rows so no orphaned settings remain after removal.\n await settings.clearDeviceStore(deviceId)\n\n // Drop any persisted wrapper activations tied to this device so\n // `getBindings({deviceId})` stops returning stale entries after remove.\n const bindingsStore = await this.readBindingsStore()\n const bindingKey = String(deviceId)\n if (bindingsStore.deviceBindings[bindingKey]) {\n const { [bindingKey]: _removedBindings, ...restBindings } = bindingsStore.deviceBindings\n await this.writeBindingsStore({ deviceBindings: restBindings })\n }\n\n // Drop every cross-process native-cap mirror entry for this device.\n // The worker's device-ops bridge removeDevice handler doesn't emit\n // `native-unregistered` events today; doing the purge here keeps the\n // hub view consistent regardless of which teardown path ran.\n this.remoteNativeCaps.delete(deviceId)\n // Hub-local natives live in the CapabilityRegistry — clear them too\n // so in-process devices don't linger in `getNativeCapsForDevice`.\n this.capabilityRegistry?.unregisterAllNativeForDevice(deviceId)\n\n idToAddonId.delete(deviceId)\n\n this.ctx.logger.info('removed device', { tags: { deviceId, deviceName: deviceName.length > 0 ? deviceName : stableId } })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceUnregistered,\n data: {\n deviceId,\n providerId: addonId,\n // Carry parentDeviceId so listeners can filter on \"is\n // this MY child?\" without consulting an in-memory map\n // they had to keep in sync. By the time this event\n // fires the meta row is already gone — we read it from\n // the resolved snapshot above.\n parentDeviceId: persistedMeta.parentDeviceId ?? null,\n },\n })\n },\n\n persistConfig: async (input) => {\n const { deviceId, data } = input\n // Per-device config is keyed by numeric id end-to-end. We still\n // verify the device is known (resolves through meta) so a write\n // for a non-existent id throws instead of silently allocating a\n // stray store entry.\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] persistConfig: unknown device id=${deviceId}`)\n await settings.writeDeviceStore(deviceId, data)\n },\n\n loadConfig: async (input) => {\n const { deviceId } = input\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return {}\n return settings.readDeviceStore(deviceId)\n },\n\n /**\n * Load the operator-organisational meta surface for one device\n * (`name` / `location` / `disabled` / `type` / `parentDeviceId`\n * / `addonId` + `id` / `stableId`). Used by the kernel proxy's\n * device-context factory to populate `ctx.deviceMeta` before\n * the device class constructor runs. Returns `null` when no\n * persisted row exists for the id.\n *\n * Reads default `location` to `null` and `disabled` to `false`\n * for legacy rows that predate the field — production code\n * relies on the IDevice type contract that both are present.\n */\n loadMeta: async (input) => {\n const { deviceId } = input\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return null\n const { addonId, stableId, meta: m } = persisted\n const key = deviceKey(addonId, stableId)\n const map = await readMetadataMap()\n const metadata = map[key] ?? null\n return {\n id: m.id,\n stableId,\n addonId,\n type: m.type as DeviceType,\n name: m.name,\n location: m.location ?? null,\n disabled: m.disabled ?? false,\n parentDeviceId: m.parentDeviceId,\n metadata,\n }\n },\n\n /**\n * Update the operator-edited display name. Writes the meta\n * row, emits a `DeviceMetaChanged` event so live consumers\n * (UI device list, alert center) see the rename without\n * polling. The live `IDevice.name` mirror is updated by the\n * kernel proxy on its side (`device-cap-proxy.ts`).\n */\n setName: async (input) => {\n const { deviceId, name } = input\n await withMetaWriteLock(async () => {\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setName: unknown device id=${deviceId}`)\n const { addonId, stableId, meta: m } = persisted\n const key = deviceKey(addonId, stableId)\n const allMeta = await readMeta()\n await settings.writeAddonStore({\n deviceMeta: { ...allMeta, [key]: { ...m, name } },\n })\n })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'name', value: name },\n })\n },\n\n /**\n * Update the operator-organisational location label. `null`\n * clears it. Mirrors the same persist-then-emit shape as\n * `setName`; consumers subscribe to `DeviceMetaChanged` and\n * filter on `field: 'location'`.\n */\n setLocation: async (input) => {\n const { deviceId, location } = input\n await withMetaWriteLock(async () => {\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setLocation: unknown device id=${deviceId}`)\n const { addonId, stableId, meta: m } = persisted\n const key = deviceKey(addonId, stableId)\n const allMeta = await readMeta()\n await settings.writeAddonStore({\n deviceMeta: { ...allMeta, [key]: { ...m, location } },\n })\n })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'location', value: location },\n })\n },\n\n /**\n * Patch the device's hardware-identity metadata blob. Shallow\n * merge — `null` removes a key, anything else overwrites.\n * Drivers populate factual fields on first probe; operators\n * augment via the Device Info tab. Idempotent: a no-op patch\n * (every key already present with the same value) doesn't emit\n * the meta-changed event.\n */\n setMetadata: async (input) => {\n const { deviceId, patch } = input\n const result = await withMetaWriteLock(async () => {\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setMetadata: unknown device id=${deviceId}`)\n const { addonId, stableId } = persisted\n const key = deviceKey(addonId, stableId)\n const map = await readMetadataMap()\n const current = (map[key] ?? {}) as Record<string, unknown>\n const next: Record<string, unknown> = { ...current }\n let changed = false\n for (const [k, v] of Object.entries(patch)) {\n if (v === null) {\n if (k in next) {\n delete next[k]\n changed = true\n }\n } else if (next[k] !== v) {\n next[k] = v\n changed = true\n }\n }\n if (!changed) return { changed: false as const }\n const hasFields = Object.keys(next).length > 0\n const updatedMap = { ...map }\n if (hasFields) {\n updatedMap[key] = next\n } else {\n delete updatedMap[key]\n }\n await settings.writeAddonStore({ deviceMetadata: updatedMap })\n return { changed: true as const, finalMeta: hasFields ? next : null }\n })\n if (!result.changed) return\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'metadata', value: result.finalMeta },\n })\n },\n\n /**\n * Soft-disable the device. Persisted on the meta row;\n * lifecycle gating is the driver's responsibility (BaseDevice\n * exposes `this.disabled` for the driver to consult at the top\n * of its lifecycle methods).\n */\n setDisabled: async (input) => {\n const { deviceId, disabled } = input\n await withMetaWriteLock(async () => {\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setDisabled: unknown device id=${deviceId}`)\n const { addonId, stableId, meta: m } = persisted\n const key = deviceKey(addonId, stableId)\n const allMeta = await readMeta()\n await settings.writeAddonStore({\n deviceMeta: { ...allMeta, [key]: { ...m, disabled } },\n })\n })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'disabled', value: disabled },\n })\n },\n\n loadRuntimeState: async (input) => {\n const { deviceId } = input\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return {}\n const data = await settings.readDeviceRuntimeState(deviceId)\n // Seed the mirror at first load so the hub knows about every\n // existing slice immediately — providers might never call\n // `setCapSlice` if their runtime state doesn't change, and\n // the mirror would otherwise stay empty until the first\n // change.\n this.seedMirror(deviceId, data)\n return data\n },\n\n /**\n * Union of (1) operator-curated location registry and (2) labels\n * currently in use on persisted devices. Case-insensitive\n * dedupe (preserves the first-seen casing). Sorted\n * case-insensitively for stable UI. Drives the Device Info\n * location autocomplete.\n */\n listLocations: async () => {\n const store = (await settings.readAddonStore()) as AddonStore\n const meta = store.deviceMeta ?? {}\n const registry = store.locations ?? []\n // Map from lower-cased key to the first-seen casing so duplicate\n // entries with different cases collapse to one.\n const seen = new Map<string, string>()\n const consider = (raw: unknown): void => {\n if (typeof raw !== 'string') return\n const trimmed = raw.trim()\n if (trimmed.length === 0) return\n const key = trimmed.toLowerCase()\n if (!seen.has(key)) seen.set(key, trimmed)\n }\n for (const label of registry) consider(label)\n for (const m of Object.values(meta)) consider(m.location)\n return [...seen.values()].sort((a, b) =>\n a.localeCompare(b, undefined, { sensitivity: 'base' }),\n )\n },\n\n /**\n * Add a label to the curated location registry. Idempotent:\n * existing entries (case-insensitive match) are silently kept.\n * Empty / whitespace-only inputs throw — operators must supply a\n * meaningful label.\n */\n addLocation: async (input) => {\n const trimmed = input.name.trim()\n if (trimmed.length === 0) {\n throw new Error('[device-manager] addLocation: name must be non-empty')\n }\n const store = (await settings.readAddonStore()) as AddonStore\n const current = store.locations ?? []\n if (current.some((l) => l.toLowerCase() === trimmed.toLowerCase())) {\n // Already present — no-op (preserves first-seen casing).\n return\n }\n await settings.writeAddonStore({ locations: [...current, trimmed] })\n },\n\n /**\n * Remove a label from the curated registry. Match is\n * case-insensitive. Devices that still reference this label keep\n * their `meta.location` value (the registry is a suggestion\n * list, not a foreign key) — pass `cascade: true` to also clear\n * `setLocation` on every device that referenced this exact\n * label. Cascade only matches case-insensitively + trimmed, same\n * as the registry equality check.\n */\n removeLocation: async (input) => {\n const trimmed = input.name.trim()\n if (trimmed.length === 0) return\n const store = (await settings.readAddonStore()) as AddonStore\n const current = store.locations ?? []\n const remaining = current.filter((l) => l.toLowerCase() !== trimmed.toLowerCase())\n if (remaining.length !== current.length) {\n await settings.writeAddonStore({ locations: remaining })\n }\n if (input.cascade !== true) return\n\n const meta = store.deviceMeta ?? {}\n const updates: Record<string, PersistedDeviceMeta> = { ...meta }\n const cleared: number[] = []\n for (const [key, m] of Object.entries(meta)) {\n if (typeof m.location !== 'string') continue\n if (m.location.trim().toLowerCase() !== trimmed.toLowerCase()) continue\n updates[key] = { ...m, location: null }\n cleared.push(m.id)\n }\n if (cleared.length === 0) return\n await settings.writeAddonStore({ deviceMeta: updates })\n for (const deviceId of cleared) {\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'location', value: null },\n })\n }\n },\n\n listPersistedByAddon: async (input) => {\n const { addonId } = input\n const [index, meta] = await Promise.all([readIndex(), readMeta()])\n const stableIds = index[addonId] ?? []\n return stableIds.map(stableId => {\n const m = meta[deviceKey(addonId, stableId)]!\n return {\n id: m.id,\n stableId,\n type: m.type,\n name: m.name,\n location: m.location ?? null,\n disabled: m.disabled ?? false,\n parentDeviceId: m.parentDeviceId,\n }\n })\n },\n\n // ── Live queries — serve from live registry when available ────────────\n\n listAll: async (input) => {\n const { addonId } = input\n\n // Union of two sources, de-duped on (addonId, stableId):\n // 1. Hub-process live registry: IDevice instances owned by this\n // process (hub-resident addons). Provides live `online`,\n // features, and isCamera.\n // 2. Persisted index (settings): authoritative cross-process ledger\n // including devices owned by forked workers, whose IDevice\n // instance lives in the worker's registry and is invisible to\n // the hub-registry.\n //\n // Live wins on the overlap, so hub-owned devices keep their accurate\n // online/features. Forked-worker devices are reconstructed from\n // persisted metadata + persisted config.\n const results: ReturnType<typeof toDeviceInfo>[] = []\n const seen = new Set<string>()\n\n const meta = await readMeta()\n // Hardware metadata lives in its own row; pre-load once and inject\n // into every result regardless of branch.\n const metadataMap = await readMetadataMap()\n\n if (registry) {\n const liveEntries = addonId\n ? registry.getAllForAddon(addonId).map(device => ({ addonId: addonId!, device }))\n : registry.getAllWithAddonId()\n for (const { addonId: aid, device } of liveEntries) {\n const key = deviceKey(aid, device.stableId)\n const metadata = metadataMap[key] ?? null\n const metaRow = meta[key] ?? null\n results.push(toDeviceInfo(aid, device, metadata, metaRow))\n seen.add(key)\n }\n }\n\n const index = await readIndex()\n const targetAddons = addonId ? [addonId] : Object.keys(index)\n for (const aid of targetAddons) {\n for (const stableId of (index[aid] ?? [])) {\n const key = deviceKey(aid, stableId)\n if (seen.has(key)) continue\n const m = meta[key]!\n const persistedType = m.type as DeviceType\n const persistedConfig = (await settings.readDeviceStore(m.id)) as Record<string, unknown>\n const metadata = metadataMap[key] ?? null\n results.push({\n id: m.id,\n stableId,\n addonId: aid,\n type: persistedType,\n name: m?.name ?? stableId,\n location: m?.location ?? null,\n disabled: m?.disabled ?? false,\n parentDeviceId: m?.parentDeviceId ?? null,\n // role is not persisted to disk — only set on the live\n // IDevice (`BaseDevice.role`) by accessory subclasses. The\n // forked-worker fallback path can't read it, so emit null;\n // accessories typically run in the same process as their\n // parent provider, so the live path covers them in practice.\n role: null,\n // registry===null was the legacy \"offline view\" signal; keep it\n // for that case so existing callers see online=false. When a\n // registry is present and the device lives elsewhere (forked\n // worker), default to online=true — persisted state implies it\n // was successfully registered by its owning process.\n online: registry !== null,\n features: persistedFeatures(m?.features),\n // Live IDevice isn't reachable from the hub, so infer\n // isCamera from the persisted DeviceType rather than the\n // method-presence check `toDeviceInfo` uses. Without this\n // forked-worker cameras looked non-camera to the stream-broker\n // and never got their RTSP restream wired up.\n isCamera: persistedType === DeviceType.Camera,\n config: persistedConfig ?? {},\n metadata,\n })\n }\n }\n return results\n },\n\n getDevice: async (input) => {\n const { deviceId } = input\n\n // Prefer live (hub-registry) when present; otherwise fall back to\n // persisted metadata so we also find devices owned by forked workers.\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n const key = deviceKey(found.addonId, found.device.stableId)\n const [map, metaMap] = await Promise.all([readMetadataMap(), readMeta()])\n const metadata = map[key] ?? null\n const metaRow = metaMap[key] ?? null\n return toDeviceInfo(found.addonId, found.device, metadata, metaRow)\n }\n }\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return null\n const { addonId: aid, stableId, meta: m } = persisted\n const persistedConfig = (await settings.readDeviceStore(m.id)) as Record<string, unknown>\n const key = deviceKey(aid, stableId)\n const map = await readMetadataMap()\n const metadata = map[key] ?? null\n // Name now lives exclusively on the meta surface (mutated via\n // `setName` action) — the legacy config.name fallback is gone\n // since drivers no longer carry `name` in their config schema.\n return {\n id: deviceId,\n stableId,\n addonId: aid,\n type: m.type as DeviceType,\n name: m.name,\n location: m.location ?? null,\n disabled: m.disabled ?? false,\n parentDeviceId: m.parentDeviceId,\n role: null,\n online: true,\n features: persistedFeatures(m.features),\n isCamera: false,\n config: persistedConfig ?? {},\n metadata,\n }\n },\n\n getChildren: async (input) => {\n const { parentDeviceId } = input\n\n // Resolve parent ownership so we can scope the persisted-child scan\n // per owning addon. Children are always registered under the same\n // addonId as their parent. The hub registry's getChildren() is a\n // pure numeric filter (`d.parentDeviceId === parentDeviceId`) so\n // we no longer need the parent's stableId for live lookups.\n let ownerAddonId: string | null = null\n if (registry) {\n const liveParent = registry.getById(parentDeviceId)\n if (liveParent) {\n ownerAddonId = registry.getAddonId(parentDeviceId)\n }\n }\n if (!ownerAddonId) {\n const persisted = await resolvePersistedById(parentDeviceId)\n if (!persisted) return []\n ownerAddonId = persisted.addonId\n }\n\n // Same union pattern as listAll: live (hub-registry) wins, persisted\n // fills in devices owned by forked workers.\n const results: ReturnType<typeof toDeviceInfo>[] = []\n const seen = new Set<string>()\n\n const [index, meta, metadataMap] = await Promise.all([readIndex(), readMeta(), readMetadataMap()])\n\n if (registry) {\n const liveChildren = registry.getChildren(parentDeviceId)\n for (const device of liveChildren) {\n const key = deviceKey(ownerAddonId, device.stableId)\n const metadata = metadataMap[key] ?? null\n const metaRow = meta[key] ?? null\n results.push(toDeviceInfo(ownerAddonId, device, metadata, metaRow))\n seen.add(key)\n }\n }\n\n const stableIds = index[ownerAddonId] ?? []\n const persistedChildren = stableIds.filter(\n sid => meta[deviceKey(ownerAddonId!, sid)]?.parentDeviceId === parentDeviceId,\n )\n for (const childStableId of persistedChildren) {\n const key = deviceKey(ownerAddonId, childStableId)\n if (seen.has(key)) continue\n const m = meta[key]!\n const persistedConfig = (await settings.readDeviceStore(m.id)) as Record<string, unknown>\n const metadata = metadataMap[key] ?? null\n results.push({\n id: m.id,\n stableId: childStableId,\n addonId: ownerAddonId,\n type: m.type as DeviceType,\n name: m.name,\n location: m.location ?? null,\n disabled: m.disabled ?? false,\n parentDeviceId,\n role: null,\n online: registry !== null,\n features: persistedFeatures(m.features),\n isCamera: false,\n config: persistedConfig ?? {},\n metadata,\n })\n }\n return results\n },\n\n // ── Live runtime methods ──────────────────────────────────────────────\n\n getStreamSources: async (input) => {\n const { deviceId } = input\n // Hub-local path\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n // Non-camera devices have no streams by definition — return\n // an empty list immediately instead of round-tripping through\n // `device-ops`. Cross-process dispatch is strictly for\n // worker-owned devices, not for wrong-type hub-local ones.\n if (!isCameraDevice(found.device)) return []\n const sources: readonly StreamSourceEntry[] = await found.device.getStreamSources()\n return sources.map(s => ({\n id: s.id,\n label: s.label,\n protocol: s.protocol,\n url: s.url,\n resolution: s.resolution,\n fps: s.fps,\n bitrate: s.bitrate,\n codec: s.codec,\n profileHint: s.profileHint,\n }))\n }\n }\n // Cross-process fallback — dispatch through the `device-ops` native\n // provider, now keyed on the numeric id in both hub and worker.\n // Errors propagate: a transient worker error must NOT masquerade as\n // \"device has no streams\" — callers need the real error.\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const ops = requireDeviceOps(deviceId)\n const sources = await ops.getStreamSources({ deviceId })\n return sources.map(s => ({ ...s }))\n },\n\n getConfigSchema: async (input) => {\n const { deviceId } = input\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n return found.device.config.entries().map(entry => ({\n key: entry.key,\n value: entry.value,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n }))\n }\n }\n // Cross-process fallback. Propagates the real error when the\n // owning worker is offline or the device genuinely doesn't exist —\n // surfacing \"no config\" silently used to let the UI render empty\n // forms for broken devices.\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const ops = requireDeviceOps(deviceId)\n const entries = await ops.getConfigEntries({ deviceId })\n return entries.map(e => ({ ...e }))\n },\n\n getSettingsSchema: async (input) => {\n const { deviceId } = input\n // Single source of truth: every IDevice implements\n // `getSettingsUISchema`. Hub-local devices answer in-process;\n // forked-worker devices go through the `device-ops.getSettingsSchema`\n // native provider which delegates to the same method worker-side.\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n return found.device.getSettingsUISchema()\n }\n }\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return null\n const ops = requireDeviceOps(deviceId)\n const schema = await ops.getSettingsSchema({ deviceId })\n return (schema as ConfigUISchemaWithValues | null) ?? null\n },\n\n updateConfig: async (input) => {\n const { deviceId } = input\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n await found.device.config.setAll(input.values)\n return { success: true as const }\n }\n }\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const ops = requireDeviceOps(deviceId)\n await ops.setConfig({ deviceId, values: input.values })\n return { success: true as const }\n },\n\n // `enable` / `disable` write to deviceMeta.disabled (the canonical\n // soft-disable flag that drivers consult via `BaseDevice.disabled`\n // at lifecycle entrypoints, and that `toDeviceInfo` projects onto\n // the wire by reading the LATEST persisted meta row — see the\n // `metaRow` argument plumbed through `listAll` / `getDevice` /\n // `getChildren`).\n //\n // Earlier these wrappers wrote `enabled: true/false` into the\n // device CONFIG blob — wrong store entirely. The config write\n // succeeded but the flag the rest of the system reads\n // (deviceMeta) stayed unchanged, so the UI's enable/disable\n // toggle appeared to do nothing. Delegating to `setDisabled`\n // routes through the correct withMetaWriteLock path and emits\n // the `DeviceMetaChanged` event for live invalidation.\n //\n // NB: `BaseDevice.disabled` is `readonly` and bound at\n // construction (base-device.ts:142). Drivers that gate behaviour\n // off `this.disabled` only see the value the device was\n // instantiated with — flipping disabled at runtime updates the\n // wire response immediately, but lifecycle gating inside the\n // driver only takes effect on the next instantiation. That's\n // acceptable today: the operator-facing semantic is \"hide /\n // mark inactive in the UI\"; the heavyweight teardown semantic\n // is `remove`.\n enable: async (input) => {\n await provider.setDisabled({ deviceId: input.deviceId, disabled: false })\n return { success: true as const }\n },\n\n disable: async (input) => {\n await provider.setDisabled({ deviceId: input.deviceId, disabled: true })\n return { success: true as const }\n },\n\n remove: async (input) => {\n const { deviceId } = input\n // (a) Device lives in the hub registry → local teardown, then persistence.\n if (registry) {\n const live = resolveDeviceById(registry, deviceId)\n if (live) {\n const deviceName = live.device.name\n await live.device.removeDevice()\n registry.remove(deviceId)\n await provider.removeDevice({ deviceId })\n this.ctx.logger.info('removed hub-local device', { tags: { deviceId, deviceName } })\n return { success: true as const }\n }\n }\n // (b) Device is owned by a forked worker → invoke its `device-ops`\n // native provider to let the driver release resources, then\n // clear hub persistence.\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const { meta: persistedMeta } = persisted\n try {\n const ops = requireDeviceOps(deviceId)\n await ops.removeDevice({ deviceId })\n } catch (err) {\n // Worker might be offline or missing — still clear persistence so\n // the UI recovers. The warning is best-effort diagnostics.\n this.ctx.logger.warn('remove via device-ops failed — clearing persistence anyway', { tags: { deviceId, deviceName: persistedMeta.name }, meta: { error: errMsg(err) } })\n }\n await provider.removeDevice({ deviceId })\n return { success: true as const }\n },\n\n getStreamProfileMap: async (input) => {\n if (!registry) return {}\n const found = resolveDeviceById(registry, input.deviceId)\n if (!found) return {}\n\n // Check for explicitly stored profile map first\n const storedMap: unknown = found.device.config.entries().find(e => e.key === '_profileMap')?.value\n if (storedMap !== undefined && typeof storedMap === 'object' && storedMap !== null) {\n return storedMap as Record<string, string>\n }\n\n // Derive from profileHint fields on stream sources\n if (!isCameraDevice(found.device)) return {}\n const sources: readonly StreamSourceEntry[] = await found.device.getStreamSources()\n const profileMap: Record<string, string> = {}\n for (const s of sources) {\n if (s.profileHint && s.id) {\n profileMap[s.profileHint] = s.id\n }\n }\n return profileMap\n },\n\n setStreamProfileMap: async (input) => {\n const { deviceId } = input\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n await found.device.config.setAll({ _profileMap: input.profileMap })\n return { success: true as const }\n }\n }\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const ops = requireDeviceOps(deviceId)\n await ops.setConfig({ deviceId, values: { _profileMap: input.profileMap } })\n return { success: true as const }\n },\n\n probeStreams: async (input) => {\n const streamProbe = this.ctx.kernel.streamProbe\n if (!streamProbe) return []\n\n const sources = await provider.getStreamSources({ deviceId: input.deviceId })\n const results: Array<{\n streamId: string\n width?: number\n height?: number\n codec?: string\n fps?: number\n bitrateKbps?: number\n }> = []\n\n for (const s of sources) {\n if (!s.url) continue\n try {\n const metadata = await streamProbe.probe(s.url, { force: true })\n results.push({\n streamId: s.id,\n width: metadata.width,\n height: metadata.height,\n codec: metadata.codec,\n fps: metadata.fps,\n bitrateKbps: metadata.bitrateKbps,\n })\n } catch (err) {\n this.ctx.logger.debug('streamProbe.probe failed — returning placeholder', {\n meta: {\n deviceId: input.deviceId,\n streamId: s.id,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n results.push({ streamId: s.id })\n }\n }\n\n return results\n },\n\n // ── Device-provider operations (routed via CapabilityRegistry) ───────\n\n discoverDevices: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n\n const supported = await dp.supportsDiscovery({})\n if (!supported) {\n throw new Error(`Addon \"${input.addonId}\" does not support device discovery`)\n }\n\n const discovered = await dp.discoverDevices({})\n return discovered.map((d) => ({\n stableId: d.stableId,\n type: d.type,\n suggestedName: d.suggestedName,\n prefilledConfig: d.prefilledConfig,\n }))\n },\n\n adoptDevice: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n\n const supported = await dp.supportsDiscovery({})\n if (!supported) {\n throw new Error(`Addon \"${input.addonId}\" does not support device adoption`)\n }\n\n return dp.adoptDiscoveredDevice({\n candidate: input.candidate as Parameters<IDeviceProviderCap['adoptDiscoveredDevice']>[0]['candidate'],\n })\n },\n\n getCreationSchema: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n\n const supported = await dp.supportsManualCreation({})\n if (!supported) return null\n\n const schema = await dp.getChildCreationSchema({ type: input.type })\n return schema ?? null\n },\n\n createDevice: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n\n const supported = await dp.supportsManualCreation({})\n if (!supported) {\n throw new Error(`Addon \"${input.addonId}\" does not support manual device creation`)\n }\n\n return dp.createDevice({ type: input.type, config: input.config })\n },\n\n testCreationField: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n return dp.testCreationField({\n type: input.type,\n key: input.key,\n value: input.value,\n ...(input.formValues !== undefined ? { formValues: input.formValues } : {}),\n })\n },\n\n testField: async (input) => {\n const { deviceId } = input\n let owningAddonId: string | null = null\n if (registry) {\n owningAddonId = registry.getAddonId(deviceId)\n }\n if (!owningAddonId) {\n const persisted = await resolvePersistedById(deviceId)\n owningAddonId = persisted?.addonId ?? null\n }\n if (!owningAddonId) {\n throw new Error(`Device with id ${deviceId} not found`)\n }\n\n const dp = await this.waitDeviceProvider(owningAddonId)\n if (!dp) {\n return { status: 'ok' as const, labels: [], error: undefined }\n }\n\n if (typeof (dp as Record<string, unknown>).testCreationField !== 'function') {\n return { status: 'ok' as const, labels: [], error: undefined }\n }\n\n return dp.testCreationField({ type: DeviceType.Camera, key: input.key, value: input.value })\n },\n\n // ── Bindings — per-device capability provider map ────────────────────\n\n getBindings: async (input) => {\n const result = await this.getBindings({ deviceId: input.deviceId })\n return { deviceId: input.deviceId, entries: result.entries }\n },\n getAllBindings: async () => {\n return this.getAllBindings()\n },\n setWrapperActive: async (input) => {\n return this.setWrapperActive({\n deviceId: input.deviceId,\n capName: input.capName,\n wrapperAddonId: input.wrapperAddonId,\n active: input.active,\n })\n },\n listWrappersForCap: async (input) => this.listWrappersForCap(input),\n listBindableCapsForDeviceType: async (input) => this.listBindableCapsForDeviceType(input),\n\n // ── Device-details aggregators ──────────────────────────────────────\n\n getDeviceSettingsAggregate: async (input) => {\n return this.getDeviceAggregate(input.deviceId, 'settings')\n },\n getDeviceLiveInfoAggregate: async (input) => {\n return this.getDeviceAggregate(input.deviceId, 'live')\n },\n getDeviceAggregate: async (input) => {\n // Run both aggregations in parallel — the live + settings passes\n // enumerate the same provider set but call different methods, so\n // there's no overlap to serialize.\n const [settings, live] = await Promise.all([\n this.getDeviceAggregate(input.deviceId, 'settings'),\n this.getDeviceAggregate(input.deviceId, 'live'),\n ])\n return { settings, live }\n },\n updateDeviceField: async (input) => {\n return this.updateDeviceField({\n deviceId: input.deviceId,\n writerCapName: input.writerCapName,\n writerAddonId: input.writerAddonId,\n key: input.key,\n value: input.value,\n })\n },\n updateDeviceFieldsBatch: async (input) => {\n return this.updateDeviceFieldsBatch({ deviceId: input.deviceId, changes: input.changes })\n },\n getDeviceStatusAggregate: async (input) => this.getDeviceStatusAggregate(input),\n }\n\n this.ctx.logger.info('registered device-manager capability', { meta: { liveRegistry: registry !== null } })\n\n // Fan-out device events to ancestor scopes. Runs only when a live\n // registry is available on this node — forked workers that don't\n // own the registry rely on the hub's propagator (events cross the\n // Moleculer bridge already, propagation is idempotent with the\n // `via` guard so emitting on both sides is safe).\n if (registry) {\n this.propagator = new DeviceEventPropagator({\n eventBus: this.ctx.eventBus,\n getParentOf: (id) => registry.getById(id)?.parentDeviceId ?? null,\n logger: {\n warn: (msg, meta) => this.ctx.logger.warn(msg, meta ?? {}),\n debug: (msg, meta) => this.ctx.logger.debug(msg, meta ?? {}),\n },\n })\n this.propagator.start()\n this.ctx.logger.info('device-event-propagator started')\n }\n\n // `deviceState` cap — generic cross-process surface over the\n // hub mirror. Drivers don't implement this directly; we mount\n // the singleton here so any addon / UI can read any cap's\n // runtime-state slice without the cap having to define its own\n // getStatus / onChanged.\n const deviceStateProvider = {\n getSnapshot: async (input: { deviceId: number }): Promise<Record<string, Record<string, unknown>>> => {\n return this.snapshotForDevice(input.deviceId)\n },\n getCapSlice: async (input: { deviceId: number; capName: string }): Promise<Record<string, unknown> | null> => {\n const perCap = this.stateMirror.get(input.deviceId)\n const slice = perCap?.get(input.capName)\n return slice ? { ...slice } : null\n },\n // Whole-system warm-boot dump. Iterates the mirror once and\n // returns a deeply-cloned `{[deviceId]: {[capName]: slice}}`\n // payload so callers (SystemManager, dashboards) seed their\n // local mirror with one round-trip instead of N per-device\n // calls. Empty inner record for devices with no slices yet.\n getAllSnapshots: async (): Promise<Record<string, Record<string, Record<string, unknown>>>> => {\n const out: Record<string, Record<string, Record<string, unknown>>> = {}\n for (const [deviceId, perCap] of this.stateMirror) {\n const dev: Record<string, Record<string, unknown>> = {}\n for (const [capName, slice] of perCap) dev[capName] = { ...slice }\n out[String(deviceId)] = dev\n }\n return out\n },\n // Canonical cross-layer write — the single entrypoint every\n // caller funnels through. In-process providers reach it via\n // `BaseDevice.runtimeState.setCapState` (which delegates here);\n // system-scope addons (e.g. pipeline runner writing the motion\n // slice on phase transitions) call this cap method directly\n // through `ctx.api.deviceState.setCapSlice.mutate(...)`.\n setCapSlice: async (input: { deviceId: number; capName: string; slice: Record<string, unknown> }): Promise<void> => {\n const { deviceId, capName, slice } = input\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setCapSlice: unknown device id=${deviceId}`)\n // Mirror update + onChanged event are synchronous and ordered;\n // disk write is debounced so frequent writes (motion phase\n // transitions, battery pushes) collapse to one fsync per\n // RUNTIME_STATE_DEBOUNCE_MS window.\n this.applySingleCapUpdate(deviceId, capName, slice)\n this.scheduleRuntimeStateDiskWrite(deviceId, settings)\n },\n }\n\n return [\n { capability: deviceManagerCapability, provider },\n { capability: deviceStateCapability, provider: deviceStateProvider },\n ]\n }\n\n /**\n * Single-cap mirror update — diff against the current mirror,\n * persist the new slice in-memory, emit `DeviceStateChanged` for\n * this cap. No-op on identical writes (both same shape and same\n * values). Called by `setCapSlice` provider.\n */\n private applySingleCapUpdate(\n deviceId: number,\n capName: string,\n slice: Record<string, unknown>,\n ): void {\n let perCap = this.stateMirror.get(deviceId)\n if (!perCap) {\n perCap = new Map()\n this.stateMirror.set(deviceId, perCap)\n }\n const prior = perCap.get(capName)\n if (prior && shallowEqual(prior, slice)) return\n perCap.set(capName, { ...slice })\n this.emitStateChanged(deviceId, capName, slice)\n }\n\n /**\n * Debounced disk writer. Coalesces frequent writes (motion phase\n * transitions, battery pushes) into one `writeDeviceRuntimeState`\n * per `RUNTIME_STATE_DEBOUNCE_MS` window. Reads the per-device\n * blob from the live mirror at flush time so the disk picture is\n * always the latest state — no risk of writing a stale snapshot.\n */\n private scheduleRuntimeStateDiskWrite(\n deviceId: number,\n settings: NonNullable<typeof this.ctx.settings>,\n ): void {\n let slot = this.runtimeStateDebounce.get(deviceId)\n if (!slot) {\n slot = { timer: null, inFlight: null }\n this.runtimeStateDebounce.set(deviceId, slot)\n }\n if (slot.timer) return\n slot.timer = setTimeout(() => {\n slot!.timer = null\n const blob = this.snapshotForDevice(deviceId)\n const write = (async () => {\n try {\n await settings.writeDeviceRuntimeState(deviceId, blob)\n } catch (err: unknown) {\n this.ctx.logger.warn('writeDeviceRuntimeState failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n } finally {\n slot!.inFlight = null\n }\n })()\n slot!.inFlight = write\n }, DeviceManagerAddon.RUNTIME_STATE_DEBOUNCE_MS)\n }\n\n /**\n * One-shot mirror seed used by `loadRuntimeState` at boot so the\n * hub knows about every persisted slice without waiting for the\n * first `setCapSlice` call. No events emitted — this is\n * initial-state population, not a transition.\n */\n private seedMirror(deviceId: number, blob: Record<string, unknown>): void {\n let perCap = this.stateMirror.get(deviceId)\n if (!perCap) {\n perCap = new Map()\n this.stateMirror.set(deviceId, perCap)\n }\n for (const [capName, raw] of Object.entries(blob)) {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) continue\n perCap.set(capName, { ...raw as Record<string, unknown> })\n }\n }\n\n private snapshotForDevice(deviceId: number): Record<string, Record<string, unknown>> {\n const perCap = this.stateMirror.get(deviceId)\n if (!perCap) return {}\n const out: Record<string, Record<string, unknown>> = {}\n for (const [k, v] of perCap) out[k] = { ...v }\n return out\n }\n\n private emitStateChanged(deviceId: number, capName: string, slice: Record<string, unknown>): void {\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceStateChanged,\n data: { deviceId, capName, slice },\n })\n }\n\n protected async onShutdown(): Promise<void> {\n this.propagator?.stop()\n this.propagator = null\n // Flush any pending debounced runtime-state disk writes so a\n // graceful shutdown doesn't drop the latest mirror state.\n const settings = this.ctx.settings\n const pending: Promise<void>[] = []\n for (const [deviceId, slot] of this.runtimeStateDebounce) {\n if (slot.timer) {\n clearTimeout(slot.timer)\n slot.timer = null\n if (settings) {\n const blob = this.snapshotForDevice(deviceId)\n pending.push(settings.writeDeviceRuntimeState(deviceId, blob).catch((err: unknown) => {\n this.ctx.logger.warn('shutdown writeDeviceRuntimeState failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }))\n }\n }\n if (slot.inFlight) pending.push(slot.inFlight)\n }\n await Promise.all(pending)\n this.runtimeStateDebounce.clear()\n }\n}\n\nexport default DeviceManagerAddon\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"device-manager.addon.js","names":[],"sources":["../../../src/builtins/device-manager/device-event-propagator.ts","../../../src/builtins/device-manager/device-config-contribution.ts","../../../src/builtins/device-manager/device-manager.addon.ts"],"sourcesContent":["import type { IEventBus, EventSource, SystemEvent } from '@camstack/types'\n\n/**\n * Walks the parent chain for every device-sourced event and re-emits a\n * copy on each ancestor scope with `via[]` populated.\n *\n * Design goals:\n * - Transparent: drivers emit once on their own device scope; the\n * framework handles fan-out. Zero provider boilerplate.\n * - Anti-loop: events that already carry `via[]` are skipped (we only\n * propagate ORIGINAL emissions).\n * - Anti-cycle: the parent chain is bounded — if the device registry\n * is corrupt and has a cycle, the walker caps at `MAX_CHAIN_DEPTH`\n * and logs a warning.\n * - Lazy: parent chain is resolved on-demand per event (no cached\n * topology). The lookup is O(depth) which is ≤2 in practice.\n *\n * `via` contract (from SystemEvent.via JSDoc):\n * - `via[0]` is the originating source (the device that produced the\n * event). Subsequent entries walk up the parent chain.\n * - On the re-emission, `source` is the ancestor at that level and\n * `via[0..i]` is the prefix of the chain up to and including the\n * first N ancestors below the current one.\n *\n * Example (grandchild → parent → grandparent):\n * Original: { source: {id: 7}, data: {...}, via: undefined }\n * Re-emit 1: { source: {id: 4}, data: {...}, via: [{id: 7}] }\n * Re-emit 2: { source: {id: 1}, data: {...}, via: [{id: 7}, {id: 4}] }\n *\n * A consumer listening at `source.id === 1` receives re-emit 2 (with\n * `via` showing the chain). A consumer listening at `source.id === 7`\n * with `via === undefined` receives the original only.\n */\n\n/** Bounded walk — paranoia against corrupt device registries with cycles. */\nconst MAX_CHAIN_DEPTH = 16\n\nexport interface DeviceEventPropagatorOptions {\n readonly eventBus: IEventBus\n /**\n * Resolve the numeric parent id of a device, or `null` when the\n * device has no parent (or was deleted). The propagator injects a\n * closure over the hub's `DeviceRegistry` so the lookup is in-\n * process and O(1) per call.\n */\n readonly getParentOf: (deviceId: number) => number | null\n /** Structured logger for anti-cycle + anti-loop diagnostics. */\n readonly logger: {\n warn: (msg: string, meta?: Record<string, unknown>) => void\n debug: (msg: string, meta?: Record<string, unknown>) => void\n }\n}\n\nexport class DeviceEventPropagator {\n private unsubscribe: (() => void) | null = null\n\n constructor(private readonly opts: DeviceEventPropagatorOptions) {}\n\n start(): void {\n if (this.unsubscribe) return\n // Subscribe to ALL events — the filter is applied inline per event\n // so we can short-circuit non-device sources without walking the\n // event bus's glob engine every time.\n const unsub = this.opts.eventBus.subscribe({}, (ev) => this.handle(ev))\n this.unsubscribe = unsub\n }\n\n stop(): void {\n if (!this.unsubscribe) return\n this.unsubscribe()\n this.unsubscribe = null\n }\n\n /** Exposed for tests — lets them inject events without the full bus. */\n handle(ev: SystemEvent): void {\n if (ev.via !== undefined) return // skip propagated\n if (ev.source.type !== 'device') return // skip non-device scopes\n const rawId = ev.source.id\n const deviceId = typeof rawId === 'number' ? rawId : Number(rawId)\n if (!Number.isFinite(deviceId)) return\n\n const chain = this.resolveParentChain(deviceId)\n if (chain.length === 0) return\n\n // Build `via[]` incrementally: for the first ancestor, via = [origin];\n // for the second, via = [origin, firstAncestor]; etc.\n const origin: EventSource = ev.source\n const via: EventSource[] = [origin]\n for (const ancestorId of chain) {\n const reEmission: SystemEvent = {\n ...ev,\n source: { type: 'device', id: ancestorId },\n via: [...via],\n }\n this.opts.eventBus.emit(reEmission)\n via.push({ type: 'device', id: ancestorId })\n }\n }\n\n private resolveParentChain(deviceId: number): readonly number[] {\n const chain: number[] = []\n const seen = new Set<number>([deviceId])\n let current = this.opts.getParentOf(deviceId)\n while (current != null) {\n if (seen.has(current)) {\n this.opts.logger.warn(\n 'device-event-propagator: cycle detected in parent chain — aborting propagation',\n { tags: { deviceId }, meta: { cycleAt: current, chainSoFar: [...chain] } },\n )\n return chain\n }\n seen.add(current)\n chain.push(current)\n if (chain.length >= MAX_CHAIN_DEPTH) {\n this.opts.logger.warn(\n 'device-event-propagator: chain depth limit hit — truncating',\n { tags: { deviceId }, meta: { depth: chain.length, max: MAX_CHAIN_DEPTH } },\n )\n break\n }\n current = this.opts.getParentOf(current)\n }\n return chain\n }\n}\n","/**\n * D14 device-config archetype — framework-side contribution derivation.\n *\n * A `deviceConfig` cap with `ui.kind: 'derived-form'` names a `builderId`.\n * This module owns the registry of `builderId → { buildSchema, applyPatch }`\n * pure reducers. The device-manager aggregator calls `deriveFormContribution`\n * to build the UI section from the cap's `getOptions` + `getStatus` output,\n * and `applyDerivedFormPatch` to route a flat form patch back through the\n * cap's `set*` mutation. No per-vendor UI code: reolink and hikvision\n * produce the same section from the same inputs.\n */\nimport {\n buildStreamParamsConfigSchema,\n parseStreamParamsFormPatch,\n STREAM_PROFILE_META,\n} from '@camstack/types'\nimport type {\n StreamParamsOptions,\n StreamParamsStatus,\n StreamProfile,\n StreamProfilePatch,\n ConfigUISchema,\n} from '@camstack/types'\n\n/** A framework-derived contribution shape — sections[] (+ optional tabs[]). */\nexport interface DerivedContributionShape {\n readonly tabs?: ReadonlyArray<{ id: string; label: string; icon: string; order?: number }>\n readonly sections: ReadonlyArray<{\n id: string\n title: string\n tab?: string\n order?: number\n description?: string\n columns?: 1 | 2 | 3 | 4\n fields: readonly unknown[]\n }>\n}\n\n/** A per-profile mutation callback the cap's provider supplies. */\nexport type ProfileSetter = (profile: StreamProfile, patch: StreamProfilePatch) => Promise<void>\n\n/** The single registered builderId today. New device-config caps add their own. */\nexport const STREAM_PARAMS_BUILDER_ID = 'stream-params' as const\n\n/**\n * Build the device-detail form section for a `derived-form` device-config\n * cap. Returns null when the camera exposes no configurable property.\n */\nexport function deriveFormContribution(\n builderId: string,\n options: unknown,\n status: unknown,\n): DerivedContributionShape | null {\n if (builderId !== STREAM_PARAMS_BUILDER_ID) {\n throw new Error(`device-config: unknown derived-form builderId \"${builderId}\"`)\n }\n const schema: ConfigUISchema | null = buildStreamParamsConfigSchema(\n options as StreamParamsOptions,\n (status as StreamParamsStatus | null) ?? null,\n )\n if (!schema) return null\n return {\n sections: schema.sections.map((s) => ({\n id: s.id,\n title: s.title,\n ...(s.tab !== undefined ? { tab: s.tab } : {}),\n ...(s.order !== undefined ? { order: s.order } : {}),\n ...(s.description !== undefined ? { description: s.description } : {}),\n ...(s.columns !== undefined ? { columns: s.columns } : {}),\n fields: [...s.fields],\n })),\n }\n}\n\n/**\n * Route a flat form patch back through the cap's per-profile setter.\n */\nexport async function applyDerivedFormPatch(\n builderId: string,\n patch: Record<string, unknown>,\n setProfile: ProfileSetter,\n): Promise<void> {\n if (builderId !== STREAM_PARAMS_BUILDER_ID) {\n throw new Error(`device-config: unknown derived-form builderId \"${builderId}\"`)\n }\n for (const meta of STREAM_PROFILE_META) {\n const profilePatch = parseStreamParamsFormPatch(patch, meta.prefix)\n if (profilePatch) await setProfile(meta.profile, profilePatch)\n }\n}\n","/**\n * Device Manager addon — hub-side singleton that unifies device persistence,\n * live registry queries, and all management operations into a single\n * tRPC-routable capability.\n *\n * Persistence strategy: all device data is stored via `ctx.settings`, the same\n * settings API every other addon uses. No raw SQLite access.\n *\n * Addon store layout:\n * deviceIndex → Record<addonId, stableId[]> (which devices exist per addon)\n * deviceMeta → Record<\"addonId:stableId\", DeviceMeta> (type, name, parentDeviceId, id)\n *\n * Device store (per-device config):\n * readDeviceStore(numericDeviceId) → config blob\n * writeDeviceStore(numericDeviceId, patch)\n *\n * Live registry: resolved from the kernel capability registry after Phase 2.\n * This gives direct access to in-memory IDevice instances registered by provider addons.\n * The DeviceManagerAddon is the single owner of the live device operations API.\n *\n * Replaces:\n * - `device-persistence` capability (absorbed here)\n * - live operations previously served by `device-management.router.ts`\n */\nimport { randomUUID } from 'node:crypto'\nimport { EventCategory,\n ProviderRegistration,\n BaseAddon,\n DeviceType,\n DeviceFeature,\n errMsg,\n isDeviceConfigCap,\n} from '@camstack/types'\nimport { DeviceEventPropagator } from './device-event-propagator.js'\nimport { CAP_NAMES_WITH_STATUS } from '@camstack/types'\nimport { WELL_KNOWN_TAB_MAP } from '@camstack/types'\nimport type {\n IDevice,\n ICameraDevice,\n StreamSourceEntry,\n DeviceSettingsContribution,\n ConfigUISchemaWithValues,\n CapabilityDefinition,\n DeviceConfigSpec,\n StreamProfile,\n StreamProfilePatch,\n} from '@camstack/types'\nimport { deriveFormContribution, applyDerivedFormPatch } from './device-config-contribution.js'\n\n/**\n * Wire shape matching `z.infer<typeof SettingsSchemaWithValuesSchema>` —\n * duplicated as a plain interface because importing the Zod schema across\n * package boundaries confuses `tsc` when the types package pins a\n * different `zod` minor than the core package. Keeping the shape local\n * keeps the addon decoupled from the schema's internal type encoding.\n */\ninterface ContributionShape {\n tabs?: Array<{ id: string; label: string; icon: string; order?: number }>\n sections: Array<{\n id: string\n title: string\n description?: string\n style?: 'card' | 'accordion'\n defaultCollapsed?: boolean\n columns?: 1 | 2 | 3 | 4\n tab?: string\n /** Where the section renders. Default 'settings' (Config tab); 'top-tab' hoists into the device-detail tab bar via DeviceDetail discovery. */\n location?: 'settings' | 'top-tab'\n order?: number\n fields: unknown[]\n }>\n}\n\n/**\n * Discriminated result from `resolveDriverConfigSchema`:\n * 'ok' – schema obtained; `schema` field is present.\n * 'none' – driver has no settings schema (legitimate empty).\n * 'unavailable' – cross-process call failed after retries (transient).\n */\ntype DriverConfigSchemaResult =\n | { status: 'ok'; schema: ContributionShape }\n | { status: 'none' }\n | { status: 'unavailable' }\n\n/**\n * Return true when `err` is a transient Moleculer error that is worth\n * retrying — specifically any `MoleculerRetryableError` subclass\n * (ServiceNotAvailableError, ServiceNotFoundError, BrokerDisconnectedError,\n * RequestTimeoutError, …). Moleculer sets `retryable: true` on all of them.\n *\n * Falls back to a message-substring check for serialised errors that arrive\n * across the Moleculer transport as plain objects rather than real instances.\n */\nfunction isTransientMoleculerError(err: unknown): boolean {\n if (err !== null && typeof err === 'object') {\n const e = err as Record<string, unknown>\n if (e['retryable'] === true) return true\n const code = typeof e['code'] === 'string' ? e['code'] : ''\n if (\n code === 'SERVICE_NOT_FOUND'\n || code === 'SERVICE_NOT_AVAILABLE'\n || code === 'REQUEST_TIMEOUT'\n || code === 'BAD_GATEWAY'\n ) return true\n }\n if (err instanceof Error) {\n const msg = err.message\n if (\n msg.includes('is not available')\n || msg.includes('is not found')\n || msg.includes('transporter has disconnected')\n || msg.includes('Request timed out')\n ) return true\n }\n return false\n}\n\nimport { deviceManagerCapability, deviceProviderCapability, deviceStateCapability } from '@camstack/types'\nimport type { InferProvider, IDeviceOpsProvider, ICapabilityRegistry } from '@camstack/types'\nimport type { IDeviceRegistry } from '@camstack/types'\nimport type { DeviceBindingEntry } from '@camstack/types'\n\ntype IDeviceManagerProvider = InferProvider<typeof deviceManagerCapability>\ntype IDeviceProviderCap = InferProvider<typeof deviceProviderCapability>\n\n// ── Bindings store types ─────────────────────────────────────────────────────\n\ninterface DeviceBindingsStore {\n readonly deviceBindings: Record<string /* deviceId */, Record<string /* capName */, { wrapperAddonId: string | null }>>\n}\n\ninterface PersistedDeviceMeta {\n type: string\n name: string\n /**\n * Operator-organisational label (room / area / zone). `null` when\n * unset. Mutable through `setLocation` action; surfaced on\n * `IDevice.location` via the kernel-built `DeviceContext.deviceMeta`\n * snapshot. Free-text — providers don't interpret it.\n */\n location: string | null\n /**\n * Soft-disabled flag. When `true`, the device class is still\n * instantiated and visible in the UI (so the operator can flip\n * back on without re-adding) but lifecycle hooks are gated by\n * `BaseDevice.disabled`. Mutable through `setDisabled` action.\n */\n disabled: boolean\n /** Numeric parent id (or null for standalone). The hub resolves the\n * parent's stableId to its numeric id at registration time, and from\n * there everything internal references the parent by `parentDeviceId`\n * — `stableId` only crosses the external boundary (autodiscovery and\n * `(addonId, stableId)` row lookups). */\n parentDeviceId: number | null\n /** Progressive, system-wide unique id. Allocated through\n * `allocateDeviceId` before `registerDevice` runs; backfilled at boot\n * for any legacy meta entry missing it (see init migration below).\n * Post-init invariant: every persisted device has a numeric id. */\n id: number\n /** Snapshot of `device.features` from the most recent `registerDevice`\n * call. Used by `getDevice` when the live `IDevice` instance lives\n * on a forked-worker / remote agent and isn't reachable from the\n * hub registry. Optional for legacy rows that predate the field —\n * callers fall back to an empty array. */\n features?: readonly string[]\n}\n\ninterface AddonStore {\n deviceIndex?: Record<string, string[]>\n deviceMeta?: Record<string, PersistedDeviceMeta>\n /** Hardware-identity metadata blob keyed by `<addonId>:<stableId>`.\n * Stored in a separate key from `deviceMeta` so the lifecycle\n * writers (registerDevice / setName / setLocation / setDisabled)\n * never need to read or preserve the metadata field — single-writer\n * per row eliminates the \"writer X clobbers writer Y's field\" bug\n * class. The `metadata` field on `PersistedDeviceMeta` is a legacy\n * fallback consulted only at read time during the lazy migration —\n * new writes always go to `deviceMetadata`. */\n deviceMetadata?: Record<string, Record<string, unknown>>\n /** Monotonic counter that feeds `PersistedDeviceMeta.id` for every\n * new device. Incremented exactly once per registerDevice; never\n * decremented. */\n nextDeviceId?: number\n /**\n * Operator-curated location registry. Each entry is a free-form room/\n * area label (e.g. \"Cucina\", \"Garage\") that drives the\n * autocomplete in the Device Info location editor. Devices reference\n * these as plain strings via `PersistedDeviceMeta.location` — the\n * registry is purely a suggestion list, NOT a foreign key. Removing a\n * location from the registry leaves devices that still reference it\n * intact; `listLocations` returns the union of registered labels +\n * labels currently in use, so nothing disappears from the UI even if\n * the operator forgot to register a label before assigning it.\n */\n locations?: readonly string[]\n}\n\nfunction shallowEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {\n const ak = Object.keys(a)\n const bk = Object.keys(b)\n if (ak.length !== bk.length) return false\n for (const k of ak) {\n if (a[k] !== b[k]) return false\n }\n return true\n}\n\nfunction deviceKey(addonId: string, stableId: string): string {\n return `${addonId}:${stableId}`\n}\n\n// ── Type guards ──────────────────────────────────────────────────────────────\n\nfunction isCameraDevice(device: IDevice): device is ICameraDevice {\n return 'getStreamSources' in device && typeof (device as ICameraDevice).getStreamSources === 'function'\n}\n\nconst DEVICE_FEATURE_VALUES = new Set<string>(Object.values(DeviceFeature) as readonly string[])\n\n/**\n * Validate persisted feature strings against the `DeviceFeature` enum\n * — workers serialise the live `device.features` array (so every entry\n * is a valid enum value at write time) but the persisted blob is loose\n * `string[]` on the wire. The narrow keeps unknown values out of the\n * `getDevice` response without losing the enum-typed contract.\n */\nfunction persistedFeatures(features: readonly string[] | undefined): DeviceFeature[] {\n if (!features) return []\n const out: DeviceFeature[] = []\n for (const f of features) {\n if (DEVICE_FEATURE_VALUES.has(f)) out.push(f as DeviceFeature)\n }\n return out\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction toDeviceInfo(\n addonId: string,\n device: IDevice,\n metadata: Record<string, unknown> | null = null,\n metaRow: PersistedDeviceMeta | null = null,\n) {\n const configValues: Record<string, unknown> = {}\n for (const entry of device.config.entries()) {\n configValues[entry.key] = entry.value\n }\n // Operator-organisational fields (`name` / `location` / `disabled`)\n // are mutated through `setName` / `setLocation` / `setDisabled`,\n // which write to the persisted `deviceMeta` row and emit\n // `DeviceMetaChanged`. The live `BaseDevice` instance reads these\n // from `ctx.deviceMeta` at CONSTRUCTION ONLY (see\n // `base-device.ts:140-142`), so its `device.name` / `device.location`\n // / `device.disabled` fields are stale until the device class is\n // re-instantiated. Reading from `metaRow` here guarantees the wire\n // response reflects the latest persisted value within one settings\n // round-trip — the UI's enable/disable toggle (and rename) flips\n // immediately after the mutation completes. Falls back to the live\n // device fields when no meta row was supplied (test fixtures, edge\n // paths) so existing behaviour is preserved.\n const name = metaRow?.name ?? device.name\n const location = metaRow?.location !== undefined ? metaRow.location : device.location\n const disabled = metaRow?.disabled ?? device.disabled\n return {\n // Progressive system-wide id — bound on the live `IDevice` at\n // construction time (see `DeviceContext.id` / `BaseDevice.id`). The\n // hub allocates via `allocateDeviceId` BEFORE the device is\n // instantiated, so every live device exposes one.\n id: device.id,\n stableId: device.stableId,\n addonId,\n type: device.type,\n name,\n location,\n disabled,\n parentDeviceId: device.parentDeviceId,\n // `role` is set by the provider on accessory children (synthesized\n // via `getAccessoryChildren()` — Reolink siren/floodlight/PIR,\n // Hikvision supplemental light/siren). Top-level devices and\n // first-class hub-adopted children leave it `undefined`. Surfacing\n // it on `DeviceInfo` lets the UI distinguish accessory rows from\n // adopted-camera rows without re-deriving the heuristic from the\n // provider's `deviceClasses` map.\n role: device.role ?? null,\n online: device.online,\n features: [...device.features],\n isCamera: isCameraDevice(device),\n config: configValues,\n metadata,\n }\n}\n\nfunction resolveDeviceById(\n registry: IDeviceRegistry,\n deviceId: number,\n): { addonId: string; device: IDevice } | null {\n const device = registry.getById(deviceId)\n if (!device) return null\n const addonId = registry.getAddonId(deviceId)\n if (!addonId) return null\n return { addonId, device }\n}\n\n// ── Aggregator merge ─────────────────────────────────────────────────────────\n//\n// Concatenate several contributions into a single wire-shape object matching\n// `z.infer<typeof SettingsSchemaWithValuesSchema>` (plain mutable arrays).\n// Tabs with the same `id` collapse into one; sections within a tab are kept\n// in contribution order (contributors self-sort via `order`).\n//\n// No field-level merge — if two providers target the same `(tab, section)`\n// they're concatenated in discovery order. This is intentional: letting the\n// aggregator pick a winner would mask accidental collisions and silently\n// drop fields.\n//\n// `ContributionShape` / `AggregateShape` mirror the cap output exactly so no\n// assertion is needed when returning from the device-manager handlers.\n\ntype AggregateTabDecl = NonNullable<ContributionShape['tabs']>[number]\ntype AggregateSection = ContributionShape['sections'][number]\n\n/**\n * Walk the sections/fields of a contribution and inject `writerCapName` +\n * `writerAddonId` + `source` on each editable field. Readonly fields and\n * structural fields (separator/info/button) pass through untouched. The\n * aggregator is the single place that knows provenance — provider schemas\n * stay clean, UI-bound metadata is attached once at the boundary.\n */\nfunction tagContribution(\n contribution: ContributionShape,\n capName: string,\n addonId: string,\n kind: 'settings' | 'live',\n): ContributionShape {\n const source = kind === 'settings' ? 'settings' : 'live'\n return {\n ...(contribution.tabs ? { tabs: [...contribution.tabs] } : {}),\n sections: contribution.sections.map(section => ({\n ...section,\n fields: section.fields.map(field => tagField(field, capName, addonId, source, kind)),\n })),\n }\n}\n\nfunction isFieldRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\n/**\n * Convert a strict `ConfigUISchemaWithValues` (readonly arrays, typed\n * field union) into the cap wire shape `ContributionShape` (mutable\n * arrays, opaque field records). Required because the cap method z.infer\n * uses mutable arrays — readonly arrays are not assignable to mutable\n * even when structurally identical, so a structural copy bridges the gap\n * without disabling the type checker.\n */\nfunction toWireShape(input: ConfigUISchemaWithValues): ContributionShape {\n const out: ContributionShape = {\n sections: input.sections.map(s => ({\n id: s.id,\n title: s.title,\n ...(s.description !== undefined ? { description: s.description } : {}),\n ...(s.style !== undefined ? { style: s.style } : {}),\n ...(s.defaultCollapsed !== undefined ? { defaultCollapsed: s.defaultCollapsed } : {}),\n ...(s.columns !== undefined ? { columns: s.columns } : {}),\n ...(s.tab !== undefined ? { tab: s.tab } : {}),\n // `location: 'top-tab'` hoists the section into a discovered top-\n // level device tab in DeviceDetail. Forgetting to forward it here\n // strips the marker before the client sees it, so the tab never\n // shows up — see `DeviceDetail.tsx` aggregate-driven tab discovery.\n ...(s.location !== undefined ? { location: s.location } : {}),\n ...(s.order !== undefined ? { order: s.order } : {}),\n fields: [...s.fields],\n })),\n }\n if (input.tabs) out.tabs = [...input.tabs]\n return out\n}\n\nfunction tagField(\n field: unknown,\n capName: string,\n addonId: string,\n source: 'settings' | 'live',\n kind: 'settings' | 'live',\n): unknown {\n if (!isFieldRecord(field)) return field\n const f = field\n const structuralTypes = new Set(['separator', 'info', 'button'])\n if (typeof f.type === 'string' && structuralTypes.has(f.type)) return field\n\n const tagged: Record<string, unknown> = { ...f, source }\n if (kind === 'live' || f.readonlyField === true) {\n tagged.readonlyField = true\n } else {\n tagged.writerCapName = capName\n tagged.writerAddonId = addonId\n }\n\n // Recurse into composite layouts so nested leaf fields carry the\n // writer routing too. Without this, fields buried inside `'group'`\n // or `'sub-tabs'` containers are surfaced to the UI without a\n // writerCapName/writerAddonId — `findWriter` then can't route the\n // patch back to the cap that produced the field. Records are\n // typed `Record<string, unknown>`, so each step needs an explicit\n // `unknown[]` narrow before `.map` to keep the no-`any` rule happy.\n if (f.type === 'group') {\n const children: readonly unknown[] = Array.isArray(f.fields) ? f.fields : []\n if (children.length > 0) {\n tagged.fields = children.map((child) => tagField(child, capName, addonId, source, kind))\n }\n } else if (f.type === 'sub-tabs') {\n const rawTabs: readonly unknown[] = Array.isArray(f.tabs) ? f.tabs : []\n if (rawTabs.length > 0) {\n tagged.tabs = rawTabs.map((tab) => {\n if (!isFieldRecord(tab)) return tab\n const tabChildren: readonly unknown[] = Array.isArray(tab.fields) ? tab.fields : []\n return {\n ...tab,\n fields: tabChildren.map((child) => tagField(child, capName, addonId, source, kind)),\n }\n })\n }\n }\n return tagged\n}\n\nfunction mergeAggregates(parts: readonly ContributionShape[]): ContributionShape {\n const tabDecls = new Map<string, AggregateTabDecl>()\n const sections: AggregateSection[] = []\n\n for (const part of parts) {\n if (part.tabs) {\n for (const t of part.tabs) {\n if (!tabDecls.has(t.id)) tabDecls.set(t.id, t)\n }\n }\n for (const s of part.sections) {\n sections.push(s)\n }\n }\n\n // Auto-derive tab declarations from section references when contributors\n // don't emit one explicitly. Every tab id referenced in a section's `tab`\n // field gets a declaration — if the id matches a well-known tab\n // (general/detection/streaming/recording/…) the canonical label+icon+order\n // is used; otherwise we fall back to the raw id as label with a generic\n // icon. Ensures the UI has everything it needs to render the tab bar\n // from a single payload — no extra lookup, no duplicate source of truth.\n for (const s of sections) {\n const tabId = s.tab ?? 'general'\n if (tabDecls.has(tabId)) continue\n const known = WELL_KNOWN_TAB_MAP[tabId as keyof typeof WELL_KNOWN_TAB_MAP]\n if (known) {\n tabDecls.set(tabId, { id: known.id, label: known.label, icon: known.icon, order: known.order })\n } else {\n tabDecls.set(tabId, { id: tabId, label: tabId, icon: 'wrench', order: 100 })\n }\n }\n\n sections.sort((a, b) => {\n const tabA = a.tab ?? 'general'\n const tabB = b.tab ?? 'general'\n if (tabA !== tabB) {\n const orderA = tabDecls.get(tabA)?.order ?? 100\n const orderB = tabDecls.get(tabB)?.order ?? 100\n if (orderA !== orderB) return orderA - orderB\n return tabA.localeCompare(tabB)\n }\n return (a.order ?? 0) - (b.order ?? 0)\n })\n\n const sortedTabs = [...tabDecls.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100))\n const out: ContributionShape = { sections }\n if (sortedTabs.length > 0) out.tabs = sortedTabs\n return out\n}\n\nexport class DeviceManagerAddon extends BaseAddon {\n constructor() { super({}) }\n\n /** Shorthand for the kernel-injected capability registry. */\n private get capabilityRegistry(): ICapabilityRegistry | undefined {\n return this.ctx.kernel.capabilityRegistry\n }\n\n /**\n * Parent-chain event propagator. Started in `onInitialize` once the\n * hub's `deviceRegistry` is available; listens to every device-sourced\n * event and re-emits a copy on each ancestor scope with `via[]`\n * populated. Stopped in `onShutdown`.\n */\n private propagator: DeviceEventPropagator | null = null\n /**\n * Hub-side mirror of every device's cap-keyed runtime state.\n * Populated whenever any caller writes via `deviceState.setCapSlice`\n * (the canonical cross-layer write entrypoint) and on first load\n * via `loadRuntimeState`. Cross-process consumers reach the mirror\n * through the `deviceState` cap router; per-cap event subscribers\n * (e.g. `battery.onStatusChanged`) get the same data via\n * cap-specific events still emitted by the owning device.\n *\n * Key: deviceId. Value: per-cap slice map. Empty by default —\n * slices show up as `setCapSlice` calls trickle in.\n */\n private readonly stateMirror = new Map<number, Map<string, Record<string, unknown>>>()\n\n /**\n * Per-device disk-write debouncer for runtime-state. `setCapSlice`\n * updates the in-memory mirror synchronously and emits the change\n * event immediately, but the disk write is coalesced — frequent\n * back-to-back writes (motion phase transitions, battery pushes,\n * etc.) collapse to one `writeDeviceRuntimeState` per\n * `RUNTIME_STATE_DEBOUNCE_MS` window. `flushRuntimeStateWrites`\n * awaits any in-flight write + scheduled flush so shutdown is\n * lossless.\n */\n private readonly runtimeStateDebounce = new Map<number, {\n timer: ReturnType<typeof setTimeout> | null\n inFlight: Promise<void> | null\n }>()\n private static readonly RUNTIME_STATE_DEBOUNCE_MS = 1000\n\n /**\n * Cross-process native-provider cache: deviceId (numeric) → capName → { addonId, nodeId }.\n * Kept in sync with `DeviceBindingsChanged` push events emitted by forked\n * workers on `ctx.registerNativeCap` / device removal. Union'd into\n * `getBindings` so hub-side consumers see every native cap regardless of\n * which process owns the IDevice.\n *\n * No persistence — entries re-populate when the worker re-handshakes or\n * re-emits its native-cap registrations after restart. Entries that were\n * lost in the Moleculer transport handshake window are recovered lazily:\n * `resolveNativeCapOwnerSync` and `getBindings` fall through to\n * `ctx.kernel.listClusterNativeCaps()` (the handshake-fed\n * `HubNodeRegistry`) when the push-based cache misses.\n *\n * The previous pull-based recovery (`syncWorkerNativeCaps`, driven by\n * `$node.connected` + `addon.restarted`) has been removed in Task 13 —\n * the D3 re-handshake after device restore is the reliable replacement.\n */\n private remoteNativeCaps: Map<number, Map<string, { addonId: string; nodeId: string }>> = new Map()\n\n /** Wait for a device-provider by addonId, returning null on timeout. */\n private async waitDeviceProvider(addonId: string, timeoutMs = 5_000): Promise<IDeviceProviderCap | null> {\n const provider = await this.capabilityRegistry?.waitForProvider('device-provider', addonId, timeoutMs)\n return provider ? (provider as IDeviceProviderCap) : null\n }\n\n /** Require a device-provider by addonId — throws if not found. */\n private async requireDeviceProvider(addonId: string): Promise<IDeviceProviderCap> {\n const dp = await this.waitDeviceProvider(addonId)\n if (!dp) {\n throw new Error(`Device provider \"${addonId}\" not found or not registered`)\n }\n return dp\n }\n\n // ── Binding store (persisted wrapper activation) ───────────────────────────\n //\n // These methods are public so the device-manager capability router can\n // expose them as cap methods. They read/write through\n // ctx.settings.readAddonStore/writeAddonStore — the same persistence channel\n // the rest of the addon uses.\n\n private async readBindingsStore(): Promise<DeviceBindingsStore> {\n const raw = (await this.ctx.settings!.readAddonStore()) as Partial<DeviceBindingsStore>\n return { deviceBindings: raw.deviceBindings ?? {} }\n }\n\n private async writeBindingsStore(next: DeviceBindingsStore): Promise<void> {\n await this.ctx.settings!.writeAddonStore({ deviceBindings: next.deviceBindings })\n }\n\n private resolveWrapperNodeId(_wrapperAddonId: string): string {\n // TODO(phase-7): read from cluster view so cluster-deployed wrappers\n // report their actual node. For now assume hub-co-located.\n return 'hub'\n }\n\n /**\n * Resolve a remote native cap entry for a given `(capName, deviceId)` by\n * consulting the handshake-fed `HubNodeRegistry` via\n * `ctx.kernel.listClusterNativeCaps()`. Called when the push-based\n * `remoteNativeCaps` cache misses — covers the Moleculer transport\n * handshake window where `DeviceBindingsChanged` events were lost but the\n * D3 re-handshake (post device restore) has already populated the registry.\n *\n * Returns `null` when the entry is genuinely not present in the cluster\n * view (cap not registered on any worker for that device).\n */\n private resolveRemoteNativeCapFromRegistry(\n capName: string,\n deviceId: number,\n ): { addonId: string; nodeId: string } | null {\n const clusterCaps = this.ctx.kernel.listClusterNativeCaps?.()\n if (!clusterCaps) return null\n // Hub-local natives are NOT in the cluster registry (they live in\n // `capabilityRegistry`) — the caller already checked those.\n // Filter to the first remote entry matching (capName, deviceId).\n for (const entry of clusterCaps) {\n if (entry.capName === capName && entry.deviceId === deviceId && entry.addonId) {\n return { addonId: entry.addonId, nodeId: entry.nodeId }\n }\n }\n return null\n }\n\n async getBindings(input: { deviceId: number }): Promise<{ deviceId: number; entries: DeviceBindingEntry[] }> {\n // Every map (bindings store, native-cap registry, remoteNativeCaps) is\n // keyed by numeric deviceId now that Phase 2 finished the migration.\n const storeKey = String(input.deviceId)\n const store = await this.readBindingsStore()\n const perDevice = store.deviceBindings[storeKey] ?? {}\n\n const entries: DeviceBindingEntry[] = []\n const seenCaps = new Set<string>()\n\n // Helper: resolve remote native for (capName, deviceId).\n // Checks the push-fed `remoteNativeCaps` cache first; falls through to\n // the handshake-fed `HubNodeRegistry` (via `listClusterNativeCaps`) for\n // entries lost in the Moleculer transport handshake window.\n const resolveRemote = (capName: string): { addonId: string; nodeId: string } | null =>\n this.remoteNativeCaps.get(input.deviceId)?.get(capName)\n ?? this.resolveRemoteNativeCapFromRegistry(capName, input.deviceId)\n\n // 1. Entries from persisted wrapper activations\n for (const [capName, { wrapperAddonId }] of Object.entries(perDevice)) {\n const hubLocalNative = this.capabilityRegistry?.getNativeAddonId(capName, input.deviceId) ?? null\n const remoteNative = resolveRemote(capName)\n const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? ''\n const nativeNodeId = hubLocalNative\n ? (this.ctx.kernel.localNodeId ?? 'hub')\n : (remoteNative?.nodeId ?? (this.ctx.kernel.localNodeId ?? 'hub'))\n // Operator explicitly disabled the wrapper AND no native is available\n // → cap is genuinely off for this device. Mark it seen so step 2's\n // defaultActive auto-bind doesn't silently re-bind the wrapper.\n if (wrapperAddonId === null && !nativeAddonId) {\n seenCaps.add(capName)\n continue\n }\n entries.push({\n capName,\n kind: wrapperAddonId ? 'wrapped' : 'native',\n providerAddonId: wrapperAddonId ?? nativeAddonId,\n providerNodeId: wrapperAddonId ? this.resolveWrapperNodeId(wrapperAddonId) : nativeNodeId,\n nativeAddonId,\n })\n seenCaps.add(capName)\n }\n\n // 2. Auto-activated wrappers — any cap a wrapper addon declared with\n // `defaultActive: true` claims the binding HERE, ahead of native\n // discovery. The wrapper is the active provider; it delegates to\n // the native via `ctx.getNativeProvider`, so \"wrapper decorates\n // native\" is the intended runtime shape and the cap router resolves\n // locally on the wrapper's node instead of trying to forward to the\n // native's worker (cap routers like `snapshot` are mounted without\n // a `createRemoteProxy` because the wrapper handles cross-process\n // delegation internally).\n //\n // Step 1 (persisted activations) still wins: an operator-toggled\n // deactivation marks the cap seen with no entry, blocking this\n // branch from re-binding the wrapper.\n //\n // Pure-wrapper caps (motion-detection, audio-analysis,\n // detection-pipeline) have NO native — the wrapper is the only\n // implementation. They still bind here with an empty `nativeAddonId`\n // so consumers (UI bindings table, pipeline-orchestrator's\n // `isCapActiveForDevice` gate) see the wrapper as active by default.\n // Mirrors the fallback semantics of step 1 (`?? ''`).\n if (this.capabilityRegistry) {\n for (const capName of this.capabilityRegistry.getCapsWithDefaultWrapper()) {\n if (seenCaps.has(capName)) continue\n const defaultWrapperAddonId = this.capabilityRegistry.getDefaultWrapperForCap(capName)\n if (!defaultWrapperAddonId) continue\n const hubLocalNative = this.capabilityRegistry.getNativeAddonId(capName, input.deviceId) ?? null\n const remoteNative = resolveRemote(capName)\n const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? ''\n entries.push({\n capName,\n kind: 'wrapped',\n providerAddonId: defaultWrapperAddonId,\n providerNodeId: this.resolveWrapperNodeId(defaultWrapperAddonId),\n nativeAddonId,\n })\n seenCaps.add(capName)\n }\n }\n\n // 3. Entries from hub-local registered natives (no wrapper configured)\n if (this.capabilityRegistry) {\n for (const capName of this.capabilityRegistry.getNativeCapsForDevice(input.deviceId)) {\n if (seenCaps.has(capName)) continue\n const nativeAddonId = this.capabilityRegistry.getNativeAddonId(capName, input.deviceId) ?? ''\n entries.push({\n capName,\n kind: 'native',\n providerAddonId: nativeAddonId,\n providerNodeId: this.ctx.kernel.localNodeId ?? 'hub',\n nativeAddonId,\n })\n seenCaps.add(capName)\n }\n }\n\n // 4. Entries from remote (forked-worker) native caps. Sourced from two\n // complementary feeds, union'd to cover all scenarios:\n //\n // a) `remoteNativeCaps` — push-fed by `DeviceBindingsChanged` events\n // emitted by workers on `ctx.registerNativeCap`. Accurate in the\n // steady state and during normal device boot.\n //\n // b) `ctx.kernel.listClusterNativeCaps()` — handshake-fed by the D3\n // `$hub.registerNode` re-handshake that fires after device restore\n // completes. Covers the Moleculer transport window where push events\n // were lost (hub restart, crash-respawn, restartAddon). This is the\n // reliable replacement for the deleted `syncWorkerNativeCaps` pull.\n //\n // We iterate `remoteNativeCaps` first (push wins for already-seen caps),\n // then fold in any handshake-fed entries not yet in the push cache.\n const pushFed = this.remoteNativeCaps.get(input.deviceId)\n if (pushFed) {\n for (const [capName, info] of pushFed) {\n if (seenCaps.has(capName)) continue\n entries.push({\n capName,\n kind: 'native',\n providerAddonId: info.addonId,\n providerNodeId: info.nodeId,\n nativeAddonId: info.addonId,\n })\n seenCaps.add(capName)\n }\n }\n // Fold in handshake-fed entries not covered by the push cache.\n const clusterCaps = this.ctx.kernel.listClusterNativeCaps?.()\n if (clusterCaps) {\n for (const entry of clusterCaps) {\n if (entry.deviceId !== input.deviceId) continue\n if (seenCaps.has(entry.capName)) continue\n if (!entry.addonId) continue\n // Skip hub-local — already handled by step 3 (capabilityRegistry).\n const localNodeId = this.ctx.kernel.localNodeId ?? 'hub'\n if (entry.nodeId === localNodeId) continue\n entries.push({\n capName: entry.capName,\n kind: 'native',\n providerAddonId: entry.addonId,\n providerNodeId: entry.nodeId,\n nativeAddonId: entry.addonId,\n })\n seenCaps.add(entry.capName)\n }\n }\n\n return { deviceId: input.deviceId, entries }\n }\n\n /**\n * Whole-fleet binding dump. Iterates every device known to the\n * deviceRegistry and reuses the per-device `getBindings` resolver\n * for each — same routing rules, single round-trip. Used by\n * `SystemManager.init()` for warm-boot.\n *\n * Bindings change rarely (wrapper toggle, device add/remove) so\n * clients invalidate via the existing\n * `capability.binding-changed` event rather than re-fetching this\n * payload periodically.\n */\n async getAllBindings(): Promise<Array<{ deviceId: number; entries: DeviceBindingEntry[] }>> {\n const hubRegistry = this.ctx.kernel?.deviceRegistry\n if (!hubRegistry) return []\n const out: Array<{ deviceId: number; entries: DeviceBindingEntry[] }> = []\n for (const device of hubRegistry.getAll()) {\n out.push(await this.getBindings({ deviceId: device.id }))\n }\n return out\n }\n\n /**\n * Resolve a numeric deviceId to a stableId via persisted meta.\n * Used only by the device-identity section of the device-details\n * aggregator (see `buildBaseDeviceSection`) to surface the stableId as\n * a readonly display field. All runtime/registry lookups are keyed by\n * numeric deviceId; this helper is display-only.\n */\n private async lookupPersistedStableId(deviceId: number): Promise<string | undefined> {\n const store = (await this.ctx.settings!.readAddonStore()) as AddonStore\n const meta = store.deviceMeta ?? {}\n for (const [key, m] of Object.entries(meta)) {\n if (m.id === deviceId) {\n const sep = key.indexOf(':')\n if (sep < 0) continue\n return key.slice(sep + 1)\n }\n }\n return undefined\n }\n\n\n // ── Device-details aggregator ──────────────────────────────────────────────\n //\n // Walks the bindings for `deviceId` (native + wrapped), picks every\n // provider whose cap declares `exposesDeviceSettings: true`, and asks\n // each for its per-device contribution. Contributions are concatenated\n // into a single `ConfigUISchemaWithValues`; editable fields are tagged\n // with `writerCapName` + `writerAddonId` so `updateDeviceField` can\n // route the mutation back to the exact provider that produced them.\n //\n // `kind` selects settings vs live-info. The two aggregators share the\n // enumeration + tagging pipeline; only the method invoked on each\n // contributing provider differs.\n async getDeviceAggregate(\n deviceId: number,\n kind: 'settings' | 'live',\n ): Promise<ContributionShape | null> {\n const registry = this.capabilityRegistry\n if (!registry) {\n this.ctx.logger.debug('capability registry unavailable — aggregate empty', { meta: { kind } })\n return null\n }\n const method = kind === 'settings' ? 'getDeviceSettingsContribution' : 'getDeviceLiveContribution'\n\n // D12: per-device aggregation is BINDING-DRIVEN. Walk only the\n // (cap, provider) pairs actually bound to this device — never the\n // global set of providers that share a cap name. A device belongs to\n // exactly one integration; its contributions come only from providers\n // bound to it. System-scoped caps holding per-device state are bound\n // to the device too (they appear in `getBindings`) and are consulted\n // because they are bound, not because they share a name.\n const { entries: bindingEntries } = await this.getBindings({ deviceId })\n\n const results = await Promise.all(bindingEntries.map(async (entry) => {\n const def = registry.getDefinition(entry.capName)\n // D14: a `deviceConfig` cap's contribution is FRAMEWORK-DERIVED.\n if (isDeviceConfigCap(def)) {\n return this.deriveDeviceConfigContribution(registry, def, entry, deviceId, kind)\n }\n if (!def?.exposesDeviceSettings) return null\n const provider = registry.getProviderForDevice<DeviceSettingsContribution>(entry.capName, deviceId)\n if (!provider) return null\n try {\n const contribution = await provider[method]({ deviceId })\n if (!contribution) return null\n return tagContribution(toWireShape(contribution), entry.capName, entry.providerAddonId, kind)\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n this.ctx.logger.warn('contribution method failed', {\n tags: { deviceId, addonId: entry.providerAddonId },\n meta: { capName: entry.capName, method, error: msg },\n })\n return null\n }\n }))\n\n const base = kind === 'settings' ? await this.buildBaseDeviceSection(deviceId) : null\n const parts: ContributionShape[] = [\n ...(base ? [tagContribution(base, 'device-manager', 'device-manager', kind)] : []),\n ...results.filter((r): r is ContributionShape => r !== null),\n ]\n if (parts.length === 0) return null\n return mergeAggregates(parts)\n }\n\n /**\n * D14: framework-derived device-config contribution.\n *\n * `kind === 'live'` — device-config caps contribute nothing to the live\n * aggregate (they hold editable config, not live observables).\n *\n * `ui.kind === 'widget'` — emits a single structural `type:'widget'`\n * section; the widget self-persists via the cap's own mutations.\n *\n * `ui.kind === 'derived-form'` — calls `getOptions`/`getStatus` on the\n * bound provider, runs the registered pure builder, and returns the\n * derived form sections. Returns null when the camera exposes nothing\n * configurable or the provider is not yet registered.\n */\n private async deriveDeviceConfigContribution(\n registry: ICapabilityRegistry,\n def: CapabilityDefinition & { deviceConfig: DeviceConfigSpec },\n entry: DeviceBindingEntry,\n deviceId: number,\n kind: 'settings' | 'live',\n ): Promise<ContributionShape | null> {\n // device-config caps contribute nothing to the LIVE aggregate.\n if (kind === 'live') return null\n const ui = def.deviceConfig.ui\n\n if (ui.kind === 'widget') {\n // Structural section: one `type:'widget'` field. The widget\n // self-persists via the cap's own mutations.\n const section: ContributionShape['sections'][number] = {\n id: `${entry.capName}-widget`,\n title: ui.label,\n tab: ui.tab,\n order: ui.order ?? 0,\n ...(ui.topTab ? { location: 'top-tab' as const } : {}),\n fields: [{\n type: 'widget',\n key: `${entry.capName}Widget`,\n label: ui.label,\n widgetId: ui.widgetId,\n }],\n }\n return tagContribution({ sections: [section] }, entry.capName, entry.providerAddonId, kind)\n }\n\n // ui.kind === 'derived-form'\n const provider = registry.getProviderForDevice<{\n getOptions?: (input: { deviceId: number }) => Promise<unknown>\n getStatus?: (input: { deviceId: number }) => Promise<unknown>\n }>(entry.capName, deviceId)\n if (!provider || typeof provider.getOptions !== 'function') return null\n try {\n const options = await provider.getOptions({ deviceId })\n const status = typeof provider.getStatus === 'function'\n ? await provider.getStatus({ deviceId })\n : null\n const derived = deriveFormContribution(ui.builderId, options, status ?? null)\n if (!derived) return null\n const wire: ContributionShape = {\n ...(derived.tabs ? { tabs: derived.tabs.map((t) => ({ ...t })) } : {}),\n sections: derived.sections.map((s) => ({\n id: s.id,\n title: s.title,\n ...(s.tab !== undefined ? { tab: s.tab } : {}),\n ...(s.order !== undefined ? { order: s.order } : {}),\n ...(s.description !== undefined ? { description: s.description } : {}),\n ...(s.columns !== undefined ? { columns: s.columns } : {}),\n fields: [...s.fields],\n })),\n }\n return tagContribution(wire, entry.capName, entry.providerAddonId, kind)\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n this.ctx.logger.warn('device-config derivation failed', {\n tags: { deviceId, addonId: entry.providerAddonId },\n meta: { capName: entry.capName, error: msg },\n })\n return null\n }\n }\n\n /**\n * Build the device-manager's own contribution to the aggregator — the\n * device identity (id, stableId, addonId, type, online) + the\n * driver-specific config exposed by the device class via\n * `zodEntriesToConfigUI`.\n *\n * Two paths, deliberately symmetric with `getSettingsSchema`:\n *\n * - Hub-local: device's IDevice instance lives in this process'\n * DeviceRegistry, we read config + schema directly by reference.\n * - Cross-process: device lives in a forked worker (RtspCamera on\n * provider-rtsp, ONVIF on provider-onvif, …). We ask the worker's\n * `device-ops.getSettingsSchema` native provider for a wire-\n * serializable ConfigUISchema and merge it in under the same\n * \"Driver Config\" section, so the UI sees the same shape regardless\n * of where the IDevice physically runs.\n *\n * Returns `null` only when the device genuinely doesn't exist anywhere\n * (no hub-local, no persisted ownership, no device-ops native). The\n * aggregator falls back to contributor sections only in that case.\n */\n private async buildBaseDeviceSection(deviceId: number): Promise<ContributionShape | null> {\n const hubRegistry = this.ctx.kernel?.deviceRegistry\n const hubLocal = hubRegistry ? resolveDeviceById(hubRegistry, deviceId) : null\n const stableId = hubLocal?.device.stableId ?? await this.lookupPersistedStableId(deviceId)\n\n // Resolve identity fields regardless of where the device lives.\n // `resolveNativeDeviceOwner` checks hub-local caps, then the push-fed\n // `remoteNativeCaps` cache, then the handshake-fed `HubNodeRegistry` —\n // so every worker-owned device that registered `device-ops` shows up\n // here with its addonId/nodeId.\n const nativeOwner = this.resolveNativeDeviceOwner(deviceId)\n const addonId = hubLocal?.addonId ?? nativeOwner?.addonId ?? null\n if (!hubLocal && !nativeOwner) return null\n\n const identityFields: unknown[] = [\n {\n type: 'text',\n key: '_deviceId',\n label: 'Device ID',\n readonlyField: true,\n value: String(deviceId),\n },\n {\n type: 'text',\n key: '_stableId',\n label: 'Stable ID',\n readonlyField: true,\n value: stableId ?? '',\n },\n {\n type: 'text',\n key: '_addonId',\n label: 'Driver',\n readonlyField: true,\n value: addonId ?? 'unknown',\n },\n ...(hubLocal\n ? [\n {\n type: 'text',\n key: '_type',\n label: 'Type',\n readonlyField: true,\n value: hubLocal.device.type,\n },\n {\n type: 'text',\n key: '_online',\n label: 'Online',\n readonlyField: true,\n value: hubLocal.device.online ? 'yes' : 'no',\n },\n ]\n : []),\n ]\n\n const sections: ContributionShape['sections'] = [\n {\n id: 'device-identity',\n title: 'Identity',\n tab: 'general',\n order: 0,\n fields: identityFields,\n },\n ]\n // Hardware-info metadata is exposed read-only via `getDevice` and\n // rendered by a dedicated UI tab — it's facts, not preferences,\n // so it doesn't belong in the editable settings aggregate.\n\n // Driver config — either built in-process from the live IDevice's Zod\n // schema (hub-local path) or obtained as a pre-built ConfigUISchema\n // from the worker that owns the device (cross-process path).\n //\n // `resolveDriverConfigSchema` now returns a discriminated result so we\n // can distinguish \"no schema\" (none) from \"worker temporarily unreachable\"\n // (unavailable). The latter renders a visible placeholder instead of\n // silently omitting the driver's settings sections.\n const driverResult = await this.resolveDriverConfigSchema(deviceId, hubLocal)\n\n if (driverResult.status === 'ok') {\n for (const section of driverResult.schema.sections) {\n sections.push({\n id: section.id,\n title: section.title,\n tab: section.tab ?? 'general',\n order: section.order ?? 1,\n fields: [...section.fields],\n ...(section.description !== undefined ? { description: section.description } : {}),\n ...(section.columns !== undefined ? { columns: section.columns } : {}),\n })\n }\n } else if (driverResult.status === 'unavailable') {\n // The forked addon worker is mid-restart or briefly disconnected.\n // Surface a read-only info section so the admin UI shows a clear\n // message instead of a silently missing configuration panel.\n sections.push({\n id: 'driver-settings-unavailable',\n title: 'Driver Settings',\n tab: 'general',\n order: 1,\n fields: [\n {\n type: 'info',\n key: 'driver-settings-unavailable-notice',\n label: 'Temporarily unavailable',\n content: 'Driver settings are temporarily unavailable — the addon may be restarting. Refresh the page in a moment.',\n variant: 'warning',\n },\n ],\n })\n }\n // status === 'none' → driver has no settings; nothing to add.\n\n // Propagate driver-declared tab metadata (label + icon + order).\n // Without this the merge step in `mergeAggregates` falls back to\n // auto-derived tabs (raw id as label, generic icon) — UI then shows\n // lowercase `sessions` / `streams` instead of the driver's\n // `Sessions` / `Streams`. First-wins inside mergeAggregates so the\n // driver's declarations beat any later contributor.\n return {\n sections,\n ...(driverResult.status === 'ok' && driverResult.schema.tabs ? { tabs: [...driverResult.schema.tabs] } : {}),\n }\n }\n\n /**\n * Lookup the native owner for `device-ops` on `deviceId` — the native-cap\n * registry (hub-local and remote) is keyed by numeric id.\n */\n private resolveNativeDeviceOwner(deviceId: number): { addonId: string; nodeId: string } | null {\n const local = this.capabilityRegistry?.getNativeAddonId('device-ops', deviceId) ?? null\n if (local) return { addonId: local, nodeId: this.ctx.kernel.localNodeId ?? 'hub' }\n // Check push-fed cache first, then fall through to handshake-fed registry.\n const remote = this.remoteNativeCaps.get(deviceId)?.get('device-ops')\n ?? this.resolveRemoteNativeCapFromRegistry('device-ops', deviceId)\n return remote ? { addonId: remote.addonId, nodeId: remote.nodeId } : null\n }\n\n /**\n * Aggregate `status` across every registered cap for a device.\n *\n * Walks the supplied cap list (or `CAP_NAMES_WITH_STATUS` when\n * omitted), looks up a native provider per cap via the capability\n * registry, calls `provider.getStatus({ deviceId })`, and validates\n * the return against the cap's own `status.schema`. Validation\n * failures log a warning and yield `null` for that cap so the\n * overall aggregate stays usable — a single misbehaving provider\n * must not blank out a device's entire status view.\n *\n * Returned shape is `Record<capName, unknown | null>`; the client-\n * side hook tightens this to `CapStatusTypeMap` via the generated\n * `cap-status-types.ts`.\n */\n private async getDeviceStatusAggregate(input: {\n readonly deviceId: number\n readonly caps?: readonly string[]\n }): Promise<Record<string, unknown | null>> {\n const capNames: readonly string[] = input.caps ?? CAP_NAMES_WITH_STATUS\n const registry = this.capabilityRegistry\n const out: Record<string, unknown | null> = {}\n if (!registry) {\n for (const name of capNames) out[name] = null\n return out\n }\n\n await Promise.all(capNames.map(async (capName) => {\n try {\n const def = registry.getDefinition(capName)\n if (!def?.status) { out[capName] = null; return }\n\n const provider = registry.getProviderForDevice<{\n getStatus?: (input: { deviceId: number }) => Promise<unknown>\n }>(capName, input.deviceId)\n if (!provider || typeof provider.getStatus !== 'function') {\n out[capName] = null\n return\n }\n\n const raw = await provider.getStatus({ deviceId: input.deviceId })\n if (raw == null) { out[capName] = null; return }\n\n // Validate against the cap's own schema — single misbehaving\n // provider must not poison the rest of the aggregate.\n const parsed = def.status.schema.safeParse(raw)\n if (!parsed.success) {\n this.ctx.logger.warn('getDeviceStatusAggregate: provider returned invalid status, dropping', {\n tags: { deviceId: input.deviceId },\n meta: { capName, issues: parsed.error.issues.slice(0, 3) },\n })\n out[capName] = null\n return\n }\n out[capName] = parsed.data\n } catch (err) {\n this.ctx.logger.warn('getDeviceStatusAggregate: provider threw, dropping', {\n tags: { deviceId: input.deviceId },\n meta: { capName, error: errMsg(err) },\n })\n out[capName] = null\n }\n }))\n\n return out\n }\n\n /**\n * Return the driver-specific device-settings contribution. Hub-local\n * devices call `getSettingsUISchema()` directly; forked-worker devices\n * go through the `device-ops.getSettingsSchema` cap method on the\n * numeric-id-keyed native registry.\n *\n * Returns a discriminated result so callers can distinguish three states:\n * 'ok' – schema obtained successfully\n * 'none' – driver genuinely has no settings schema\n * 'unavailable' – worker was unreachable after retries (transient)\n */\n private async resolveDriverConfigSchema(\n deviceId: number,\n hubLocal: { addonId: string; device: IDevice } | null,\n ): Promise<DriverConfigSchemaResult> {\n if (hubLocal) {\n const schema = hubLocal.device.getSettingsUISchema()\n return schema.sections.length === 0\n ? { status: 'none' }\n : { status: 'ok', schema: toWireShape(schema) }\n }\n const ops = this.capabilityRegistry?.getNativeProvider<IDeviceOpsProvider>('device-ops', deviceId)\n if (!ops) return { status: 'none' }\n\n // Attempt the cross-process call, retrying on transient Moleculer\n // errors (service not yet available, node mid-reconnect). The worker\n // typically re-registers within 1-2 s, so two retries at 750 ms\n // intervals give it ~1.5 s of recovery time without blocking the UI\n // for longer than a single render cycle.\n const MAX_RETRIES = 2\n const RETRY_DELAY_MS = 750\n\n let lastErr: unknown\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n if (attempt > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, RETRY_DELAY_MS))\n }\n try {\n const schema = await ops.getSettingsSchema({ deviceId })\n if (!schema) return { status: 'none' }\n const wire = schema as ConfigUISchemaWithValues\n return wire.sections.length === 0\n ? { status: 'none' }\n : { status: 'ok', schema: toWireShape(wire) }\n } catch (err: unknown) {\n lastErr = err\n // Only retry on transient Moleculer errors (retryable flag set by\n // ServiceNotAvailableError, ServiceNotFoundError, BrokerDisconnectedError).\n // A permanent error (e.g. the provider threw intentionally) breaks\n // out of the retry loop immediately.\n const isTransient = isTransientMoleculerError(err)\n if (!isTransient) break\n if (attempt < MAX_RETRIES) {\n this.ctx.logger.debug('cross-process getSettingsSchema transient failure, retrying', {\n tags: { deviceId },\n meta: { attempt: attempt + 1, maxRetries: MAX_RETRIES, error: errMsg(err) },\n })\n }\n }\n }\n\n const msg = errMsg(lastErr)\n this.ctx.logger.warn('cross-process getSettingsSchema failed after retries — driver settings temporarily unavailable', {\n tags: { deviceId },\n meta: { error: msg },\n })\n return { status: 'unavailable' }\n }\n\n async updateDeviceField(input: {\n deviceId: number\n writerCapName: string\n writerAddonId: string\n key: string\n value: unknown\n }): Promise<{ success: true }> {\n // Special-case: the device-manager's own base section tags fields with\n // writer='device-manager'. Route those to `device.config.setAll` via the\n // live registry — the device-manager cap itself does not participate in\n // the binding-enumerated aggregator (it's a hub singleton), so it would\n // otherwise fail the capability lookup below.\n if (input.writerCapName === 'device-manager') {\n // Hub-local fast path — the device instance is in this process.\n const hubRegistry = this.ctx.kernel?.deviceRegistry\n const found = hubRegistry ? resolveDeviceById(hubRegistry, input.deviceId) : null\n if (found) {\n // Always go through `applySettingsPatch` — drivers whose UI shape\n // differs from storage (RtspCamera's flat mainStreamUrl projecting\n // onto a `streams: [{label,url}]` array) reshape here. `BaseDevice`\n // defaults the method to `config.setAll` so drivers that don't\n // reshape keep the old behaviour for free.\n await found.device.applySettingsPatch({ [input.key]: input.value })\n return { success: true as const }\n }\n // Cross-process fallback — device lives in a forked worker. Native\n // registry is numeric-keyed; dispatch through device-ops directly.\n const ops = this.capabilityRegistry?.getNativeProvider<IDeviceOpsProvider>('device-ops', input.deviceId)\n if (!ops) {\n throw new Error(`[device-manager] device \"${input.deviceId}\" not found (no hub-local entry, no device-ops native provider)`)\n }\n await ops.setConfig({ deviceId: input.deviceId, values: { [input.key]: input.value } })\n return { success: true as const }\n }\n\n const registry = this.capabilityRegistry\n if (!registry) {\n throw new Error('[device-manager] updateDeviceField requires capability registry — unavailable on this node')\n }\n\n const def = registry.getDefinition(input.writerCapName)\n\n // D14: device-config caps are framework-routed — they never expose\n // `exposesDeviceSettings`; their save path goes through the archetype.\n if (isDeviceConfigCap(def)) {\n if (def.deviceConfig.ui.kind === 'widget') {\n // Widget fields self-persist; a save arriving here is a no-op.\n return { success: true as const }\n }\n // ui.kind === 'derived-form': route through the cap's setProfile mutation.\n const dcProvider = registry.getProviderForDevice<{\n setProfile: (input: { deviceId: number; profile: StreamProfile; patch: StreamProfilePatch }) => Promise<void>\n }>(input.writerCapName, input.deviceId)\n if (!dcProvider) {\n throw new Error(`[device-manager] no provider for device-config cap \"${input.writerCapName}\" on device ${input.deviceId}`)\n }\n await applyDerivedFormPatch(\n def.deviceConfig.ui.builderId,\n { [input.key]: input.value },\n (profile, patch) => dcProvider.setProfile({ deviceId: input.deviceId, profile, patch }),\n )\n return { success: true as const }\n }\n\n if (!def?.exposesDeviceSettings) {\n throw new Error(`[device-manager] cap \"${input.writerCapName}\" does not expose device settings`)\n }\n\n const provider = this.resolveContributionProvider(registry, def, input.writerCapName, input.writerAddonId, input.deviceId)\n\n await provider.applyDeviceSettingsPatch({\n deviceId: input.deviceId,\n patch: { [input.key]: input.value },\n })\n return { success: true as const }\n }\n\n /**\n * Resolve the `DeviceSettingsContribution` provider that owns a tagged\n * field. System-scoped caps resolve via `getProviderByAddon`; native\n * device-scoped caps (registered per-device via `registerNativeCap`)\n * live in the separate native map and resolve via `getNativeProvider`.\n * Throws only if BOTH lookups miss.\n */\n private resolveContributionProvider(\n registry: ICapabilityRegistry,\n _def: CapabilityDefinition,\n writerCapName: string,\n writerAddonId: string,\n deviceId: number,\n ): DeviceSettingsContribution {\n // D13: one resolver — native or system, hub-local or cross-process.\n // `kind` is no longer a branch; `writerAddonId` is retained only for\n // the diagnostic message.\n const provider = registry.getProviderForDevice<DeviceSettingsContribution>(writerCapName, deviceId)\n if (provider) return provider\n throw new Error(\n `[device-manager] no provider for cap \"${writerCapName}\" (addon \"${writerAddonId}\") on device ${deviceId}`,\n )\n }\n\n /**\n * Batched counterpart of `updateDeviceField`. Groups changes by\n * `(writerCapName, writerAddonId)` so each contributor receives a\n * single `applyDeviceSettingsPatch` with all of its updates merged —\n * avoids N round-trips for simultaneous edits in the same save.\n *\n * Per-provider failures are captured in the `failures[]` output so the\n * admin UI can highlight which sections didn't persist; a failure on\n * one provider does NOT abort the others.\n */\n async updateDeviceFieldsBatch(input: {\n deviceId: number\n changes: ReadonlyArray<{\n writerCapName: string\n writerAddonId: string\n key: string\n value: unknown\n }>\n }): Promise<{ success: true; failures: { writerCapName: string; writerAddonId: string; error: string }[] }> {\n // Group by `(writerCapName, writerAddonId)` so we fire at most one\n // applyDeviceSettingsPatch per contributing provider.\n const groups = new Map<string, { writerCapName: string; writerAddonId: string; patch: Record<string, unknown> }>()\n for (const change of input.changes) {\n const key = `${change.writerCapName}::${change.writerAddonId}`\n const existing = groups.get(key)\n if (existing) {\n existing.patch[change.key] = change.value\n } else {\n groups.set(key, {\n writerCapName: change.writerCapName,\n writerAddonId: change.writerAddonId,\n patch: { [change.key]: change.value },\n })\n }\n }\n\n const failures: { writerCapName: string; writerAddonId: string; error: string }[] = []\n for (const group of groups.values()) {\n try {\n await this.applyGroupPatch(input.deviceId, group)\n } catch (err: unknown) {\n failures.push({\n writerCapName: group.writerCapName,\n writerAddonId: group.writerAddonId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n return { success: true as const, failures }\n }\n\n /** Apply a single grouped patch to the appropriate provider. Mirrors\n * `updateDeviceField` routing (special-case device-manager, else\n * registry lookup). Used by `updateDeviceFieldsBatch`. */\n private async applyGroupPatch(\n deviceId: number,\n group: { writerCapName: string; writerAddonId: string; patch: Record<string, unknown> },\n ): Promise<void> {\n if (group.writerCapName === 'device-manager') {\n // Hub-local fast path (same reshape-aware path used by `updateDeviceField`).\n const hubRegistry = this.ctx.kernel?.deviceRegistry\n const found = hubRegistry ? resolveDeviceById(hubRegistry, deviceId) : null\n if (found) {\n await found.device.applySettingsPatch(group.patch)\n return\n }\n // Cross-process fallback via `device-ops.setConfig` — native registry\n // is numeric-keyed.\n const ops = this.capabilityRegistry?.getNativeProvider<IDeviceOpsProvider>('device-ops', deviceId)\n if (!ops) {\n throw new Error(`[device-manager] device \"${deviceId}\" not found (no hub-local entry, no device-ops native provider)`)\n }\n await ops.setConfig({ deviceId, values: group.patch })\n return\n }\n\n const registry = this.capabilityRegistry\n if (!registry) throw new Error('[device-manager] capability registry unavailable')\n const def = registry.getDefinition(group.writerCapName)\n\n // D14: device-config caps are framework-routed — they never expose\n // `exposesDeviceSettings`; their batch save path mirrors the single-field path.\n if (isDeviceConfigCap(def)) {\n if (def.deviceConfig.ui.kind === 'widget') {\n // Widget fields self-persist; a batch save arriving here is a no-op.\n return\n }\n // ui.kind === 'derived-form': route the merged patch through setProfile once per profile.\n const dcProvider = registry.getProviderForDevice<{\n setProfile: (input: { deviceId: number; profile: StreamProfile; patch: StreamProfilePatch }) => Promise<void>\n }>(group.writerCapName, deviceId)\n if (!dcProvider) {\n throw new Error(`[device-manager] no provider for device-config cap \"${group.writerCapName}\" on device ${deviceId}`)\n }\n await applyDerivedFormPatch(\n def.deviceConfig.ui.builderId,\n group.patch,\n (profile, patch) => dcProvider.setProfile({ deviceId, profile, patch }),\n )\n return\n }\n\n if (!def?.exposesDeviceSettings) {\n throw new Error(`[device-manager] cap \"${group.writerCapName}\" does not expose device settings`)\n }\n const provider = this.resolveContributionProvider(registry, def, group.writerCapName, group.writerAddonId, deviceId)\n await provider.applyDeviceSettingsPatch({ deviceId, patch: group.patch })\n }\n\n async listWrappersForCap(input: { capName: string }): Promise<string[]> {\n return [...(this.capabilityRegistry?.getWrappersForCap(input.capName) ?? [])]\n }\n\n async listBindableCapsForDeviceType(input: { deviceType: string }): Promise<Array<{ capName: string; wrappers: string[] }>> {\n const registry = this.capabilityRegistry\n if (!registry) return []\n const capNames = registry.listDeviceScopedCapsForType(input.deviceType)\n return capNames.map(capName => ({\n capName,\n wrappers: [...registry.getWrappersForCap(capName)],\n }))\n }\n\n async setWrapperActive(input: {\n deviceId: number\n capName: string\n wrapperAddonId: string\n active: boolean\n }): Promise<void> {\n const storeKey = String(input.deviceId)\n const store = await this.readBindingsStore()\n const perDevice = { ...(store.deviceBindings[storeKey] ?? {}) }\n if (input.active) {\n perDevice[input.capName] = { wrapperAddonId: input.wrapperAddonId }\n } else {\n // Explicit \"operator disabled\" marker. Persisting {wrapperAddonId:null}\n // (instead of deleting) prevents getBindings step 4 from re-binding\n // the defaultActive wrapper after a toggle-off. When a native provider\n // exists, the null marker still surfaces as `kind:'native'` (the revert\n // path the old delete branch was optimising for); when no native is\n // registered, the cap stays truly unbound for this device.\n perDevice[input.capName] = { wrapperAddonId: null }\n }\n const nextDeviceBindings = Object.keys(perDevice).length > 0\n ? { ...store.deviceBindings, [storeKey]: perDevice }\n : (() => { const { [storeKey]: _drop, ...rest } = store.deviceBindings; return rest })()\n await this.writeBindingsStore({ deviceBindings: nextDeviceBindings })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: this.ctx.id },\n category: EventCategory.DeviceBindingsChanged,\n data: {\n deviceId: input.deviceId,\n capName: input.capName,\n reason: input.active ? 'wrapper-activated' : 'wrapper-deactivated',\n addonId: input.wrapperAddonId,\n nodeId: this.resolveWrapperNodeId(input.wrapperAddonId),\n },\n })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[] | void> {\n const settings = this.ctx.settings\n if (!settings) {\n this.ctx.logger.warn('ctx.settings not available — device persistence unavailable')\n return\n }\n\n const registry = this.ctx.kernel.deviceRegistry ?? null\n\n if (!registry) {\n this.ctx.logger.warn('device-registry not available — live operations will use persisted data only')\n }\n\n // Cross-process device-ops dispatch. Forked-worker addons (e.g.\n // provider-rtsp) register devices whose live IDevice instance lives in\n // the worker's own DeviceRegistry — invisible to this hub-side\n // `registry`. Instead of a bespoke Moleculer bridge, every worker\n // auto-registers a `device-ops` NATIVE capability provider per device\n // (see createBrokerDeviceManagerApi.register). Hub consumers reach it\n // via `capabilityRegistry.getNativeProvider('device-ops', deviceId)`:\n // the CapabilityRegistry's native-fallback builds a typed proxy that\n // routes the call to the owning worker through the standard native-cap\n // bridge — same plumbing every other per-device cap (snapshot, events,\n // ptz, …) already uses.\n\n // Cross-process native-cap discovery (push path). Workers emit\n // `DeviceBindingsChanged` on `ctx.registerNativeCap` / device removal;\n // we mirror those into `remoteNativeCaps` so `getBindings` returns the\n // full cluster view. Events from the local node are ignored: hub-local\n // natives live in `capabilityRegistry` and are folded in directly by\n // getBindings.\n //\n // Push events are accurate in the steady state but can be lost during\n // the Moleculer transport handshake window (hub restart, crash-respawn,\n // restartAddon). The reliable replacement for lost events is the D3\n // re-handshake: after device restore completes, the worker sends\n // `$hub.registerNode` with a populated `nativeCaps` field, and\n // `MoleculerService.listClusterNativeCaps()` surfaces it synchronously\n // via `ctx.kernel.listClusterNativeCaps`. Both `resolveNativeCapOwnerSync`\n // and `getBindings` step 4 fall through to that accessor on a push miss.\n const localNodeId = this.ctx.kernel.localNodeId ?? 'hub'\n this.ctx.eventBus.subscribe(\n { category: EventCategory.DeviceBindingsChanged },\n (event) => {\n const { deviceId, capName, reason, addonId, nodeId } = event.data\n if (nodeId === localNodeId) return\n if (reason === 'native-registered') {\n let perDevice = this.remoteNativeCaps.get(deviceId)\n if (!perDevice) {\n perDevice = new Map()\n this.remoteNativeCaps.set(deviceId, perDevice)\n }\n perDevice.set(capName, { addonId, nodeId })\n } else if (reason === 'native-unregistered') {\n const perDevice = this.remoteNativeCaps.get(deviceId)\n if (!perDevice) return\n perDevice.delete(capName)\n if (perDevice.size === 0) this.remoteNativeCaps.delete(deviceId)\n }\n },\n )\n\n // Purge the push-fed `remoteNativeCaps` cache for a node that just\n // disconnected. The worker will re-handshake (and re-emit\n // `native-registered`) on its next boot; the HubNodeRegistry\n // (`listClusterNativeCaps`) handles its own cleanup via\n // `MoleculerService.removeNodeFromRegistry → nodeRegistry.removeNode`.\n const cluster = this.ctx.kernel.cluster\n if (cluster) {\n cluster.broker.localBus.on('$node.disconnected', (payload) => {\n const gone = payload.node.id\n const emptyDevices: number[] = []\n for (const [deviceId, perDevice] of this.remoteNativeCaps) {\n const toDelete: string[] = []\n for (const [capName, entry] of perDevice) {\n if (entry.nodeId === gone) toDelete.push(capName)\n }\n for (const capName of toDelete) perDevice.delete(capName)\n if (perDevice.size === 0) emptyDevices.push(deviceId)\n }\n for (const deviceId of emptyDevices) this.remoteNativeCaps.delete(deviceId)\n })\n }\n // Resolve the per-device `device-ops` native provider. Works hub-local\n // (direct CapabilityRegistry lookup) and cross-process (the native-cap\n // fallback builds a proxy that routes to the owning worker). Consumers\n // that previously spoke the `<addonId>.device-ops.device.<method>`\n // Moleculer action go through this path instead — no bridge, no\n // parallel transport, just the standard native-cap dispatch.\n const requireDeviceOps = (deviceId: number): IDeviceOpsProvider => {\n const ops = this.capabilityRegistry?.getNativeProvider<IDeviceOpsProvider>('device-ops', deviceId)\n if (!ops) {\n throw new Error(`[device-manager] device-ops native provider not found for '${deviceId}'`)\n }\n return ops\n }\n\n // ── Helpers ──────────────────────────────────────────────────────────────\n\n const readStore = async (): Promise<AddonStore> => {\n return (await settings.readAddonStore()) as AddonStore\n }\n\n const readIndex = async (): Promise<Record<string, string[]>> => {\n const store = await readStore()\n return store.deviceIndex ?? {}\n }\n\n const readMeta = async (): Promise<Record<string, PersistedDeviceMeta>> => {\n const store = await readStore()\n return store.deviceMeta ?? {}\n }\n\n /** Hardware-identity metadata map. Lives in a sibling key on the\n * device-manager addon store so its writers (`setMetadata`) never\n * collide with the lifecycle writers on `deviceMeta`\n * (`registerDevice` / `setName` / `setLocation` / `setDisabled`).\n * Single-writer per row eliminates the \"writer X clobbers writer\n * Y's field\" bug class — `setMetadata` is the only producer. */\n const readMetadataMap = async (): Promise<Record<string, Record<string, unknown>>> => {\n const store = await readStore()\n return store.deviceMetadata ?? {}\n }\n\n // Serialize every read-modify-write of the deviceMeta / deviceIndex\n // blob through a single promise chain. Without this, concurrent\n // setMetadata / setName / setLocation / setDisabled callers race:\n // each reads the same `allMeta` snapshot, patches its own row,\n // then writes back the FULL blob — last writer wins, prior patches\n // are silently lost. The race fired on every addon restart when 5\n // Reolink cameras populate metadata in parallel and only the last\n // write survives. The mutex queues writes; reads in between still\n // hit `readStore()` directly (no penalty for hot read paths).\n let metaWriteChain: Promise<unknown> = Promise.resolve()\n const withMetaWriteLock = async <T>(fn: () => Promise<T>): Promise<T> => {\n const previous = metaWriteChain\n let release: () => void = () => {}\n const next = new Promise<void>((resolve) => { release = resolve })\n metaWriteChain = next\n try {\n await previous.catch(() => { /* swallow — keep chain alive */ })\n return await fn()\n } finally {\n release()\n }\n }\n\n /**\n * Resolve a numeric deviceId to the owning `(addonId, stableId)` pair.\n * Scans persisted meta — live IDevice lookup (hub registry) is handled\n * separately per call site so callers can decide whether to route to\n * an in-process driver or to the cross-process `device-ops` bridge.\n * Returns null when no device with that id is known to the hub.\n */\n const resolvePersistedById = async (\n deviceId: number,\n ): Promise<{ addonId: string; stableId: string; meta: PersistedDeviceMeta } | null> => {\n const meta = await readMeta()\n for (const [key, m] of Object.entries(meta)) {\n if (m.id === deviceId) {\n const sep = key.indexOf(':')\n if (sep < 0) continue\n return { addonId: key.slice(0, sep), stableId: key.slice(sep + 1), meta: m }\n }\n }\n return null\n }\n\n // ── Provider implementation ───────────────────────────────────────────────\n\n // Synchronous ownership cache, keyed by NUMERIC deviceId now that the\n // hub allocates ids before construction. The persisted meta store is\n // authoritative but reads are async; hub-side callers (e.g.\n // `CapabilityRegistry.getNativeProvider` fallback) need ownership\n // without awaiting. We keep this map in sync with every\n // registerDevice/removeDevice the provider serves, and warm it from\n // persistence on boot.\n const idToAddonId = new Map<number, string>()\n {\n const meta = await readMeta()\n for (const [key, m] of Object.entries(meta)) {\n const sep = key.indexOf(':')\n if (sep < 0) continue\n idToAddonId.set(m.id, key.slice(0, sep))\n }\n }\n\n // D3 note: The previous pull-based recovery triggers (`$node.connected` →\n // `syncWorkerNativeCaps` and `AddonRestarted` → `syncWorkerNativeCaps`)\n // have been removed in Task 13. Native caps lost in the Moleculer\n // transport handshake window are recovered lazily: when\n // `resolveNativeCapOwnerSync` / `getBindings` step 4 miss in\n // `remoteNativeCaps`, they fall through to `ctx.kernel.listClusterNativeCaps()`\n // which reads the handshake-fed `HubNodeRegistry` (populated by the D3\n // re-handshake that fires after each worker's device restore completes).\n\n const allocateNextDeviceId = async (): Promise<number> => {\n // Read-modify-write under the same settings writer the rest of the\n // addon uses — sqlite-settings serializes writes per addon, so two\n // concurrent registerDevice calls are ordered and each gets a unique\n // id. No in-memory counter: the store is the single source of truth.\n const store = await readStore()\n const current = store.nextDeviceId ?? 1\n await settings.writeAddonStore({ nextDeviceId: current + 1 })\n return current\n }\n\n const provider: IDeviceManagerProvider & {\n resolveDeviceOwnerSync: (deviceId: number) => string | null\n resolveNativeCapOwnerSync: (capName: string, deviceId: number) => { addonId: string; nodeId: string } | null\n } = {\n\n /** Sync ownership lookup backing persistence fallbacks (e.g. remove()\n * when the owning worker is offline). Ownership is keyed by numeric\n * deviceId → owning addonId as recorded in the persisted meta. NOT\n * a native-cap lookup: an addon can own a device without registering\n * every possible cap natively (e.g. RtspCamera without snapshotUrl\n * doesn't register the snapshot cap). Use\n * `resolveNativeCapOwnerSync` for cap-resolution paths.\n */\n resolveDeviceOwnerSync: (deviceId: number): string | null => {\n return idToAddonId.get(deviceId) ?? null\n },\n\n /** Sync lookup for the addon that registered a native provider for\n * `(capName, deviceId)`. Backs `CapabilityRegistry`'s native fallback\n * so the hub only synthesizes a cross-process proxy when the cap is\n * actually published — never on speculative device ownership.\n *\n * Resolution order:\n * 1. Hub-local `capabilityRegistry` (in-process natives — fastest path).\n * 2. Push-fed `remoteNativeCaps` cache (`DeviceBindingsChanged` events\n * from forked workers — accurate in steady state).\n * 3. Handshake-fed `HubNodeRegistry` via `listClusterNativeCaps()`\n * (D3 re-handshake after device restore — covers the Moleculer\n * transport window where push events were lost). This is the\n * reliable replacement for the deleted `syncWorkerNativeCaps` pull.\n */\n resolveNativeCapOwnerSync: (capName: string, deviceId: number): { addonId: string; nodeId: string } | null => {\n const localAddonId = this.capabilityRegistry?.getNativeAddonId(capName, deviceId) ?? null\n if (localAddonId) {\n return { addonId: localAddonId, nodeId: this.ctx.kernel.localNodeId ?? 'hub' }\n }\n const remote = this.remoteNativeCaps.get(deviceId)?.get(capName) ?? null\n if (remote) return { addonId: remote.addonId, nodeId: remote.nodeId }\n // Fall through to the handshake-fed registry for cases where the push\n // event was lost (hub restart, crash-respawn, restartAddon).\n return this.resolveRemoteNativeCapFromRegistry(capName, deviceId)\n },\n\n // ── Persistence ────────────────────────────────────────────────────────\n\n /** Idempotent numeric-id reservation. Callers invoke this before\n * constructing the owning `IDevice` so `DeviceContext.id` is bound\n * at construction time. A repeat call for the same `(addonId,\n * stableId)` returns the already-persisted id — same physical\n * device reconnecting after a driver restart keeps its original\n * number. Fresh pairs burn one slot from the monotonic\n * `nextDeviceId` counter and seed a meta placeholder so the\n * `deviceMeta` → `id` invariant holds even before\n * `registerDevice` completes. */\n allocateDeviceId: async (input) => {\n const { addonId, stableId } = input\n const key = deviceKey(addonId, stableId)\n return await withMetaWriteLock(async () => {\n const meta = await readMeta()\n const existing = meta[key]\n if (existing) return { id: existing.id }\n const id = await allocateNextDeviceId()\n await settings.writeAddonStore({\n deviceMeta: {\n ...meta,\n [key]: {\n type: 'generic',\n name: stableId,\n location: null,\n disabled: false,\n parentDeviceId: null,\n id,\n },\n },\n })\n return { id }\n })\n },\n\n registerDevice: async (input) => {\n const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input\n const key = deviceKey(addonId, stableId)\n\n const featuresArr = Array.isArray(features) ? [...features] : []\n // Index update + meta write together under the lock so a\n // concurrent registerDevice for a different (addonId, stableId)\n // can't read/clobber our deviceMeta snapshot.\n const { isFirstRegistration } = await withMetaWriteLock(async () => {\n const index = await readIndex()\n const existing = index[addonId] ?? []\n const wasInIndex = existing.includes(stableId)\n if (!wasInIndex) {\n await settings.writeAddonStore({\n deviceIndex: { ...index, [addonId]: [...existing, stableId] },\n })\n }\n\n // Persist the full meta record. The kernel's `create()`\n // shortcut writes operator-supplied `name` + `location`\n // BEFORE construction via `setName` / `setLocation` actions,\n // so by the time we land here the placeholder name is already\n // overwritten. Preserve every operator-edited field; the\n // `name` arg coming through this action is the device's\n // construction-time `device.name` which already reflects\n // whatever the meta row carries (`BaseDevice` reads from\n // `ctx.deviceMeta.name`).\n const meta = await readMeta()\n const existingMeta = meta[key]\n // First registration vs subsequent meta refresh (e.g. post-probe\n // features update). Drives whether we emit a `DeviceRegistered`\n // event below — listeners (ReolinkHub discovery refresh, etc)\n // must not re-fire on every meta update or downstream work\n // multiplies.\n const isFirst = !existingMeta || !wasInIndex\n await settings.writeAddonStore({\n deviceMeta: {\n ...meta,\n [key]: {\n type,\n // Prefer the persisted name (set by `setName` pre-construct)\n // over the construction-time fallback. Only when the meta\n // row literally still carries `name === stableId` (the\n // placeholder seeded by `allocateDeviceId`) do we fall\n // back to the action arg.\n name: existingMeta && existingMeta.name !== stableId ? existingMeta.name : name,\n location: existingMeta?.location ?? null,\n disabled: existingMeta?.disabled ?? false,\n parentDeviceId,\n id,\n features: featuresArr,\n },\n },\n })\n return { isFirstRegistration: isFirst }\n })\n\n // Config is pre-persisted by the kernel's `create()` shortcut\n // via `persistConfig` BEFORE construction. We still accept a\n // non-empty `config` payload here for back-compat with paths\n // that haven't migrated, and write it through — but the\n // canonical CREATE path passes `config: {}` and the persisted\n // row stays untouched.\n if (Object.keys(config).length > 0) {\n await settings.writeDeviceStore(id, config)\n }\n\n idToAddonId.set(id, addonId)\n\n // Emit `DeviceRegistered` ONLY on first registration. Subsequent\n // calls (driver re-publishing post-probe features, accessory\n // count update, …) are silent meta refreshes — listeners that\n // care about the device existing already saw the first event.\n // Skipping the duplicate prevents listeners like ReolinkHub from\n // burning a discovery-refresh roundtrip per re-register.\n if (isFirstRegistration) {\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id },\n category: EventCategory.DeviceRegistered,\n data: {\n deviceId: id,\n name: (name.length > 0 ? name : stableId),\n providerId: addonId,\n parentDeviceId: parentDeviceId ?? null,\n },\n })\n } else {\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id },\n category: EventCategory.DeviceMetaChanged,\n data: {\n deviceId: id,\n name: (name.length > 0 ? name : stableId),\n providerId: addonId,\n parentDeviceId: parentDeviceId ?? null,\n features: featuresArr,\n },\n })\n }\n },\n\n removeDevice: async (input) => {\n const { deviceId } = input\n // Resolve `(addonId, stableId)` from the meta store. The cap input\n // only carries the numeric id now — internal cleanup still needs\n // the composite key for index/meta storage rows (those are the\n // external-linkage rows we deliberately keep stableId in).\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) {\n // Removal of an unknown device is a no-op (idempotent) — the\n // caller may be racing teardown after a crash. No throw: the\n // hub-level `remove()` already error-checks first.\n return\n }\n const { addonId, stableId, meta: persistedMeta } = persisted\n const key = deviceKey(addonId, stableId)\n const deviceName = persistedMeta.name\n\n await withMetaWriteLock(async () => {\n // Update index + meta + hardware-metadata together under the\n // lock so we can't lose a concurrent register's freshly-persisted\n // row by reading a pre-write snapshot of `meta`.\n const index = await readIndex()\n const remaining = (index[addonId] ?? []).filter(sid => sid !== stableId)\n const updatedIndex = remaining.length > 0\n ? { ...index, [addonId]: remaining }\n : (() => { const { [addonId]: _removed, ...rest } = index; return rest })()\n await settings.writeAddonStore({ deviceIndex: updatedIndex })\n\n const meta = await readMeta()\n const { [key]: _removedMeta, ...restMeta } = meta\n await settings.writeAddonStore({ deviceMeta: restMeta })\n\n const map = await readMetadataMap()\n if (key in map) {\n const { [key]: _removedMetadata, ...restMap } = map\n await settings.writeAddonStore({ deviceMetadata: restMap })\n }\n })\n\n // Delete all per-device config rows so no orphaned settings remain after removal.\n await settings.clearDeviceStore(deviceId)\n\n // Drop any persisted wrapper activations tied to this device so\n // `getBindings({deviceId})` stops returning stale entries after remove.\n const bindingsStore = await this.readBindingsStore()\n const bindingKey = String(deviceId)\n if (bindingsStore.deviceBindings[bindingKey]) {\n const { [bindingKey]: _removedBindings, ...restBindings } = bindingsStore.deviceBindings\n await this.writeBindingsStore({ deviceBindings: restBindings })\n }\n\n // Drop the push-fed `remoteNativeCaps` cache entry for this device.\n // The HubNodeRegistry (`listClusterNativeCaps`) will be cleaned up\n // automatically when the worker re-handshakes without the device.\n this.remoteNativeCaps.delete(deviceId)\n // Hub-local natives live in the CapabilityRegistry — clear them too\n // so in-process devices don't linger in `getNativeCapsForDevice`.\n this.capabilityRegistry?.unregisterAllNativeForDevice(deviceId)\n\n idToAddonId.delete(deviceId)\n\n this.ctx.logger.info('removed device', { tags: { deviceId, deviceName: deviceName.length > 0 ? deviceName : stableId } })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceUnregistered,\n data: {\n deviceId,\n providerId: addonId,\n // Carry parentDeviceId so listeners can filter on \"is\n // this MY child?\" without consulting an in-memory map\n // they had to keep in sync. By the time this event\n // fires the meta row is already gone — we read it from\n // the resolved snapshot above.\n parentDeviceId: persistedMeta.parentDeviceId ?? null,\n },\n })\n },\n\n persistConfig: async (input) => {\n const { deviceId, data } = input\n // Per-device config is keyed by numeric id end-to-end. We still\n // verify the device is known (resolves through meta) so a write\n // for a non-existent id throws instead of silently allocating a\n // stray store entry.\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] persistConfig: unknown device id=${deviceId}`)\n await settings.writeDeviceStore(deviceId, data)\n },\n\n loadConfig: async (input) => {\n const { deviceId } = input\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return {}\n return settings.readDeviceStore(deviceId)\n },\n\n /**\n * Load the operator-organisational meta surface for one device\n * (`name` / `location` / `disabled` / `type` / `parentDeviceId`\n * / `addonId` + `id` / `stableId`). Used by the kernel proxy's\n * device-context factory to populate `ctx.deviceMeta` before\n * the device class constructor runs. Returns `null` when no\n * persisted row exists for the id.\n *\n * Reads default `location` to `null` and `disabled` to `false`\n * for legacy rows that predate the field — production code\n * relies on the IDevice type contract that both are present.\n */\n loadMeta: async (input) => {\n const { deviceId } = input\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return null\n const { addonId, stableId, meta: m } = persisted\n const key = deviceKey(addonId, stableId)\n const map = await readMetadataMap()\n const metadata = map[key] ?? null\n return {\n id: m.id,\n stableId,\n addonId,\n type: m.type as DeviceType,\n name: m.name,\n location: m.location ?? null,\n disabled: m.disabled ?? false,\n parentDeviceId: m.parentDeviceId,\n metadata,\n }\n },\n\n /**\n * Update the operator-edited display name. Writes the meta\n * row, emits a `DeviceMetaChanged` event so live consumers\n * (UI device list, alert center) see the rename without\n * polling. The live `IDevice.name` mirror is updated by the\n * kernel proxy on its side (`device-cap-proxy.ts`).\n */\n setName: async (input) => {\n const { deviceId, name } = input\n await withMetaWriteLock(async () => {\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setName: unknown device id=${deviceId}`)\n const { addonId, stableId, meta: m } = persisted\n const key = deviceKey(addonId, stableId)\n const allMeta = await readMeta()\n await settings.writeAddonStore({\n deviceMeta: { ...allMeta, [key]: { ...m, name } },\n })\n })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'name', value: name },\n })\n },\n\n /**\n * Update the operator-organisational location label. `null`\n * clears it. Mirrors the same persist-then-emit shape as\n * `setName`; consumers subscribe to `DeviceMetaChanged` and\n * filter on `field: 'location'`.\n */\n setLocation: async (input) => {\n const { deviceId, location } = input\n await withMetaWriteLock(async () => {\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setLocation: unknown device id=${deviceId}`)\n const { addonId, stableId, meta: m } = persisted\n const key = deviceKey(addonId, stableId)\n const allMeta = await readMeta()\n await settings.writeAddonStore({\n deviceMeta: { ...allMeta, [key]: { ...m, location } },\n })\n })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'location', value: location },\n })\n },\n\n /**\n * Patch the device's hardware-identity metadata blob. Shallow\n * merge — `null` removes a key, anything else overwrites.\n * Drivers populate factual fields on first probe; operators\n * augment via the Device Info tab. Idempotent: a no-op patch\n * (every key already present with the same value) doesn't emit\n * the meta-changed event.\n */\n setMetadata: async (input) => {\n const { deviceId, patch } = input\n const result = await withMetaWriteLock(async () => {\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setMetadata: unknown device id=${deviceId}`)\n const { addonId, stableId } = persisted\n const key = deviceKey(addonId, stableId)\n const map = await readMetadataMap()\n const current = (map[key] ?? {}) as Record<string, unknown>\n const next: Record<string, unknown> = { ...current }\n let changed = false\n for (const [k, v] of Object.entries(patch)) {\n if (v === null) {\n if (k in next) {\n delete next[k]\n changed = true\n }\n } else if (next[k] !== v) {\n next[k] = v\n changed = true\n }\n }\n if (!changed) return { changed: false as const }\n const hasFields = Object.keys(next).length > 0\n const updatedMap = { ...map }\n if (hasFields) {\n updatedMap[key] = next\n } else {\n delete updatedMap[key]\n }\n await settings.writeAddonStore({ deviceMetadata: updatedMap })\n return { changed: true as const, finalMeta: hasFields ? next : null }\n })\n if (!result.changed) return\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'metadata', value: result.finalMeta },\n })\n },\n\n /**\n * Soft-disable the device. Persisted on the meta row;\n * lifecycle gating is the driver's responsibility (BaseDevice\n * exposes `this.disabled` for the driver to consult at the top\n * of its lifecycle methods).\n */\n setDisabled: async (input) => {\n const { deviceId, disabled } = input\n await withMetaWriteLock(async () => {\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setDisabled: unknown device id=${deviceId}`)\n const { addonId, stableId, meta: m } = persisted\n const key = deviceKey(addonId, stableId)\n const allMeta = await readMeta()\n await settings.writeAddonStore({\n deviceMeta: { ...allMeta, [key]: { ...m, disabled } },\n })\n })\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'disabled', value: disabled },\n })\n },\n\n loadRuntimeState: async (input) => {\n const { deviceId } = input\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return {}\n const data = await settings.readDeviceRuntimeState(deviceId)\n // Seed the mirror at first load so the hub knows about every\n // existing slice immediately — providers might never call\n // `setCapSlice` if their runtime state doesn't change, and\n // the mirror would otherwise stay empty until the first\n // change.\n this.seedMirror(deviceId, data)\n return data\n },\n\n /**\n * Union of (1) operator-curated location registry and (2) labels\n * currently in use on persisted devices. Case-insensitive\n * dedupe (preserves the first-seen casing). Sorted\n * case-insensitively for stable UI. Drives the Device Info\n * location autocomplete.\n */\n listLocations: async () => {\n const store = (await settings.readAddonStore()) as AddonStore\n const meta = store.deviceMeta ?? {}\n const registry = store.locations ?? []\n // Map from lower-cased key to the first-seen casing so duplicate\n // entries with different cases collapse to one.\n const seen = new Map<string, string>()\n const consider = (raw: unknown): void => {\n if (typeof raw !== 'string') return\n const trimmed = raw.trim()\n if (trimmed.length === 0) return\n const key = trimmed.toLowerCase()\n if (!seen.has(key)) seen.set(key, trimmed)\n }\n for (const label of registry) consider(label)\n for (const m of Object.values(meta)) consider(m.location)\n return [...seen.values()].sort((a, b) =>\n a.localeCompare(b, undefined, { sensitivity: 'base' }),\n )\n },\n\n /**\n * Add a label to the curated location registry. Idempotent:\n * existing entries (case-insensitive match) are silently kept.\n * Empty / whitespace-only inputs throw — operators must supply a\n * meaningful label.\n */\n addLocation: async (input) => {\n const trimmed = input.name.trim()\n if (trimmed.length === 0) {\n throw new Error('[device-manager] addLocation: name must be non-empty')\n }\n const store = (await settings.readAddonStore()) as AddonStore\n const current = store.locations ?? []\n if (current.some((l) => l.toLowerCase() === trimmed.toLowerCase())) {\n // Already present — no-op (preserves first-seen casing).\n return\n }\n await settings.writeAddonStore({ locations: [...current, trimmed] })\n },\n\n /**\n * Remove a label from the curated registry. Match is\n * case-insensitive. Devices that still reference this label keep\n * their `meta.location` value (the registry is a suggestion\n * list, not a foreign key) — pass `cascade: true` to also clear\n * `setLocation` on every device that referenced this exact\n * label. Cascade only matches case-insensitively + trimmed, same\n * as the registry equality check.\n */\n removeLocation: async (input) => {\n const trimmed = input.name.trim()\n if (trimmed.length === 0) return\n const store = (await settings.readAddonStore()) as AddonStore\n const current = store.locations ?? []\n const remaining = current.filter((l) => l.toLowerCase() !== trimmed.toLowerCase())\n if (remaining.length !== current.length) {\n await settings.writeAddonStore({ locations: remaining })\n }\n if (input.cascade !== true) return\n\n const meta = store.deviceMeta ?? {}\n const updates: Record<string, PersistedDeviceMeta> = { ...meta }\n const cleared: number[] = []\n for (const [key, m] of Object.entries(meta)) {\n if (typeof m.location !== 'string') continue\n if (m.location.trim().toLowerCase() !== trimmed.toLowerCase()) continue\n updates[key] = { ...m, location: null }\n cleared.push(m.id)\n }\n if (cleared.length === 0) return\n await settings.writeAddonStore({ deviceMeta: updates })\n for (const deviceId of cleared) {\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceMetaChanged,\n data: { deviceId, field: 'location', value: null },\n })\n }\n },\n\n listPersistedByAddon: async (input) => {\n const { addonId } = input\n const [index, meta] = await Promise.all([readIndex(), readMeta()])\n const stableIds = index[addonId] ?? []\n return stableIds.map(stableId => {\n const m = meta[deviceKey(addonId, stableId)]!\n return {\n id: m.id,\n stableId,\n type: m.type,\n name: m.name,\n location: m.location ?? null,\n disabled: m.disabled ?? false,\n parentDeviceId: m.parentDeviceId,\n }\n })\n },\n\n // ── Live queries — serve from live registry when available ────────────\n\n listAll: async (input) => {\n const { addonId } = input\n\n // Union of two sources, de-duped on (addonId, stableId):\n // 1. Hub-process live registry: IDevice instances owned by this\n // process (hub-resident addons). Provides live `online`,\n // features, and isCamera.\n // 2. Persisted index (settings): authoritative cross-process ledger\n // including devices owned by forked workers, whose IDevice\n // instance lives in the worker's registry and is invisible to\n // the hub-registry.\n //\n // Live wins on the overlap, so hub-owned devices keep their accurate\n // online/features. Forked-worker devices are reconstructed from\n // persisted metadata + persisted config.\n const results: ReturnType<typeof toDeviceInfo>[] = []\n const seen = new Set<string>()\n\n const meta = await readMeta()\n // Hardware metadata lives in its own row; pre-load once and inject\n // into every result regardless of branch.\n const metadataMap = await readMetadataMap()\n\n if (registry) {\n const liveEntries = addonId\n ? registry.getAllForAddon(addonId).map(device => ({ addonId: addonId!, device }))\n : registry.getAllWithAddonId()\n for (const { addonId: aid, device } of liveEntries) {\n const key = deviceKey(aid, device.stableId)\n const metadata = metadataMap[key] ?? null\n const metaRow = meta[key] ?? null\n results.push(toDeviceInfo(aid, device, metadata, metaRow))\n seen.add(key)\n }\n }\n\n const index = await readIndex()\n const targetAddons = addonId ? [addonId] : Object.keys(index)\n for (const aid of targetAddons) {\n for (const stableId of (index[aid] ?? [])) {\n const key = deviceKey(aid, stableId)\n if (seen.has(key)) continue\n const m = meta[key]!\n const persistedType = m.type as DeviceType\n const persistedConfig = (await settings.readDeviceStore(m.id)) as Record<string, unknown>\n const metadata = metadataMap[key] ?? null\n results.push({\n id: m.id,\n stableId,\n addonId: aid,\n type: persistedType,\n name: m?.name ?? stableId,\n location: m?.location ?? null,\n disabled: m?.disabled ?? false,\n parentDeviceId: m?.parentDeviceId ?? null,\n // role is not persisted to disk — only set on the live\n // IDevice (`BaseDevice.role`) by accessory subclasses. The\n // forked-worker fallback path can't read it, so emit null;\n // accessories typically run in the same process as their\n // parent provider, so the live path covers them in practice.\n role: null,\n // registry===null was the legacy \"offline view\" signal; keep it\n // for that case so existing callers see online=false. When a\n // registry is present and the device lives elsewhere (forked\n // worker), default to online=true — persisted state implies it\n // was successfully registered by its owning process.\n online: registry !== null,\n features: persistedFeatures(m?.features),\n // Live IDevice isn't reachable from the hub, so infer\n // isCamera from the persisted DeviceType rather than the\n // method-presence check `toDeviceInfo` uses. Without this\n // forked-worker cameras looked non-camera to the stream-broker\n // and never got their RTSP restream wired up.\n isCamera: persistedType === DeviceType.Camera,\n config: persistedConfig ?? {},\n metadata,\n })\n }\n }\n return results\n },\n\n getDevice: async (input) => {\n const { deviceId } = input\n\n // Prefer live (hub-registry) when present; otherwise fall back to\n // persisted metadata so we also find devices owned by forked workers.\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n const key = deviceKey(found.addonId, found.device.stableId)\n const [map, metaMap] = await Promise.all([readMetadataMap(), readMeta()])\n const metadata = map[key] ?? null\n const metaRow = metaMap[key] ?? null\n return toDeviceInfo(found.addonId, found.device, metadata, metaRow)\n }\n }\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return null\n const { addonId: aid, stableId, meta: m } = persisted\n const persistedConfig = (await settings.readDeviceStore(m.id)) as Record<string, unknown>\n const key = deviceKey(aid, stableId)\n const map = await readMetadataMap()\n const metadata = map[key] ?? null\n // Name now lives exclusively on the meta surface (mutated via\n // `setName` action) — the legacy config.name fallback is gone\n // since drivers no longer carry `name` in their config schema.\n return {\n id: deviceId,\n stableId,\n addonId: aid,\n type: m.type as DeviceType,\n name: m.name,\n location: m.location ?? null,\n disabled: m.disabled ?? false,\n parentDeviceId: m.parentDeviceId,\n role: null,\n online: true,\n features: persistedFeatures(m.features),\n isCamera: false,\n config: persistedConfig ?? {},\n metadata,\n }\n },\n\n getChildren: async (input) => {\n const { parentDeviceId } = input\n\n // Resolve parent ownership so we can scope the persisted-child scan\n // per owning addon. Children are always registered under the same\n // addonId as their parent. The hub registry's getChildren() is a\n // pure numeric filter (`d.parentDeviceId === parentDeviceId`) so\n // we no longer need the parent's stableId for live lookups.\n let ownerAddonId: string | null = null\n if (registry) {\n const liveParent = registry.getById(parentDeviceId)\n if (liveParent) {\n ownerAddonId = registry.getAddonId(parentDeviceId)\n }\n }\n if (!ownerAddonId) {\n const persisted = await resolvePersistedById(parentDeviceId)\n if (!persisted) return []\n ownerAddonId = persisted.addonId\n }\n\n // Same union pattern as listAll: live (hub-registry) wins, persisted\n // fills in devices owned by forked workers.\n const results: ReturnType<typeof toDeviceInfo>[] = []\n const seen = new Set<string>()\n\n const [index, meta, metadataMap] = await Promise.all([readIndex(), readMeta(), readMetadataMap()])\n\n if (registry) {\n const liveChildren = registry.getChildren(parentDeviceId)\n for (const device of liveChildren) {\n const key = deviceKey(ownerAddonId, device.stableId)\n const metadata = metadataMap[key] ?? null\n const metaRow = meta[key] ?? null\n results.push(toDeviceInfo(ownerAddonId, device, metadata, metaRow))\n seen.add(key)\n }\n }\n\n const stableIds = index[ownerAddonId] ?? []\n const persistedChildren = stableIds.filter(\n sid => meta[deviceKey(ownerAddonId!, sid)]?.parentDeviceId === parentDeviceId,\n )\n for (const childStableId of persistedChildren) {\n const key = deviceKey(ownerAddonId, childStableId)\n if (seen.has(key)) continue\n const m = meta[key]!\n const persistedConfig = (await settings.readDeviceStore(m.id)) as Record<string, unknown>\n const metadata = metadataMap[key] ?? null\n results.push({\n id: m.id,\n stableId: childStableId,\n addonId: ownerAddonId,\n type: m.type as DeviceType,\n name: m.name,\n location: m.location ?? null,\n disabled: m.disabled ?? false,\n parentDeviceId,\n role: null,\n online: registry !== null,\n features: persistedFeatures(m.features),\n isCamera: false,\n config: persistedConfig ?? {},\n metadata,\n })\n }\n return results\n },\n\n // ── Live runtime methods ──────────────────────────────────────────────\n\n getStreamSources: async (input) => {\n const { deviceId } = input\n // Hub-local path\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n // Non-camera devices have no streams by definition — return\n // an empty list immediately instead of round-tripping through\n // `device-ops`. Cross-process dispatch is strictly for\n // worker-owned devices, not for wrong-type hub-local ones.\n if (!isCameraDevice(found.device)) return []\n const sources: readonly StreamSourceEntry[] = await found.device.getStreamSources()\n return sources.map(s => ({\n id: s.id,\n label: s.label,\n protocol: s.protocol,\n url: s.url,\n resolution: s.resolution,\n fps: s.fps,\n bitrate: s.bitrate,\n codec: s.codec,\n profileHint: s.profileHint,\n }))\n }\n }\n // Cross-process fallback — dispatch through the `device-ops` native\n // provider, now keyed on the numeric id in both hub and worker.\n // Errors propagate: a transient worker error must NOT masquerade as\n // \"device has no streams\" — callers need the real error.\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const ops = requireDeviceOps(deviceId)\n const sources = await ops.getStreamSources({ deviceId })\n return sources.map(s => ({ ...s }))\n },\n\n getConfigSchema: async (input) => {\n const { deviceId } = input\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n return found.device.config.entries().map(entry => ({\n key: entry.key,\n value: entry.value,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n }))\n }\n }\n // Cross-process fallback. Propagates the real error when the\n // owning worker is offline or the device genuinely doesn't exist —\n // surfacing \"no config\" silently used to let the UI render empty\n // forms for broken devices.\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const ops = requireDeviceOps(deviceId)\n const entries = await ops.getConfigEntries({ deviceId })\n return entries.map(e => ({ ...e }))\n },\n\n getSettingsSchema: async (input) => {\n const { deviceId } = input\n // Single source of truth: every IDevice implements\n // `getSettingsUISchema`. Hub-local devices answer in-process;\n // forked-worker devices go through the `device-ops.getSettingsSchema`\n // native provider which delegates to the same method worker-side.\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n return found.device.getSettingsUISchema()\n }\n }\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) return null\n const ops = requireDeviceOps(deviceId)\n const schema = await ops.getSettingsSchema({ deviceId })\n return (schema as ConfigUISchemaWithValues | null) ?? null\n },\n\n updateConfig: async (input) => {\n const { deviceId } = input\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n await found.device.config.setAll(input.values)\n return { success: true as const }\n }\n }\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const ops = requireDeviceOps(deviceId)\n await ops.setConfig({ deviceId, values: input.values })\n return { success: true as const }\n },\n\n // `enable` / `disable` write to deviceMeta.disabled (the canonical\n // soft-disable flag that drivers consult via `BaseDevice.disabled`\n // at lifecycle entrypoints, and that `toDeviceInfo` projects onto\n // the wire by reading the LATEST persisted meta row — see the\n // `metaRow` argument plumbed through `listAll` / `getDevice` /\n // `getChildren`).\n //\n // Earlier these wrappers wrote `enabled: true/false` into the\n // device CONFIG blob — wrong store entirely. The config write\n // succeeded but the flag the rest of the system reads\n // (deviceMeta) stayed unchanged, so the UI's enable/disable\n // toggle appeared to do nothing. Delegating to `setDisabled`\n // routes through the correct withMetaWriteLock path and emits\n // the `DeviceMetaChanged` event for live invalidation.\n //\n // NB: `BaseDevice.disabled` is `readonly` and bound at\n // construction (base-device.ts:142). Drivers that gate behaviour\n // off `this.disabled` only see the value the device was\n // instantiated with — flipping disabled at runtime updates the\n // wire response immediately, but lifecycle gating inside the\n // driver only takes effect on the next instantiation. That's\n // acceptable today: the operator-facing semantic is \"hide /\n // mark inactive in the UI\"; the heavyweight teardown semantic\n // is `remove`.\n enable: async (input) => {\n await provider.setDisabled({ deviceId: input.deviceId, disabled: false })\n return { success: true as const }\n },\n\n disable: async (input) => {\n await provider.setDisabled({ deviceId: input.deviceId, disabled: true })\n return { success: true as const }\n },\n\n remove: async (input) => {\n const { deviceId } = input\n // (a) Device lives in the hub registry → local teardown, then persistence.\n if (registry) {\n const live = resolveDeviceById(registry, deviceId)\n if (live) {\n const deviceName = live.device.name\n await live.device.removeDevice()\n registry.remove(deviceId)\n await provider.removeDevice({ deviceId })\n this.ctx.logger.info('removed hub-local device', { tags: { deviceId, deviceName } })\n return { success: true as const }\n }\n }\n // (b) Device is owned by a forked worker → invoke its `device-ops`\n // native provider to let the driver release resources, then\n // clear hub persistence.\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const { meta: persistedMeta } = persisted\n try {\n const ops = requireDeviceOps(deviceId)\n await ops.removeDevice({ deviceId })\n } catch (err) {\n // Worker might be offline or missing — still clear persistence so\n // the UI recovers. The warning is best-effort diagnostics.\n this.ctx.logger.warn('remove via device-ops failed — clearing persistence anyway', { tags: { deviceId, deviceName: persistedMeta.name }, meta: { error: errMsg(err) } })\n }\n await provider.removeDevice({ deviceId })\n return { success: true as const }\n },\n\n getStreamProfileMap: async (input) => {\n if (!registry) return {}\n const found = resolveDeviceById(registry, input.deviceId)\n if (!found) return {}\n\n // Check for explicitly stored profile map first\n const storedMap: unknown = found.device.config.entries().find(e => e.key === '_profileMap')?.value\n if (storedMap !== undefined && typeof storedMap === 'object' && storedMap !== null) {\n return storedMap as Record<string, string>\n }\n\n // Derive from profileHint fields on stream sources\n if (!isCameraDevice(found.device)) return {}\n const sources: readonly StreamSourceEntry[] = await found.device.getStreamSources()\n const profileMap: Record<string, string> = {}\n for (const s of sources) {\n if (s.profileHint && s.id) {\n profileMap[s.profileHint] = s.id\n }\n }\n return profileMap\n },\n\n setStreamProfileMap: async (input) => {\n const { deviceId } = input\n if (registry) {\n const found = resolveDeviceById(registry, deviceId)\n if (found) {\n await found.device.config.setAll({ _profileMap: input.profileMap })\n return { success: true as const }\n }\n }\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`)\n const ops = requireDeviceOps(deviceId)\n await ops.setConfig({ deviceId, values: { _profileMap: input.profileMap } })\n return { success: true as const }\n },\n\n probeStreams: async (input) => {\n const streamProbe = this.ctx.kernel.streamProbe\n if (!streamProbe) return []\n\n const sources = await provider.getStreamSources({ deviceId: input.deviceId })\n const results: Array<{\n streamId: string\n width?: number\n height?: number\n codec?: string\n fps?: number\n bitrateKbps?: number\n }> = []\n\n for (const s of sources) {\n if (!s.url) continue\n try {\n const metadata = await streamProbe.probe(s.url, { force: true })\n results.push({\n streamId: s.id,\n width: metadata.width,\n height: metadata.height,\n codec: metadata.codec,\n fps: metadata.fps,\n bitrateKbps: metadata.bitrateKbps,\n })\n } catch (err) {\n this.ctx.logger.debug('streamProbe.probe failed — returning placeholder', {\n meta: {\n deviceId: input.deviceId,\n streamId: s.id,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n results.push({ streamId: s.id })\n }\n }\n\n return results\n },\n\n // ── Device-provider operations (routed via CapabilityRegistry) ───────\n\n discoverDevices: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n\n const supported = await dp.supportsDiscovery({})\n if (!supported) {\n throw new Error(`Addon \"${input.addonId}\" does not support device discovery`)\n }\n\n const discovered = await dp.discoverDevices({})\n return discovered.map((d) => ({\n stableId: d.stableId,\n type: d.type,\n suggestedName: d.suggestedName,\n prefilledConfig: d.prefilledConfig,\n }))\n },\n\n adoptDevice: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n\n const supported = await dp.supportsDiscovery({})\n if (!supported) {\n throw new Error(`Addon \"${input.addonId}\" does not support device adoption`)\n }\n\n return dp.adoptDiscoveredDevice({\n candidate: input.candidate as Parameters<IDeviceProviderCap['adoptDiscoveredDevice']>[0]['candidate'],\n })\n },\n\n getCreationSchema: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n\n const supported = await dp.supportsManualCreation({})\n if (!supported) return null\n\n const schema = await dp.getChildCreationSchema({ type: input.type })\n return schema ?? null\n },\n\n createDevice: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n\n const supported = await dp.supportsManualCreation({})\n if (!supported) {\n throw new Error(`Addon \"${input.addonId}\" does not support manual device creation`)\n }\n\n return dp.createDevice({ type: input.type, config: input.config })\n },\n\n testCreationField: async (input) => {\n const dp = await this.requireDeviceProvider(input.addonId)\n return dp.testCreationField({\n type: input.type,\n key: input.key,\n value: input.value,\n ...(input.formValues !== undefined ? { formValues: input.formValues } : {}),\n })\n },\n\n testField: async (input) => {\n const { deviceId } = input\n let owningAddonId: string | null = null\n if (registry) {\n owningAddonId = registry.getAddonId(deviceId)\n }\n if (!owningAddonId) {\n const persisted = await resolvePersistedById(deviceId)\n owningAddonId = persisted?.addonId ?? null\n }\n if (!owningAddonId) {\n throw new Error(`Device with id ${deviceId} not found`)\n }\n\n const dp = await this.waitDeviceProvider(owningAddonId)\n if (!dp) {\n return { status: 'ok' as const, labels: [], error: undefined }\n }\n\n if (typeof (dp as Record<string, unknown>).testCreationField !== 'function') {\n return { status: 'ok' as const, labels: [], error: undefined }\n }\n\n return dp.testCreationField({ type: DeviceType.Camera, key: input.key, value: input.value })\n },\n\n // ── Bindings — per-device capability provider map ────────────────────\n\n getBindings: async (input) => {\n const result = await this.getBindings({ deviceId: input.deviceId })\n return { deviceId: input.deviceId, entries: result.entries }\n },\n getAllBindings: async () => {\n return this.getAllBindings()\n },\n setWrapperActive: async (input) => {\n return this.setWrapperActive({\n deviceId: input.deviceId,\n capName: input.capName,\n wrapperAddonId: input.wrapperAddonId,\n active: input.active,\n })\n },\n listWrappersForCap: async (input) => this.listWrappersForCap(input),\n listBindableCapsForDeviceType: async (input) => this.listBindableCapsForDeviceType(input),\n\n // ── Device-details aggregators ──────────────────────────────────────\n\n getDeviceSettingsAggregate: async (input) => {\n return this.getDeviceAggregate(input.deviceId, 'settings')\n },\n getDeviceLiveInfoAggregate: async (input) => {\n return this.getDeviceAggregate(input.deviceId, 'live')\n },\n getDeviceAggregate: async (input) => {\n // Run both aggregations in parallel — the live + settings passes\n // enumerate the same provider set but call different methods, so\n // there's no overlap to serialize.\n const [settings, live] = await Promise.all([\n this.getDeviceAggregate(input.deviceId, 'settings'),\n this.getDeviceAggregate(input.deviceId, 'live'),\n ])\n return { settings, live }\n },\n updateDeviceField: async (input) => {\n return this.updateDeviceField({\n deviceId: input.deviceId,\n writerCapName: input.writerCapName,\n writerAddonId: input.writerAddonId,\n key: input.key,\n value: input.value,\n })\n },\n updateDeviceFieldsBatch: async (input) => {\n return this.updateDeviceFieldsBatch({ deviceId: input.deviceId, changes: input.changes })\n },\n getDeviceStatusAggregate: async (input) => this.getDeviceStatusAggregate(input),\n }\n\n this.ctx.logger.info('registered device-manager capability', { meta: { liveRegistry: registry !== null } })\n\n // Fan-out device events to ancestor scopes. Runs only when a live\n // registry is available on this node — forked workers that don't\n // own the registry rely on the hub's propagator (events cross the\n // Moleculer bridge already, propagation is idempotent with the\n // `via` guard so emitting on both sides is safe).\n if (registry) {\n this.propagator = new DeviceEventPropagator({\n eventBus: this.ctx.eventBus,\n getParentOf: (id) => registry.getById(id)?.parentDeviceId ?? null,\n logger: {\n warn: (msg, meta) => this.ctx.logger.warn(msg, meta ?? {}),\n debug: (msg, meta) => this.ctx.logger.debug(msg, meta ?? {}),\n },\n })\n this.propagator.start()\n this.ctx.logger.info('device-event-propagator started')\n }\n\n // `deviceState` cap — generic cross-process surface over the\n // hub mirror. Drivers don't implement this directly; we mount\n // the singleton here so any addon / UI can read any cap's\n // runtime-state slice without the cap having to define its own\n // getStatus / onChanged.\n const deviceStateProvider = {\n getSnapshot: async (input: { deviceId: number }): Promise<Record<string, Record<string, unknown>>> => {\n return this.snapshotForDevice(input.deviceId)\n },\n getCapSlice: async (input: { deviceId: number; capName: string }): Promise<Record<string, unknown> | null> => {\n const perCap = this.stateMirror.get(input.deviceId)\n const slice = perCap?.get(input.capName)\n return slice ? { ...slice } : null\n },\n // Whole-system warm-boot dump. Iterates the mirror once and\n // returns a deeply-cloned `{[deviceId]: {[capName]: slice}}`\n // payload so callers (SystemManager, dashboards) seed their\n // local mirror with one round-trip instead of N per-device\n // calls. Empty inner record for devices with no slices yet.\n getAllSnapshots: async (): Promise<Record<string, Record<string, Record<string, unknown>>>> => {\n const out: Record<string, Record<string, Record<string, unknown>>> = {}\n for (const [deviceId, perCap] of this.stateMirror) {\n const dev: Record<string, Record<string, unknown>> = {}\n for (const [capName, slice] of perCap) dev[capName] = { ...slice }\n out[String(deviceId)] = dev\n }\n return out\n },\n // Canonical cross-layer write — the single entrypoint every\n // caller funnels through. In-process providers reach it via\n // `BaseDevice.runtimeState.setCapState` (which delegates here);\n // system-scope addons (e.g. pipeline runner writing the motion\n // slice on phase transitions) call this cap method directly\n // through `ctx.api.deviceState.setCapSlice.mutate(...)`.\n setCapSlice: async (input: { deviceId: number; capName: string; slice: Record<string, unknown> }): Promise<void> => {\n const { deviceId, capName, slice } = input\n const persisted = await resolvePersistedById(deviceId)\n if (!persisted) throw new Error(`[device-manager] setCapSlice: unknown device id=${deviceId}`)\n // Mirror update + onChanged event are synchronous and ordered;\n // disk write is debounced so frequent writes (motion phase\n // transitions, battery pushes) collapse to one fsync per\n // RUNTIME_STATE_DEBOUNCE_MS window.\n this.applySingleCapUpdate(deviceId, capName, slice)\n this.scheduleRuntimeStateDiskWrite(deviceId, settings)\n },\n }\n\n return [\n { capability: deviceManagerCapability, provider },\n { capability: deviceStateCapability, provider: deviceStateProvider },\n ]\n }\n\n /**\n * Single-cap mirror update — diff against the current mirror,\n * persist the new slice in-memory, emit `DeviceStateChanged` for\n * this cap. No-op on identical writes (both same shape and same\n * values). Called by `setCapSlice` provider.\n */\n private applySingleCapUpdate(\n deviceId: number,\n capName: string,\n slice: Record<string, unknown>,\n ): void {\n let perCap = this.stateMirror.get(deviceId)\n if (!perCap) {\n perCap = new Map()\n this.stateMirror.set(deviceId, perCap)\n }\n const prior = perCap.get(capName)\n if (prior && shallowEqual(prior, slice)) return\n perCap.set(capName, { ...slice })\n this.emitStateChanged(deviceId, capName, slice)\n }\n\n /**\n * Debounced disk writer. Coalesces frequent writes (motion phase\n * transitions, battery pushes) into one `writeDeviceRuntimeState`\n * per `RUNTIME_STATE_DEBOUNCE_MS` window. Reads the per-device\n * blob from the live mirror at flush time so the disk picture is\n * always the latest state — no risk of writing a stale snapshot.\n */\n private scheduleRuntimeStateDiskWrite(\n deviceId: number,\n settings: NonNullable<typeof this.ctx.settings>,\n ): void {\n let slot = this.runtimeStateDebounce.get(deviceId)\n if (!slot) {\n slot = { timer: null, inFlight: null }\n this.runtimeStateDebounce.set(deviceId, slot)\n }\n if (slot.timer) return\n slot.timer = setTimeout(() => {\n slot!.timer = null\n const blob = this.snapshotForDevice(deviceId)\n const write = (async () => {\n try {\n await settings.writeDeviceRuntimeState(deviceId, blob)\n } catch (err: unknown) {\n this.ctx.logger.warn('writeDeviceRuntimeState failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n } finally {\n slot!.inFlight = null\n }\n })()\n slot!.inFlight = write\n }, DeviceManagerAddon.RUNTIME_STATE_DEBOUNCE_MS)\n }\n\n /**\n * One-shot mirror seed used by `loadRuntimeState` at boot so the\n * hub knows about every persisted slice without waiting for the\n * first `setCapSlice` call. No events emitted — this is\n * initial-state population, not a transition.\n */\n private seedMirror(deviceId: number, blob: Record<string, unknown>): void {\n let perCap = this.stateMirror.get(deviceId)\n if (!perCap) {\n perCap = new Map()\n this.stateMirror.set(deviceId, perCap)\n }\n for (const [capName, raw] of Object.entries(blob)) {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) continue\n perCap.set(capName, { ...raw as Record<string, unknown> })\n }\n }\n\n private snapshotForDevice(deviceId: number): Record<string, Record<string, unknown>> {\n const perCap = this.stateMirror.get(deviceId)\n if (!perCap) return {}\n const out: Record<string, Record<string, unknown>> = {}\n for (const [k, v] of perCap) out[k] = { ...v }\n return out\n }\n\n private emitStateChanged(deviceId: number, capName: string, slice: Record<string, unknown>): void {\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'device', id: deviceId },\n category: EventCategory.DeviceStateChanged,\n data: { deviceId, capName, slice },\n })\n }\n\n protected async onShutdown(): Promise<void> {\n this.propagator?.stop()\n this.propagator = null\n // Flush any pending debounced runtime-state disk writes so a\n // graceful shutdown doesn't drop the latest mirror state.\n const settings = this.ctx.settings\n const pending: Promise<void>[] = []\n for (const [deviceId, slot] of this.runtimeStateDebounce) {\n if (slot.timer) {\n clearTimeout(slot.timer)\n slot.timer = null\n if (settings) {\n const blob = this.snapshotForDevice(deviceId)\n pending.push(settings.writeDeviceRuntimeState(deviceId, blob).catch((err: unknown) => {\n this.ctx.logger.warn('shutdown writeDeviceRuntimeState failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }))\n }\n }\n if (slot.inFlight) pending.push(slot.inFlight)\n }\n await Promise.all(pending)\n this.runtimeStateDebounce.clear()\n }\n}\n\nexport default DeviceManagerAddon\n"],"mappings":"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"}
|