@camstack/addon-decoder-nodeav 1.1.8 → 1.1.10

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.
Files changed (3) hide show
  1. package/dist/index.js +687 -532
  2. package/dist/index.mjs +673 -528
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,276 +5,6 @@ Object.defineProperties(exports, {
5
5
  let _camstack_shm_ring = require("@camstack/shm-ring");
6
6
  let node_crypto = require("node:crypto");
7
7
  let node_fs = require("node:fs");
8
- //#region src/frame-ring-sink.ts
9
- /**
10
- * `DecoderFrameRingSink` — the decoder's shared-memory write side (Phase 5 / D9).
11
- *
12
- * When a decoder session is configured with `frameSink: 'shm'`, the decoder
13
- * **owns** the shared-memory ring segment for that stream: it creates the
14
- * segment on the first decoded frame (when the output geometry is known),
15
- * writes every subsequent decoded frame into the ring via a `FrameRingWriter`,
16
- * and closes + unlinks the segment when the session is destroyed.
17
- *
18
- * What leaves the decoder is no longer the pixel `Buffer` — it is a tiny,
19
- * serialisable `FrameHandle` (`FrameRingWriter.writeFrame`'s return value).
20
- * Same-host consumers (motion, detection, the WebRTC encoder) open the same
21
- * segment with a `FrameRingReader` and read the pixels zero-copy.
22
- *
23
- * ## Lazy segment creation
24
- *
25
- * The segment cannot be sized until the first frame: `slotByteLength` is
26
- * `width × height × bytesPerPixel`, and the output dimensions are only known
27
- * once the scaler has produced its first `dstFrame`. So `writeFrame` is a
28
- * no-op-until-armed: the first call sizes + creates the segment, every later
29
- * call writes into it.
30
- *
31
- * ## Resolution-change decision
32
- *
33
- * A live camera stream can change resolution mid-stream (the decoder's scaler
34
- * is rebuilt on a config toggle, or the source renegotiates). The slot is
35
- * sized for the **first** frame's geometry. A later frame that no longer fits
36
- * the slot triggers a **segment re-create**: the old segment is closed +
37
- * unlinked and a fresh, larger segment is created under a new generation-tagged
38
- * name. This is simpler and leak-free versus over-allocating slots for a
39
- * worst-case 4K frame on every stream; resolution changes on a live camera are
40
- * rare, and a brief gap while consumers re-open the segment is acceptable
41
- * (latest-wins — a missed frame is correct behaviour).
42
- */
43
- /**
44
- * Per-ring shared-memory budget (MB) — the slot count is derived per-resolution
45
- * from this budget via {@link deriveSlotCount}, so a 360p stream gets many
46
- * slots and a 4K stream a few, both inside the same memory footprint.
47
- *
48
- * Read ONCE at module load from `CAMSTACK_SHM_RING_BUDGET_MB`; a non-finite or
49
- * non-positive value falls back to the 16 MB default.
50
- *
51
- * The default is deliberately small (16 MB) so many concurrent per-camera rings
52
- * fit inside a bounded `/dev/shm`. The decoder output is capped at 640px wide, so
53
- * a slot is ≤ ~920 KB and 16 MB still yields ~17–70 latest-wins slots. A ring
54
- * segment larger than the container's `/dev/shm` tmpfs backing (Docker default is
55
- * only 64 MB) faults an **uncatchable SIGBUS** on write past `st_size` — the old
56
- * 128 MB default overflowed a 64 MB `/dev/shm` and crashed the decoder on 8MP
57
- * streams. Pair this with a `--shm-size` that scales with concurrent-decode load.
58
- */
59
- var RING_BUDGET_MB = (() => {
60
- const raw = Number(process.env["CAMSTACK_SHM_RING_BUDGET_MB"]);
61
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 16;
62
- })();
63
- /** {@link RING_BUDGET_MB} in bytes — the budget passed to `deriveSlotCount`. */
64
- var RING_BUDGET_BYTES = RING_BUDGET_MB * 1024 * 1024;
65
- /** A unique, stable shared-memory segment name for a decoder stream.
66
- *
67
- * macOS POSIX shm names are capped at ~31 characters (`PSHMNAMLEN`). A
68
- * `camstack.frames.<deviceId>.<streamId>` scheme overflows that for realistic
69
- * ids, so the sink uses a short, collision-resistant scheme instead:
70
- * `csf.<base36 hash>.<gen>`. The hash folds the device id, the session tag
71
- * and a per-process random salt; the generation suffix makes a re-created
72
- * segment (resolution change) a distinct name so a stale consumer mapping is
73
- * never silently reused.
74
- */
75
- /**
76
- * Shared prefix for every decoder shm segment name. Startup orphan reclamation
77
- * (`purgeOrphanSegments`) keys off this to find segments left behind by a
78
- * crashed prior instance.
79
- */
80
- var SEGMENT_NAME_PREFIX = "csf.";
81
- function makeSegmentName(seed, generation) {
82
- let hash = 5381;
83
- for (let i = 0; i < seed.length; i += 1) hash = (hash << 5) + hash + seed.charCodeAt(i) | 0;
84
- return `${SEGMENT_NAME_PREFIX}${(hash >>> 0).toString(36)}.${generation}`;
85
- }
86
- /**
87
- * The decoder-side owner of one stream's shared-memory frame ring.
88
- *
89
- * Not constructed until a session actually uses the shm sink; the segment
90
- * itself is created lazily on the first `writeFrame`.
91
- */
92
- var DecoderFrameRingSink = class {
93
- seed;
94
- logger;
95
- nodeId;
96
- segment = null;
97
- writer = null;
98
- segmentName = null;
99
- slotByteLength = 0;
100
- generation = 0;
101
- destroyed = false;
102
- /** Frames committed into the ring across this sink's lifetime (all generations). */
103
- framesWritten = 0;
104
- constructor(options) {
105
- const salt = Math.random().toString(36).slice(2, 8);
106
- this.seed = `${options.seed}.${salt}`;
107
- this.logger = options.logger;
108
- this.nodeId = options.nodeId;
109
- }
110
- /** Whether a segment has been created (i.e. at least one frame written). */
111
- get isArmed() {
112
- return this.writer !== null;
113
- }
114
- /** The current segment name, or `null` before the first frame. */
115
- get currentSegmentName() {
116
- return this.segmentName;
117
- }
118
- /**
119
- * Write one decoded frame into the ring and return its `FrameHandle`.
120
- *
121
- * On the first call (or after a geometry change that overflows the current
122
- * slot) the segment is created / re-created sized for this frame. Returns
123
- * `null` only when the sink has been destroyed.
124
- *
125
- * This is the copy-in convenience form (it copies `pixels` into the slot).
126
- * The decoder's hot path uses the zero-copy {@link beginFrame} /
127
- * {@link commitFrame} scatter-write pair instead — the scaler produces its
128
- * packed output directly into the slot, eliminating the write-side memcpy.
129
- */
130
- writeFrame(pixels, meta) {
131
- if (this.destroyed) return null;
132
- if (this.writer === null || (0, _camstack_shm_ring.computeSlotByteLength)(meta.width, meta.height, meta.format) > this.slotByteLength) this.recreateSegment((0, _camstack_shm_ring.computeSlotByteLength)(meta.width, meta.height, meta.format));
133
- const writer = this.writer;
134
- if (writer === null) return null;
135
- const handle = writer.writeFrame(pixels, meta);
136
- this.framesWritten += 1;
137
- return handle;
138
- }
139
- /**
140
- * Reserve a ring slot for a frame of the given geometry — the **zero-copy**
141
- * scatter-write entry point (Phase 5 / D9 Task 7c).
142
- *
143
- * The segment is created / re-created here if this is the first frame or the
144
- * geometry overflows the current slot capacity, so the slot is correctly
145
- * sized before the caller fills it. The returned `buffer` is a writable view
146
- * **directly over the mapped segment** — the node-av scaler scatters its
147
- * packed output straight into it, with no intermediate copy. The caller MUST
148
- * call {@link commitFrame} with the returned `slot` once the slot is filled.
149
- *
150
- * Returns `null` when the sink is destroyed or the segment cannot be created.
151
- */
152
- beginFrame(width, height, format) {
153
- if (this.destroyed) return null;
154
- const requiredSlotBytes = (0, _camstack_shm_ring.computeSlotByteLength)(width, height, format);
155
- if (this.writer === null || requiredSlotBytes > this.slotByteLength) this.recreateSegment(requiredSlotBytes);
156
- const writer = this.writer;
157
- if (writer === null) return null;
158
- const { slot, buffer } = writer.beginFrame();
159
- return {
160
- slot,
161
- buffer
162
- };
163
- }
164
- /**
165
- * Publish the frame whose slot was reserved by {@link beginFrame} and filled
166
- * in place by the caller. `slot` MUST be the value from the matching
167
- * `beginFrame`. Returns the published `FrameHandle`, or `null` if the sink
168
- * was destroyed (or the segment lost) between begin and commit.
169
- */
170
- commitFrame(slot, meta) {
171
- if (this.destroyed) return null;
172
- const writer = this.writer;
173
- if (writer === null) return null;
174
- const handle = writer.commitFrame(slot, meta);
175
- this.framesWritten += 1;
176
- return handle;
177
- }
178
- /**
179
- * Current shm ring usage — `null` until the first frame arms the segment.
180
- * Surfaced through `decoder.getShmStats` so a downstream consumer can
181
- * observe ring pressure (slot depth, byte budget, frames written).
182
- */
183
- getShmStats() {
184
- if (this.writer === null) return null;
185
- return {
186
- slotCount: this.writer.slotCount,
187
- slotByteLength: this.slotByteLength,
188
- segmentBytes: (0, _camstack_shm_ring.computeSegmentSize)(this.writer.slotCount, this.slotByteLength),
189
- framesWritten: this.framesWritten
190
- };
191
- }
192
- /**
193
- * Abandon a slot reserved by {@link beginFrame} **without publishing it** —
194
- * the degenerate-path counterpart of {@link commitFrame}.
195
- *
196
- * A caller that reserved a slot but then could not produce valid pixels (no
197
- * decoded source planes, or the scaler threw) MUST call this instead of
198
- * `commitFrame`: it closes the open seqlock without advancing `writeIndex`,
199
- * so no reader ever sees the slot's uninitialised bytes as a real frame, and
200
- * no `FrameHandle` is handed downstream. `slot` MUST be the value from the
201
- * matching `beginFrame`. A no-op if the sink was destroyed (or the segment
202
- * lost) between begin and abort.
203
- */
204
- abortFrame(slot) {
205
- if (this.destroyed) return;
206
- const writer = this.writer;
207
- if (writer === null) return;
208
- writer.abortFrame(slot);
209
- }
210
- /** Close + unlink the segment. Idempotent. */
211
- destroy() {
212
- if (this.destroyed) return;
213
- this.destroyed = true;
214
- this.releaseSegment();
215
- }
216
- /**
217
- * Create a fresh segment sized for at least `slotByteLength` bytes per slot,
218
- * replacing any prior one. A re-create bumps the generation so the new
219
- * segment has a distinct name — a consumer holding the old mapping is never
220
- * silently handed a resized segment.
221
- */
222
- recreateSegment(slotByteLength) {
223
- this.releaseSegment();
224
- this.generation += 1;
225
- const name = makeSegmentName(this.seed, this.generation);
226
- const slotCount = (0, _camstack_shm_ring.deriveSlotCount)(RING_BUDGET_BYTES, slotByteLength);
227
- if (slotCount === _camstack_shm_ring.MIN_RING_SLOTS && _camstack_shm_ring.MIN_RING_SLOTS * slotByteLength > RING_BUDGET_BYTES) this.logger.warn("decoder shm ring: budget too small for resolution — using MIN slots", { meta: {
228
- slotByteLength,
229
- budgetMb: RING_BUDGET_MB
230
- } });
231
- const totalBytes = (0, _camstack_shm_ring.computeSegmentSize)(slotCount, slotByteLength);
232
- try {
233
- const segment = (0, _camstack_shm_ring.createSegment)(name, totalBytes);
234
- this.segment = segment;
235
- this.segmentName = name;
236
- this.slotByteLength = slotByteLength;
237
- this.writer = new _camstack_shm_ring.FrameRingWriter(segment.buffer, name, slotCount, slotByteLength, this.nodeId);
238
- this.logger.info("decoder shm ring: segment created", { meta: {
239
- segment: name,
240
- slotCount,
241
- slotByteLength,
242
- totalBytes,
243
- generation: this.generation
244
- } });
245
- } catch (err) {
246
- this.segment = null;
247
- this.writer = null;
248
- this.segmentName = null;
249
- this.slotByteLength = 0;
250
- this.logger.error("decoder shm ring: segment create failed", { meta: {
251
- segment: name,
252
- slotByteLength,
253
- error: err instanceof Error ? err.message : String(err)
254
- } });
255
- }
256
- }
257
- /** Unmap + unlink the current segment, if any. */
258
- releaseSegment() {
259
- const segment = this.segment;
260
- if (segment === null) return;
261
- this.segment = null;
262
- this.writer = null;
263
- const name = this.segmentName;
264
- this.segmentName = null;
265
- try {
266
- segment.close();
267
- segment.unlink();
268
- this.logger.info("decoder shm ring: segment released", { meta: { segment: name } });
269
- } catch (err) {
270
- this.logger.warn("decoder shm ring: segment release failed", { meta: {
271
- segment: name,
272
- error: err instanceof Error ? err.message : String(err)
273
- } });
274
- }
275
- }
276
- };
277
- //#endregion
278
8
  //#region ../../node_modules/zod/v4/core/core.js
279
9
  var _a$1;
280
10
  function $constructor(name, initializer, params) {
@@ -4904,7 +4634,7 @@ function _instanceof(cls, params = {}) {
4904
4634
  return inst;
4905
4635
  }
4906
4636
  //#endregion
4907
- //#region ../types/dist/sleep-CZDdRBua.mjs
4637
+ //#region ../types/dist/sleep-BC9Yqte7.mjs
4908
4638
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4909
4639
  EventCategory["SystemBoot"] = "system.boot";
4910
4640
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5090,6 +4820,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5090
4820
  */
5091
4821
  EventCategory["PipelineCameraUpdated"] = "pipeline.camera-updated";
5092
4822
  /**
4823
+ * The cluster camera-source OWNER changed (`clusterRoles.ingestNode`).
4824
+ * Emitted by addon-pipeline-orchestrator whenever it (re)derives node
4825
+ * capabilities — at boot, on agent online/offline, and on an ingest-node
4826
+ * flip. Carries the resolved `ownerNodeId`. The stream-broker consumes it to
4827
+ * keep its ingest-owner-gate decision current WITHOUT a per-`ensureBroker`
4828
+ * cross-process `getIngestOwner` query (push the authority's decision instead
4829
+ * of polling it on the hot path). Idempotent state — re-emitted on every
4830
+ * topology change, so a dropped event self-heals on the next one (plus the
4831
+ * broker's long backstop reconcile query).
4832
+ */
4833
+ EventCategory["PipelineIngestOwnerChanged"] = "pipeline.ingest-owner-changed";
4834
+ /**
5093
4835
  * Periodic snapshot of per-node pipeline-runner load
5094
4836
  * (`RunnerLocalLoad`). Emitted ~1Hz by every runner so UI dashboards
5095
4837
  * subscribe instead of polling `pipelineRunner.getLocalLoad`.
@@ -5613,10 +5355,6 @@ function hydrateField(field, values) {
5613
5355
  };
5614
5356
  }
5615
5357
  const rawValue = storedValue !== void 0 ? storedValue : defaultValue !== void 0 ? defaultValue : null;
5616
- if (field.type === "password") return {
5617
- ...field,
5618
- value: ""
5619
- };
5620
5358
  const value = field.type === "textarea" && field.isJson && rawValue !== null && typeof rawValue === "object" ? JSON.stringify(rawValue, null, 2) : rawValue;
5621
5359
  return {
5622
5360
  ...field,
@@ -7000,6 +6738,21 @@ function method(input, output, options) {
7000
6738
  timeoutMs: options?.timeoutMs
7001
6739
  };
7002
6740
  }
6741
+ /**
6742
+ * A wrapper/system-only method: served exclusively by the cap's system-level
6743
+ * provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
6744
+ * driver natives don't stub out a wrapper concern (e.g. a cross-device cache
6745
+ * overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
6746
+ */
6747
+ function systemMethod(input, output, options) {
6748
+ return {
6749
+ ...method(input, output, options),
6750
+ systemOnly: true
6751
+ };
6752
+ }
6753
+ var StaticDirOutputSchema$1 = object({ staticDir: string() });
6754
+ var VersionOutputSchema$1 = object({ version: string() });
6755
+ method(_void(), StaticDirOutputSchema$1), method(_void(), VersionOutputSchema$1);
7003
6756
  var StaticDirOutputSchema = object({ staticDir: string() });
7004
6757
  var VersionOutputSchema = object({ version: string() });
7005
6758
  method(_void(), StaticDirOutputSchema), method(_void(), VersionOutputSchema);
@@ -7181,6 +6934,36 @@ var ModelFormatsSchema = object({
7181
6934
  tflite: ModelFormatEntrySchema.optional(),
7182
6935
  pt: ModelFormatEntrySchema.optional()
7183
6936
  });
6937
+ /**
6938
+ * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
6939
+ * the reduced `PipelineModelOption` returned in `pipeline.getSchema()` so the
6940
+ * grouped Family→Tier→Variant picker renders identically in the config UI and
6941
+ * in the pipeline/device steppers. The flat `id` stays the source of truth for
6942
+ * resolution/download/persistence; this is a presentation overlay resolved back
6943
+ * to an `id`.
6944
+ */
6945
+ var ModelVariantGroupSchema = object({
6946
+ /** Top-level family, e.g. `yolo26` (later `d-fine`, `rf-detr`). */
6947
+ family: string(),
6948
+ /** Size within the family, e.g. `n` | `s` | `m` | `l`. */
6949
+ tier: string(),
6950
+ /** Quantization axis. Omit ⇒ the fp32 base build. */
6951
+ precision: _enum(["fp32", "int8"]).optional(),
6952
+ /**
6953
+ * Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
6954
+ * latency-optimized export (e.g. ReLU-activation variant) — the slot the
6955
+ * future performance variants plug into.
6956
+ */
6957
+ optimization: _enum(["standard", "fast"]).optional(),
6958
+ /**
6959
+ * Input-resolution axis (square input side, px). Omit ⇒ the family's native
6960
+ * resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
6961
+ * cheap latency lever — especially on Apple ANE and the Intel N100 — at a
6962
+ * small-object accuracy cost. Mirrors the model's `inputSize` but lifted onto
6963
+ * the group so the selector can offer it as a variant axis.
6964
+ */
6965
+ resolution: number().int().positive().optional()
6966
+ });
7184
6967
  var ModelCatalogEntrySchema = object({
7185
6968
  id: string(),
7186
6969
  name: string(),
@@ -7210,7 +6993,43 @@ var ModelCatalogEntrySchema = object({
7210
6993
  * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
7211
6994
  * Downloaded into the same modelsDir alongside the model file.
7212
6995
  */
7213
- extraFiles: array(ModelExtraFileSchema).readonly().optional()
6996
+ extraFiles: array(ModelExtraFileSchema).readonly().optional(),
6997
+ /**
6998
+ * LEGACY entry — retained in the catalog so a persisted operator selection
6999
+ * still RESOLVES (and can be re-activated), but hidden from the selectable
7000
+ * model list and excluded from the auto format-default pick. Set on the
7001
+ * superseded / consolidated models (older lineages, redundant fp16 IRs) so
7002
+ * the active lineup stays the coherent curated ladder without deleting a
7003
+ * model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
7004
+ * an explicit legacy id that has a build for the node's format.
7005
+ */
7006
+ legacy: boolean().optional(),
7007
+ /**
7008
+ * Measured quality/latency metadata — populated from the benchmark addon on
7009
+ * the real node classes. Absent = not yet measured (most entries today; the
7010
+ * catalog historically carried only `sizeMB`, a poor cross-architecture
7011
+ * speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
7012
+ */
7013
+ metrics: object({
7014
+ map50: number().optional(),
7015
+ p95LatencyMs: record(string(), number()).optional()
7016
+ }).optional(),
7017
+ /**
7018
+ * SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
7019
+ * YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
7020
+ * the retraining addon and any future commercial distribution.
7021
+ */
7022
+ license: string().optional(),
7023
+ /**
7024
+ * Variant-selector grouping. The UI groups models by `family` + `tier` and
7025
+ * offers `precision` / `optimization` as variant axes WITHIN a tier — so all
7026
+ * of a family's sizes and quantizations collapse into one grouped picker
7027
+ * instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
7028
+ * (legacy / custom models) — never shown in the grouped selector. The flat
7029
+ * `id` stays the source of truth for resolution/download/persistence; grouping
7030
+ * is a presentation overlay resolved back to an `id`.
7031
+ */
7032
+ group: ModelVariantGroupSchema.optional()
7214
7033
  });
7215
7034
  var ConvertTargetSchema = discriminatedUnion("format", [object({
7216
7035
  format: literal("openvino"),
@@ -7271,8 +7090,8 @@ var RecordingModeSchema = _enum([
7271
7090
  "onAudioThreshold"
7272
7091
  ]);
7273
7092
  /**
7274
- * First-class, authoritative per-camera storage mode — the netta choice the UI
7275
- * reads directly (never inferred from `rules`):
7093
+ * First-class, authoritative per-camera storage mode — the explicit choice the
7094
+ * UI reads directly (never inferred from `rules`):
7276
7095
  * - `off` — not recording.
7277
7096
  * - `events` — record only around triggers (motion / audio threshold),
7278
7097
  * with pre/post-buffer.
@@ -8920,26 +8739,13 @@ DeviceType.Light, method(object({
8920
8739
  percentage: number().min(0).max(100),
8921
8740
  lastChangedAt: number()
8922
8741
  });
8742
+ /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8923
8743
  var StreamFormatSchema = _enum([
8924
8744
  "webrtc",
8925
8745
  "hls",
8926
8746
  "mjpeg",
8927
8747
  "rtsp"
8928
8748
  ]);
8929
- var StreamInfoSchema = object({
8930
- streamId: string(),
8931
- format: StreamFormatSchema,
8932
- url: string().nullable(),
8933
- active: boolean()
8934
- });
8935
- method(object({
8936
- streamId: string(),
8937
- sourceUrl: string(),
8938
- codec: string().optional()
8939
- }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
8940
- streamId: string(),
8941
- format: StreamFormatSchema
8942
- }), string().nullable()), method(_void(), array(StreamInfoSchema));
8943
8749
  var RtspRestreamEntrySchema = object({
8944
8750
  brokerId: string(),
8945
8751
  url: string(),
@@ -9604,7 +9410,7 @@ var ConsumablesStatusSchema = object({
9604
9410
  })),
9605
9411
  lastChangedAt: number()
9606
9412
  });
9607
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, method(object({
9413
+ Object.values(DeviceType), method(object({
9608
9414
  deviceId: number().int().nonnegative(),
9609
9415
  key: string().min(1)
9610
9416
  }), _void(), {
@@ -10519,7 +10325,7 @@ var BoundingBoxSchema = object({
10519
10325
  w: number(),
10520
10326
  h: number()
10521
10327
  });
10522
- var SpatialDetectionSchema = object({
10328
+ object({
10523
10329
  class: string(),
10524
10330
  originalClass: string(),
10525
10331
  score: number(),
@@ -10654,7 +10460,6 @@ var PipelineDefaultStepSchema = lazy(() => object({
10654
10460
  enabled: boolean(),
10655
10461
  modelId: string(),
10656
10462
  children: array(PipelineDefaultStepSchema).readonly(),
10657
- engine: PipelineEngineChoiceSchema.optional(),
10658
10463
  group: string().optional(),
10659
10464
  settings: record(string(), unknown()).optional()
10660
10465
  }));
@@ -10679,7 +10484,9 @@ var PipelineModelOptionSchema = object({
10679
10484
  formats: record(string(), object({
10680
10485
  downloaded: boolean(),
10681
10486
  sizeMB: number()
10682
- }))
10487
+ })),
10488
+ group: ModelVariantGroupSchema.optional(),
10489
+ legacy: boolean().optional()
10683
10490
  });
10684
10491
  var ConfigFieldBridge = custom();
10685
10492
  var PipelineAddonSchemaSchema = object({
@@ -10693,6 +10500,7 @@ var PipelineAddonSchemaSchema = object({
10693
10500
  defaultModelId: string(),
10694
10501
  defaultModelIdByFormat: record(string(), string()).optional(),
10695
10502
  enabledByDefault: boolean().optional(),
10503
+ backfillIntoExistingOverrides: boolean().optional(),
10696
10504
  defaultConfidence: number(),
10697
10505
  group: string().optional(),
10698
10506
  configSchema: array(ConfigFieldBridge).readonly().optional()
@@ -10709,11 +10517,6 @@ var PipelineSchemaSchema = object({
10709
10517
  selectedEngine: PipelineEngineChoiceSchema,
10710
10518
  slots: array(PipelineSlotSchemaSchema).readonly()
10711
10519
  });
10712
- var DetectorOutputSchema = object({
10713
- detections: array(SpatialDetectionSchema).readonly(),
10714
- inferenceMs: number(),
10715
- modelId: string()
10716
- });
10717
10520
  var EngineProvisioningSchema = object({
10718
10521
  runtimeId: _enum([
10719
10522
  "onnx",
@@ -10730,15 +10533,42 @@ var EngineProvisioningSchema = object({
10730
10533
  ]),
10731
10534
  progress: number().optional(),
10732
10535
  error: string().optional(),
10733
- nextRetryAt: number().optional()
10536
+ nextRetryAt: number().optional(),
10537
+ /**
10538
+ * Gate A (config-correctness gate at engine change): human-readable
10539
+ * config issues surfaced EAGERLY when the node's engine changes — model
10540
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
10541
+ * has a <format> build"). Additive/optional: informational only, never
10542
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
10543
+ * Absent/empty when the node-default tree resolves cleanly.
10544
+ */
10545
+ configIssues: array(string()).optional()
10734
10546
  });
10735
10547
  var PipelineStepInputSchema = lazy(() => object({
10736
10548
  addonId: string(),
10737
- modelId: string(),
10549
+ modelId: string().optional(),
10738
10550
  enabled: boolean().default(true),
10739
10551
  children: array(PipelineStepInputSchema).optional(),
10740
10552
  settings: record(string(), unknown()).optional()
10741
10553
  }));
10554
+ var ModelSubstitutionSchema = object({
10555
+ addonId: string(),
10556
+ chosen: string(),
10557
+ running: string(),
10558
+ format: string()
10559
+ });
10560
+ var PipelineValidationIssueSchema = object({
10561
+ addonId: string(),
10562
+ kind: _enum(["unknown-addon", "no-format-build"]),
10563
+ detail: string()
10564
+ });
10565
+ var PipelineValidationResultSchema = object({
10566
+ ok: boolean(),
10567
+ issues: array(PipelineValidationIssueSchema).readonly(),
10568
+ substitutions: array(ModelSubstitutionSchema).readonly(),
10569
+ /** The node's `currentEngine.format` this validation ran against. */
10570
+ format: string()
10571
+ });
10742
10572
  var ReferenceImageEntrySchema = object({
10743
10573
  filename: string(),
10744
10574
  stepIds: array(string()).readonly().optional()
@@ -10809,7 +10639,13 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10809
10639
  })) }), object({ success: literal(true) }), {
10810
10640
  kind: "mutation",
10811
10641
  auth: "admin"
10812
- }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
10642
+ }), method(object({ nodeId: string() }), object({
10643
+ success: literal(true),
10644
+ clearedDevices: number()
10645
+ }), {
10646
+ kind: "mutation",
10647
+ auth: "admin"
10648
+ }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({ steps: array(PipelineStepInputSchema) }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
10813
10649
  name: string(),
10814
10650
  steps: array(PipelineTemplateStepSchema).readonly(),
10815
10651
  engine: PipelineEngineChoiceSchema
@@ -10826,10 +10662,6 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10826
10662
  modelId: string(),
10827
10663
  format: ModelFormatSchema$1
10828
10664
  }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
10829
- addonId: string(),
10830
- frame: FrameInputSchema,
10831
- config: record(string(), unknown()).optional()
10832
- }), DetectorOutputSchema), method(object({
10833
10665
  engine: PipelineEngineChoiceSchema.optional(),
10834
10666
  steps: array(PipelineStepInputSchema).min(1),
10835
10667
  frame: FrameInputSchema.optional(),
@@ -10975,6 +10807,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).read
10975
10807
  auth: "admin"
10976
10808
  }), object({ zones: array(ZoneSchema).readonly() });
10977
10809
  /**
10810
+ * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
10811
+ * decode worker resolves it against the RETAINED native frame's real pixel dims,
10812
+ * so the caller supplies only the detection-res bbox divided by the detection
10813
+ * dims — no native resolution to plumb.
10814
+ */
10815
+ var NativeCropBboxSchema = object({
10816
+ x: number(),
10817
+ y: number(),
10818
+ w: number(),
10819
+ h: number()
10820
+ });
10821
+ /** Result of a best-effort native-resolution crop (`getNativeCrop`). */
10822
+ var NativeCropResultSchema = object({
10823
+ /** Packed rgb (24-bit) pixels of the crop. */
10824
+ bytes: _instanceof(Uint8Array),
10825
+ width: number().int().positive(),
10826
+ height: number().int().positive()
10827
+ });
10828
+ /**
10978
10829
  * Per-camera tunable ranges + defaults. Single source of truth used
10979
10830
  * by both the Zod data schema (validation + default fallback) and
10980
10831
  * the device settings UI (slider min/max/step). Touch one place and
@@ -11069,6 +10920,13 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
11069
10920
  kind: literal("remote-restream"),
11070
10921
  /** The camera's source-owner node (slice 1: always the hub). */
11071
10922
  ownerNodeId: string(),
10923
+ /**
10924
+ * The owner's LAN-reachable host, resolved by the orchestrator from the
10925
+ * per-node `reachableHost` override (Cluster UI). When present the runner
10926
+ * dials THIS host for the owner's restream, in preference to the
10927
+ * `CAMSTACK_HUB_URL`-derived default. Absent → auto-detect fallback.
10928
+ */
10929
+ ownerReachableHost: string().optional(),
11072
10930
  /** Operator override for the owner host the runner dials. */
11073
10931
  hubHostnameOverride: string().optional()
11074
10932
  })]).describe("Per-camera frame-source mode for the runner (P2c)");
@@ -11077,13 +10935,11 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
11077
10935
  * specific runner instance via `attachCamera`. Carries everything the
11078
10936
  * runner needs to subscribe to the local broker and execute inference.
11079
10937
  *
11080
- * Stateless-pipeline model: the full pipeline content (`engine`, `steps`,
11081
- * optional `audio`) travels with the attach payload. The runner keeps it
11082
- * in RAM for the lifetime of the attach — on rebalance, edit, or
11083
- * restart the orchestrator re-sends the latest snapshot.
11084
- *
11085
- * `engine`/`steps`/`audio` are optional during the additive migration
11086
- * window; once orchestrator + UI are migrated they become required.
10938
+ * Stateless-pipeline model: the pipeline content (`steps`, optional
10939
+ * `audio`) travels with the attach payload. The runner keeps it in RAM
10940
+ * for the lifetime of the attach — on rebalance, edit, or restart the
10941
+ * orchestrator re-sends the latest snapshot. Engine is NOT carried: it is
10942
+ * node-local, resolved by the executing runner at dispatch time.
11087
10943
  */
11088
10944
  var RunnerCameraConfigSchema = object({
11089
10945
  deviceId: number(),
@@ -11134,14 +10990,11 @@ var RunnerCameraConfigSchema = object({
11134
10990
  */
11135
10991
  motionSources: MotionSourcesSchema.default(["analyzer"]),
11136
10992
  pipelineEnabled: boolean().default(true),
11137
- /** Engine choice for video steps (runtime+backend+format). */
11138
- engine: PipelineEngineChoiceSchema.optional(),
11139
10993
  /** Ordered tree of video steps. Absent → runner skips video detection. */
11140
10994
  steps: array(PipelineStepInputSchema).readonly().optional(),
11141
10995
  /** Audio classification branch. `enabled:false` disables, null skips. */
11142
10996
  audio: object({
11143
- engine: PipelineEngineChoiceSchema,
11144
- modelId: string(),
10997
+ modelId: string().optional(),
11145
10998
  enabled: boolean()
11146
10999
  }).nullable().optional(),
11147
11000
  /**
@@ -11228,7 +11081,11 @@ var RunnerLocalMetricsSchema = object({
11228
11081
  avgInferenceTimeMs: number(),
11229
11082
  queueDepth: number()
11230
11083
  });
11231
- method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly());
11084
+ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
11085
+ handle: FrameHandleSchema,
11086
+ bbox: NativeCropBboxSchema,
11087
+ maxWidth: number().int().positive().optional()
11088
+ }), NativeCropResultSchema.nullable());
11232
11089
  object({
11233
11090
  detected: boolean(),
11234
11091
  /** Ms epoch of the last detected-true observation. Null if never detected. */
@@ -12522,7 +12379,9 @@ var AddonPageDeclarationSchema$1 = object({
12522
12379
  icon: string(),
12523
12380
  path: string(),
12524
12381
  remoteName: string(),
12525
- bundle: string()
12382
+ bundle: string(),
12383
+ section: string().optional(),
12384
+ sectionLabel: string().optional()
12526
12385
  });
12527
12386
  var AddonPageInfoSchema = object({
12528
12387
  addonId: string(),
@@ -12562,7 +12421,18 @@ var AddonPageDeclarationSchema = object({
12562
12421
  * the static-file route can compute an mtime-based cache-buster URL
12563
12422
  * without a separate filesystem stat.
12564
12423
  */
12565
- bundle: string()
12424
+ bundle: string(),
12425
+ /**
12426
+ * Sidebar section this page docks into. Well-known ids: `'detection'`,
12427
+ * `'cluster'`, `'administration'` — the page renders inside that group.
12428
+ * Any OTHER string creates (or joins) a custom section rendered after
12429
+ * the built-in groups; its label comes from `sectionLabel` (first
12430
+ * declaration wins), falling back to the id. Absent → the legacy
12431
+ * "Addon Pages" group.
12432
+ */
12433
+ section: string().optional(),
12434
+ /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
12435
+ sectionLabel: string().optional()
12566
12436
  });
12567
12437
  method(_void(), array(AddonPageDeclarationSchema).readonly());
12568
12438
  var AddonHttpRouteSchema = object({
@@ -12778,6 +12648,17 @@ var WidgetMetadataSchema = object({
12778
12648
  deviceContext: boolean().default(false),
12779
12649
  integrationContext: boolean().default(false)
12780
12650
  }),
12651
+ /**
12652
+ * Loadable BEFORE authentication. The normal widget registry listing
12653
+ * (`addon-widgets.listWidgets`) is auth-gated, so a pre-auth surface
12654
+ * (the login page) cannot discover a widget through it. A widget that
12655
+ * declares `preAuth: true` marks itself as safe to mount on a pre-auth
12656
+ * screen — it is surfaced through the PUBLIC `auth.listLoginMethods`
12657
+ * login-method contribution channel (see `login-method.cap.ts`) rather
12658
+ * than the authenticated registry, and its bundle is served by the
12659
+ * public `/api/addon-widgets/:addonId/*` static route. Defaults false.
12660
+ */
12661
+ preAuth: boolean().optional().default(false),
12781
12662
  /** Dashboard placement HINTS (operator can override per instance). */
12782
12663
  defaultSize: WidgetSizeEnum.default("md"),
12783
12664
  allowedSizes: array(WidgetSizeEnum).readonly().default([
@@ -13116,6 +12997,66 @@ method(object({
13116
12997
  password: string()
13117
12998
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
13118
12999
  /**
13000
+ * `login-method` — collection cap through which auth addons contribute
13001
+ * their pre-auth login surfaces to the login page. This is the SINGLE,
13002
+ * generic mechanism that supersedes the dead `auth.listProviders` reader:
13003
+ * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
13004
+ * `login-method` provider and the PUBLIC `auth.listLoginMethods`
13005
+ * procedure aggregates them for the unauthenticated login page.
13006
+ *
13007
+ * A contribution is a discriminated union on `kind`:
13008
+ *
13009
+ * - `redirect` — a declarative button. The login page renders a generic
13010
+ * button that navigates to `startUrl` (an addon-owned HTTP route).
13011
+ * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
13012
+ * ZERO shell-side JS. A future SSO addon plugs in the same way — the
13013
+ * login page needs NO change.
13014
+ *
13015
+ * - `widget` — a Module-Federation widget the login page mounts (via
13016
+ * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
13017
+ * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
13018
+ * addon bundle. The referenced widget also declares `preAuth: true` in
13019
+ * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
13020
+ * stamps a public `bundleUrl` from `addonId` + `bundle`.
13021
+ *
13022
+ * Every contribution carries a `stage`:
13023
+ * - `primary` — shown on the first credentials screen (OIDC /
13024
+ * magic-link buttons; a future usernameless passkey).
13025
+ * - `second-factor` — shown AFTER the password leg, gated on the
13026
+ * returned `factors` (passkey-as-2FA today).
13027
+ *
13028
+ * `mount: skip` — the cap is read server-side by the core auth router
13029
+ * (`registry.getCollection('login-method')`), never mounted as its own
13030
+ * tRPC router.
13031
+ */
13032
+ /** When a login method renders in the two-phase login flow. */
13033
+ var LoginStageEnum = _enum(["primary", "second-factor"]);
13034
+ /** One login-method contribution — redirect button OR pre-auth widget. */
13035
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13036
+ kind: literal("redirect"),
13037
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13038
+ id: string(),
13039
+ /** Operator-facing button label. */
13040
+ label: string(),
13041
+ /** lucide-react icon name. */
13042
+ icon: string().optional(),
13043
+ /** Addon-owned HTTP route the button navigates to (GET). */
13044
+ startUrl: string(),
13045
+ stage: LoginStageEnum
13046
+ }), object({
13047
+ kind: literal("widget"),
13048
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13049
+ id: string(),
13050
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
13051
+ addonId: string(),
13052
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13053
+ bundle: string(),
13054
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
13055
+ remote: WidgetRemoteSchema,
13056
+ stage: LoginStageEnum
13057
+ })]);
13058
+ method(_void(), array(LoginMethodContributionSchema).readonly());
13059
+ /**
13119
13060
  * Orchestrator-side destination metadata. The orchestrator computes
13120
13061
  * `id = <addonId>:<subId>` from its provider lookup so consumers
13121
13062
  * (admin UI, restore flow) see one canonical key.
@@ -15312,7 +15253,17 @@ var TrackSchema = object({
15312
15253
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
15313
15254
  totalDistance: number(),
15314
15255
  state: TrackStateSchema,
15315
- active: boolean()
15256
+ active: boolean(),
15257
+ /** Deterministic key-event importance score in [0,1] (server-computed at
15258
+ * track expiry, recomputed on late label). Absent on legacy rows written
15259
+ * before scoring shipped — consumers degrade to absence / compute-on-read. */
15260
+ importance: number().optional(),
15261
+ /** Id of the track's highest-confidence ObjectEvent (its representative
15262
+ * "best" frame). Absent when the track produced no object events. */
15263
+ bestEventId: string().optional(),
15264
+ /** Tag of the importance sub-signal that dominated the score
15265
+ * (identity|dwell|proximity|class|confidence|travel|zone). */
15266
+ importanceReason: string().optional()
15316
15267
  });
15317
15268
  var BaseEventFields = {
15318
15269
  id: string(),
@@ -15377,8 +15328,18 @@ var ObjectEventSchema = object({
15377
15328
  frameHeight: number().optional(),
15378
15329
  /** MediaStore key for the crop attached to this event (if any). */
15379
15330
  mediaKey: string().optional(),
15331
+ /** Design B: MediaStore key of the track's native-resolution key frame (the
15332
+ * best-detection full frame). Resolve via the event-media data-plane
15333
+ * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`) for a detail view that
15334
+ * draws `bbox` over the native frame. Absent on legacy rows / non-decoded
15335
+ * sources — consumers fall back to `mediaKey` (the tight crop). */
15336
+ keyFrameMediaKey: string().optional(),
15380
15337
  /** Populated by B5 (recording playback URL for this event). */
15381
- mediaUrl: string().optional()
15338
+ mediaUrl: string().optional(),
15339
+ /** The parent track's key-event importance [0,1], propagated to every object
15340
+ * event of the track (so an event row can be sorted by importance without a
15341
+ * track join). Absent on legacy rows / before the track was scored. */
15342
+ importance: number().optional()
15382
15343
  });
15383
15344
  var AudioEventSchema = object({
15384
15345
  ...BaseEventFields,
@@ -15402,7 +15363,8 @@ var MediaFileKindEnum = _enum([
15402
15363
  "fullFrame",
15403
15364
  "fullFrameBoxed",
15404
15365
  "faceCrop",
15405
- "plateCrop"
15366
+ "plateCrop",
15367
+ "keyFrame"
15406
15368
  ]);
15407
15369
  var MediaFileSchema = object({
15408
15370
  key: string(),
@@ -15423,6 +15385,32 @@ var DeviceEventQueryInput = object({
15423
15385
  projection: _enum(["full", "slim"]).optional()
15424
15386
  });
15425
15387
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
15388
+ var KeyEventQueryInput = object({
15389
+ deviceId: number(),
15390
+ /** Window lower bound (track firstSeen ≥ since). */
15391
+ since: number(),
15392
+ /** Window upper bound (track firstSeen ≤ until). */
15393
+ until: number(),
15394
+ limit: number().int().min(1).max(200).default(50),
15395
+ /** Drop tracks scoring below this importance. */
15396
+ minImportance: number().min(0).max(1).optional(),
15397
+ /** Restrict to a single class (e.g. 'person'). */
15398
+ classFilter: string().optional()
15399
+ });
15400
+ var KeyEventSchema = object({
15401
+ /** The representative event id (the track's best ObjectEvent, else its trackId). */
15402
+ id: string(),
15403
+ trackId: string(),
15404
+ /** Track start time (firstSeen). */
15405
+ timestamp: number(),
15406
+ className: string(),
15407
+ label: string().optional(),
15408
+ importance: number(),
15409
+ /** Highest-confidence ObjectEvent id for the track (empty when none). */
15410
+ bestEventId: string(),
15411
+ /** Track lifetime in ms (lastSeen - firstSeen). */
15412
+ windowMs: number().optional()
15413
+ });
15426
15414
  var TrackedDetectionSchema = object({
15427
15415
  trackId: string(),
15428
15416
  className: string(),
@@ -15452,7 +15440,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15452
15440
  }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
15453
15441
  kind: "mutation",
15454
15442
  auth: "admin"
15455
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({
15443
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
15456
15444
  deviceId: number(),
15457
15445
  since: number(),
15458
15446
  until: number(),
@@ -15497,11 +15485,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15497
15485
  timestamp: number()
15498
15486
  });
15499
15487
  var CameraPipelineConfigSchema = object({
15500
- engine: PipelineEngineChoiceSchema,
15488
+ engine: PipelineEngineChoiceSchema.optional(),
15501
15489
  steps: array(PipelineStepInputSchema).readonly(),
15502
15490
  audio: object({
15503
- engine: PipelineEngineChoiceSchema,
15504
- modelId: string(),
15491
+ engine: PipelineEngineChoiceSchema.optional(),
15492
+ modelId: string().optional(),
15505
15493
  enabled: boolean(),
15506
15494
  settings: record(string(), unknown()).readonly().optional()
15507
15495
  }).nullable().optional()
@@ -15516,7 +15504,7 @@ var PipelineTemplateSchema = object({
15516
15504
  });
15517
15505
  var AgentAddonConfigSchema = object({
15518
15506
  enabled: boolean(),
15519
- modelId: string(),
15507
+ modelId: string().optional(),
15520
15508
  settings: record(string(), unknown()).readonly()
15521
15509
  });
15522
15510
  var AgentPipelineSettingsSchema = object({
@@ -15526,12 +15514,25 @@ var AgentPipelineSettingsSchema = object({
15526
15514
  detectWeight: number().positive().optional(),
15527
15515
  /** Node is eligible to run the detection pipeline (decode + inference). */
15528
15516
  detect: boolean().optional(),
15529
- /** Node is eligible to host decoder sessions. */
15517
+ /**
15518
+ * DEPRECATED AND IGNORED. Decode is always co-located with its frame
15519
+ * consumer, so decode eligibility IS detect eligibility. Kept optional in
15520
+ * the schema ONLY so persisted stores written before the removal still
15521
+ * parse — no code reads it and no write path emits it.
15522
+ */
15530
15523
  decode: boolean().optional(),
15531
15524
  /** Node is eligible to run audio-analyzer sessions. */
15532
15525
  audio: boolean().optional(),
15533
15526
  /** Node is eligible to be the ingest / source-owner (serve the restream). */
15534
- ingest: boolean().optional()
15527
+ ingest: boolean().optional(),
15528
+ /**
15529
+ * Operator override for the LAN host a cross-node decoder dials to reach
15530
+ * THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
15531
+ * falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
15532
+ * it already uses to reach the hub). Set this only when the auto-detected
15533
+ * address is wrong (multi-homed host, NAT, custom interface).
15534
+ */
15535
+ reachableHost: string().optional()
15535
15536
  });
15536
15537
  var CameraPipelineForAgentSchema = object({
15537
15538
  steps: array(PipelineStepInputSchema).readonly(),
@@ -15579,25 +15580,6 @@ var PipelineAssignmentSchema = object({
15579
15580
  assignedAt: number()
15580
15581
  });
15581
15582
  /**
15582
- * Decoder placement record. Symmetric to `PipelineAssignmentSchema` but for
15583
- * the decoder-node placement domain (`balanceDecoder` decision: manual pin
15584
- * → co-located with pipeline → capacity).
15585
- */
15586
- var DecoderAssignmentSchema = object({
15587
- deviceId: number(),
15588
- /** Moleculer node id of the decoder provider currently responsible for this camera. */
15589
- decoderNodeId: string(),
15590
- /** True when the assignment was set manually via `assignDecoder`, false when chosen by the balancer. */
15591
- pinned: boolean(),
15592
- /** Why this assignment was made — useful for debugging the decoder balancer. */
15593
- reason: _enum([
15594
- "manual",
15595
- "co-located",
15596
- "capacity",
15597
- "hardware-affinity"
15598
- ])
15599
- });
15600
- /**
15601
15583
  * Per-agent load summary surfaced to the load balancer + dashboards.
15602
15584
  * Aggregated from each runner's `getLocalLoad` cap call.
15603
15585
  */
@@ -15637,6 +15619,15 @@ var GlobalMetricsSchema = object({
15637
15619
  * capability providers.
15638
15620
  */
15639
15621
  var CapabilityBindingsSchema = record(string(), string());
15622
+ /**
15623
+ * The cluster's single camera-source owner (`clusterRoles.ingestNode`) plus
15624
+ * its LAN-reachable host, if one is registered. See `getIngestOwner`.
15625
+ */
15626
+ var IngestOwnerSchema = object({
15627
+ ownerNodeId: string(),
15628
+ reachableHost: string().optional(),
15629
+ configIssue: string().optional()
15630
+ });
15640
15631
  /** Source block — always present; derives from the stream catalog. */
15641
15632
  var CameraSourceStatusSchema = object({ streams: array(object({
15642
15633
  camStreamId: string(),
@@ -15651,6 +15642,14 @@ var CameraAssignmentStatusSchema = object({
15651
15642
  detectionNodeId: string().nullable(),
15652
15643
  decoderNodeId: string().nullable(),
15653
15644
  audioNodeId: string().nullable(),
15645
+ /**
15646
+ * The node that OWNS this camera's physical source pull (dials the RTSP and
15647
+ * hosts the broker/restream) — the cluster ingest owner today
15648
+ * (`clusterRoles.ingestNode`), per-camera once source assignment lands. Lets
15649
+ * the UI show WHERE a camera is sourced without SSH/logs, and is the node the
15650
+ * broker block below was read from (pinned). Nullable only pre-wiring.
15651
+ */
15652
+ sourceNodeId: string().nullable(),
15654
15653
  pinned: object({
15655
15654
  detection: boolean(),
15656
15655
  decoder: boolean(),
@@ -15783,16 +15782,7 @@ method(object({
15783
15782
  }), object({ success: literal(true) }), {
15784
15783
  kind: "mutation",
15785
15784
  auth: "admin"
15786
- }), method(object({
15787
- deviceId: number(),
15788
- nodeId: string()
15789
- }), _void(), {
15790
- kind: "mutation",
15791
- auth: "admin"
15792
- }), method(object({ deviceId: number() }), _void(), {
15793
- kind: "mutation",
15794
- auth: "admin"
15795
- }), method(_void(), array(DecoderAssignmentSchema).readonly()), method(object({
15785
+ }), method(_void(), IngestOwnerSchema), method(object({
15796
15786
  deviceId: number(),
15797
15787
  nodeId: string()
15798
15788
  }), object({ success: literal(true) }), {
@@ -15813,10 +15803,7 @@ method(object({
15813
15803
  nodeId: string(),
15814
15804
  pinned: boolean(),
15815
15805
  assignedAt: number()
15816
- }))), method(object({
15817
- deviceId: number(),
15818
- pipelineNodeId: string().optional()
15819
- }), DecoderAssignmentSchema), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
15806
+ }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
15820
15807
  nodeId: string(),
15821
15808
  settings: AgentPipelineSettingsSchema
15822
15809
  })).readonly()), method(object({
@@ -15846,12 +15833,26 @@ method(object({
15846
15833
  }), method(object({
15847
15834
  agentNodeId: string(),
15848
15835
  detect: boolean().nullable().optional(),
15849
- decode: boolean().nullable().optional(),
15850
15836
  audio: boolean().nullable().optional(),
15851
15837
  ingest: boolean().nullable().optional()
15852
15838
  }), object({ success: literal(true) }), {
15853
15839
  kind: "mutation",
15854
15840
  auth: "admin"
15841
+ }), method(object({
15842
+ agentNodeId: string(),
15843
+ reachableHost: string().nullable()
15844
+ }), object({ success: literal(true) }), {
15845
+ kind: "mutation",
15846
+ auth: "admin"
15847
+ }), method(object({ agentNodeId: string() }), object({
15848
+ success: literal(true),
15849
+ /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
15850
+ effectiveModelId: string().nullable(),
15851
+ /** Number of cameras whose node-scoped overrides were cleared. */
15852
+ clearedCameraOverrides: number()
15853
+ }), {
15854
+ kind: "mutation",
15855
+ auth: "admin"
15855
15856
  }), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
15856
15857
  deviceId: number(),
15857
15858
  addonId: string(),
@@ -15896,22 +15897,131 @@ method(object({
15896
15897
  kind: "mutation",
15897
15898
  auth: "admin"
15898
15899
  });
15899
- var RegisteredStreamSchema = object({
15900
- streamId: string(),
15901
- label: string().optional(),
15902
- codec: string(),
15903
- type: _enum(["video", "audio"]),
15904
- sourceUrl: string()
15900
+ /**
15901
+ * server-management — per-NODE singleton capability for a node's ROOT
15902
+ * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
15903
+ * agents).
15904
+ *
15905
+ * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
15906
+ * on agents) carries the whole software stack in its npm dep tree, so ONE
15907
+ * version describes the node. Updates install into
15908
+ * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
15909
+ * starter (probation boot + auto-rollback to N-1).
15910
+ *
15911
+ * Providers:
15912
+ * - HUB: `ServerUpdateService` behind the `server-provided` mount
15913
+ * (`buildServerProviders` in trpc.router.ts) — the default target for
15914
+ * unpinned calls.
15915
+ * - AGENT: `AgentUpdateService` registered by the agent bootstrap under
15916
+ * the synthetic `agent-runtime` addonId and declared in the agent's
15917
+ * `$hub.registerNode` manifest.
15918
+ *
15919
+ * Node routing: singleton caps get the codegen/runtime-builder `nodeId`
15920
+ * injection on every method — `input.nodeId` (or `nodePin(nodeId)` from the
15921
+ * SDK) routes the call to that node's provider via the standard remote
15922
+ * proxy (`createCapabilityProxy` → `$agent-cap-fwd` → the agent's
15923
+ * in-process provider lookup). No `nodeId` → the hub's own provider.
15924
+ *
15925
+ * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md
15926
+ */
15927
+ /**
15928
+ * Where the running hub's code was loaded from:
15929
+ * - `workspace` — dev checkout (tsx / workspace dist); the starter defers to
15930
+ * plain resolution and runtime updates are refused.
15931
+ * - `baked` — the immutable image seed closure (no data-dir root active).
15932
+ * - `data-root` — the runtime-updatable `<dataDir>/server-root` closure.
15933
+ */
15934
+ var ServerBootModeSchema = _enum([
15935
+ "workspace",
15936
+ "baked",
15937
+ "data-root"
15938
+ ]);
15939
+ /**
15940
+ * Update lifecycle state:
15941
+ * - `idle` / `checking` / `staging` — steady / in-flight registry work.
15942
+ * - `pending-restart` — a version is staged and the node has NOT yet
15943
+ * restarted onto it (still running the OLD version).
15944
+ * - `awaiting-confirmation` — the node HAS restarted onto the staged version
15945
+ * (it is the active probation boot) and is waiting to confirm boot-health.
15946
+ * Apply/rollback are refused in this state and the node must NOT be
15947
+ * manually restarted, or the probation boot auto-rolls-back.
15948
+ */
15949
+ var ServerUpdateStateSchema = _enum([
15950
+ "idle",
15951
+ "checking",
15952
+ "staging",
15953
+ "pending-restart",
15954
+ "awaiting-confirmation"
15955
+ ]);
15956
+ var ServerRollbackInfoSchema = object({
15957
+ /** The version that failed (or was manually rolled back). */
15958
+ fromVersion: string(),
15959
+ /** The version rolled back to; null = the baked seed. */
15960
+ toVersion: string().nullable(),
15961
+ atMs: number(),
15962
+ reason: string()
15905
15963
  });
15906
- var ExposedResourceSchema = object({
15907
- streamId: string(),
15908
- format: string(),
15909
- value: string()
15964
+ var ServerPackageStatusSchema = object({
15965
+ /** Root package name (`@camstack/server` on the hub). */
15966
+ packageName: string(),
15967
+ /** Version of the code the running process ACTUALLY loaded. */
15968
+ runningVersion: string().nullable(),
15969
+ /** Node.js runtime version the node's process runs on (`process.versions.node`). */
15970
+ nodeRuntimeVersion: string().nullable(),
15971
+ /** Active data-dir root version; null when booted from seed/workspace. */
15972
+ activeVersion: string().nullable(),
15973
+ /** N-1 version kept for rollback; null when no previous version exists. */
15974
+ previousVersion: string().nullable(),
15975
+ /** Version of the immutable baked seed closure (image fallback). */
15976
+ seedVersion: string().nullable(),
15977
+ /** Latest registry version from the most recent check (null = never checked). */
15978
+ latestVersion: string().nullable(),
15979
+ updateAvailable: boolean(),
15980
+ bootMode: ServerBootModeSchema,
15981
+ updateState: ServerUpdateStateSchema,
15982
+ /** Version staged + awaiting its probation boot, when one is pending. */
15983
+ pendingVersion: string().nullable(),
15984
+ /** Set when the last freshly-activated version failed its boot health-check. */
15985
+ rolledBack: ServerRollbackInfoSchema.nullable(),
15986
+ /**
15987
+ * True when `server-root/state.json` EXISTS but is unreadable/corrupt — the
15988
+ * hub is running from the baked seed (or workspace) while installed data-dir
15989
+ * versions are being IGNORED. Surfaced as a warning in the UI.
15990
+ */
15991
+ stateFileCorrupt: boolean(),
15992
+ lastCheckedAtMs: number().nullable()
15993
+ });
15994
+ var ServerUpdateCheckResultSchema = object({
15995
+ packageName: string(),
15996
+ runningVersion: string().nullable(),
15997
+ latestVersion: string().nullable(),
15998
+ updateAvailable: boolean(),
15999
+ checkedAtMs: number(),
16000
+ /** Non-null when the registry lookup failed (offline, bad registry, …). */
16001
+ error: string().nullable()
16002
+ });
16003
+ var ServerUpdateActionResultSchema = object({
16004
+ accepted: boolean(),
16005
+ targetVersion: string().nullable(),
16006
+ /** True when a graceful restart was scheduled to apply the change. */
16007
+ restarting: boolean(),
16008
+ message: string()
16009
+ });
16010
+ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
16011
+ kind: "mutation",
16012
+ auth: "admin"
16013
+ }), method(object({
16014
+ /** Explicit target version; omitted = latest from the registry. */
16015
+ version: string().optional() }), ServerUpdateActionResultSchema, {
16016
+ kind: "mutation",
16017
+ auth: "admin"
16018
+ }), method(_void(), ServerUpdateActionResultSchema, {
16019
+ kind: "mutation",
16020
+ auth: "admin"
16021
+ }), method(_void(), ServerUpdateActionResultSchema, {
16022
+ kind: "mutation",
16023
+ auth: "admin"
15910
16024
  });
15911
- method(object({
15912
- deviceId: number(),
15913
- streams: array(RegisteredStreamSchema).readonly()
15914
- }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(ExposedResourceSchema).readonly());
15915
16025
  /**
15916
16026
  * Query filter for settings-store collections.
15917
16027
  */
@@ -16064,9 +16174,9 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
16064
16174
  /**
16065
16175
  * A single device snapshot returned as base64 JPEG/PNG.
16066
16176
  *
16067
- * Shared with the `snapshot-provider` collection cap the orchestrator
16068
- * receives the same shape from each native provider and from the
16069
- * broker-based fallback.
16177
+ * The `SnapshotAddon` wrapper returns this shape whether the frame came from
16178
+ * the device-native provider (onboard capture) or from the stream-broker
16179
+ * prebuffer fallback.
16070
16180
  */
16071
16181
  var SnapshotImageSchema = object({
16072
16182
  base64: string(),
@@ -16097,11 +16207,12 @@ DeviceType.Camera, method(object({
16097
16207
  }), SnapshotImageSchema.nullable()), method(object({ deviceId: number() }), _void(), {
16098
16208
  kind: "mutation",
16099
16209
  auth: "admin"
16100
- });
16101
- method(object({ deviceId: number() }), boolean()), method(object({
16210
+ }), systemMethod(object({ deviceIds: array(number()).min(1).max(200) }), array(object({
16102
16211
  deviceId: number(),
16103
- streamId: string().optional()
16104
- }), SnapshotImageSchema.nullable());
16212
+ lastCapturedAt: number().nullable(),
16213
+ cacheAgeMs: number().nullable(),
16214
+ etag: string().nullable()
16215
+ })));
16105
16216
  /**
16106
16217
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
16107
16218
  * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
@@ -16352,10 +16463,32 @@ method(_void(), array(TurnServerSchema).readonly());
16352
16463
  * b. `finishAuthentication({userId, response})` → server verifies
16353
16464
  * the assertion, bumps the credential counter, returns ok.
16354
16465
  *
16466
+ * 2b. Usernameless (discoverable-credential) authentication — the
16467
+ * passkey IS the primary factor, no password leg:
16468
+ * a. `beginDiscoverableAuthentication({})` → assertion options with
16469
+ * EMPTY `allowCredentials` (the browser offers every resident
16470
+ * passkey it holds for this RP) + `userVerification: 'required'`
16471
+ * (the passkey replaces both factors, so UV is mandatory).
16472
+ * The challenge is stored server-side, NOT bound to any user.
16473
+ * b. `finishDiscoverableAuthentication({response})` → the provider
16474
+ * resolves the credential by the response's credential id,
16475
+ * verifies the assertion against the stored challenge + that
16476
+ * credential's public key/counter, and returns the OWNING
16477
+ * `userId` — the caller (core auth router) mints the session.
16478
+ *
16355
16479
  * 3. Management:
16356
16480
  * - `listPasskeys({userId})` — enumerate user's enrolled credentials.
16357
16481
  * - `removePasskey({userId, credentialId})` — revoke one credential.
16358
16482
  *
16483
+ * 4. Second-factor preference (opt-in, default OFF):
16484
+ * Enrolling a passkey only enables passkey-FIRST sign-in. It is
16485
+ * demanded as a second factor after a password login ONLY when the
16486
+ * user explicitly opts in via `setSecondFactorPreference`.
16487
+ * - `getSecondFactorPreference({userId})` → `{ enabled }` (missing
16488
+ * row ⇒ `enabled: false`).
16489
+ * - `setSecondFactorPreference({userId, enabled})` — persisted by
16490
+ * the providing addon beside its credentials.
16491
+ *
16359
16492
  * Challenges are short-lived (5 min, in-memory). The cap is internal —
16360
16493
  * the admin-ui composes the begin/finish round-trip and never exposes
16361
16494
  * the cap to non-admins.
@@ -16398,6 +16531,17 @@ method(object({
16398
16531
  }), object({ verified: boolean() }), {
16399
16532
  kind: "mutation",
16400
16533
  access: "view"
16534
+ }), method(object({}), object({ optionsJSON: record(string(), unknown()) }), {
16535
+ kind: "mutation",
16536
+ access: "view"
16537
+ }), method(object({
16538
+ /** AuthenticationResponseJSON from the browser. */
16539
+ response: record(string(), unknown()) }), object({
16540
+ verified: boolean(),
16541
+ userId: string().nullable()
16542
+ }), {
16543
+ kind: "mutation",
16544
+ access: "view"
16401
16545
  }), method(object({ userId: string() }), array(PasskeySummarySchema), { auth: "admin" }), method(object({
16402
16546
  userId: string(),
16403
16547
  credentialId: string()
@@ -16405,6 +16549,13 @@ method(object({
16405
16549
  kind: "mutation",
16406
16550
  auth: "admin",
16407
16551
  access: "delete"
16552
+ }), method(object({ userId: string() }), object({ enabled: boolean() }), { auth: "admin" }), method(object({
16553
+ userId: string(),
16554
+ enabled: boolean()
16555
+ }), object({ success: literal(true) }), {
16556
+ kind: "mutation",
16557
+ auth: "admin",
16558
+ access: "create"
16408
16559
  });
16409
16560
  /**
16410
16561
  * `videoclips` — the unified, navigable-clip surface for a camera.
@@ -16462,9 +16613,10 @@ method(object({
16462
16613
  auth: "admin"
16463
16614
  });
16464
16615
  /**
16465
- * Optional client-side hints sent at session creation to help the
16466
- * provider pick the best native source. All fields are optional —
16467
- * a viewer that knows nothing still gets a sane default.
16616
+ * Optional client-side hints sent at session creation to help the provider
16617
+ * pick the best native source. All fields optional — a viewer that knows
16618
+ * nothing still gets a sane default. (Relocated from the retired `webrtc`
16619
+ * collection cap; this `webrtc-session` cap is the live signaling surface.)
16468
16620
  */
16469
16621
  var webrtcClientHintsSchema = object({
16470
16622
  viewportWidth: number().int().positive().optional(),
@@ -16475,22 +16627,6 @@ var webrtcClientHintsSchema = object({
16475
16627
  /** Hard tier override; takes precedence over scoring when registered. */
16476
16628
  prefersTier: string().optional()
16477
16629
  }).partial();
16478
- method(object({
16479
- streamId: string(),
16480
- sdpOffer: string()
16481
- }), string(), { kind: "mutation" }), method(object({ streamId: string() }), boolean()), method(object({
16482
- streamId: string(),
16483
- codec: string()
16484
- }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
16485
- streamId: string(),
16486
- hints: webrtcClientHintsSchema.optional()
16487
- }), object({
16488
- sessionId: string(),
16489
- sdpOffer: string()
16490
- }), { kind: "mutation" }), method(object({
16491
- sessionId: string(),
16492
- sdpAnswer: string()
16493
- }), _void(), { kind: "mutation" }), method(object({ sessionId: string() }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), boolean());
16494
16630
  /**
16495
16631
  * Discriminated target for a WebRTC session. The client sends this
16496
16632
  * structured object instead of building / parsing brokerId strings;
@@ -17221,7 +17357,17 @@ var FaceInfoSchema = object({
17221
17357
  recognizedIdentityId: string().optional(),
17222
17358
  identityName: string().optional(),
17223
17359
  assigned: boolean(),
17224
- base64: string().optional()
17360
+ base64: string().optional(),
17361
+ /** Design B: the face bbox (pixel space) on the key frame — lets a detail
17362
+ * view draw the box over the native `keyFrameMediaKey` frame. Absent on
17363
+ * legacy rows written before design B. */
17364
+ faceBbox: BoundingBoxSchema.optional(),
17365
+ /** Design B: MediaStore key of the track's native-resolution key frame.
17366
+ * Fetch the native JPEG via the event-media data-plane
17367
+ * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`). Absent when the
17368
+ * track produced no key frame (e.g. native/onboard source) — the UI falls
17369
+ * back to the inline `base64` face crop. */
17370
+ keyFrameMediaKey: string().optional()
17225
17371
  });
17226
17372
  var FaceFilterEnum = _enum([
17227
17373
  "unassigned",
@@ -17918,6 +18064,16 @@ var TopologyCategorySchema = object({
17918
18064
  healthy: number(),
17919
18065
  addons: array(TopologyCategoryAddonSchema).readonly()
17920
18066
  });
18067
+ /**
18068
+ * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
18069
+ * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
18070
+ * version visibility for the Server management surface. Nullable: offline
18071
+ * rows and pre-phase-2 nodes report none.
18072
+ */
18073
+ var TopologyRootPackageSchema = object({
18074
+ name: string(),
18075
+ version: string()
18076
+ });
17921
18077
  var TopologyNodeSchema = object({
17922
18078
  id: string(),
17923
18079
  name: string(),
@@ -17941,7 +18097,8 @@ var TopologyNodeSchema = object({
17941
18097
  status: string()
17942
18098
  })).readonly(),
17943
18099
  processes: array(TopologyProcessSchema).readonly(),
17944
- categories: array(TopologyCategorySchema).readonly()
18100
+ categories: array(TopologyCategorySchema).readonly(),
18101
+ rootPackage: TopologyRootPackageSchema.nullable()
17945
18102
  });
17946
18103
  var CapUsageEdgeSchema = object({
17947
18104
  callerAddonId: string(),
@@ -20741,6 +20898,12 @@ Object.freeze({
20741
20898
  addonId: null,
20742
20899
  access: "create"
20743
20900
  },
20901
+ "loginMethod.getLoginMethods": {
20902
+ capName: "login-method",
20903
+ capScope: "system",
20904
+ addonId: null,
20905
+ access: "view"
20906
+ },
20744
20907
  "mediaPlayer.next": {
20745
20908
  capName: "media-player",
20746
20909
  capScope: "device",
@@ -21323,6 +21486,12 @@ Object.freeze({
21323
21486
  addonId: null,
21324
21487
  access: "view"
21325
21488
  },
21489
+ "pipelineAnalytics.getKeyEvents": {
21490
+ capName: "pipeline-analytics",
21491
+ capScope: "device",
21492
+ addonId: null,
21493
+ access: "view"
21494
+ },
21326
21495
  "pipelineAnalytics.getMotionEvents": {
21327
21496
  capName: "pipeline-analytics",
21328
21497
  capScope: "device",
@@ -21371,23 +21540,23 @@ Object.freeze({
21371
21540
  addonId: null,
21372
21541
  access: "create"
21373
21542
  },
21374
- "pipelineExecutor.deleteModel": {
21543
+ "pipelineExecutor.clearDeviceOverrides": {
21375
21544
  capName: "pipeline-executor",
21376
21545
  capScope: "system",
21377
21546
  addonId: null,
21378
21547
  access: "delete"
21379
21548
  },
21380
- "pipelineExecutor.deleteTemplate": {
21549
+ "pipelineExecutor.deleteModel": {
21381
21550
  capName: "pipeline-executor",
21382
21551
  capScope: "system",
21383
21552
  addonId: null,
21384
21553
  access: "delete"
21385
21554
  },
21386
- "pipelineExecutor.detect": {
21555
+ "pipelineExecutor.deleteTemplate": {
21387
21556
  capName: "pipeline-executor",
21388
21557
  capScope: "system",
21389
21558
  addonId: null,
21390
- access: "view"
21559
+ access: "delete"
21391
21560
  },
21392
21561
  "pipelineExecutor.downloadModel": {
21393
21562
  capName: "pipeline-executor",
@@ -21581,13 +21750,13 @@ Object.freeze({
21581
21750
  addonId: null,
21582
21751
  access: "create"
21583
21752
  },
21584
- "pipelineOrchestrator.assignAudio": {
21585
- capName: "pipeline-orchestrator",
21753
+ "pipelineExecutor.validatePipeline": {
21754
+ capName: "pipeline-executor",
21586
21755
  capScope: "system",
21587
21756
  addonId: null,
21588
- access: "create"
21757
+ access: "view"
21589
21758
  },
21590
- "pipelineOrchestrator.assignDecoder": {
21759
+ "pipelineOrchestrator.assignAudio": {
21591
21760
  capName: "pipeline-orchestrator",
21592
21761
  capScope: "system",
21593
21762
  addonId: null,
@@ -21671,19 +21840,13 @@ Object.freeze({
21671
21840
  addonId: null,
21672
21841
  access: "view"
21673
21842
  },
21674
- "pipelineOrchestrator.getDecoderAssignment": {
21675
- capName: "pipeline-orchestrator",
21676
- capScope: "system",
21677
- addonId: null,
21678
- access: "view"
21679
- },
21680
- "pipelineOrchestrator.getDecoderAssignments": {
21843
+ "pipelineOrchestrator.getGlobalMetrics": {
21681
21844
  capName: "pipeline-orchestrator",
21682
21845
  capScope: "system",
21683
21846
  addonId: null,
21684
21847
  access: "view"
21685
21848
  },
21686
- "pipelineOrchestrator.getGlobalMetrics": {
21849
+ "pipelineOrchestrator.getIngestOwner": {
21687
21850
  capName: "pipeline-orchestrator",
21688
21851
  capScope: "system",
21689
21852
  addonId: null,
@@ -21725,6 +21888,12 @@ Object.freeze({
21725
21888
  addonId: null,
21726
21889
  access: "delete"
21727
21890
  },
21891
+ "pipelineOrchestrator.resetNodePipelineDefaults": {
21892
+ capName: "pipeline-orchestrator",
21893
+ capScope: "system",
21894
+ addonId: null,
21895
+ access: "delete"
21896
+ },
21728
21897
  "pipelineOrchestrator.resolvePipeline": {
21729
21898
  capName: "pipeline-orchestrator",
21730
21899
  capScope: "system",
@@ -21761,37 +21930,37 @@ Object.freeze({
21761
21930
  addonId: null,
21762
21931
  access: "create"
21763
21932
  },
21764
- "pipelineOrchestrator.setCameraPipelineForAgent": {
21933
+ "pipelineOrchestrator.setAgentReachableHost": {
21765
21934
  capName: "pipeline-orchestrator",
21766
21935
  capScope: "system",
21767
21936
  addonId: null,
21768
21937
  access: "create"
21769
21938
  },
21770
- "pipelineOrchestrator.setCameraStepOverride": {
21939
+ "pipelineOrchestrator.setCameraPipelineForAgent": {
21771
21940
  capName: "pipeline-orchestrator",
21772
21941
  capScope: "system",
21773
21942
  addonId: null,
21774
21943
  access: "create"
21775
21944
  },
21776
- "pipelineOrchestrator.setCameraStepToggle": {
21945
+ "pipelineOrchestrator.setCameraStepOverride": {
21777
21946
  capName: "pipeline-orchestrator",
21778
21947
  capScope: "system",
21779
21948
  addonId: null,
21780
21949
  access: "create"
21781
21950
  },
21782
- "pipelineOrchestrator.setCapabilityBinding": {
21951
+ "pipelineOrchestrator.setCameraStepToggle": {
21783
21952
  capName: "pipeline-orchestrator",
21784
21953
  capScope: "system",
21785
21954
  addonId: null,
21786
21955
  access: "create"
21787
21956
  },
21788
- "pipelineOrchestrator.unassignAudio": {
21957
+ "pipelineOrchestrator.setCapabilityBinding": {
21789
21958
  capName: "pipeline-orchestrator",
21790
21959
  capScope: "system",
21791
21960
  addonId: null,
21792
21961
  access: "create"
21793
21962
  },
21794
- "pipelineOrchestrator.unassignDecoder": {
21963
+ "pipelineOrchestrator.unassignAudio": {
21795
21964
  capName: "pipeline-orchestrator",
21796
21965
  capScope: "system",
21797
21966
  addonId: null,
@@ -21851,6 +22020,12 @@ Object.freeze({
21851
22020
  addonId: null,
21852
22021
  access: "view"
21853
22022
  },
22023
+ "pipelineRunner.getNativeCrop": {
22024
+ capName: "pipeline-runner",
22025
+ capScope: "system",
22026
+ addonId: null,
22027
+ access: "view"
22028
+ },
21854
22029
  "pipelineRunner.reportMotion": {
21855
22030
  capName: "pipeline-runner",
21856
22031
  capScope: "system",
@@ -22091,33 +22266,45 @@ Object.freeze({
22091
22266
  addonId: null,
22092
22267
  access: "create"
22093
22268
  },
22094
- "restreamer.getExposedResources": {
22095
- capName: "restreamer",
22269
+ "scriptRunner.run": {
22270
+ capName: "script-runner",
22271
+ capScope: "device",
22272
+ addonId: null,
22273
+ access: "create"
22274
+ },
22275
+ "scriptRunner.stop": {
22276
+ capName: "script-runner",
22277
+ capScope: "device",
22278
+ addonId: null,
22279
+ access: "create"
22280
+ },
22281
+ "serverManagement.applyServerUpdate": {
22282
+ capName: "server-management",
22096
22283
  capScope: "system",
22097
22284
  addonId: null,
22098
- access: "view"
22285
+ access: "create"
22099
22286
  },
22100
- "restreamer.registerDevice": {
22101
- capName: "restreamer",
22287
+ "serverManagement.checkServerUpdate": {
22288
+ capName: "server-management",
22102
22289
  capScope: "system",
22103
22290
  addonId: null,
22104
22291
  access: "create"
22105
22292
  },
22106
- "restreamer.unregisterDevice": {
22107
- capName: "restreamer",
22293
+ "serverManagement.getServerPackageStatus": {
22294
+ capName: "server-management",
22108
22295
  capScope: "system",
22109
22296
  addonId: null,
22110
- access: "delete"
22297
+ access: "view"
22111
22298
  },
22112
- "scriptRunner.run": {
22113
- capName: "script-runner",
22114
- capScope: "device",
22299
+ "serverManagement.restartServer": {
22300
+ capName: "server-management",
22301
+ capScope: "system",
22115
22302
  addonId: null,
22116
22303
  access: "create"
22117
22304
  },
22118
- "scriptRunner.stop": {
22119
- capName: "script-runner",
22120
- capScope: "device",
22305
+ "serverManagement.rollbackServerUpdate": {
22306
+ capName: "server-management",
22307
+ capScope: "system",
22121
22308
  addonId: null,
22122
22309
  access: "create"
22123
22310
  },
@@ -22205,23 +22392,17 @@ Object.freeze({
22205
22392
  addonId: null,
22206
22393
  access: "view"
22207
22394
  },
22208
- "snapshot.invalidateCache": {
22395
+ "snapshot.getSnapshotOverview": {
22209
22396
  capName: "snapshot",
22210
22397
  capScope: "device",
22211
22398
  addonId: null,
22212
- access: "create"
22213
- },
22214
- "snapshotProvider.getSnapshot": {
22215
- capName: "snapshot-provider",
22216
- capScope: "system",
22217
- addonId: null,
22218
22399
  access: "view"
22219
22400
  },
22220
- "snapshotProvider.supportsDevice": {
22221
- capName: "snapshot-provider",
22222
- capScope: "system",
22401
+ "snapshot.invalidateCache": {
22402
+ capName: "snapshot",
22403
+ capScope: "device",
22223
22404
  addonId: null,
22224
- access: "view"
22405
+ access: "create"
22225
22406
  },
22226
22407
  "ssoBridge.signBridgeToken": {
22227
22408
  capName: "sso-bridge",
@@ -22649,30 +22830,6 @@ Object.freeze({
22649
22830
  addonId: null,
22650
22831
  access: "view"
22651
22832
  },
22652
- "streamingEngine.getStreamUrl": {
22653
- capName: "streaming-engine",
22654
- capScope: "system",
22655
- addonId: null,
22656
- access: "view"
22657
- },
22658
- "streamingEngine.listStreams": {
22659
- capName: "streaming-engine",
22660
- capScope: "system",
22661
- addonId: null,
22662
- access: "view"
22663
- },
22664
- "streamingEngine.registerStream": {
22665
- capName: "streaming-engine",
22666
- capScope: "system",
22667
- addonId: null,
22668
- access: "create"
22669
- },
22670
- "streamingEngine.unregisterStream": {
22671
- capName: "streaming-engine",
22672
- capScope: "system",
22673
- addonId: null,
22674
- access: "delete"
22675
- },
22676
22833
  "streamParams.getConfigSchema": {
22677
22834
  capName: "stream-params",
22678
22835
  capScope: "device",
@@ -22919,6 +23076,12 @@ Object.freeze({
22919
23076
  addonId: null,
22920
23077
  access: "view"
22921
23078
  },
23079
+ "userPasskeys.beginDiscoverableAuthentication": {
23080
+ capName: "user-passkeys",
23081
+ capScope: "system",
23082
+ addonId: null,
23083
+ access: "view"
23084
+ },
22922
23085
  "userPasskeys.beginRegistration": {
22923
23086
  capName: "user-passkeys",
22924
23087
  capScope: "system",
@@ -22931,12 +23094,24 @@ Object.freeze({
22931
23094
  addonId: null,
22932
23095
  access: "view"
22933
23096
  },
23097
+ "userPasskeys.finishDiscoverableAuthentication": {
23098
+ capName: "user-passkeys",
23099
+ capScope: "system",
23100
+ addonId: null,
23101
+ access: "view"
23102
+ },
22934
23103
  "userPasskeys.finishRegistration": {
22935
23104
  capName: "user-passkeys",
22936
23105
  capScope: "system",
22937
23106
  addonId: null,
22938
23107
  access: "create"
22939
23108
  },
23109
+ "userPasskeys.getSecondFactorPreference": {
23110
+ capName: "user-passkeys",
23111
+ capScope: "system",
23112
+ addonId: null,
23113
+ access: "view"
23114
+ },
22940
23115
  "userPasskeys.listPasskeys": {
22941
23116
  capName: "user-passkeys",
22942
23117
  capScope: "system",
@@ -22949,6 +23124,12 @@ Object.freeze({
22949
23124
  addonId: null,
22950
23125
  access: "delete"
22951
23126
  },
23127
+ "userPasskeys.setSecondFactorPreference": {
23128
+ capName: "user-passkeys",
23129
+ capScope: "system",
23130
+ addonId: null,
23131
+ access: "create"
23132
+ },
22952
23133
  "vacuumControl.locate": {
22953
23134
  capName: "vacuum-control",
22954
23135
  capScope: "device",
@@ -23021,6 +23202,18 @@ Object.freeze({
23021
23202
  addonId: null,
23022
23203
  access: "view"
23023
23204
  },
23205
+ "viewerUi.getStaticDir": {
23206
+ capName: "viewer-ui",
23207
+ capScope: "system",
23208
+ addonId: null,
23209
+ access: "view"
23210
+ },
23211
+ "viewerUi.getVersion": {
23212
+ capName: "viewer-ui",
23213
+ capScope: "system",
23214
+ addonId: null,
23215
+ access: "view"
23216
+ },
23024
23217
  "waterHeater.setAway": {
23025
23218
  capName: "water-heater",
23026
23219
  capScope: "device",
@@ -23039,54 +23232,6 @@ Object.freeze({
23039
23232
  addonId: null,
23040
23233
  access: "create"
23041
23234
  },
23042
- "webrtc.closeSession": {
23043
- capName: "webrtc",
23044
- capScope: "system",
23045
- addonId: null,
23046
- access: "create"
23047
- },
23048
- "webrtc.createSession": {
23049
- capName: "webrtc",
23050
- capScope: "system",
23051
- addonId: null,
23052
- access: "create"
23053
- },
23054
- "webrtc.handleAnswer": {
23055
- capName: "webrtc",
23056
- capScope: "system",
23057
- addonId: null,
23058
- access: "create"
23059
- },
23060
- "webrtc.handleOffer": {
23061
- capName: "webrtc",
23062
- capScope: "system",
23063
- addonId: null,
23064
- access: "create"
23065
- },
23066
- "webrtc.hasAdaptiveBitrate": {
23067
- capName: "webrtc",
23068
- capScope: "system",
23069
- addonId: null,
23070
- access: "view"
23071
- },
23072
- "webrtc.registerStream": {
23073
- capName: "webrtc",
23074
- capScope: "system",
23075
- addonId: null,
23076
- access: "create"
23077
- },
23078
- "webrtc.supportsStream": {
23079
- capName: "webrtc",
23080
- capScope: "system",
23081
- addonId: null,
23082
- access: "view"
23083
- },
23084
- "webrtc.unregisterStream": {
23085
- capName: "webrtc",
23086
- capScope: "system",
23087
- addonId: null,
23088
- access: "delete"
23089
- },
23090
23235
  "webrtcSession.addIceCandidate": {
23091
23236
  capName: "webrtc-session",
23092
23237
  capScope: "device",
@@ -23935,7 +24080,7 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
23935
24080
  if (typeof this.config.deviceId === "number") seedParts.push(String(this.config.deviceId));
23936
24081
  if (typeof this.config.tag === "string" && this.config.tag.length > 0) seedParts.push(this.config.tag);
23937
24082
  const seed = seedParts.length > 0 ? seedParts.join(":") : "anon";
23938
- this.frameRingSink = new DecoderFrameRingSink({
24083
+ this.frameRingSink = new _camstack_shm_ring.DecoderFrameRingSink({
23939
24084
  seed,
23940
24085
  logger: this.logger,
23941
24086
  nodeId: this.nodeId
@@ -25789,7 +25934,7 @@ var DecoderNodeAvAddon = class extends BaseAddon {
25789
25934
  return registrations;
25790
25935
  }
25791
25936
  this.ctx.logger.info("node-av decoder addon initialized", { meta: { selectedBackend: backend } });
25792
- const purged = purgeOrphanSegments(SEGMENT_NAME_PREFIX);
25937
+ const purged = purgeOrphanSegments(_camstack_shm_ring.SEGMENT_NAME_PREFIX);
25793
25938
  if (purged.removed > 0) this.ctx.logger.warn("node-av decoder: reclaimed orphaned shm segments at startup", { meta: {
25794
25939
  removed: purged.removed,
25795
25940
  scanned: purged.scanned
@@ -26050,7 +26195,7 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26050
26195
  return {
26051
26196
  sessionId: input.sessionId,
26052
26197
  ...stats,
26053
- budgetMb: RING_BUDGET_MB,
26198
+ budgetMb: _camstack_shm_ring.RING_BUDGET_MB,
26054
26199
  getFrameHits: this.getFrameHits,
26055
26200
  getFrameMisses: this.getFrameMisses
26056
26201
  };
@@ -26076,7 +26221,12 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26076
26221
  }
26077
26222
  };
26078
26223
  //#endregion
26079
- exports.DecoderFrameRingSink = DecoderFrameRingSink;
26224
+ Object.defineProperty(exports, "DecoderFrameRingSink", {
26225
+ enumerable: true,
26226
+ get: function() {
26227
+ return _camstack_shm_ring.DecoderFrameRingSink;
26228
+ }
26229
+ });
26080
26230
  exports.DecoderNodeAvAddon = DecoderNodeAvAddon;
26081
26231
  exports.NodeAvAudioCodecProvider = NodeAvAudioCodecProvider;
26082
26232
  exports.NodeAvAudioDecodeSession = NodeAvAudioDecodeSession;
@@ -26084,5 +26234,10 @@ exports.NodeAvAudioEncodeSession = NodeAvAudioEncodeSession;
26084
26234
  exports.NodeAvDecoderSession = NodeAvDecoderSession;
26085
26235
  exports.default = DecoderNodeAvAddon;
26086
26236
  exports.loadNodeAvRuntime = loadNodeAvRuntime;
26087
- exports.makeSegmentName = makeSegmentName;
26237
+ Object.defineProperty(exports, "makeSegmentName", {
26238
+ enumerable: true,
26239
+ get: function() {
26240
+ return _camstack_shm_ring.makeSegmentName;
26241
+ }
26242
+ });
26088
26243
  exports.peekNodeAvRuntime = peekNodeAvRuntime;