@camstack/addon-pipeline 0.1.19 → 0.1.20
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/audio-analyzer/index.js +5 -2
- package/dist/audio-analyzer/index.js.map +1 -1
- package/dist/audio-analyzer/index.mjs +5 -2
- package/dist/audio-analyzer/index.mjs.map +1 -1
- package/dist/audio-codec-nodeav/index.js +1 -1
- package/dist/audio-codec-nodeav/index.mjs +1 -1
- package/dist/decoder-nodeav/index.js +2 -2
- package/dist/decoder-nodeav/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +25 -25
- package/dist/detection-pipeline/index.js.map +1 -1
- package/dist/detection-pipeline/index.mjs +25 -25
- package/dist/detection-pipeline/index.mjs.map +1 -1
- package/dist/{index-Bmlkm0Fd.mjs → index-5aYef068.mjs} +3601 -770
- package/dist/index-5aYef068.mjs.map +1 -0
- package/dist/{index-BbPPvoCx.js → index-B36NMAdu.js} +3577 -746
- package/dist/index-B36NMAdu.js.map +1 -0
- package/dist/{index-D_cl0Qqb.js → index-CMcx_k6Y.js} +48 -48
- package/dist/{index-D_cl0Qqb.js.map → index-CMcx_k6Y.js.map} +1 -1
- package/dist/{index-UbcdLS7a.mjs → index-CYb7cFrv.mjs} +46 -46
- package/dist/{index-UbcdLS7a.mjs.map → index-CYb7cFrv.mjs.map} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +74 -77
- package/dist/pipeline-runner/index.js.map +1 -1
- package/dist/pipeline-runner/index.mjs +74 -77
- package/dist/pipeline-runner/index.mjs.map +1 -1
- package/dist/recorder/index.js +2209 -0
- package/dist/recorder/index.js.map +1 -0
- package/dist/recorder/index.mjs +2209 -0
- package/dist/recorder/index.mjs.map +1 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/FfmpegParamsField.d.ts +41 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/GeometryBuilder.d.ts +54 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/format-ua.d.ts +13 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/index.d.ts +2 -0
- package/dist/stream-broker/@mf-types.zip +0 -0
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-h5aXOPSA.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-lantnv8e.mjs} +1 -1
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-DJ3UNg7O.mjs +30 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-CYXy_bhS.mjs +21 -0
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-BsB2G7oY.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-CaDEYBIU.mjs} +8 -7
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-xrRiPUpA.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-D6EROtlA.mjs} +1 -1
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-gBEZsQrp.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-x6pP3Ghk.mjs} +2 -2
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-C0E2yCzO.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-CcnN6sbA.mjs} +1 -1
- package/dist/stream-broker/_stub.js +963 -333
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CupRlwqG.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CL9DR49k.mjs} +6 -6
- package/dist/stream-broker/{client-NPZqorv9.mjs → client-BvTmMOQu.mjs} +2 -2
- package/dist/stream-broker/{hostInit-Bh4w7o5_.mjs → hostInit-ChmiMPS0.mjs} +12 -12
- package/dist/stream-broker/{index-D_1p2K9B.mjs → index-BxsFuFmE.mjs} +24 -24
- package/dist/stream-broker/{index-mX3Kgiv1.mjs → index-C-248uOU.mjs} +2 -2
- package/dist/stream-broker/{index-2Qp8vT3w.mjs → index-C05B6jqp.mjs} +1 -1
- package/dist/stream-broker/index-DOJoSShD.mjs +67784 -0
- package/dist/stream-broker/{index-Dy2V7VOm.mjs → index-DtOI1aTU.mjs} +10112 -5987
- package/dist/stream-broker/{index-Cc6QBqMk.mjs → index-oMq6ilgR.mjs} +253 -267
- package/dist/stream-broker/{index-BBcZvb5t.mjs → index-vIWZQBIL.mjs} +1 -1
- package/dist/stream-broker/index.js +3168 -543
- package/dist/stream-broker/index.js.map +1 -1
- package/dist/stream-broker/index.mjs +3172 -546
- package/dist/stream-broker/index.mjs.map +1 -1
- package/dist/stream-broker/{jsx-runtime-lb0mH5st.mjs → jsx-runtime-BRT_HL0A.mjs} +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/stream-broker/{schemas-ClCuS4qa.mjs → schemas-B7L0qZtq.mjs} +411 -406
- package/package.json +51 -3
- package/dist/index-BbPPvoCx.js.map +0 -1
- package/dist/index-Bmlkm0Fd.mjs.map +0 -1
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-NjF4kxzW.mjs +0 -19
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BAv_5ISf.mjs +0 -20
- package/dist/stream-broker/index-CIJue-4t.mjs +0 -37880
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../src/pipeline-runner/frame-queue.ts","../../src/pipeline-runner/semaphore.ts","../../src/pipeline-runner/timing-sampler.ts","../../src/pipeline-runner/runner.ts","../../src/pipeline-runner/frame-handle-poller.ts","../../src/pipeline-runner/bench-actions.ts","../../src/pipeline-runner/index.ts"],"sourcesContent":["import type { DecodedFrame } from '@camstack/types'\n\n/**\n * Latest-frame-only buffer. Keeps only the most recent item, dropping all\n * older items immediately. This ensures inference always runs on the\n * freshest available frame, never accumulating a backlog regardless of\n * inference latency.\n *\n * ## Buffer ownership (Phase 5 / D9 Task 7b)\n *\n * Frames arrive from the shm frame plane via a `FrameRingReader`, whose\n * `FrameRead.pixels` is **borrowed** — a view onto the reader's reusable\n * scratch buffer, valid only until that reader's next read (see\n * `@camstack/shm-ring`'s `FrameRead` contract). Both queue consumers retain\n * the frame PAST the next read:\n *\n * - the **motion** queue is drained by the scheduler's `tick`, not by the\n * poller that read the frame — and the poller drains a burst of handles\n * per poll, so even handle #2's read would clobber handle #1's pixels;\n * - the **detection** queue is drained asynchronously after a semaphore wait\n * and a 10s–100s ms inference.\n *\n * In both cases holding the borrowed buffer is silent corruption. So\n * `enqueue` runs the queue's injected `clone` function which copies the\n * pixels into queue-owned storage at the retention boundary. The queue is\n * latest-only (capacity 1), so this is exactly one allocation per *queued*\n * frame — bounded by the scheduler/inference cadence, NOT per *read* frame.\n * The high-frequency per-read allocation that failed the D9 perf gate is\n * eliminated; this bounded copy-on-retain is the correct cost.\n *\n * The generic parameter `T` lets the detection path queue a\n * `{ frame, handle }` entry (Task 7 — propagating the `FrameHandle`\n * through to the inference-result event), while the motion path keeps\n * queueing bare `DecodedFrame`s. Each instance provides its own\n * `clone` so the inner pixel buffer is detached at the same retention\n * boundary regardless of the wrapping entry shape.\n */\nexport class FrameQueue<T = DecodedFrame> {\n private latest: T | null = null\n private _droppedFrames = 0\n private readonly clone: (item: T) => T\n\n /**\n * `clone` runs on every {@link enqueue} to detach a queue-owned copy from\n * the (possibly borrowed) source — required by the D9 Task 7b ownership\n * contract above. Motion call sites pass {@link ownFrame}; the detection\n * path passes a clone that copies its inner frame's pixel buffer.\n */\n constructor(\n readonly maxSize: number,\n clone: (item: T) => T,\n ) {\n this.clone = clone\n }\n\n enqueue(item: T): void {\n if (this.latest !== null) {\n this._droppedFrames++\n }\n // Copy the borrowed shm scratch pixels into queue-owned storage — the\n // frame is retained past the reader's next read (see the class doc).\n this.latest = this.clone(item)\n }\n\n dequeue(): T | undefined {\n const item = this.latest ?? undefined\n this.latest = null\n return item\n }\n\n get size(): number {\n return this.latest !== null ? 1 : 0\n }\n\n get droppedFrames(): number {\n return this._droppedFrames\n }\n\n clear(): void {\n this.latest = null\n }\n}\n\n/**\n * Return a copy of `frame` whose `data` is queue-owned — a detached\n * `Buffer.from` of the (possibly borrowed) source pixels. Every other field is\n * a plain value and is carried through unchanged; any extra non-`DecodedFrame`\n * property the runner pins on a frame (e.g. `_enqueuedAt`) is preserved by the\n * spread so downstream timing accounting is unaffected.\n */\nexport function ownFrame(frame: DecodedFrame): DecodedFrame {\n return { ...frame, data: Buffer.from(frame.data) }\n}\n","/**\n * Counting semaphore with FIFO waiter queue. Used by the runner to bound\n * concurrent inference invocations across all attached cameras.\n *\n * The concurrency limit is **mutable** via `resize()` so the\n * pipeline-runner addon can hot-reload `maxConcurrentInferences` without\n * tearing down and restarting the runner. In-flight permits are\n * preserved: shrinking the limit just lowers the headroom until existing\n * releases catch up; growing it wakes pending waiters immediately.\n */\nexport class Semaphore {\n private _concurrency: number\n private _available: number\n private readonly waiters: Array<() => void> = []\n\n constructor(concurrency: number) {\n this._concurrency = concurrency\n this._available = concurrency\n }\n\n get concurrency(): number {\n return this._concurrency\n }\n\n get available(): number {\n return this._available\n }\n\n /**\n * Change the concurrency limit at runtime. Growing wakes as many\n * pending waiters as possible without exceeding the new headroom;\n * shrinking simply caps `_available` to `max(0, _available + delta)`.\n * In-flight permits are never revoked — the excess will drain\n * naturally as existing callers release.\n */\n resize(newConcurrency: number): void {\n if (newConcurrency < 1) throw new Error('Semaphore: concurrency must be >= 1')\n const delta = newConcurrency - this._concurrency\n this._concurrency = newConcurrency\n this._available = Math.max(0, this._available + delta)\n // If we just grew, wake as many waiters as new headroom allows.\n while (this._available > 0 && this.waiters.length > 0) {\n const next = this.waiters.shift()\n if (next) next()\n }\n }\n\n async acquire(): Promise<() => void> {\n if (this._available > 0) {\n this._available--\n return () => this.release()\n }\n\n return new Promise<() => void>((resolve) => {\n this.waiters.push(() => {\n this._available--\n resolve(() => this.release())\n })\n })\n }\n\n private release(): void {\n this._available++\n const next = this.waiters.shift()\n if (next) next()\n }\n}\n","import type { IScopedLogger } from '@camstack/types'\n\nconst REPORT_INTERVAL_MS = 10_000\n\n/**\n * Audio sample shape consumed by the timing sampler. Pushed by the runner's\n * audio subscription handler so that the periodic timing log includes audio\n * classification stats alongside motion + detection stats.\n */\nexport interface AudioSample {\n readonly classifyMs: number\n readonly dbfs: number\n readonly topLabel: string | null\n readonly topScore: number\n}\n\n/**\n * Runtime info labels for the timing sampler — purely cosmetic, used to tag\n * the periodic timing log lines with the active engine + addon names.\n */\nexport interface RuntimeInfo {\n readonly pipelineRuntime?: string\n readonly pipelineModels?: readonly string[]\n readonly motionAddon?: string\n readonly audioEngine?: string\n}\n\ninterface PhaseSample {\n /** ms waiting in queue before being picked */\n queueWait: number\n /** ms waiting to acquire semaphore */\n semaphoreWait: number\n /** ms for the full processFrame call (IPC + inference) */\n inference: number\n /** ms from result ready to event emitted */\n resultToEmit: number\n /** wall-clock pipeline latency: enqueue → event emitted */\n endToEnd: number\n /** wall-clock frame age when picked for inference: pickedAt − frame.capturedAt\n * (how stale the analyzed frame is vs when it entered the shm ring). -1 if unknown. */\n frameAge: number\n /** number of detections returned */\n detections: number\n}\n\n/**\n * Periodic timing-stats sampler. Accumulates per-camera samples for the\n * detection pipeline (capture → enqueue → semaphore → inference → emit),\n * motion analysis, and audio classification, and logs aggregated stats\n * every REPORT_INTERVAL_MS.\n */\nexport class PipelineTimingSampler {\n private readonly detSamples = new Map<number, PhaseSample[]>()\n private readonly motSamples = new Map<number, { ms: number; frameAge: number }[]>()\n private readonly audioSamples = new Map<number, AudioSample[]>()\n private droppedFrames = 0\n private reportTimer: ReturnType<typeof setInterval> | null = null\n private log: IScopedLogger | null = null\n runtimeInfo: RuntimeInfo = {}\n\n setLogger(logger: IScopedLogger): void {\n this.log = logger\n }\n\n start(): void {\n if (this.reportTimer) return\n this.reportTimer = setInterval(() => this.report(), REPORT_INTERVAL_MS)\n }\n\n stop(): void {\n if (this.reportTimer) {\n clearInterval(this.reportTimer)\n this.reportTimer = null\n }\n }\n\n addSample(deviceId: number, s: PhaseSample): void {\n if (!this.detSamples.has(deviceId)) this.detSamples.set(deviceId, [])\n this.detSamples.get(deviceId)!.push(s)\n }\n\n addMotionSample(deviceId: number, ms: number, frameAge = -1): void {\n if (!this.motSamples.has(deviceId)) this.motSamples.set(deviceId, [])\n this.motSamples.get(deviceId)!.push({ ms, frameAge })\n }\n\n addAudioSample(deviceId: number, s: AudioSample): void {\n if (!this.audioSamples.has(deviceId)) this.audioSamples.set(deviceId, [])\n this.audioSamples.get(deviceId)!.push(s)\n }\n\n addDrop(): void {\n this.droppedFrames++\n }\n\n private report(): void {\n if (!this.log) return\n\n const dropped = this.droppedFrames\n this.droppedFrames = 0\n\n const avg = (arr: number[]): number => arr.length > 0 ? Math.round(arr.reduce((a, b) => a + b, 0) / arr.length) : 0\n const max = (arr: number[]): number => arr.length > 0 ? Math.round(Math.max(...arr)) : 0\n const p95 = (arr: number[]): number => {\n if (arr.length === 0) return 0\n const sorted = [...arr].sort((a, b) => a - b)\n return Math.round(sorted[Math.floor(sorted.length * 0.95)] ?? sorted[sorted.length - 1]!)\n }\n\n const rt = this.runtimeInfo\n\n for (const [deviceId, det] of this.detSamples) {\n if (det.length === 0) continue\n const e2e = det.map((s) => s.endToEnd)\n const inf = det.map((s) => s.inference)\n const frameAge = det.map((s) => s.frameAge).filter((v) => v >= 0)\n const totalDet = det.reduce((s, d) => s + d.detections, 0)\n\n this.log.info(\n 'pipeline stats',\n {\n tags: { deviceId },\n meta: {\n frames: det.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n // enqueue → emit, in ms. Stage breakdown (avg) exposes WHERE the\n // latency sits: queue backlog vs semaphore contention vs inference\n // vs result-emit. inference is usually the floor (model chain).\n e2e: { avg: avg(e2e), p95: p95(e2e), max: max(e2e) },\n // Frame age (capture→inference-pick): if large, the analyzed frame is\n // already stale (decoder/ring behind), which delays the overlay\n // regardless of how fast the result is delivered.\n frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) },\n stagesMs: {\n queueWait: avg(det.map((s) => s.queueWait)),\n semaphoreWait: avg(det.map((s) => s.semaphoreWait)),\n inference: avg(inf),\n resultToEmit: avg(det.map((s) => s.resultToEmit)),\n },\n inference: { avg: avg(inf), p95: p95(inf) },\n detections: totalDet,\n dropped,\n pipelineRuntime: rt.pipelineRuntime ?? null,\n pipelineModels: rt.pipelineModels ?? null,\n },\n },\n )\n }\n this.detSamples.clear()\n\n for (const [deviceId, mot] of this.motSamples) {\n if (mot.length === 0) continue\n const ms = mot.map((s) => s.ms)\n const frameAge = mot.map((s) => s.frameAge).filter((v) => v >= 0)\n this.log.info(\n 'motion stats',\n {\n tags: { deviceId },\n meta: {\n frames: mot.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n avg: avg(ms),\n p95: p95(ms),\n max: max(ms),\n // Frame age at motion analysis (capture→analysis). Large = stale\n // input frame (decoder/ring behind) → motion box lags real movement.\n frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) },\n },\n },\n )\n }\n this.motSamples.clear()\n\n for (const [deviceId, aud] of this.audioSamples) {\n if (aud.length === 0) continue\n const classifyTimes = aud.filter((a) => a.classifyMs > 0).map((a) => a.classifyMs)\n const classified = aud.filter((a) => a.topLabel !== null)\n const topLabels = new Map<string, number>()\n for (const a of classified) {\n if (a.topLabel) topLabels.set(a.topLabel, (topLabels.get(a.topLabel) ?? 0) + 1)\n }\n const topSummary = [...topLabels.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([l, c]) => `${l}×${c}`).join(', ')\n const avgDbfs = avg(aud.map((a) => Math.round(a.dbfs)))\n\n this.log.info(\n 'audio stats',\n {\n tags: { deviceId },\n meta: {\n chunks: aud.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n classified: classified.length,\n classifyAvgMs: classifyTimes.length > 0 ? avg(classifyTimes) : 0,\n avgDbfs,\n topLabels: topSummary,\n audioEngine: rt.audioEngine ?? null,\n },\n },\n )\n }\n this.audioSamples.clear()\n }\n}\n","import type {\n FrameInput, FrameResult, OrchestratorMetrics, CameraMetrics,\n DecodedFrame, FrameHandle, CameraPhase, IScopedLogger,\n MotionSource, MotionRegion,\n PipelinePhaseMode,\n} from '@camstack/types'\nimport { FrameQueue, ownFrame } from './frame-queue.js'\nimport { Semaphore } from './semaphore.js'\nimport { PipelineTimingSampler } from './timing-sampler.js'\n\nexport type StreamType = 'motion' | 'detection'\n\n/**\n * Metadata accompanying a phase transition emitted by the runner.\n * The addon wrapping the scheduler turns this into a `motion`\n * cap-state slice + an `onMotionChanged` bus event.\n */\nexport interface PhaseTransitionMeta {\n readonly source: MotionSource\n readonly regions?: ReadonlyArray<MotionRegion>\n readonly timestamp: number\n readonly cooldownMs: number\n}\n\nexport interface RunnerConfig {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n /** Run inference on a detection frame (heavy, semaphore-limited). */\n readonly processFrame: (deviceId: number, frame: FrameInput) => Promise<FrameResult | null>\n /** Analyze a motion frame for activity (lightweight, no semaphore). */\n readonly analyzeMotion: (deviceId: number, frame: DecodedFrame) => Promise<void>\n /**\n * Fired on every phase transition (`watching ↔ active`). The\n * surrounding addon hooks this to (1) write the device's `motion`\n * cap-state slice via the unified `device-state.setCapSlice`\n * entrypoint and (2) emit `motion.onMotionChanged` on the bus.\n * Optional — test harnesses skip it.\n */\n readonly onPhaseChanged?: (\n deviceId: number,\n phase: CameraPhase,\n meta: PhaseTransitionMeta,\n ) => void\n /** Optional scoped logger; when provided, motion-gate transitions log here. */\n readonly logger?: IScopedLogger\n}\n\n/**\n * Per-camera registration. Mirrors the public RunnerCameraConfig but\n * narrowed to the fields the scheduler core actually needs (mode, fps,\n * cooldown). Broker subscription details are owned by the addon class\n * wrapping this scheduler — they don't reach the scheduler itself.\n */\nexport interface CameraRegistration {\n /** Operator-facing scheduling mode for the SW detection pipeline.\n * `'disabled'` — scheduler stays in `'idle'`, never processes frames.\n * `'always-on'` — initial phase `'active'`, no motion-gate fallback.\n * `'on-motion'` — initial phase `'watching'`, motion-gated. */\n readonly detectionMode: PipelinePhaseMode\n readonly fps: number\n readonly motionCooldownMs?: number\n}\n\nexport type ResultCallback = (\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n streamType: StreamType,\n handle: FrameHandle | undefined,\n) => Promise<void> | void\n\n/**\n * Detection-queue entry — the runner's detection path carries the\n * `FrameHandle` paired with the decoded frame end-to-end so that the\n * emitted `PipelineInferenceResultPayload` can name the same shm slot\n * downstream consumers (post-analysis) read pixels from. Motion frames\n * never propagate beyond the runner so the motion queue keeps queueing\n * bare `DecodedFrame`s.\n */\nexport interface DetectionQueueEntry {\n readonly frame: DecodedFrame\n readonly handle: FrameHandle\n}\n\nexport type DetectionStreamHandler = (\n deviceId: number,\n action: 'subscribe' | 'unsubscribe',\n) => void\n\ninterface CameraState {\n readonly registration: CameraRegistration\n readonly motionQueue: FrameQueue\n readonly detectionQueue: FrameQueue<DetectionQueueEntry>\n readonly inferenceTimes: number[]\n processedCount: number\n startTime: number\n phase: CameraPhase\n motionCooldownTimer: ReturnType<typeof setTimeout> | null\n /**\n * Source that armed the current cooldown timer. Carried into the\n * transition-to-watching event so consumers know which path was\n * the last positive signal before timeout.\n */\n lastArmedSource: MotionSource | null\n /** Regions from the most recent positive `analyzer` event — undefined for `onboard`. */\n lastArmedRegions: ReadonlyArray<MotionRegion> | undefined\n}\n\n/**\n * Clone a {@link DetectionQueueEntry} for the queue's copy-on-retain\n * boundary: the inner frame's pixel buffer is detached via\n * {@link ownFrame}, the `FrameHandle` is carried through unchanged (it's\n * a plain serialisable record with no borrowed buffers).\n */\nfunction ownDetectionEntry(entry: DetectionQueueEntry): DetectionQueueEntry {\n return { frame: ownFrame(entry.frame), handle: entry.handle }\n}\n\n// Fallback cooldown when the runner registration omits an explicit\n// `motionCooldownMs`. Aligned with the schema default in\n// `pipeline-runner.cap.ts` (motionCooldownMsField.default) so\n// always-on / synthetic registrations get the same window the\n// device-settings schema serves to operator-driven attaches.\nconst DEFAULT_MOTION_COOLDOWN_MS = 30_000\n\nfunction toFrameInput(frame: DecodedFrame): FrameInput {\n return {\n data: frame.data,\n width: frame.width,\n height: frame.height,\n format: frame.format,\n timestamp: frame.timestamp,\n }\n}\n\n/**\n * Pipeline runner scheduler — the per-node detection scheduler.\n *\n * Owns per-camera frame queues, the inference semaphore, the round-robin\n * scheduler, and the motion → detection phase machine. Inference and\n * motion analysis are delegated to caller-provided callbacks injected at\n * construction time so the scheduler stays decoupled from the surrounding\n * addon ecosystem (the addon class wrapping this scheduler injects\n * callbacks that resolve to the local pipeline-executor / motion-detection\n * caps when running in production).\n *\n * This class is intentionally framework-agnostic and side-effect-free\n * outside the callbacks — it can be unit tested with mock callbacks\n * without spinning up Moleculer or the addon framework.\n */\nexport class PipelineRunner {\n // Config is mutable (not `readonly`) because `updateLimits()` hot-reloads\n // the four tuning fields when the pipeline-runner addon's\n // `updateAddonSettings` is invoked via the new three-level settings API.\n // The callbacks (`processFrame`, `analyzeMotion`) are invariants captured\n // at construction and never changed.\n private config: RunnerConfig\n private readonly cameras = new Map<number, CameraState>()\n private readonly semaphore: Semaphore\n private readonly resultCallbacks: ResultCallback[] = []\n private readonly defaultRoundRobinKeys: number[] = []\n private defaultRoundRobinIndex = 0\n private intervalHandle: ReturnType<typeof setInterval> | null = null\n private detectionStreamHandler: DetectionStreamHandler | null = null\n private readonly logger: IScopedLogger | undefined\n readonly timingSampler = new PipelineTimingSampler()\n\n constructor(config: RunnerConfig) {\n this.config = config\n this.logger = config.logger\n this.semaphore = new Semaphore(config.maxConcurrentInferences)\n }\n\n /**\n * Hot-reload the four tuning fields without tearing down the runner.\n * - `maxConcurrentInferences`: resized on the live semaphore; in-flight\n * permits are preserved, new capacity is available immediately.\n * - `maxQueueDepth`: new `FrameQueue`s created from this point on use\n * the updated ceiling. Existing per-camera queues are not resized\n * (the FrameQueue implementation is latest-only and ignores maxSize\n * anyway — see `frame-queue.ts` — so the field is effectively a\n * metadata hint for observability).\n * - `targetLoadPercent` / `minThrottledFps`: stored for future\n * throttling logic (not yet consumed in the current runner body).\n *\n * Only keys present in the patch are overwritten; unspecified keys\n * retain their current value. Any illegal combination (e.g.\n * concurrency < 1) throws and leaves the runner unchanged.\n */\n updateLimits(patch: {\n readonly maxQueueDepth?: number\n readonly maxConcurrentInferences?: number\n readonly targetLoadPercent?: number\n readonly minThrottledFps?: number\n }): void {\n const next: RunnerConfig = {\n ...this.config,\n maxQueueDepth: patch.maxQueueDepth ?? this.config.maxQueueDepth,\n maxConcurrentInferences: patch.maxConcurrentInferences ?? this.config.maxConcurrentInferences,\n targetLoadPercent: patch.targetLoadPercent ?? this.config.targetLoadPercent,\n minThrottledFps: patch.minThrottledFps ?? this.config.minThrottledFps,\n }\n if (next.maxConcurrentInferences !== this.config.maxConcurrentInferences) {\n this.semaphore.resize(next.maxConcurrentInferences)\n }\n this.config = next\n }\n\n /** Read the current tuning fields for diagnostics / tests. */\n getLimits(): {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n } {\n return {\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n }\n }\n\n /** Set a handler called when the runner needs to subscribe/unsubscribe the detection stream. */\n onDetectionStreamChange(handler: DetectionStreamHandler): void {\n this.detectionStreamHandler = handler\n }\n\n registerCamera(deviceId: number, registration: CameraRegistration): void {\n const motionQueue = new FrameQueue(this.config.maxQueueDepth, ownFrame)\n const detectionQueue = new FrameQueue<DetectionQueueEntry>(\n this.config.maxQueueDepth,\n ownDetectionEntry,\n )\n // Map the operator-facing detectionMode to the scheduler's initial\n // phase. `'disabled'` short-circuits everything (no subscribe, no\n // round-robin slot, no detection queue activity).\n const initialPhase: CameraPhase = registration.detectionMode === 'disabled'\n ? 'idle'\n : registration.detectionMode === 'always-on'\n ? 'active'\n : 'watching'\n\n const state: CameraState = {\n registration,\n motionQueue,\n detectionQueue,\n inferenceTimes: [],\n processedCount: 0,\n startTime: Date.now(),\n phase: initialPhase,\n motionCooldownTimer: null,\n lastArmedSource: null,\n lastArmedRegions: undefined,\n }\n this.cameras.set(deviceId, state)\n\n // Round-robin scheduling applies only to `on-motion`. `always-on`\n // doesn't need a slot (always at the front of the queue).\n // `disabled` skips entirely.\n if (registration.detectionMode === 'on-motion') {\n this.defaultRoundRobinKeys.push(deviceId)\n }\n\n if (initialPhase === 'active') {\n this.detectionStreamHandler?.(deviceId, 'subscribe')\n // Always-on cameras start in `active` immediately — emit the\n // synthetic ON transition so consumers (motion cap-state +\n // bus event) see the same shape the gated path produces.\n const cooldownMs = registration.motionCooldownMs ?? DEFAULT_MOTION_COOLDOWN_MS\n this.config.onPhaseChanged?.(deviceId, 'active', {\n source: 'analyzer',\n regions: undefined,\n timestamp: Date.now(),\n cooldownMs,\n })\n }\n // `idle` (detectionMode='disabled') intentionally never emits a\n // phase event or subscribes — the runner registered the camera\n // for accounting but nothing flows.\n }\n\n unregisterCamera(deviceId: number): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n\n if (state.phase === 'active') {\n this.detectionStreamHandler?.(deviceId, 'unsubscribe')\n }\n\n state.motionQueue.clear()\n state.detectionQueue.clear()\n this.cameras.delete(deviceId)\n\n const idx = this.defaultRoundRobinKeys.indexOf(deviceId)\n if (idx !== -1) {\n this.defaultRoundRobinKeys.splice(idx, 1)\n if (this.defaultRoundRobinIndex >= this.defaultRoundRobinKeys.length) {\n this.defaultRoundRobinIndex = 0\n }\n }\n }\n\n enqueueMotionFrame(deviceId: number, frame: DecodedFrame): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n state.motionQueue.enqueue(frame)\n }\n\n enqueueDetectionFrame(\n deviceId: number,\n frame: DecodedFrame,\n handle: FrameHandle,\n ): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n if (state.phase !== 'active') return\n ;(frame as { _enqueuedAt?: number })._enqueuedAt = Date.now()\n state.detectionQueue.enqueue({ frame, handle })\n }\n\n /**\n * Report a motion event for a camera. Drives the unified phase\n * machine for both motion sources (analyzer + onboard):\n *\n * - Every `detected: true` (any source) clears + rearms the\n * cooldown timer and transitions watching → active. The same\n * timer applies regardless of which source(s) are configured;\n * concurrent sources just keep refreshing the same window.\n * - `detected: false` is a no-op. Onboard sources never send an\n * explicit clear, and the analyzer's \"false\" pulses would\n * otherwise fight the cooldown when motion paused briefly\n * during a scene. The timer is the single closure path.\n * - Timer expiry transitions active → watching.\n *\n * Always-on cameras silently ignore reportMotion calls — they're\n * already in `active` and have no cooldown.\n *\n * `source` and `regions` propagate into the phase-transition event\n * so the wrapping addon can attach them to the cap-state slice +\n * bus event.\n */\n reportMotion(\n deviceId: number,\n detected: boolean,\n source: MotionSource = 'analyzer',\n regions: ReadonlyArray<MotionRegion> | undefined = undefined,\n ): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n\n // `always-on` ignores motion (already active); `disabled` ignores\n // motion (camera registered but never subscribed to detection).\n // Only `on-motion` flows through the cooldown path.\n if (state.registration.detectionMode !== 'on-motion') return\n if (!detected) return\n\n state.lastArmedSource = source\n state.lastArmedRegions = regions\n const cooldownMs = state.registration.motionCooldownMs ?? DEFAULT_MOTION_COOLDOWN_MS\n\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n\n if (state.phase === 'watching') {\n this.transitionToActive(deviceId, state, source, regions, cooldownMs)\n }\n\n state.motionCooldownTimer = setTimeout(() => {\n state.motionCooldownTimer = null\n this.transitionToWatching(deviceId, state, cooldownMs)\n }, cooldownMs)\n }\n\n getPhase(deviceId: number): CameraPhase | undefined {\n return this.cameras.get(deviceId)?.phase\n }\n\n onResult(callback: ResultCallback): void {\n this.resultCallbacks.push(callback)\n }\n\n start(): void {\n if (this.intervalHandle !== null) return\n this.intervalHandle = setInterval(() => this.tick(), 10)\n this.timingSampler.start()\n }\n\n stop(): void {\n if (this.intervalHandle !== null) {\n clearInterval(this.intervalHandle)\n this.intervalHandle = null\n }\n this.timingSampler.stop()\n\n for (const state of this.cameras.values()) {\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n }\n }\n\n getMetrics(): OrchestratorMetrics {\n let totalQueueDepth = 0\n let totalInferenceTime = 0\n let totalInferenceCount = 0\n\n for (const state of this.cameras.values()) {\n totalQueueDepth += state.motionQueue.size + state.detectionQueue.size\n for (const t of state.inferenceTimes) {\n totalInferenceTime += t\n totalInferenceCount++\n }\n }\n\n return {\n activeCameras: this.cameras.size,\n throttledCameras: 0,\n avgInferenceTimeMs: totalInferenceCount > 0 ? totalInferenceTime / totalInferenceCount : 0,\n queueDepth: totalQueueDepth,\n }\n }\n\n getCameraMetrics(deviceId: number): CameraMetrics | undefined {\n const state = this.cameras.get(deviceId)\n if (!state) return undefined\n\n const elapsedMs = Date.now() - state.startTime\n const elapsedSec = elapsedMs / 1000\n const actualFps = elapsedSec > 0 ? state.processedCount / elapsedSec : 0\n\n const times = state.inferenceTimes\n const avgInference = times.length > 0 ? times.reduce((a, b) => a + b, 0) / times.length : 0\n\n return {\n detectionMode: state.registration.detectionMode,\n configuredFps: state.registration.fps,\n actualFps,\n queueDepth: state.motionQueue.size + state.detectionQueue.size,\n avgInferenceTimeMs: avgInference,\n droppedFrames: state.motionQueue.droppedFrames + state.detectionQueue.droppedFrames,\n phase: state.phase,\n }\n }\n\n getAllCameraMetrics(): Array<{ deviceId: number } & CameraMetrics> {\n const results: Array<{ deviceId: number } & CameraMetrics> = []\n for (const [deviceId] of this.cameras) {\n const metrics = this.getCameraMetrics(deviceId)\n if (metrics) {\n results.push({ deviceId, ...metrics })\n }\n }\n return results\n }\n\n getAttachedCameras(): readonly number[] {\n return [...this.cameras.keys()]\n }\n\n private transitionToActive(\n deviceId: number,\n state: CameraState,\n source: MotionSource,\n regions: ReadonlyArray<MotionRegion> | undefined,\n cooldownMs: number,\n ): void {\n state.phase = 'active'\n this.logger?.info('motion gate opened — phase=active', {\n tags: { deviceId },\n meta: { detectionMode: state.registration.detectionMode, source },\n })\n this.detectionStreamHandler?.(deviceId, 'subscribe')\n this.config.onPhaseChanged?.(deviceId, 'active', {\n source,\n regions,\n timestamp: Date.now(),\n cooldownMs,\n })\n }\n\n private transitionToWatching(\n deviceId: number,\n state: CameraState,\n cooldownMs: number,\n ): void {\n state.phase = 'watching'\n state.detectionQueue.clear()\n this.logger?.info('motion gate closed — phase=watching', {\n tags: { deviceId },\n meta: { lastSource: state.lastArmedSource },\n })\n this.detectionStreamHandler?.(deviceId, 'unsubscribe')\n // Last source that armed the timer carries the OFF event payload —\n // analyzer or onboard, whatever last fired before the silence\n // window expired. Regions are intentionally not propagated on\n // OFF: they describe a positive observation, not a closure.\n const source: MotionSource = state.lastArmedSource ?? 'analyzer'\n this.config.onPhaseChanged?.(deviceId, 'watching', {\n source,\n regions: undefined,\n timestamp: Date.now(),\n cooldownMs,\n })\n state.lastArmedSource = null\n state.lastArmedRegions = undefined\n }\n\n private tick(): void {\n this.drainMotionQueues()\n\n if (this.semaphore.available <= 0) return\n\n const picked = this.pickNextDetectionFrame()\n if (!picked) return\n\n const { deviceId, entry, state } = picked\n const frameInput = toFrameInput(entry.frame)\n\n void this.processWithSemaphore(deviceId, entry, frameInput, state, 'detection')\n }\n\n private drainMotionQueues(): void {\n for (const [deviceId, state] of this.cameras) {\n while (state.motionQueue.size > 0) {\n const frame = state.motionQueue.dequeue()\n if (frame) {\n void this.config.analyzeMotion(deviceId, frame)\n }\n }\n }\n }\n\n private async processWithSemaphore(\n deviceId: number,\n entry: DetectionQueueEntry,\n frameInput: FrameInput,\n state: CameraState,\n streamType: StreamType,\n ): Promise<void> {\n const pickedAt = Date.now()\n const { frame, handle } = entry\n // `frame.timestamp` is the codec PTS (shm-ring `frame.meta.pts`), NOT a\n // wall-clock time — subtracting it from Date.now() yields a ~1.7e12 garbage\n // \"latency\". `_enqueuedAt` is the wall-clock stamp written when the frame\n // entered the detection queue (see enqueueDetectionFrame); fall back to\n // `pickedAt` (also wall-clock) so all latency math stays in one time base.\n const enqueuedAt = (frame as { _enqueuedAt?: number })._enqueuedAt ?? pickedAt\n\n const release = await this.semaphore.acquire()\n const semAcquiredAt = Date.now()\n try {\n const result = await this.config.processFrame(deviceId, frameInput)\n const inferDoneAt = Date.now()\n const inferenceMs = inferDoneAt - semAcquiredAt\n\n state.inferenceTimes.push(inferenceMs)\n if (state.inferenceTimes.length > 100) {\n state.inferenceTimes.shift()\n }\n state.processedCount++\n\n if (result) {\n await this.notifyCallbacks(deviceId, frame, result, streamType, handle)\n const emittedAt = Date.now()\n\n const capturedAt = frame.capturedAt\n this.timingSampler.addSample(deviceId, {\n queueWait: pickedAt - enqueuedAt,\n semaphoreWait: semAcquiredAt - pickedAt,\n inference: inferenceMs,\n resultToEmit: emittedAt - inferDoneAt,\n // capturedAt is the shm-ring commit wall-clock; pickedAt − capturedAt\n // is how stale the frame was when inference started. <0/absent → unknown.\n frameAge: typeof capturedAt === 'number' && capturedAt > 0 ? pickedAt - capturedAt : -1,\n // Wall-clock pipeline latency: enqueue → result emitted. (Capture →\n // enqueue, i.e. RTSP decode + shm-ring write, is not measured here —\n // the shm frame carries only a PTS, no wall-clock capture stamp.)\n endToEnd: emittedAt - enqueuedAt,\n detections: result.detections?.length ?? 0,\n })\n }\n } finally {\n release()\n }\n }\n\n private async notifyCallbacks(\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n streamType: StreamType,\n handle: FrameHandle | undefined,\n ): Promise<void> {\n for (const callback of this.resultCallbacks) {\n try {\n await callback(deviceId, frame, result, streamType, handle)\n } catch {\n // Error isolation: individual callback failures don't affect others\n }\n }\n }\n\n private pickNextDetectionFrame(): {\n deviceId: number\n entry: DetectionQueueEntry\n state: CameraState\n } | null {\n for (const [deviceId, state] of this.cameras) {\n if (state.registration.detectionMode === 'always-on' && state.detectionQueue.size > 0) {\n const entry = state.detectionQueue.dequeue()!\n return { deviceId, entry, state }\n }\n }\n\n if (this.defaultRoundRobinKeys.length === 0) return null\n\n const startIndex = this.defaultRoundRobinIndex\n for (let i = 0; i < this.defaultRoundRobinKeys.length; i++) {\n const idx = (startIndex + i) % this.defaultRoundRobinKeys.length\n const deviceId = this.defaultRoundRobinKeys[idx]\n if (!deviceId) continue\n const state = this.cameras.get(deviceId)\n if (!state) continue\n\n if (state.phase === 'active' && state.detectionQueue.size > 0) {\n this.defaultRoundRobinIndex = (idx + 1) % this.defaultRoundRobinKeys.length\n const entry = state.detectionQueue.dequeue()\n if (!entry) continue\n return { deviceId, entry, state }\n }\n }\n\n return null\n }\n}\n","/**\n * `FrameHandlePoller` — the consumer-side poll loop of the shared-memory frame\n * plane (Phase 5 / D9).\n *\n * Replaces the live-object `IStreamBroker.onDecodedFrame(cb)` callback path.\n * A consumer (motion, detection):\n *\n * 1. `subscribeFrames({ brokerId, format, maxFps, tag })` over tRPC — the\n * broker spins up a `frameSink: 'shm'` decoder session for that format\n * and returns a `subscriptionId`;\n * 2. polls `pullFrameHandles({ subscriptionId, maxCount })` at the cadence\n * `maxFps` implies, draining zero-pixel `FrameHandle`s;\n * 3. feeds each handle to a `FrameRingReaderCache` which opens the named shm\n * segment and reads the pixels back zero-copy;\n * 4. on teardown, `unsubscribeFrames` + closes the cached readers.\n *\n * The loop tolerates an initially-empty stream — `pullFrameHandles` returns\n * `[]` until the broker has a source, and a recycled ring slot yields a `null`\n * pixel read (a dropped frame, the correct latest-wins behaviour).\n */\nimport type {\n AddonApi,\n DecodedFrame,\n FrameHandle,\n FrameHandleFormat,\n IScopedLogger,\n} from '@camstack/types'\nimport { errMsg } from '@camstack/types'\nimport { FrameRingReaderCache } from '@camstack/shm-ring'\n\n/** How many handles to drain per poll — a small burst absorbs jitter. */\nconst PULL_MAX_COUNT = 4\n\n/** Floor on the poll period so a 0/absurd `maxFps` can't busy-spin. */\nconst MIN_POLL_INTERVAL_MS = 20\n\n/** Poll period when the broker reports a non-positive cadence hint. */\nconst FALLBACK_POLL_INTERVAL_MS = 200\n\n/** Inputs for a single frame subscription. */\nexport interface FrameHandlePollerOptions {\n /** The cross-addon API surface — frame handles travel over `streamBroker`. */\n readonly api: AddonApi\n /** Broker id (`<deviceId>/<streamId>`) to subscribe to. */\n readonly brokerId: string\n /** Pixel format this consumer reads (`gray` for motion, `rgb` for detection). */\n readonly format: FrameHandleFormat\n /** Reader-side cadence hint, frames/s. */\n readonly maxFps: number\n /** Short caller-identity tag for broker-side diagnostics. */\n readonly tag: string\n /**\n * Called for every successfully-read frame. The `handle` is the same\n * `FrameHandle` that produced the decoded pixels — downstream\n * consumers (detection-result post-analysis) carry it back to the\n * emitted inference-result event so the original frame can be\n * resolved zero-copy.\n */\n readonly onFrame: (frame: DecodedFrame, handle: FrameHandle) => void\n readonly logger: IScopedLogger\n}\n\n/**\n * Subscribe to a broker's shm frame stream and start the poll loop. Resolves\n * to a teardown closure once the subscription is established, or `null` when\n * the broker has no subscription surface (e.g. no broker for `brokerId`).\n *\n * The teardown closure stops the loop, releases the broker subscription, and\n * closes every shm segment the reader cache opened.\n */\nexport async function startFrameHandlePoller(\n options: FrameHandlePollerOptions,\n): Promise<(() => void) | null> {\n const { api, brokerId, format, maxFps, tag, onFrame, logger } = options\n\n let result: { subscriptionId: string; maxFps: number }\n try {\n result = await api.streamBroker.subscribeFrames.mutate({\n brokerId,\n format,\n maxFps,\n tag,\n })\n } catch (err) {\n logger.warn('frame-handle poller: subscribeFrames failed', {\n meta: { brokerId, format, tag, error: errMsg(err) },\n })\n return null\n }\n\n const { subscriptionId } = result\n const readers = new FrameRingReaderCache(logger)\n const pollIntervalMs =\n result.maxFps > 0\n ? Math.max(MIN_POLL_INTERVAL_MS, Math.round(1000 / result.maxFps))\n : FALLBACK_POLL_INTERVAL_MS\n\n let stopped = false\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const tick = async (): Promise<void> => {\n if (stopped) return\n try {\n const handles = await api.streamBroker.pullFrameHandles.query({\n subscriptionId,\n maxCount: PULL_MAX_COUNT,\n })\n for (const handle of handles) {\n if (stopped) break\n const frame = readers.read(handle)\n // A `null` read = a recycled ring slot = a dropped frame. Skip it.\n if (frame) onFrame(frame, handle)\n }\n } catch (err) {\n logger.warn('frame-handle poller: pullFrameHandles failed', {\n meta: { brokerId, subscriptionId, error: errMsg(err) },\n })\n }\n if (!stopped) {\n timer = setTimeout(() => void tick(), pollIntervalMs)\n }\n }\n\n // Run the first poll immediately so a consumer is not blind for a full poll\n // interval (up to ~200ms) before the first `pullFrameHandles`. `tick`\n // self-schedules every later poll, and its `stopped` guard means a teardown\n // that fires before/just after this first tick still stops the loop cleanly.\n void tick()\n\n return () => {\n if (stopped) return\n stopped = true\n if (timer) {\n clearTimeout(timer)\n timer = undefined\n }\n readers.close()\n api.streamBroker.unsubscribeFrames\n .mutate({ subscriptionId })\n .catch((err: unknown) => {\n logger.warn('frame-handle poller: unsubscribeFrames failed', {\n meta: { brokerId, subscriptionId, error: errMsg(err) },\n })\n })\n }\n}\n","/**\n * Synthetic-bench custom actions for pipeline-runner.\n *\n * Lets the admin UI drive a production-realistic detection benchmark:\n * the runner — already co-located with detection-pipeline in the\n * `pipeline` group — invokes `api.pipelineExecutor.runPipeline` directly\n * (in-process via localProviderLink, no Moleculer hop), N workers\n * concurrent × M iterations. Returns aggregate stats.\n *\n * Why this exists: the older bench path (UI → benchmark addon → pipeline\n * addon) crosses TWO Moleculer process boundaries that the production\n * camera path does NOT have (broker / runner / detection-pipeline are\n * all in the same group). Running the loop from inside the runner gives\n * fps numbers that match what real cameras achieve.\n */\nimport { z } from 'zod'\nimport { customAction, defineCustomActions } from '@camstack/types'\n\n// Local mirror of pipeline-executor's engine-choice schema. Re-declared here\n// to avoid a cap-package coupling for one bench cap; shape is fixed by the\n// runtime contract (runtime + backend + format + optional device).\nconst BenchEngineChoiceSchema = z.object({\n runtime: z.enum(['node', 'python']),\n backend: z.string(),\n format: z.enum(['onnx', 'coreml', 'openvino', 'tflite', 'pt']),\n device: z.string().optional(),\n})\n\n// Recursive step shape — matches PipelineStepInput minimally for cap input.\ninterface BenchStepShape {\n readonly addonId: string\n readonly modelId: string\n readonly enabled: boolean\n readonly children?: readonly BenchStepShape[]\n}\nconst BenchStepSchema: z.ZodType<BenchStepShape> = z.lazy(() => z.object({\n addonId: z.string(),\n modelId: z.string(),\n enabled: z.boolean(),\n children: z.array(BenchStepSchema).optional(),\n}))\n\ninterface CacheBenchFrameInputShape {\n readonly imageBase64: string\n readonly ttlSeconds?: number\n}\ninterface CacheBenchFrameResultShape {\n readonly frameId: string\n readonly width: number\n readonly height: number\n readonly expiresAt: number\n}\nexport const CacheBenchFrameInputSchema: z.ZodType<CacheBenchFrameInputShape> = z.object({\n imageBase64: z.string(),\n ttlSeconds: z.number().int().positive().optional(),\n})\nexport const CacheBenchFrameResultSchema: z.ZodType<CacheBenchFrameResultShape> = z.object({\n frameId: z.string(),\n width: z.number(),\n height: z.number(),\n expiresAt: z.number(),\n})\n\ninterface ReleaseBenchFrameInputShape { readonly frameId: string }\ninterface ReleaseBenchFrameResultShape { readonly released: boolean }\nexport const ReleaseBenchFrameInputSchema: z.ZodType<ReleaseBenchFrameInputShape> = z.object({\n frameId: z.string(),\n})\nexport const ReleaseBenchFrameResultSchema: z.ZodType<ReleaseBenchFrameResultShape> = z.object({\n released: z.boolean(),\n})\n\ninterface RunSyntheticBenchInputShape {\n readonly frameId: string\n readonly steps: readonly BenchStepShape[]\n readonly parallel: number\n readonly iterations: number\n readonly warmup?: number\n readonly sessionId?: string\n /** When true, forces the full runPipeline path (1.2MB pipe transfer per\n * call) instead of the cached inferCached fast path. Simulates the real\n * camera detection pipeline cost including IPC overhead. */\n readonly simulatePipeline?: boolean\n readonly engine?: { readonly runtime: 'node' | 'python'; readonly backend: string; readonly format: 'onnx' | 'coreml' | 'openvino' | 'tflite' | 'pt'; readonly device?: string }\n}\nexport const RunSyntheticBenchInputSchema: z.ZodType<RunSyntheticBenchInputShape> = z.object({\n frameId: z.string(),\n steps: z.array(BenchStepSchema).min(1),\n parallel: z.number().int().min(1).max(32),\n iterations: z.number().int().min(1).max(10_000),\n warmup: z.number().int().min(0).max(100).optional(),\n sessionId: z.string().optional(),\n simulatePipeline: z.boolean().optional(),\n engine: BenchEngineChoiceSchema.optional(),\n})\n\ninterface TimingSplitShape {\n readonly mean: number\n readonly p50: number\n readonly p95: number\n readonly p99: number\n}\nconst TimingSplitSchema: z.ZodType<TimingSplitShape> = z.object({\n mean: z.number(),\n p50: z.number(),\n p95: z.number(),\n p99: z.number(),\n})\n\ninterface RunSyntheticBenchResultShape {\n readonly runs: number\n readonly wallSec: number\n readonly fps: number\n readonly detectionsPerSec: number\n readonly avgDetections: number\n readonly callMs: TimingSplitShape\n readonly inferMs: number\n readonly preprocessMs: number\n readonly predictMs: number\n readonly batchSizeMean: number\n readonly batchSizeMax: number\n readonly engine?: { readonly runtime: string; readonly backend: string; readonly device?: string }\n readonly tuning?: { readonly batchMode: string; readonly windowMs: number; readonly maxBatchSize: number; readonly concurrency: number }\n readonly path?: string\n}\nexport const RunSyntheticBenchResultSchema: z.ZodType<RunSyntheticBenchResultShape> = z.object({\n runs: z.number(),\n wallSec: z.number(),\n fps: z.number(),\n detectionsPerSec: z.number(),\n avgDetections: z.number(),\n callMs: TimingSplitSchema,\n inferMs: z.number(),\n preprocessMs: z.number(),\n predictMs: z.number(),\n batchSizeMean: z.number(),\n batchSizeMax: z.number(),\n engine: z.object({ runtime: z.string(), backend: z.string(), device: z.string().optional() }).optional(),\n tuning: z.object({ batchMode: z.string(), windowMs: z.number(), maxBatchSize: z.number(), concurrency: z.number() }).optional(),\n path: z.string().optional(),\n})\n\nexport const pipelineRunnerBenchActions = defineCustomActions({\n cacheBenchFrame: customAction(\n CacheBenchFrameInputSchema,\n CacheBenchFrameResultSchema,\n { kind: 'mutation' },\n ),\n releaseBenchFrame: customAction(\n ReleaseBenchFrameInputSchema,\n ReleaseBenchFrameResultSchema,\n { kind: 'mutation' },\n ),\n runSyntheticBench: customAction(\n RunSyntheticBenchInputSchema,\n RunSyntheticBenchResultSchema,\n { kind: 'mutation' },\n ),\n})\n\nexport type PipelineRunnerBenchActions = typeof pipelineRunnerBenchActions\n\n// Explicit TypeScript types — z.infer<> on customAction's stored input/output\n// types loses precision through the spec wrapper. Caller code uses these.\nexport interface BenchStep {\n readonly addonId: string\n readonly modelId: string\n readonly enabled: boolean\n readonly children?: readonly BenchStep[]\n}\nexport interface BenchEngineChoice {\n readonly runtime: 'node' | 'python'\n readonly backend: string\n readonly format: 'onnx' | 'coreml' | 'openvino' | 'tflite' | 'pt'\n readonly device?: string\n}\nexport interface CacheBenchFrameInput {\n readonly imageBase64: string\n readonly ttlSeconds?: number\n}\nexport interface CacheBenchFrameResult {\n readonly frameId: string\n readonly width: number\n readonly height: number\n readonly expiresAt: number\n}\nexport interface ReleaseBenchFrameInput { readonly frameId: string }\nexport interface ReleaseBenchFrameResult { readonly released: boolean }\nexport interface RunSyntheticBenchInput {\n readonly frameId: string\n readonly steps: readonly BenchStep[]\n readonly parallel: number\n readonly iterations: number\n readonly warmup?: number\n readonly sessionId?: string\n readonly simulatePipeline?: boolean\n readonly engine?: BenchEngineChoice\n}\nexport interface RunSyntheticBenchResult {\n readonly runs: number\n readonly wallSec: number\n readonly fps: number\n readonly detectionsPerSec: number\n readonly avgDetections: number\n readonly callMs: { readonly mean: number; readonly p50: number; readonly p95: number; readonly p99: number }\n readonly inferMs: number\n readonly preprocessMs: number\n readonly predictMs: number\n readonly batchSizeMean: number\n readonly batchSizeMax: number\n readonly engine?: { readonly runtime: string; readonly backend: string; readonly device?: string }\n readonly tuning?: { readonly batchMode: string; readonly windowMs: number; readonly maxBatchSize: number; readonly concurrency: number }\n readonly path?: string\n}\n","/**\n * addon-pipeline-runner — per-node detection scheduler.\n *\n * Lives on every \"vision node\" (hub if hub is one, every agent that runs\n * decoders/cameras). Receives camera assignments from the hub-side\n * `addon-pipeline-orchestrator` via cap calls, owns the per-camera frame\n * queues + semaphore + motion → detection phase machine, calls the local\n * `motion-detection` and `pipeline-executor` capabilities for the actual\n * work, and emits typed events on the bus\n * (`detection.motion-analysis`, `pipeline.inference-result`, `pipeline.trace`).\n *\n * Design notes:\n *\n * - All cap inputs/outputs are serializable so the runner is transparently\n * routable across the cluster: when the runner is co-located with the\n * broker/decoder/motion/detection (the default and recommended deploy),\n * capability lookups return live singletons and frame data never crosses\n * a process boundary. When a deploy splits the runner from the executor\n * (rare), the lookup returns null and the runner falls back to\n * `this.ctx.api` (broker proxy) at the cost of serialization.\n *\n * - The scheduler core (`PipelineRunner`) is callback-driven and framework\n * agnostic. The addon class injects the inference + motion callbacks at\n * construction time, capturing references to the relevant providers\n * lazily on each invocation.\n *\n * - Result post-processing (analysis pipeline, class filters, notifications)\n * is intentionally NOT done by the runner. The runner emits the raw\n * inference output as a `pipeline.inference-result` event on the bus;\n * a hub-side subscriber consumes the event and runs the post-processing\n * pipeline. This keeps the runner stateless w.r.t. detection-side\n * business logic and makes it possible to deploy the post-processor\n * independently from the inference workers.\n */\nimport type {\n IPipelineRunnerProvider, RunnerCameraConfig, RunnerLocalLoad, RunnerLocalMetrics,\n CameraMetrics, FrameInput, FrameResult, DecodedFrame, FrameHandle,\n PipelineInferenceResultPayload, MotionAnalysisPayload, MotionZonesRawPayload, PhaseTransitionPayload,\n PipelineStepInput,\n CameraPhase, MotionSource, MotionRegion,\n} from '@camstack/types'\nimport { BaseAddon, EventCategory, createEvent, pipelineRunnerCapability, errMsg } from '@camstack/types'\nimport type { AddonInitResult } from '@camstack/types'\nimport { PipelineRunner, type PhaseTransitionMeta } from './runner.js'\nimport { startFrameHandlePoller } from './frame-handle-poller.js'\nimport {\n pipelineRunnerBenchActions,\n type PipelineRunnerBenchActions,\n type CacheBenchFrameInput,\n type CacheBenchFrameResult,\n type ReleaseBenchFrameInput,\n type ReleaseBenchFrameResult,\n type RunSyntheticBenchInput,\n type RunSyntheticBenchResult,\n} from './bench-actions.js'\n\ninterface RunnerAddonConfig {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n}\n\nconst DEFAULT_CONFIG: RunnerAddonConfig = {\n maxQueueDepth: 30,\n // CoreML window accumulator coalesces concurrent calls into a single\n // model.predict([list]) — the more in-flight, the larger the batch and\n // the higher the per-frame throughput. With concurrency=2 the window\n // never fills past batch=2, capping the pool at ~50 fps single-node.\n // 16 matches the slider ceiling and lines up with bench numbers\n // (parallel=16 hits batch=7-8/8, sustaining ~140 fps full path).\n maxConcurrentInferences: 16,\n targetLoadPercent: 80,\n minThrottledFps: 1,\n}\n\n/**\n * Camera attachment record kept by the runner addon. Holds the original\n * RunnerCameraConfig for introspection via `getLocalCameras` plus the\n * live broker subscription handles needed to tear down on detach.\n */\ninterface AttachedCamera {\n readonly config: RunnerCameraConfig\n motionUnsubscribe: (() => void) | null\n detectionUnsubscribe: (() => void) | null\n}\n\n/**\n * Pure decision helper: returns `true` when a dynamic frame-diff analyzer\n * subscription should be opened for an onboard-motion camera.\n *\n * Conditions (all must hold):\n * - `onboardMotionDrivesAnalyzer` flag is enabled on the config.\n * - `motionSources` does NOT already include `'analyzer'` — if it does,\n * the analyzer runs continuously and there is nothing to gate.\n *\n * Extracted as a pure function so the unit suite can exercise the decision\n * logic without instantiating the full addon.\n */\nexport function shouldStartOnboardAnalyzer(config: RunnerCameraConfig): boolean {\n if (!config.onboardMotionDrivesAnalyzer) return false\n if (config.motionSources.includes('analyzer')) return false\n return true\n}\n\nfunction toFrameInput(frame: DecodedFrame): FrameInput {\n return {\n data: frame.data,\n width: frame.width,\n height: frame.height,\n format: frame.format,\n timestamp: frame.timestamp,\n }\n}\n\n/** Interval for the per-camera step roster dump (ms). */\nconst STEP_LOG_INTERVAL_MS = 30_000\n\n/**\n * Interval for periodic metric snapshot emission. ~1 Hz strikes the\n * balance between UI freshness (overlay phase / fps numbers feel live)\n * and bus traffic (one snapshot per attached camera + one per node\n * load is well under 100 events/s even at 50 cameras).\n */\nconst METRICS_SNAPSHOT_INTERVAL_MS = 1_000\n/**\n * Force a metrics-snapshot emit at least every 30s even when the\n * payload hasn't changed — gives the UI's \"agent reachable\" chip a\n * heartbeat without the per-tick spam an unconditional emit\n * produces. Picked so a 5-minute idle window emits ~10 events\n * instead of ~300.\n */\nconst METRICS_HEARTBEAT_MS = 30_000\n\nexport default class PipelineRunnerAddon extends BaseAddon<RunnerAddonConfig> implements IPipelineRunnerProvider {\n private runner: PipelineRunner | null = null\n private readonly attached = new Map<number, AttachedCamera>()\n private nodeId = 'unknown'\n private stepLogTimer: ReturnType<typeof setInterval> | null = null\n private metricsSnapshotTimer: ReturnType<typeof setInterval> | null = null\n private unsubMotionEvents: (() => void) | null = null\n /** Last analyzer-detected state per device — gates the\n * `MotionOnMotionChanged` emit in `runMotionAnalysis` to transitions\n * only (otherwise we'd emit on every analyzer frame). */\n private readonly lastAnalyzerDetected = new Map<number, boolean>()\n /**\n * Last positive motion timestamp per device — preserved across the\n * OFF transition so the motion runtime-state slice keeps a stable\n * `lastDetectedAt` after the cooldown closes the phase. Cleared on\n * detach.\n */\n private readonly lastMotionAt = new Map<number, number>()\n\n /**\n * Dynamic analyzer subscriptions opened on `MotionOnMotionChanged\n * source:'onboard'` when `onboardMotionDrivesAnalyzer === true`. Each\n * entry is the unsubscribe handle returned by `subscribeMotionFrames`.\n * Cleared on teardown timer fire, detach, and shutdown.\n */\n private readonly onboardAnalyzerSubs = new Map<number, () => void>()\n /**\n * Teardown timers that close the dynamic analyzer subscription after\n * `motionCooldownMs` without a new motion event. Re-armed on every\n * `MotionOnMotionChanged source:'onboard'` call so the sub stays open\n * while motion persists.\n */\n private readonly onboardAnalyzerTeardownTimers = new Map<number, ReturnType<typeof setTimeout>>()\n\n /**\n * Snapshot-equality cache for metrics-snapshot defer. The runner\n * fires per-camera metrics every `METRICS_SNAPSHOT_INTERVAL_MS`;\n * for an idle camera (no inference, queue empty, fps=0) every tick\n * carries an identical payload. We skip the bus emit when the\n * payload deep-equals the previous one so the events tab + remote\n * subscribers stop seeing 60 metrics-snapshots/min/camera that\n * convey nothing. A periodic heartbeat re-emits every\n * METRICS_HEARTBEAT_MS so consumers know the runner is still\n * alive.\n */\n private readonly lastEmittedCameraMetrics = new Map<number, { json: string; emittedAt: number }>()\n private lastEmittedRunnerLoad: { json: string; emittedAt: number } | null = null\n\n /**\n * In-memory bench-frame cache (decoded JPEG bytes). Populated by the\n * `cacheBenchFrame` custom action. Fed into the synthetic-bench loop\n * via the `frame: FrameInput` shape that mirrors what stream-broker\n * delivers to this very addon during real camera detection.\n */\n private readonly benchFrameCache = new Map<string, {\n readonly data: Uint8Array\n readonly width: number\n readonly height: number\n readonly format: 'rgb'\n readonly expiresAt: number\n }>()\n private benchFrameSweeper: ReturnType<typeof setInterval> | null = null\n\n constructor() { super({ ...DEFAULT_CONFIG }) }\n\n protected async onInitialize(): Promise<AddonInitResult<PipelineRunnerBenchActions>> {\n // Use the cluster node id (hub, dev-agent-0, etc.) for dispatch routing,\n // not the addon id (pipeline-runner). The orchestrator uses this to track\n // which cluster node a camera is assigned to.\n // When the runner lives in a group-runner subprocess the broker\n // nodeID is hierarchical (`hub/pipeline`); strip the suffix so\n // emitted events / cap calls refer to the cluster-visible parent\n // node. In-process boot leaves the value untouched.\n const raw = this.ctx.kernel.localNodeId ?? this.ctx.id\n this.nodeId = raw.includes('/') ? raw.split('/')[0]! : raw\n\n this.runner = new PipelineRunner({\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n processFrame: (deviceId, frame) => this.runInference(deviceId, frame),\n analyzeMotion: (deviceId, frame) => this.runMotionAnalysis(deviceId, frame),\n onPhaseChanged: (deviceId, phase, meta) => this.handlePhaseChanged(deviceId, phase, meta),\n logger: this.ctx.logger,\n })\n\n this.runner.timingSampler.setLogger(this.ctx.logger.child('timing'))\n\n // Hook the per-camera detection-stream subscribe/unsubscribe lifecycle\n // into the local broker. The runner's phase machine triggers this\n // handler when a camera transitions WATCHING ↔ ACTIVE.\n this.runner.onDetectionStreamChange((deviceId, action) => {\n this.handleDetectionStreamChange(deviceId, action)\n })\n\n // Emit raw inference results to the bus. Hub-side post-processors\n // (analysis pipeline, class filters, notifications) consume these\n // and emit the post-processed `detection.result` event. The\n // `handle` is the shm-ring `FrameHandle` the runner read pixels\n // from — forwarded onto the emitted event so post-analysis\n // consumers (Task 8) can resolve the same frame zero-copy.\n this.runner.onResult(async (deviceId, frame, result, _streamType, handle) => {\n this.emitInferenceResult(deviceId, frame, result, handle)\n })\n\n this.runner.start()\n this.ctx.logger.info(\n 'Pipeline runner started',\n {\n tags: { nodeId: this.nodeId },\n meta: {\n maxConcurrent: this.config.maxConcurrentInferences,\n queueDepth: this.config.maxQueueDepth,\n },\n },\n )\n\n // Unified motion event subscription. Sources (Reolink firmware push\n // events emitting `source: 'onboard'`, this runner's own analyzer\n // path emitting `source: 'analyzer'` from `runMotionAnalysis`) all\n // funnel through `MotionOnMotionChanged`. The phase machine reacts\n // via `reportMotion` regardless of source — `motionSources`\n // selector at the runner-config level still gates which sources\n // trigger detection downstream.\n if (this.ctx.eventBus) {\n this.unsubMotionEvents = this.ctx.eventBus.subscribe(\n { category: EventCategory.MotionOnMotionChanged },\n (event) => {\n const data = event.data\n const deviceId = data.deviceId\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n const source = data.source\n // Dynamic analyzer gate: when the camera uses onboard-only\n // motion, open/close the frame-diff subscription on demand so\n // `runMotionAnalysis` runs (and emits MotionZonesRaw) during\n // active-motion windows without holding the substream open\n // continuously. Runs regardless of the `motionSources` gate\n // below — the dynamic sub is a side-channel that does NOT feed\n // the phase machine (onboard remains the sole driver).\n if (source === 'onboard') {\n void this.handleOnboardMotionAnalyzer(deviceId, data.detected)\n }\n if (!attachment.config.motionSources.includes(source)) return\n this.runner?.reportMotion(\n deviceId,\n data.detected,\n source,\n data.regions ? [...data.regions] : undefined,\n )\n },\n )\n }\n\n // Periodic per-camera step roster dump. Logs once every\n // STEP_LOG_INTERVAL_MS for each attached camera so operators can\n // see at-a-glance which detection + audio steps are configured on\n // each device without correlating attach events. Skips when no\n // cameras are attached so quiet dev runs don't emit noise.\n this.stepLogTimer = setInterval(() => this.logAttachedSteps(), STEP_LOG_INTERVAL_MS)\n\n // Periodic metric-snapshot emission: one runner-load event +\n // one per-camera metrics event per attached camera per tick.\n // UI dashboards subscribe to these instead of polling the cap.\n this.metricsSnapshotTimer = setInterval(\n () => this.emitMetricsSnapshot(),\n METRICS_SNAPSHOT_INTERVAL_MS,\n )\n\n return {\n providers: [{ capability: pipelineRunnerCapability, provider: this }],\n customActions: pipelineRunnerBenchActions,\n actionHandlers: {\n cacheBenchFrame: async (input) => this.cacheBenchFrame(input),\n releaseBenchFrame: async (input) => this.releaseBenchFrame(input),\n runSyntheticBench: async (input) => this.runSyntheticBench(input),\n },\n }\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.metricsSnapshotTimer) {\n clearInterval(this.metricsSnapshotTimer)\n this.metricsSnapshotTimer = null\n }\n if (this.stepLogTimer) {\n clearInterval(this.stepLogTimer)\n this.stepLogTimer = null\n }\n if (this.benchFrameSweeper) {\n clearInterval(this.benchFrameSweeper)\n this.benchFrameSweeper = null\n }\n this.benchFrameCache.clear()\n if (this.unsubMotionEvents) {\n this.unsubMotionEvents()\n this.unsubMotionEvents = null\n }\n this.lastAnalyzerDetected.clear()\n\n // Clear all dynamic onboard-analyzer subscriptions before stopping\n // the runner — `clearOnboardAnalyzer` cancels the timer then calls\n // the unsubscribe handle so no stray callbacks fire after shutdown.\n for (const deviceId of [...this.onboardAnalyzerTeardownTimers.keys(), ...this.onboardAnalyzerSubs.keys()]) {\n this.clearOnboardAnalyzer(deviceId)\n }\n\n if (this.runner) {\n this.runner.stop()\n this.runner = null\n }\n\n for (const attachment of this.attached.values()) {\n attachment.motionUnsubscribe?.()\n attachment.detectionUnsubscribe?.()\n }\n this.attached.clear()\n }\n\n // ── Synthetic bench (production-equivalent measurement) ───────────────\n\n private async cacheBenchFrame(input: CacheBenchFrameInput): Promise<CacheBenchFrameResult> {\n // Decode JPEG → raw RGB ONCE. The runner then feeds this raw buffer\n // to api.pipelineExecutor.runPipeline via the `frame` field — same\n // shape that stream-broker hands the runner during real camera\n // detection. In-process call; no Moleculer serialisation cost.\n const sharp = (await import('sharp')).default\n const jpeg = Buffer.from(input.imageBase64, 'base64')\n const { data, info } = await sharp(jpeg).raw().toBuffer({ resolveWithObject: true })\n if (info.channels !== 3) {\n throw new Error(`cacheBenchFrame: expected 3 channels (rgb), got ${info.channels}`)\n }\n const rgb = new Uint8Array(data)\n const ttlMs = Math.max(60_000, (input.ttlSeconds ?? 600) * 1000)\n const frameId = `runner-bench-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n const expiresAt = Date.now() + ttlMs\n this.benchFrameCache.set(frameId, { data: rgb, width: info.width, height: info.height, format: 'rgb', expiresAt })\n if (!this.benchFrameSweeper) {\n this.benchFrameSweeper = setInterval(() => this.sweepBenchFrameCache(), 60_000)\n this.benchFrameSweeper.unref?.()\n }\n this.ctx.logger.info('cached bench frame', {\n meta: { frameId, width: info.width, height: info.height, bytes: rgb.length, ttlMs },\n })\n return { frameId, width: info.width, height: info.height, expiresAt }\n }\n\n private async releaseBenchFrame(input: ReleaseBenchFrameInput): Promise<ReleaseBenchFrameResult> {\n return { released: this.benchFrameCache.delete(input.frameId) }\n }\n\n private sweepBenchFrameCache(): void {\n const now = Date.now()\n for (const [id, entry] of this.benchFrameCache) {\n if (entry.expiresAt < now) this.benchFrameCache.delete(id)\n }\n }\n\n private async runSyntheticBench(input: RunSyntheticBenchInput): Promise<RunSyntheticBenchResult> {\n const ctx = this.ctx\n const api = ctx.api\n if (!api) throw new Error('runSyntheticBench: ctx.api unavailable')\n ctx.logger.info('runSyntheticBench input', {\n meta: { frameId: input.frameId, parallel: input.parallel, iterations: input.iterations },\n })\n\n const cached = this.benchFrameCache.get(input.frameId)\n if (!cached) {\n throw new Error(`runSyntheticBench: frameId ${input.frameId} not cached (call cacheBenchFrame first)`)\n }\n\n const stepsToRun = input.steps.map(s => ({\n addonId: s.addonId,\n modelId: s.modelId,\n enabled: s.enabled,\n children: s.children ?? [],\n }))\n\n // Determine if we can use the fast cache path:\n // Single root step, no enabled children, and simulatePipeline not requested\n // → bypass runPipeline entirely, cache frame in Python pool once,\n // then send only 5-byte references.\n const enabledSteps = stepsToRun.filter(s => s.enabled)\n const isSingleStep = enabledSteps.length === 1\n && (!enabledSteps[0]!.children || enabledSteps[0]!.children.filter(c => c.enabled).length === 0)\n const useFastPath = isSingleStep && !input.simulatePipeline\n const rootStep = enabledSteps[0]\n\n // Ensure engine + model are loaded (same as runPipeline does)\n // Run one warmup call through the FULL path to trigger model load\n const sharedFrame: FrameInput = {\n data: cached.data,\n format: cached.format,\n width: cached.width,\n height: cached.height,\n timestamp: Date.now(),\n }\n\n let poolFrameId: number | null = null\n\n if (useFastPath && rootStep) {\n // ── Fast path: cache frame in Python, infer via 5-byte reference ──\n ctx.logger.info('synthetic bench: using Python cache path', {\n meta: { step: rootStep.addonId, model: rootStep.modelId },\n })\n\n // Cache the raw frame in the Python pool\n const cacheResult = await api.pipelineExecutor.cacheFrameInPool.mutate({\n data: new Uint8Array(cached.data.slice().buffer),\n width: cached.width,\n height: cached.height,\n format: cached.format,\n })\n poolFrameId = cacheResult.frameId\n\n // Warmup through full runPipeline path to ensure model is loaded.\n // Engine override is applied here — loads the model on the overridden\n // factory. However, inferCached bypasses the provider's override swap\n // (it goes directly to the current engineFactory). For same-engine\n // bench runs this is correct. TODO: for cross-engine override, expose\n // applyEngineOverride/restore on the cap so the cached path can use\n // the overridden factory.\n await api.pipelineExecutor.runPipeline.mutate({\n steps: stepsToRun,\n frame: sharedFrame,\n ...(input.engine ? { engine: input.engine } : {}),\n })\n // Additional warmup via cached path\n const warmupCount = input.warmup ?? 1\n for (let w = 0; w < warmupCount; w++) {\n await api.pipelineExecutor.inferCached.mutate({\n stepId: rootStep.addonId,\n frameId: poolFrameId,\n })\n }\n\n // Timed run — each call sends only 5 bytes through the pipe\n const wallTimings: number[] = []\n const inferTimings: number[] = []\n const preprocessTimings: number[] = []\n const predictTimings: number[] = []\n const batchSizes: number[] = []\n const detCounts: number[] = []\n let _n = 0\n const sessionId = input.sessionId ?? `synth-${Date.now().toString(36)}`\n const totalRuns = input.parallel * input.iterations\n\n const wallStart = performance.now()\n const worker = async (): Promise<void> => {\n for (let i = 0; i < input.iterations; i++) {\n const t0 = performance.now()\n const result = await api.pipelineExecutor.inferCached.mutate({\n stepId: rootStep.addonId,\n frameId: poolFrameId!,\n })\n const wallMs = performance.now() - t0\n const r = result as Record<string, unknown>\n const inferMs = typeof r['inferenceMs'] === 'number' ? r['inferenceMs'] as number : wallMs\n const preMs = typeof r['preprocessMs'] === 'number' ? r['preprocessMs'] as number : 0\n const predMs = typeof r['predictMs'] === 'number' ? r['predictMs'] as number : 0\n const bs = typeof r['batchSize'] === 'number' ? r['batchSize'] as number : 1\n const dets = Array.isArray(r['detections']) ? (r['detections'] as unknown[]).length : 0\n wallTimings.push(wallMs)\n inferTimings.push(inferMs)\n preprocessTimings.push(preMs)\n predictTimings.push(predMs)\n batchSizes.push(bs)\n detCounts.push(dets)\n const n = ++_n\n if (n <= 20) {\n ctx.logger.info('bench call trace (cached)', {\n meta: { n, wallMs: Math.round(wallMs), inferMs: Math.round(inferMs), preMs: Math.round(preMs * 10) / 10, predMs: Math.round(predMs * 10) / 10, bs },\n })\n }\n // Emit live progress every `parallel` completions\n if (n % Math.max(1, input.parallel) === 0) {\n const elapsed = (performance.now() - wallStart) / 1000\n const fps = elapsed > 0 ? n / elapsed : 0\n const meanCallMs = wallTimings.reduce((s, v) => s + v, 0) / wallTimings.length\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const p95 = sorted[Math.min(sorted.length - 1, Math.floor(0.95 * sorted.length))] ?? 0\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n const avgDet = detCounts.length > 0 ? totalDet / detCounts.length : 0\n const bsMean = batchSizes.reduce((s, v) => s + v, 0) / batchSizes.length\n const msg = `runs ${n}/${totalRuns} \\u00b7 ${fps.toFixed(1)} fps \\u00b7 call ${meanCallMs.toFixed(1)}ms \\u00b7 batch ${bsMean.toFixed(1)}`\n if (ctx.eventBus) {\n // Emit directly (bypassing createEvent's strict typing) so we\n // can attach structured bench stats as extra fields. The UI's\n // pipeline.progress subscription reads them from event.data.\n ctx.eventBus.emit({\n id: `bench-${n}`,\n timestamp: new Date(),\n source: { type: 'pipeline' as const, id: 'synthetic-bench' },\n category: EventCategory.PipelineProgress,\n data: {\n nodeId: 'hub', sessionId, step: 'synthetic-bench', message: msg,\n benchProgress: true, runs: n, totalRuns,\n fps: Math.round(fps * 100) / 100,\n meanMs: Math.round(meanCallMs * 100) / 100,\n p95Ms: Math.round(p95 * 100) / 100,\n inferMeanMs: Math.round((inferTimings.reduce((s, v) => s + v, 0) / inferTimings.length) * 100) / 100,\n preprocessMeanMs: Math.round((preprocessTimings.reduce((s, v) => s + v, 0) / preprocessTimings.length) * 100) / 100,\n predictMeanMs: Math.round((predictTimings.reduce((s, v) => s + v, 0) / predictTimings.length) * 100) / 100,\n batchSizeMean: Math.round(bsMean * 100) / 100,\n detPerSec: elapsed > 0 ? Math.round((totalDet / elapsed) * 100) / 100 : 0,\n avgDetections: Math.round(avgDet * 100) / 100,\n },\n })\n } else {\n ctx.logger.warn('emitProgress: NO eventBus')\n }\n }\n }\n }\n\n await Promise.all(Array.from({ length: input.parallel }, () => worker()))\n const wallSec = (performance.now() - wallStart) / 1000\n\n // Cleanup\n await api.pipelineExecutor.uncacheFrame.mutate({ frameId: poolFrameId }).catch(() => {})\n\n return this.buildBenchResult(wallTimings, inferTimings, preprocessTimings, predictTimings, batchSizes, detCounts, wallSec, 'cached')\n }\n\n // ── Slow path: full runPipeline per call (multi-step trees) ──\n ctx.logger.info('synthetic bench: using full runPipeline path', {\n meta: { steps: enabledSteps.length, simulatePipeline: !!input.simulatePipeline },\n })\n\n let _callCount = 0\n const callOnce = async (): Promise<{ wallMs: number; result: FrameResult }> => {\n const t0 = performance.now()\n const result = await api.pipelineExecutor.runPipeline.mutate({\n steps: stepsToRun,\n frame: sharedFrame,\n ...(input.engine ? { engine: input.engine } : {}),\n })\n const wallMs = performance.now() - t0\n const n = ++_callCount\n if (n <= 20) {\n ctx.logger.info('bench call trace', {\n meta: {\n n,\n wallMs: Math.round(wallMs),\n totalInferenceMs: Math.round(result.debug?.totalInferenceMs ?? 0),\n predictMs: Math.round((result.debug?.predictMs ?? 0) * 10) / 10,\n preprocessMs: Math.round((result.debug?.preprocessMs ?? 0) * 10) / 10,\n batchSize: result.debug?.batchSize ?? 1,\n },\n })\n }\n return { wallMs, result }\n }\n\n // Warmup — sequential to ensure engine is hot before timing.\n const warmupCount = input.warmup ?? 1\n for (let i = 0; i < warmupCount; i++) {\n await callOnce()\n }\n\n const wallTimings: number[] = []\n const serverWallTimings: number[] = []\n const inferTimings: number[] = []\n const preprocessTimings: number[] = []\n const predictTimings: number[] = []\n const batchSizes: number[] = []\n const detCounts: number[] = []\n const sessionId = input.sessionId ?? `synth-${Date.now().toString(36)}`\n const totalRuns = input.parallel * input.iterations\n\n const wallStart = performance.now()\n const worker = async (): Promise<void> => {\n for (let i = 0; i < input.iterations; i++) {\n const { wallMs, result } = await callOnce()\n wallTimings.push(wallMs)\n serverWallTimings.push(result.debug?.wallMs ?? 0)\n inferTimings.push(result.debug?.totalInferenceMs ?? 0)\n preprocessTimings.push(result.debug?.preprocessMs ?? 0)\n predictTimings.push(result.debug?.predictMs ?? 0)\n batchSizes.push(result.debug?.batchSize ?? 1)\n detCounts.push(result.detections?.length ?? 0)\n const n = wallTimings.length\n if (n % Math.max(1, input.parallel) === 0 && ctx.eventBus) {\n const elapsed = (performance.now() - wallStart) / 1000\n const fps = elapsed > 0 ? n / elapsed : 0\n const meanMs = wallTimings.reduce((s, v) => s + v, 0) / n\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const p95 = sorted[Math.min(sorted.length - 1, Math.floor(0.95 * sorted.length))] ?? 0\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n const bsMean = batchSizes.reduce((s, v) => s + v, 0) / n\n ctx.eventBus.emit({\n id: `bench-${n}`,\n timestamp: new Date(),\n source: { type: 'pipeline' as const, id: 'synthetic-bench' },\n category: EventCategory.PipelineProgress,\n data: {\n nodeId: 'hub', sessionId, step: 'synthetic-bench',\n message: `runs ${n}/${totalRuns} \\u00b7 ${fps.toFixed(1)} fps \\u00b7 call ${meanMs.toFixed(1)}ms \\u00b7 batch ${bsMean.toFixed(1)}`,\n benchProgress: true, runs: n, totalRuns,\n fps: Math.round(fps * 100) / 100,\n meanMs: Math.round(meanMs * 100) / 100,\n p95Ms: Math.round(p95 * 100) / 100,\n inferMeanMs: Math.round((inferTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n preprocessMeanMs: Math.round((preprocessTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n predictMeanMs: Math.round((predictTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n batchSizeMean: Math.round(bsMean * 100) / 100,\n detPerSec: elapsed > 0 ? Math.round((totalDet / elapsed) * 100) / 100 : 0,\n avgDetections: n > 0 ? Math.round((totalDet / n) * 100) / 100 : 0,\n },\n })\n }\n }\n }\n\n await Promise.all(Array.from({ length: input.parallel }, () => worker()))\n const wallSec = (performance.now() - wallStart) / 1000\n\n return this.buildBenchResult(wallTimings, inferTimings, preprocessTimings, predictTimings, batchSizes, detCounts, wallSec, 'pipeline')\n }\n\n private async buildBenchResult(\n wallTimings: number[],\n inferTimings: number[],\n preprocessTimings: number[],\n predictTimings: number[],\n batchSizes: number[],\n detCounts: number[],\n wallSec: number,\n path: string,\n ): Promise<RunSyntheticBenchResult> {\n const meanOfArr = (xs: readonly number[]): number =>\n xs.length > 0 ? xs.reduce((s, v) => s + v, 0) / xs.length : 0\n this.ctx.logger.info('synthetic bench summary', {\n meta: {\n runs: wallTimings.length,\n wallSec: Math.round(wallSec * 100) / 100,\n fps: Math.round((wallTimings.length / wallSec) * 100) / 100,\n callMeanMs: Math.round(meanOfArr(wallTimings)),\n inferMeanMs: Math.round(meanOfArr(inferTimings)),\n preprocessMeanMs: Math.round(meanOfArr(preprocessTimings)),\n predictMeanMs: Math.round(meanOfArr(predictTimings)),\n batchSizeMean: Math.round(meanOfArr(batchSizes) * 100) / 100,\n batchSizeMax: batchSizes.length > 0 ? Math.max(...batchSizes) : 0,\n },\n })\n\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const pick = (q: number): number =>\n sorted.length > 0\n ? sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))]!\n : 0\n const meanOf = (xs: readonly number[]): number =>\n xs.length > 0 ? xs.reduce((s, v) => s + v, 0) / xs.length : 0\n\n const totalRuns = wallTimings.length\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n\n return {\n runs: totalRuns,\n wallSec: Math.round(wallSec * 1000) / 1000,\n fps: wallSec > 0 ? Math.round((totalRuns / wallSec) * 100) / 100 : 0,\n detectionsPerSec: wallSec > 0 ? Math.round((totalDet / wallSec) * 100) / 100 : 0,\n avgDetections: totalRuns > 0 ? Math.round((totalDet / totalRuns) * 100) / 100 : 0,\n callMs: {\n mean: Math.round(meanOf(wallTimings) * 100) / 100,\n p50: Math.round(pick(0.5) * 100) / 100,\n p95: Math.round(pick(0.95) * 100) / 100,\n p99: Math.round(pick(0.99) * 100) / 100,\n },\n inferMs: Math.round(meanOf(inferTimings) * 100) / 100,\n preprocessMs: Math.round(meanOf(preprocessTimings) * 100) / 100,\n predictMs: Math.round(meanOf(predictTimings) * 100) / 100,\n batchSizeMean: Math.round(meanOf(batchSizes) * 100) / 100,\n batchSizeMax: batchSizes.length > 0 ? Math.max(...batchSizes) : 0,\n path,\n ...(await this.getEngineAndTuning()),\n }\n }\n\n private async getEngineAndTuning(): Promise<Pick<RunSyntheticBenchResult, 'engine' | 'tuning'>> {\n try {\n const api = this.ctx.api\n if (!api) return {}\n const [eng, tuning] = await Promise.all([\n api.pipelineExecutor.getSelectedEngine.query(),\n api.pipelineExecutor.getEffectiveTuning.query(),\n ])\n return {\n engine: eng ? { runtime: eng.runtime, backend: eng.backend, device: eng.device } : undefined,\n tuning: tuning ?? undefined,\n }\n } catch {\n return {}\n }\n }\n\n // ── IPipelineRunnerProvider implementation ────────────────────────────\n\n async attachCamera(config: RunnerCameraConfig): Promise<{ success: true }> {\n const runner = this.runner\n const ctx = this.ctx\n if (!runner || !ctx) {\n throw new Error('PipelineRunnerAddon: attachCamera called before initialize completed')\n }\n\n // Diagnostic: log the incoming config to understand why\n // subscribeMotionFrames isn't being called.\n this.ctx.logger.info('attachCamera received config', {\n tags: { deviceId: config.deviceId },\n meta: {\n motionSources: config.motionSources,\n motionSourcesType: Array.isArray(config.motionSources) ? `array(${config.motionSources.length})` : typeof config.motionSources,\n motionStreamId: config.motionStreamId,\n detectionStreamId: config.detectionStreamId,\n keys: Object.keys(config as unknown as Record<string, unknown>),\n },\n })\n\n if (this.attached.has(config.deviceId)) {\n // Idempotent: re-attach with new config replaces the existing one.\n this.detachInternal(config.deviceId)\n }\n\n runner.registerCamera(config.deviceId, {\n detectionMode: config.detectionMode,\n fps: config.detectionFps,\n motionCooldownMs: config.motionCooldownMs,\n })\n\n const attachment: AttachedCamera = {\n config,\n motionUnsubscribe: null,\n detectionUnsubscribe: null,\n }\n this.attached.set(config.deviceId, attachment)\n\n // Subscribe to the local motion stream broker only when 'analyzer'\n // is among the active motion sources — that's the source that\n // needs decoded frames flowing through the motion-detection cap.\n // 'onboard' alone arrives via the bus (ProviderMotion → orchestrator\n // → reportMotion) without needing broker frames.\n if (config.motionSources.includes('analyzer')) {\n attachment.motionUnsubscribe = await this.subscribeMotionFrames(config)\n }\n\n // Note: detection stream subscription is wired LAZILY by the\n // runner's phase machine via `handleDetectionStreamChange`, which\n // fires when the camera transitions to ACTIVE. Always-on cameras\n // transition immediately on registerCamera, so the subscribe call\n // already happened by the time we get here.\n\n const stepsCount = config.steps?.length ?? 0\n const dispatch = stepsCount > 0\n ? `runPipeline(${stepsCount}step${stepsCount === 1 ? '' : 's'})`\n : config.steps !== undefined\n ? 'skip(0steps)'\n : 'runFrame(legacy)'\n const engineLabel = config.engine ? `${config.engine.runtime}+${config.engine.backend}/${config.engine.format}` : 'default'\n this.ctx.logger.info(\n 'attachCamera',\n {\n tags: { deviceId: config.deviceId },\n meta: {\n detectionMode: config.detectionMode,\n audioMode: config.audioMode,\n motionFps: config.motionFps,\n detectionFps: config.detectionFps,\n motionSources: config.motionSources,\n dispatch,\n engine: engineLabel,\n },\n },\n )\n\n return { success: true }\n }\n\n async detachCamera(input: { deviceId: number }): Promise<{ success: true }> {\n this.detachInternal(input.deviceId)\n return { success: true }\n }\n\n async reportMotion(input: {\n deviceId: number\n detected: boolean\n source?: MotionSource\n regions?: ReadonlyArray<MotionRegion>\n }): Promise<{ success: true }> {\n this.runner?.reportMotion(input.deviceId, input.detected, input.source, input.regions)\n return { success: true }\n }\n\n /**\n * Periodic per-camera step roster dump. Once every\n * STEP_LOG_INTERVAL_MS (30s) emits one log line per attached camera\n * with the configured detection step tree + audio classifier branch\n * so an operator looking at the agent log can quickly see what each\n * camera is currently running without crossing tRPC. Skips when no\n * cameras are attached so quiet dev runs stay silent.\n */\n private logAttachedSteps(): void {\n if (this.attached.size === 0) return\n for (const [deviceId, attachment] of this.attached) {\n const cfg = attachment.config\n const detectionSteps = cfg.steps && cfg.steps.length > 0\n ? this.flattenSteps(cfg.steps).filter((s) => s.enabled)\n : []\n const detectionLabel = detectionSteps.length > 0\n ? detectionSteps.map((s) => `${s.addonId}/${s.modelId}`).join(' → ')\n : '<none>'\n const audioLabel = cfg.audio && cfg.audio.enabled\n ? `${cfg.audio.engine.runtime}/${cfg.audio.engine.backend}/${cfg.audio.modelId}`\n : '<off>'\n const engineLabel = cfg.engine\n ? `${cfg.engine.runtime}/${cfg.engine.backend}${cfg.engine.device ? `/${cfg.engine.device}` : ''}`\n : '<unset>'\n this.ctx.logger.info('Camera pipeline roster', {\n tags: { deviceId },\n meta: {\n phase: 'roster',\n intervalSec: STEP_LOG_INTERVAL_MS / 1000,\n pipelineEnabled: cfg.pipelineEnabled,\n motionSources: cfg.motionSources,\n motionFps: cfg.motionFps,\n detectionFps: cfg.detectionFps,\n engine: engineLabel,\n videoSteps: detectionLabel,\n videoStepCount: detectionSteps.length,\n audio: audioLabel,\n },\n })\n }\n }\n\n /** Recursively flatten the step tree → ordered list of every node. */\n private flattenSteps(steps: readonly PipelineStepInput[]): readonly PipelineStepInput[] {\n const out: PipelineStepInput[] = []\n const walk = (s: PipelineStepInput): void => {\n out.push(s)\n if (s.children) {\n for (const c of s.children) walk(c)\n }\n }\n for (const s of steps) walk(s)\n return out\n }\n\n private detachInternal(deviceId: number): void {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n // Tear down any dynamic onboard-analyzer subscription + its timer\n // before clearing the attachment — order matters so the timer can't\n // fire and look up a no-longer-present attachment entry.\n this.clearOnboardAnalyzer(deviceId)\n\n attachment.motionUnsubscribe?.()\n attachment.detectionUnsubscribe?.()\n this.attached.delete(deviceId)\n this.lastMotionAt.delete(deviceId)\n this.lastEmittedCameraMetrics.delete(deviceId)\n\n this.runner?.unregisterCamera(deviceId)\n this.ctx?.logger.info('detachCamera', { tags: { deviceId } })\n }\n\n /**\n * Synchronously cancel the teardown timer and call the unsubscribe\n * handle for the dynamic onboard analyzer, if one is open. Safe to\n * call when no subscription exists.\n */\n private clearOnboardAnalyzer(deviceId: number): void {\n const timer = this.onboardAnalyzerTeardownTimers.get(deviceId)\n if (timer !== undefined) {\n clearTimeout(timer)\n this.onboardAnalyzerTeardownTimers.delete(deviceId)\n }\n const unsub = this.onboardAnalyzerSubs.get(deviceId)\n if (unsub !== undefined) {\n try { unsub() } catch { /* swallow — broker may already be gone */ }\n this.onboardAnalyzerSubs.delete(deviceId)\n }\n }\n\n /**\n * Dynamic analyzer gate for onboard-motion cameras.\n *\n * Called from the `MotionOnMotionChanged` subscriber whenever\n * `source === 'onboard'`. Opens a `subscribeMotionFrames` subscription\n * the first time motion is detected (idempotent — a second `detected:true`\n * while the sub is already open is a no-op). Always re-arms the teardown\n * timer so the subscription stays open as long as motion events keep\n * arriving and tears down `motionCooldownMs` after the last event.\n *\n * No-op when:\n * - The camera is not currently attached.\n * - `shouldStartOnboardAnalyzer(config)` returns false (flag off or\n * `motionSources` already includes `'analyzer'`).\n */\n private async handleOnboardMotionAnalyzer(deviceId: number, detected: boolean): Promise<void> {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n const config = attachment.config\n if (!shouldStartOnboardAnalyzer(config)) return\n\n const log = this.ctx.logger.withTags({ deviceId })\n\n // Open the subscription on the first `detected:true` if not already open.\n if (detected && !this.onboardAnalyzerSubs.has(deviceId)) {\n const unsub = await this.subscribeMotionFrames(config)\n if (unsub) {\n this.onboardAnalyzerSubs.set(deviceId, unsub)\n log.debug('onboard-analyzer: opened motion-frame subscription')\n }\n }\n\n // (Re-)arm the teardown timer on every event — both true and false.\n // Onboard cameras may emit only `detected:true`; the cooldown timer\n // is the single closure path (matching the phase machine's design).\n const existing = this.onboardAnalyzerTeardownTimers.get(deviceId)\n if (existing !== undefined) clearTimeout(existing)\n\n const cooldownMs = config.motionCooldownMs\n const timer = setTimeout(() => {\n this.onboardAnalyzerTeardownTimers.delete(deviceId)\n const unsub = this.onboardAnalyzerSubs.get(deviceId)\n if (!unsub) return\n try { unsub() } catch { /* swallow */ }\n this.onboardAnalyzerSubs.delete(deviceId)\n log.debug('onboard-analyzer: closed after motion cooldown', { meta: { cooldownMs } })\n }, cooldownMs)\n this.onboardAnalyzerTeardownTimers.set(deviceId, timer)\n }\n\n async getLocalLoad(): Promise<RunnerLocalLoad> {\n const metrics = this.runner?.getMetrics() ?? { activeCameras: 0, throttledCameras: 0, avgInferenceTimeMs: 0, queueDepth: 0 }\n const allCameraMetrics = this.runner?.getAllCameraMetrics() ?? []\n\n let activeCameras = 0\n let totalActualFps = 0\n for (const cm of allCameraMetrics) {\n if (cm.phase === 'active') activeCameras++\n totalActualFps += cm.actualFps\n }\n\n // Phase 4: hardware probe moved off the runner.\n //\n // In the stateless-pipeline model each camera declares its own\n // engine. L3 hardware-aware balancing now lives on the orchestrator,\n // which knows `pipelines[deviceId].engine` and can match against\n // `nodes.topology.availableEngines` directly (spec Phase 3 extends\n // topology; consumed by the balancer once Phase 8 lands). The runner\n // no longer calls `pipelineExecutor.getSelectedEngine` — that cap is\n // being removed and its legacy \"one engine per node\" assumption\n // doesn't fit multi-engine nodes.\n return {\n nodeId: this.nodeId,\n attachedCameras: this.attached.size,\n activeCameras,\n avgInferenceFps: totalActualFps,\n avgInferenceTimeMs: metrics.avgInferenceTimeMs,\n queueDepthTotal: metrics.queueDepth,\n hardware: {\n hasGpu: false,\n inferenceBackend: undefined,\n },\n }\n }\n\n async getLocalMetrics(): Promise<RunnerLocalMetrics> {\n const m = this.runner?.getMetrics() ?? { activeCameras: 0, throttledCameras: 0, avgInferenceTimeMs: 0, queueDepth: 0 }\n return { nodeId: this.nodeId, ...m }\n }\n\n async getCameraMetrics(input: { deviceId: number }): Promise<CameraMetrics | null> {\n return this.runner?.getCameraMetrics(input.deviceId) ?? null\n }\n\n getAllCameraMetrics(): ReadonlyArray<{ deviceId: number } & CameraMetrics> {\n return this.runner?.getAllCameraMetrics() ?? []\n }\n\n getLocalCameras(): readonly number[] {\n return [...this.attached.keys()]\n }\n\n // ── Internal: broker subscription wiring ─────────────────────────────\n\n private async subscribeMotionFrames(config: RunnerCameraConfig): Promise<(() => void) | null> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return null\n\n const log = this.ctx.logger.withTags({ deviceId: config.deviceId })\n const api = this.ctx.api\n if (!api) {\n log.warn('subscribeMotionFrames: this.ctx.api not available')\n return null\n }\n\n // Phase 5 / D9: motion reads `gray` frames from the broker's shm ring.\n // `subscribeFrames` spins up a `frameSink: 'shm'` gray decoder session;\n // the poller drains `FrameHandle`s and reads pixels back via a\n // `FrameRingReader`, feeding the runner's existing motion queue unchanged.\n return startFrameHandlePoller({\n api,\n brokerId: `${config.deviceId}/${config.motionStreamId}`,\n format: 'gray',\n maxFps: config.motionFps,\n tag: 'motion',\n logger: log,\n // Motion analysis is intra-process and never propagates beyond\n // the runner; the handle is intentionally ignored here.\n onFrame: (frame: DecodedFrame, _handle) => {\n runner.enqueueMotionFrame(config.deviceId, frame)\n },\n })\n }\n\n private handleDetectionStreamChange(deviceId: number, action: 'subscribe' | 'unsubscribe'): void {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n if (action === 'subscribe') {\n void this.subscribeDetectionFrames(attachment.config).then(unsub => {\n attachment.detectionUnsubscribe = unsub\n })\n } else {\n attachment.detectionUnsubscribe?.()\n attachment.detectionUnsubscribe = null\n }\n }\n\n /**\n * Bridge runner phase transitions to the device's `motion` runtime\n * state + the bus. Single ownership point — every motion source\n * (analyzer, onboard, future variants) funnels through the runner's\n * phase machine and lands here.\n *\n * - Cap-state via the unified `device-state.setCapSlice` API.\n * `autoClearAfterMs = cooldownMs` on ON, `null` on OFF.\n * `lastDetectedAt` is preserved across OFF using `lastMotionAt`.\n * - Bus event `MotionOnMotionChanged` fires alongside for consumers\n * that prefer event-driven over runtime-state polling.\n */\n private handlePhaseChanged(\n deviceId: number,\n phase: CameraPhase,\n meta: PhaseTransitionMeta,\n ): void {\n const detected = phase === 'active'\n if (detected) this.lastMotionAt.set(deviceId, meta.timestamp)\n const lastDetectedAt = this.lastMotionAt.get(deviceId) ?? null\n const slice = {\n detected,\n lastDetectedAt,\n autoClearAfterMs: detected ? meta.cooldownMs : null,\n }\n void (async () => {\n const dev = await this.ctx.fetchDevice(deviceId)\n await dev.deviceState.setCapSlice({ capName: 'motion', slice })\n })().catch((err: unknown) => {\n this.ctx.logger.debug('motion cap-state write failed', {\n tags: { deviceId },\n meta: { error: errMsg(err) },\n })\n })\n // Emit `DetectionPhaseTransition` so observers (UI events panel,\n // analytics, alert center) see WATCHING ↔ ACTIVE flips on the bus.\n // Sources (Reolink firmware, this addon's analyzer path) still own\n // `MotionOnMotionChanged` — that channel carries the raw motion\n // signal. This event carries the post-phase-machine truth (after\n // cooldown debouncing). Different consumers, different cadences.\n if (this.ctx.eventBus) {\n const from: 'watching' | 'active' = detected ? 'watching' : 'active'\n const to: 'watching' | 'active' = detected ? 'active' : 'watching'\n const reason: 'motion_detected' | 'cooldown_expired' = detected ? 'motion_detected' : 'cooldown_expired'\n const payload: PhaseTransitionPayload = {\n deviceId,\n from,\n to,\n reason,\n source: meta.source,\n cooldownMs: meta.cooldownMs,\n timestamp: meta.timestamp,\n }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.DetectionPhaseTransition,\n { type: 'device', id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId },\n payload,\n ))\n }\n }\n\n private async subscribeDetectionFrames(config: RunnerCameraConfig): Promise<(() => void) | null> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return null\n\n const log = this.ctx.logger.withTags({ deviceId: config.deviceId })\n const api = this.ctx.api\n if (!api) {\n log.warn('subscribeDetectionFrames: this.ctx.api not available')\n return null\n }\n\n // Phase 5 / D9: detection reads `rgb` frames from the broker's shm ring.\n // `format: 'rgb'` is the Phase 4 hot-path switch — detection consumes raw\n // RGB24 so the Python pool skips a PIL JPEG decode. The broker's shm\n // plane runs a dedicated `rgb` decoder session feeding a per-format ring;\n // the poller reads each frame back and feeds the runner's detection queue.\n return startFrameHandlePoller({\n api,\n brokerId: `${config.deviceId}/${config.detectionStreamId}`,\n format: 'rgb',\n maxFps: config.detectionFps,\n tag: 'detection',\n logger: log,\n // Detection threads the `FrameHandle` through the runner so the\n // emitted `PipelineInferenceResultPayload` can name the shm slot\n // post-analysis (Task 8) reads pixels back from.\n onFrame: (frame: DecodedFrame, handle) => {\n runner.enqueueDetectionFrame(config.deviceId, frame, handle)\n },\n })\n }\n\n // ── Internal: inference + motion callbacks ───────────────────────────\n\n private async runInference(deviceId: number, frame: FrameInput): Promise<FrameResult | null> {\n const ctx = this.ctx\n if (!ctx) return null\n\n const log = this.ctx.logger.withTags({ deviceId })\n const api = this.ctx.api\n if (!api) {\n log.error('runInference: this.ctx.api not available')\n return null\n }\n\n const attachment = this.attached.get(deviceId)\n const camConfig = attachment?.config\n const steps = camConfig?.steps\n const engine = camConfig?.engine\n\n // Stateless-pipeline dispatch (Phase 4): the orchestrator hydrates\n // every attach with `steps` (from `pipelines[deviceId]` or the\n // cluster default). Absent steps means the camera was attached\n // before the orchestrator migrated — skip the frame rather than\n // falling back to the removed legacy `runFrame` path.\n if (!steps) {\n log.warn('runInference: no steps in attach config — skipping frame (legacy attach?)')\n return null\n }\n if (steps.length === 0) {\n return null\n }\n\n try {\n return await api.pipelineExecutor.runPipeline.mutate({\n // tRPC input is a mutable array; the attach payload holds it\n // as readonly. One spread copy at the cap boundary is cheap\n // (pipeline step trees are tiny) and keeps the type surface\n // clean without casting.\n steps: [...steps],\n frame,\n deviceId,\n ...(engine ? { engine } : {}),\n })\n } catch (err: unknown) {\n const msg = errMsg(err)\n log.error('runInference failed', { meta: { error: msg } })\n return null\n }\n }\n\n private async runMotionAnalysis(deviceId: number, frame: DecodedFrame): Promise<void> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return\n\n const log = this.ctx.logger.withTags({ deviceId })\n const motionStart = Date.now()\n try {\n const api = this.ctx.api\n if (!api) {\n log.warn('runMotionAnalysis: this.ctx.api not available')\n return\n }\n const result = await api.motionDetection.analyze.mutate({ deviceId, frame: toFrameInput(frame) })\n if (!result) return\n\n const detected = result.regions.length > 0\n\n // Emit `MotionOnMotionChanged` ONLY on transitions (analyzer runs\n // every motion frame at `motionFps`; we don't want one event per\n // frame). The runner's bus subscriber receives this and drives\n // the phase machine — same path Reolink's firmware events take.\n // No in-process `reportMotion` call here: the unified subscription\n // is the single dispatch point so onboard and analyzer go through\n // identical logic.\n const prevDetected = this.lastAnalyzerDetected.get(deviceId) ?? false\n if (detected !== prevDetected) {\n this.lastAnalyzerDetected.set(deviceId, detected)\n if (this.ctx.eventBus) {\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionOnMotionChanged,\n // EventSource wrapper kept symmetric with the onboard\n // emit (Reolink/ONVIF/etc.) so consumers grouping by\n // addonId / deviceId see consistent provenance. `nodeId`\n // identifies which cluster node ran the analyzer.\n { type: 'device', id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId },\n {\n deviceId,\n detected,\n timestamp: frame.timestamp,\n source: 'analyzer',\n ...(detected ? { regions: result.regions } : {}),\n },\n ))\n }\n }\n\n if (this.ctx.eventBus) {\n const motionPayload: MotionAnalysisPayload = {\n detected,\n regionCount: result.regions.length,\n regions: result.regions.map((r) => ({\n bbox: { x: r.bbox.x, y: r.bbox.y, w: r.bbox.w, h: r.bbox.h },\n pixelCount: r.pixelCount,\n intensity: r.intensity,\n })),\n frameWidth: frame.width,\n frameHeight: frame.height,\n analysisMs: result.analysisMs,\n }\n const analyzerSource = { type: 'device' as const, id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionAnalysis,\n analyzerSource,\n motionPayload,\n ))\n\n const zonesPayload: MotionZonesRawPayload = {\n deviceId,\n timestamp: frame.timestamp,\n zones: result.rawRegions.map((r) => ({\n bbox: [r.bbox.x, r.bbox.y, r.bbox.x + r.bbox.w, r.bbox.y + r.bbox.h] as const,\n pixelCount: r.pixelCount,\n changeScore: r.intensity / 255,\n })),\n frameSize: { width: frame.width, height: frame.height },\n }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionZonesRaw,\n analyzerSource,\n zonesPayload,\n ))\n }\n\n // frameAge = how stale the analyzed frame was (shm-ring commit → analysis\n // start). Large → decoder/ring behind, so the motion box lags real movement\n // even though emit→UI delivery is fast.\n const capturedAt = frame.capturedAt\n const motionFrameAge = typeof capturedAt === 'number' && capturedAt > 0 ? motionStart - capturedAt : -1\n runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart, motionFrameAge)\n } catch (error: unknown) {\n const msg = errMsg(error)\n log.error('runMotionAnalysis failed', { meta: { error: msg } })\n }\n }\n\n private emitInferenceResult(\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n handle: FrameHandle | undefined,\n ): void {\n const ctx = this.ctx\n if (!ctx?.eventBus) return\n\n // The new FrameResult already carries width/height/timestamp at the\n // root + debug.stepTimings / debug.totalInferenceMs. No frameMetadata\n // wrapper needed — the hub subscribes and reads `payload.frame`\n // directly. The `frameHandle` lets post-analysis consumers (Task 8)\n // resolve the original decoded frame zero-copy from the shm ring.\n // `capturedAt` (shm-commit wall-clock) is forwarded so downstream\n // consumers can measure true frame-capture → delivery latency.\n const capturedAt = frame.capturedAt\n const payload: PipelineInferenceResultPayload = {\n deviceId,\n frame: result,\n nodeId: this.nodeId,\n frameHandle: handle,\n ...(typeof capturedAt === 'number' && capturedAt > 0 ? { capturedAt } : {}),\n }\n\n this.ctx.eventBus.emit(createEvent(\n EventCategory.PipelineInferenceResult,\n { type: 'device', id: deviceId, nodeId: this.nodeId },\n payload,\n ))\n }\n\n /**\n * Emit periodic metric snapshots: one runner-load event for the\n * node + one camera-metrics event per attached camera. Subscribed\n * by admin-ui dashboards (LiveLoadPanel, NodeDetailHeader,\n * CameraStreamPanel) to drive live overlays without polling.\n *\n * Skipped when there are no cameras attached so quiet dev runs\n * don't emit needless bus traffic. The runner-load event is still\n * emitted in that case because the dashboards rely on it to see\n * \"agent reachable, idle\".\n */\n private emitMetricsSnapshot(): void {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx?.eventBus || !runner) return\n const timestamp = Date.now()\n\n // Runner-load snapshot. Skip emit when the load payload is\n // bytewise-identical to the previous one (idle node — same\n // running-cap counts, same queue depths, same memory). Heartbeat\n // re-emits every METRICS_HEARTBEAT_MS so the \"agent reachable\"\n // chip in the UI doesn't go stale.\n void this.getLocalLoad().then((load) => {\n if (!ctx.eventBus) return\n const json = JSON.stringify(load)\n const prev = this.lastEmittedRunnerLoad\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) return\n this.lastEmittedRunnerLoad = { json, emittedAt: timestamp }\n ctx.eventBus.emit(createEvent(\n EventCategory.PipelineRunnerLoadSnapshot,\n { type: 'node', id: this.nodeId, nodeId: this.nodeId },\n { nodeId: this.nodeId, load, timestamp },\n ))\n }).catch(() => {\n // Skip this tick on transient failure — next interval will\n // reconcile. No need to log: the snapshot is a soft observability\n // path, not a correctness boundary.\n })\n\n // Per-camera metrics snapshot — same defer pattern.\n if (this.attached.size === 0) return\n for (const deviceId of this.attached.keys()) {\n const metrics = runner.getCameraMetrics(deviceId)\n if (!metrics) continue\n const json = JSON.stringify(metrics)\n const prev = this.lastEmittedCameraMetrics.get(deviceId)\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) continue\n this.lastEmittedCameraMetrics.set(deviceId, { json, emittedAt: timestamp })\n ctx.eventBus.emit(createEvent(\n EventCategory.PipelineCameraMetricsSnapshot,\n { type: 'device', id: deviceId, nodeId: this.nodeId },\n { deviceId, nodeId: this.nodeId, metrics, timestamp },\n ))\n }\n }\n\n // ── Standard ICamstackAddon — three-level settings API (Phase 3) ─────\n //\n // The runner is a per-node addon with only ADDON-LEVEL settings (no\n // per-device overrides, no cluster-wide tunables). All four tuning\n // fields live in `getAddonSettings()`. When the UI surface moves in\n // Phase 9 these will be rendered under Pipeline -> node -> Settings.\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'pipeline-runner-tuning',\n title: 'Pipeline Runner',\n tab: 'scheduler',\n description: 'Per-node detection scheduler tuning. Change only if you understand the pipeline internals.',\n columns: 2,\n fields: [\n {\n type: 'slider',\n key: 'maxConcurrentInferences',\n label: 'Scheduler concurrency',\n description:\n 'Max parallel inferences the runner scheduler allows across all cameras on this node. ' +\n 'Distinct from the detection-pipeline inference-pool worker count (Pipeline tab → \"Worker concurrency\"), ' +\n 'which controls Python-side thread pool sizing inside a single inference job.',\n min: 1,\n max: 16,\n step: 1,\n default: DEFAULT_CONFIG.maxConcurrentInferences,\n showValue: true,\n },\n {\n type: 'slider',\n key: 'maxQueueDepth',\n label: 'Max queue depth',\n description: 'Maximum frames held per camera before dropping.',\n min: 5,\n max: 100,\n step: 5,\n default: DEFAULT_CONFIG.maxQueueDepth,\n showValue: true,\n },\n {\n type: 'slider',\n key: 'targetLoadPercent',\n label: 'Target load',\n description: 'Percentage of inference capacity to target before throttling FPS.',\n min: 50,\n max: 100,\n step: 5,\n default: DEFAULT_CONFIG.targetLoadPercent,\n unit: '%',\n showValue: true,\n },\n {\n type: 'slider',\n key: 'minThrottledFps',\n label: 'Min throttled FPS',\n description: 'Lowest FPS the runner will allow when load-shedding.',\n min: 1,\n max: 10,\n step: 1,\n default: DEFAULT_CONFIG.minThrottledFps,\n showValue: true,\n },\n ],\n },\n ],\n })\n }\n\n protected async onConfigChanged(): Promise<void> {\n this.runner?.updateLimits(this.config)\n this.ctx.logger.info(\n 'pipeline-runner tuning updated',\n {\n meta: {\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n },\n },\n )\n }\n}\n\n// Re-export for tests / consumers that need direct class access.\nexport { PipelineRunner } from './runner.js'\nexport { FrameQueue } from './frame-queue.js'\nexport { Semaphore } from './semaphore.js'\nexport { PipelineTimingSampler } from './timing-sampler.js'\n\n/**\n * Static `customActions` catalog — read by the addon registry at boot time\n * (before instantiating the addon class) so cross-process Moleculer routes\n * can be wired for `addons.custom.<action>` dispatch. Mirrors the pattern\n * in addon-benchmark/index.ts.\n */\nexport { pipelineRunnerBenchActions as customActions } from './bench-actions.js'\n"],"names":["toFrameInput","z.object","z.enum","z.string","z.lazy","z.boolean","z.array","z.number","warmupCount","wallTimings","inferTimings","preprocessTimings","predictTimings","batchSizes","detCounts","sessionId","totalRuns","wallStart","worker","wallSec"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/pipeline-runner/frame-queue.ts","../../src/pipeline-runner/semaphore.ts","../../src/pipeline-runner/timing-sampler.ts","../../src/pipeline-runner/runner.ts","../../src/pipeline-runner/frame-handle-poller.ts","../../src/pipeline-runner/bench-actions.ts","../../src/pipeline-runner/index.ts"],"sourcesContent":["import type { DecodedFrame } from '@camstack/types'\n\n/**\n * Latest-frame-only buffer. Keeps only the most recent item, dropping all\n * older items immediately. This ensures inference always runs on the\n * freshest available frame, never accumulating a backlog regardless of\n * inference latency.\n *\n * ## Buffer ownership (Phase 5 / D9 Task 7b)\n *\n * Frames arrive from the shm frame plane via a `FrameRingReader`, whose\n * `FrameRead.pixels` is **borrowed** — a view onto the reader's reusable\n * scratch buffer, valid only until that reader's next read (see\n * `@camstack/shm-ring`'s `FrameRead` contract). Both queue consumers retain\n * the frame PAST the next read:\n *\n * - the **motion** queue is drained by the scheduler's `tick`, not by the\n * poller that read the frame — and the poller drains a burst of handles\n * per poll, so even handle #2's read would clobber handle #1's pixels;\n * - the **detection** queue is drained asynchronously after a semaphore wait\n * and a 10s–100s ms inference.\n *\n * In both cases holding the borrowed buffer is silent corruption. So\n * `enqueue` runs the queue's injected `clone` function which copies the\n * pixels into queue-owned storage at the retention boundary. The queue is\n * latest-only (capacity 1), so this is exactly one allocation per *queued*\n * frame — bounded by the scheduler/inference cadence, NOT per *read* frame.\n * The high-frequency per-read allocation that failed the D9 perf gate is\n * eliminated; this bounded copy-on-retain is the correct cost.\n *\n * The generic parameter `T` lets the detection path queue a\n * `{ frame, handle }` entry (Task 7 — propagating the `FrameHandle`\n * through to the inference-result event), while the motion path keeps\n * queueing bare `DecodedFrame`s. Each instance provides its own\n * `clone` so the inner pixel buffer is detached at the same retention\n * boundary regardless of the wrapping entry shape.\n */\nexport class FrameQueue<T = DecodedFrame> {\n private latest: T | null = null\n private _droppedFrames = 0\n private readonly clone: (item: T) => T\n\n /**\n * `clone` runs on every {@link enqueue} to detach a queue-owned copy from\n * the (possibly borrowed) source — required by the D9 Task 7b ownership\n * contract above. Motion call sites pass {@link ownFrame}; the detection\n * path passes a clone that copies its inner frame's pixel buffer.\n */\n constructor(\n readonly maxSize: number,\n clone: (item: T) => T,\n ) {\n this.clone = clone\n }\n\n enqueue(item: T): void {\n if (this.latest !== null) {\n this._droppedFrames++\n }\n // Copy the borrowed shm scratch pixels into queue-owned storage — the\n // frame is retained past the reader's next read (see the class doc).\n this.latest = this.clone(item)\n }\n\n dequeue(): T | undefined {\n const item = this.latest ?? undefined\n this.latest = null\n return item\n }\n\n get size(): number {\n return this.latest !== null ? 1 : 0\n }\n\n get droppedFrames(): number {\n return this._droppedFrames\n }\n\n clear(): void {\n this.latest = null\n }\n}\n\n/**\n * Return a copy of `frame` whose `data` is queue-owned — a detached\n * `Buffer.from` of the (possibly borrowed) source pixels. Every other field is\n * a plain value and is carried through unchanged; any extra non-`DecodedFrame`\n * property the runner pins on a frame (e.g. `_enqueuedAt`) is preserved by the\n * spread so downstream timing accounting is unaffected.\n */\nexport function ownFrame(frame: DecodedFrame): DecodedFrame {\n return { ...frame, data: Buffer.from(frame.data) }\n}\n","/**\n * Counting semaphore with FIFO waiter queue. Used by the runner to bound\n * concurrent inference invocations across all attached cameras.\n *\n * The concurrency limit is **mutable** via `resize()` so the\n * pipeline-runner addon can hot-reload `maxConcurrentInferences` without\n * tearing down and restarting the runner. In-flight permits are\n * preserved: shrinking the limit just lowers the headroom until existing\n * releases catch up; growing it wakes pending waiters immediately.\n */\nexport class Semaphore {\n private _concurrency: number\n private _available: number\n private readonly waiters: Array<() => void> = []\n\n constructor(concurrency: number) {\n this._concurrency = concurrency\n this._available = concurrency\n }\n\n get concurrency(): number {\n return this._concurrency\n }\n\n get available(): number {\n return this._available\n }\n\n /**\n * Change the concurrency limit at runtime. Growing wakes as many\n * pending waiters as possible without exceeding the new headroom;\n * shrinking simply caps `_available` to `max(0, _available + delta)`.\n * In-flight permits are never revoked — the excess will drain\n * naturally as existing callers release.\n */\n resize(newConcurrency: number): void {\n if (newConcurrency < 1) throw new Error('Semaphore: concurrency must be >= 1')\n const delta = newConcurrency - this._concurrency\n this._concurrency = newConcurrency\n this._available = Math.max(0, this._available + delta)\n // If we just grew, wake as many waiters as new headroom allows.\n while (this._available > 0 && this.waiters.length > 0) {\n const next = this.waiters.shift()\n if (next) next()\n }\n }\n\n async acquire(): Promise<() => void> {\n if (this._available > 0) {\n this._available--\n return () => this.release()\n }\n\n return new Promise<() => void>((resolve) => {\n this.waiters.push(() => {\n this._available--\n resolve(() => this.release())\n })\n })\n }\n\n private release(): void {\n this._available++\n const next = this.waiters.shift()\n if (next) next()\n }\n}\n","import type { IScopedLogger } from '@camstack/types'\n\nconst REPORT_INTERVAL_MS = 10_000\n\n/**\n * Audio sample shape consumed by the timing sampler. Pushed by the runner's\n * audio subscription handler so that the periodic timing log includes audio\n * classification stats alongside motion + detection stats.\n */\nexport interface AudioSample {\n readonly classifyMs: number\n readonly dbfs: number\n readonly topLabel: string | null\n readonly topScore: number\n}\n\n/**\n * Runtime info labels for the timing sampler — purely cosmetic, used to tag\n * the periodic timing log lines with the active engine + addon names.\n */\nexport interface RuntimeInfo {\n readonly pipelineRuntime?: string\n readonly pipelineModels?: readonly string[]\n readonly motionAddon?: string\n readonly audioEngine?: string\n}\n\ninterface PhaseSample {\n /** ms waiting in queue before being picked */\n queueWait: number\n /** ms waiting to acquire semaphore */\n semaphoreWait: number\n /** ms for the full processFrame call (IPC + inference) */\n inference: number\n /** ms from result ready to event emitted */\n resultToEmit: number\n /** wall-clock pipeline latency: enqueue → event emitted */\n endToEnd: number\n /** wall-clock frame age when picked for inference: pickedAt − frame.capturedAt\n * (how stale the analyzed frame is vs when it entered the shm ring). -1 if unknown. */\n frameAge: number\n /** number of detections returned */\n detections: number\n}\n\n/**\n * Periodic timing-stats sampler. Accumulates per-camera samples for the\n * detection pipeline (capture → enqueue → semaphore → inference → emit),\n * motion analysis, and audio classification, and logs aggregated stats\n * every REPORT_INTERVAL_MS.\n */\nexport class PipelineTimingSampler {\n private readonly detSamples = new Map<number, PhaseSample[]>()\n private readonly motSamples = new Map<number, { ms: number; frameAge: number }[]>()\n private readonly audioSamples = new Map<number, AudioSample[]>()\n private droppedFrames = 0\n private reportTimer: ReturnType<typeof setInterval> | null = null\n private log: IScopedLogger | null = null\n runtimeInfo: RuntimeInfo = {}\n\n setLogger(logger: IScopedLogger): void {\n this.log = logger\n }\n\n start(): void {\n if (this.reportTimer) return\n this.reportTimer = setInterval(() => this.report(), REPORT_INTERVAL_MS)\n }\n\n stop(): void {\n if (this.reportTimer) {\n clearInterval(this.reportTimer)\n this.reportTimer = null\n }\n }\n\n addSample(deviceId: number, s: PhaseSample): void {\n if (!this.detSamples.has(deviceId)) this.detSamples.set(deviceId, [])\n this.detSamples.get(deviceId)!.push(s)\n }\n\n addMotionSample(deviceId: number, ms: number, frameAge = -1): void {\n if (!this.motSamples.has(deviceId)) this.motSamples.set(deviceId, [])\n this.motSamples.get(deviceId)!.push({ ms, frameAge })\n }\n\n addAudioSample(deviceId: number, s: AudioSample): void {\n if (!this.audioSamples.has(deviceId)) this.audioSamples.set(deviceId, [])\n this.audioSamples.get(deviceId)!.push(s)\n }\n\n addDrop(): void {\n this.droppedFrames++\n }\n\n private report(): void {\n if (!this.log) return\n\n const dropped = this.droppedFrames\n this.droppedFrames = 0\n\n const avg = (arr: number[]): number => arr.length > 0 ? Math.round(arr.reduce((a, b) => a + b, 0) / arr.length) : 0\n const max = (arr: number[]): number => arr.length > 0 ? Math.round(Math.max(...arr)) : 0\n const p95 = (arr: number[]): number => {\n if (arr.length === 0) return 0\n const sorted = [...arr].sort((a, b) => a - b)\n return Math.round(sorted[Math.floor(sorted.length * 0.95)] ?? sorted[sorted.length - 1]!)\n }\n\n const rt = this.runtimeInfo\n\n for (const [deviceId, det] of this.detSamples) {\n if (det.length === 0) continue\n const e2e = det.map((s) => s.endToEnd)\n const inf = det.map((s) => s.inference)\n const frameAge = det.map((s) => s.frameAge).filter((v) => v >= 0)\n const totalDet = det.reduce((s, d) => s + d.detections, 0)\n\n this.log.info(\n 'pipeline stats',\n {\n tags: { deviceId },\n meta: {\n frames: det.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n // enqueue → emit, in ms. Stage breakdown (avg) exposes WHERE the\n // latency sits: queue backlog vs semaphore contention vs inference\n // vs result-emit. inference is usually the floor (model chain).\n e2e: { avg: avg(e2e), p95: p95(e2e), max: max(e2e) },\n // Frame age (capture→inference-pick): if large, the analyzed frame is\n // already stale (decoder/ring behind), which delays the overlay\n // regardless of how fast the result is delivered.\n frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) },\n stagesMs: {\n queueWait: avg(det.map((s) => s.queueWait)),\n semaphoreWait: avg(det.map((s) => s.semaphoreWait)),\n inference: avg(inf),\n resultToEmit: avg(det.map((s) => s.resultToEmit)),\n },\n inference: { avg: avg(inf), p95: p95(inf) },\n detections: totalDet,\n dropped,\n pipelineRuntime: rt.pipelineRuntime ?? null,\n pipelineModels: rt.pipelineModels ?? null,\n },\n },\n )\n }\n this.detSamples.clear()\n\n for (const [deviceId, mot] of this.motSamples) {\n if (mot.length === 0) continue\n const ms = mot.map((s) => s.ms)\n const frameAge = mot.map((s) => s.frameAge).filter((v) => v >= 0)\n this.log.info(\n 'motion stats',\n {\n tags: { deviceId },\n meta: {\n frames: mot.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n avg: avg(ms),\n p95: p95(ms),\n max: max(ms),\n // Frame age at motion analysis (capture→analysis). Large = stale\n // input frame (decoder/ring behind) → motion box lags real movement.\n frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) },\n },\n },\n )\n }\n this.motSamples.clear()\n\n for (const [deviceId, aud] of this.audioSamples) {\n if (aud.length === 0) continue\n const classifyTimes = aud.filter((a) => a.classifyMs > 0).map((a) => a.classifyMs)\n const classified = aud.filter((a) => a.topLabel !== null)\n const topLabels = new Map<string, number>()\n for (const a of classified) {\n if (a.topLabel) topLabels.set(a.topLabel, (topLabels.get(a.topLabel) ?? 0) + 1)\n }\n const topSummary = [...topLabels.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([l, c]) => `${l}×${c}`).join(', ')\n const avgDbfs = avg(aud.map((a) => Math.round(a.dbfs)))\n\n this.log.info(\n 'audio stats',\n {\n tags: { deviceId },\n meta: {\n chunks: aud.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n classified: classified.length,\n classifyAvgMs: classifyTimes.length > 0 ? avg(classifyTimes) : 0,\n avgDbfs,\n topLabels: topSummary,\n audioEngine: rt.audioEngine ?? null,\n },\n },\n )\n }\n this.audioSamples.clear()\n }\n}\n","import type {\n FrameInput, FrameResult, OrchestratorMetrics, CameraMetrics,\n DecodedFrame, FrameHandle, CameraPhase, IScopedLogger,\n MotionSource, MotionRegion,\n PipelinePhaseMode,\n} from '@camstack/types'\nimport { FrameQueue, ownFrame } from './frame-queue.js'\nimport { Semaphore } from './semaphore.js'\nimport { PipelineTimingSampler } from './timing-sampler.js'\n\nexport type StreamType = 'motion' | 'detection'\n\n/**\n * Metadata accompanying a phase transition emitted by the runner.\n * The addon wrapping the scheduler turns this into a `motion`\n * cap-state slice + an `onMotionChanged` bus event.\n */\nexport interface PhaseTransitionMeta {\n readonly source: MotionSource\n readonly regions?: ReadonlyArray<MotionRegion>\n readonly timestamp: number\n readonly cooldownMs: number\n}\n\nexport interface RunnerConfig {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n /** Run inference on a detection frame (heavy, semaphore-limited). */\n readonly processFrame: (deviceId: number, frame: FrameInput) => Promise<FrameResult | null>\n /** Analyze a motion frame for activity (lightweight, no semaphore). */\n readonly analyzeMotion: (deviceId: number, frame: DecodedFrame) => Promise<void>\n /**\n * Fired on every phase transition (`watching ↔ active`). The\n * surrounding addon hooks this to (1) write the device's `motion`\n * cap-state slice via the unified `device-state.setCapSlice`\n * entrypoint and (2) emit `motion.onMotionChanged` on the bus.\n * Optional — test harnesses skip it.\n */\n readonly onPhaseChanged?: (\n deviceId: number,\n phase: CameraPhase,\n meta: PhaseTransitionMeta,\n ) => void\n /** Optional scoped logger; when provided, motion-gate transitions log here. */\n readonly logger?: IScopedLogger\n}\n\n/**\n * Per-camera registration. Mirrors the public RunnerCameraConfig but\n * narrowed to the fields the scheduler core actually needs (mode, fps,\n * cooldown). Broker subscription details are owned by the addon class\n * wrapping this scheduler — they don't reach the scheduler itself.\n */\nexport interface CameraRegistration {\n /** Operator-facing scheduling mode for the SW detection pipeline.\n * `'disabled'` — scheduler stays in `'idle'`, never processes frames.\n * `'always-on'` — initial phase `'active'`, no motion-gate fallback.\n * `'on-motion'` — initial phase `'watching'`, motion-gated. */\n readonly detectionMode: PipelinePhaseMode\n readonly fps: number\n readonly motionCooldownMs?: number\n}\n\nexport type ResultCallback = (\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n streamType: StreamType,\n handle: FrameHandle | undefined,\n) => Promise<void> | void\n\n/**\n * Detection-queue entry — the runner's detection path carries the\n * `FrameHandle` paired with the decoded frame end-to-end so that the\n * emitted `PipelineInferenceResultPayload` can name the same shm slot\n * downstream consumers (post-analysis) read pixels from. Motion frames\n * never propagate beyond the runner so the motion queue keeps queueing\n * bare `DecodedFrame`s.\n */\nexport interface DetectionQueueEntry {\n readonly frame: DecodedFrame\n readonly handle: FrameHandle\n}\n\nexport type DetectionStreamHandler = (\n deviceId: number,\n action: 'subscribe' | 'unsubscribe',\n) => void\n\ninterface CameraState {\n readonly registration: CameraRegistration\n readonly motionQueue: FrameQueue\n readonly detectionQueue: FrameQueue<DetectionQueueEntry>\n readonly inferenceTimes: number[]\n processedCount: number\n startTime: number\n phase: CameraPhase\n motionCooldownTimer: ReturnType<typeof setTimeout> | null\n /**\n * Source that armed the current cooldown timer. Carried into the\n * transition-to-watching event so consumers know which path was\n * the last positive signal before timeout.\n */\n lastArmedSource: MotionSource | null\n /** Regions from the most recent positive `analyzer` event — undefined for `onboard`. */\n lastArmedRegions: ReadonlyArray<MotionRegion> | undefined\n}\n\n/**\n * Clone a {@link DetectionQueueEntry} for the queue's copy-on-retain\n * boundary: the inner frame's pixel buffer is detached via\n * {@link ownFrame}, the `FrameHandle` is carried through unchanged (it's\n * a plain serialisable record with no borrowed buffers).\n */\nfunction ownDetectionEntry(entry: DetectionQueueEntry): DetectionQueueEntry {\n return { frame: ownFrame(entry.frame), handle: entry.handle }\n}\n\n// Fallback cooldown when the runner registration omits an explicit\n// `motionCooldownMs`. Aligned with the schema default in\n// `pipeline-runner.cap.ts` (motionCooldownMsField.default) so\n// always-on / synthetic registrations get the same window the\n// device-settings schema serves to operator-driven attaches.\nconst DEFAULT_MOTION_COOLDOWN_MS = 30_000\n\nfunction toFrameInput(frame: DecodedFrame): FrameInput {\n return {\n data: frame.data,\n width: frame.width,\n height: frame.height,\n format: frame.format,\n timestamp: frame.timestamp,\n }\n}\n\n/**\n * Pipeline runner scheduler — the per-node detection scheduler.\n *\n * Owns per-camera frame queues, the inference semaphore, the round-robin\n * scheduler, and the motion → detection phase machine. Inference and\n * motion analysis are delegated to caller-provided callbacks injected at\n * construction time so the scheduler stays decoupled from the surrounding\n * addon ecosystem (the addon class wrapping this scheduler injects\n * callbacks that resolve to the local pipeline-executor / motion-detection\n * caps when running in production).\n *\n * This class is intentionally framework-agnostic and side-effect-free\n * outside the callbacks — it can be unit tested with mock callbacks\n * without spinning up Moleculer or the addon framework.\n */\nexport class PipelineRunner {\n // Config is mutable (not `readonly`) because `updateLimits()` hot-reloads\n // the four tuning fields when the pipeline-runner addon's\n // `updateAddonSettings` is invoked via the new three-level settings API.\n // The callbacks (`processFrame`, `analyzeMotion`) are invariants captured\n // at construction and never changed.\n private config: RunnerConfig\n private readonly cameras = new Map<number, CameraState>()\n private readonly semaphore: Semaphore\n private readonly resultCallbacks: ResultCallback[] = []\n private readonly defaultRoundRobinKeys: number[] = []\n private defaultRoundRobinIndex = 0\n private intervalHandle: ReturnType<typeof setInterval> | null = null\n private detectionStreamHandler: DetectionStreamHandler | null = null\n private readonly logger: IScopedLogger | undefined\n readonly timingSampler = new PipelineTimingSampler()\n\n constructor(config: RunnerConfig) {\n this.config = config\n this.logger = config.logger\n this.semaphore = new Semaphore(config.maxConcurrentInferences)\n }\n\n /**\n * Hot-reload the four tuning fields without tearing down the runner.\n * - `maxConcurrentInferences`: resized on the live semaphore; in-flight\n * permits are preserved, new capacity is available immediately.\n * - `maxQueueDepth`: new `FrameQueue`s created from this point on use\n * the updated ceiling. Existing per-camera queues are not resized\n * (the FrameQueue implementation is latest-only and ignores maxSize\n * anyway — see `frame-queue.ts` — so the field is effectively a\n * metadata hint for observability).\n * - `targetLoadPercent` / `minThrottledFps`: stored for future\n * throttling logic (not yet consumed in the current runner body).\n *\n * Only keys present in the patch are overwritten; unspecified keys\n * retain their current value. Any illegal combination (e.g.\n * concurrency < 1) throws and leaves the runner unchanged.\n */\n updateLimits(patch: {\n readonly maxQueueDepth?: number\n readonly maxConcurrentInferences?: number\n readonly targetLoadPercent?: number\n readonly minThrottledFps?: number\n }): void {\n const next: RunnerConfig = {\n ...this.config,\n maxQueueDepth: patch.maxQueueDepth ?? this.config.maxQueueDepth,\n maxConcurrentInferences: patch.maxConcurrentInferences ?? this.config.maxConcurrentInferences,\n targetLoadPercent: patch.targetLoadPercent ?? this.config.targetLoadPercent,\n minThrottledFps: patch.minThrottledFps ?? this.config.minThrottledFps,\n }\n if (next.maxConcurrentInferences !== this.config.maxConcurrentInferences) {\n this.semaphore.resize(next.maxConcurrentInferences)\n }\n this.config = next\n }\n\n /** Read the current tuning fields for diagnostics / tests. */\n getLimits(): {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n } {\n return {\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n }\n }\n\n /** Set a handler called when the runner needs to subscribe/unsubscribe the detection stream. */\n onDetectionStreamChange(handler: DetectionStreamHandler): void {\n this.detectionStreamHandler = handler\n }\n\n registerCamera(deviceId: number, registration: CameraRegistration): void {\n const motionQueue = new FrameQueue(this.config.maxQueueDepth, ownFrame)\n const detectionQueue = new FrameQueue<DetectionQueueEntry>(\n this.config.maxQueueDepth,\n ownDetectionEntry,\n )\n // Map the operator-facing detectionMode to the scheduler's initial\n // phase. `'disabled'` short-circuits everything (no subscribe, no\n // round-robin slot, no detection queue activity).\n const initialPhase: CameraPhase = registration.detectionMode === 'disabled'\n ? 'idle'\n : registration.detectionMode === 'always-on'\n ? 'active'\n : 'watching'\n\n const state: CameraState = {\n registration,\n motionQueue,\n detectionQueue,\n inferenceTimes: [],\n processedCount: 0,\n startTime: Date.now(),\n phase: initialPhase,\n motionCooldownTimer: null,\n lastArmedSource: null,\n lastArmedRegions: undefined,\n }\n this.cameras.set(deviceId, state)\n\n // Round-robin scheduling applies only to `on-motion`. `always-on`\n // doesn't need a slot (always at the front of the queue).\n // `disabled` skips entirely.\n if (registration.detectionMode === 'on-motion') {\n this.defaultRoundRobinKeys.push(deviceId)\n }\n\n if (initialPhase === 'active') {\n this.detectionStreamHandler?.(deviceId, 'subscribe')\n // Always-on cameras start in `active` immediately — emit the\n // synthetic ON transition so consumers (motion cap-state +\n // bus event) see the same shape the gated path produces.\n const cooldownMs = registration.motionCooldownMs ?? DEFAULT_MOTION_COOLDOWN_MS\n this.config.onPhaseChanged?.(deviceId, 'active', {\n source: 'analyzer',\n regions: undefined,\n timestamp: Date.now(),\n cooldownMs,\n })\n }\n // `idle` (detectionMode='disabled') intentionally never emits a\n // phase event or subscribes — the runner registered the camera\n // for accounting but nothing flows.\n }\n\n unregisterCamera(deviceId: number): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n\n if (state.phase === 'active') {\n this.detectionStreamHandler?.(deviceId, 'unsubscribe')\n }\n\n state.motionQueue.clear()\n state.detectionQueue.clear()\n this.cameras.delete(deviceId)\n\n const idx = this.defaultRoundRobinKeys.indexOf(deviceId)\n if (idx !== -1) {\n this.defaultRoundRobinKeys.splice(idx, 1)\n if (this.defaultRoundRobinIndex >= this.defaultRoundRobinKeys.length) {\n this.defaultRoundRobinIndex = 0\n }\n }\n }\n\n enqueueMotionFrame(deviceId: number, frame: DecodedFrame): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n state.motionQueue.enqueue(frame)\n }\n\n enqueueDetectionFrame(\n deviceId: number,\n frame: DecodedFrame,\n handle: FrameHandle,\n ): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n if (state.phase !== 'active') return\n ;(frame as { _enqueuedAt?: number })._enqueuedAt = Date.now()\n state.detectionQueue.enqueue({ frame, handle })\n }\n\n /**\n * Report a motion event for a camera. Drives the unified phase\n * machine for both motion sources (analyzer + onboard):\n *\n * - Every `detected: true` (any source) clears + rearms the\n * cooldown timer and transitions watching → active. The same\n * timer applies regardless of which source(s) are configured;\n * concurrent sources just keep refreshing the same window.\n * - `detected: false` is a no-op. Onboard sources never send an\n * explicit clear, and the analyzer's \"false\" pulses would\n * otherwise fight the cooldown when motion paused briefly\n * during a scene. The timer is the single closure path.\n * - Timer expiry transitions active → watching.\n *\n * Always-on cameras silently ignore reportMotion calls — they're\n * already in `active` and have no cooldown.\n *\n * `source` and `regions` propagate into the phase-transition event\n * so the wrapping addon can attach them to the cap-state slice +\n * bus event.\n */\n reportMotion(\n deviceId: number,\n detected: boolean,\n source: MotionSource = 'analyzer',\n regions: ReadonlyArray<MotionRegion> | undefined = undefined,\n ): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n\n // `always-on` ignores motion (already active); `disabled` ignores\n // motion (camera registered but never subscribed to detection).\n // Only `on-motion` flows through the cooldown path.\n if (state.registration.detectionMode !== 'on-motion') return\n if (!detected) return\n\n state.lastArmedSource = source\n state.lastArmedRegions = regions\n const cooldownMs = state.registration.motionCooldownMs ?? DEFAULT_MOTION_COOLDOWN_MS\n\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n\n if (state.phase === 'watching') {\n this.transitionToActive(deviceId, state, source, regions, cooldownMs)\n }\n\n state.motionCooldownTimer = setTimeout(() => {\n state.motionCooldownTimer = null\n this.transitionToWatching(deviceId, state, cooldownMs)\n }, cooldownMs)\n }\n\n getPhase(deviceId: number): CameraPhase | undefined {\n return this.cameras.get(deviceId)?.phase\n }\n\n onResult(callback: ResultCallback): void {\n this.resultCallbacks.push(callback)\n }\n\n start(): void {\n if (this.intervalHandle !== null) return\n this.intervalHandle = setInterval(() => this.tick(), 10)\n this.timingSampler.start()\n }\n\n stop(): void {\n if (this.intervalHandle !== null) {\n clearInterval(this.intervalHandle)\n this.intervalHandle = null\n }\n this.timingSampler.stop()\n\n for (const state of this.cameras.values()) {\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n }\n }\n\n getMetrics(): OrchestratorMetrics {\n let totalQueueDepth = 0\n let totalInferenceTime = 0\n let totalInferenceCount = 0\n\n for (const state of this.cameras.values()) {\n totalQueueDepth += state.motionQueue.size + state.detectionQueue.size\n for (const t of state.inferenceTimes) {\n totalInferenceTime += t\n totalInferenceCount++\n }\n }\n\n return {\n activeCameras: this.cameras.size,\n throttledCameras: 0,\n avgInferenceTimeMs: totalInferenceCount > 0 ? totalInferenceTime / totalInferenceCount : 0,\n queueDepth: totalQueueDepth,\n }\n }\n\n getCameraMetrics(deviceId: number): CameraMetrics | undefined {\n const state = this.cameras.get(deviceId)\n if (!state) return undefined\n\n const elapsedMs = Date.now() - state.startTime\n const elapsedSec = elapsedMs / 1000\n const actualFps = elapsedSec > 0 ? state.processedCount / elapsedSec : 0\n\n const times = state.inferenceTimes\n const avgInference = times.length > 0 ? times.reduce((a, b) => a + b, 0) / times.length : 0\n\n return {\n detectionMode: state.registration.detectionMode,\n configuredFps: state.registration.fps,\n actualFps,\n queueDepth: state.motionQueue.size + state.detectionQueue.size,\n avgInferenceTimeMs: avgInference,\n droppedFrames: state.motionQueue.droppedFrames + state.detectionQueue.droppedFrames,\n phase: state.phase,\n }\n }\n\n getAllCameraMetrics(): Array<{ deviceId: number } & CameraMetrics> {\n const results: Array<{ deviceId: number } & CameraMetrics> = []\n for (const [deviceId] of this.cameras) {\n const metrics = this.getCameraMetrics(deviceId)\n if (metrics) {\n results.push({ deviceId, ...metrics })\n }\n }\n return results\n }\n\n getAttachedCameras(): readonly number[] {\n return [...this.cameras.keys()]\n }\n\n private transitionToActive(\n deviceId: number,\n state: CameraState,\n source: MotionSource,\n regions: ReadonlyArray<MotionRegion> | undefined,\n cooldownMs: number,\n ): void {\n state.phase = 'active'\n this.logger?.info('motion gate opened — phase=active', {\n tags: { deviceId },\n meta: { detectionMode: state.registration.detectionMode, source },\n })\n this.detectionStreamHandler?.(deviceId, 'subscribe')\n this.config.onPhaseChanged?.(deviceId, 'active', {\n source,\n regions,\n timestamp: Date.now(),\n cooldownMs,\n })\n }\n\n private transitionToWatching(\n deviceId: number,\n state: CameraState,\n cooldownMs: number,\n ): void {\n state.phase = 'watching'\n state.detectionQueue.clear()\n this.logger?.info('motion gate closed — phase=watching', {\n tags: { deviceId },\n meta: { lastSource: state.lastArmedSource },\n })\n this.detectionStreamHandler?.(deviceId, 'unsubscribe')\n // Last source that armed the timer carries the OFF event payload —\n // analyzer or onboard, whatever last fired before the silence\n // window expired. Regions are intentionally not propagated on\n // OFF: they describe a positive observation, not a closure.\n const source: MotionSource = state.lastArmedSource ?? 'analyzer'\n this.config.onPhaseChanged?.(deviceId, 'watching', {\n source,\n regions: undefined,\n timestamp: Date.now(),\n cooldownMs,\n })\n state.lastArmedSource = null\n state.lastArmedRegions = undefined\n }\n\n private tick(): void {\n this.drainMotionQueues()\n\n if (this.semaphore.available <= 0) return\n\n const picked = this.pickNextDetectionFrame()\n if (!picked) return\n\n const { deviceId, entry, state } = picked\n const frameInput = toFrameInput(entry.frame)\n\n void this.processWithSemaphore(deviceId, entry, frameInput, state, 'detection')\n }\n\n private drainMotionQueues(): void {\n for (const [deviceId, state] of this.cameras) {\n while (state.motionQueue.size > 0) {\n const frame = state.motionQueue.dequeue()\n if (frame) {\n void this.config.analyzeMotion(deviceId, frame)\n }\n }\n }\n }\n\n private async processWithSemaphore(\n deviceId: number,\n entry: DetectionQueueEntry,\n frameInput: FrameInput,\n state: CameraState,\n streamType: StreamType,\n ): Promise<void> {\n const pickedAt = Date.now()\n const { frame, handle } = entry\n // `frame.timestamp` is the codec PTS (shm-ring `frame.meta.pts`), NOT a\n // wall-clock time — subtracting it from Date.now() yields a ~1.7e12 garbage\n // \"latency\". `_enqueuedAt` is the wall-clock stamp written when the frame\n // entered the detection queue (see enqueueDetectionFrame); fall back to\n // `pickedAt` (also wall-clock) so all latency math stays in one time base.\n const enqueuedAt = (frame as { _enqueuedAt?: number })._enqueuedAt ?? pickedAt\n\n const release = await this.semaphore.acquire()\n const semAcquiredAt = Date.now()\n try {\n const result = await this.config.processFrame(deviceId, frameInput)\n const inferDoneAt = Date.now()\n const inferenceMs = inferDoneAt - semAcquiredAt\n\n state.inferenceTimes.push(inferenceMs)\n if (state.inferenceTimes.length > 100) {\n state.inferenceTimes.shift()\n }\n state.processedCount++\n\n if (result) {\n await this.notifyCallbacks(deviceId, frame, result, streamType, handle)\n const emittedAt = Date.now()\n\n const capturedAt = frame.capturedAt\n this.timingSampler.addSample(deviceId, {\n queueWait: pickedAt - enqueuedAt,\n semaphoreWait: semAcquiredAt - pickedAt,\n inference: inferenceMs,\n resultToEmit: emittedAt - inferDoneAt,\n // capturedAt is the shm-ring commit wall-clock; pickedAt − capturedAt\n // is how stale the frame was when inference started. <0/absent → unknown.\n frameAge: typeof capturedAt === 'number' && capturedAt > 0 ? pickedAt - capturedAt : -1,\n // Wall-clock pipeline latency: enqueue → result emitted. (Capture →\n // enqueue, i.e. RTSP decode + shm-ring write, is not measured here —\n // the shm frame carries only a PTS, no wall-clock capture stamp.)\n endToEnd: emittedAt - enqueuedAt,\n detections: result.detections?.length ?? 0,\n })\n }\n } finally {\n release()\n }\n }\n\n private async notifyCallbacks(\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n streamType: StreamType,\n handle: FrameHandle | undefined,\n ): Promise<void> {\n for (const callback of this.resultCallbacks) {\n try {\n await callback(deviceId, frame, result, streamType, handle)\n } catch {\n // Error isolation: individual callback failures don't affect others\n }\n }\n }\n\n private pickNextDetectionFrame(): {\n deviceId: number\n entry: DetectionQueueEntry\n state: CameraState\n } | null {\n for (const [deviceId, state] of this.cameras) {\n if (state.registration.detectionMode === 'always-on' && state.detectionQueue.size > 0) {\n const entry = state.detectionQueue.dequeue()!\n return { deviceId, entry, state }\n }\n }\n\n if (this.defaultRoundRobinKeys.length === 0) return null\n\n const startIndex = this.defaultRoundRobinIndex\n for (let i = 0; i < this.defaultRoundRobinKeys.length; i++) {\n const idx = (startIndex + i) % this.defaultRoundRobinKeys.length\n const deviceId = this.defaultRoundRobinKeys[idx]\n if (!deviceId) continue\n const state = this.cameras.get(deviceId)\n if (!state) continue\n\n if (state.phase === 'active' && state.detectionQueue.size > 0) {\n this.defaultRoundRobinIndex = (idx + 1) % this.defaultRoundRobinKeys.length\n const entry = state.detectionQueue.dequeue()\n if (!entry) continue\n return { deviceId, entry, state }\n }\n }\n\n return null\n }\n}\n","/**\n * `FrameHandlePoller` — the consumer-side poll loop of the shared-memory frame\n * plane (Phase 5 / D9).\n *\n * Replaces the live-object `IStreamBroker.onDecodedFrame(cb)` callback path.\n * A consumer (motion, detection):\n *\n * 1. `subscribeFrames({ brokerId, format, maxFps, tag })` over tRPC — the\n * broker spins up a `frameSink: 'shm'` decoder session for that format\n * and returns a `subscriptionId`;\n * 2. polls `pullFrameHandles({ subscriptionId, maxCount })` at the cadence\n * `maxFps` implies, draining zero-pixel `FrameHandle`s;\n * 3. feeds each handle to a `FrameRingReaderCache` which opens the named shm\n * segment and reads the pixels back zero-copy;\n * 4. on teardown, `unsubscribeFrames` + closes the cached readers.\n *\n * The loop tolerates an initially-empty stream — `pullFrameHandles` returns\n * `[]` until the broker has a source, and a recycled ring slot yields a `null`\n * pixel read (a dropped frame, the correct latest-wins behaviour).\n *\n * Boot-race tolerance: the broker for a given camStream may not be registered\n * yet when a consumer attempts to subscribe (the pipeline-runner spawns before\n * provider addons publish their cameraStreams). `subscribeFrames` retries with\n * exponential backoff (capped at `MAX_SUBSCRIBE_RETRY_BACKOFF_MS`) until it\n * succeeds or the returned teardown closure is invoked. The closure always\n * resolves to a function, even when the broker is missing; the function\n * cancels pending retries and releases any subscription acquired in flight.\n */\nimport type {\n AddonApi,\n DecodedFrame,\n FrameHandle,\n FrameHandleFormat,\n IScopedLogger,\n} from '@camstack/types'\nimport { errMsg } from '@camstack/types'\nimport { FrameRingReaderCache } from '@camstack/shm-ring'\n\n/** How many handles to drain per poll — a small burst absorbs jitter. */\nconst PULL_MAX_COUNT = 4\n\n/** Floor on the poll period so a 0/absurd `maxFps` can't busy-spin. */\nconst MIN_POLL_INTERVAL_MS = 20\n\n/** Poll period when the broker reports a non-positive cadence hint. */\nconst FALLBACK_POLL_INTERVAL_MS = 200\n\n/** First subscribe-retry delay, doubled on every subsequent failure. */\nconst INITIAL_SUBSCRIBE_RETRY_BACKOFF_MS = 250\n\n/**\n * Subscribe-retry backoff ceiling. 5 s is fast enough to recover within a\n * single roster refresh of the consuming UI and slow enough that a\n * misconfigured camStream costs ~12 op/min, not 50/s.\n */\nconst MAX_SUBSCRIBE_RETRY_BACKOFF_MS = 5000\n\n/** Inputs for a single frame subscription. */\nexport interface FrameHandlePollerOptions {\n /** The cross-addon API surface — frame handles travel over `streamBroker`. */\n readonly api: AddonApi\n /** Broker id (`<deviceId>/<streamId>`) to subscribe to. */\n readonly brokerId: string\n /** Pixel format this consumer reads (`gray` for motion, `rgb` for detection). */\n readonly format: FrameHandleFormat\n /** Reader-side cadence hint, frames/s. */\n readonly maxFps: number\n /** Short caller-identity tag for broker-side diagnostics. */\n readonly tag: string\n /**\n * Called for every successfully-read frame. The `handle` is the same\n * `FrameHandle` that produced the decoded pixels — downstream\n * consumers (detection-result post-analysis) carry it back to the\n * emitted inference-result event so the original frame can be\n * resolved zero-copy.\n */\n readonly onFrame: (frame: DecodedFrame, handle: FrameHandle) => void\n readonly logger: IScopedLogger\n}\n\ninterface Lifecycle {\n stopped: boolean\n retryTimer: ReturnType<typeof setTimeout> | undefined\n activeTeardown: (() => void) | null\n}\n\n/**\n * Subscribe to a broker's shm frame stream and start the poll loop. Always\n * resolves to a teardown closure — when the broker is not yet registered the\n * closure cancels the ongoing retry loop; when polling is active it stops the\n * loop, releases the broker subscription, and closes every shm segment the\n * reader cache opened.\n */\nexport async function startFrameHandlePoller(\n options: FrameHandlePollerOptions,\n): Promise<() => void> {\n const lifecycle: Lifecycle = {\n stopped: false,\n retryTimer: undefined,\n activeTeardown: null,\n }\n\n const teardown = (): void => {\n if (lifecycle.stopped) return\n lifecycle.stopped = true\n if (lifecycle.retryTimer) {\n clearTimeout(lifecycle.retryTimer)\n lifecycle.retryTimer = undefined\n }\n lifecycle.activeTeardown?.()\n }\n\n void subscribeWithRetry(options, lifecycle)\n return teardown\n}\n\n/**\n * Run the subscribe → poll handshake with exponential backoff on subscribe\n * failures. Resolves once the subscription is acquired (and the poll loop has\n * been started) or once `lifecycle.stopped` flips, whichever comes first.\n */\nasync function subscribeWithRetry(\n options: FrameHandlePollerOptions,\n lifecycle: Lifecycle,\n): Promise<void> {\n const { api, brokerId, format, maxFps, tag, logger } = options\n let backoffMs = INITIAL_SUBSCRIBE_RETRY_BACKOFF_MS\n let attempt = 0\n\n while (!lifecycle.stopped) {\n attempt += 1\n try {\n const result = await api.streamBroker.subscribeFrames.mutate({\n brokerId,\n format,\n maxFps,\n tag,\n })\n if (lifecycle.stopped) {\n // The caller tore the poller down while subscribe was in flight —\n // release the subscription we just acquired so the broker doesn't\n // leak a `frameSink: 'shm'` decoder session.\n await api.streamBroker.unsubscribeFrames\n .mutate({ subscriptionId: result.subscriptionId })\n .catch((err: unknown) => {\n logger.warn('frame-handle poller: late unsubscribe failed', {\n meta: { brokerId, subscriptionId: result.subscriptionId, error: errMsg(err) },\n })\n })\n return\n }\n lifecycle.activeTeardown = startPolling(options, result.subscriptionId, result.maxFps, lifecycle)\n return\n } catch (err) {\n if (lifecycle.stopped) return\n // First attempt logs at `warn` to surface boot races on a fresh hub;\n // subsequent retries drop to `debug` so a missing broker doesn't spam\n // the log at INFO/WARN forever. The retry cadence is logged once so an\n // operator can correlate the silence against the backoff schedule.\n if (attempt === 1) {\n logger.warn('frame-handle poller: subscribeFrames failed, retrying', {\n meta: { brokerId, format, tag, error: errMsg(err), nextRetryInMs: backoffMs },\n })\n } else {\n logger.debug('frame-handle poller: subscribeFrames still failing', {\n meta: { brokerId, format, tag, attempt, error: errMsg(err), nextRetryInMs: backoffMs },\n })\n }\n await sleep(backoffMs, lifecycle)\n backoffMs = Math.min(MAX_SUBSCRIBE_RETRY_BACKOFF_MS, backoffMs * 2)\n }\n }\n}\n\n/**\n * Sleep `ms` milliseconds, but resolve early when `lifecycle.stopped` flips so\n * a teardown during a retry pause doesn't have to wait out the full backoff.\n */\nfunction sleep(ms: number, lifecycle: Lifecycle): Promise<void> {\n return new Promise<void>((resolve) => {\n lifecycle.retryTimer = setTimeout(() => {\n lifecycle.retryTimer = undefined\n resolve()\n }, ms)\n })\n}\n\n/**\n * Run the steady-state poll loop for a successfully-acquired subscription.\n * Returns the teardown closure that stops the loop and releases the\n * subscription.\n */\nfunction startPolling(\n options: FrameHandlePollerOptions,\n subscriptionId: string,\n resolvedMaxFps: number,\n lifecycle: Lifecycle,\n): () => void {\n const { api, brokerId, onFrame, logger } = options\n const readers = new FrameRingReaderCache(logger)\n const pollIntervalMs =\n resolvedMaxFps > 0\n ? Math.max(MIN_POLL_INTERVAL_MS, Math.round(1000 / resolvedMaxFps))\n : FALLBACK_POLL_INTERVAL_MS\n\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const tick = async (): Promise<void> => {\n if (lifecycle.stopped) return\n try {\n const handles = await api.streamBroker.pullFrameHandles.query({\n subscriptionId,\n maxCount: PULL_MAX_COUNT,\n })\n for (const handle of handles) {\n if (lifecycle.stopped) break\n const frame = readers.read(handle)\n // A `null` read = a recycled ring slot = a dropped frame. Skip it.\n if (frame) onFrame(frame, handle)\n }\n } catch (err) {\n logger.warn('frame-handle poller: pullFrameHandles failed', {\n meta: { brokerId, subscriptionId, error: errMsg(err) },\n })\n }\n if (!lifecycle.stopped) {\n timer = setTimeout(() => void tick(), pollIntervalMs)\n }\n }\n\n // Run the first poll immediately so a consumer is not blind for a full poll\n // interval (up to ~200ms) before the first `pullFrameHandles`. `tick`\n // self-schedules every later poll, and its `lifecycle.stopped` guard means a\n // teardown that fires before/just after this first tick still stops the\n // loop cleanly.\n void tick()\n\n return () => {\n if (timer) {\n clearTimeout(timer)\n timer = undefined\n }\n readers.close()\n api.streamBroker.unsubscribeFrames\n .mutate({ subscriptionId })\n .catch((err: unknown) => {\n logger.warn('frame-handle poller: unsubscribeFrames failed', {\n meta: { brokerId, subscriptionId, error: errMsg(err) },\n })\n })\n }\n}\n","/**\n * Synthetic-bench custom actions for pipeline-runner.\n *\n * Lets the admin UI drive a production-realistic detection benchmark:\n * the runner — already co-located with detection-pipeline in the\n * `pipeline` group — invokes `api.pipelineExecutor.runPipeline` directly\n * (in-process via localProviderLink, no Moleculer hop), N workers\n * concurrent × M iterations. Returns aggregate stats.\n *\n * Why this exists: the older bench path (UI → benchmark addon → pipeline\n * addon) crosses TWO Moleculer process boundaries that the production\n * camera path does NOT have (broker / runner / detection-pipeline are\n * all in the same group). Running the loop from inside the runner gives\n * fps numbers that match what real cameras achieve.\n */\nimport { z } from 'zod'\nimport { customAction, defineCustomActions } from '@camstack/types'\n\n// Local mirror of pipeline-executor's engine-choice schema. Re-declared here\n// to avoid a cap-package coupling for one bench cap; shape is fixed by the\n// runtime contract (runtime + backend + format + optional device).\nconst BenchEngineChoiceSchema = z.object({\n runtime: z.enum(['node', 'python']),\n backend: z.string(),\n format: z.enum(['onnx', 'coreml', 'openvino', 'tflite', 'pt']),\n device: z.string().optional(),\n})\n\n// Recursive step shape — matches PipelineStepInput minimally for cap input.\ninterface BenchStepShape {\n readonly addonId: string\n readonly modelId: string\n readonly enabled: boolean\n readonly children?: readonly BenchStepShape[]\n}\nconst BenchStepSchema: z.ZodType<BenchStepShape> = z.lazy(() => z.object({\n addonId: z.string(),\n modelId: z.string(),\n enabled: z.boolean(),\n children: z.array(BenchStepSchema).optional(),\n}))\n\ninterface CacheBenchFrameInputShape {\n readonly imageBase64: string\n readonly ttlSeconds?: number\n}\ninterface CacheBenchFrameResultShape {\n readonly frameId: string\n readonly width: number\n readonly height: number\n readonly expiresAt: number\n}\nexport const CacheBenchFrameInputSchema: z.ZodType<CacheBenchFrameInputShape> = z.object({\n imageBase64: z.string(),\n ttlSeconds: z.number().int().positive().optional(),\n})\nexport const CacheBenchFrameResultSchema: z.ZodType<CacheBenchFrameResultShape> = z.object({\n frameId: z.string(),\n width: z.number(),\n height: z.number(),\n expiresAt: z.number(),\n})\n\ninterface ReleaseBenchFrameInputShape { readonly frameId: string }\ninterface ReleaseBenchFrameResultShape { readonly released: boolean }\nexport const ReleaseBenchFrameInputSchema: z.ZodType<ReleaseBenchFrameInputShape> = z.object({\n frameId: z.string(),\n})\nexport const ReleaseBenchFrameResultSchema: z.ZodType<ReleaseBenchFrameResultShape> = z.object({\n released: z.boolean(),\n})\n\ninterface RunSyntheticBenchInputShape {\n readonly frameId: string\n readonly steps: readonly BenchStepShape[]\n readonly parallel: number\n readonly iterations: number\n readonly warmup?: number\n readonly sessionId?: string\n /** When true, forces the full runPipeline path (1.2MB pipe transfer per\n * call) instead of the cached inferCached fast path. Simulates the real\n * camera detection pipeline cost including IPC overhead. */\n readonly simulatePipeline?: boolean\n readonly engine?: { readonly runtime: 'node' | 'python'; readonly backend: string; readonly format: 'onnx' | 'coreml' | 'openvino' | 'tflite' | 'pt'; readonly device?: string }\n}\nexport const RunSyntheticBenchInputSchema: z.ZodType<RunSyntheticBenchInputShape> = z.object({\n frameId: z.string(),\n steps: z.array(BenchStepSchema).min(1),\n parallel: z.number().int().min(1).max(32),\n iterations: z.number().int().min(1).max(10_000),\n warmup: z.number().int().min(0).max(100).optional(),\n sessionId: z.string().optional(),\n simulatePipeline: z.boolean().optional(),\n engine: BenchEngineChoiceSchema.optional(),\n})\n\ninterface TimingSplitShape {\n readonly mean: number\n readonly p50: number\n readonly p95: number\n readonly p99: number\n}\nconst TimingSplitSchema: z.ZodType<TimingSplitShape> = z.object({\n mean: z.number(),\n p50: z.number(),\n p95: z.number(),\n p99: z.number(),\n})\n\ninterface RunSyntheticBenchResultShape {\n readonly runs: number\n readonly wallSec: number\n readonly fps: number\n readonly detectionsPerSec: number\n readonly avgDetections: number\n readonly callMs: TimingSplitShape\n readonly inferMs: number\n readonly preprocessMs: number\n readonly predictMs: number\n readonly batchSizeMean: number\n readonly batchSizeMax: number\n readonly engine?: { readonly runtime: string; readonly backend: string; readonly device?: string }\n readonly tuning?: { readonly batchMode: string; readonly windowMs: number; readonly maxBatchSize: number; readonly concurrency: number }\n readonly path?: string\n}\nexport const RunSyntheticBenchResultSchema: z.ZodType<RunSyntheticBenchResultShape> = z.object({\n runs: z.number(),\n wallSec: z.number(),\n fps: z.number(),\n detectionsPerSec: z.number(),\n avgDetections: z.number(),\n callMs: TimingSplitSchema,\n inferMs: z.number(),\n preprocessMs: z.number(),\n predictMs: z.number(),\n batchSizeMean: z.number(),\n batchSizeMax: z.number(),\n engine: z.object({ runtime: z.string(), backend: z.string(), device: z.string().optional() }).optional(),\n tuning: z.object({ batchMode: z.string(), windowMs: z.number(), maxBatchSize: z.number(), concurrency: z.number() }).optional(),\n path: z.string().optional(),\n})\n\nexport const pipelineRunnerBenchActions = defineCustomActions({\n cacheBenchFrame: customAction(\n CacheBenchFrameInputSchema,\n CacheBenchFrameResultSchema,\n { kind: 'mutation' },\n ),\n releaseBenchFrame: customAction(\n ReleaseBenchFrameInputSchema,\n ReleaseBenchFrameResultSchema,\n { kind: 'mutation' },\n ),\n runSyntheticBench: customAction(\n RunSyntheticBenchInputSchema,\n RunSyntheticBenchResultSchema,\n { kind: 'mutation' },\n ),\n})\n\nexport type PipelineRunnerBenchActions = typeof pipelineRunnerBenchActions\n\n// Explicit TypeScript types — z.infer<> on customAction's stored input/output\n// types loses precision through the spec wrapper. Caller code uses these.\nexport interface BenchStep {\n readonly addonId: string\n readonly modelId: string\n readonly enabled: boolean\n readonly children?: readonly BenchStep[]\n}\nexport interface BenchEngineChoice {\n readonly runtime: 'node' | 'python'\n readonly backend: string\n readonly format: 'onnx' | 'coreml' | 'openvino' | 'tflite' | 'pt'\n readonly device?: string\n}\nexport interface CacheBenchFrameInput {\n readonly imageBase64: string\n readonly ttlSeconds?: number\n}\nexport interface CacheBenchFrameResult {\n readonly frameId: string\n readonly width: number\n readonly height: number\n readonly expiresAt: number\n}\nexport interface ReleaseBenchFrameInput { readonly frameId: string }\nexport interface ReleaseBenchFrameResult { readonly released: boolean }\nexport interface RunSyntheticBenchInput {\n readonly frameId: string\n readonly steps: readonly BenchStep[]\n readonly parallel: number\n readonly iterations: number\n readonly warmup?: number\n readonly sessionId?: string\n readonly simulatePipeline?: boolean\n readonly engine?: BenchEngineChoice\n}\nexport interface RunSyntheticBenchResult {\n readonly runs: number\n readonly wallSec: number\n readonly fps: number\n readonly detectionsPerSec: number\n readonly avgDetections: number\n readonly callMs: { readonly mean: number; readonly p50: number; readonly p95: number; readonly p99: number }\n readonly inferMs: number\n readonly preprocessMs: number\n readonly predictMs: number\n readonly batchSizeMean: number\n readonly batchSizeMax: number\n readonly engine?: { readonly runtime: string; readonly backend: string; readonly device?: string }\n readonly tuning?: { readonly batchMode: string; readonly windowMs: number; readonly maxBatchSize: number; readonly concurrency: number }\n readonly path?: string\n}\n","/**\n * addon-pipeline-runner — per-node detection scheduler.\n *\n * Lives on every \"vision node\" (hub if hub is one, every agent that runs\n * decoders/cameras). Receives camera assignments from the hub-side\n * `addon-pipeline-orchestrator` via cap calls, owns the per-camera frame\n * queues + semaphore + motion → detection phase machine, calls the local\n * `motion-detection` and `pipeline-executor` capabilities for the actual\n * work, and emits typed events on the bus\n * (`detection.motion-analysis`, `pipeline.inference-result`, `pipeline.trace`).\n *\n * Design notes:\n *\n * - All cap inputs/outputs are serializable so the runner is transparently\n * routable across the cluster: when the runner is co-located with the\n * broker/decoder/motion/detection (the default and recommended deploy),\n * capability lookups return live singletons and frame data never crosses\n * a process boundary. When a deploy splits the runner from the executor\n * (rare), the lookup returns null and the runner falls back to\n * `this.ctx.api` (broker proxy) at the cost of serialization.\n *\n * - The scheduler core (`PipelineRunner`) is callback-driven and framework\n * agnostic. The addon class injects the inference + motion callbacks at\n * construction time, capturing references to the relevant providers\n * lazily on each invocation.\n *\n * - Result post-processing (analysis pipeline, class filters, notifications)\n * is intentionally NOT done by the runner. The runner emits the raw\n * inference output as a `pipeline.inference-result` event on the bus;\n * a hub-side subscriber consumes the event and runs the post-processing\n * pipeline. This keeps the runner stateless w.r.t. detection-side\n * business logic and makes it possible to deploy the post-processor\n * independently from the inference workers.\n */\nimport type {\n IPipelineRunnerProvider, RunnerCameraConfig, RunnerLocalLoad, RunnerLocalMetrics,\n CameraMetrics, FrameInput, FrameResult, DecodedFrame, FrameHandle,\n PipelineInferenceResultPayload, MotionAnalysisPayload, MotionZonesRawPayload, PhaseTransitionPayload,\n CameraPhase, MotionSource, MotionRegion,\n} from '@camstack/types'\nimport { BaseAddon, EventCategory, createEvent, pipelineRunnerCapability, errMsg, makeSourceBrokerId } from '@camstack/types'\nimport type { AddonInitResult } from '@camstack/types'\nimport { PipelineRunner, type PhaseTransitionMeta } from './runner.js'\nimport { startFrameHandlePoller } from './frame-handle-poller.js'\nimport {\n pipelineRunnerBenchActions,\n type PipelineRunnerBenchActions,\n type CacheBenchFrameInput,\n type CacheBenchFrameResult,\n type ReleaseBenchFrameInput,\n type ReleaseBenchFrameResult,\n type RunSyntheticBenchInput,\n type RunSyntheticBenchResult,\n} from './bench-actions.js'\n\ninterface RunnerAddonConfig {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n}\n\nconst DEFAULT_CONFIG: RunnerAddonConfig = {\n maxQueueDepth: 30,\n // CoreML window accumulator coalesces concurrent calls into a single\n // model.predict([list]) — the more in-flight, the larger the batch and\n // the higher the per-frame throughput. With concurrency=2 the window\n // never fills past batch=2, capping the pool at ~50 fps single-node.\n // 16 matches the slider ceiling and lines up with bench numbers\n // (parallel=16 hits batch=7-8/8, sustaining ~140 fps full path).\n maxConcurrentInferences: 16,\n targetLoadPercent: 80,\n minThrottledFps: 1,\n}\n\n/**\n * Camera attachment record kept by the runner addon. Holds the original\n * RunnerCameraConfig for introspection via `getLocalCameras` plus the\n * live broker subscription handles needed to tear down on detach.\n */\ninterface AttachedCamera {\n readonly config: RunnerCameraConfig\n motionUnsubscribe: (() => void) | null\n detectionUnsubscribe: (() => void) | null\n}\n\n/**\n * Pure decision helper: returns `true` when a dynamic frame-diff analyzer\n * subscription should be opened for an onboard-motion camera.\n *\n * Conditions (all must hold):\n * - `onboardMotionDrivesAnalyzer` flag is enabled on the config.\n * - `motionSources` does NOT already include `'analyzer'` — if it does,\n * the analyzer runs continuously and there is nothing to gate.\n *\n * Extracted as a pure function so the unit suite can exercise the decision\n * logic without instantiating the full addon.\n */\nexport function shouldStartOnboardAnalyzer(config: RunnerCameraConfig): boolean {\n if (!config.onboardMotionDrivesAnalyzer) return false\n if (config.motionSources.includes('analyzer')) return false\n return true\n}\n\nfunction toFrameInput(frame: DecodedFrame): FrameInput {\n return {\n data: frame.data,\n width: frame.width,\n height: frame.height,\n format: frame.format,\n timestamp: frame.timestamp,\n }\n}\n\n/**\n * Interval for periodic metric snapshot emission. ~1 Hz strikes the\n * balance between UI freshness (overlay phase / fps numbers feel live)\n * and bus traffic (one snapshot per attached camera + one per node\n * load is well under 100 events/s even at 50 cameras).\n */\nconst METRICS_SNAPSHOT_INTERVAL_MS = 1_000\n/**\n * Force a metrics-snapshot emit at least every 30s even when the\n * payload hasn't changed — gives the UI's \"agent reachable\" chip a\n * heartbeat without the per-tick spam an unconditional emit\n * produces. Picked so a 5-minute idle window emits ~10 events\n * instead of ~300.\n */\nconst METRICS_HEARTBEAT_MS = 30_000\n\nexport default class PipelineRunnerAddon extends BaseAddon<RunnerAddonConfig> implements IPipelineRunnerProvider {\n private runner: PipelineRunner | null = null\n private readonly attached = new Map<number, AttachedCamera>()\n private nodeId = 'unknown'\n private metricsSnapshotTimer: ReturnType<typeof setInterval> | null = null\n private unsubMotionEvents: (() => void) | null = null\n /** Last analyzer-detected state per device — gates the\n * `MotionOnMotionChanged` emit in `runMotionAnalysis` to transitions\n * only (otherwise we'd emit on every analyzer frame). */\n private readonly lastAnalyzerDetected = new Map<number, boolean>()\n /**\n * Last positive motion timestamp per device — preserved across the\n * OFF transition so the motion runtime-state slice keeps a stable\n * `lastDetectedAt` after the cooldown closes the phase. Cleared on\n * detach.\n */\n private readonly lastMotionAt = new Map<number, number>()\n\n /**\n * Dynamic analyzer subscriptions opened on `MotionOnMotionChanged\n * source:'onboard'` when `onboardMotionDrivesAnalyzer === true`. Each\n * entry is the unsubscribe handle returned by `subscribeMotionFrames`.\n * Cleared on teardown timer fire, detach, and shutdown.\n */\n private readonly onboardAnalyzerSubs = new Map<number, () => void>()\n /**\n * Teardown timers that close the dynamic analyzer subscription after\n * `motionCooldownMs` without a new motion event. Re-armed on every\n * `MotionOnMotionChanged source:'onboard'` call so the sub stays open\n * while motion persists.\n */\n private readonly onboardAnalyzerTeardownTimers = new Map<number, ReturnType<typeof setTimeout>>()\n\n /**\n * Snapshot-equality cache for metrics-snapshot defer. The runner\n * fires per-camera metrics every `METRICS_SNAPSHOT_INTERVAL_MS`;\n * for an idle camera (no inference, queue empty, fps=0) every tick\n * carries an identical payload. We skip the bus emit when the\n * payload deep-equals the previous one so the events tab + remote\n * subscribers stop seeing 60 metrics-snapshots/min/camera that\n * convey nothing. A periodic heartbeat re-emits every\n * METRICS_HEARTBEAT_MS so consumers know the runner is still\n * alive.\n */\n private readonly lastEmittedCameraMetrics = new Map<number, { json: string; emittedAt: number }>()\n private lastEmittedRunnerLoad: { json: string; emittedAt: number } | null = null\n\n /**\n * In-memory bench-frame cache (decoded JPEG bytes). Populated by the\n * `cacheBenchFrame` custom action. Fed into the synthetic-bench loop\n * via the `frame: FrameInput` shape that mirrors what stream-broker\n * delivers to this very addon during real camera detection.\n */\n private readonly benchFrameCache = new Map<string, {\n readonly data: Uint8Array\n readonly width: number\n readonly height: number\n readonly format: 'rgb'\n readonly expiresAt: number\n }>()\n private benchFrameSweeper: ReturnType<typeof setInterval> | null = null\n\n constructor() { super({ ...DEFAULT_CONFIG }) }\n\n protected async onInitialize(): Promise<AddonInitResult<PipelineRunnerBenchActions>> {\n // Use the cluster node id (hub, dev-agent-0, etc.) for dispatch routing,\n // not the addon id (pipeline-runner). The orchestrator uses this to track\n // which cluster node a camera is assigned to.\n // When the runner lives in a group-runner subprocess the broker\n // nodeID is hierarchical (`hub/pipeline`); strip the suffix so\n // emitted events / cap calls refer to the cluster-visible parent\n // node. In-process boot leaves the value untouched.\n const raw = this.ctx.kernel.localNodeId ?? this.ctx.id\n this.nodeId = raw.includes('/') ? raw.split('/')[0]! : raw\n\n this.runner = new PipelineRunner({\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n processFrame: (deviceId, frame) => this.runInference(deviceId, frame),\n analyzeMotion: (deviceId, frame) => this.runMotionAnalysis(deviceId, frame),\n onPhaseChanged: (deviceId, phase, meta) => this.handlePhaseChanged(deviceId, phase, meta),\n logger: this.ctx.logger,\n })\n\n this.runner.timingSampler.setLogger(this.ctx.logger.child('timing'))\n\n // Hook the per-camera detection-stream subscribe/unsubscribe lifecycle\n // into the local broker. The runner's phase machine triggers this\n // handler when a camera transitions WATCHING ↔ ACTIVE.\n this.runner.onDetectionStreamChange((deviceId, action) => {\n this.handleDetectionStreamChange(deviceId, action)\n })\n\n // Emit raw inference results to the bus. Hub-side post-processors\n // (analysis pipeline, class filters, notifications) consume these\n // and emit the post-processed `detection.result` event. The\n // `handle` is the shm-ring `FrameHandle` the runner read pixels\n // from — forwarded onto the emitted event so post-analysis\n // consumers (Task 8) can resolve the same frame zero-copy.\n this.runner.onResult(async (deviceId, frame, result, _streamType, handle) => {\n this.emitInferenceResult(deviceId, frame, result, handle)\n })\n\n this.runner.start()\n this.ctx.logger.info(\n 'Pipeline runner started',\n {\n tags: { nodeId: this.nodeId },\n meta: {\n maxConcurrent: this.config.maxConcurrentInferences,\n queueDepth: this.config.maxQueueDepth,\n },\n },\n )\n\n // Unified motion event subscription. Sources (Reolink firmware push\n // events emitting `source: 'onboard'`, this runner's own analyzer\n // path emitting `source: 'analyzer'` from `runMotionAnalysis`) all\n // funnel through `MotionOnMotionChanged`. The phase machine reacts\n // via `reportMotion` regardless of source — `motionSources`\n // selector at the runner-config level still gates which sources\n // trigger detection downstream.\n if (this.ctx.eventBus) {\n this.unsubMotionEvents = this.ctx.eventBus.subscribe(\n { category: EventCategory.MotionOnMotionChanged },\n (event) => {\n const data = event.data\n const deviceId = data.deviceId\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n const source = data.source\n // Dynamic analyzer gate: when the camera uses onboard-only\n // motion, open/close the frame-diff subscription on demand so\n // `runMotionAnalysis` runs (and emits MotionZonesRaw) during\n // active-motion windows without holding the substream open\n // continuously. Runs regardless of the `motionSources` gate\n // below — the dynamic sub is a side-channel that does NOT feed\n // the phase machine (onboard remains the sole driver).\n if (source === 'onboard') {\n void this.handleOnboardMotionAnalyzer(deviceId, data.detected)\n }\n if (!attachment.config.motionSources.includes(source)) return\n this.runner?.reportMotion(\n deviceId,\n data.detected,\n source,\n data.regions ? [...data.regions] : undefined,\n )\n },\n )\n }\n\n // Periodic metric-snapshot emission: one runner-load event +\n // one per-camera metrics event per attached camera per tick.\n // UI dashboards subscribe to these instead of polling the cap.\n this.metricsSnapshotTimer = setInterval(\n () => this.emitMetricsSnapshot(),\n METRICS_SNAPSHOT_INTERVAL_MS,\n )\n\n return {\n providers: [{ capability: pipelineRunnerCapability, provider: this }],\n customActions: pipelineRunnerBenchActions,\n actionHandlers: {\n cacheBenchFrame: async (input) => this.cacheBenchFrame(input),\n releaseBenchFrame: async (input) => this.releaseBenchFrame(input),\n runSyntheticBench: async (input) => this.runSyntheticBench(input),\n },\n }\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.metricsSnapshotTimer) {\n clearInterval(this.metricsSnapshotTimer)\n this.metricsSnapshotTimer = null\n }\n if (this.benchFrameSweeper) {\n clearInterval(this.benchFrameSweeper)\n this.benchFrameSweeper = null\n }\n this.benchFrameCache.clear()\n if (this.unsubMotionEvents) {\n this.unsubMotionEvents()\n this.unsubMotionEvents = null\n }\n this.lastAnalyzerDetected.clear()\n\n // Clear all dynamic onboard-analyzer subscriptions before stopping\n // the runner — `clearOnboardAnalyzer` cancels the timer then calls\n // the unsubscribe handle so no stray callbacks fire after shutdown.\n for (const deviceId of [...this.onboardAnalyzerTeardownTimers.keys(), ...this.onboardAnalyzerSubs.keys()]) {\n this.clearOnboardAnalyzer(deviceId)\n }\n\n if (this.runner) {\n this.runner.stop()\n this.runner = null\n }\n\n for (const attachment of this.attached.values()) {\n attachment.motionUnsubscribe?.()\n attachment.detectionUnsubscribe?.()\n }\n this.attached.clear()\n }\n\n // ── Synthetic bench (production-equivalent measurement) ───────────────\n\n private async cacheBenchFrame(input: CacheBenchFrameInput): Promise<CacheBenchFrameResult> {\n // Decode JPEG → raw RGB ONCE. The runner then feeds this raw buffer\n // to api.pipelineExecutor.runPipeline via the `frame` field — same\n // shape that stream-broker hands the runner during real camera\n // detection. In-process call; no Moleculer serialisation cost.\n const sharp = (await import('sharp')).default\n const jpeg = Buffer.from(input.imageBase64, 'base64')\n const { data, info } = await sharp(jpeg).raw().toBuffer({ resolveWithObject: true })\n if (info.channels !== 3) {\n throw new Error(`cacheBenchFrame: expected 3 channels (rgb), got ${info.channels}`)\n }\n const rgb = new Uint8Array(data)\n const ttlMs = Math.max(60_000, (input.ttlSeconds ?? 600) * 1000)\n const frameId = `runner-bench-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n const expiresAt = Date.now() + ttlMs\n this.benchFrameCache.set(frameId, { data: rgb, width: info.width, height: info.height, format: 'rgb', expiresAt })\n if (!this.benchFrameSweeper) {\n this.benchFrameSweeper = setInterval(() => this.sweepBenchFrameCache(), 60_000)\n this.benchFrameSweeper.unref?.()\n }\n this.ctx.logger.info('cached bench frame', {\n meta: { frameId, width: info.width, height: info.height, bytes: rgb.length, ttlMs },\n })\n return { frameId, width: info.width, height: info.height, expiresAt }\n }\n\n private async releaseBenchFrame(input: ReleaseBenchFrameInput): Promise<ReleaseBenchFrameResult> {\n return { released: this.benchFrameCache.delete(input.frameId) }\n }\n\n private sweepBenchFrameCache(): void {\n const now = Date.now()\n for (const [id, entry] of this.benchFrameCache) {\n if (entry.expiresAt < now) this.benchFrameCache.delete(id)\n }\n }\n\n private async runSyntheticBench(input: RunSyntheticBenchInput): Promise<RunSyntheticBenchResult> {\n const ctx = this.ctx\n const api = ctx.api\n if (!api) throw new Error('runSyntheticBench: ctx.api unavailable')\n ctx.logger.info('runSyntheticBench input', {\n meta: { frameId: input.frameId, parallel: input.parallel, iterations: input.iterations },\n })\n\n const cached = this.benchFrameCache.get(input.frameId)\n if (!cached) {\n throw new Error(`runSyntheticBench: frameId ${input.frameId} not cached (call cacheBenchFrame first)`)\n }\n\n const stepsToRun = input.steps.map(s => ({\n addonId: s.addonId,\n modelId: s.modelId,\n enabled: s.enabled,\n children: s.children ?? [],\n }))\n\n // Determine if we can use the fast cache path:\n // Single root step, no enabled children, and simulatePipeline not requested\n // → bypass runPipeline entirely, cache frame in Python pool once,\n // then send only 5-byte references.\n const enabledSteps = stepsToRun.filter(s => s.enabled)\n const isSingleStep = enabledSteps.length === 1\n && (!enabledSteps[0]!.children || enabledSteps[0]!.children.filter(c => c.enabled).length === 0)\n const useFastPath = isSingleStep && !input.simulatePipeline\n const rootStep = enabledSteps[0]\n\n // Ensure engine + model are loaded (same as runPipeline does)\n // Run one warmup call through the FULL path to trigger model load\n const sharedFrame: FrameInput = {\n data: cached.data,\n format: cached.format,\n width: cached.width,\n height: cached.height,\n timestamp: Date.now(),\n }\n\n let poolFrameId: number | null = null\n\n if (useFastPath && rootStep) {\n // ── Fast path: cache frame in Python, infer via 5-byte reference ──\n ctx.logger.info('synthetic bench: using Python cache path', {\n meta: { step: rootStep.addonId, model: rootStep.modelId },\n })\n\n // Cache the raw frame in the Python pool\n const cacheResult = await api.pipelineExecutor.cacheFrameInPool.mutate({\n data: new Uint8Array(cached.data.slice().buffer),\n width: cached.width,\n height: cached.height,\n format: cached.format,\n })\n poolFrameId = cacheResult.frameId\n\n // Warmup through full runPipeline path to ensure model is loaded.\n // Engine override is applied here — loads the model on the overridden\n // factory. However, inferCached bypasses the provider's override swap\n // (it goes directly to the current engineFactory). For same-engine\n // bench runs this is correct. TODO: for cross-engine override, expose\n // applyEngineOverride/restore on the cap so the cached path can use\n // the overridden factory.\n await api.pipelineExecutor.runPipeline.mutate({\n steps: stepsToRun,\n frame: sharedFrame,\n ...(input.engine ? { engine: input.engine } : {}),\n })\n // Additional warmup via cached path\n const warmupCount = input.warmup ?? 1\n for (let w = 0; w < warmupCount; w++) {\n await api.pipelineExecutor.inferCached.mutate({\n stepId: rootStep.addonId,\n frameId: poolFrameId,\n })\n }\n\n // Timed run — each call sends only 5 bytes through the pipe\n const wallTimings: number[] = []\n const inferTimings: number[] = []\n const preprocessTimings: number[] = []\n const predictTimings: number[] = []\n const batchSizes: number[] = []\n const detCounts: number[] = []\n let _n = 0\n const sessionId = input.sessionId ?? `synth-${Date.now().toString(36)}`\n const totalRuns = input.parallel * input.iterations\n\n const wallStart = performance.now()\n const worker = async (): Promise<void> => {\n for (let i = 0; i < input.iterations; i++) {\n const t0 = performance.now()\n const result = await api.pipelineExecutor.inferCached.mutate({\n stepId: rootStep.addonId,\n frameId: poolFrameId!,\n })\n const wallMs = performance.now() - t0\n const r = result as Record<string, unknown>\n const inferMs = typeof r['inferenceMs'] === 'number' ? r['inferenceMs'] as number : wallMs\n const preMs = typeof r['preprocessMs'] === 'number' ? r['preprocessMs'] as number : 0\n const predMs = typeof r['predictMs'] === 'number' ? r['predictMs'] as number : 0\n const bs = typeof r['batchSize'] === 'number' ? r['batchSize'] as number : 1\n const dets = Array.isArray(r['detections']) ? (r['detections'] as unknown[]).length : 0\n wallTimings.push(wallMs)\n inferTimings.push(inferMs)\n preprocessTimings.push(preMs)\n predictTimings.push(predMs)\n batchSizes.push(bs)\n detCounts.push(dets)\n const n = ++_n\n if (n <= 20) {\n ctx.logger.info('bench call trace (cached)', {\n meta: { n, wallMs: Math.round(wallMs), inferMs: Math.round(inferMs), preMs: Math.round(preMs * 10) / 10, predMs: Math.round(predMs * 10) / 10, bs },\n })\n }\n // Emit live progress every `parallel` completions\n if (n % Math.max(1, input.parallel) === 0) {\n const elapsed = (performance.now() - wallStart) / 1000\n const fps = elapsed > 0 ? n / elapsed : 0\n const meanCallMs = wallTimings.reduce((s, v) => s + v, 0) / wallTimings.length\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const p95 = sorted[Math.min(sorted.length - 1, Math.floor(0.95 * sorted.length))] ?? 0\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n const avgDet = detCounts.length > 0 ? totalDet / detCounts.length : 0\n const bsMean = batchSizes.reduce((s, v) => s + v, 0) / batchSizes.length\n const msg = `runs ${n}/${totalRuns} \\u00b7 ${fps.toFixed(1)} fps \\u00b7 call ${meanCallMs.toFixed(1)}ms \\u00b7 batch ${bsMean.toFixed(1)}`\n if (ctx.eventBus) {\n // Emit directly (bypassing createEvent's strict typing) so we\n // can attach structured bench stats as extra fields. The UI's\n // pipeline.progress subscription reads them from event.data.\n ctx.eventBus.emit({\n id: `bench-${n}`,\n timestamp: new Date(),\n source: { type: 'pipeline' as const, id: 'synthetic-bench' },\n category: EventCategory.PipelineProgress,\n data: {\n nodeId: 'hub', sessionId, step: 'synthetic-bench', message: msg,\n benchProgress: true, runs: n, totalRuns,\n fps: Math.round(fps * 100) / 100,\n meanMs: Math.round(meanCallMs * 100) / 100,\n p95Ms: Math.round(p95 * 100) / 100,\n inferMeanMs: Math.round((inferTimings.reduce((s, v) => s + v, 0) / inferTimings.length) * 100) / 100,\n preprocessMeanMs: Math.round((preprocessTimings.reduce((s, v) => s + v, 0) / preprocessTimings.length) * 100) / 100,\n predictMeanMs: Math.round((predictTimings.reduce((s, v) => s + v, 0) / predictTimings.length) * 100) / 100,\n batchSizeMean: Math.round(bsMean * 100) / 100,\n detPerSec: elapsed > 0 ? Math.round((totalDet / elapsed) * 100) / 100 : 0,\n avgDetections: Math.round(avgDet * 100) / 100,\n },\n })\n } else {\n ctx.logger.warn('emitProgress: NO eventBus')\n }\n }\n }\n }\n\n await Promise.all(Array.from({ length: input.parallel }, () => worker()))\n const wallSec = (performance.now() - wallStart) / 1000\n\n // Cleanup\n await api.pipelineExecutor.uncacheFrame.mutate({ frameId: poolFrameId }).catch(() => {})\n\n return this.buildBenchResult(wallTimings, inferTimings, preprocessTimings, predictTimings, batchSizes, detCounts, wallSec, 'cached')\n }\n\n // ── Slow path: full runPipeline per call (multi-step trees) ──\n ctx.logger.info('synthetic bench: using full runPipeline path', {\n meta: { steps: enabledSteps.length, simulatePipeline: !!input.simulatePipeline },\n })\n\n let _callCount = 0\n const callOnce = async (): Promise<{ wallMs: number; result: FrameResult }> => {\n const t0 = performance.now()\n const result = await api.pipelineExecutor.runPipeline.mutate({\n steps: stepsToRun,\n frame: sharedFrame,\n ...(input.engine ? { engine: input.engine } : {}),\n })\n const wallMs = performance.now() - t0\n const n = ++_callCount\n if (n <= 20) {\n ctx.logger.info('bench call trace', {\n meta: {\n n,\n wallMs: Math.round(wallMs),\n totalInferenceMs: Math.round(result.debug?.totalInferenceMs ?? 0),\n predictMs: Math.round((result.debug?.predictMs ?? 0) * 10) / 10,\n preprocessMs: Math.round((result.debug?.preprocessMs ?? 0) * 10) / 10,\n batchSize: result.debug?.batchSize ?? 1,\n },\n })\n }\n return { wallMs, result }\n }\n\n // Warmup — sequential to ensure engine is hot before timing.\n const warmupCount = input.warmup ?? 1\n for (let i = 0; i < warmupCount; i++) {\n await callOnce()\n }\n\n const wallTimings: number[] = []\n const serverWallTimings: number[] = []\n const inferTimings: number[] = []\n const preprocessTimings: number[] = []\n const predictTimings: number[] = []\n const batchSizes: number[] = []\n const detCounts: number[] = []\n const sessionId = input.sessionId ?? `synth-${Date.now().toString(36)}`\n const totalRuns = input.parallel * input.iterations\n\n const wallStart = performance.now()\n const worker = async (): Promise<void> => {\n for (let i = 0; i < input.iterations; i++) {\n const { wallMs, result } = await callOnce()\n wallTimings.push(wallMs)\n serverWallTimings.push(result.debug?.wallMs ?? 0)\n inferTimings.push(result.debug?.totalInferenceMs ?? 0)\n preprocessTimings.push(result.debug?.preprocessMs ?? 0)\n predictTimings.push(result.debug?.predictMs ?? 0)\n batchSizes.push(result.debug?.batchSize ?? 1)\n detCounts.push(result.detections?.length ?? 0)\n const n = wallTimings.length\n if (n % Math.max(1, input.parallel) === 0 && ctx.eventBus) {\n const elapsed = (performance.now() - wallStart) / 1000\n const fps = elapsed > 0 ? n / elapsed : 0\n const meanMs = wallTimings.reduce((s, v) => s + v, 0) / n\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const p95 = sorted[Math.min(sorted.length - 1, Math.floor(0.95 * sorted.length))] ?? 0\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n const bsMean = batchSizes.reduce((s, v) => s + v, 0) / n\n ctx.eventBus.emit({\n id: `bench-${n}`,\n timestamp: new Date(),\n source: { type: 'pipeline' as const, id: 'synthetic-bench' },\n category: EventCategory.PipelineProgress,\n data: {\n nodeId: 'hub', sessionId, step: 'synthetic-bench',\n message: `runs ${n}/${totalRuns} \\u00b7 ${fps.toFixed(1)} fps \\u00b7 call ${meanMs.toFixed(1)}ms \\u00b7 batch ${bsMean.toFixed(1)}`,\n benchProgress: true, runs: n, totalRuns,\n fps: Math.round(fps * 100) / 100,\n meanMs: Math.round(meanMs * 100) / 100,\n p95Ms: Math.round(p95 * 100) / 100,\n inferMeanMs: Math.round((inferTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n preprocessMeanMs: Math.round((preprocessTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n predictMeanMs: Math.round((predictTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n batchSizeMean: Math.round(bsMean * 100) / 100,\n detPerSec: elapsed > 0 ? Math.round((totalDet / elapsed) * 100) / 100 : 0,\n avgDetections: n > 0 ? Math.round((totalDet / n) * 100) / 100 : 0,\n },\n })\n }\n }\n }\n\n await Promise.all(Array.from({ length: input.parallel }, () => worker()))\n const wallSec = (performance.now() - wallStart) / 1000\n\n return this.buildBenchResult(wallTimings, inferTimings, preprocessTimings, predictTimings, batchSizes, detCounts, wallSec, 'pipeline')\n }\n\n private async buildBenchResult(\n wallTimings: number[],\n inferTimings: number[],\n preprocessTimings: number[],\n predictTimings: number[],\n batchSizes: number[],\n detCounts: number[],\n wallSec: number,\n path: string,\n ): Promise<RunSyntheticBenchResult> {\n const meanOfArr = (xs: readonly number[]): number =>\n xs.length > 0 ? xs.reduce((s, v) => s + v, 0) / xs.length : 0\n this.ctx.logger.info('synthetic bench summary', {\n meta: {\n runs: wallTimings.length,\n wallSec: Math.round(wallSec * 100) / 100,\n fps: Math.round((wallTimings.length / wallSec) * 100) / 100,\n callMeanMs: Math.round(meanOfArr(wallTimings)),\n inferMeanMs: Math.round(meanOfArr(inferTimings)),\n preprocessMeanMs: Math.round(meanOfArr(preprocessTimings)),\n predictMeanMs: Math.round(meanOfArr(predictTimings)),\n batchSizeMean: Math.round(meanOfArr(batchSizes) * 100) / 100,\n batchSizeMax: batchSizes.length > 0 ? Math.max(...batchSizes) : 0,\n },\n })\n\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const pick = (q: number): number =>\n sorted.length > 0\n ? sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))]!\n : 0\n const meanOf = (xs: readonly number[]): number =>\n xs.length > 0 ? xs.reduce((s, v) => s + v, 0) / xs.length : 0\n\n const totalRuns = wallTimings.length\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n\n return {\n runs: totalRuns,\n wallSec: Math.round(wallSec * 1000) / 1000,\n fps: wallSec > 0 ? Math.round((totalRuns / wallSec) * 100) / 100 : 0,\n detectionsPerSec: wallSec > 0 ? Math.round((totalDet / wallSec) * 100) / 100 : 0,\n avgDetections: totalRuns > 0 ? Math.round((totalDet / totalRuns) * 100) / 100 : 0,\n callMs: {\n mean: Math.round(meanOf(wallTimings) * 100) / 100,\n p50: Math.round(pick(0.5) * 100) / 100,\n p95: Math.round(pick(0.95) * 100) / 100,\n p99: Math.round(pick(0.99) * 100) / 100,\n },\n inferMs: Math.round(meanOf(inferTimings) * 100) / 100,\n preprocessMs: Math.round(meanOf(preprocessTimings) * 100) / 100,\n predictMs: Math.round(meanOf(predictTimings) * 100) / 100,\n batchSizeMean: Math.round(meanOf(batchSizes) * 100) / 100,\n batchSizeMax: batchSizes.length > 0 ? Math.max(...batchSizes) : 0,\n path,\n ...(await this.getEngineAndTuning()),\n }\n }\n\n private async getEngineAndTuning(): Promise<Pick<RunSyntheticBenchResult, 'engine' | 'tuning'>> {\n try {\n const api = this.ctx.api\n if (!api) return {}\n const [eng, tuning] = await Promise.all([\n api.pipelineExecutor.getSelectedEngine.query(),\n api.pipelineExecutor.getEffectiveTuning.query(),\n ])\n return {\n engine: eng ? { runtime: eng.runtime, backend: eng.backend, device: eng.device } : undefined,\n tuning: tuning ?? undefined,\n }\n } catch {\n return {}\n }\n }\n\n // ── IPipelineRunnerProvider implementation ────────────────────────────\n\n async attachCamera(config: RunnerCameraConfig): Promise<{ success: true }> {\n const runner = this.runner\n const ctx = this.ctx\n if (!runner || !ctx) {\n throw new Error('PipelineRunnerAddon: attachCamera called before initialize completed')\n }\n\n // Diagnostic: log the incoming config to understand why\n // subscribeMotionFrames isn't being called.\n this.ctx.logger.info('attachCamera received config', {\n tags: { deviceId: config.deviceId },\n meta: {\n motionSources: config.motionSources,\n motionSourcesType: Array.isArray(config.motionSources) ? `array(${config.motionSources.length})` : typeof config.motionSources,\n motionStreamId: config.motionStreamId,\n detectionStreamId: config.detectionStreamId,\n keys: Object.keys(config as unknown as Record<string, unknown>),\n },\n })\n\n if (this.attached.has(config.deviceId)) {\n // Idempotent: re-attach with new config replaces the existing one.\n this.detachInternal(config.deviceId)\n }\n\n runner.registerCamera(config.deviceId, {\n detectionMode: config.detectionMode,\n fps: config.detectionFps,\n motionCooldownMs: config.motionCooldownMs,\n })\n\n const attachment: AttachedCamera = {\n config,\n motionUnsubscribe: null,\n detectionUnsubscribe: null,\n }\n this.attached.set(config.deviceId, attachment)\n\n // Subscribe to the local motion stream broker only when 'analyzer'\n // is among the active motion sources — that's the source that\n // needs decoded frames flowing through the motion-detection cap.\n // 'onboard' alone arrives via the bus (ProviderMotion → orchestrator\n // → reportMotion) without needing broker frames.\n if (config.motionSources.includes('analyzer')) {\n attachment.motionUnsubscribe = await this.subscribeMotionFrames(config)\n }\n\n // Note: detection stream subscription is wired LAZILY by the\n // runner's phase machine via `handleDetectionStreamChange`, which\n // fires when the camera transitions to ACTIVE. Always-on cameras\n // transition immediately on registerCamera, so the subscribe call\n // already happened by the time we get here.\n\n const stepsCount = config.steps?.length ?? 0\n const dispatch = stepsCount > 0\n ? `runPipeline(${stepsCount}step${stepsCount === 1 ? '' : 's'})`\n : config.steps !== undefined\n ? 'skip(0steps)'\n : 'runFrame(legacy)'\n const engineLabel = config.engine ? `${config.engine.runtime}+${config.engine.backend}/${config.engine.format}` : 'default'\n this.ctx.logger.info(\n 'attachCamera',\n {\n tags: { deviceId: config.deviceId },\n meta: {\n detectionMode: config.detectionMode,\n audioMode: config.audioMode,\n motionFps: config.motionFps,\n detectionFps: config.detectionFps,\n motionSources: config.motionSources,\n dispatch,\n engine: engineLabel,\n },\n },\n )\n\n return { success: true }\n }\n\n async detachCamera(input: { deviceId: number }): Promise<{ success: true }> {\n this.detachInternal(input.deviceId)\n return { success: true }\n }\n\n async reportMotion(input: {\n deviceId: number\n detected: boolean\n source?: MotionSource\n regions?: ReadonlyArray<MotionRegion>\n }): Promise<{ success: true }> {\n this.runner?.reportMotion(input.deviceId, input.detected, input.source, input.regions)\n return { success: true }\n }\n\n private detachInternal(deviceId: number): void {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n // Tear down any dynamic onboard-analyzer subscription + its timer\n // before clearing the attachment — order matters so the timer can't\n // fire and look up a no-longer-present attachment entry.\n this.clearOnboardAnalyzer(deviceId)\n\n attachment.motionUnsubscribe?.()\n attachment.detectionUnsubscribe?.()\n this.attached.delete(deviceId)\n this.lastMotionAt.delete(deviceId)\n this.lastEmittedCameraMetrics.delete(deviceId)\n\n this.runner?.unregisterCamera(deviceId)\n this.ctx?.logger.info('detachCamera', { tags: { deviceId } })\n }\n\n /**\n * Synchronously cancel the teardown timer and call the unsubscribe\n * handle for the dynamic onboard analyzer, if one is open. Safe to\n * call when no subscription exists.\n */\n private clearOnboardAnalyzer(deviceId: number): void {\n const timer = this.onboardAnalyzerTeardownTimers.get(deviceId)\n if (timer !== undefined) {\n clearTimeout(timer)\n this.onboardAnalyzerTeardownTimers.delete(deviceId)\n }\n const unsub = this.onboardAnalyzerSubs.get(deviceId)\n if (unsub !== undefined) {\n try { unsub() } catch { /* swallow — broker may already be gone */ }\n this.onboardAnalyzerSubs.delete(deviceId)\n }\n }\n\n /**\n * Dynamic analyzer gate for onboard-motion cameras.\n *\n * Called from the `MotionOnMotionChanged` subscriber whenever\n * `source === 'onboard'`. Opens a `subscribeMotionFrames` subscription\n * the first time motion is detected (idempotent — a second `detected:true`\n * while the sub is already open is a no-op). Always re-arms the teardown\n * timer so the subscription stays open as long as motion events keep\n * arriving and tears down `motionCooldownMs` after the last event.\n *\n * No-op when:\n * - The camera is not currently attached.\n * - `shouldStartOnboardAnalyzer(config)` returns false (flag off or\n * `motionSources` already includes `'analyzer'`).\n */\n private async handleOnboardMotionAnalyzer(deviceId: number, detected: boolean): Promise<void> {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n const config = attachment.config\n if (!shouldStartOnboardAnalyzer(config)) return\n\n const log = this.ctx.logger.withTags({ deviceId })\n\n // Open the subscription on the first `detected:true` if not already open.\n if (detected && !this.onboardAnalyzerSubs.has(deviceId)) {\n const unsub = await this.subscribeMotionFrames(config)\n if (unsub) {\n this.onboardAnalyzerSubs.set(deviceId, unsub)\n log.debug('onboard-analyzer: opened motion-frame subscription')\n }\n }\n\n // (Re-)arm the teardown timer on every event — both true and false.\n // Onboard cameras may emit only `detected:true`; the cooldown timer\n // is the single closure path (matching the phase machine's design).\n const existing = this.onboardAnalyzerTeardownTimers.get(deviceId)\n if (existing !== undefined) clearTimeout(existing)\n\n const cooldownMs = config.motionCooldownMs\n const timer = setTimeout(() => {\n this.onboardAnalyzerTeardownTimers.delete(deviceId)\n const unsub = this.onboardAnalyzerSubs.get(deviceId)\n if (!unsub) return\n try { unsub() } catch { /* swallow */ }\n this.onboardAnalyzerSubs.delete(deviceId)\n log.debug('onboard-analyzer: closed after motion cooldown', { meta: { cooldownMs } })\n }, cooldownMs)\n this.onboardAnalyzerTeardownTimers.set(deviceId, timer)\n }\n\n async getLocalLoad(): Promise<RunnerLocalLoad> {\n const metrics = this.runner?.getMetrics() ?? { activeCameras: 0, throttledCameras: 0, avgInferenceTimeMs: 0, queueDepth: 0 }\n const allCameraMetrics = this.runner?.getAllCameraMetrics() ?? []\n\n let activeCameras = 0\n let totalActualFps = 0\n for (const cm of allCameraMetrics) {\n if (cm.phase === 'active') activeCameras++\n totalActualFps += cm.actualFps\n }\n\n // Phase 4: hardware probe moved off the runner.\n //\n // In the stateless-pipeline model each camera declares its own\n // engine. L3 hardware-aware balancing now lives on the orchestrator,\n // which knows `pipelines[deviceId].engine` and can match against\n // `nodes.topology.availableEngines` directly (spec Phase 3 extends\n // topology; consumed by the balancer once Phase 8 lands). The runner\n // no longer calls `pipelineExecutor.getSelectedEngine` — that cap is\n // being removed and its legacy \"one engine per node\" assumption\n // doesn't fit multi-engine nodes.\n return {\n nodeId: this.nodeId,\n attachedCameras: this.attached.size,\n activeCameras,\n avgInferenceFps: totalActualFps,\n avgInferenceTimeMs: metrics.avgInferenceTimeMs,\n queueDepthTotal: metrics.queueDepth,\n hardware: {\n hasGpu: false,\n inferenceBackend: undefined,\n },\n }\n }\n\n async getLocalMetrics(): Promise<RunnerLocalMetrics> {\n const m = this.runner?.getMetrics() ?? { activeCameras: 0, throttledCameras: 0, avgInferenceTimeMs: 0, queueDepth: 0 }\n return { nodeId: this.nodeId, ...m }\n }\n\n async getCameraMetrics(input: { deviceId: number }): Promise<CameraMetrics | null> {\n return this.runner?.getCameraMetrics(input.deviceId) ?? null\n }\n\n getAllCameraMetrics(): ReadonlyArray<{ deviceId: number } & CameraMetrics> {\n return this.runner?.getAllCameraMetrics() ?? []\n }\n\n getLocalCameras(): readonly number[] {\n return [...this.attached.keys()]\n }\n\n // ── Internal: broker subscription wiring ─────────────────────────────\n\n private async subscribeMotionFrames(config: RunnerCameraConfig): Promise<(() => void) | null> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return null\n\n const log = this.ctx.logger.withTags({ deviceId: config.deviceId })\n const api = this.ctx.api\n if (!api) {\n log.warn('subscribeMotionFrames: this.ctx.api not available')\n return null\n }\n\n // Phase 5 / D9: motion reads `gray` frames from the broker's shm ring.\n // `subscribeFrames` spins up a `frameSink: 'shm'` gray decoder session;\n // the poller drains `FrameHandle`s and reads pixels back via a\n // `FrameRingReader`, feeding the runner's existing motion queue unchanged.\n return startFrameHandlePoller({\n api,\n brokerId: makeSourceBrokerId(config.deviceId, config.motionStreamId),\n format: 'gray',\n maxFps: config.motionFps,\n tag: 'motion',\n logger: log,\n // Motion analysis is intra-process and never propagates beyond\n // the runner; the handle is intentionally ignored here.\n onFrame: (frame: DecodedFrame, _handle) => {\n runner.enqueueMotionFrame(config.deviceId, frame)\n },\n })\n }\n\n private handleDetectionStreamChange(deviceId: number, action: 'subscribe' | 'unsubscribe'): void {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n if (action === 'subscribe') {\n void this.subscribeDetectionFrames(attachment.config).then(unsub => {\n attachment.detectionUnsubscribe = unsub\n })\n } else {\n attachment.detectionUnsubscribe?.()\n attachment.detectionUnsubscribe = null\n }\n }\n\n /**\n * Bridge runner phase transitions to the device's `motion` runtime\n * state + the bus. Single ownership point — every motion source\n * (analyzer, onboard, future variants) funnels through the runner's\n * phase machine and lands here.\n *\n * - Cap-state via the unified `device-state.setCapSlice` API.\n * `autoClearAfterMs = cooldownMs` on ON, `null` on OFF.\n * `lastDetectedAt` is preserved across OFF using `lastMotionAt`.\n * - Bus event `MotionOnMotionChanged` fires alongside for consumers\n * that prefer event-driven over runtime-state polling.\n */\n private handlePhaseChanged(\n deviceId: number,\n phase: CameraPhase,\n meta: PhaseTransitionMeta,\n ): void {\n const detected = phase === 'active'\n if (detected) this.lastMotionAt.set(deviceId, meta.timestamp)\n const lastDetectedAt = this.lastMotionAt.get(deviceId) ?? null\n const slice = {\n detected,\n lastDetectedAt,\n autoClearAfterMs: detected ? meta.cooldownMs : null,\n }\n void (async () => {\n const dev = await this.ctx.fetchDevice(deviceId)\n await dev.deviceState.setCapSlice({ capName: 'motion', slice })\n })().catch((err: unknown) => {\n this.ctx.logger.debug('motion cap-state write failed', {\n tags: { deviceId },\n meta: { error: errMsg(err) },\n })\n })\n // Emit `DetectionPhaseTransition` so observers (UI events panel,\n // analytics, alert center) see WATCHING ↔ ACTIVE flips on the bus.\n // Sources (Reolink firmware, this addon's analyzer path) still own\n // `MotionOnMotionChanged` — that channel carries the raw motion\n // signal. This event carries the post-phase-machine truth (after\n // cooldown debouncing). Different consumers, different cadences.\n if (this.ctx.eventBus) {\n const from: 'watching' | 'active' = detected ? 'watching' : 'active'\n const to: 'watching' | 'active' = detected ? 'active' : 'watching'\n const reason: 'motion_detected' | 'cooldown_expired' = detected ? 'motion_detected' : 'cooldown_expired'\n const payload: PhaseTransitionPayload = {\n deviceId,\n from,\n to,\n reason,\n source: meta.source,\n cooldownMs: meta.cooldownMs,\n timestamp: meta.timestamp,\n }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.DetectionPhaseTransition,\n { type: 'device', id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId },\n payload,\n ))\n }\n }\n\n private async subscribeDetectionFrames(config: RunnerCameraConfig): Promise<(() => void) | null> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return null\n\n const log = this.ctx.logger.withTags({ deviceId: config.deviceId })\n const api = this.ctx.api\n if (!api) {\n log.warn('subscribeDetectionFrames: this.ctx.api not available')\n return null\n }\n\n // Phase 5 / D9: detection reads `rgb` frames from the broker's shm ring.\n // `format: 'rgb'` is the Phase 4 hot-path switch — detection consumes raw\n // RGB24 so the Python pool skips a PIL JPEG decode. The broker's shm\n // plane runs a dedicated `rgb` decoder session feeding a per-format ring;\n // the poller reads each frame back and feeds the runner's detection queue.\n return startFrameHandlePoller({\n api,\n brokerId: makeSourceBrokerId(config.deviceId, config.detectionStreamId),\n format: 'rgb',\n maxFps: config.detectionFps,\n tag: 'detection',\n logger: log,\n // Detection threads the `FrameHandle` through the runner so the\n // emitted `PipelineInferenceResultPayload` can name the shm slot\n // post-analysis (Task 8) reads pixels back from.\n onFrame: (frame: DecodedFrame, handle) => {\n runner.enqueueDetectionFrame(config.deviceId, frame, handle)\n },\n })\n }\n\n // ── Internal: inference + motion callbacks ───────────────────────────\n\n private async runInference(deviceId: number, frame: FrameInput): Promise<FrameResult | null> {\n const ctx = this.ctx\n if (!ctx) return null\n\n const log = this.ctx.logger.withTags({ deviceId })\n const api = this.ctx.api\n if (!api) {\n log.error('runInference: this.ctx.api not available')\n return null\n }\n\n const attachment = this.attached.get(deviceId)\n const camConfig = attachment?.config\n const steps = camConfig?.steps\n const engine = camConfig?.engine\n\n // Stateless-pipeline dispatch (Phase 4): the orchestrator hydrates\n // every attach with `steps` (from `pipelines[deviceId]` or the\n // cluster default). Absent steps means the camera was attached\n // before the orchestrator migrated — skip the frame rather than\n // falling back to the removed legacy `runFrame` path.\n if (!steps) {\n log.warn('runInference: no steps in attach config — skipping frame (legacy attach?)')\n return null\n }\n if (steps.length === 0) {\n return null\n }\n\n try {\n return await api.pipelineExecutor.runPipeline.mutate({\n // tRPC input is a mutable array; the attach payload holds it\n // as readonly. One spread copy at the cap boundary is cheap\n // (pipeline step trees are tiny) and keeps the type surface\n // clean without casting.\n steps: [...steps],\n frame,\n deviceId,\n ...(engine ? { engine } : {}),\n })\n } catch (err: unknown) {\n const msg = errMsg(err)\n log.error('runInference failed', { meta: { error: msg } })\n return null\n }\n }\n\n private async runMotionAnalysis(deviceId: number, frame: DecodedFrame): Promise<void> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return\n\n const log = this.ctx.logger.withTags({ deviceId })\n const motionStart = Date.now()\n try {\n const api = this.ctx.api\n if (!api) {\n log.warn('runMotionAnalysis: this.ctx.api not available')\n return\n }\n const result = await api.motionDetection.analyze.mutate({ deviceId, frame: toFrameInput(frame) })\n if (!result) return\n\n const detected = result.regions.length > 0\n\n // Emit `MotionOnMotionChanged` ONLY on transitions (analyzer runs\n // every motion frame at `motionFps`; we don't want one event per\n // frame). The runner's bus subscriber receives this and drives\n // the phase machine — same path Reolink's firmware events take.\n // No in-process `reportMotion` call here: the unified subscription\n // is the single dispatch point so onboard and analyzer go through\n // identical logic.\n const prevDetected = this.lastAnalyzerDetected.get(deviceId) ?? false\n if (detected !== prevDetected) {\n this.lastAnalyzerDetected.set(deviceId, detected)\n if (this.ctx.eventBus) {\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionOnMotionChanged,\n // EventSource wrapper kept symmetric with the onboard\n // emit (Reolink/ONVIF/etc.) so consumers grouping by\n // addonId / deviceId see consistent provenance. `nodeId`\n // identifies which cluster node ran the analyzer.\n { type: 'device', id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId },\n {\n deviceId,\n detected,\n timestamp: frame.timestamp,\n source: 'analyzer',\n ...(detected ? { regions: result.regions } : {}),\n },\n ))\n }\n }\n\n if (this.ctx.eventBus) {\n const motionPayload: MotionAnalysisPayload = {\n detected,\n regionCount: result.regions.length,\n regions: result.regions.map((r) => ({\n bbox: { x: r.bbox.x, y: r.bbox.y, w: r.bbox.w, h: r.bbox.h },\n pixelCount: r.pixelCount,\n intensity: r.intensity,\n })),\n frameWidth: frame.width,\n frameHeight: frame.height,\n analysisMs: result.analysisMs,\n }\n const analyzerSource = { type: 'device' as const, id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionAnalysis,\n analyzerSource,\n motionPayload,\n ))\n\n const zonesPayload: MotionZonesRawPayload = {\n deviceId,\n timestamp: frame.timestamp,\n zones: result.rawRegions.map((r) => ({\n bbox: [r.bbox.x, r.bbox.y, r.bbox.x + r.bbox.w, r.bbox.y + r.bbox.h] as const,\n pixelCount: r.pixelCount,\n changeScore: r.intensity / 255,\n })),\n frameSize: { width: frame.width, height: frame.height },\n }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionZonesRaw,\n analyzerSource,\n zonesPayload,\n ))\n }\n\n // frameAge = how stale the analyzed frame was (shm-ring commit → analysis\n // start). Large → decoder/ring behind, so the motion box lags real movement\n // even though emit→UI delivery is fast.\n const capturedAt = frame.capturedAt\n const motionFrameAge = typeof capturedAt === 'number' && capturedAt > 0 ? motionStart - capturedAt : -1\n runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart, motionFrameAge)\n } catch (error: unknown) {\n const msg = errMsg(error)\n log.error('runMotionAnalysis failed', { meta: { error: msg } })\n }\n }\n\n private emitInferenceResult(\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n handle: FrameHandle | undefined,\n ): void {\n const ctx = this.ctx\n if (!ctx?.eventBus) return\n\n // The new FrameResult already carries width/height/timestamp at the\n // root + debug.stepTimings / debug.totalInferenceMs. No frameMetadata\n // wrapper needed — the hub subscribes and reads `payload.frame`\n // directly. The `frameHandle` lets post-analysis consumers (Task 8)\n // resolve the original decoded frame zero-copy from the shm ring.\n // `capturedAt` (shm-commit wall-clock) is forwarded so downstream\n // consumers can measure true frame-capture → delivery latency.\n const capturedAt = frame.capturedAt\n const payload: PipelineInferenceResultPayload = {\n deviceId,\n frame: result,\n nodeId: this.nodeId,\n frameHandle: handle,\n ...(typeof capturedAt === 'number' && capturedAt > 0 ? { capturedAt } : {}),\n }\n\n this.ctx.eventBus.emit(createEvent(\n EventCategory.PipelineInferenceResult,\n { type: 'device', id: deviceId, nodeId: this.nodeId },\n payload,\n ))\n }\n\n /**\n * Emit periodic metric snapshots: one runner-load event for the\n * node + one camera-metrics event per attached camera. Subscribed\n * by admin-ui dashboards (LiveLoadPanel, NodeDetailHeader,\n * CameraStreamPanel) to drive live overlays without polling.\n *\n * Skipped when there are no cameras attached so quiet dev runs\n * don't emit needless bus traffic. The runner-load event is still\n * emitted in that case because the dashboards rely on it to see\n * \"agent reachable, idle\".\n */\n private emitMetricsSnapshot(): void {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx?.eventBus || !runner) return\n const timestamp = Date.now()\n\n // Runner-load snapshot. Skip emit when the load payload is\n // bytewise-identical to the previous one (idle node — same\n // running-cap counts, same queue depths, same memory). Heartbeat\n // re-emits every METRICS_HEARTBEAT_MS so the \"agent reachable\"\n // chip in the UI doesn't go stale.\n void this.getLocalLoad().then((load) => {\n if (!ctx.eventBus) return\n const json = JSON.stringify(load)\n const prev = this.lastEmittedRunnerLoad\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) return\n this.lastEmittedRunnerLoad = { json, emittedAt: timestamp }\n ctx.eventBus.emit(createEvent(\n EventCategory.PipelineRunnerLoadSnapshot,\n { type: 'node', id: this.nodeId, nodeId: this.nodeId },\n { nodeId: this.nodeId, load, timestamp },\n ))\n }).catch(() => {\n // Skip this tick on transient failure — next interval will\n // reconcile. No need to log: the snapshot is a soft observability\n // path, not a correctness boundary.\n })\n\n // Per-camera metrics snapshot — same defer pattern.\n if (this.attached.size === 0) return\n for (const deviceId of this.attached.keys()) {\n const metrics = runner.getCameraMetrics(deviceId)\n if (!metrics) continue\n const json = JSON.stringify(metrics)\n const prev = this.lastEmittedCameraMetrics.get(deviceId)\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) continue\n this.lastEmittedCameraMetrics.set(deviceId, { json, emittedAt: timestamp })\n ctx.eventBus.emit(createEvent(\n EventCategory.PipelineCameraMetricsSnapshot,\n { type: 'device', id: deviceId, nodeId: this.nodeId },\n { deviceId, nodeId: this.nodeId, metrics, timestamp },\n ))\n }\n }\n\n // ── Standard ICamstackAddon — three-level settings API (Phase 3) ─────\n //\n // The runner is a per-node addon with only ADDON-LEVEL settings (no\n // per-device overrides, no cluster-wide tunables). All four tuning\n // fields live in `getAddonSettings()`. When the UI surface moves in\n // Phase 9 these will be rendered under Pipeline -> node -> Settings.\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'pipeline-runner-tuning',\n title: 'Pipeline Runner',\n tab: 'scheduler',\n description: 'Per-node detection scheduler tuning. Change only if you understand the pipeline internals.',\n columns: 2,\n fields: [\n {\n type: 'slider',\n key: 'maxConcurrentInferences',\n label: 'Scheduler concurrency',\n description:\n 'Max parallel inferences the runner scheduler allows across all cameras on this node. ' +\n 'Distinct from the detection-pipeline inference-pool worker count (Pipeline tab → \"Worker concurrency\"), ' +\n 'which controls Python-side thread pool sizing inside a single inference job.',\n min: 1,\n max: 16,\n step: 1,\n default: DEFAULT_CONFIG.maxConcurrentInferences,\n showValue: true,\n },\n {\n type: 'slider',\n key: 'maxQueueDepth',\n label: 'Max queue depth',\n description: 'Maximum frames held per camera before dropping.',\n min: 5,\n max: 100,\n step: 5,\n default: DEFAULT_CONFIG.maxQueueDepth,\n showValue: true,\n },\n {\n type: 'slider',\n key: 'targetLoadPercent',\n label: 'Target load',\n description: 'Percentage of inference capacity to target before throttling FPS.',\n min: 50,\n max: 100,\n step: 5,\n default: DEFAULT_CONFIG.targetLoadPercent,\n unit: '%',\n showValue: true,\n },\n {\n type: 'slider',\n key: 'minThrottledFps',\n label: 'Min throttled FPS',\n description: 'Lowest FPS the runner will allow when load-shedding.',\n min: 1,\n max: 10,\n step: 1,\n default: DEFAULT_CONFIG.minThrottledFps,\n showValue: true,\n },\n ],\n },\n ],\n })\n }\n\n protected async onConfigChanged(): Promise<void> {\n this.runner?.updateLimits(this.config)\n this.ctx.logger.info(\n 'pipeline-runner tuning updated',\n {\n meta: {\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n },\n },\n )\n }\n}\n\n// Re-export for tests / consumers that need direct class access.\nexport { PipelineRunner } from './runner.js'\nexport { FrameQueue } from './frame-queue.js'\nexport { Semaphore } from './semaphore.js'\nexport { PipelineTimingSampler } from './timing-sampler.js'\n\n/**\n * Static `customActions` catalog — read by the addon registry at boot time\n * (before instantiating the addon class) so cross-process Moleculer routes\n * can be wired for `addons.custom.<action>` dispatch. Mirrors the pattern\n * in addon-benchmark/index.ts.\n */\nexport { pipelineRunnerBenchActions as customActions } from './bench-actions.js'\n"],"names":["toFrameInput","z.object","z.enum","z.string","z.lazy","z.boolean","z.array","z.number","warmupCount","wallTimings","inferTimings","preprocessTimings","predictTimings","batchSizes","detCounts","sessionId","totalRuns","wallStart","worker","wallSec"],"mappings":";;AAqCO,MAAM,WAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWxC,YACW,SACT,OACA;AAFS,SAAA,UAAA;AAGT,SAAK,QAAQ;AAAA,EACf;AAAA,EAfQ,SAAmB;AAAA,EACnB,iBAAiB;AAAA,EACR;AAAA,EAejB,QAAQ,MAAe;AACrB,QAAI,KAAK,WAAW,MAAM;AACxB,WAAK;AAAA,IACP;AAGA,SAAK,SAAS,KAAK,MAAM,IAAI;AAAA,EAC/B;AAAA,EAEA,UAAyB;AACvB,UAAM,OAAO,KAAK,UAAU;AAC5B,SAAK,SAAS;AACd,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,WAAW,OAAO,IAAI;AAAA,EACpC;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,QAAc;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AASO,SAAS,SAAS,OAAmC;AAC1D,SAAO,EAAE,GAAG,OAAO,MAAM,OAAO,KAAK,MAAM,IAAI,EAAA;AACjD;AClFO,MAAM,UAAU;AAAA,EACb;AAAA,EACA;AAAA,EACS,UAA6B,CAAA;AAAA,EAE9C,YAAY,aAAqB;AAC/B,SAAK,eAAe;AACpB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,IAAI,cAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,gBAA8B;AACnC,QAAI,iBAAiB,EAAG,OAAM,IAAI,MAAM,qCAAqC;AAC7E,UAAM,QAAQ,iBAAiB,KAAK;AACpC,SAAK,eAAe;AACpB,SAAK,aAAa,KAAK,IAAI,GAAG,KAAK,aAAa,KAAK;AAErD,WAAO,KAAK,aAAa,KAAK,KAAK,QAAQ,SAAS,GAAG;AACrD,YAAM,OAAO,KAAK,QAAQ,MAAA;AAC1B,UAAI,KAAM,MAAA;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,MAAM,UAA+B;AACnC,QAAI,KAAK,aAAa,GAAG;AACvB,WAAK;AACL,aAAO,MAAM,KAAK,QAAA;AAAA,IACpB;AAEA,WAAO,IAAI,QAAoB,CAAC,YAAY;AAC1C,WAAK,QAAQ,KAAK,MAAM;AACtB,aAAK;AACL,gBAAQ,MAAM,KAAK,SAAS;AAAA,MAC9B,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,UAAgB;AACtB,SAAK;AACL,UAAM,OAAO,KAAK,QAAQ,MAAA;AAC1B,QAAI,KAAM,MAAA;AAAA,EACZ;AACF;AChEA,MAAM,qBAAqB;AAiDpB,MAAM,sBAAsB;AAAA,EAChB,iCAAiB,IAAA;AAAA,EACjB,iCAAiB,IAAA;AAAA,EACjB,mCAAmB,IAAA;AAAA,EAC5B,gBAAgB;AAAA,EAChB,cAAqD;AAAA,EACrD,MAA4B;AAAA,EACpC,cAA2B,CAAA;AAAA,EAE3B,UAAU,QAA6B;AACrC,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,YAAa;AACtB,SAAK,cAAc,YAAY,MAAM,KAAK,OAAA,GAAU,kBAAkB;AAAA,EACxE;AAAA,EAEA,OAAa;AACX,QAAI,KAAK,aAAa;AACpB,oBAAc,KAAK,WAAW;AAC9B,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,UAAU,UAAkB,GAAsB;AAChD,QAAI,CAAC,KAAK,WAAW,IAAI,QAAQ,EAAG,MAAK,WAAW,IAAI,UAAU,EAAE;AACpE,SAAK,WAAW,IAAI,QAAQ,EAAG,KAAK,CAAC;AAAA,EACvC;AAAA,EAEA,gBAAgB,UAAkB,IAAY,WAAW,IAAU;AACjE,QAAI,CAAC,KAAK,WAAW,IAAI,QAAQ,EAAG,MAAK,WAAW,IAAI,UAAU,EAAE;AACpE,SAAK,WAAW,IAAI,QAAQ,EAAG,KAAK,EAAE,IAAI,UAAU;AAAA,EACtD;AAAA,EAEA,eAAe,UAAkB,GAAsB;AACrD,QAAI,CAAC,KAAK,aAAa,IAAI,QAAQ,EAAG,MAAK,aAAa,IAAI,UAAU,EAAE;AACxE,SAAK,aAAa,IAAI,QAAQ,EAAG,KAAK,CAAC;AAAA,EACzC;AAAA,EAEA,UAAgB;AACd,SAAK;AAAA,EACP;AAAA,EAEQ,SAAe;AACrB,QAAI,CAAC,KAAK,IAAK;AAEf,UAAM,UAAU,KAAK;AACrB,SAAK,gBAAgB;AAErB,UAAM,MAAM,CAAC,QAA0B,IAAI,SAAS,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,MAAM,IAAI;AAClH,UAAM,MAAM,CAAC,QAA0B,IAAI,SAAS,IAAI,KAAK,MAAM,KAAK,IAAI,GAAG,GAAG,CAAC,IAAI;AACvF,UAAM,MAAM,CAAC,QAA0B;AACrC,UAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,YAAM,SAAS,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC5C,aAAO,KAAK,MAAM,OAAO,KAAK,MAAM,OAAO,SAAS,IAAI,CAAC,KAAK,OAAO,OAAO,SAAS,CAAC,CAAE;AAAA,IAC1F;AAEA,UAAM,KAAK,KAAK;AAEhB,eAAW,CAAC,UAAU,GAAG,KAAK,KAAK,YAAY;AAC7C,UAAI,IAAI,WAAW,EAAG;AACtB,YAAM,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,QAAQ;AACrC,YAAM,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS;AACtC,YAAM,WAAW,IAAI,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;AAChE,YAAM,WAAW,IAAI,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,YAAY,CAAC;AAEzD,WAAK,IAAI;AAAA,QACP;AAAA,QACA;AAAA,UACE,MAAM,EAAE,SAAA;AAAA,UACR,MAAM;AAAA,YACJ,QAAQ,IAAI;AAAA,YACZ,aAAa,qBAAqB;AAAA;AAAA;AAAA;AAAA,YAIlC,KAAK,EAAE,KAAK,IAAI,GAAG,GAAG,KAAK,IAAI,GAAG,GAAG,KAAK,IAAI,GAAG,EAAA;AAAA;AAAA;AAAA;AAAA,YAIjD,UAAU,EAAE,KAAK,IAAI,QAAQ,GAAG,KAAK,IAAI,QAAQ,GAAG,KAAK,IAAI,QAAQ,EAAA;AAAA,YACrE,UAAU;AAAA,cACR,WAAW,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC;AAAA,cAC1C,eAAe,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC;AAAA,cAClD,WAAW,IAAI,GAAG;AAAA,cAClB,cAAc,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AAAA,YAAA;AAAA,YAElD,WAAW,EAAE,KAAK,IAAI,GAAG,GAAG,KAAK,IAAI,GAAG,EAAA;AAAA,YACxC,YAAY;AAAA,YACZ;AAAA,YACA,iBAAiB,GAAG,mBAAmB;AAAA,YACvC,gBAAgB,GAAG,kBAAkB;AAAA,UAAA;AAAA,QACvC;AAAA,MACF;AAAA,IAEJ;AACA,SAAK,WAAW,MAAA;AAEhB,eAAW,CAAC,UAAU,GAAG,KAAK,KAAK,YAAY;AAC7C,UAAI,IAAI,WAAW,EAAG;AACtB,YAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE;AAC9B,YAAM,WAAW,IAAI,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;AAChE,WAAK,IAAI;AAAA,QACP;AAAA,QACA;AAAA,UACE,MAAM,EAAE,SAAA;AAAA,UACR,MAAM;AAAA,YACJ,QAAQ,IAAI;AAAA,YACZ,aAAa,qBAAqB;AAAA,YAClC,KAAK,IAAI,EAAE;AAAA,YACX,KAAK,IAAI,EAAE;AAAA,YACX,KAAK,IAAI,EAAE;AAAA;AAAA;AAAA,YAGX,UAAU,EAAE,KAAK,IAAI,QAAQ,GAAG,KAAK,IAAI,QAAQ,GAAG,KAAK,IAAI,QAAQ,EAAA;AAAA,UAAE;AAAA,QACzE;AAAA,MACF;AAAA,IAEJ;AACA,SAAK,WAAW,MAAA;AAEhB,eAAW,CAAC,UAAU,GAAG,KAAK,KAAK,cAAc;AAC/C,UAAI,IAAI,WAAW,EAAG;AACtB,YAAM,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU;AACjF,YAAM,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,aAAa,IAAI;AACxD,YAAM,gCAAgB,IAAA;AACtB,iBAAW,KAAK,YAAY;AAC1B,YAAI,EAAE,SAAU,WAAU,IAAI,EAAE,WAAW,UAAU,IAAI,EAAE,QAAQ,KAAK,KAAK,CAAC;AAAA,MAChF;AACA,YAAM,aAAa,CAAC,GAAG,UAAU,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC1H,YAAM,UAAU,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC;AAEtD,WAAK,IAAI;AAAA,QACP;AAAA,QACA;AAAA,UACE,MAAM,EAAE,SAAA;AAAA,UACR,MAAM;AAAA,YACJ,QAAQ,IAAI;AAAA,YACZ,aAAa,qBAAqB;AAAA,YAClC,YAAY,WAAW;AAAA,YACvB,eAAe,cAAc,SAAS,IAAI,IAAI,aAAa,IAAI;AAAA,YAC/D;AAAA,YACA,WAAW;AAAA,YACX,aAAa,GAAG,eAAe;AAAA,UAAA;AAAA,QACjC;AAAA,MACF;AAAA,IAEJ;AACA,SAAK,aAAa,MAAA;AAAA,EACpB;AACF;ACtFA,SAAS,kBAAkB,OAAiD;AAC1E,SAAO,EAAE,OAAO,SAAS,MAAM,KAAK,GAAG,QAAQ,MAAM,OAAA;AACvD;AAOA,MAAM,6BAA6B;AAEnC,SAASA,eAAa,OAAiC;AACrD,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,OAAO,MAAM;AAAA,IACb,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM;AAAA,EAAA;AAErB;AAiBO,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlB;AAAA,EACS,8BAAc,IAAA;AAAA,EACd;AAAA,EACA,kBAAoC,CAAA;AAAA,EACpC,wBAAkC,CAAA;AAAA,EAC3C,yBAAyB;AAAA,EACzB,iBAAwD;AAAA,EACxD,yBAAwD;AAAA,EAC/C;AAAA,EACR,gBAAgB,IAAI,sBAAA;AAAA,EAE7B,YAAY,QAAsB;AAChC,SAAK,SAAS;AACd,SAAK,SAAS,OAAO;AACrB,SAAK,YAAY,IAAI,UAAU,OAAO,uBAAuB;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,aAAa,OAKJ;AACP,UAAM,OAAqB;AAAA,MACzB,GAAG,KAAK;AAAA,MACR,eAAe,MAAM,iBAAiB,KAAK,OAAO;AAAA,MAClD,yBAAyB,MAAM,2BAA2B,KAAK,OAAO;AAAA,MACtE,mBAAmB,MAAM,qBAAqB,KAAK,OAAO;AAAA,MAC1D,iBAAiB,MAAM,mBAAmB,KAAK,OAAO;AAAA,IAAA;AAExD,QAAI,KAAK,4BAA4B,KAAK,OAAO,yBAAyB;AACxE,WAAK,UAAU,OAAO,KAAK,uBAAuB;AAAA,IACpD;AACA,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,YAKE;AACA,WAAO;AAAA,MACL,eAAe,KAAK,OAAO;AAAA,MAC3B,yBAAyB,KAAK,OAAO;AAAA,MACrC,mBAAmB,KAAK,OAAO;AAAA,MAC/B,iBAAiB,KAAK,OAAO;AAAA,IAAA;AAAA,EAEjC;AAAA;AAAA,EAGA,wBAAwB,SAAuC;AAC7D,SAAK,yBAAyB;AAAA,EAChC;AAAA,EAEA,eAAe,UAAkB,cAAwC;AACvE,UAAM,cAAc,IAAI,WAAW,KAAK,OAAO,eAAe,QAAQ;AACtE,UAAM,iBAAiB,IAAI;AAAA,MACzB,KAAK,OAAO;AAAA,MACZ;AAAA,IAAA;AAKF,UAAM,eAA4B,aAAa,kBAAkB,aAC7D,SACA,aAAa,kBAAkB,cAC7B,WACA;AAEN,UAAM,QAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB,CAAA;AAAA,MAChB,gBAAgB;AAAA,MAChB,WAAW,KAAK,IAAA;AAAA,MAChB,OAAO;AAAA,MACP,qBAAqB;AAAA,MACrB,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,IAAA;AAEpB,SAAK,QAAQ,IAAI,UAAU,KAAK;AAKhC,QAAI,aAAa,kBAAkB,aAAa;AAC9C,WAAK,sBAAsB,KAAK,QAAQ;AAAA,IAC1C;AAEA,QAAI,iBAAiB,UAAU;AAC7B,WAAK,yBAAyB,UAAU,WAAW;AAInD,YAAM,aAAa,aAAa,oBAAoB;AACpD,WAAK,OAAO,iBAAiB,UAAU,UAAU;AAAA,QAC/C,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW,KAAK,IAAA;AAAA,QAChB;AAAA,MAAA,CACD;AAAA,IACH;AAAA,EAIF;AAAA,EAEA,iBAAiB,UAAwB;AACvC,UAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ;AACvC,QAAI,CAAC,MAAO;AAEZ,QAAI,MAAM,wBAAwB,MAAM;AACtC,mBAAa,MAAM,mBAAmB;AACtC,YAAM,sBAAsB;AAAA,IAC9B;AAEA,QAAI,MAAM,UAAU,UAAU;AAC5B,WAAK,yBAAyB,UAAU,aAAa;AAAA,IACvD;AAEA,UAAM,YAAY,MAAA;AAClB,UAAM,eAAe,MAAA;AACrB,SAAK,QAAQ,OAAO,QAAQ;AAE5B,UAAM,MAAM,KAAK,sBAAsB,QAAQ,QAAQ;AACvD,QAAI,QAAQ,IAAI;AACd,WAAK,sBAAsB,OAAO,KAAK,CAAC;AACxC,UAAI,KAAK,0BAA0B,KAAK,sBAAsB,QAAQ;AACpE,aAAK,yBAAyB;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,mBAAmB,UAAkB,OAA2B;AAC9D,UAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ;AACvC,QAAI,CAAC,MAAO;AACZ,UAAM,YAAY,QAAQ,KAAK;AAAA,EACjC;AAAA,EAEA,sBACE,UACA,OACA,QACM;AACN,UAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ;AACvC,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,UAAU,SAAU;AAC5B,UAAmC,cAAc,KAAK,IAAA;AACxD,UAAM,eAAe,QAAQ,EAAE,OAAO,QAAQ;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,aACE,UACA,UACA,SAAuB,YACvB,UAAmD,QAC7C;AACN,UAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ;AACvC,QAAI,CAAC,MAAO;AAKZ,QAAI,MAAM,aAAa,kBAAkB,YAAa;AACtD,QAAI,CAAC,SAAU;AAEf,UAAM,kBAAkB;AACxB,UAAM,mBAAmB;AACzB,UAAM,aAAa,MAAM,aAAa,oBAAoB;AAE1D,QAAI,MAAM,wBAAwB,MAAM;AACtC,mBAAa,MAAM,mBAAmB;AACtC,YAAM,sBAAsB;AAAA,IAC9B;AAEA,QAAI,MAAM,UAAU,YAAY;AAC9B,WAAK,mBAAmB,UAAU,OAAO,QAAQ,SAAS,UAAU;AAAA,IACtE;AAEA,UAAM,sBAAsB,WAAW,MAAM;AAC3C,YAAM,sBAAsB;AAC5B,WAAK,qBAAqB,UAAU,OAAO,UAAU;AAAA,IACvD,GAAG,UAAU;AAAA,EACf;AAAA,EAEA,SAAS,UAA2C;AAClD,WAAO,KAAK,QAAQ,IAAI,QAAQ,GAAG;AAAA,EACrC;AAAA,EAEA,SAAS,UAAgC;AACvC,SAAK,gBAAgB,KAAK,QAAQ;AAAA,EACpC;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,mBAAmB,KAAM;AAClC,SAAK,iBAAiB,YAAY,MAAM,KAAK,KAAA,GAAQ,EAAE;AACvD,SAAK,cAAc,MAAA;AAAA,EACrB;AAAA,EAEA,OAAa;AACX,QAAI,KAAK,mBAAmB,MAAM;AAChC,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACxB;AACA,SAAK,cAAc,KAAA;AAEnB,eAAW,SAAS,KAAK,QAAQ,OAAA,GAAU;AACzC,UAAI,MAAM,wBAAwB,MAAM;AACtC,qBAAa,MAAM,mBAAmB;AACtC,cAAM,sBAAsB;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,aAAkC;AAChC,QAAI,kBAAkB;AACtB,QAAI,qBAAqB;AACzB,QAAI,sBAAsB;AAE1B,eAAW,SAAS,KAAK,QAAQ,OAAA,GAAU;AACzC,yBAAmB,MAAM,YAAY,OAAO,MAAM,eAAe;AACjE,iBAAW,KAAK,MAAM,gBAAgB;AACpC,8BAAsB;AACtB;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,eAAe,KAAK,QAAQ;AAAA,MAC5B,kBAAkB;AAAA,MAClB,oBAAoB,sBAAsB,IAAI,qBAAqB,sBAAsB;AAAA,MACzF,YAAY;AAAA,IAAA;AAAA,EAEhB;AAAA,EAEA,iBAAiB,UAA6C;AAC5D,UAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ;AACvC,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,YAAY,KAAK,IAAA,IAAQ,MAAM;AACrC,UAAM,aAAa,YAAY;AAC/B,UAAM,YAAY,aAAa,IAAI,MAAM,iBAAiB,aAAa;AAEvE,UAAM,QAAQ,MAAM;AACpB,UAAM,eAAe,MAAM,SAAS,IAAI,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,MAAM,SAAS;AAE1F,WAAO;AAAA,MACL,eAAe,MAAM,aAAa;AAAA,MAClC,eAAe,MAAM,aAAa;AAAA,MAClC;AAAA,MACA,YAAY,MAAM,YAAY,OAAO,MAAM,eAAe;AAAA,MAC1D,oBAAoB;AAAA,MACpB,eAAe,MAAM,YAAY,gBAAgB,MAAM,eAAe;AAAA,MACtE,OAAO,MAAM;AAAA,IAAA;AAAA,EAEjB;AAAA,EAEA,sBAAmE;AACjE,UAAM,UAAuD,CAAA;AAC7D,eAAW,CAAC,QAAQ,KAAK,KAAK,SAAS;AACrC,YAAM,UAAU,KAAK,iBAAiB,QAAQ;AAC9C,UAAI,SAAS;AACX,gBAAQ,KAAK,EAAE,UAAU,GAAG,SAAS;AAAA,MACvC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,qBAAwC;AACtC,WAAO,CAAC,GAAG,KAAK,QAAQ,MAAM;AAAA,EAChC;AAAA,EAEQ,mBACN,UACA,OACA,QACA,SACA,YACM;AACN,UAAM,QAAQ;AACd,SAAK,QAAQ,KAAK,qCAAqC;AAAA,MACrD,MAAM,EAAE,SAAA;AAAA,MACR,MAAM,EAAE,eAAe,MAAM,aAAa,eAAe,OAAA;AAAA,IAAO,CACjE;AACD,SAAK,yBAAyB,UAAU,WAAW;AACnD,SAAK,OAAO,iBAAiB,UAAU,UAAU;AAAA,MAC/C;AAAA,MACA;AAAA,MACA,WAAW,KAAK,IAAA;AAAA,MAChB;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEQ,qBACN,UACA,OACA,YACM;AACN,UAAM,QAAQ;AACd,UAAM,eAAe,MAAA;AACrB,SAAK,QAAQ,KAAK,uCAAuC;AAAA,MACvD,MAAM,EAAE,SAAA;AAAA,MACR,MAAM,EAAE,YAAY,MAAM,gBAAA;AAAA,IAAgB,CAC3C;AACD,SAAK,yBAAyB,UAAU,aAAa;AAKrD,UAAM,SAAuB,MAAM,mBAAmB;AACtD,SAAK,OAAO,iBAAiB,UAAU,YAAY;AAAA,MACjD;AAAA,MACA,SAAS;AAAA,MACT,WAAW,KAAK,IAAA;AAAA,MAChB;AAAA,IAAA,CACD;AACD,UAAM,kBAAkB;AACxB,UAAM,mBAAmB;AAAA,EAC3B;AAAA,EAEQ,OAAa;AACnB,SAAK,kBAAA;AAEL,QAAI,KAAK,UAAU,aAAa,EAAG;AAEnC,UAAM,SAAS,KAAK,uBAAA;AACpB,QAAI,CAAC,OAAQ;AAEb,UAAM,EAAE,UAAU,OAAO,MAAA,IAAU;AACnC,UAAM,aAAaA,eAAa,MAAM,KAAK;AAE3C,SAAK,KAAK,qBAAqB,UAAU,OAAO,YAAY,OAAO,WAAW;AAAA,EAChF;AAAA,EAEQ,oBAA0B;AAChC,eAAW,CAAC,UAAU,KAAK,KAAK,KAAK,SAAS;AAC5C,aAAO,MAAM,YAAY,OAAO,GAAG;AACjC,cAAM,QAAQ,MAAM,YAAY,QAAA;AAChC,YAAI,OAAO;AACT,eAAK,KAAK,OAAO,cAAc,UAAU,KAAK;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,qBACZ,UACA,OACA,YACA,OACA,YACe;AACf,UAAM,WAAW,KAAK,IAAA;AACtB,UAAM,EAAE,OAAO,OAAA,IAAW;AAM1B,UAAM,aAAc,MAAmC,eAAe;AAEtE,UAAM,UAAU,MAAM,KAAK,UAAU,QAAA;AACrC,UAAM,gBAAgB,KAAK,IAAA;AAC3B,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,OAAO,aAAa,UAAU,UAAU;AAClE,YAAM,cAAc,KAAK,IAAA;AACzB,YAAM,cAAc,cAAc;AAElC,YAAM,eAAe,KAAK,WAAW;AACrC,UAAI,MAAM,eAAe,SAAS,KAAK;AACrC,cAAM,eAAe,MAAA;AAAA,MACvB;AACA,YAAM;AAEN,UAAI,QAAQ;AACV,cAAM,KAAK,gBAAgB,UAAU,OAAO,QAAQ,YAAY,MAAM;AACtE,cAAM,YAAY,KAAK,IAAA;AAEvB,cAAM,aAAa,MAAM;AACzB,aAAK,cAAc,UAAU,UAAU;AAAA,UACrC,WAAW,WAAW;AAAA,UACtB,eAAe,gBAAgB;AAAA,UAC/B,WAAW;AAAA,UACX,cAAc,YAAY;AAAA;AAAA;AAAA,UAG1B,UAAU,OAAO,eAAe,YAAY,aAAa,IAAI,WAAW,aAAa;AAAA;AAAA;AAAA;AAAA,UAIrF,UAAU,YAAY;AAAA,UACtB,YAAY,OAAO,YAAY,UAAU;AAAA,QAAA,CAC1C;AAAA,MACH;AAAA,IACF,UAAA;AACE,cAAA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,gBACZ,UACA,OACA,QACA,YACA,QACe;AACf,eAAW,YAAY,KAAK,iBAAiB;AAC3C,UAAI;AACF,cAAM,SAAS,UAAU,OAAO,QAAQ,YAAY,MAAM;AAAA,MAC5D,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,yBAIC;AACP,eAAW,CAAC,UAAU,KAAK,KAAK,KAAK,SAAS;AAC5C,UAAI,MAAM,aAAa,kBAAkB,eAAe,MAAM,eAAe,OAAO,GAAG;AACrF,cAAM,QAAQ,MAAM,eAAe,QAAA;AACnC,eAAO,EAAE,UAAU,OAAO,MAAA;AAAA,MAC5B;AAAA,IACF;AAEA,QAAI,KAAK,sBAAsB,WAAW,EAAG,QAAO;AAEpD,UAAM,aAAa,KAAK;AACxB,aAAS,IAAI,GAAG,IAAI,KAAK,sBAAsB,QAAQ,KAAK;AAC1D,YAAM,OAAO,aAAa,KAAK,KAAK,sBAAsB;AAC1D,YAAM,WAAW,KAAK,sBAAsB,GAAG;AAC/C,UAAI,CAAC,SAAU;AACf,YAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ;AACvC,UAAI,CAAC,MAAO;AAEZ,UAAI,MAAM,UAAU,YAAY,MAAM,eAAe,OAAO,GAAG;AAC7D,aAAK,0BAA0B,MAAM,KAAK,KAAK,sBAAsB;AACrE,cAAM,QAAQ,MAAM,eAAe,QAAA;AACnC,YAAI,CAAC,MAAO;AACZ,eAAO,EAAE,UAAU,OAAO,MAAA;AAAA,MAC5B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AC9lBA,MAAM,iBAAiB;AAGvB,MAAM,uBAAuB;AAG7B,MAAM,4BAA4B;AAGlC,MAAM,qCAAqC;AAO3C,MAAM,iCAAiC;AAsCvC,eAAsB,uBACpB,SACqB;AACrB,QAAM,YAAuB;AAAA,IAC3B,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,gBAAgB;AAAA,EAAA;AAGlB,QAAM,WAAW,MAAY;AAC3B,QAAI,UAAU,QAAS;AACvB,cAAU,UAAU;AACpB,QAAI,UAAU,YAAY;AACxB,mBAAa,UAAU,UAAU;AACjC,gBAAU,aAAa;AAAA,IACzB;AACA,cAAU,iBAAA;AAAA,EACZ;AAEA,OAAK,mBAAmB,SAAS,SAAS;AAC1C,SAAO;AACT;AAOA,eAAe,mBACb,SACA,WACe;AACf,QAAM,EAAE,KAAK,UAAU,QAAQ,QAAQ,KAAK,WAAW;AACvD,MAAI,YAAY;AAChB,MAAI,UAAU;AAEd,SAAO,CAAC,UAAU,SAAS;AACzB,eAAW;AACX,QAAI;AACF,YAAM,SAAS,MAAM,IAAI,aAAa,gBAAgB,OAAO;AAAA,QAC3D;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA,CACD;AACD,UAAI,UAAU,SAAS;AAIrB,cAAM,IAAI,aAAa,kBACpB,OAAO,EAAE,gBAAgB,OAAO,eAAA,CAAgB,EAChD,MAAM,CAAC,QAAiB;AACvB,iBAAO,KAAK,gDAAgD;AAAA,YAC1D,MAAM,EAAE,UAAU,gBAAgB,OAAO,gBAAgB,OAAO,OAAO,GAAG,EAAA;AAAA,UAAE,CAC7E;AAAA,QACH,CAAC;AACH;AAAA,MACF;AACA,gBAAU,iBAAiB,aAAa,SAAS,OAAO,gBAAgB,OAAO,QAAQ,SAAS;AAChG;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,UAAU,QAAS;AAKvB,UAAI,YAAY,GAAG;AACjB,eAAO,KAAK,yDAAyD;AAAA,UACnE,MAAM,EAAE,UAAU,QAAQ,KAAK,OAAO,OAAO,GAAG,GAAG,eAAe,UAAA;AAAA,QAAU,CAC7E;AAAA,MACH,OAAO;AACL,eAAO,MAAM,sDAAsD;AAAA,UACjE,MAAM,EAAE,UAAU,QAAQ,KAAK,SAAS,OAAO,OAAO,GAAG,GAAG,eAAe,UAAA;AAAA,QAAU,CACtF;AAAA,MACH;AACA,YAAM,MAAM,WAAW,SAAS;AAChC,kBAAY,KAAK,IAAI,gCAAgC,YAAY,CAAC;AAAA,IACpE;AAAA,EACF;AACF;AAMA,SAAS,MAAM,IAAY,WAAqC;AAC9D,SAAO,IAAI,QAAc,CAAC,YAAY;AACpC,cAAU,aAAa,WAAW,MAAM;AACtC,gBAAU,aAAa;AACvB,cAAA;AAAA,IACF,GAAG,EAAE;AAAA,EACP,CAAC;AACH;AAOA,SAAS,aACP,SACA,gBACA,gBACA,WACY;AACZ,QAAM,EAAE,KAAK,UAAU,SAAS,WAAW;AAC3C,QAAM,UAAU,IAAI,qBAAqB,MAAM;AAC/C,QAAM,iBACJ,iBAAiB,IACb,KAAK,IAAI,sBAAsB,KAAK,MAAM,MAAO,cAAc,CAAC,IAChE;AAEN,MAAI;AAEJ,QAAM,OAAO,YAA2B;AACtC,QAAI,UAAU,QAAS;AACvB,QAAI;AACF,YAAM,UAAU,MAAM,IAAI,aAAa,iBAAiB,MAAM;AAAA,QAC5D;AAAA,QACA,UAAU;AAAA,MAAA,CACX;AACD,iBAAW,UAAU,SAAS;AAC5B,YAAI,UAAU,QAAS;AACvB,cAAM,QAAQ,QAAQ,KAAK,MAAM;AAEjC,YAAI,MAAO,SAAQ,OAAO,MAAM;AAAA,MAClC;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,KAAK,gDAAgD;AAAA,QAC1D,MAAM,EAAE,UAAU,gBAAgB,OAAO,OAAO,GAAG,EAAA;AAAA,MAAE,CACtD;AAAA,IACH;AACA,QAAI,CAAC,UAAU,SAAS;AACtB,cAAQ,WAAW,MAAM,KAAK,KAAA,GAAQ,cAAc;AAAA,IACtD;AAAA,EACF;AAOA,OAAK,KAAA;AAEL,SAAO,MAAM;AACX,QAAI,OAAO;AACT,mBAAa,KAAK;AAClB,cAAQ;AAAA,IACV;AACA,YAAQ,MAAA;AACR,QAAI,aAAa,kBACd,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,QAAiB;AACvB,aAAO,KAAK,iDAAiD;AAAA,QAC3D,MAAM,EAAE,UAAU,gBAAgB,OAAO,OAAO,GAAG,EAAA;AAAA,MAAE,CACtD;AAAA,IACH,CAAC;AAAA,EACL;AACF;ACtOA,MAAM,0BAA0BC,OAAS;AAAA,EACvC,SAASC,MAAO,CAAC,QAAQ,QAAQ,CAAC;AAAA,EAClC,SAASC,OAAE;AAAA,EACX,QAAQD,MAAO,CAAC,QAAQ,UAAU,YAAY,UAAU,IAAI,CAAC;AAAA,EAC7D,QAAQC,OAAE,EAAS,SAAA;AACrB,CAAC;AASD,MAAM,kBAA6CC,KAAO,MAAMH,OAAS;AAAA,EACvE,SAASE,OAAE;AAAA,EACX,SAASA,OAAE;AAAA,EACX,SAASE,QAAE;AAAA,EACX,UAAUC,MAAQ,eAAe,EAAE,SAAA;AACrC,CAAC,CAAC;AAYK,MAAM,6BAAmEL,OAAS;AAAA,EACvF,aAAaE,OAAE;AAAA,EACf,YAAYI,OAAE,EAAS,MAAM,SAAA,EAAW,SAAA;AAC1C,CAAC;AACM,MAAM,8BAAqEN,OAAS;AAAA,EACzF,SAASE,OAAE;AAAA,EACX,OAAOI,OAAE;AAAA,EACT,QAAQA,OAAE;AAAA,EACV,WAAWA,OAAE;AACf,CAAC;AAIM,MAAM,+BAAuEN,OAAS;AAAA,EAC3F,SAASE,OAAE;AACb,CAAC;AACM,MAAM,gCAAyEF,OAAS;AAAA,EAC7F,UAAUI,QAAE;AACd,CAAC;AAeM,MAAM,+BAAuEJ,OAAS;AAAA,EAC3F,SAASE,OAAE;AAAA,EACX,OAAOG,MAAQ,eAAe,EAAE,IAAI,CAAC;AAAA,EACrC,UAAUC,OAAE,EAAS,IAAA,EAAM,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EACxC,YAAYA,OAAE,EAAS,IAAA,EAAM,IAAI,CAAC,EAAE,IAAI,GAAM;AAAA,EAC9C,QAAQA,SAAW,MAAM,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAA;AAAA,EACzC,WAAWJ,OAAE,EAAS,SAAA;AAAA,EACtB,kBAAkBE,QAAE,EAAU,SAAA;AAAA,EAC9B,QAAQ,wBAAwB,SAAA;AAClC,CAAC;AAQD,MAAM,oBAAiDJ,OAAS;AAAA,EAC9D,MAAMM,OAAE;AAAA,EACR,KAAKA,OAAE;AAAA,EACP,KAAKA,OAAE;AAAA,EACP,KAAKA,OAAE;AACT,CAAC;AAkBM,MAAM,gCAAyEN,OAAS;AAAA,EAC7F,MAAMM,OAAE;AAAA,EACR,SAASA,OAAE;AAAA,EACX,KAAKA,OAAE;AAAA,EACP,kBAAkBA,OAAE;AAAA,EACpB,eAAeA,OAAE;AAAA,EACjB,QAAQ;AAAA,EACR,SAASA,OAAE;AAAA,EACX,cAAcA,OAAE;AAAA,EAChB,WAAWA,OAAE;AAAA,EACb,eAAeA,OAAE;AAAA,EACjB,cAAcA,OAAE;AAAA,EAChB,QAAQN,OAAS,EAAE,SAASE,OAAE,GAAU,SAASA,UAAY,QAAQA,OAAE,EAAS,WAAS,CAAG,EAAE,SAAA;AAAA,EAC9F,QAAQF,OAAS,EAAE,WAAWE,OAAE,GAAU,UAAUI,OAAE,GAAU,cAAcA,UAAY,aAAaA,OAAE,EAAO,CAAG,EAAE,SAAA;AAAA,EACrH,MAAMJ,OAAE,EAAS,SAAA;AACnB,CAAC;AAEM,MAAM,6BAA6B,oBAAoB;AAAA,EAC5D,iBAAiB;AAAA,IACf;AAAA,IACA;AAAA,IACA,EAAE,MAAM,WAAA;AAAA,EAAW;AAAA,EAErB,mBAAmB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,EAAE,MAAM,WAAA;AAAA,EAAW;AAAA,EAErB,mBAAmB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,EAAE,MAAM,WAAA;AAAA,EAAW;AAEvB,CAAC;AChGD,MAAM,iBAAoC;AAAA,EACxC,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOf,yBAAyB;AAAA,EACzB,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;AAyBO,SAAS,2BAA2B,QAAqC;AAC9E,MAAI,CAAC,OAAO,4BAA6B,QAAO;AAChD,MAAI,OAAO,cAAc,SAAS,UAAU,EAAG,QAAO;AACtD,SAAO;AACT;AAEA,SAAS,aAAa,OAAiC;AACrD,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,OAAO,MAAM;AAAA,IACb,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM;AAAA,EAAA;AAErB;AAQA,MAAM,+BAA+B;AAQrC,MAAM,uBAAuB;AAE7B,MAAqB,4BAA4B,UAAgE;AAAA,EACvG,SAAgC;AAAA,EACvB,+BAAe,IAAA;AAAA,EACxB,SAAS;AAAA,EACT,uBAA8D;AAAA,EAC9D,oBAAyC;AAAA;AAAA;AAAA;AAAA,EAIhC,2CAA2B,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO3B,mCAAmB,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQnB,0CAA0B,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO1B,oDAAoC,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAapC,+CAA+B,IAAA;AAAA,EACxC,wBAAoE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ3D,sCAAsB,IAAA;AAAA,EAO/B,oBAA2D;AAAA,EAEnE,cAAc;AAAE,UAAM,EAAE,GAAG,gBAAgB;AAAA,EAAE;AAAA,EAE7C,MAAgB,eAAqE;AAQnF,UAAM,MAAM,KAAK,IAAI,OAAO,eAAe,KAAK,IAAI;AACpD,SAAK,SAAS,IAAI,SAAS,GAAG,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAK;AAEvD,SAAK,SAAS,IAAI,eAAe;AAAA,MAC/B,eAAe,KAAK,OAAO;AAAA,MAC3B,yBAAyB,KAAK,OAAO;AAAA,MACrC,mBAAmB,KAAK,OAAO;AAAA,MAC/B,iBAAiB,KAAK,OAAO;AAAA,MAC7B,cAAc,CAAC,UAAU,UAAU,KAAK,aAAa,UAAU,KAAK;AAAA,MACpE,eAAe,CAAC,UAAU,UAAU,KAAK,kBAAkB,UAAU,KAAK;AAAA,MAC1E,gBAAgB,CAAC,UAAU,OAAO,SAAS,KAAK,mBAAmB,UAAU,OAAO,IAAI;AAAA,MACxF,QAAQ,KAAK,IAAI;AAAA,IAAA,CAClB;AAED,SAAK,OAAO,cAAc,UAAU,KAAK,IAAI,OAAO,MAAM,QAAQ,CAAC;AAKnE,SAAK,OAAO,wBAAwB,CAAC,UAAU,WAAW;AACxD,WAAK,4BAA4B,UAAU,MAAM;AAAA,IACnD,CAAC;AAQD,SAAK,OAAO,SAAS,OAAO,UAAU,OAAO,QAAQ,aAAa,WAAW;AAC3E,WAAK,oBAAoB,UAAU,OAAO,QAAQ,MAAM;AAAA,IAC1D,CAAC;AAED,SAAK,OAAO,MAAA;AACZ,SAAK,IAAI,OAAO;AAAA,MACd;AAAA,MACA;AAAA,QACE,MAAM,EAAE,QAAQ,KAAK,OAAA;AAAA,QACrB,MAAM;AAAA,UACJ,eAAe,KAAK,OAAO;AAAA,UAC3B,YAAY,KAAK,OAAO;AAAA,QAAA;AAAA,MAC1B;AAAA,IACF;AAUF,QAAI,KAAK,IAAI,UAAU;AACrB,WAAK,oBAAoB,KAAK,IAAI,SAAS;AAAA,QACzC,EAAE,UAAU,cAAc,sBAAA;AAAA,QAC1B,CAAC,UAAU;AACT,gBAAM,OAAO,MAAM;AACnB,gBAAM,WAAW,KAAK;AACtB,gBAAM,aAAa,KAAK,SAAS,IAAI,QAAQ;AAC7C,cAAI,CAAC,WAAY;AACjB,gBAAM,SAAS,KAAK;AAQpB,cAAI,WAAW,WAAW;AACxB,iBAAK,KAAK,4BAA4B,UAAU,KAAK,QAAQ;AAAA,UAC/D;AACA,cAAI,CAAC,WAAW,OAAO,cAAc,SAAS,MAAM,EAAG;AACvD,eAAK,QAAQ;AAAA,YACX;AAAA,YACA,KAAK;AAAA,YACL;AAAA,YACA,KAAK,UAAU,CAAC,GAAG,KAAK,OAAO,IAAI;AAAA,UAAA;AAAA,QAEvC;AAAA,MAAA;AAAA,IAEJ;AAKA,SAAK,uBAAuB;AAAA,MAC1B,MAAM,KAAK,oBAAA;AAAA,MACX;AAAA,IAAA;AAGF,WAAO;AAAA,MACL,WAAW,CAAC,EAAE,YAAY,0BAA0B,UAAU,MAAM;AAAA,MACpE,eAAe;AAAA,MACf,gBAAgB;AAAA,QACd,iBAAiB,OAAO,UAAU,KAAK,gBAAgB,KAAK;AAAA,QAC5D,mBAAmB,OAAO,UAAU,KAAK,kBAAkB,KAAK;AAAA,QAChE,mBAAmB,OAAO,UAAU,KAAK,kBAAkB,KAAK;AAAA,MAAA;AAAA,IAClE;AAAA,EAEJ;AAAA,EAEA,MAAgB,aAA4B;AAC1C,QAAI,KAAK,sBAAsB;AAC7B,oBAAc,KAAK,oBAAoB;AACvC,WAAK,uBAAuB;AAAA,IAC9B;AACA,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AACA,SAAK,gBAAgB,MAAA;AACrB,QAAI,KAAK,mBAAmB;AAC1B,WAAK,kBAAA;AACL,WAAK,oBAAoB;AAAA,IAC3B;AACA,SAAK,qBAAqB,MAAA;AAK1B,eAAW,YAAY,CAAC,GAAG,KAAK,8BAA8B,KAAA,GAAQ,GAAG,KAAK,oBAAoB,KAAA,CAAM,GAAG;AACzG,WAAK,qBAAqB,QAAQ;AAAA,IACpC;AAEA,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO,KAAA;AACZ,WAAK,SAAS;AAAA,IAChB;AAEA,eAAW,cAAc,KAAK,SAAS,OAAA,GAAU;AAC/C,iBAAW,oBAAA;AACX,iBAAW,uBAAA;AAAA,IACb;AACA,SAAK,SAAS,MAAA;AAAA,EAChB;AAAA;AAAA,EAIA,MAAc,gBAAgB,OAA6D;AAKzF,UAAM,SAAS,MAAM,OAAO,OAAO,GAAG;AACtC,UAAM,OAAO,OAAO,KAAK,MAAM,aAAa,QAAQ;AACpD,UAAM,EAAE,MAAM,KAAA,IAAS,MAAM,MAAM,IAAI,EAAE,IAAA,EAAM,SAAS,EAAE,mBAAmB,MAAM;AACnF,QAAI,KAAK,aAAa,GAAG;AACvB,YAAM,IAAI,MAAM,mDAAmD,KAAK,QAAQ,EAAE;AAAA,IACpF;AACA,UAAM,MAAM,IAAI,WAAW,IAAI;AAC/B,UAAM,QAAQ,KAAK,IAAI,MAAS,MAAM,cAAc,OAAO,GAAI;AAC/D,UAAM,UAAU,gBAAgB,KAAK,MAAM,SAAS,EAAE,CAAC,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAClG,UAAM,YAAY,KAAK,IAAA,IAAQ;AAC/B,SAAK,gBAAgB,IAAI,SAAS,EAAE,MAAM,KAAK,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,QAAQ,OAAO,WAAW;AACjH,QAAI,CAAC,KAAK,mBAAmB;AAC3B,WAAK,oBAAoB,YAAY,MAAM,KAAK,qBAAA,GAAwB,GAAM;AAC9E,WAAK,kBAAkB,QAAA;AAAA,IACzB;AACA,SAAK,IAAI,OAAO,KAAK,sBAAsB;AAAA,MACzC,MAAM,EAAE,SAAS,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,OAAO,IAAI,QAAQ,MAAA;AAAA,IAAM,CACnF;AACD,WAAO,EAAE,SAAS,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,UAAA;AAAA,EAC5D;AAAA,EAEA,MAAc,kBAAkB,OAAiE;AAC/F,WAAO,EAAE,UAAU,KAAK,gBAAgB,OAAO,MAAM,OAAO,EAAA;AAAA,EAC9D;AAAA,EAEQ,uBAA6B;AACnC,UAAM,MAAM,KAAK,IAAA;AACjB,eAAW,CAAC,IAAI,KAAK,KAAK,KAAK,iBAAiB;AAC9C,UAAI,MAAM,YAAY,IAAK,MAAK,gBAAgB,OAAO,EAAE;AAAA,IAC3D;AAAA,EACF;AAAA,EAEA,MAAc,kBAAkB,OAAiE;AAC/F,UAAM,MAAM,KAAK;AACjB,UAAM,MAAM,IAAI;AAChB,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,wCAAwC;AAClE,QAAI,OAAO,KAAK,2BAA2B;AAAA,MACzC,MAAM,EAAE,SAAS,MAAM,SAAS,UAAU,MAAM,UAAU,YAAY,MAAM,WAAA;AAAA,IAAW,CACxF;AAED,UAAM,SAAS,KAAK,gBAAgB,IAAI,MAAM,OAAO;AACrD,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,0CAA0C;AAAA,IACvG;AAEA,UAAM,aAAa,MAAM,MAAM,IAAI,CAAA,OAAM;AAAA,MACvC,SAAS,EAAE;AAAA,MACX,SAAS,EAAE;AAAA,MACX,SAAS,EAAE;AAAA,MACX,UAAU,EAAE,YAAY,CAAA;AAAA,IAAC,EACzB;AAMF,UAAM,eAAe,WAAW,OAAO,CAAA,MAAK,EAAE,OAAO;AACrD,UAAM,eAAe,aAAa,WAAW,MACvC,CAAC,aAAa,CAAC,EAAG,YAAY,aAAa,CAAC,EAAG,SAAS,OAAO,OAAK,EAAE,OAAO,EAAE,WAAW;AAChG,UAAM,cAAc,gBAAgB,CAAC,MAAM;AAC3C,UAAM,WAAW,aAAa,CAAC;AAI/B,UAAM,cAA0B;AAAA,MAC9B,MAAM,OAAO;AAAA,MACb,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,WAAW,KAAK,IAAA;AAAA,IAAI;AAGtB,QAAI,cAA6B;AAEjC,QAAI,eAAe,UAAU;AAE3B,UAAI,OAAO,KAAK,4CAA4C;AAAA,QAC1D,MAAM,EAAE,MAAM,SAAS,SAAS,OAAO,SAAS,QAAA;AAAA,MAAQ,CACzD;AAGD,YAAM,cAAc,MAAM,IAAI,iBAAiB,iBAAiB,OAAO;AAAA,QACrE,MAAM,IAAI,WAAW,OAAO,KAAK,MAAA,EAAQ,MAAM;AAAA,QAC/C,OAAO,OAAO;AAAA,QACd,QAAQ,OAAO;AAAA,QACf,QAAQ,OAAO;AAAA,MAAA,CAChB;AACD,oBAAc,YAAY;AAS1B,YAAM,IAAI,iBAAiB,YAAY,OAAO;AAAA,QAC5C,OAAO;AAAA,QACP,OAAO;AAAA,QACP,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAA,IAAW,CAAA;AAAA,MAAC,CAChD;AAED,YAAMK,eAAc,MAAM,UAAU;AACpC,eAAS,IAAI,GAAG,IAAIA,cAAa,KAAK;AACpC,cAAM,IAAI,iBAAiB,YAAY,OAAO;AAAA,UAC5C,QAAQ,SAAS;AAAA,UACjB,SAAS;AAAA,QAAA,CACV;AAAA,MACH;AAGA,YAAMC,eAAwB,CAAA;AAC9B,YAAMC,gBAAyB,CAAA;AAC/B,YAAMC,qBAA8B,CAAA;AACpC,YAAMC,kBAA2B,CAAA;AACjC,YAAMC,cAAuB,CAAA;AAC7B,YAAMC,aAAsB,CAAA;AAC5B,UAAI,KAAK;AACT,YAAMC,aAAY,MAAM,aAAa,SAAS,KAAK,MAAM,SAAS,EAAE,CAAC;AACrE,YAAMC,aAAY,MAAM,WAAW,MAAM;AAEzC,YAAMC,aAAY,YAAY,IAAA;AAC9B,YAAMC,UAAS,YAA2B;AACxC,iBAAS,IAAI,GAAG,IAAI,MAAM,YAAY,KAAK;AACzC,gBAAM,KAAK,YAAY,IAAA;AACvB,gBAAM,SAAS,MAAM,IAAI,iBAAiB,YAAY,OAAO;AAAA,YAC3D,QAAQ,SAAS;AAAA,YACjB,SAAS;AAAA,UAAA,CACV;AACD,gBAAM,SAAS,YAAY,IAAA,IAAQ;AACnC,gBAAM,IAAI;AACV,gBAAM,UAAU,OAAO,EAAE,aAAa,MAAM,WAAW,EAAE,aAAa,IAAc;AACpF,gBAAM,QAAQ,OAAO,EAAE,cAAc,MAAM,WAAW,EAAE,cAAc,IAAc;AACpF,gBAAM,SAAS,OAAO,EAAE,WAAW,MAAM,WAAW,EAAE,WAAW,IAAc;AAC/E,gBAAM,KAAK,OAAO,EAAE,WAAW,MAAM,WAAW,EAAE,WAAW,IAAc;AAC3E,gBAAM,OAAO,MAAM,QAAQ,EAAE,YAAY,CAAC,IAAK,EAAE,YAAY,EAAgB,SAAS;AACtFT,uBAAY,KAAK,MAAM;AACvBC,wBAAa,KAAK,OAAO;AACzBC,6BAAkB,KAAK,KAAK;AAC5BC,0BAAe,KAAK,MAAM;AAC1BC,sBAAW,KAAK,EAAE;AAClBC,qBAAU,KAAK,IAAI;AACnB,gBAAM,IAAI,EAAE;AACZ,cAAI,KAAK,IAAI;AACX,gBAAI,OAAO,KAAK,6BAA6B;AAAA,cAC3C,MAAM,EAAE,GAAG,QAAQ,KAAK,MAAM,MAAM,GAAG,SAAS,KAAK,MAAM,OAAO,GAAG,OAAO,KAAK,MAAM,QAAQ,EAAE,IAAI,IAAI,QAAQ,KAAK,MAAM,SAAS,EAAE,IAAI,IAAI,GAAA;AAAA,YAAG,CACnJ;AAAA,UACH;AAEA,cAAI,IAAI,KAAK,IAAI,GAAG,MAAM,QAAQ,MAAM,GAAG;AACzC,kBAAM,WAAW,YAAY,IAAA,IAAQG,cAAa;AAClD,kBAAM,MAAM,UAAU,IAAI,IAAI,UAAU;AACxC,kBAAM,aAAaR,aAAY,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAIA,aAAY;AACxE,kBAAM,SAAS,CAAC,GAAGA,YAAW,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACpD,kBAAM,MAAM,OAAO,KAAK,IAAI,OAAO,SAAS,GAAG,KAAK,MAAM,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK;AACrF,kBAAM,WAAWK,WAAU,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AACpD,kBAAM,SAASA,WAAU,SAAS,IAAI,WAAWA,WAAU,SAAS;AACpE,kBAAM,SAASD,YAAW,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAIA,YAAW;AAClE,kBAAM,MAAM,QAAQ,CAAC,IAAIG,UAAS,MAAW,IAAI,QAAQ,CAAC,CAAC,eAAoB,WAAW,QAAQ,CAAC,CAAC,cAAmB,OAAO,QAAQ,CAAC,CAAC;AACxI,gBAAI,IAAI,UAAU;AAIhB,kBAAI,SAAS,KAAK;AAAA,gBAChB,IAAI,SAAS,CAAC;AAAA,gBACd,+BAAe,KAAA;AAAA,gBACf,QAAQ,EAAE,MAAM,YAAqB,IAAI,kBAAA;AAAA,gBACzC,UAAU,cAAc;AAAA,gBACxB,MAAM;AAAA,kBACJ,QAAQ;AAAA,kBAAO,WAAAD;AAAAA,kBAAW,MAAM;AAAA,kBAAmB,SAAS;AAAA,kBAC5D,eAAe;AAAA,kBAAM,MAAM;AAAA,kBAAG,WAAAC;AAAAA,kBAC9B,KAAK,KAAK,MAAM,MAAM,GAAG,IAAI;AAAA,kBAC7B,QAAQ,KAAK,MAAM,aAAa,GAAG,IAAI;AAAA,kBACvC,OAAO,KAAK,MAAM,MAAM,GAAG,IAAI;AAAA,kBAC/B,aAAa,KAAK,MAAON,cAAa,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAIA,cAAa,SAAU,GAAG,IAAI;AAAA,kBACjG,kBAAkB,KAAK,MAAOC,mBAAkB,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAIA,mBAAkB,SAAU,GAAG,IAAI;AAAA,kBAChH,eAAe,KAAK,MAAOC,gBAAe,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAIA,gBAAe,SAAU,GAAG,IAAI;AAAA,kBACvG,eAAe,KAAK,MAAM,SAAS,GAAG,IAAI;AAAA,kBAC1C,WAAW,UAAU,IAAI,KAAK,MAAO,WAAW,UAAW,GAAG,IAAI,MAAM;AAAA,kBACxE,eAAe,KAAK,MAAM,SAAS,GAAG,IAAI;AAAA,gBAAA;AAAA,cAC5C,CACD;AAAA,YACH,OAAO;AACL,kBAAI,OAAO,KAAK,2BAA2B;AAAA,YAC7C;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,QAAQ,MAAM,SAAA,GAAY,MAAMM,QAAAA,CAAQ,CAAC;AACxE,YAAMC,YAAW,YAAY,IAAA,IAAQF,cAAa;AAGlD,YAAM,IAAI,iBAAiB,aAAa,OAAO,EAAE,SAAS,YAAA,CAAa,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAEvF,aAAO,KAAK,iBAAiBR,cAAaC,eAAcC,oBAAmBC,iBAAgBC,aAAYC,YAAWK,UAAS,QAAQ;AAAA,IACrI;AAGA,QAAI,OAAO,KAAK,gDAAgD;AAAA,MAC9D,MAAM,EAAE,OAAO,aAAa,QAAQ,kBAAkB,CAAC,CAAC,MAAM,iBAAA;AAAA,IAAiB,CAChF;AAED,QAAI,aAAa;AACjB,UAAM,WAAW,YAA8D;AAC7E,YAAM,KAAK,YAAY,IAAA;AACvB,YAAM,SAAS,MAAM,IAAI,iBAAiB,YAAY,OAAO;AAAA,QAC3D,OAAO;AAAA,QACP,OAAO;AAAA,QACP,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAA,IAAW,CAAA;AAAA,MAAC,CAChD;AACD,YAAM,SAAS,YAAY,IAAA,IAAQ;AACnC,YAAM,IAAI,EAAE;AACZ,UAAI,KAAK,IAAI;AACX,YAAI,OAAO,KAAK,oBAAoB;AAAA,UAClC,MAAM;AAAA,YACJ;AAAA,YACA,QAAQ,KAAK,MAAM,MAAM;AAAA,YACzB,kBAAkB,KAAK,MAAM,OAAO,OAAO,oBAAoB,CAAC;AAAA,YAChE,WAAW,KAAK,OAAO,OAAO,OAAO,aAAa,KAAK,EAAE,IAAI;AAAA,YAC7D,cAAc,KAAK,OAAO,OAAO,OAAO,gBAAgB,KAAK,EAAE,IAAI;AAAA,YACnE,WAAW,OAAO,OAAO,aAAa;AAAA,UAAA;AAAA,QACxC,CACD;AAAA,MACH;AACA,aAAO,EAAE,QAAQ,OAAA;AAAA,IACnB;AAGA,UAAM,cAAc,MAAM,UAAU;AACpC,aAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,YAAM,SAAA;AAAA,IACR;AAEA,UAAM,cAAwB,CAAA;AAC9B,UAAM,oBAA8B,CAAA;AACpC,UAAM,eAAyB,CAAA;AAC/B,UAAM,oBAA8B,CAAA;AACpC,UAAM,iBAA2B,CAAA;AACjC,UAAM,aAAuB,CAAA;AAC7B,UAAM,YAAsB,CAAA;AAC5B,UAAM,YAAY,MAAM,aAAa,SAAS,KAAK,MAAM,SAAS,EAAE,CAAC;AACrE,UAAM,YAAY,MAAM,WAAW,MAAM;AAEzC,UAAM,YAAY,YAAY,IAAA;AAC9B,UAAM,SAAS,YAA2B;AACxC,eAAS,IAAI,GAAG,IAAI,MAAM,YAAY,KAAK;AACzC,cAAM,EAAE,QAAQ,OAAA,IAAW,MAAM,SAAA;AACjC,oBAAY,KAAK,MAAM;AACvB,0BAAkB,KAAK,OAAO,OAAO,UAAU,CAAC;AAChD,qBAAa,KAAK,OAAO,OAAO,oBAAoB,CAAC;AACrD,0BAAkB,KAAK,OAAO,OAAO,gBAAgB,CAAC;AACtD,uBAAe,KAAK,OAAO,OAAO,aAAa,CAAC;AAChD,mBAAW,KAAK,OAAO,OAAO,aAAa,CAAC;AAC5C,kBAAU,KAAK,OAAO,YAAY,UAAU,CAAC;AAC7C,cAAM,IAAI,YAAY;AACtB,YAAI,IAAI,KAAK,IAAI,GAAG,MAAM,QAAQ,MAAM,KAAK,IAAI,UAAU;AACzD,gBAAM,WAAW,YAAY,IAAA,IAAQ,aAAa;AAClD,gBAAM,MAAM,UAAU,IAAI,IAAI,UAAU;AACxC,gBAAM,SAAS,YAAY,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI;AACxD,gBAAM,SAAS,CAAC,GAAG,WAAW,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACpD,gBAAM,MAAM,OAAO,KAAK,IAAI,OAAO,SAAS,GAAG,KAAK,MAAM,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK;AACrF,gBAAM,WAAW,UAAU,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AACpD,gBAAM,SAAS,WAAW,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI;AACvD,cAAI,SAAS,KAAK;AAAA,YAChB,IAAI,SAAS,CAAC;AAAA,YACd,+BAAe,KAAA;AAAA,YACf,QAAQ,EAAE,MAAM,YAAqB,IAAI,kBAAA;AAAA,YACzC,UAAU,cAAc;AAAA,YACxB,MAAM;AAAA,cACJ,QAAQ;AAAA,cAAO;AAAA,cAAW,MAAM;AAAA,cAChC,SAAS,QAAQ,CAAC,IAAI,SAAS,MAAW,IAAI,QAAQ,CAAC,CAAC,eAAoB,OAAO,QAAQ,CAAC,CAAC,cAAmB,OAAO,QAAQ,CAAC,CAAC;AAAA,cACjI,eAAe;AAAA,cAAM,MAAM;AAAA,cAAG;AAAA,cAC9B,KAAK,KAAK,MAAM,MAAM,GAAG,IAAI;AAAA,cAC7B,QAAQ,KAAK,MAAM,SAAS,GAAG,IAAI;AAAA,cACnC,OAAO,KAAK,MAAM,MAAM,GAAG,IAAI;AAAA,cAC/B,aAAa,KAAK,MAAO,aAAa,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,IAAK,GAAG,IAAI;AAAA,cAC/E,kBAAkB,KAAK,MAAO,kBAAkB,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,IAAK,GAAG,IAAI;AAAA,cACzF,eAAe,KAAK,MAAO,eAAe,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,IAAK,GAAG,IAAI;AAAA,cACnF,eAAe,KAAK,MAAM,SAAS,GAAG,IAAI;AAAA,cAC1C,WAAW,UAAU,IAAI,KAAK,MAAO,WAAW,UAAW,GAAG,IAAI,MAAM;AAAA,cACxE,eAAe,IAAI,IAAI,KAAK,MAAO,WAAW,IAAK,GAAG,IAAI,MAAM;AAAA,YAAA;AAAA,UAClE,CACD;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,UAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,QAAQ,MAAM,SAAA,GAAY,MAAM,OAAA,CAAQ,CAAC;AACxE,UAAM,WAAW,YAAY,IAAA,IAAQ,aAAa;AAElD,WAAO,KAAK,iBAAiB,aAAa,cAAc,mBAAmB,gBAAgB,YAAY,WAAW,SAAS,UAAU;AAAA,EACvI;AAAA,EAEA,MAAc,iBACZ,aACA,cACA,mBACA,gBACA,YACA,WACA,SACA,MACkC;AAClC,UAAM,YAAY,CAAC,OACjB,GAAG,SAAS,IAAI,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,SAAS;AAC9D,SAAK,IAAI,OAAO,KAAK,2BAA2B;AAAA,MAC9C,MAAM;AAAA,QACJ,MAAM,YAAY;AAAA,QAClB,SAAS,KAAK,MAAM,UAAU,GAAG,IAAI;AAAA,QACrC,KAAK,KAAK,MAAO,YAAY,SAAS,UAAW,GAAG,IAAI;AAAA,QACxD,YAAY,KAAK,MAAM,UAAU,WAAW,CAAC;AAAA,QAC7C,aAAa,KAAK,MAAM,UAAU,YAAY,CAAC;AAAA,QAC/C,kBAAkB,KAAK,MAAM,UAAU,iBAAiB,CAAC;AAAA,QACzD,eAAe,KAAK,MAAM,UAAU,cAAc,CAAC;AAAA,QACnD,eAAe,KAAK,MAAM,UAAU,UAAU,IAAI,GAAG,IAAI;AAAA,QACzD,cAAc,WAAW,SAAS,IAAI,KAAK,IAAI,GAAG,UAAU,IAAI;AAAA,MAAA;AAAA,IAClE,CACD;AAED,UAAM,SAAS,CAAC,GAAG,WAAW,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACpD,UAAM,OAAO,CAAC,MACZ,OAAO,SAAS,IACZ,OAAO,KAAK,IAAI,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,OAAO,MAAM,CAAC,CAAC,IACjE;AACN,UAAM,SAAS,CAAC,OACd,GAAG,SAAS,IAAI,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,SAAS;AAE9D,UAAM,YAAY,YAAY;AAC9B,UAAM,WAAW,UAAU,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAEpD,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,KAAK,MAAM,UAAU,GAAI,IAAI;AAAA,MACtC,KAAK,UAAU,IAAI,KAAK,MAAO,YAAY,UAAW,GAAG,IAAI,MAAM;AAAA,MACnE,kBAAkB,UAAU,IAAI,KAAK,MAAO,WAAW,UAAW,GAAG,IAAI,MAAM;AAAA,MAC/E,eAAe,YAAY,IAAI,KAAK,MAAO,WAAW,YAAa,GAAG,IAAI,MAAM;AAAA,MAChF,QAAQ;AAAA,QACN,MAAM,KAAK,MAAM,OAAO,WAAW,IAAI,GAAG,IAAI;AAAA,QAC9C,KAAK,KAAK,MAAM,KAAK,GAAG,IAAI,GAAG,IAAI;AAAA,QACnC,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,GAAG,IAAI;AAAA,QACpC,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,GAAG,IAAI;AAAA,MAAA;AAAA,MAEtC,SAAS,KAAK,MAAM,OAAO,YAAY,IAAI,GAAG,IAAI;AAAA,MAClD,cAAc,KAAK,MAAM,OAAO,iBAAiB,IAAI,GAAG,IAAI;AAAA,MAC5D,WAAW,KAAK,MAAM,OAAO,cAAc,IAAI,GAAG,IAAI;AAAA,MACtD,eAAe,KAAK,MAAM,OAAO,UAAU,IAAI,GAAG,IAAI;AAAA,MACtD,cAAc,WAAW,SAAS,IAAI,KAAK,IAAI,GAAG,UAAU,IAAI;AAAA,MAChE;AAAA,MACA,GAAI,MAAM,KAAK,mBAAA;AAAA,IAAmB;AAAA,EAEtC;AAAA,EAEA,MAAc,qBAAkF;AAC9F,QAAI;AACF,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,CAAC,IAAK,QAAO,CAAA;AACjB,YAAM,CAAC,KAAK,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,QACtC,IAAI,iBAAiB,kBAAkB,MAAA;AAAA,QACvC,IAAI,iBAAiB,mBAAmB,MAAA;AAAA,MAAM,CAC/C;AACD,aAAO;AAAA,QACL,QAAQ,MAAM,EAAE,SAAS,IAAI,SAAS,SAAS,IAAI,SAAS,QAAQ,IAAI,OAAA,IAAW;AAAA,QACnF,QAAQ,UAAU;AAAA,MAAA;AAAA,IAEtB,QAAQ;AACN,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,aAAa,QAAwD;AACzE,UAAM,SAAS,KAAK;AACpB,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,UAAU,CAAC,KAAK;AACnB,YAAM,IAAI,MAAM,sEAAsE;AAAA,IACxF;AAIA,SAAK,IAAI,OAAO,KAAK,gCAAgC;AAAA,MACnD,MAAM,EAAE,UAAU,OAAO,SAAA;AAAA,MACzB,MAAM;AAAA,QACJ,eAAe,OAAO;AAAA,QACtB,mBAAmB,MAAM,QAAQ,OAAO,aAAa,IAAI,SAAS,OAAO,cAAc,MAAM,MAAM,OAAO,OAAO;AAAA,QACjH,gBAAgB,OAAO;AAAA,QACvB,mBAAmB,OAAO;AAAA,QAC1B,MAAM,OAAO,KAAK,MAA4C;AAAA,MAAA;AAAA,IAChE,CACD;AAED,QAAI,KAAK,SAAS,IAAI,OAAO,QAAQ,GAAG;AAEtC,WAAK,eAAe,OAAO,QAAQ;AAAA,IACrC;AAEA,WAAO,eAAe,OAAO,UAAU;AAAA,MACrC,eAAe,OAAO;AAAA,MACtB,KAAK,OAAO;AAAA,MACZ,kBAAkB,OAAO;AAAA,IAAA,CAC1B;AAED,UAAM,aAA6B;AAAA,MACjC;AAAA,MACA,mBAAmB;AAAA,MACnB,sBAAsB;AAAA,IAAA;AAExB,SAAK,SAAS,IAAI,OAAO,UAAU,UAAU;AAO7C,QAAI,OAAO,cAAc,SAAS,UAAU,GAAG;AAC7C,iBAAW,oBAAoB,MAAM,KAAK,sBAAsB,MAAM;AAAA,IACxE;AAQA,UAAM,aAAa,OAAO,OAAO,UAAU;AAC3C,UAAM,WAAW,aAAa,IAC1B,eAAe,UAAU,OAAO,eAAe,IAAI,KAAK,GAAG,MAC3D,OAAO,UAAU,SACf,iBACA;AACN,UAAM,cAAc,OAAO,SAAS,GAAG,OAAO,OAAO,OAAO,IAAI,OAAO,OAAO,OAAO,IAAI,OAAO,OAAO,MAAM,KAAK;AAClH,SAAK,IAAI,OAAO;AAAA,MACd;AAAA,MACA;AAAA,QACE,MAAM,EAAE,UAAU,OAAO,SAAA;AAAA,QACzB,MAAM;AAAA,UACJ,eAAe,OAAO;AAAA,UACtB,WAAW,OAAO;AAAA,UAClB,WAAW,OAAO;AAAA,UAClB,cAAc,OAAO;AAAA,UACrB,eAAe,OAAO;AAAA,UACtB;AAAA,UACA,QAAQ;AAAA,QAAA;AAAA,MACV;AAAA,IACF;AAGF,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA,EAEA,MAAM,aAAa,OAAyD;AAC1E,SAAK,eAAe,MAAM,QAAQ;AAClC,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA,EAEA,MAAM,aAAa,OAKY;AAC7B,SAAK,QAAQ,aAAa,MAAM,UAAU,MAAM,UAAU,MAAM,QAAQ,MAAM,OAAO;AACrF,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA,EAEQ,eAAe,UAAwB;AAC7C,UAAM,aAAa,KAAK,SAAS,IAAI,QAAQ;AAC7C,QAAI,CAAC,WAAY;AAKjB,SAAK,qBAAqB,QAAQ;AAElC,eAAW,oBAAA;AACX,eAAW,uBAAA;AACX,SAAK,SAAS,OAAO,QAAQ;AAC7B,SAAK,aAAa,OAAO,QAAQ;AACjC,SAAK,yBAAyB,OAAO,QAAQ;AAE7C,SAAK,QAAQ,iBAAiB,QAAQ;AACtC,SAAK,KAAK,OAAO,KAAK,gBAAgB,EAAE,MAAM,EAAE,SAAA,GAAY;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,qBAAqB,UAAwB;AACnD,UAAM,QAAQ,KAAK,8BAA8B,IAAI,QAAQ;AAC7D,QAAI,UAAU,QAAW;AACvB,mBAAa,KAAK;AAClB,WAAK,8BAA8B,OAAO,QAAQ;AAAA,IACpD;AACA,UAAM,QAAQ,KAAK,oBAAoB,IAAI,QAAQ;AACnD,QAAI,UAAU,QAAW;AACvB,UAAI;AAAE,cAAA;AAAA,MAAQ,QAAQ;AAAA,MAA6C;AACnE,WAAK,oBAAoB,OAAO,QAAQ;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAc,4BAA4B,UAAkB,UAAkC;AAC5F,UAAM,aAAa,KAAK,SAAS,IAAI,QAAQ;AAC7C,QAAI,CAAC,WAAY;AAEjB,UAAM,SAAS,WAAW;AAC1B,QAAI,CAAC,2BAA2B,MAAM,EAAG;AAEzC,UAAM,MAAM,KAAK,IAAI,OAAO,SAAS,EAAE,UAAU;AAGjD,QAAI,YAAY,CAAC,KAAK,oBAAoB,IAAI,QAAQ,GAAG;AACvD,YAAM,QAAQ,MAAM,KAAK,sBAAsB,MAAM;AACrD,UAAI,OAAO;AACT,aAAK,oBAAoB,IAAI,UAAU,KAAK;AAC5C,YAAI,MAAM,oDAAoD;AAAA,MAChE;AAAA,IACF;AAKA,UAAM,WAAW,KAAK,8BAA8B,IAAI,QAAQ;AAChE,QAAI,aAAa,OAAW,cAAa,QAAQ;AAEjD,UAAM,aAAa,OAAO;AAC1B,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK,8BAA8B,OAAO,QAAQ;AAClD,YAAM,QAAQ,KAAK,oBAAoB,IAAI,QAAQ;AACnD,UAAI,CAAC,MAAO;AACZ,UAAI;AAAE,cAAA;AAAA,MAAQ,QAAQ;AAAA,MAAgB;AACtC,WAAK,oBAAoB,OAAO,QAAQ;AACxC,UAAI,MAAM,kDAAkD,EAAE,MAAM,EAAE,WAAA,GAAc;AAAA,IACtF,GAAG,UAAU;AACb,SAAK,8BAA8B,IAAI,UAAU,KAAK;AAAA,EACxD;AAAA,EAEA,MAAM,eAAyC;AAC7C,UAAM,UAAU,KAAK,QAAQ,WAAA,KAAgB,EAAyC,oBAAoB,GAAG,YAAY,EAAA;AACzH,UAAM,mBAAmB,KAAK,QAAQ,oBAAA,KAAyB,CAAA;AAE/D,QAAI,gBAAgB;AACpB,QAAI,iBAAiB;AACrB,eAAW,MAAM,kBAAkB;AACjC,UAAI,GAAG,UAAU,SAAU;AAC3B,wBAAkB,GAAG;AAAA,IACvB;AAYA,WAAO;AAAA,MACL,QAAQ,KAAK;AAAA,MACb,iBAAiB,KAAK,SAAS;AAAA,MAC/B;AAAA,MACA,iBAAiB;AAAA,MACjB,oBAAoB,QAAQ;AAAA,MAC5B,iBAAiB,QAAQ;AAAA,MACzB,UAAU;AAAA,QACR,QAAQ;AAAA,QACR,kBAAkB;AAAA,MAAA;AAAA,IACpB;AAAA,EAEJ;AAAA,EAEA,MAAM,kBAA+C;AACnD,UAAM,IAAI,KAAK,QAAQ,WAAA,KAAgB,EAAE,eAAe,GAAG,kBAAkB,GAAG,oBAAoB,GAAG,YAAY,EAAA;AACnH,WAAO,EAAE,QAAQ,KAAK,QAAQ,GAAG,EAAA;AAAA,EACnC;AAAA,EAEA,MAAM,iBAAiB,OAA4D;AACjF,WAAO,KAAK,QAAQ,iBAAiB,MAAM,QAAQ,KAAK;AAAA,EAC1D;AAAA,EAEA,sBAA2E;AACzE,WAAO,KAAK,QAAQ,oBAAA,KAAyB,CAAA;AAAA,EAC/C;AAAA,EAEA,kBAAqC;AACnC,WAAO,CAAC,GAAG,KAAK,SAAS,MAAM;AAAA,EACjC;AAAA;AAAA,EAIA,MAAc,sBAAsB,QAA0D;AAC5F,UAAM,MAAM,KAAK;AACjB,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,OAAO,CAAC,OAAQ,QAAO;AAE5B,UAAM,MAAM,KAAK,IAAI,OAAO,SAAS,EAAE,UAAU,OAAO,UAAU;AAClE,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,CAAC,KAAK;AACR,UAAI,KAAK,mDAAmD;AAC5D,aAAO;AAAA,IACT;AAMA,WAAO,uBAAuB;AAAA,MAC5B;AAAA,MACA,UAAU,mBAAmB,OAAO,UAAU,OAAO,cAAc;AAAA,MACnE,QAAQ;AAAA,MACR,QAAQ,OAAO;AAAA,MACf,KAAK;AAAA,MACL,QAAQ;AAAA;AAAA;AAAA,MAGR,SAAS,CAAC,OAAqB,YAAY;AACzC,eAAO,mBAAmB,OAAO,UAAU,KAAK;AAAA,MAClD;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEQ,4BAA4B,UAAkB,QAA2C;AAC/F,UAAM,aAAa,KAAK,SAAS,IAAI,QAAQ;AAC7C,QAAI,CAAC,WAAY;AAEjB,QAAI,WAAW,aAAa;AAC1B,WAAK,KAAK,yBAAyB,WAAW,MAAM,EAAE,KAAK,CAAA,UAAS;AAClE,mBAAW,uBAAuB;AAAA,MACpC,CAAC;AAAA,IACH,OAAO;AACL,iBAAW,uBAAA;AACX,iBAAW,uBAAuB;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,mBACN,UACA,OACA,MACM;AACN,UAAM,WAAW,UAAU;AAC3B,QAAI,SAAU,MAAK,aAAa,IAAI,UAAU,KAAK,SAAS;AAC5D,UAAM,iBAAiB,KAAK,aAAa,IAAI,QAAQ,KAAK;AAC1D,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA;AAAA,MACA,kBAAkB,WAAW,KAAK,aAAa;AAAA,IAAA;AAEjD,UAAM,YAAY;AAChB,YAAM,MAAM,MAAM,KAAK,IAAI,YAAY,QAAQ;AAC/C,YAAM,IAAI,YAAY,YAAY,EAAE,SAAS,UAAU,OAAO;AAAA,IAChE,GAAA,EAAK,MAAM,CAAC,QAAiB;AAC3B,WAAK,IAAI,OAAO,MAAM,iCAAiC;AAAA,QACrD,MAAM,EAAE,SAAA;AAAA,QACR,MAAM,EAAE,OAAO,OAAO,GAAG,EAAA;AAAA,MAAE,CAC5B;AAAA,IACH,CAAC;AAOD,QAAI,KAAK,IAAI,UAAU;AACrB,YAAM,OAA8B,WAAW,aAAa;AAC5D,YAAM,KAA4B,WAAW,WAAW;AACxD,YAAM,SAAiD,WAAW,oBAAoB;AACtF,YAAM,UAAkC;AAAA,QACtC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,KAAK;AAAA,QACb,YAAY,KAAK;AAAA,QACjB,WAAW,KAAK;AAAA,MAAA;AAElB,WAAK,IAAI,SAAS,KAAK;AAAA,QACrB,cAAc;AAAA,QACd,EAAE,MAAM,UAAU,IAAI,UAAU,SAAS,KAAK,IAAI,IAAI,UAAU,QAAQ,KAAK,OAAA;AAAA,QAC7E;AAAA,MAAA,CACD;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,yBAAyB,QAA0D;AAC/F,UAAM,MAAM,KAAK;AACjB,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,OAAO,CAAC,OAAQ,QAAO;AAE5B,UAAM,MAAM,KAAK,IAAI,OAAO,SAAS,EAAE,UAAU,OAAO,UAAU;AAClE,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,CAAC,KAAK;AACR,UAAI,KAAK,sDAAsD;AAC/D,aAAO;AAAA,IACT;AAOA,WAAO,uBAAuB;AAAA,MAC5B;AAAA,MACA,UAAU,mBAAmB,OAAO,UAAU,OAAO,iBAAiB;AAAA,MACtE,QAAQ;AAAA,MACR,QAAQ,OAAO;AAAA,MACf,KAAK;AAAA,MACL,QAAQ;AAAA;AAAA;AAAA;AAAA,MAIR,SAAS,CAAC,OAAqB,WAAW;AACxC,eAAO,sBAAsB,OAAO,UAAU,OAAO,MAAM;AAAA,MAC7D;AAAA,IAAA,CACD;AAAA,EACH;AAAA;AAAA,EAIA,MAAc,aAAa,UAAkB,OAAgD;AAC3F,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK,QAAO;AAEjB,UAAM,MAAM,KAAK,IAAI,OAAO,SAAS,EAAE,UAAU;AACjD,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,CAAC,KAAK;AACR,UAAI,MAAM,0CAA0C;AACpD,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,KAAK,SAAS,IAAI,QAAQ;AAC7C,UAAM,YAAY,YAAY;AAC9B,UAAM,QAAQ,WAAW;AACzB,UAAM,SAAS,WAAW;AAO1B,QAAI,CAAC,OAAO;AACV,UAAI,KAAK,2EAA2E;AACpF,aAAO;AAAA,IACT;AACA,QAAI,MAAM,WAAW,GAAG;AACtB,aAAO;AAAA,IACT;AAEA,QAAI;AACF,aAAO,MAAM,IAAI,iBAAiB,YAAY,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,QAKnD,OAAO,CAAC,GAAG,KAAK;AAAA,QAChB;AAAA,QACA;AAAA,QACA,GAAI,SAAS,EAAE,WAAW,CAAA;AAAA,MAAC,CAC5B;AAAA,IACH,SAAS,KAAc;AACrB,YAAM,MAAM,OAAO,GAAG;AACtB,UAAI,MAAM,uBAAuB,EAAE,MAAM,EAAE,OAAO,IAAA,GAAO;AACzD,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,kBAAkB,UAAkB,OAAoC;AACpF,UAAM,MAAM,KAAK;AACjB,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,OAAO,CAAC,OAAQ;AAErB,UAAM,MAAM,KAAK,IAAI,OAAO,SAAS,EAAE,UAAU;AACjD,UAAM,cAAc,KAAK,IAAA;AACzB,QAAI;AACF,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,CAAC,KAAK;AACR,YAAI,KAAK,+CAA+C;AACxD;AAAA,MACF;AACA,YAAM,SAAS,MAAM,IAAI,gBAAgB,QAAQ,OAAO,EAAE,UAAU,OAAO,aAAa,KAAK,EAAA,CAAG;AAChG,UAAI,CAAC,OAAQ;AAEb,YAAM,WAAW,OAAO,QAAQ,SAAS;AASzC,YAAM,eAAe,KAAK,qBAAqB,IAAI,QAAQ,KAAK;AAChE,UAAI,aAAa,cAAc;AAC7B,aAAK,qBAAqB,IAAI,UAAU,QAAQ;AAChD,YAAI,KAAK,IAAI,UAAU;AACrB,eAAK,IAAI,SAAS,KAAK;AAAA,YACrB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA,YAKd,EAAE,MAAM,UAAU,IAAI,UAAU,SAAS,KAAK,IAAI,IAAI,UAAU,QAAQ,KAAK,OAAA;AAAA,YAC7E;AAAA,cACE;AAAA,cACA;AAAA,cACA,WAAW,MAAM;AAAA,cACjB,QAAQ;AAAA,cACR,GAAI,WAAW,EAAE,SAAS,OAAO,QAAA,IAAY,CAAA;AAAA,YAAC;AAAA,UAChD,CACD;AAAA,QACH;AAAA,MACF;AAEA,UAAI,KAAK,IAAI,UAAU;AACrB,cAAM,gBAAuC;AAAA,UAC3C;AAAA,UACA,aAAa,OAAO,QAAQ;AAAA,UAC5B,SAAS,OAAO,QAAQ,IAAI,CAAC,OAAO;AAAA,YAClC,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,EAAA;AAAA,YACzD,YAAY,EAAE;AAAA,YACd,WAAW,EAAE;AAAA,UAAA,EACb;AAAA,UACF,YAAY,MAAM;AAAA,UAClB,aAAa,MAAM;AAAA,UACnB,YAAY,OAAO;AAAA,QAAA;AAErB,cAAM,iBAAiB,EAAE,MAAM,UAAmB,IAAI,UAAU,SAAS,KAAK,IAAI,IAAI,UAAU,QAAQ,KAAK,OAAA;AAC7G,aAAK,IAAI,SAAS,KAAK;AAAA,UACrB,cAAc;AAAA,UACd;AAAA,UACA;AAAA,QAAA,CACD;AAED,cAAM,eAAsC;AAAA,UAC1C;AAAA,UACA,WAAW,MAAM;AAAA,UACjB,OAAO,OAAO,WAAW,IAAI,CAAC,OAAO;AAAA,YACnC,MAAM,CAAC,EAAE,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI,EAAE,KAAK,CAAC;AAAA,YACnE,YAAY,EAAE;AAAA,YACd,aAAa,EAAE,YAAY;AAAA,UAAA,EAC3B;AAAA,UACF,WAAW,EAAE,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAA;AAAA,QAAO;AAExD,aAAK,IAAI,SAAS,KAAK;AAAA,UACrB,cAAc;AAAA,UACd;AAAA,UACA;AAAA,QAAA,CACD;AAAA,MACH;AAKA,YAAM,aAAa,MAAM;AACzB,YAAM,iBAAiB,OAAO,eAAe,YAAY,aAAa,IAAI,cAAc,aAAa;AACrG,aAAO,cAAc,gBAAgB,UAAU,KAAK,IAAA,IAAQ,aAAa,cAAc;AAAA,IACzF,SAAS,OAAgB;AACvB,YAAM,MAAM,OAAO,KAAK;AACxB,UAAI,MAAM,4BAA4B,EAAE,MAAM,EAAE,OAAO,IAAA,GAAO;AAAA,IAChE;AAAA,EACF;AAAA,EAEQ,oBACN,UACA,OACA,QACA,QACM;AACN,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,KAAK,SAAU;AASpB,UAAM,aAAa,MAAM;AACzB,UAAM,UAA0C;AAAA,MAC9C;AAAA,MACA,OAAO;AAAA,MACP,QAAQ,KAAK;AAAA,MACb,aAAa;AAAA,MACb,GAAI,OAAO,eAAe,YAAY,aAAa,IAAI,EAAE,eAAe,CAAA;AAAA,IAAC;AAG3E,SAAK,IAAI,SAAS,KAAK;AAAA,MACrB,cAAc;AAAA,MACd,EAAE,MAAM,UAAU,IAAI,UAAU,QAAQ,KAAK,OAAA;AAAA,MAC7C;AAAA,IAAA,CACD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,sBAA4B;AAClC,UAAM,MAAM,KAAK;AACjB,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,KAAK,YAAY,CAAC,OAAQ;AAC/B,UAAM,YAAY,KAAK,IAAA;AAOvB,SAAK,KAAK,aAAA,EAAe,KAAK,CAAC,SAAS;AACtC,UAAI,CAAC,IAAI,SAAU;AACnB,YAAM,OAAO,KAAK,UAAU,IAAI;AAChC,YAAM,OAAO,KAAK;AAClB,YAAM,eAAe,CAAC,QAAQ,YAAY,KAAK,aAAa;AAC5D,UAAI,QAAQ,KAAK,SAAS,QAAQ,CAAC,aAAc;AACjD,WAAK,wBAAwB,EAAE,MAAM,WAAW,UAAA;AAChD,UAAI,SAAS,KAAK;AAAA,QAChB,cAAc;AAAA,QACd,EAAE,MAAM,QAAQ,IAAI,KAAK,QAAQ,QAAQ,KAAK,OAAA;AAAA,QAC9C,EAAE,QAAQ,KAAK,QAAQ,MAAM,UAAA;AAAA,MAAU,CACxC;AAAA,IACH,CAAC,EAAE,MAAM,MAAM;AAAA,IAIf,CAAC;AAGD,QAAI,KAAK,SAAS,SAAS,EAAG;AAC9B,eAAW,YAAY,KAAK,SAAS,KAAA,GAAQ;AAC3C,YAAM,UAAU,OAAO,iBAAiB,QAAQ;AAChD,UAAI,CAAC,QAAS;AACd,YAAM,OAAO,KAAK,UAAU,OAAO;AACnC,YAAM,OAAO,KAAK,yBAAyB,IAAI,QAAQ;AACvD,YAAM,eAAe,CAAC,QAAQ,YAAY,KAAK,aAAa;AAC5D,UAAI,QAAQ,KAAK,SAAS,QAAQ,CAAC,aAAc;AACjD,WAAK,yBAAyB,IAAI,UAAU,EAAE,MAAM,WAAW,WAAW;AAC1E,UAAI,SAAS,KAAK;AAAA,QAChB,cAAc;AAAA,QACd,EAAE,MAAM,UAAU,IAAI,UAAU,QAAQ,KAAK,OAAA;AAAA,QAC7C,EAAE,UAAU,QAAQ,KAAK,QAAQ,SAAS,UAAA;AAAA,MAAU,CACrD;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASU,uBAAuB;AAC/B,WAAO,KAAK,OAAO;AAAA,MACjB,UAAU;AAAA,QACR;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,KAAK;AAAA,UACL,aAAa;AAAA,UACb,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aACE;AAAA,cAGF,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,eAAe;AAAA,cACxB,WAAW;AAAA,YAAA;AAAA,YAEb;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,eAAe;AAAA,cACxB,WAAW;AAAA,YAAA;AAAA,YAEb;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,eAAe;AAAA,cACxB,MAAM;AAAA,cACN,WAAW;AAAA,YAAA;AAAA,YAEb;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,eAAe;AAAA,cACxB,WAAW;AAAA,YAAA;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF,CACD;AAAA,EACH;AAAA,EAEA,MAAgB,kBAAiC;AAC/C,SAAK,QAAQ,aAAa,KAAK,MAAM;AACrC,SAAK,IAAI,OAAO;AAAA,MACd;AAAA,MACA;AAAA,QACE,MAAM;AAAA,UACJ,eAAe,KAAK,OAAO;AAAA,UAC3B,yBAAyB,KAAK,OAAO;AAAA,UACrC,mBAAmB,KAAK,OAAO;AAAA,UAC/B,iBAAiB,KAAK,OAAO;AAAA,QAAA;AAAA,MAC/B;AAAA,IACF;AAAA,EAEJ;AACF;"}
|