@camstack/addon-export-alexa 1.2.134 → 1.2.135

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.
@@ -1,6 +1,6 @@
1
1
  import * as crypto$1 from "node:crypto";
2
2
  import { randomUUID } from "node:crypto";
3
- //#region ../types/dist/event-category-BGmz_oxW.mjs
3
+ //#region ../types/dist/event-category-B_0XEcLF.mjs
4
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
5
5
  EventCategory["SystemBoot"] = "system.boot";
6
6
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -501,15 +501,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
501
501
  EventCategory["AgentWsDisconnected"] = "agent.ws.disconnected";
502
502
  EventCategory["AgentBackupActivated"] = "agent.backup.activated";
503
503
  EventCategory["OrchestrationSettingsUpdated"] = "orchestration.settings-updated";
504
- /**
505
- * Per-agent hwaccel preference changed (user override set, cleared, or
506
- * re-probed). Observability event — decoders pull the current pref at
507
- * `createSession`, so running sessions keep their current backend until
508
- * they rotate naturally (camera add/remove, stream restart). Future
509
- * work can wire a listener in stream-broker that force-rotates live
510
- * sessions; for now this event powers logs + admin-UI toast feedback.
511
- */
512
- EventCategory["PipelineAgentHwaccelChanged"] = "pipeline.agent-hwaccel-changed";
513
504
  EventCategory["MotionAnalysis"] = "detection.motion-analysis";
514
505
  /** All raw motion zones from CCL before minArea filter — for UI debug overlay. */
515
506
  EventCategory["MotionZonesRaw"] = "detection.motion-zones-raw";
@@ -5380,7 +5371,7 @@ var ZodIssueCode = {
5380
5371
  var ZodFirstPartyTypeKind;
5381
5372
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5382
5373
  //#endregion
5383
- //#region ../types/dist/sleep-DAJ9Er57.mjs
5374
+ //#region ../types/dist/sleep-BNeXenvr.mjs
5384
5375
  /**
5385
5376
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5386
5377
  * window to float samples (D455).
@@ -6746,32 +6737,12 @@ object({
6746
6737
  audioCodec: string().optional(),
6747
6738
  metadata: record(string(), unknown()).readonly().optional()
6748
6739
  });
6749
- var EncodedPacketSchema = object({
6750
- type: _enum(["video", "audio"]),
6751
- data: _instanceof(Uint8Array),
6752
- pts: number(),
6753
- dts: number(),
6754
- keyframe: boolean(),
6755
- codec: string()
6756
- });
6757
- var DecodedFrameSchema = object({
6758
- data: _instanceof(Uint8Array),
6759
- width: number(),
6760
- height: number(),
6761
- format: _enum([
6762
- "jpeg",
6763
- "rgb",
6764
- "bgr",
6765
- "yuv420",
6766
- "gray"
6767
- ]),
6768
- timestamp: number()
6769
- });
6770
6740
  /**
6771
6741
  * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
6772
6742
  * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
6773
- * interface in `interfaces/frame-handle.ts` field-for-field so a decoder cap
6774
- * method (`pullHandles`) can carry handles over tRPC / Moleculer.
6743
+ * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
6744
+ * (the runner's native-crop and pipeline-executor inputs) can carry handles
6745
+ * over tRPC / Moleculer.
6775
6746
  *
6776
6747
  * The `satisfies` assertion below pins this schema to the interface: if a
6777
6748
  * field is added to / removed from `FrameHandle` without a matching schema
@@ -6797,50 +6768,6 @@ var FrameHandleSchema = object({
6797
6768
  slotCount: number().int().positive()
6798
6769
  });
6799
6770
  /**
6800
- * Pixel format a frame-handle subscriber can request from the broker
6801
- * (Phase 5 / D9). The packed, raster subset of `FrameFormat` — the formats a
6802
- * `frameSink: 'shm'` decoder session can write into a `FrameRing` slot.
6803
- * `jpeg` is excluded: a variable-length stream is not a fixed-stride ring
6804
- * payload, and no decoded-frame consumer requests it over the shm plane.
6805
- */
6806
- var FrameHandleFormatSchema = _enum([
6807
- "rgb",
6808
- "bgr",
6809
- "yuv420",
6810
- "gray"
6811
- ]);
6812
- /**
6813
- * Input for `stream-broker.subscribeFrames` (Phase 5 / D9). A consumer asks the
6814
- * broker for a `FrameHandle` stream of one `format`; the broker maintains one
6815
- * shared-memory ring per `(brokerId, format)` actually requested, so a `gray`
6816
- * subscriber (motion) and an `rgb` subscriber (detection) each read the format
6817
- * they asked for with no broker-side conversion.
6818
- */
6819
- var SubscribeFramesInputSchema = object({
6820
- brokerId: string(),
6821
- format: FrameHandleFormatSchema,
6822
- /**
6823
- * Optional reader-side cadence hint in frames per second. The broker does
6824
- * NOT throttle — latest-wins ring reads drop frames implicitly for a slow
6825
- * consumer. The value is echoed back in `SubscribeFramesResult.maxFps` so
6826
- * the consumer can pace its own `pullFrameHandles` polling.
6827
- */
6828
- maxFps: number().positive().optional(),
6829
- /** Short caller-identity tag (`motion`, `detection`, …) for diagnostics. */
6830
- tag: string().optional()
6831
- });
6832
- /**
6833
- * Result of `stream-broker.subscribeFrames`. The consumer then polls
6834
- * `pullFrameHandles({ subscriptionId, maxCount })` and feeds each returned
6835
- * `FrameHandle` to a `FrameRingReader`.
6836
- */
6837
- var SubscribeFramesResultSchema = object({
6838
- /** Opaque id the consumer passes to `pullFrameHandles` / `unsubscribeFrames`. */
6839
- subscriptionId: string(),
6840
- /** Reader-side cadence hint (frames/s) — echoes `SubscribeFramesInput.maxFps`. */
6841
- maxFps: number().nonnegative()
6842
- });
6843
- /**
6844
6771
  * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
6845
6772
  * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
6846
6773
  * sample bytes plus the track parameters a consumer needs to interpret them.
@@ -6848,8 +6775,8 @@ var SubscribeFramesResultSchema = object({
6848
6775
  * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
6849
6776
  * Float32 PCM), so unlike video frames they ship their bytes INLINE over
6850
6777
  * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
6851
- * (the wire-serialisable supertype of `Buffer`) to match `DecodedFrameSchema`
6852
- * / `EncodedPacketSchema`'s precedent; a `Buffer` is assignable to it.
6778
+ * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
6779
+ * it.
6853
6780
  */
6854
6781
  var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
6855
6782
  var DecodedAudioChunkSchema = object({
@@ -6867,11 +6794,10 @@ var DecodedAudioChunkSchema = object({
6867
6794
  format: AudioChunkFormatSchema.optional()
6868
6795
  });
6869
6796
  /**
6870
- * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). The
6871
- * handle-free, ship-bytes-inline analogue of `subscribeFrames`: a consumer
6872
- * asks the broker for the decoded audio-chunk stream of one broker; the
6873
- * broker keeps a per-subscription bounded FIFO queue the consumer drains via
6874
- * `pullAudioChunks`.
6797
+ * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
6798
+ * its bytes inline: a consumer asks the broker for the decoded audio-chunk
6799
+ * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
6800
+ * the consumer drains via `pullAudioChunks`.
6875
6801
  */
6876
6802
  var SubscribeAudioChunksInputSchema = object({
6877
6803
  brokerId: string(),
@@ -6902,9 +6828,7 @@ var BrokerStatsSchema = object({
6902
6828
  "stopped"
6903
6829
  ]),
6904
6830
  inputFps: number(),
6905
- decodeFps: number(),
6906
6831
  encodedSubscribers: number(),
6907
- decodedSubscribers: number(),
6908
6832
  uptimeMs: number(),
6909
6833
  bitrateKbps: number(),
6910
6834
  idrIntervalMs: number(),
@@ -6917,14 +6841,6 @@ var BrokerStatsSchema = object({
6917
6841
  preBufferMs: number(),
6918
6842
  preBufferPackets: number(),
6919
6843
  /**
6920
- * Moleculer node id of the decoder provider currently servicing this
6921
- * stream's decoded subscribers. `null` until the deferred decoder is
6922
- * created (no decoded clients yet, or codec not detected). Surfaces the
6923
- * runtime decoder placement so the UI can show "Decoder: <agent>" without
6924
- * a separate cap call per broker.
6925
- */
6926
- decoderNodeId: string().nullable(),
6927
- /**
6928
6844
  * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
6929
6845
  * when the stream has no audio track or the broker is in cold start.
6930
6846
  * `supported = false` means the codec was detected but the local
@@ -10447,70 +10363,6 @@ var StorageLocationDeclarationSchema = object({
10447
10363
  "backup"
10448
10364
  ]).optional()
10449
10365
  });
10450
- var DecoderStatsSchema = object({
10451
- inputFps: number(),
10452
- outputFps: number(),
10453
- avgDecodeTimeMs: number(),
10454
- droppedFrames: number(),
10455
- /**
10456
- * Pull-mode adaptive-fps telemetry (optional — only pull sessions run the
10457
- * lag-driven controller; push sessions omit these). `lagMs` is the EWMA of
10458
- * the decoder's real-time drift (rising = falling behind live); `adaptiveFps`
10459
- * is the current lag-throttled emit rate (≤ `effectiveFps` ceiling).
10460
- */
10461
- lagMs: number().optional(),
10462
- effectiveFps: number().optional(),
10463
- adaptiveFps: number().optional()
10464
- });
10465
- var DecoderSessionConfigSchema = object({
10466
- codec: string(),
10467
- maxFps: number().default(0),
10468
- outputFormat: _enum([
10469
- "jpeg",
10470
- "rgb",
10471
- "bgr",
10472
- "yuv420",
10473
- "gray"
10474
- ]).default("jpeg"),
10475
- scale: number().default(1),
10476
- width: number().optional(),
10477
- height: number().optional(),
10478
- /**
10479
- * Identifier of the camera this decoder session serves. Optional
10480
- * because the cap is generic (any caller could request decode), but
10481
- * stream-broker passes it so decoder logs include `deviceId` for
10482
- * per-camera filtering when diagnosing failures (e.g. node-av
10483
- * sendPacket errors on a single hung camera).
10484
- */
10485
- deviceId: number().int().nonnegative().optional(),
10486
- /**
10487
- * Free-form tag for log scoping. Stream-broker uses
10488
- * `broker:<deviceId>/<profile>`. Decoder session logger surfaces it
10489
- * on every line so `grep tag=broker:5/high` filters one camera
10490
- * profile cleanly.
10491
- */
10492
- tag: string().optional(),
10493
- /**
10494
- * Where the session delivers decoded frames (Phase 5 / D9):
10495
- *
10496
- * - `'callback'` (default) — the legacy pixel path: decoded frames are
10497
- * buffered as `DecodedFrame`s and drained via `pullFrames`.
10498
- * - `'shm'` — the shared-memory frame plane: decoded frames are written
10499
- * into an OS shared-memory ring and drained as zero-pixel
10500
- * `FrameHandle`s via `pullHandles`. A session is one mode or the
10501
- * other — `pullFrames` returns nothing for an `'shm'` session and
10502
- * `pullHandles` returns nothing for a `'callback'` session.
10503
- */
10504
- frameSink: _enum(["callback", "shm"]).default("callback"),
10505
- /**
10506
- * Per-camera decoder DEBUG facility. When `true`, a pull-mode session emits
10507
- * a throttled (~1Hz) structured `decoder debug` line (effective/adaptive fps,
10508
- * real-time lag, dropped-frame delta, avg decode time, hwaccel). Mirrors the
10509
- * stream-broker's `streamingDebug` gate — off by default so production logs
10510
- * stay quiet and the emit path pays zero per-frame cost when disabled.
10511
- */
10512
- debug: boolean().optional()
10513
- });
10514
10366
  /**
10515
10367
  * Per-component log CHANNELS — the gate a hot path consults, and the registry
10516
10368
  * an addon declares its channels in.
@@ -12771,13 +12623,6 @@ var BrokerRtspClientSchema = object({
12771
12623
  lastRtpAt: number(),
12772
12624
  bytesSent: number()
12773
12625
  });
12774
- var BrokerDecodedClientSchema = object({
12775
- tag: string(),
12776
- subscribedAt: number(),
12777
- maxFps: number(),
12778
- framesDelivered: number(),
12779
- framesDropped: number()
12780
- });
12781
12626
  var BrokerAudioClientSchema = object({
12782
12627
  tag: string(),
12783
12628
  subscribedAt: number(),
@@ -12869,7 +12714,6 @@ var BrokerEncodedClientSchema = object({
12869
12714
  });
12870
12715
  var BrokerClientsSchema = object({
12871
12716
  rtsp: array(BrokerRtspClientSchema).readonly(),
12872
- decoded: array(BrokerDecodedClientSchema).readonly(),
12873
12717
  audio: array(BrokerAudioClientSchema).readonly(),
12874
12718
  encoded: array(BrokerEncodedClientSchema).readonly(),
12875
12719
  pipeClients: number(),
@@ -13238,11 +13082,7 @@ method(object({
13238
13082
  auth: "admin"
13239
13083
  }), method(object({ brokerId: string() }), BrokerClientsSchema), method(object({
13240
13084
  brokerId: string(),
13241
- channel: _enum([
13242
- "rtsp",
13243
- "decoded",
13244
- "audio"
13245
- ]),
13085
+ channel: _enum(["rtsp", "audio"]),
13246
13086
  handle: string()
13247
13087
  }), object({ killed: boolean() }), {
13248
13088
  kind: "mutation",
@@ -13277,10 +13117,7 @@ method(object({
13277
13117
  }), method(SubscribeAudioChunksInputSchema, SubscribeAudioChunksResultSchema, { kind: "mutation" }), method(object({
13278
13118
  subscriptionId: string(),
13279
13119
  maxCount: number().int().positive().default(8)
13280
- }), array(DecodedAudioChunkSchema).readonly()), method(object({ subscriptionId: string() }), object({ released: boolean() }), { kind: "mutation" }), method(SubscribeFramesInputSchema, SubscribeFramesResultSchema, { kind: "mutation" }), method(object({
13281
- subscriptionId: string(),
13282
- maxCount: number().int().positive().default(4)
13283
- }), array(FrameHandleSchema).readonly()), method(object({ subscriptionId: string() }), object({ released: boolean() }), { kind: "mutation" }), method(object({
13120
+ }), array(DecodedAudioChunkSchema).readonly()), method(object({ subscriptionId: string() }), object({ released: boolean() }), { kind: "mutation" }), method(object({
13284
13121
  brokerId: string(),
13285
13122
  seconds: number().min(0).max(30)
13286
13123
  }), _void(), {
@@ -14437,54 +14274,12 @@ id: union([string(), number()]) }), {
14437
14274
  kind: "mutation",
14438
14275
  auth: "admin"
14439
14276
  });
14440
- /**
14441
- * shm ring usage stats for a `frameSink: 'shm'` decoder session —
14442
- * exposed via `decoder.getShmStats` so downstream consumers can
14443
- * observe ring pressure (slot count, byte budget, hit/miss ratio).
14444
- */
14445
- var ShmRingStatsSchema = object({
14446
- sessionId: string(),
14447
- slotCount: number().int(),
14448
- slotByteLength: number().int(),
14449
- segmentBytes: number().int(),
14450
- budgetMb: number().int(),
14451
- framesWritten: number().int(),
14452
- getFrameHits: number().int(),
14453
- getFrameMisses: number().int()
14454
- });
14455
- method(object({ codec: string() }), boolean()), method(_void(), object({
14277
+ method(_void(), object({
14456
14278
  id: string(),
14457
14279
  name: string(),
14458
- isPullMode: boolean().optional(),
14459
- priority: number().optional(),
14460
14280
  hwaccel: string().optional(),
14461
14281
  probedBestHwaccel: string().optional()
14462
- })), method(DecoderSessionConfigSchema, object({
14463
- sessionId: string(),
14464
- nodeId: string()
14465
- })), method(object({ sessionId: string() }), _void()), method(object({
14466
- sessionId: string(),
14467
- packet: EncodedPacketSchema
14468
- }), _void()), method(object({
14469
- sessionId: string(),
14470
- url: string()
14471
- }), _void()), method(object({
14472
- sessionId: string(),
14473
- maxCount: number().default(1),
14474
- waitMs: number().optional()
14475
- }), array(DecodedFrameSchema)), method(object({
14476
- sessionId: string(),
14477
- maxCount: number().default(1),
14478
- waitMs: number().optional()
14479
- }), array(FrameHandleSchema)), method(object({ handle: FrameHandleSchema }), DecodedFrameSchema.nullable()), method(object({ sessionId: string() }), ShmRingStatsSchema.nullable()), method(object({
14480
- sessionId: string(),
14481
- config: DecoderSessionConfigSchema.partial()
14482
- }), _void()), method(object({ sessionId: string() }), DecoderStatsSchema), method(_void(), array(object({
14483
- sessionId: string(),
14484
- codec: string(),
14485
- outputFormat: string(),
14486
- createdAtMs: number()
14487
- })).readonly()), method(_void(), object({ backend: string() }), {
14282
+ })), method(_void(), object({ backend: string() }), {
14488
14283
  kind: "mutation",
14489
14284
  auth: "admin"
14490
14285
  });
@@ -21545,8 +21340,8 @@ Object.freeze(Object.fromEntries([{
21545
21340
  var CLUSTER_STEP_SETTING_FIELDS = [{
21546
21341
  stepId: "face-embedding",
21547
21342
  key: "minLandmarkFaceSize",
21548
- label: "Min face size for recognition (detection px)",
21549
- description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
21343
+ label: "Min face size to embed (detection-frame px)",
21344
+ description: "Faces smaller than this in the small frame the detector sees are not embedded, so they can never be recognised. Below it the face cannot be aligned reliably and a bad embedding matches everyone. Same value on every node. 0 = no limit.",
21550
21345
  type: "slider",
21551
21346
  min: 0,
21552
21347
  max: 64,
@@ -23598,21 +23393,6 @@ var PipelineDefaultStepSchema = lazy(() => object({
23598
23393
  group: string().optional(),
23599
23394
  settings: record(string(), unknown()).optional()
23600
23395
  }));
23601
- var PipelineTemplateStepSchema = lazy(() => object({
23602
- addonId: string(),
23603
- enabled: boolean(),
23604
- modelId: string(),
23605
- children: array(PipelineTemplateStepSchema).readonly(),
23606
- settings: record(string(), unknown()).optional()
23607
- }));
23608
- var PipelineTemplateSchema$1 = object({
23609
- id: string(),
23610
- name: string(),
23611
- createdAt: string(),
23612
- updatedAt: string(),
23613
- engine: PipelineEngineChoiceSchema,
23614
- steps: array(PipelineTemplateStepSchema).readonly()
23615
- });
23616
23396
  var PipelineModelOptionSchema = object({
23617
23397
  id: string(),
23618
23398
  name: string(),
@@ -23824,15 +23604,7 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
23824
23604
  }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({
23825
23605
  steps: array(PipelineStepInputSchema),
23826
23606
  deviceKey: string().optional()
23827
- }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
23828
- name: string(),
23829
- steps: array(PipelineTemplateStepSchema).readonly(),
23830
- engine: PipelineEngineChoiceSchema
23831
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
23832
- id: string(),
23833
- name: string().optional(),
23834
- steps: array(PipelineTemplateStepSchema).readonly().optional()
23835
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
23607
+ }), PipelineValidationResultSchema), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
23836
23608
  addonId: string(),
23837
23609
  modelId: string(),
23838
23610
  format: ModelFormatSchema$2
@@ -25242,14 +25014,15 @@ var RunnerLocalLoadSchema = object({
25242
25014
  * Per-inference-device live load (multi-device C4). One entry per deviceKey
25243
25015
  * this runner currently has attached cameras on, so the orchestrator's second
25244
25016
  * `balance()` pass (over a node's devices) weights on real per-pool session
25245
- * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
25246
- * per device is 0 until the pool backlog gets a public accessor (follow-up).
25017
+ * counts. Empty on single-device / pre-multi-device runners. There is no
25018
+ * per-device queue depth: the inference pool's backlog has no accessor, and
25019
+ * a field that always read 0 was removed rather than shown as a measurement
25020
+ * (D762).
25247
25021
  */
25248
25022
  devices: array(object({
25249
25023
  deviceKey: string(),
25250
25024
  backend: string(),
25251
- attachedCameras: number(),
25252
- queueDepthTotal: number()
25025
+ attachedCameras: number()
25253
25026
  })).default([]),
25254
25027
  /** Hardware capability flags reported by this node. */
25255
25028
  hardware: object({
@@ -25463,14 +25236,6 @@ var CameraPipelineConfigSchema = object({
25463
25236
  settings: record(string(), unknown()).readonly().optional()
25464
25237
  }).nullable().optional()
25465
25238
  });
25466
- var PipelineTemplateSchema = object({
25467
- id: string(),
25468
- name: string(),
25469
- description: string().optional(),
25470
- config: CameraPipelineConfigSchema,
25471
- createdAt: string(),
25472
- updatedAt: string()
25473
- });
25474
25239
  var DeviceStepConfigSchema = object({
25475
25240
  modelId: string().optional(),
25476
25241
  settings: record(string(), unknown()).optional(),
@@ -25580,7 +25345,10 @@ var PipelineAssignmentSchema = object({
25580
25345
  });
25581
25346
  /**
25582
25347
  * Per-agent load summary surfaced to the load balancer + dashboards.
25583
- * Aggregated from each runner's `getLocalLoad` cap call.
25348
+ * Aggregated from each runner's `getLocalLoad` cap call. In the
25349
+ * `getAgentLoad` report, a single-device node's default-pool sessions (no
25350
+ * `deviceKey`) are credited to its one eligible device in `load.devices` —
25351
+ * the default pool IS that device (D745); the balancer's census stays raw.
25584
25352
  */
25585
25353
  var AgentLoadSummarySchema = object({
25586
25354
  nodeId: string(),
@@ -25669,26 +25437,27 @@ var CameraBrokerStatusSchema = object({
25669
25437
  width: number(),
25670
25438
  height: number(),
25671
25439
  subscribers: number(),
25672
- inFps: number(),
25673
- outFps: number()
25440
+ /**
25441
+ * Encoded packets/s the broker receives from the camera. null when the
25442
+ * broker's stats read failed — unknown, never 0 (a 0 here reads as a stall).
25443
+ * There is no output rate: decoding runs in the session decode workers, not
25444
+ * in the broker, so the broker's own decoded-frame rate is 0 on every live
25445
+ * camera and is not served (D758).
25446
+ */
25447
+ inFps: number().nullable()
25674
25448
  })).readonly(),
25675
25449
  webrtcSessions: number(),
25676
25450
  rtspRestream: boolean()
25677
25451
  });
25678
- /** Shared-memory ring statistics within the decoder block. */
25679
- var CameraDecoderShmSchema = object({
25680
- framesWritten: number(),
25681
- getFrameHits: number(),
25682
- getFrameMisses: number(),
25683
- budgetMb: number()
25684
- });
25685
- /** Decoder block — null when the decoder stage is unreachable or inactive. */
25686
- var CameraDecoderStatusSchema = object({
25687
- nodeId: string(),
25688
- formats: array(string()).readonly(),
25689
- sessionCount: number(),
25690
- shm: CameraDecoderShmSchema
25691
- });
25452
+ /**
25453
+ * Decoder block — WHERE decode runs (co-located with the detection node by
25454
+ * construction); null when the camera has no detection node. It carries no
25455
+ * frame counter: the shared-memory plane whose counters it used to serve is
25456
+ * gone, and those fields read 0 forever. Whether frames actually flow is read
25457
+ * from `detection.actualFps` (frames delivered to and processed by the
25458
+ * detector), never from a counter nothing feeds.
25459
+ */
25460
+ var CameraDecoderStatusSchema = object({ nodeId: string() });
25692
25461
  /** Motion block — null when motion detection is not active for this device. */
25693
25462
  var CameraMotionStatusSchema = object({
25694
25463
  enabled: boolean(),
@@ -26141,25 +25910,7 @@ method(object({
26141
25910
  }), method(_void(), DiskReconcileJobSchema, {
26142
25911
  kind: "mutation",
26143
25912
  auth: "admin"
26144
- }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
26145
- name: string(),
26146
- description: string().optional(),
26147
- config: CameraPipelineConfigSchema
26148
- }), PipelineTemplateSchema, {
26149
- kind: "mutation",
26150
- auth: "admin"
26151
- }), method(object({
26152
- id: string(),
26153
- name: string().optional(),
26154
- description: string().optional(),
26155
- config: CameraPipelineConfigSchema.optional()
26156
- }), PipelineTemplateSchema, {
26157
- kind: "mutation",
26158
- auth: "admin"
26159
- }), method(object({ id: string() }), object({ success: literal(true) }), {
26160
- kind: "mutation",
26161
- auth: "admin"
26162
- });
25913
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" });
26163
25914
  /**
26164
25915
  * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
26165
25916
  * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
@@ -28379,8 +28130,6 @@ var WebrtcStreamChoiceSchema = object({
28379
28130
  ]).nullable(),
28380
28131
  /** Input FPS from the camera (null for adaptive). */
28381
28132
  inputFps: number().nullable(),
28382
- /** Decoded FPS (null for adaptive). */
28383
- decodeFps: number().nullable(),
28384
28133
  /** Bitrate in kbps (null for adaptive). */
28385
28134
  bitrateKbps: number().nullable()
28386
28135
  });
@@ -38515,90 +38264,18 @@ Object.freeze({
38515
38264
  addonId: null,
38516
38265
  access: "create"
38517
38266
  },
38518
- "decoder.createSession": {
38519
- capName: "decoder",
38520
- capScope: "system",
38521
- addonId: null,
38522
- access: "view"
38523
- },
38524
- "decoder.destroySession": {
38525
- capName: "decoder",
38526
- capScope: "system",
38527
- addonId: null,
38528
- access: "delete"
38529
- },
38530
- "decoder.getFrame": {
38531
- capName: "decoder",
38532
- capScope: "system",
38533
- addonId: null,
38534
- access: "view"
38535
- },
38536
38267
  "decoder.getInfo": {
38537
38268
  capName: "decoder",
38538
38269
  capScope: "system",
38539
38270
  addonId: null,
38540
38271
  access: "view"
38541
38272
  },
38542
- "decoder.getShmStats": {
38543
- capName: "decoder",
38544
- capScope: "system",
38545
- addonId: null,
38546
- access: "view"
38547
- },
38548
- "decoder.getStats": {
38549
- capName: "decoder",
38550
- capScope: "system",
38551
- addonId: null,
38552
- access: "view"
38553
- },
38554
- "decoder.listActiveSessions": {
38555
- capName: "decoder",
38556
- capScope: "system",
38557
- addonId: null,
38558
- access: "view"
38559
- },
38560
- "decoder.openStream": {
38561
- capName: "decoder",
38562
- capScope: "system",
38563
- addonId: null,
38564
- access: "view"
38565
- },
38566
- "decoder.pullFrames": {
38567
- capName: "decoder",
38568
- capScope: "system",
38569
- addonId: null,
38570
- access: "view"
38571
- },
38572
- "decoder.pullHandles": {
38573
- capName: "decoder",
38574
- capScope: "system",
38575
- addonId: null,
38576
- access: "view"
38577
- },
38578
- "decoder.pushPacket": {
38579
- capName: "decoder",
38580
- capScope: "system",
38581
- addonId: null,
38582
- access: "view"
38583
- },
38584
38273
  "decoder.reprobeHwaccel": {
38585
38274
  capName: "decoder",
38586
38275
  capScope: "system",
38587
38276
  addonId: null,
38588
38277
  access: "create"
38589
38278
  },
38590
- "decoder.supportsCodec": {
38591
- capName: "decoder",
38592
- capScope: "system",
38593
- addonId: null,
38594
- access: "view"
38595
- },
38596
- "decoder.updateConfig": {
38597
- capName: "decoder",
38598
- capScope: "system",
38599
- addonId: null,
38600
- access: "view"
38601
- },
38602
38279
  "detectionPipeline.applyDeviceSettingsPatch": {
38603
38280
  capName: "detection-pipeline",
38604
38281
  capScope: "device",
@@ -41689,12 +41366,6 @@ Object.freeze({
41689
41366
  addonId: null,
41690
41367
  access: "delete"
41691
41368
  },
41692
- "pipelineExecutor.deleteTemplate": {
41693
- capName: "pipeline-executor",
41694
- capScope: "system",
41695
- addonId: null,
41696
- access: "delete"
41697
- },
41698
41369
  "pipelineExecutor.downloadModel": {
41699
41370
  capName: "pipeline-executor",
41700
41371
  capScope: "system",
@@ -41833,12 +41504,6 @@ Object.freeze({
41833
41504
  addonId: null,
41834
41505
  access: "view"
41835
41506
  },
41836
- "pipelineExecutor.listTemplates": {
41837
- capName: "pipeline-executor",
41838
- capScope: "system",
41839
- addonId: null,
41840
- access: "view"
41841
- },
41842
41507
  "pipelineExecutor.rearmInferenceDevice": {
41843
41508
  capName: "pipeline-executor",
41844
41509
  capScope: "system",
@@ -41863,12 +41528,6 @@ Object.freeze({
41863
41528
  addonId: null,
41864
41529
  access: "create"
41865
41530
  },
41866
- "pipelineExecutor.saveTemplate": {
41867
- capName: "pipeline-executor",
41868
- capScope: "system",
41869
- addonId: null,
41870
- access: "create"
41871
- },
41872
41531
  "pipelineExecutor.setVideoPipelineSteps": {
41873
41532
  capName: "pipeline-executor",
41874
41533
  capScope: "system",
@@ -41887,12 +41546,6 @@ Object.freeze({
41887
41546
  addonId: null,
41888
41547
  access: "create"
41889
41548
  },
41890
- "pipelineExecutor.updateTemplate": {
41891
- capName: "pipeline-executor",
41892
- capScope: "system",
41893
- addonId: null,
41894
- access: "create"
41895
- },
41896
41549
  "pipelineExecutor.validatePipeline": {
41897
41550
  capName: "pipeline-executor",
41898
41551
  capScope: "system",
@@ -41923,12 +41576,6 @@ Object.freeze({
41923
41576
  addonId: null,
41924
41577
  access: "create"
41925
41578
  },
41926
- "pipelineOrchestrator.deleteTemplate": {
41927
- capName: "pipeline-orchestrator",
41928
- capScope: "system",
41929
- addonId: null,
41930
- access: "delete"
41931
- },
41932
41579
  "pipelineOrchestrator.getAgentLoad": {
41933
41580
  capName: "pipeline-orchestrator",
41934
41581
  capScope: "system",
@@ -42079,12 +41726,6 @@ Object.freeze({
42079
41726
  addonId: null,
42080
41727
  access: "view"
42081
41728
  },
42082
- "pipelineOrchestrator.listTemplates": {
42083
- capName: "pipeline-orchestrator",
42084
- capScope: "system",
42085
- addonId: null,
42086
- access: "view"
42087
- },
42088
41729
  "pipelineOrchestrator.pauseForStorageMigration": {
42089
41730
  capName: "pipeline-orchestrator",
42090
41731
  capScope: "system",
@@ -42139,12 +41780,6 @@ Object.freeze({
42139
41780
  addonId: null,
42140
41781
  access: "create"
42141
41782
  },
42142
- "pipelineOrchestrator.saveTemplate": {
42143
- capName: "pipeline-orchestrator",
42144
- capScope: "system",
42145
- addonId: null,
42146
- access: "create"
42147
- },
42148
41783
  "pipelineOrchestrator.setAgentCapabilities": {
42149
41784
  capName: "pipeline-orchestrator",
42150
41785
  capScope: "system",
@@ -42253,12 +41888,6 @@ Object.freeze({
42253
41888
  addonId: null,
42254
41889
  access: "create"
42255
41890
  },
42256
- "pipelineOrchestrator.updateTemplate": {
42257
- capName: "pipeline-orchestrator",
42258
- capScope: "system",
42259
- addonId: null,
42260
- access: "create"
42261
- },
42262
41891
  "pipelineRunner.attachCamera": {
42263
41892
  capName: "pipeline-runner",
42264
41893
  capScope: "system",
@@ -43693,12 +43322,6 @@ Object.freeze({
43693
43322
  addonId: null,
43694
43323
  access: "view"
43695
43324
  },
43696
- "streamBroker.pullFrameHandles": {
43697
- capName: "stream-broker",
43698
- capScope: "system",
43699
- addonId: null,
43700
- access: "view"
43701
- },
43702
43325
  "streamBroker.regenerateRtspToken": {
43703
43326
  capName: "stream-broker",
43704
43327
  capScope: "system",
@@ -43765,12 +43388,6 @@ Object.freeze({
43765
43388
  addonId: null,
43766
43389
  access: "create"
43767
43390
  },
43768
- "streamBroker.subscribeFrames": {
43769
- capName: "stream-broker",
43770
- capScope: "system",
43771
- addonId: null,
43772
- access: "create"
43773
- },
43774
43391
  "streamBroker.unassignProfile": {
43775
43392
  capName: "stream-broker",
43776
43393
  capScope: "system",
@@ -43783,12 +43400,6 @@ Object.freeze({
43783
43400
  addonId: null,
43784
43401
  access: "create"
43785
43402
  },
43786
- "streamBroker.unsubscribeFrames": {
43787
- capName: "stream-broker",
43788
- capScope: "system",
43789
- addonId: null,
43790
- access: "create"
43791
- },
43792
43403
  "streamCatalog.getCatalog": {
43793
43404
  capName: "stream-catalog",
43794
43405
  capScope: "device",
@@ -46302,11 +45913,6 @@ Object.freeze({
46302
45913
  form: "single",
46303
45914
  optional: false
46304
45915
  }],
46305
- "decoder.createSession": [{
46306
- name: "deviceId",
46307
- form: "single",
46308
- optional: true
46309
- }],
46310
45916
  "detectionPipeline.applyDeviceSettingsPatch": [{
46311
45917
  name: "deviceId",
46312
45918
  form: "single",
@@ -50447,24 +50053,6 @@ var CAMERA_STREAM_CONTROLLER = {
50447
50053
  audioCodecs: ["AAC"]
50448
50054
  }]
50449
50055
  };
50450
- /**
50451
- * `Alexa.EndpointHealth` — REQUIRED on a sensor endpoint (alexa-endpointhealth;
50452
- * alexa-motionsensor: "Must also implement `Alexa.EndpointHealth`"), and every
50453
- * camera endpoint may carry `Alexa.MotionSensor`. Declared on every camera so
50454
- * Alexa can tell an offline camera from a broken one: `connectivity` comes
50455
- * from the `device-status` cap in ReportState and is pushed as a ChangeReport
50456
- * on `DeviceOnline` / `DeviceOffline` by the Event Gateway.
50457
- */
50458
- var ENDPOINT_HEALTH = {
50459
- type: "AlexaInterface",
50460
- interface: "Alexa.EndpointHealth",
50461
- version: "3",
50462
- properties: {
50463
- supported: [{ name: "connectivity" }],
50464
- proactivelyReported: true,
50465
- retrievable: true
50466
- }
50467
- };
50468
50056
  var DOORBELL_EVENT_SOURCE = {
50469
50057
  type: "AlexaInterface",
50470
50058
  interface: "Alexa.DoorbellEventSource",
@@ -50627,6 +50215,27 @@ var PTZ_TILT_MODE_CONTROLLER = ptzDirectionalModeController("ptz.tilt", "Tilt",
50627
50215
  label: "Down"
50628
50216
  }
50629
50217
  ]);
50218
+ /** Amazon (alexa-modecontroller): "You must include a minimum of two mode
50219
+ * objects." One preset made the camera's whole AddOrUpdateReport fail
50220
+ * schema validation, and Discover dropped the camera. */
50221
+ var MIN_MODE_CONTROLLER_MODES = 2;
50222
+ /**
50223
+ * Preset names as distinct mode values. Two presets with the same name (the
50224
+ * camera allows it: `Stanza`, `Stanza`; `CamStack Home` ×3) produced two
50225
+ * identical modes, and Alexa rejected the capability — "Invalid capability
50226
+ * received." — and with it every endpoint of the camera. First wins,
50227
+ * case-insensitively: the SetMode handler resolves a value to the first
50228
+ * preset of that name, which is the one kept.
50229
+ */
50230
+ function distinctPresetNames(names) {
50231
+ const seen = /* @__PURE__ */ new Set();
50232
+ return names.filter((name) => {
50233
+ const key = name.toLowerCase();
50234
+ if (seen.has(key)) return false;
50235
+ seen.add(key);
50236
+ return true;
50237
+ });
50238
+ }
50630
50239
  function ptzPresetModeController(presetNames) {
50631
50240
  return {
50632
50241
  type: "AlexaInterface",
@@ -50666,13 +50275,13 @@ function buildParentCameraEndpoint(input) {
50666
50275
  const capabilities = [
50667
50276
  ALEXA_BASE_CAPABILITY,
50668
50277
  CAMERA_STREAM_CONTROLLER,
50669
- rtcSessionController(input.hasIntercom),
50670
- ENDPOINT_HEALTH
50278
+ rtcSessionController(input.hasIntercom)
50671
50279
  ];
50672
50280
  if (input.isDoorbell) capabilities.push(DOORBELL_EVENT_SOURCE);
50673
50281
  if (input.hasMotion) capabilities.push(MOTION_SENSOR);
50674
50282
  if (input.hasBattery) capabilities.push(BATTERY_LEVEL_SENSOR);
50675
- if (input.ptzPresetNames.length > 0) capabilities.push(ptzPresetModeController(input.ptzPresetNames));
50283
+ const presetNames = distinctPresetNames(input.ptzPresetNames);
50284
+ if (presetNames.length >= MIN_MODE_CONTROLLER_MODES) capabilities.push(ptzPresetModeController(presetNames));
50676
50285
  if (input.hasPtz) {
50677
50286
  capabilities.push(PTZ_PAN_MODE_CONTROLLER);
50678
50287
  capabilities.push(PTZ_TILT_MODE_CONTROLLER);
@@ -50811,6 +50420,12 @@ function buildAlarmPanelEndpoint(input) {
50811
50420
  };
50812
50421
  }
50813
50422
  //#endregion
50423
+ //#region src/handlers/ptz-preset-recallable.ts
50424
+ var RECALLABLE_FUNCTIONS = new Set(["home"]);
50425
+ function isAlexaRecallablePreset(preset) {
50426
+ return preset.fn === null || RECALLABLE_FUNCTIONS.has(preset.fn);
50427
+ }
50428
+ //#endregion
50814
50429
  //#region src/handlers/discovery.ts
50815
50430
  var discoveryPayloadSchema = DiscoveryDirectiveSchema.shape.payload;
50816
50431
  /** Devices built at once in `handleDiscovery` — latency-bound tRPC reads, so
@@ -50998,7 +50613,7 @@ async function loadBoundDeviceInfo(device, api) {
50998
50613
  };
50999
50614
  }
51000
50615
  async function fetchPtzPresetNames(deviceId, api) {
51001
- return (await api.ptz.getPresets.query({ deviceId })).map((p) => p.name).filter((n) => n.length > 0);
50616
+ return (await api.ptz.getPresets.query({ deviceId })).filter(isAlexaRecallablePreset).map((p) => p.name).filter((n) => n.length > 0);
51002
50617
  }
51003
50618
  function classifyChildKind(deviceType, role) {
51004
50619
  if (role === "floodlight" || role === "spotlight" || role === "nightvision") return "light";
@@ -51398,8 +51013,8 @@ async function handleSetMode(header, endpointRaw, payloadRaw, deps, _callerIdent
51398
51013
  if (!mode.startsWith(PTZ_PRESET_MODE_PREFIX)) return errorEnvelope(header, "INVALID_DIRECTIVE", `mode '${mode}' is not a ptz.preset mode`);
51399
51014
  const presetName = mode.slice(11);
51400
51015
  if (presetName.length === 0) return errorEnvelope(header, "INVALID_DIRECTIVE", "preset name missing after prefix");
51401
- const preset = (await api.ptz.getPresets.query({ deviceId: parsedId.deviceId })).find((p) => p.name === presetName);
51402
- if (!preset) return errorEnvelope(header, "INVALID_DIRECTIVE", `preset '${presetName}' not found on device ${parsedId.deviceId}`);
51016
+ const preset = (await api.ptz.getPresets.query({ deviceId: parsedId.deviceId })).find((p) => p.name === presetName && isAlexaRecallablePreset(p));
51017
+ if (!preset) return errorEnvelope(header, "INVALID_DIRECTIVE", `preset '${presetName}' is not a recallable preset on device ${parsedId.deviceId}`);
51403
51018
  await api.ptz.goToPreset.mutate({
51404
51019
  deviceId: parsedId.deviceId,
51405
51020
  presetId: preset.id
@@ -51491,20 +51106,6 @@ async function collectCameraProperties(api, deviceId, properties, timeOfSample,
51491
51106
  timeOfSample,
51492
51107
  uncertaintyInMilliseconds: 0
51493
51108
  });
51494
- const online = boolField(await api.deviceStatus.getStatus.query({ deviceId }).catch((err) => {
51495
- logger.warn("alexa report-state: deviceStatus.getStatus failed — connectivity omitted", {
51496
- tags: { deviceId },
51497
- meta: { error: err instanceof Error ? err.message : String(err) }
51498
- });
51499
- return null;
51500
- }), "online");
51501
- if (online !== null) properties.push({
51502
- namespace: "Alexa.EndpointHealth",
51503
- name: "connectivity",
51504
- value: { value: online ? "OK" : "UNREACHABLE" },
51505
- timeOfSample,
51506
- uncertaintyInMilliseconds: 0
51507
- });
51508
51109
  const detected = boolField(await api.motion.getStatus.query({ deviceId }).catch((err) => {
51509
51110
  logger.info("alexa report-state: motion.getStatus skipped", { meta: {
51510
51111
  deviceId,
@@ -53461,7 +53062,7 @@ function buildAlexaOauthIntegration(publicHubUrl) {
53461
53062
  * client id and SECRET, on the theory that a per-account refresh token was the
53462
53063
  * real gate. The package is public on npm, so that secret was published with
53463
53064
  * every version and has to be rotated. Since D753 the secret lives only in the
53464
- * skill's Lambda (AWS Secrets Manager); the hub gets short-lived Event Gateway
53065
+ * skill's Lambda (SSM Parameter Store SecureString, D761); the hub gets short-lived Event Gateway
53465
53066
  * tokens from the Lambda's broker (`broker-client.ts`) with a per-link
53466
53067
  * credential. `scripts/check-no-client-secrets.ts` keeps it that way.
53467
53068
  *
@@ -54492,7 +54093,7 @@ var ExportAlexaAddon = class extends BaseAddon {
54492
54093
  content: [
54493
54094
  "Public skill (default): enable the \"CamStack\" skill in the Alexa app and link your account — no Amazon developer setup is needed. A single published skill + hosted Lambda route to this hub using its public HTTPS URL (baked into every JWT).",
54494
54095
  "The skill's Lambda keeps the Amazon credentials: this hub never holds an LWA client secret. It receives short-lived event tokens from the Lambda's token broker with a credential that is unique to this link.",
54495
- "Custom skill: switch \"Alexa skill\" below to Custom, enter your skill id, and deploy the skill Lambda from this addon's lambda-template in your own AWS account (your LWA credentials go into AWS Secrets Manager there, not here).",
54096
+ "Custom skill: switch \"Alexa skill\" below to Custom, enter your skill id, and deploy the skill Lambda from this addon's lambda-template in your own AWS account (your LWA credentials go into SSM Parameter Store there, not here).",
54496
54097
  "Either way this hub must be reachable from the public internet (Cloudflare Tunnel, Tailscale Funnel, …). The \"External address\" picker selects which detected endpoint the Lambda calls back on — press \"Refresh external addresses\" after setting up a new tunnel."
54497
54098
  ].join("\n")
54498
54099
  },
@@ -54559,7 +54160,7 @@ var ExportAlexaAddon = class extends BaseAddon {
54559
54160
  key: "__custom-skill-info",
54560
54161
  label: "Your skill Lambda",
54561
54162
  variant: "info",
54562
- content: "Proactive events (doorbell, motion, battery, device-list updates) are authorised by YOUR skill Lambda: it keeps the \"Send Alexa Events\" LWA client id/secret in AWS Secrets Manager and hands this hub short-lived tokens. Nothing to paste here — see the custom-skill setup guide."
54163
+ content: "Proactive events (doorbell, motion, battery, device-list updates) are authorised by YOUR skill Lambda: it keeps the \"Send Alexa Events\" LWA client id/secret in SSM Parameter Store and hands this hub short-lived tokens. Nothing to paste here — see the custom-skill setup guide."
54563
54164
  }] : [{
54564
54165
  type: "info",
54565
54166
  key: "__public-skill-info",