@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.mjs CHANGED
@@ -1,276 +1,6 @@
1
- import { FrameRingReaderCache, FrameRingWriter, MIN_RING_SLOTS, computeSegmentSize, computeSlotByteLength, createSegment, deriveSlotCount, unlinkSegment } from "@camstack/shm-ring";
1
+ import { DecoderFrameRingSink, FrameRingReaderCache, RING_BUDGET_MB, SEGMENT_NAME_PREFIX, makeSegmentName, unlinkSegment } from "@camstack/shm-ring";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { readdirSync } from "node:fs";
4
- //#region src/frame-ring-sink.ts
5
- /**
6
- * `DecoderFrameRingSink` — the decoder's shared-memory write side (Phase 5 / D9).
7
- *
8
- * When a decoder session is configured with `frameSink: 'shm'`, the decoder
9
- * **owns** the shared-memory ring segment for that stream: it creates the
10
- * segment on the first decoded frame (when the output geometry is known),
11
- * writes every subsequent decoded frame into the ring via a `FrameRingWriter`,
12
- * and closes + unlinks the segment when the session is destroyed.
13
- *
14
- * What leaves the decoder is no longer the pixel `Buffer` — it is a tiny,
15
- * serialisable `FrameHandle` (`FrameRingWriter.writeFrame`'s return value).
16
- * Same-host consumers (motion, detection, the WebRTC encoder) open the same
17
- * segment with a `FrameRingReader` and read the pixels zero-copy.
18
- *
19
- * ## Lazy segment creation
20
- *
21
- * The segment cannot be sized until the first frame: `slotByteLength` is
22
- * `width × height × bytesPerPixel`, and the output dimensions are only known
23
- * once the scaler has produced its first `dstFrame`. So `writeFrame` is a
24
- * no-op-until-armed: the first call sizes + creates the segment, every later
25
- * call writes into it.
26
- *
27
- * ## Resolution-change decision
28
- *
29
- * A live camera stream can change resolution mid-stream (the decoder's scaler
30
- * is rebuilt on a config toggle, or the source renegotiates). The slot is
31
- * sized for the **first** frame's geometry. A later frame that no longer fits
32
- * the slot triggers a **segment re-create**: the old segment is closed +
33
- * unlinked and a fresh, larger segment is created under a new generation-tagged
34
- * name. This is simpler and leak-free versus over-allocating slots for a
35
- * worst-case 4K frame on every stream; resolution changes on a live camera are
36
- * rare, and a brief gap while consumers re-open the segment is acceptable
37
- * (latest-wins — a missed frame is correct behaviour).
38
- */
39
- /**
40
- * Per-ring shared-memory budget (MB) — the slot count is derived per-resolution
41
- * from this budget via {@link deriveSlotCount}, so a 360p stream gets many
42
- * slots and a 4K stream a few, both inside the same memory footprint.
43
- *
44
- * Read ONCE at module load from `CAMSTACK_SHM_RING_BUDGET_MB`; a non-finite or
45
- * non-positive value falls back to the 16 MB default.
46
- *
47
- * The default is deliberately small (16 MB) so many concurrent per-camera rings
48
- * fit inside a bounded `/dev/shm`. The decoder output is capped at 640px wide, so
49
- * a slot is ≤ ~920 KB and 16 MB still yields ~17–70 latest-wins slots. A ring
50
- * segment larger than the container's `/dev/shm` tmpfs backing (Docker default is
51
- * only 64 MB) faults an **uncatchable SIGBUS** on write past `st_size` — the old
52
- * 128 MB default overflowed a 64 MB `/dev/shm` and crashed the decoder on 8MP
53
- * streams. Pair this with a `--shm-size` that scales with concurrent-decode load.
54
- */
55
- var RING_BUDGET_MB = (() => {
56
- const raw = Number(process.env["CAMSTACK_SHM_RING_BUDGET_MB"]);
57
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 16;
58
- })();
59
- /** {@link RING_BUDGET_MB} in bytes — the budget passed to `deriveSlotCount`. */
60
- var RING_BUDGET_BYTES = RING_BUDGET_MB * 1024 * 1024;
61
- /** A unique, stable shared-memory segment name for a decoder stream.
62
- *
63
- * macOS POSIX shm names are capped at ~31 characters (`PSHMNAMLEN`). A
64
- * `camstack.frames.<deviceId>.<streamId>` scheme overflows that for realistic
65
- * ids, so the sink uses a short, collision-resistant scheme instead:
66
- * `csf.<base36 hash>.<gen>`. The hash folds the device id, the session tag
67
- * and a per-process random salt; the generation suffix makes a re-created
68
- * segment (resolution change) a distinct name so a stale consumer mapping is
69
- * never silently reused.
70
- */
71
- /**
72
- * Shared prefix for every decoder shm segment name. Startup orphan reclamation
73
- * (`purgeOrphanSegments`) keys off this to find segments left behind by a
74
- * crashed prior instance.
75
- */
76
- var SEGMENT_NAME_PREFIX = "csf.";
77
- function makeSegmentName(seed, generation) {
78
- let hash = 5381;
79
- for (let i = 0; i < seed.length; i += 1) hash = (hash << 5) + hash + seed.charCodeAt(i) | 0;
80
- return `${SEGMENT_NAME_PREFIX}${(hash >>> 0).toString(36)}.${generation}`;
81
- }
82
- /**
83
- * The decoder-side owner of one stream's shared-memory frame ring.
84
- *
85
- * Not constructed until a session actually uses the shm sink; the segment
86
- * itself is created lazily on the first `writeFrame`.
87
- */
88
- var DecoderFrameRingSink = class {
89
- seed;
90
- logger;
91
- nodeId;
92
- segment = null;
93
- writer = null;
94
- segmentName = null;
95
- slotByteLength = 0;
96
- generation = 0;
97
- destroyed = false;
98
- /** Frames committed into the ring across this sink's lifetime (all generations). */
99
- framesWritten = 0;
100
- constructor(options) {
101
- const salt = Math.random().toString(36).slice(2, 8);
102
- this.seed = `${options.seed}.${salt}`;
103
- this.logger = options.logger;
104
- this.nodeId = options.nodeId;
105
- }
106
- /** Whether a segment has been created (i.e. at least one frame written). */
107
- get isArmed() {
108
- return this.writer !== null;
109
- }
110
- /** The current segment name, or `null` before the first frame. */
111
- get currentSegmentName() {
112
- return this.segmentName;
113
- }
114
- /**
115
- * Write one decoded frame into the ring and return its `FrameHandle`.
116
- *
117
- * On the first call (or after a geometry change that overflows the current
118
- * slot) the segment is created / re-created sized for this frame. Returns
119
- * `null` only when the sink has been destroyed.
120
- *
121
- * This is the copy-in convenience form (it copies `pixels` into the slot).
122
- * The decoder's hot path uses the zero-copy {@link beginFrame} /
123
- * {@link commitFrame} scatter-write pair instead — the scaler produces its
124
- * packed output directly into the slot, eliminating the write-side memcpy.
125
- */
126
- writeFrame(pixels, meta) {
127
- if (this.destroyed) return null;
128
- if (this.writer === null || computeSlotByteLength(meta.width, meta.height, meta.format) > this.slotByteLength) this.recreateSegment(computeSlotByteLength(meta.width, meta.height, meta.format));
129
- const writer = this.writer;
130
- if (writer === null) return null;
131
- const handle = writer.writeFrame(pixels, meta);
132
- this.framesWritten += 1;
133
- return handle;
134
- }
135
- /**
136
- * Reserve a ring slot for a frame of the given geometry — the **zero-copy**
137
- * scatter-write entry point (Phase 5 / D9 Task 7c).
138
- *
139
- * The segment is created / re-created here if this is the first frame or the
140
- * geometry overflows the current slot capacity, so the slot is correctly
141
- * sized before the caller fills it. The returned `buffer` is a writable view
142
- * **directly over the mapped segment** — the node-av scaler scatters its
143
- * packed output straight into it, with no intermediate copy. The caller MUST
144
- * call {@link commitFrame} with the returned `slot` once the slot is filled.
145
- *
146
- * Returns `null` when the sink is destroyed or the segment cannot be created.
147
- */
148
- beginFrame(width, height, format) {
149
- if (this.destroyed) return null;
150
- const requiredSlotBytes = computeSlotByteLength(width, height, format);
151
- if (this.writer === null || requiredSlotBytes > this.slotByteLength) this.recreateSegment(requiredSlotBytes);
152
- const writer = this.writer;
153
- if (writer === null) return null;
154
- const { slot, buffer } = writer.beginFrame();
155
- return {
156
- slot,
157
- buffer
158
- };
159
- }
160
- /**
161
- * Publish the frame whose slot was reserved by {@link beginFrame} and filled
162
- * in place by the caller. `slot` MUST be the value from the matching
163
- * `beginFrame`. Returns the published `FrameHandle`, or `null` if the sink
164
- * was destroyed (or the segment lost) between begin and commit.
165
- */
166
- commitFrame(slot, meta) {
167
- if (this.destroyed) return null;
168
- const writer = this.writer;
169
- if (writer === null) return null;
170
- const handle = writer.commitFrame(slot, meta);
171
- this.framesWritten += 1;
172
- return handle;
173
- }
174
- /**
175
- * Current shm ring usage — `null` until the first frame arms the segment.
176
- * Surfaced through `decoder.getShmStats` so a downstream consumer can
177
- * observe ring pressure (slot depth, byte budget, frames written).
178
- */
179
- getShmStats() {
180
- if (this.writer === null) return null;
181
- return {
182
- slotCount: this.writer.slotCount,
183
- slotByteLength: this.slotByteLength,
184
- segmentBytes: computeSegmentSize(this.writer.slotCount, this.slotByteLength),
185
- framesWritten: this.framesWritten
186
- };
187
- }
188
- /**
189
- * Abandon a slot reserved by {@link beginFrame} **without publishing it** —
190
- * the degenerate-path counterpart of {@link commitFrame}.
191
- *
192
- * A caller that reserved a slot but then could not produce valid pixels (no
193
- * decoded source planes, or the scaler threw) MUST call this instead of
194
- * `commitFrame`: it closes the open seqlock without advancing `writeIndex`,
195
- * so no reader ever sees the slot's uninitialised bytes as a real frame, and
196
- * no `FrameHandle` is handed downstream. `slot` MUST be the value from the
197
- * matching `beginFrame`. A no-op if the sink was destroyed (or the segment
198
- * lost) between begin and abort.
199
- */
200
- abortFrame(slot) {
201
- if (this.destroyed) return;
202
- const writer = this.writer;
203
- if (writer === null) return;
204
- writer.abortFrame(slot);
205
- }
206
- /** Close + unlink the segment. Idempotent. */
207
- destroy() {
208
- if (this.destroyed) return;
209
- this.destroyed = true;
210
- this.releaseSegment();
211
- }
212
- /**
213
- * Create a fresh segment sized for at least `slotByteLength` bytes per slot,
214
- * replacing any prior one. A re-create bumps the generation so the new
215
- * segment has a distinct name — a consumer holding the old mapping is never
216
- * silently handed a resized segment.
217
- */
218
- recreateSegment(slotByteLength) {
219
- this.releaseSegment();
220
- this.generation += 1;
221
- const name = makeSegmentName(this.seed, this.generation);
222
- const slotCount = deriveSlotCount(RING_BUDGET_BYTES, slotByteLength);
223
- if (slotCount === MIN_RING_SLOTS && MIN_RING_SLOTS * slotByteLength > RING_BUDGET_BYTES) this.logger.warn("decoder shm ring: budget too small for resolution — using MIN slots", { meta: {
224
- slotByteLength,
225
- budgetMb: RING_BUDGET_MB
226
- } });
227
- const totalBytes = computeSegmentSize(slotCount, slotByteLength);
228
- try {
229
- const segment = createSegment(name, totalBytes);
230
- this.segment = segment;
231
- this.segmentName = name;
232
- this.slotByteLength = slotByteLength;
233
- this.writer = new FrameRingWriter(segment.buffer, name, slotCount, slotByteLength, this.nodeId);
234
- this.logger.info("decoder shm ring: segment created", { meta: {
235
- segment: name,
236
- slotCount,
237
- slotByteLength,
238
- totalBytes,
239
- generation: this.generation
240
- } });
241
- } catch (err) {
242
- this.segment = null;
243
- this.writer = null;
244
- this.segmentName = null;
245
- this.slotByteLength = 0;
246
- this.logger.error("decoder shm ring: segment create failed", { meta: {
247
- segment: name,
248
- slotByteLength,
249
- error: err instanceof Error ? err.message : String(err)
250
- } });
251
- }
252
- }
253
- /** Unmap + unlink the current segment, if any. */
254
- releaseSegment() {
255
- const segment = this.segment;
256
- if (segment === null) return;
257
- this.segment = null;
258
- this.writer = null;
259
- const name = this.segmentName;
260
- this.segmentName = null;
261
- try {
262
- segment.close();
263
- segment.unlink();
264
- this.logger.info("decoder shm ring: segment released", { meta: { segment: name } });
265
- } catch (err) {
266
- this.logger.warn("decoder shm ring: segment release failed", { meta: {
267
- segment: name,
268
- error: err instanceof Error ? err.message : String(err)
269
- } });
270
- }
271
- }
272
- };
273
- //#endregion
274
4
  //#region ../../node_modules/zod/v4/core/core.js
275
5
  var _a$1;
276
6
  function $constructor(name, initializer, params) {
@@ -4900,7 +4630,7 @@ function _instanceof(cls, params = {}) {
4900
4630
  return inst;
4901
4631
  }
4902
4632
  //#endregion
4903
- //#region ../types/dist/sleep-CZDdRBua.mjs
4633
+ //#region ../types/dist/sleep-BC9Yqte7.mjs
4904
4634
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4905
4635
  EventCategory["SystemBoot"] = "system.boot";
4906
4636
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5086,6 +4816,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5086
4816
  */
5087
4817
  EventCategory["PipelineCameraUpdated"] = "pipeline.camera-updated";
5088
4818
  /**
4819
+ * The cluster camera-source OWNER changed (`clusterRoles.ingestNode`).
4820
+ * Emitted by addon-pipeline-orchestrator whenever it (re)derives node
4821
+ * capabilities — at boot, on agent online/offline, and on an ingest-node
4822
+ * flip. Carries the resolved `ownerNodeId`. The stream-broker consumes it to
4823
+ * keep its ingest-owner-gate decision current WITHOUT a per-`ensureBroker`
4824
+ * cross-process `getIngestOwner` query (push the authority's decision instead
4825
+ * of polling it on the hot path). Idempotent state — re-emitted on every
4826
+ * topology change, so a dropped event self-heals on the next one (plus the
4827
+ * broker's long backstop reconcile query).
4828
+ */
4829
+ EventCategory["PipelineIngestOwnerChanged"] = "pipeline.ingest-owner-changed";
4830
+ /**
5089
4831
  * Periodic snapshot of per-node pipeline-runner load
5090
4832
  * (`RunnerLocalLoad`). Emitted ~1Hz by every runner so UI dashboards
5091
4833
  * subscribe instead of polling `pipelineRunner.getLocalLoad`.
@@ -5609,10 +5351,6 @@ function hydrateField(field, values) {
5609
5351
  };
5610
5352
  }
5611
5353
  const rawValue = storedValue !== void 0 ? storedValue : defaultValue !== void 0 ? defaultValue : null;
5612
- if (field.type === "password") return {
5613
- ...field,
5614
- value: ""
5615
- };
5616
5354
  const value = field.type === "textarea" && field.isJson && rawValue !== null && typeof rawValue === "object" ? JSON.stringify(rawValue, null, 2) : rawValue;
5617
5355
  return {
5618
5356
  ...field,
@@ -6996,6 +6734,21 @@ function method(input, output, options) {
6996
6734
  timeoutMs: options?.timeoutMs
6997
6735
  };
6998
6736
  }
6737
+ /**
6738
+ * A wrapper/system-only method: served exclusively by the cap's system-level
6739
+ * provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
6740
+ * driver natives don't stub out a wrapper concern (e.g. a cross-device cache
6741
+ * overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
6742
+ */
6743
+ function systemMethod(input, output, options) {
6744
+ return {
6745
+ ...method(input, output, options),
6746
+ systemOnly: true
6747
+ };
6748
+ }
6749
+ var StaticDirOutputSchema$1 = object({ staticDir: string() });
6750
+ var VersionOutputSchema$1 = object({ version: string() });
6751
+ method(_void(), StaticDirOutputSchema$1), method(_void(), VersionOutputSchema$1);
6999
6752
  var StaticDirOutputSchema = object({ staticDir: string() });
7000
6753
  var VersionOutputSchema = object({ version: string() });
7001
6754
  method(_void(), StaticDirOutputSchema), method(_void(), VersionOutputSchema);
@@ -7177,6 +6930,36 @@ var ModelFormatsSchema = object({
7177
6930
  tflite: ModelFormatEntrySchema.optional(),
7178
6931
  pt: ModelFormatEntrySchema.optional()
7179
6932
  });
6933
+ /**
6934
+ * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
6935
+ * the reduced `PipelineModelOption` returned in `pipeline.getSchema()` so the
6936
+ * grouped Family→Tier→Variant picker renders identically in the config UI and
6937
+ * in the pipeline/device steppers. The flat `id` stays the source of truth for
6938
+ * resolution/download/persistence; this is a presentation overlay resolved back
6939
+ * to an `id`.
6940
+ */
6941
+ var ModelVariantGroupSchema = object({
6942
+ /** Top-level family, e.g. `yolo26` (later `d-fine`, `rf-detr`). */
6943
+ family: string(),
6944
+ /** Size within the family, e.g. `n` | `s` | `m` | `l`. */
6945
+ tier: string(),
6946
+ /** Quantization axis. Omit ⇒ the fp32 base build. */
6947
+ precision: _enum(["fp32", "int8"]).optional(),
6948
+ /**
6949
+ * Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
6950
+ * latency-optimized export (e.g. ReLU-activation variant) — the slot the
6951
+ * future performance variants plug into.
6952
+ */
6953
+ optimization: _enum(["standard", "fast"]).optional(),
6954
+ /**
6955
+ * Input-resolution axis (square input side, px). Omit ⇒ the family's native
6956
+ * resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
6957
+ * cheap latency lever — especially on Apple ANE and the Intel N100 — at a
6958
+ * small-object accuracy cost. Mirrors the model's `inputSize` but lifted onto
6959
+ * the group so the selector can offer it as a variant axis.
6960
+ */
6961
+ resolution: number().int().positive().optional()
6962
+ });
7180
6963
  var ModelCatalogEntrySchema = object({
7181
6964
  id: string(),
7182
6965
  name: string(),
@@ -7206,7 +6989,43 @@ var ModelCatalogEntrySchema = object({
7206
6989
  * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
7207
6990
  * Downloaded into the same modelsDir alongside the model file.
7208
6991
  */
7209
- extraFiles: array(ModelExtraFileSchema).readonly().optional()
6992
+ extraFiles: array(ModelExtraFileSchema).readonly().optional(),
6993
+ /**
6994
+ * LEGACY entry — retained in the catalog so a persisted operator selection
6995
+ * still RESOLVES (and can be re-activated), but hidden from the selectable
6996
+ * model list and excluded from the auto format-default pick. Set on the
6997
+ * superseded / consolidated models (older lineages, redundant fp16 IRs) so
6998
+ * the active lineup stays the coherent curated ladder without deleting a
6999
+ * model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
7000
+ * an explicit legacy id that has a build for the node's format.
7001
+ */
7002
+ legacy: boolean().optional(),
7003
+ /**
7004
+ * Measured quality/latency metadata — populated from the benchmark addon on
7005
+ * the real node classes. Absent = not yet measured (most entries today; the
7006
+ * catalog historically carried only `sizeMB`, a poor cross-architecture
7007
+ * speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
7008
+ */
7009
+ metrics: object({
7010
+ map50: number().optional(),
7011
+ p95LatencyMs: record(string(), number()).optional()
7012
+ }).optional(),
7013
+ /**
7014
+ * SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
7015
+ * YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
7016
+ * the retraining addon and any future commercial distribution.
7017
+ */
7018
+ license: string().optional(),
7019
+ /**
7020
+ * Variant-selector grouping. The UI groups models by `family` + `tier` and
7021
+ * offers `precision` / `optimization` as variant axes WITHIN a tier — so all
7022
+ * of a family's sizes and quantizations collapse into one grouped picker
7023
+ * instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
7024
+ * (legacy / custom models) — never shown in the grouped selector. The flat
7025
+ * `id` stays the source of truth for resolution/download/persistence; grouping
7026
+ * is a presentation overlay resolved back to an `id`.
7027
+ */
7028
+ group: ModelVariantGroupSchema.optional()
7210
7029
  });
7211
7030
  var ConvertTargetSchema = discriminatedUnion("format", [object({
7212
7031
  format: literal("openvino"),
@@ -7267,8 +7086,8 @@ var RecordingModeSchema = _enum([
7267
7086
  "onAudioThreshold"
7268
7087
  ]);
7269
7088
  /**
7270
- * First-class, authoritative per-camera storage mode — the netta choice the UI
7271
- * reads directly (never inferred from `rules`):
7089
+ * First-class, authoritative per-camera storage mode — the explicit choice the
7090
+ * UI reads directly (never inferred from `rules`):
7272
7091
  * - `off` — not recording.
7273
7092
  * - `events` — record only around triggers (motion / audio threshold),
7274
7093
  * with pre/post-buffer.
@@ -8916,26 +8735,13 @@ DeviceType.Light, method(object({
8916
8735
  percentage: number().min(0).max(100),
8917
8736
  lastChangedAt: number()
8918
8737
  });
8738
+ /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8919
8739
  var StreamFormatSchema = _enum([
8920
8740
  "webrtc",
8921
8741
  "hls",
8922
8742
  "mjpeg",
8923
8743
  "rtsp"
8924
8744
  ]);
8925
- var StreamInfoSchema = object({
8926
- streamId: string(),
8927
- format: StreamFormatSchema,
8928
- url: string().nullable(),
8929
- active: boolean()
8930
- });
8931
- method(object({
8932
- streamId: string(),
8933
- sourceUrl: string(),
8934
- codec: string().optional()
8935
- }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
8936
- streamId: string(),
8937
- format: StreamFormatSchema
8938
- }), string().nullable()), method(_void(), array(StreamInfoSchema));
8939
8745
  var RtspRestreamEntrySchema = object({
8940
8746
  brokerId: string(),
8941
8747
  url: string(),
@@ -9600,7 +9406,7 @@ var ConsumablesStatusSchema = object({
9600
9406
  })),
9601
9407
  lastChangedAt: number()
9602
9408
  });
9603
- 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({
9409
+ Object.values(DeviceType), method(object({
9604
9410
  deviceId: number().int().nonnegative(),
9605
9411
  key: string().min(1)
9606
9412
  }), _void(), {
@@ -10515,7 +10321,7 @@ var BoundingBoxSchema = object({
10515
10321
  w: number(),
10516
10322
  h: number()
10517
10323
  });
10518
- var SpatialDetectionSchema = object({
10324
+ object({
10519
10325
  class: string(),
10520
10326
  originalClass: string(),
10521
10327
  score: number(),
@@ -10650,7 +10456,6 @@ var PipelineDefaultStepSchema = lazy(() => object({
10650
10456
  enabled: boolean(),
10651
10457
  modelId: string(),
10652
10458
  children: array(PipelineDefaultStepSchema).readonly(),
10653
- engine: PipelineEngineChoiceSchema.optional(),
10654
10459
  group: string().optional(),
10655
10460
  settings: record(string(), unknown()).optional()
10656
10461
  }));
@@ -10675,7 +10480,9 @@ var PipelineModelOptionSchema = object({
10675
10480
  formats: record(string(), object({
10676
10481
  downloaded: boolean(),
10677
10482
  sizeMB: number()
10678
- }))
10483
+ })),
10484
+ group: ModelVariantGroupSchema.optional(),
10485
+ legacy: boolean().optional()
10679
10486
  });
10680
10487
  var ConfigFieldBridge = custom();
10681
10488
  var PipelineAddonSchemaSchema = object({
@@ -10689,6 +10496,7 @@ var PipelineAddonSchemaSchema = object({
10689
10496
  defaultModelId: string(),
10690
10497
  defaultModelIdByFormat: record(string(), string()).optional(),
10691
10498
  enabledByDefault: boolean().optional(),
10499
+ backfillIntoExistingOverrides: boolean().optional(),
10692
10500
  defaultConfidence: number(),
10693
10501
  group: string().optional(),
10694
10502
  configSchema: array(ConfigFieldBridge).readonly().optional()
@@ -10705,11 +10513,6 @@ var PipelineSchemaSchema = object({
10705
10513
  selectedEngine: PipelineEngineChoiceSchema,
10706
10514
  slots: array(PipelineSlotSchemaSchema).readonly()
10707
10515
  });
10708
- var DetectorOutputSchema = object({
10709
- detections: array(SpatialDetectionSchema).readonly(),
10710
- inferenceMs: number(),
10711
- modelId: string()
10712
- });
10713
10516
  var EngineProvisioningSchema = object({
10714
10517
  runtimeId: _enum([
10715
10518
  "onnx",
@@ -10726,15 +10529,42 @@ var EngineProvisioningSchema = object({
10726
10529
  ]),
10727
10530
  progress: number().optional(),
10728
10531
  error: string().optional(),
10729
- nextRetryAt: number().optional()
10532
+ nextRetryAt: number().optional(),
10533
+ /**
10534
+ * Gate A (config-correctness gate at engine change): human-readable
10535
+ * config issues surfaced EAGERLY when the node's engine changes — model
10536
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
10537
+ * has a <format> build"). Additive/optional: informational only, never
10538
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
10539
+ * Absent/empty when the node-default tree resolves cleanly.
10540
+ */
10541
+ configIssues: array(string()).optional()
10730
10542
  });
10731
10543
  var PipelineStepInputSchema = lazy(() => object({
10732
10544
  addonId: string(),
10733
- modelId: string(),
10545
+ modelId: string().optional(),
10734
10546
  enabled: boolean().default(true),
10735
10547
  children: array(PipelineStepInputSchema).optional(),
10736
10548
  settings: record(string(), unknown()).optional()
10737
10549
  }));
10550
+ var ModelSubstitutionSchema = object({
10551
+ addonId: string(),
10552
+ chosen: string(),
10553
+ running: string(),
10554
+ format: string()
10555
+ });
10556
+ var PipelineValidationIssueSchema = object({
10557
+ addonId: string(),
10558
+ kind: _enum(["unknown-addon", "no-format-build"]),
10559
+ detail: string()
10560
+ });
10561
+ var PipelineValidationResultSchema = object({
10562
+ ok: boolean(),
10563
+ issues: array(PipelineValidationIssueSchema).readonly(),
10564
+ substitutions: array(ModelSubstitutionSchema).readonly(),
10565
+ /** The node's `currentEngine.format` this validation ran against. */
10566
+ format: string()
10567
+ });
10738
10568
  var ReferenceImageEntrySchema = object({
10739
10569
  filename: string(),
10740
10570
  stepIds: array(string()).readonly().optional()
@@ -10805,7 +10635,13 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10805
10635
  })) }), object({ success: literal(true) }), {
10806
10636
  kind: "mutation",
10807
10637
  auth: "admin"
10808
- }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
10638
+ }), method(object({ nodeId: string() }), object({
10639
+ success: literal(true),
10640
+ clearedDevices: number()
10641
+ }), {
10642
+ kind: "mutation",
10643
+ auth: "admin"
10644
+ }), 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({
10809
10645
  name: string(),
10810
10646
  steps: array(PipelineTemplateStepSchema).readonly(),
10811
10647
  engine: PipelineEngineChoiceSchema
@@ -10822,10 +10658,6 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10822
10658
  modelId: string(),
10823
10659
  format: ModelFormatSchema$1
10824
10660
  }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
10825
- addonId: string(),
10826
- frame: FrameInputSchema,
10827
- config: record(string(), unknown()).optional()
10828
- }), DetectorOutputSchema), method(object({
10829
10661
  engine: PipelineEngineChoiceSchema.optional(),
10830
10662
  steps: array(PipelineStepInputSchema).min(1),
10831
10663
  frame: FrameInputSchema.optional(),
@@ -10971,6 +10803,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).read
10971
10803
  auth: "admin"
10972
10804
  }), object({ zones: array(ZoneSchema).readonly() });
10973
10805
  /**
10806
+ * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
10807
+ * decode worker resolves it against the RETAINED native frame's real pixel dims,
10808
+ * so the caller supplies only the detection-res bbox divided by the detection
10809
+ * dims — no native resolution to plumb.
10810
+ */
10811
+ var NativeCropBboxSchema = object({
10812
+ x: number(),
10813
+ y: number(),
10814
+ w: number(),
10815
+ h: number()
10816
+ });
10817
+ /** Result of a best-effort native-resolution crop (`getNativeCrop`). */
10818
+ var NativeCropResultSchema = object({
10819
+ /** Packed rgb (24-bit) pixels of the crop. */
10820
+ bytes: _instanceof(Uint8Array),
10821
+ width: number().int().positive(),
10822
+ height: number().int().positive()
10823
+ });
10824
+ /**
10974
10825
  * Per-camera tunable ranges + defaults. Single source of truth used
10975
10826
  * by both the Zod data schema (validation + default fallback) and
10976
10827
  * the device settings UI (slider min/max/step). Touch one place and
@@ -11065,6 +10916,13 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
11065
10916
  kind: literal("remote-restream"),
11066
10917
  /** The camera's source-owner node (slice 1: always the hub). */
11067
10918
  ownerNodeId: string(),
10919
+ /**
10920
+ * The owner's LAN-reachable host, resolved by the orchestrator from the
10921
+ * per-node `reachableHost` override (Cluster UI). When present the runner
10922
+ * dials THIS host for the owner's restream, in preference to the
10923
+ * `CAMSTACK_HUB_URL`-derived default. Absent → auto-detect fallback.
10924
+ */
10925
+ ownerReachableHost: string().optional(),
11068
10926
  /** Operator override for the owner host the runner dials. */
11069
10927
  hubHostnameOverride: string().optional()
11070
10928
  })]).describe("Per-camera frame-source mode for the runner (P2c)");
@@ -11073,13 +10931,11 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
11073
10931
  * specific runner instance via `attachCamera`. Carries everything the
11074
10932
  * runner needs to subscribe to the local broker and execute inference.
11075
10933
  *
11076
- * Stateless-pipeline model: the full pipeline content (`engine`, `steps`,
11077
- * optional `audio`) travels with the attach payload. The runner keeps it
11078
- * in RAM for the lifetime of the attach — on rebalance, edit, or
11079
- * restart the orchestrator re-sends the latest snapshot.
11080
- *
11081
- * `engine`/`steps`/`audio` are optional during the additive migration
11082
- * window; once orchestrator + UI are migrated they become required.
10934
+ * Stateless-pipeline model: the pipeline content (`steps`, optional
10935
+ * `audio`) travels with the attach payload. The runner keeps it in RAM
10936
+ * for the lifetime of the attach — on rebalance, edit, or restart the
10937
+ * orchestrator re-sends the latest snapshot. Engine is NOT carried: it is
10938
+ * node-local, resolved by the executing runner at dispatch time.
11083
10939
  */
11084
10940
  var RunnerCameraConfigSchema = object({
11085
10941
  deviceId: number(),
@@ -11130,14 +10986,11 @@ var RunnerCameraConfigSchema = object({
11130
10986
  */
11131
10987
  motionSources: MotionSourcesSchema.default(["analyzer"]),
11132
10988
  pipelineEnabled: boolean().default(true),
11133
- /** Engine choice for video steps (runtime+backend+format). */
11134
- engine: PipelineEngineChoiceSchema.optional(),
11135
10989
  /** Ordered tree of video steps. Absent → runner skips video detection. */
11136
10990
  steps: array(PipelineStepInputSchema).readonly().optional(),
11137
10991
  /** Audio classification branch. `enabled:false` disables, null skips. */
11138
10992
  audio: object({
11139
- engine: PipelineEngineChoiceSchema,
11140
- modelId: string(),
10993
+ modelId: string().optional(),
11141
10994
  enabled: boolean()
11142
10995
  }).nullable().optional(),
11143
10996
  /**
@@ -11224,7 +11077,11 @@ var RunnerLocalMetricsSchema = object({
11224
11077
  avgInferenceTimeMs: number(),
11225
11078
  queueDepth: number()
11226
11079
  });
11227
- 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());
11080
+ 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({
11081
+ handle: FrameHandleSchema,
11082
+ bbox: NativeCropBboxSchema,
11083
+ maxWidth: number().int().positive().optional()
11084
+ }), NativeCropResultSchema.nullable());
11228
11085
  object({
11229
11086
  detected: boolean(),
11230
11087
  /** Ms epoch of the last detected-true observation. Null if never detected. */
@@ -12518,7 +12375,9 @@ var AddonPageDeclarationSchema$1 = object({
12518
12375
  icon: string(),
12519
12376
  path: string(),
12520
12377
  remoteName: string(),
12521
- bundle: string()
12378
+ bundle: string(),
12379
+ section: string().optional(),
12380
+ sectionLabel: string().optional()
12522
12381
  });
12523
12382
  var AddonPageInfoSchema = object({
12524
12383
  addonId: string(),
@@ -12558,7 +12417,18 @@ var AddonPageDeclarationSchema = object({
12558
12417
  * the static-file route can compute an mtime-based cache-buster URL
12559
12418
  * without a separate filesystem stat.
12560
12419
  */
12561
- bundle: string()
12420
+ bundle: string(),
12421
+ /**
12422
+ * Sidebar section this page docks into. Well-known ids: `'detection'`,
12423
+ * `'cluster'`, `'administration'` — the page renders inside that group.
12424
+ * Any OTHER string creates (or joins) a custom section rendered after
12425
+ * the built-in groups; its label comes from `sectionLabel` (first
12426
+ * declaration wins), falling back to the id. Absent → the legacy
12427
+ * "Addon Pages" group.
12428
+ */
12429
+ section: string().optional(),
12430
+ /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
12431
+ sectionLabel: string().optional()
12562
12432
  });
12563
12433
  method(_void(), array(AddonPageDeclarationSchema).readonly());
12564
12434
  var AddonHttpRouteSchema = object({
@@ -12774,6 +12644,17 @@ var WidgetMetadataSchema = object({
12774
12644
  deviceContext: boolean().default(false),
12775
12645
  integrationContext: boolean().default(false)
12776
12646
  }),
12647
+ /**
12648
+ * Loadable BEFORE authentication. The normal widget registry listing
12649
+ * (`addon-widgets.listWidgets`) is auth-gated, so a pre-auth surface
12650
+ * (the login page) cannot discover a widget through it. A widget that
12651
+ * declares `preAuth: true` marks itself as safe to mount on a pre-auth
12652
+ * screen — it is surfaced through the PUBLIC `auth.listLoginMethods`
12653
+ * login-method contribution channel (see `login-method.cap.ts`) rather
12654
+ * than the authenticated registry, and its bundle is served by the
12655
+ * public `/api/addon-widgets/:addonId/*` static route. Defaults false.
12656
+ */
12657
+ preAuth: boolean().optional().default(false),
12777
12658
  /** Dashboard placement HINTS (operator can override per instance). */
12778
12659
  defaultSize: WidgetSizeEnum.default("md"),
12779
12660
  allowedSizes: array(WidgetSizeEnum).readonly().default([
@@ -13112,6 +12993,66 @@ method(object({
13112
12993
  password: string()
13113
12994
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
13114
12995
  /**
12996
+ * `login-method` — collection cap through which auth addons contribute
12997
+ * their pre-auth login surfaces to the login page. This is the SINGLE,
12998
+ * generic mechanism that supersedes the dead `auth.listProviders` reader:
12999
+ * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
13000
+ * `login-method` provider and the PUBLIC `auth.listLoginMethods`
13001
+ * procedure aggregates them for the unauthenticated login page.
13002
+ *
13003
+ * A contribution is a discriminated union on `kind`:
13004
+ *
13005
+ * - `redirect` — a declarative button. The login page renders a generic
13006
+ * button that navigates to `startUrl` (an addon-owned HTTP route).
13007
+ * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
13008
+ * ZERO shell-side JS. A future SSO addon plugs in the same way — the
13009
+ * login page needs NO change.
13010
+ *
13011
+ * - `widget` — a Module-Federation widget the login page mounts (via
13012
+ * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
13013
+ * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
13014
+ * addon bundle. The referenced widget also declares `preAuth: true` in
13015
+ * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
13016
+ * stamps a public `bundleUrl` from `addonId` + `bundle`.
13017
+ *
13018
+ * Every contribution carries a `stage`:
13019
+ * - `primary` — shown on the first credentials screen (OIDC /
13020
+ * magic-link buttons; a future usernameless passkey).
13021
+ * - `second-factor` — shown AFTER the password leg, gated on the
13022
+ * returned `factors` (passkey-as-2FA today).
13023
+ *
13024
+ * `mount: skip` — the cap is read server-side by the core auth router
13025
+ * (`registry.getCollection('login-method')`), never mounted as its own
13026
+ * tRPC router.
13027
+ */
13028
+ /** When a login method renders in the two-phase login flow. */
13029
+ var LoginStageEnum = _enum(["primary", "second-factor"]);
13030
+ /** One login-method contribution — redirect button OR pre-auth widget. */
13031
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13032
+ kind: literal("redirect"),
13033
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13034
+ id: string(),
13035
+ /** Operator-facing button label. */
13036
+ label: string(),
13037
+ /** lucide-react icon name. */
13038
+ icon: string().optional(),
13039
+ /** Addon-owned HTTP route the button navigates to (GET). */
13040
+ startUrl: string(),
13041
+ stage: LoginStageEnum
13042
+ }), object({
13043
+ kind: literal("widget"),
13044
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13045
+ id: string(),
13046
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
13047
+ addonId: string(),
13048
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13049
+ bundle: string(),
13050
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
13051
+ remote: WidgetRemoteSchema,
13052
+ stage: LoginStageEnum
13053
+ })]);
13054
+ method(_void(), array(LoginMethodContributionSchema).readonly());
13055
+ /**
13115
13056
  * Orchestrator-side destination metadata. The orchestrator computes
13116
13057
  * `id = <addonId>:<subId>` from its provider lookup so consumers
13117
13058
  * (admin UI, restore flow) see one canonical key.
@@ -15308,7 +15249,17 @@ var TrackSchema = object({
15308
15249
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
15309
15250
  totalDistance: number(),
15310
15251
  state: TrackStateSchema,
15311
- active: boolean()
15252
+ active: boolean(),
15253
+ /** Deterministic key-event importance score in [0,1] (server-computed at
15254
+ * track expiry, recomputed on late label). Absent on legacy rows written
15255
+ * before scoring shipped — consumers degrade to absence / compute-on-read. */
15256
+ importance: number().optional(),
15257
+ /** Id of the track's highest-confidence ObjectEvent (its representative
15258
+ * "best" frame). Absent when the track produced no object events. */
15259
+ bestEventId: string().optional(),
15260
+ /** Tag of the importance sub-signal that dominated the score
15261
+ * (identity|dwell|proximity|class|confidence|travel|zone). */
15262
+ importanceReason: string().optional()
15312
15263
  });
15313
15264
  var BaseEventFields = {
15314
15265
  id: string(),
@@ -15373,8 +15324,18 @@ var ObjectEventSchema = object({
15373
15324
  frameHeight: number().optional(),
15374
15325
  /** MediaStore key for the crop attached to this event (if any). */
15375
15326
  mediaKey: string().optional(),
15327
+ /** Design B: MediaStore key of the track's native-resolution key frame (the
15328
+ * best-detection full frame). Resolve via the event-media data-plane
15329
+ * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`) for a detail view that
15330
+ * draws `bbox` over the native frame. Absent on legacy rows / non-decoded
15331
+ * sources — consumers fall back to `mediaKey` (the tight crop). */
15332
+ keyFrameMediaKey: string().optional(),
15376
15333
  /** Populated by B5 (recording playback URL for this event). */
15377
- mediaUrl: string().optional()
15334
+ mediaUrl: string().optional(),
15335
+ /** The parent track's key-event importance [0,1], propagated to every object
15336
+ * event of the track (so an event row can be sorted by importance without a
15337
+ * track join). Absent on legacy rows / before the track was scored. */
15338
+ importance: number().optional()
15378
15339
  });
15379
15340
  var AudioEventSchema = object({
15380
15341
  ...BaseEventFields,
@@ -15398,7 +15359,8 @@ var MediaFileKindEnum = _enum([
15398
15359
  "fullFrame",
15399
15360
  "fullFrameBoxed",
15400
15361
  "faceCrop",
15401
- "plateCrop"
15362
+ "plateCrop",
15363
+ "keyFrame"
15402
15364
  ]);
15403
15365
  var MediaFileSchema = object({
15404
15366
  key: string(),
@@ -15419,6 +15381,32 @@ var DeviceEventQueryInput = object({
15419
15381
  projection: _enum(["full", "slim"]).optional()
15420
15382
  });
15421
15383
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
15384
+ var KeyEventQueryInput = object({
15385
+ deviceId: number(),
15386
+ /** Window lower bound (track firstSeen ≥ since). */
15387
+ since: number(),
15388
+ /** Window upper bound (track firstSeen ≤ until). */
15389
+ until: number(),
15390
+ limit: number().int().min(1).max(200).default(50),
15391
+ /** Drop tracks scoring below this importance. */
15392
+ minImportance: number().min(0).max(1).optional(),
15393
+ /** Restrict to a single class (e.g. 'person'). */
15394
+ classFilter: string().optional()
15395
+ });
15396
+ var KeyEventSchema = object({
15397
+ /** The representative event id (the track's best ObjectEvent, else its trackId). */
15398
+ id: string(),
15399
+ trackId: string(),
15400
+ /** Track start time (firstSeen). */
15401
+ timestamp: number(),
15402
+ className: string(),
15403
+ label: string().optional(),
15404
+ importance: number(),
15405
+ /** Highest-confidence ObjectEvent id for the track (empty when none). */
15406
+ bestEventId: string(),
15407
+ /** Track lifetime in ms (lastSeen - firstSeen). */
15408
+ windowMs: number().optional()
15409
+ });
15422
15410
  var TrackedDetectionSchema = object({
15423
15411
  trackId: string(),
15424
15412
  className: string(),
@@ -15448,7 +15436,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15448
15436
  }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
15449
15437
  kind: "mutation",
15450
15438
  auth: "admin"
15451
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({
15439
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
15452
15440
  deviceId: number(),
15453
15441
  since: number(),
15454
15442
  until: number(),
@@ -15493,11 +15481,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15493
15481
  timestamp: number()
15494
15482
  });
15495
15483
  var CameraPipelineConfigSchema = object({
15496
- engine: PipelineEngineChoiceSchema,
15484
+ engine: PipelineEngineChoiceSchema.optional(),
15497
15485
  steps: array(PipelineStepInputSchema).readonly(),
15498
15486
  audio: object({
15499
- engine: PipelineEngineChoiceSchema,
15500
- modelId: string(),
15487
+ engine: PipelineEngineChoiceSchema.optional(),
15488
+ modelId: string().optional(),
15501
15489
  enabled: boolean(),
15502
15490
  settings: record(string(), unknown()).readonly().optional()
15503
15491
  }).nullable().optional()
@@ -15512,7 +15500,7 @@ var PipelineTemplateSchema = object({
15512
15500
  });
15513
15501
  var AgentAddonConfigSchema = object({
15514
15502
  enabled: boolean(),
15515
- modelId: string(),
15503
+ modelId: string().optional(),
15516
15504
  settings: record(string(), unknown()).readonly()
15517
15505
  });
15518
15506
  var AgentPipelineSettingsSchema = object({
@@ -15522,12 +15510,25 @@ var AgentPipelineSettingsSchema = object({
15522
15510
  detectWeight: number().positive().optional(),
15523
15511
  /** Node is eligible to run the detection pipeline (decode + inference). */
15524
15512
  detect: boolean().optional(),
15525
- /** Node is eligible to host decoder sessions. */
15513
+ /**
15514
+ * DEPRECATED AND IGNORED. Decode is always co-located with its frame
15515
+ * consumer, so decode eligibility IS detect eligibility. Kept optional in
15516
+ * the schema ONLY so persisted stores written before the removal still
15517
+ * parse — no code reads it and no write path emits it.
15518
+ */
15526
15519
  decode: boolean().optional(),
15527
15520
  /** Node is eligible to run audio-analyzer sessions. */
15528
15521
  audio: boolean().optional(),
15529
15522
  /** Node is eligible to be the ingest / source-owner (serve the restream). */
15530
- ingest: boolean().optional()
15523
+ ingest: boolean().optional(),
15524
+ /**
15525
+ * Operator override for the LAN host a cross-node decoder dials to reach
15526
+ * THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
15527
+ * falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
15528
+ * it already uses to reach the hub). Set this only when the auto-detected
15529
+ * address is wrong (multi-homed host, NAT, custom interface).
15530
+ */
15531
+ reachableHost: string().optional()
15531
15532
  });
15532
15533
  var CameraPipelineForAgentSchema = object({
15533
15534
  steps: array(PipelineStepInputSchema).readonly(),
@@ -15575,25 +15576,6 @@ var PipelineAssignmentSchema = object({
15575
15576
  assignedAt: number()
15576
15577
  });
15577
15578
  /**
15578
- * Decoder placement record. Symmetric to `PipelineAssignmentSchema` but for
15579
- * the decoder-node placement domain (`balanceDecoder` decision: manual pin
15580
- * → co-located with pipeline → capacity).
15581
- */
15582
- var DecoderAssignmentSchema = object({
15583
- deviceId: number(),
15584
- /** Moleculer node id of the decoder provider currently responsible for this camera. */
15585
- decoderNodeId: string(),
15586
- /** True when the assignment was set manually via `assignDecoder`, false when chosen by the balancer. */
15587
- pinned: boolean(),
15588
- /** Why this assignment was made — useful for debugging the decoder balancer. */
15589
- reason: _enum([
15590
- "manual",
15591
- "co-located",
15592
- "capacity",
15593
- "hardware-affinity"
15594
- ])
15595
- });
15596
- /**
15597
15579
  * Per-agent load summary surfaced to the load balancer + dashboards.
15598
15580
  * Aggregated from each runner's `getLocalLoad` cap call.
15599
15581
  */
@@ -15633,6 +15615,15 @@ var GlobalMetricsSchema = object({
15633
15615
  * capability providers.
15634
15616
  */
15635
15617
  var CapabilityBindingsSchema = record(string(), string());
15618
+ /**
15619
+ * The cluster's single camera-source owner (`clusterRoles.ingestNode`) plus
15620
+ * its LAN-reachable host, if one is registered. See `getIngestOwner`.
15621
+ */
15622
+ var IngestOwnerSchema = object({
15623
+ ownerNodeId: string(),
15624
+ reachableHost: string().optional(),
15625
+ configIssue: string().optional()
15626
+ });
15636
15627
  /** Source block — always present; derives from the stream catalog. */
15637
15628
  var CameraSourceStatusSchema = object({ streams: array(object({
15638
15629
  camStreamId: string(),
@@ -15647,6 +15638,14 @@ var CameraAssignmentStatusSchema = object({
15647
15638
  detectionNodeId: string().nullable(),
15648
15639
  decoderNodeId: string().nullable(),
15649
15640
  audioNodeId: string().nullable(),
15641
+ /**
15642
+ * The node that OWNS this camera's physical source pull (dials the RTSP and
15643
+ * hosts the broker/restream) — the cluster ingest owner today
15644
+ * (`clusterRoles.ingestNode`), per-camera once source assignment lands. Lets
15645
+ * the UI show WHERE a camera is sourced without SSH/logs, and is the node the
15646
+ * broker block below was read from (pinned). Nullable only pre-wiring.
15647
+ */
15648
+ sourceNodeId: string().nullable(),
15650
15649
  pinned: object({
15651
15650
  detection: boolean(),
15652
15651
  decoder: boolean(),
@@ -15779,16 +15778,7 @@ method(object({
15779
15778
  }), object({ success: literal(true) }), {
15780
15779
  kind: "mutation",
15781
15780
  auth: "admin"
15782
- }), method(object({
15783
- deviceId: number(),
15784
- nodeId: string()
15785
- }), _void(), {
15786
- kind: "mutation",
15787
- auth: "admin"
15788
- }), method(object({ deviceId: number() }), _void(), {
15789
- kind: "mutation",
15790
- auth: "admin"
15791
- }), method(_void(), array(DecoderAssignmentSchema).readonly()), method(object({
15781
+ }), method(_void(), IngestOwnerSchema), method(object({
15792
15782
  deviceId: number(),
15793
15783
  nodeId: string()
15794
15784
  }), object({ success: literal(true) }), {
@@ -15809,10 +15799,7 @@ method(object({
15809
15799
  nodeId: string(),
15810
15800
  pinned: boolean(),
15811
15801
  assignedAt: number()
15812
- }))), method(object({
15813
- deviceId: number(),
15814
- pipelineNodeId: string().optional()
15815
- }), DecoderAssignmentSchema), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
15802
+ }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
15816
15803
  nodeId: string(),
15817
15804
  settings: AgentPipelineSettingsSchema
15818
15805
  })).readonly()), method(object({
@@ -15842,12 +15829,26 @@ method(object({
15842
15829
  }), method(object({
15843
15830
  agentNodeId: string(),
15844
15831
  detect: boolean().nullable().optional(),
15845
- decode: boolean().nullable().optional(),
15846
15832
  audio: boolean().nullable().optional(),
15847
15833
  ingest: boolean().nullable().optional()
15848
15834
  }), object({ success: literal(true) }), {
15849
15835
  kind: "mutation",
15850
15836
  auth: "admin"
15837
+ }), method(object({
15838
+ agentNodeId: string(),
15839
+ reachableHost: string().nullable()
15840
+ }), object({ success: literal(true) }), {
15841
+ kind: "mutation",
15842
+ auth: "admin"
15843
+ }), method(object({ agentNodeId: string() }), object({
15844
+ success: literal(true),
15845
+ /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
15846
+ effectiveModelId: string().nullable(),
15847
+ /** Number of cameras whose node-scoped overrides were cleared. */
15848
+ clearedCameraOverrides: number()
15849
+ }), {
15850
+ kind: "mutation",
15851
+ auth: "admin"
15851
15852
  }), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
15852
15853
  deviceId: number(),
15853
15854
  addonId: string(),
@@ -15892,22 +15893,131 @@ method(object({
15892
15893
  kind: "mutation",
15893
15894
  auth: "admin"
15894
15895
  });
15895
- var RegisteredStreamSchema = object({
15896
- streamId: string(),
15897
- label: string().optional(),
15898
- codec: string(),
15899
- type: _enum(["video", "audio"]),
15900
- sourceUrl: string()
15896
+ /**
15897
+ * server-management — per-NODE singleton capability for a node's ROOT
15898
+ * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
15899
+ * agents).
15900
+ *
15901
+ * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
15902
+ * on agents) carries the whole software stack in its npm dep tree, so ONE
15903
+ * version describes the node. Updates install into
15904
+ * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
15905
+ * starter (probation boot + auto-rollback to N-1).
15906
+ *
15907
+ * Providers:
15908
+ * - HUB: `ServerUpdateService` behind the `server-provided` mount
15909
+ * (`buildServerProviders` in trpc.router.ts) — the default target for
15910
+ * unpinned calls.
15911
+ * - AGENT: `AgentUpdateService` registered by the agent bootstrap under
15912
+ * the synthetic `agent-runtime` addonId and declared in the agent's
15913
+ * `$hub.registerNode` manifest.
15914
+ *
15915
+ * Node routing: singleton caps get the codegen/runtime-builder `nodeId`
15916
+ * injection on every method — `input.nodeId` (or `nodePin(nodeId)` from the
15917
+ * SDK) routes the call to that node's provider via the standard remote
15918
+ * proxy (`createCapabilityProxy` → `$agent-cap-fwd` → the agent's
15919
+ * in-process provider lookup). No `nodeId` → the hub's own provider.
15920
+ *
15921
+ * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md
15922
+ */
15923
+ /**
15924
+ * Where the running hub's code was loaded from:
15925
+ * - `workspace` — dev checkout (tsx / workspace dist); the starter defers to
15926
+ * plain resolution and runtime updates are refused.
15927
+ * - `baked` — the immutable image seed closure (no data-dir root active).
15928
+ * - `data-root` — the runtime-updatable `<dataDir>/server-root` closure.
15929
+ */
15930
+ var ServerBootModeSchema = _enum([
15931
+ "workspace",
15932
+ "baked",
15933
+ "data-root"
15934
+ ]);
15935
+ /**
15936
+ * Update lifecycle state:
15937
+ * - `idle` / `checking` / `staging` — steady / in-flight registry work.
15938
+ * - `pending-restart` — a version is staged and the node has NOT yet
15939
+ * restarted onto it (still running the OLD version).
15940
+ * - `awaiting-confirmation` — the node HAS restarted onto the staged version
15941
+ * (it is the active probation boot) and is waiting to confirm boot-health.
15942
+ * Apply/rollback are refused in this state and the node must NOT be
15943
+ * manually restarted, or the probation boot auto-rolls-back.
15944
+ */
15945
+ var ServerUpdateStateSchema = _enum([
15946
+ "idle",
15947
+ "checking",
15948
+ "staging",
15949
+ "pending-restart",
15950
+ "awaiting-confirmation"
15951
+ ]);
15952
+ var ServerRollbackInfoSchema = object({
15953
+ /** The version that failed (or was manually rolled back). */
15954
+ fromVersion: string(),
15955
+ /** The version rolled back to; null = the baked seed. */
15956
+ toVersion: string().nullable(),
15957
+ atMs: number(),
15958
+ reason: string()
15901
15959
  });
15902
- var ExposedResourceSchema = object({
15903
- streamId: string(),
15904
- format: string(),
15905
- value: string()
15960
+ var ServerPackageStatusSchema = object({
15961
+ /** Root package name (`@camstack/server` on the hub). */
15962
+ packageName: string(),
15963
+ /** Version of the code the running process ACTUALLY loaded. */
15964
+ runningVersion: string().nullable(),
15965
+ /** Node.js runtime version the node's process runs on (`process.versions.node`). */
15966
+ nodeRuntimeVersion: string().nullable(),
15967
+ /** Active data-dir root version; null when booted from seed/workspace. */
15968
+ activeVersion: string().nullable(),
15969
+ /** N-1 version kept for rollback; null when no previous version exists. */
15970
+ previousVersion: string().nullable(),
15971
+ /** Version of the immutable baked seed closure (image fallback). */
15972
+ seedVersion: string().nullable(),
15973
+ /** Latest registry version from the most recent check (null = never checked). */
15974
+ latestVersion: string().nullable(),
15975
+ updateAvailable: boolean(),
15976
+ bootMode: ServerBootModeSchema,
15977
+ updateState: ServerUpdateStateSchema,
15978
+ /** Version staged + awaiting its probation boot, when one is pending. */
15979
+ pendingVersion: string().nullable(),
15980
+ /** Set when the last freshly-activated version failed its boot health-check. */
15981
+ rolledBack: ServerRollbackInfoSchema.nullable(),
15982
+ /**
15983
+ * True when `server-root/state.json` EXISTS but is unreadable/corrupt — the
15984
+ * hub is running from the baked seed (or workspace) while installed data-dir
15985
+ * versions are being IGNORED. Surfaced as a warning in the UI.
15986
+ */
15987
+ stateFileCorrupt: boolean(),
15988
+ lastCheckedAtMs: number().nullable()
15989
+ });
15990
+ var ServerUpdateCheckResultSchema = object({
15991
+ packageName: string(),
15992
+ runningVersion: string().nullable(),
15993
+ latestVersion: string().nullable(),
15994
+ updateAvailable: boolean(),
15995
+ checkedAtMs: number(),
15996
+ /** Non-null when the registry lookup failed (offline, bad registry, …). */
15997
+ error: string().nullable()
15998
+ });
15999
+ var ServerUpdateActionResultSchema = object({
16000
+ accepted: boolean(),
16001
+ targetVersion: string().nullable(),
16002
+ /** True when a graceful restart was scheduled to apply the change. */
16003
+ restarting: boolean(),
16004
+ message: string()
16005
+ });
16006
+ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
16007
+ kind: "mutation",
16008
+ auth: "admin"
16009
+ }), method(object({
16010
+ /** Explicit target version; omitted = latest from the registry. */
16011
+ version: string().optional() }), ServerUpdateActionResultSchema, {
16012
+ kind: "mutation",
16013
+ auth: "admin"
16014
+ }), method(_void(), ServerUpdateActionResultSchema, {
16015
+ kind: "mutation",
16016
+ auth: "admin"
16017
+ }), method(_void(), ServerUpdateActionResultSchema, {
16018
+ kind: "mutation",
16019
+ auth: "admin"
15906
16020
  });
15907
- method(object({
15908
- deviceId: number(),
15909
- streams: array(RegisteredStreamSchema).readonly()
15910
- }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(ExposedResourceSchema).readonly());
15911
16021
  /**
15912
16022
  * Query filter for settings-store collections.
15913
16023
  */
@@ -16060,9 +16170,9 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
16060
16170
  /**
16061
16171
  * A single device snapshot returned as base64 JPEG/PNG.
16062
16172
  *
16063
- * Shared with the `snapshot-provider` collection cap the orchestrator
16064
- * receives the same shape from each native provider and from the
16065
- * broker-based fallback.
16173
+ * The `SnapshotAddon` wrapper returns this shape whether the frame came from
16174
+ * the device-native provider (onboard capture) or from the stream-broker
16175
+ * prebuffer fallback.
16066
16176
  */
16067
16177
  var SnapshotImageSchema = object({
16068
16178
  base64: string(),
@@ -16093,11 +16203,12 @@ DeviceType.Camera, method(object({
16093
16203
  }), SnapshotImageSchema.nullable()), method(object({ deviceId: number() }), _void(), {
16094
16204
  kind: "mutation",
16095
16205
  auth: "admin"
16096
- });
16097
- method(object({ deviceId: number() }), boolean()), method(object({
16206
+ }), systemMethod(object({ deviceIds: array(number()).min(1).max(200) }), array(object({
16098
16207
  deviceId: number(),
16099
- streamId: string().optional()
16100
- }), SnapshotImageSchema.nullable());
16208
+ lastCapturedAt: number().nullable(),
16209
+ cacheAgeMs: number().nullable(),
16210
+ etag: string().nullable()
16211
+ })));
16101
16212
  /**
16102
16213
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
16103
16214
  * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
@@ -16348,10 +16459,32 @@ method(_void(), array(TurnServerSchema).readonly());
16348
16459
  * b. `finishAuthentication({userId, response})` → server verifies
16349
16460
  * the assertion, bumps the credential counter, returns ok.
16350
16461
  *
16462
+ * 2b. Usernameless (discoverable-credential) authentication — the
16463
+ * passkey IS the primary factor, no password leg:
16464
+ * a. `beginDiscoverableAuthentication({})` → assertion options with
16465
+ * EMPTY `allowCredentials` (the browser offers every resident
16466
+ * passkey it holds for this RP) + `userVerification: 'required'`
16467
+ * (the passkey replaces both factors, so UV is mandatory).
16468
+ * The challenge is stored server-side, NOT bound to any user.
16469
+ * b. `finishDiscoverableAuthentication({response})` → the provider
16470
+ * resolves the credential by the response's credential id,
16471
+ * verifies the assertion against the stored challenge + that
16472
+ * credential's public key/counter, and returns the OWNING
16473
+ * `userId` — the caller (core auth router) mints the session.
16474
+ *
16351
16475
  * 3. Management:
16352
16476
  * - `listPasskeys({userId})` — enumerate user's enrolled credentials.
16353
16477
  * - `removePasskey({userId, credentialId})` — revoke one credential.
16354
16478
  *
16479
+ * 4. Second-factor preference (opt-in, default OFF):
16480
+ * Enrolling a passkey only enables passkey-FIRST sign-in. It is
16481
+ * demanded as a second factor after a password login ONLY when the
16482
+ * user explicitly opts in via `setSecondFactorPreference`.
16483
+ * - `getSecondFactorPreference({userId})` → `{ enabled }` (missing
16484
+ * row ⇒ `enabled: false`).
16485
+ * - `setSecondFactorPreference({userId, enabled})` — persisted by
16486
+ * the providing addon beside its credentials.
16487
+ *
16355
16488
  * Challenges are short-lived (5 min, in-memory). The cap is internal —
16356
16489
  * the admin-ui composes the begin/finish round-trip and never exposes
16357
16490
  * the cap to non-admins.
@@ -16394,6 +16527,17 @@ method(object({
16394
16527
  }), object({ verified: boolean() }), {
16395
16528
  kind: "mutation",
16396
16529
  access: "view"
16530
+ }), method(object({}), object({ optionsJSON: record(string(), unknown()) }), {
16531
+ kind: "mutation",
16532
+ access: "view"
16533
+ }), method(object({
16534
+ /** AuthenticationResponseJSON from the browser. */
16535
+ response: record(string(), unknown()) }), object({
16536
+ verified: boolean(),
16537
+ userId: string().nullable()
16538
+ }), {
16539
+ kind: "mutation",
16540
+ access: "view"
16397
16541
  }), method(object({ userId: string() }), array(PasskeySummarySchema), { auth: "admin" }), method(object({
16398
16542
  userId: string(),
16399
16543
  credentialId: string()
@@ -16401,6 +16545,13 @@ method(object({
16401
16545
  kind: "mutation",
16402
16546
  auth: "admin",
16403
16547
  access: "delete"
16548
+ }), method(object({ userId: string() }), object({ enabled: boolean() }), { auth: "admin" }), method(object({
16549
+ userId: string(),
16550
+ enabled: boolean()
16551
+ }), object({ success: literal(true) }), {
16552
+ kind: "mutation",
16553
+ auth: "admin",
16554
+ access: "create"
16404
16555
  });
16405
16556
  /**
16406
16557
  * `videoclips` — the unified, navigable-clip surface for a camera.
@@ -16458,9 +16609,10 @@ method(object({
16458
16609
  auth: "admin"
16459
16610
  });
16460
16611
  /**
16461
- * Optional client-side hints sent at session creation to help the
16462
- * provider pick the best native source. All fields are optional —
16463
- * a viewer that knows nothing still gets a sane default.
16612
+ * Optional client-side hints sent at session creation to help the provider
16613
+ * pick the best native source. All fields optional — a viewer that knows
16614
+ * nothing still gets a sane default. (Relocated from the retired `webrtc`
16615
+ * collection cap; this `webrtc-session` cap is the live signaling surface.)
16464
16616
  */
16465
16617
  var webrtcClientHintsSchema = object({
16466
16618
  viewportWidth: number().int().positive().optional(),
@@ -16471,22 +16623,6 @@ var webrtcClientHintsSchema = object({
16471
16623
  /** Hard tier override; takes precedence over scoring when registered. */
16472
16624
  prefersTier: string().optional()
16473
16625
  }).partial();
16474
- method(object({
16475
- streamId: string(),
16476
- sdpOffer: string()
16477
- }), string(), { kind: "mutation" }), method(object({ streamId: string() }), boolean()), method(object({
16478
- streamId: string(),
16479
- codec: string()
16480
- }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
16481
- streamId: string(),
16482
- hints: webrtcClientHintsSchema.optional()
16483
- }), object({
16484
- sessionId: string(),
16485
- sdpOffer: string()
16486
- }), { kind: "mutation" }), method(object({
16487
- sessionId: string(),
16488
- sdpAnswer: string()
16489
- }), _void(), { kind: "mutation" }), method(object({ sessionId: string() }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), boolean());
16490
16626
  /**
16491
16627
  * Discriminated target for a WebRTC session. The client sends this
16492
16628
  * structured object instead of building / parsing brokerId strings;
@@ -17217,7 +17353,17 @@ var FaceInfoSchema = object({
17217
17353
  recognizedIdentityId: string().optional(),
17218
17354
  identityName: string().optional(),
17219
17355
  assigned: boolean(),
17220
- base64: string().optional()
17356
+ base64: string().optional(),
17357
+ /** Design B: the face bbox (pixel space) on the key frame — lets a detail
17358
+ * view draw the box over the native `keyFrameMediaKey` frame. Absent on
17359
+ * legacy rows written before design B. */
17360
+ faceBbox: BoundingBoxSchema.optional(),
17361
+ /** Design B: MediaStore key of the track's native-resolution key frame.
17362
+ * Fetch the native JPEG via the event-media data-plane
17363
+ * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`). Absent when the
17364
+ * track produced no key frame (e.g. native/onboard source) — the UI falls
17365
+ * back to the inline `base64` face crop. */
17366
+ keyFrameMediaKey: string().optional()
17221
17367
  });
17222
17368
  var FaceFilterEnum = _enum([
17223
17369
  "unassigned",
@@ -17914,6 +18060,16 @@ var TopologyCategorySchema = object({
17914
18060
  healthy: number(),
17915
18061
  addons: array(TopologyCategoryAddonSchema).readonly()
17916
18062
  });
18063
+ /**
18064
+ * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
18065
+ * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
18066
+ * version visibility for the Server management surface. Nullable: offline
18067
+ * rows and pre-phase-2 nodes report none.
18068
+ */
18069
+ var TopologyRootPackageSchema = object({
18070
+ name: string(),
18071
+ version: string()
18072
+ });
17917
18073
  var TopologyNodeSchema = object({
17918
18074
  id: string(),
17919
18075
  name: string(),
@@ -17937,7 +18093,8 @@ var TopologyNodeSchema = object({
17937
18093
  status: string()
17938
18094
  })).readonly(),
17939
18095
  processes: array(TopologyProcessSchema).readonly(),
17940
- categories: array(TopologyCategorySchema).readonly()
18096
+ categories: array(TopologyCategorySchema).readonly(),
18097
+ rootPackage: TopologyRootPackageSchema.nullable()
17941
18098
  });
17942
18099
  var CapUsageEdgeSchema = object({
17943
18100
  callerAddonId: string(),
@@ -20737,6 +20894,12 @@ Object.freeze({
20737
20894
  addonId: null,
20738
20895
  access: "create"
20739
20896
  },
20897
+ "loginMethod.getLoginMethods": {
20898
+ capName: "login-method",
20899
+ capScope: "system",
20900
+ addonId: null,
20901
+ access: "view"
20902
+ },
20740
20903
  "mediaPlayer.next": {
20741
20904
  capName: "media-player",
20742
20905
  capScope: "device",
@@ -21319,6 +21482,12 @@ Object.freeze({
21319
21482
  addonId: null,
21320
21483
  access: "view"
21321
21484
  },
21485
+ "pipelineAnalytics.getKeyEvents": {
21486
+ capName: "pipeline-analytics",
21487
+ capScope: "device",
21488
+ addonId: null,
21489
+ access: "view"
21490
+ },
21322
21491
  "pipelineAnalytics.getMotionEvents": {
21323
21492
  capName: "pipeline-analytics",
21324
21493
  capScope: "device",
@@ -21367,23 +21536,23 @@ Object.freeze({
21367
21536
  addonId: null,
21368
21537
  access: "create"
21369
21538
  },
21370
- "pipelineExecutor.deleteModel": {
21539
+ "pipelineExecutor.clearDeviceOverrides": {
21371
21540
  capName: "pipeline-executor",
21372
21541
  capScope: "system",
21373
21542
  addonId: null,
21374
21543
  access: "delete"
21375
21544
  },
21376
- "pipelineExecutor.deleteTemplate": {
21545
+ "pipelineExecutor.deleteModel": {
21377
21546
  capName: "pipeline-executor",
21378
21547
  capScope: "system",
21379
21548
  addonId: null,
21380
21549
  access: "delete"
21381
21550
  },
21382
- "pipelineExecutor.detect": {
21551
+ "pipelineExecutor.deleteTemplate": {
21383
21552
  capName: "pipeline-executor",
21384
21553
  capScope: "system",
21385
21554
  addonId: null,
21386
- access: "view"
21555
+ access: "delete"
21387
21556
  },
21388
21557
  "pipelineExecutor.downloadModel": {
21389
21558
  capName: "pipeline-executor",
@@ -21577,13 +21746,13 @@ Object.freeze({
21577
21746
  addonId: null,
21578
21747
  access: "create"
21579
21748
  },
21580
- "pipelineOrchestrator.assignAudio": {
21581
- capName: "pipeline-orchestrator",
21749
+ "pipelineExecutor.validatePipeline": {
21750
+ capName: "pipeline-executor",
21582
21751
  capScope: "system",
21583
21752
  addonId: null,
21584
- access: "create"
21753
+ access: "view"
21585
21754
  },
21586
- "pipelineOrchestrator.assignDecoder": {
21755
+ "pipelineOrchestrator.assignAudio": {
21587
21756
  capName: "pipeline-orchestrator",
21588
21757
  capScope: "system",
21589
21758
  addonId: null,
@@ -21667,19 +21836,13 @@ Object.freeze({
21667
21836
  addonId: null,
21668
21837
  access: "view"
21669
21838
  },
21670
- "pipelineOrchestrator.getDecoderAssignment": {
21671
- capName: "pipeline-orchestrator",
21672
- capScope: "system",
21673
- addonId: null,
21674
- access: "view"
21675
- },
21676
- "pipelineOrchestrator.getDecoderAssignments": {
21839
+ "pipelineOrchestrator.getGlobalMetrics": {
21677
21840
  capName: "pipeline-orchestrator",
21678
21841
  capScope: "system",
21679
21842
  addonId: null,
21680
21843
  access: "view"
21681
21844
  },
21682
- "pipelineOrchestrator.getGlobalMetrics": {
21845
+ "pipelineOrchestrator.getIngestOwner": {
21683
21846
  capName: "pipeline-orchestrator",
21684
21847
  capScope: "system",
21685
21848
  addonId: null,
@@ -21721,6 +21884,12 @@ Object.freeze({
21721
21884
  addonId: null,
21722
21885
  access: "delete"
21723
21886
  },
21887
+ "pipelineOrchestrator.resetNodePipelineDefaults": {
21888
+ capName: "pipeline-orchestrator",
21889
+ capScope: "system",
21890
+ addonId: null,
21891
+ access: "delete"
21892
+ },
21724
21893
  "pipelineOrchestrator.resolvePipeline": {
21725
21894
  capName: "pipeline-orchestrator",
21726
21895
  capScope: "system",
@@ -21757,37 +21926,37 @@ Object.freeze({
21757
21926
  addonId: null,
21758
21927
  access: "create"
21759
21928
  },
21760
- "pipelineOrchestrator.setCameraPipelineForAgent": {
21929
+ "pipelineOrchestrator.setAgentReachableHost": {
21761
21930
  capName: "pipeline-orchestrator",
21762
21931
  capScope: "system",
21763
21932
  addonId: null,
21764
21933
  access: "create"
21765
21934
  },
21766
- "pipelineOrchestrator.setCameraStepOverride": {
21935
+ "pipelineOrchestrator.setCameraPipelineForAgent": {
21767
21936
  capName: "pipeline-orchestrator",
21768
21937
  capScope: "system",
21769
21938
  addonId: null,
21770
21939
  access: "create"
21771
21940
  },
21772
- "pipelineOrchestrator.setCameraStepToggle": {
21941
+ "pipelineOrchestrator.setCameraStepOverride": {
21773
21942
  capName: "pipeline-orchestrator",
21774
21943
  capScope: "system",
21775
21944
  addonId: null,
21776
21945
  access: "create"
21777
21946
  },
21778
- "pipelineOrchestrator.setCapabilityBinding": {
21947
+ "pipelineOrchestrator.setCameraStepToggle": {
21779
21948
  capName: "pipeline-orchestrator",
21780
21949
  capScope: "system",
21781
21950
  addonId: null,
21782
21951
  access: "create"
21783
21952
  },
21784
- "pipelineOrchestrator.unassignAudio": {
21953
+ "pipelineOrchestrator.setCapabilityBinding": {
21785
21954
  capName: "pipeline-orchestrator",
21786
21955
  capScope: "system",
21787
21956
  addonId: null,
21788
21957
  access: "create"
21789
21958
  },
21790
- "pipelineOrchestrator.unassignDecoder": {
21959
+ "pipelineOrchestrator.unassignAudio": {
21791
21960
  capName: "pipeline-orchestrator",
21792
21961
  capScope: "system",
21793
21962
  addonId: null,
@@ -21847,6 +22016,12 @@ Object.freeze({
21847
22016
  addonId: null,
21848
22017
  access: "view"
21849
22018
  },
22019
+ "pipelineRunner.getNativeCrop": {
22020
+ capName: "pipeline-runner",
22021
+ capScope: "system",
22022
+ addonId: null,
22023
+ access: "view"
22024
+ },
21850
22025
  "pipelineRunner.reportMotion": {
21851
22026
  capName: "pipeline-runner",
21852
22027
  capScope: "system",
@@ -22087,33 +22262,45 @@ Object.freeze({
22087
22262
  addonId: null,
22088
22263
  access: "create"
22089
22264
  },
22090
- "restreamer.getExposedResources": {
22091
- capName: "restreamer",
22265
+ "scriptRunner.run": {
22266
+ capName: "script-runner",
22267
+ capScope: "device",
22268
+ addonId: null,
22269
+ access: "create"
22270
+ },
22271
+ "scriptRunner.stop": {
22272
+ capName: "script-runner",
22273
+ capScope: "device",
22274
+ addonId: null,
22275
+ access: "create"
22276
+ },
22277
+ "serverManagement.applyServerUpdate": {
22278
+ capName: "server-management",
22092
22279
  capScope: "system",
22093
22280
  addonId: null,
22094
- access: "view"
22281
+ access: "create"
22095
22282
  },
22096
- "restreamer.registerDevice": {
22097
- capName: "restreamer",
22283
+ "serverManagement.checkServerUpdate": {
22284
+ capName: "server-management",
22098
22285
  capScope: "system",
22099
22286
  addonId: null,
22100
22287
  access: "create"
22101
22288
  },
22102
- "restreamer.unregisterDevice": {
22103
- capName: "restreamer",
22289
+ "serverManagement.getServerPackageStatus": {
22290
+ capName: "server-management",
22104
22291
  capScope: "system",
22105
22292
  addonId: null,
22106
- access: "delete"
22293
+ access: "view"
22107
22294
  },
22108
- "scriptRunner.run": {
22109
- capName: "script-runner",
22110
- capScope: "device",
22295
+ "serverManagement.restartServer": {
22296
+ capName: "server-management",
22297
+ capScope: "system",
22111
22298
  addonId: null,
22112
22299
  access: "create"
22113
22300
  },
22114
- "scriptRunner.stop": {
22115
- capName: "script-runner",
22116
- capScope: "device",
22301
+ "serverManagement.rollbackServerUpdate": {
22302
+ capName: "server-management",
22303
+ capScope: "system",
22117
22304
  addonId: null,
22118
22305
  access: "create"
22119
22306
  },
@@ -22201,23 +22388,17 @@ Object.freeze({
22201
22388
  addonId: null,
22202
22389
  access: "view"
22203
22390
  },
22204
- "snapshot.invalidateCache": {
22391
+ "snapshot.getSnapshotOverview": {
22205
22392
  capName: "snapshot",
22206
22393
  capScope: "device",
22207
22394
  addonId: null,
22208
- access: "create"
22209
- },
22210
- "snapshotProvider.getSnapshot": {
22211
- capName: "snapshot-provider",
22212
- capScope: "system",
22213
- addonId: null,
22214
22395
  access: "view"
22215
22396
  },
22216
- "snapshotProvider.supportsDevice": {
22217
- capName: "snapshot-provider",
22218
- capScope: "system",
22397
+ "snapshot.invalidateCache": {
22398
+ capName: "snapshot",
22399
+ capScope: "device",
22219
22400
  addonId: null,
22220
- access: "view"
22401
+ access: "create"
22221
22402
  },
22222
22403
  "ssoBridge.signBridgeToken": {
22223
22404
  capName: "sso-bridge",
@@ -22645,30 +22826,6 @@ Object.freeze({
22645
22826
  addonId: null,
22646
22827
  access: "view"
22647
22828
  },
22648
- "streamingEngine.getStreamUrl": {
22649
- capName: "streaming-engine",
22650
- capScope: "system",
22651
- addonId: null,
22652
- access: "view"
22653
- },
22654
- "streamingEngine.listStreams": {
22655
- capName: "streaming-engine",
22656
- capScope: "system",
22657
- addonId: null,
22658
- access: "view"
22659
- },
22660
- "streamingEngine.registerStream": {
22661
- capName: "streaming-engine",
22662
- capScope: "system",
22663
- addonId: null,
22664
- access: "create"
22665
- },
22666
- "streamingEngine.unregisterStream": {
22667
- capName: "streaming-engine",
22668
- capScope: "system",
22669
- addonId: null,
22670
- access: "delete"
22671
- },
22672
22829
  "streamParams.getConfigSchema": {
22673
22830
  capName: "stream-params",
22674
22831
  capScope: "device",
@@ -22915,6 +23072,12 @@ Object.freeze({
22915
23072
  addonId: null,
22916
23073
  access: "view"
22917
23074
  },
23075
+ "userPasskeys.beginDiscoverableAuthentication": {
23076
+ capName: "user-passkeys",
23077
+ capScope: "system",
23078
+ addonId: null,
23079
+ access: "view"
23080
+ },
22918
23081
  "userPasskeys.beginRegistration": {
22919
23082
  capName: "user-passkeys",
22920
23083
  capScope: "system",
@@ -22927,12 +23090,24 @@ Object.freeze({
22927
23090
  addonId: null,
22928
23091
  access: "view"
22929
23092
  },
23093
+ "userPasskeys.finishDiscoverableAuthentication": {
23094
+ capName: "user-passkeys",
23095
+ capScope: "system",
23096
+ addonId: null,
23097
+ access: "view"
23098
+ },
22930
23099
  "userPasskeys.finishRegistration": {
22931
23100
  capName: "user-passkeys",
22932
23101
  capScope: "system",
22933
23102
  addonId: null,
22934
23103
  access: "create"
22935
23104
  },
23105
+ "userPasskeys.getSecondFactorPreference": {
23106
+ capName: "user-passkeys",
23107
+ capScope: "system",
23108
+ addonId: null,
23109
+ access: "view"
23110
+ },
22936
23111
  "userPasskeys.listPasskeys": {
22937
23112
  capName: "user-passkeys",
22938
23113
  capScope: "system",
@@ -22945,6 +23120,12 @@ Object.freeze({
22945
23120
  addonId: null,
22946
23121
  access: "delete"
22947
23122
  },
23123
+ "userPasskeys.setSecondFactorPreference": {
23124
+ capName: "user-passkeys",
23125
+ capScope: "system",
23126
+ addonId: null,
23127
+ access: "create"
23128
+ },
22948
23129
  "vacuumControl.locate": {
22949
23130
  capName: "vacuum-control",
22950
23131
  capScope: "device",
@@ -23017,6 +23198,18 @@ Object.freeze({
23017
23198
  addonId: null,
23018
23199
  access: "view"
23019
23200
  },
23201
+ "viewerUi.getStaticDir": {
23202
+ capName: "viewer-ui",
23203
+ capScope: "system",
23204
+ addonId: null,
23205
+ access: "view"
23206
+ },
23207
+ "viewerUi.getVersion": {
23208
+ capName: "viewer-ui",
23209
+ capScope: "system",
23210
+ addonId: null,
23211
+ access: "view"
23212
+ },
23020
23213
  "waterHeater.setAway": {
23021
23214
  capName: "water-heater",
23022
23215
  capScope: "device",
@@ -23035,54 +23228,6 @@ Object.freeze({
23035
23228
  addonId: null,
23036
23229
  access: "create"
23037
23230
  },
23038
- "webrtc.closeSession": {
23039
- capName: "webrtc",
23040
- capScope: "system",
23041
- addonId: null,
23042
- access: "create"
23043
- },
23044
- "webrtc.createSession": {
23045
- capName: "webrtc",
23046
- capScope: "system",
23047
- addonId: null,
23048
- access: "create"
23049
- },
23050
- "webrtc.handleAnswer": {
23051
- capName: "webrtc",
23052
- capScope: "system",
23053
- addonId: null,
23054
- access: "create"
23055
- },
23056
- "webrtc.handleOffer": {
23057
- capName: "webrtc",
23058
- capScope: "system",
23059
- addonId: null,
23060
- access: "create"
23061
- },
23062
- "webrtc.hasAdaptiveBitrate": {
23063
- capName: "webrtc",
23064
- capScope: "system",
23065
- addonId: null,
23066
- access: "view"
23067
- },
23068
- "webrtc.registerStream": {
23069
- capName: "webrtc",
23070
- capScope: "system",
23071
- addonId: null,
23072
- access: "create"
23073
- },
23074
- "webrtc.supportsStream": {
23075
- capName: "webrtc",
23076
- capScope: "system",
23077
- addonId: null,
23078
- access: "view"
23079
- },
23080
- "webrtc.unregisterStream": {
23081
- capName: "webrtc",
23082
- capScope: "system",
23083
- addonId: null,
23084
- access: "delete"
23085
- },
23086
23231
  "webrtcSession.addIceCandidate": {
23087
23232
  capName: "webrtc-session",
23088
23233
  capScope: "device",