@camstack/addon-decoder-nodeav 1.1.8 → 1.1.9

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 +502 -527
  2. package/dist/index.mjs +488 -523
  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-DJaTV2D7.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,9 @@ function method(input, output, options) {
7000
6738
  timeoutMs: options?.timeoutMs
7001
6739
  };
7002
6740
  }
6741
+ var StaticDirOutputSchema$1 = object({ staticDir: string() });
6742
+ var VersionOutputSchema$1 = object({ version: string() });
6743
+ method(_void(), StaticDirOutputSchema$1), method(_void(), VersionOutputSchema$1);
7003
6744
  var StaticDirOutputSchema = object({ staticDir: string() });
7004
6745
  var VersionOutputSchema = object({ version: string() });
7005
6746
  method(_void(), StaticDirOutputSchema), method(_void(), VersionOutputSchema);
@@ -7181,6 +6922,36 @@ var ModelFormatsSchema = object({
7181
6922
  tflite: ModelFormatEntrySchema.optional(),
7182
6923
  pt: ModelFormatEntrySchema.optional()
7183
6924
  });
6925
+ /**
6926
+ * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
6927
+ * the reduced `PipelineModelOption` returned in `pipeline.getSchema()` so the
6928
+ * grouped Family→Tier→Variant picker renders identically in the config UI and
6929
+ * in the pipeline/device steppers. The flat `id` stays the source of truth for
6930
+ * resolution/download/persistence; this is a presentation overlay resolved back
6931
+ * to an `id`.
6932
+ */
6933
+ var ModelVariantGroupSchema = object({
6934
+ /** Top-level family, e.g. `yolo26` (later `d-fine`, `rf-detr`). */
6935
+ family: string(),
6936
+ /** Size within the family, e.g. `n` | `s` | `m` | `l`. */
6937
+ tier: string(),
6938
+ /** Quantization axis. Omit ⇒ the fp32 base build. */
6939
+ precision: _enum(["fp32", "int8"]).optional(),
6940
+ /**
6941
+ * Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
6942
+ * latency-optimized export (e.g. ReLU-activation variant) — the slot the
6943
+ * future performance variants plug into.
6944
+ */
6945
+ optimization: _enum(["standard", "fast"]).optional(),
6946
+ /**
6947
+ * Input-resolution axis (square input side, px). Omit ⇒ the family's native
6948
+ * resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
6949
+ * cheap latency lever — especially on Apple ANE and the Intel N100 — at a
6950
+ * small-object accuracy cost. Mirrors the model's `inputSize` but lifted onto
6951
+ * the group so the selector can offer it as a variant axis.
6952
+ */
6953
+ resolution: number().int().positive().optional()
6954
+ });
7184
6955
  var ModelCatalogEntrySchema = object({
7185
6956
  id: string(),
7186
6957
  name: string(),
@@ -7210,7 +6981,43 @@ var ModelCatalogEntrySchema = object({
7210
6981
  * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
7211
6982
  * Downloaded into the same modelsDir alongside the model file.
7212
6983
  */
7213
- extraFiles: array(ModelExtraFileSchema).readonly().optional()
6984
+ extraFiles: array(ModelExtraFileSchema).readonly().optional(),
6985
+ /**
6986
+ * LEGACY entry — retained in the catalog so a persisted operator selection
6987
+ * still RESOLVES (and can be re-activated), but hidden from the selectable
6988
+ * model list and excluded from the auto format-default pick. Set on the
6989
+ * superseded / consolidated models (older lineages, redundant fp16 IRs) so
6990
+ * the active lineup stays the coherent curated ladder without deleting a
6991
+ * model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
6992
+ * an explicit legacy id that has a build for the node's format.
6993
+ */
6994
+ legacy: boolean().optional(),
6995
+ /**
6996
+ * Measured quality/latency metadata — populated from the benchmark addon on
6997
+ * the real node classes. Absent = not yet measured (most entries today; the
6998
+ * catalog historically carried only `sizeMB`, a poor cross-architecture
6999
+ * speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
7000
+ */
7001
+ metrics: object({
7002
+ map50: number().optional(),
7003
+ p95LatencyMs: record(string(), number()).optional()
7004
+ }).optional(),
7005
+ /**
7006
+ * SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
7007
+ * YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
7008
+ * the retraining addon and any future commercial distribution.
7009
+ */
7010
+ license: string().optional(),
7011
+ /**
7012
+ * Variant-selector grouping. The UI groups models by `family` + `tier` and
7013
+ * offers `precision` / `optimization` as variant axes WITHIN a tier — so all
7014
+ * of a family's sizes and quantizations collapse into one grouped picker
7015
+ * instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
7016
+ * (legacy / custom models) — never shown in the grouped selector. The flat
7017
+ * `id` stays the source of truth for resolution/download/persistence; grouping
7018
+ * is a presentation overlay resolved back to an `id`.
7019
+ */
7020
+ group: ModelVariantGroupSchema.optional()
7214
7021
  });
7215
7022
  var ConvertTargetSchema = discriminatedUnion("format", [object({
7216
7023
  format: literal("openvino"),
@@ -7271,8 +7078,8 @@ var RecordingModeSchema = _enum([
7271
7078
  "onAudioThreshold"
7272
7079
  ]);
7273
7080
  /**
7274
- * First-class, authoritative per-camera storage mode — the netta choice the UI
7275
- * reads directly (never inferred from `rules`):
7081
+ * First-class, authoritative per-camera storage mode — the explicit choice the
7082
+ * UI reads directly (never inferred from `rules`):
7276
7083
  * - `off` — not recording.
7277
7084
  * - `events` — record only around triggers (motion / audio threshold),
7278
7085
  * with pre/post-buffer.
@@ -8920,26 +8727,13 @@ DeviceType.Light, method(object({
8920
8727
  percentage: number().min(0).max(100),
8921
8728
  lastChangedAt: number()
8922
8729
  });
8730
+ /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8923
8731
  var StreamFormatSchema = _enum([
8924
8732
  "webrtc",
8925
8733
  "hls",
8926
8734
  "mjpeg",
8927
8735
  "rtsp"
8928
8736
  ]);
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
8737
  var RtspRestreamEntrySchema = object({
8944
8738
  brokerId: string(),
8945
8739
  url: string(),
@@ -9604,7 +9398,7 @@ var ConsumablesStatusSchema = object({
9604
9398
  })),
9605
9399
  lastChangedAt: number()
9606
9400
  });
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({
9401
+ Object.values(DeviceType), method(object({
9608
9402
  deviceId: number().int().nonnegative(),
9609
9403
  key: string().min(1)
9610
9404
  }), _void(), {
@@ -10519,7 +10313,7 @@ var BoundingBoxSchema = object({
10519
10313
  w: number(),
10520
10314
  h: number()
10521
10315
  });
10522
- var SpatialDetectionSchema = object({
10316
+ object({
10523
10317
  class: string(),
10524
10318
  originalClass: string(),
10525
10319
  score: number(),
@@ -10654,7 +10448,6 @@ var PipelineDefaultStepSchema = lazy(() => object({
10654
10448
  enabled: boolean(),
10655
10449
  modelId: string(),
10656
10450
  children: array(PipelineDefaultStepSchema).readonly(),
10657
- engine: PipelineEngineChoiceSchema.optional(),
10658
10451
  group: string().optional(),
10659
10452
  settings: record(string(), unknown()).optional()
10660
10453
  }));
@@ -10679,7 +10472,9 @@ var PipelineModelOptionSchema = object({
10679
10472
  formats: record(string(), object({
10680
10473
  downloaded: boolean(),
10681
10474
  sizeMB: number()
10682
- }))
10475
+ })),
10476
+ group: ModelVariantGroupSchema.optional(),
10477
+ legacy: boolean().optional()
10683
10478
  });
10684
10479
  var ConfigFieldBridge = custom();
10685
10480
  var PipelineAddonSchemaSchema = object({
@@ -10709,11 +10504,6 @@ var PipelineSchemaSchema = object({
10709
10504
  selectedEngine: PipelineEngineChoiceSchema,
10710
10505
  slots: array(PipelineSlotSchemaSchema).readonly()
10711
10506
  });
10712
- var DetectorOutputSchema = object({
10713
- detections: array(SpatialDetectionSchema).readonly(),
10714
- inferenceMs: number(),
10715
- modelId: string()
10716
- });
10717
10507
  var EngineProvisioningSchema = object({
10718
10508
  runtimeId: _enum([
10719
10509
  "onnx",
@@ -10730,15 +10520,42 @@ var EngineProvisioningSchema = object({
10730
10520
  ]),
10731
10521
  progress: number().optional(),
10732
10522
  error: string().optional(),
10733
- nextRetryAt: number().optional()
10523
+ nextRetryAt: number().optional(),
10524
+ /**
10525
+ * Gate A (config-correctness gate at engine change): human-readable
10526
+ * config issues surfaced EAGERLY when the node's engine changes — model
10527
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
10528
+ * has a <format> build"). Additive/optional: informational only, never
10529
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
10530
+ * Absent/empty when the node-default tree resolves cleanly.
10531
+ */
10532
+ configIssues: array(string()).optional()
10734
10533
  });
10735
10534
  var PipelineStepInputSchema = lazy(() => object({
10736
10535
  addonId: string(),
10737
- modelId: string(),
10536
+ modelId: string().optional(),
10738
10537
  enabled: boolean().default(true),
10739
10538
  children: array(PipelineStepInputSchema).optional(),
10740
10539
  settings: record(string(), unknown()).optional()
10741
10540
  }));
10541
+ var ModelSubstitutionSchema = object({
10542
+ addonId: string(),
10543
+ chosen: string(),
10544
+ running: string(),
10545
+ format: string()
10546
+ });
10547
+ var PipelineValidationIssueSchema = object({
10548
+ addonId: string(),
10549
+ kind: _enum(["unknown-addon", "no-format-build"]),
10550
+ detail: string()
10551
+ });
10552
+ var PipelineValidationResultSchema = object({
10553
+ ok: boolean(),
10554
+ issues: array(PipelineValidationIssueSchema).readonly(),
10555
+ substitutions: array(ModelSubstitutionSchema).readonly(),
10556
+ /** The node's `currentEngine.format` this validation ran against. */
10557
+ format: string()
10558
+ });
10742
10559
  var ReferenceImageEntrySchema = object({
10743
10560
  filename: string(),
10744
10561
  stepIds: array(string()).readonly().optional()
@@ -10809,7 +10626,13 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10809
10626
  })) }), object({ success: literal(true) }), {
10810
10627
  kind: "mutation",
10811
10628
  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({
10629
+ }), method(object({ nodeId: string() }), object({
10630
+ success: literal(true),
10631
+ clearedDevices: number()
10632
+ }), {
10633
+ kind: "mutation",
10634
+ auth: "admin"
10635
+ }), 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
10636
  name: string(),
10814
10637
  steps: array(PipelineTemplateStepSchema).readonly(),
10815
10638
  engine: PipelineEngineChoiceSchema
@@ -10826,10 +10649,6 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10826
10649
  modelId: string(),
10827
10650
  format: ModelFormatSchema$1
10828
10651
  }), 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
10652
  engine: PipelineEngineChoiceSchema.optional(),
10834
10653
  steps: array(PipelineStepInputSchema).min(1),
10835
10654
  frame: FrameInputSchema.optional(),
@@ -11069,6 +10888,13 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
11069
10888
  kind: literal("remote-restream"),
11070
10889
  /** The camera's source-owner node (slice 1: always the hub). */
11071
10890
  ownerNodeId: string(),
10891
+ /**
10892
+ * The owner's LAN-reachable host, resolved by the orchestrator from the
10893
+ * per-node `reachableHost` override (Cluster UI). When present the runner
10894
+ * dials THIS host for the owner's restream, in preference to the
10895
+ * `CAMSTACK_HUB_URL`-derived default. Absent → auto-detect fallback.
10896
+ */
10897
+ ownerReachableHost: string().optional(),
11072
10898
  /** Operator override for the owner host the runner dials. */
11073
10899
  hubHostnameOverride: string().optional()
11074
10900
  })]).describe("Per-camera frame-source mode for the runner (P2c)");
@@ -11077,13 +10903,11 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
11077
10903
  * specific runner instance via `attachCamera`. Carries everything the
11078
10904
  * runner needs to subscribe to the local broker and execute inference.
11079
10905
  *
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.
10906
+ * Stateless-pipeline model: the pipeline content (`steps`, optional
10907
+ * `audio`) travels with the attach payload. The runner keeps it in RAM
10908
+ * for the lifetime of the attach — on rebalance, edit, or restart the
10909
+ * orchestrator re-sends the latest snapshot. Engine is NOT carried: it is
10910
+ * node-local, resolved by the executing runner at dispatch time.
11087
10911
  */
11088
10912
  var RunnerCameraConfigSchema = object({
11089
10913
  deviceId: number(),
@@ -11134,14 +10958,11 @@ var RunnerCameraConfigSchema = object({
11134
10958
  */
11135
10959
  motionSources: MotionSourcesSchema.default(["analyzer"]),
11136
10960
  pipelineEnabled: boolean().default(true),
11137
- /** Engine choice for video steps (runtime+backend+format). */
11138
- engine: PipelineEngineChoiceSchema.optional(),
11139
10961
  /** Ordered tree of video steps. Absent → runner skips video detection. */
11140
10962
  steps: array(PipelineStepInputSchema).readonly().optional(),
11141
10963
  /** Audio classification branch. `enabled:false` disables, null skips. */
11142
10964
  audio: object({
11143
- engine: PipelineEngineChoiceSchema,
11144
- modelId: string(),
10965
+ modelId: string().optional(),
11145
10966
  enabled: boolean()
11146
10967
  }).nullable().optional(),
11147
10968
  /**
@@ -12522,7 +12343,9 @@ var AddonPageDeclarationSchema$1 = object({
12522
12343
  icon: string(),
12523
12344
  path: string(),
12524
12345
  remoteName: string(),
12525
- bundle: string()
12346
+ bundle: string(),
12347
+ section: string().optional(),
12348
+ sectionLabel: string().optional()
12526
12349
  });
12527
12350
  var AddonPageInfoSchema = object({
12528
12351
  addonId: string(),
@@ -12562,7 +12385,18 @@ var AddonPageDeclarationSchema = object({
12562
12385
  * the static-file route can compute an mtime-based cache-buster URL
12563
12386
  * without a separate filesystem stat.
12564
12387
  */
12565
- bundle: string()
12388
+ bundle: string(),
12389
+ /**
12390
+ * Sidebar section this page docks into. Well-known ids: `'detection'`,
12391
+ * `'cluster'`, `'administration'` — the page renders inside that group.
12392
+ * Any OTHER string creates (or joins) a custom section rendered after
12393
+ * the built-in groups; its label comes from `sectionLabel` (first
12394
+ * declaration wins), falling back to the id. Absent → the legacy
12395
+ * "Addon Pages" group.
12396
+ */
12397
+ section: string().optional(),
12398
+ /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
12399
+ sectionLabel: string().optional()
12566
12400
  });
12567
12401
  method(_void(), array(AddonPageDeclarationSchema).readonly());
12568
12402
  var AddonHttpRouteSchema = object({
@@ -12778,6 +12612,17 @@ var WidgetMetadataSchema = object({
12778
12612
  deviceContext: boolean().default(false),
12779
12613
  integrationContext: boolean().default(false)
12780
12614
  }),
12615
+ /**
12616
+ * Loadable BEFORE authentication. The normal widget registry listing
12617
+ * (`addon-widgets.listWidgets`) is auth-gated, so a pre-auth surface
12618
+ * (the login page) cannot discover a widget through it. A widget that
12619
+ * declares `preAuth: true` marks itself as safe to mount on a pre-auth
12620
+ * screen — it is surfaced through the PUBLIC `auth.listLoginMethods`
12621
+ * login-method contribution channel (see `login-method.cap.ts`) rather
12622
+ * than the authenticated registry, and its bundle is served by the
12623
+ * public `/api/addon-widgets/:addonId/*` static route. Defaults false.
12624
+ */
12625
+ preAuth: boolean().optional().default(false),
12781
12626
  /** Dashboard placement HINTS (operator can override per instance). */
12782
12627
  defaultSize: WidgetSizeEnum.default("md"),
12783
12628
  allowedSizes: array(WidgetSizeEnum).readonly().default([
@@ -13116,6 +12961,66 @@ method(object({
13116
12961
  password: string()
13117
12962
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
13118
12963
  /**
12964
+ * `login-method` — collection cap through which auth addons contribute
12965
+ * their pre-auth login surfaces to the login page. This is the SINGLE,
12966
+ * generic mechanism that supersedes the dead `auth.listProviders` reader:
12967
+ * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
12968
+ * `login-method` provider and the PUBLIC `auth.listLoginMethods`
12969
+ * procedure aggregates them for the unauthenticated login page.
12970
+ *
12971
+ * A contribution is a discriminated union on `kind`:
12972
+ *
12973
+ * - `redirect` — a declarative button. The login page renders a generic
12974
+ * button that navigates to `startUrl` (an addon-owned HTTP route).
12975
+ * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
12976
+ * ZERO shell-side JS. A future SSO addon plugs in the same way — the
12977
+ * login page needs NO change.
12978
+ *
12979
+ * - `widget` — a Module-Federation widget the login page mounts (via
12980
+ * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
12981
+ * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
12982
+ * addon bundle. The referenced widget also declares `preAuth: true` in
12983
+ * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
12984
+ * stamps a public `bundleUrl` from `addonId` + `bundle`.
12985
+ *
12986
+ * Every contribution carries a `stage`:
12987
+ * - `primary` — shown on the first credentials screen (OIDC /
12988
+ * magic-link buttons; a future usernameless passkey).
12989
+ * - `second-factor` — shown AFTER the password leg, gated on the
12990
+ * returned `factors` (passkey-as-2FA today).
12991
+ *
12992
+ * `mount: skip` — the cap is read server-side by the core auth router
12993
+ * (`registry.getCollection('login-method')`), never mounted as its own
12994
+ * tRPC router.
12995
+ */
12996
+ /** When a login method renders in the two-phase login flow. */
12997
+ var LoginStageEnum = _enum(["primary", "second-factor"]);
12998
+ /** One login-method contribution — redirect button OR pre-auth widget. */
12999
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13000
+ kind: literal("redirect"),
13001
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13002
+ id: string(),
13003
+ /** Operator-facing button label. */
13004
+ label: string(),
13005
+ /** lucide-react icon name. */
13006
+ icon: string().optional(),
13007
+ /** Addon-owned HTTP route the button navigates to (GET). */
13008
+ startUrl: string(),
13009
+ stage: LoginStageEnum
13010
+ }), object({
13011
+ kind: literal("widget"),
13012
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13013
+ id: string(),
13014
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
13015
+ addonId: string(),
13016
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13017
+ bundle: string(),
13018
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
13019
+ remote: WidgetRemoteSchema,
13020
+ stage: LoginStageEnum
13021
+ })]);
13022
+ method(_void(), array(LoginMethodContributionSchema).readonly());
13023
+ /**
13119
13024
  * Orchestrator-side destination metadata. The orchestrator computes
13120
13025
  * `id = <addonId>:<subId>` from its provider lookup so consumers
13121
13026
  * (admin UI, restore flow) see one canonical key.
@@ -15497,11 +15402,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15497
15402
  timestamp: number()
15498
15403
  });
15499
15404
  var CameraPipelineConfigSchema = object({
15500
- engine: PipelineEngineChoiceSchema,
15405
+ engine: PipelineEngineChoiceSchema.optional(),
15501
15406
  steps: array(PipelineStepInputSchema).readonly(),
15502
15407
  audio: object({
15503
- engine: PipelineEngineChoiceSchema,
15504
- modelId: string(),
15408
+ engine: PipelineEngineChoiceSchema.optional(),
15409
+ modelId: string().optional(),
15505
15410
  enabled: boolean(),
15506
15411
  settings: record(string(), unknown()).readonly().optional()
15507
15412
  }).nullable().optional()
@@ -15516,7 +15421,7 @@ var PipelineTemplateSchema = object({
15516
15421
  });
15517
15422
  var AgentAddonConfigSchema = object({
15518
15423
  enabled: boolean(),
15519
- modelId: string(),
15424
+ modelId: string().optional(),
15520
15425
  settings: record(string(), unknown()).readonly()
15521
15426
  });
15522
15427
  var AgentPipelineSettingsSchema = object({
@@ -15526,12 +15431,25 @@ var AgentPipelineSettingsSchema = object({
15526
15431
  detectWeight: number().positive().optional(),
15527
15432
  /** Node is eligible to run the detection pipeline (decode + inference). */
15528
15433
  detect: boolean().optional(),
15529
- /** Node is eligible to host decoder sessions. */
15434
+ /**
15435
+ * DEPRECATED AND IGNORED. Decode is always co-located with its frame
15436
+ * consumer, so decode eligibility IS detect eligibility. Kept optional in
15437
+ * the schema ONLY so persisted stores written before the removal still
15438
+ * parse — no code reads it and no write path emits it.
15439
+ */
15530
15440
  decode: boolean().optional(),
15531
15441
  /** Node is eligible to run audio-analyzer sessions. */
15532
15442
  audio: boolean().optional(),
15533
15443
  /** Node is eligible to be the ingest / source-owner (serve the restream). */
15534
- ingest: boolean().optional()
15444
+ ingest: boolean().optional(),
15445
+ /**
15446
+ * Operator override for the LAN host a cross-node decoder dials to reach
15447
+ * THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
15448
+ * falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
15449
+ * it already uses to reach the hub). Set this only when the auto-detected
15450
+ * address is wrong (multi-homed host, NAT, custom interface).
15451
+ */
15452
+ reachableHost: string().optional()
15535
15453
  });
15536
15454
  var CameraPipelineForAgentSchema = object({
15537
15455
  steps: array(PipelineStepInputSchema).readonly(),
@@ -15579,25 +15497,6 @@ var PipelineAssignmentSchema = object({
15579
15497
  assignedAt: number()
15580
15498
  });
15581
15499
  /**
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
15500
  * Per-agent load summary surfaced to the load balancer + dashboards.
15602
15501
  * Aggregated from each runner's `getLocalLoad` cap call.
15603
15502
  */
@@ -15637,6 +15536,15 @@ var GlobalMetricsSchema = object({
15637
15536
  * capability providers.
15638
15537
  */
15639
15538
  var CapabilityBindingsSchema = record(string(), string());
15539
+ /**
15540
+ * The cluster's single camera-source owner (`clusterRoles.ingestNode`) plus
15541
+ * its LAN-reachable host, if one is registered. See `getIngestOwner`.
15542
+ */
15543
+ var IngestOwnerSchema = object({
15544
+ ownerNodeId: string(),
15545
+ reachableHost: string().optional(),
15546
+ configIssue: string().optional()
15547
+ });
15640
15548
  /** Source block — always present; derives from the stream catalog. */
15641
15549
  var CameraSourceStatusSchema = object({ streams: array(object({
15642
15550
  camStreamId: string(),
@@ -15651,6 +15559,14 @@ var CameraAssignmentStatusSchema = object({
15651
15559
  detectionNodeId: string().nullable(),
15652
15560
  decoderNodeId: string().nullable(),
15653
15561
  audioNodeId: string().nullable(),
15562
+ /**
15563
+ * The node that OWNS this camera's physical source pull (dials the RTSP and
15564
+ * hosts the broker/restream) — the cluster ingest owner today
15565
+ * (`clusterRoles.ingestNode`), per-camera once source assignment lands. Lets
15566
+ * the UI show WHERE a camera is sourced without SSH/logs, and is the node the
15567
+ * broker block below was read from (pinned). Nullable only pre-wiring.
15568
+ */
15569
+ sourceNodeId: string().nullable(),
15654
15570
  pinned: object({
15655
15571
  detection: boolean(),
15656
15572
  decoder: boolean(),
@@ -15783,16 +15699,7 @@ method(object({
15783
15699
  }), object({ success: literal(true) }), {
15784
15700
  kind: "mutation",
15785
15701
  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({
15702
+ }), method(_void(), IngestOwnerSchema), method(object({
15796
15703
  deviceId: number(),
15797
15704
  nodeId: string()
15798
15705
  }), object({ success: literal(true) }), {
@@ -15813,10 +15720,7 @@ method(object({
15813
15720
  nodeId: string(),
15814
15721
  pinned: boolean(),
15815
15722
  assignedAt: number()
15816
- }))), method(object({
15817
- deviceId: number(),
15818
- pipelineNodeId: string().optional()
15819
- }), DecoderAssignmentSchema), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
15723
+ }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
15820
15724
  nodeId: string(),
15821
15725
  settings: AgentPipelineSettingsSchema
15822
15726
  })).readonly()), method(object({
@@ -15846,12 +15750,26 @@ method(object({
15846
15750
  }), method(object({
15847
15751
  agentNodeId: string(),
15848
15752
  detect: boolean().nullable().optional(),
15849
- decode: boolean().nullable().optional(),
15850
15753
  audio: boolean().nullable().optional(),
15851
15754
  ingest: boolean().nullable().optional()
15852
15755
  }), object({ success: literal(true) }), {
15853
15756
  kind: "mutation",
15854
15757
  auth: "admin"
15758
+ }), method(object({
15759
+ agentNodeId: string(),
15760
+ reachableHost: string().nullable()
15761
+ }), object({ success: literal(true) }), {
15762
+ kind: "mutation",
15763
+ auth: "admin"
15764
+ }), method(object({ agentNodeId: string() }), object({
15765
+ success: literal(true),
15766
+ /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
15767
+ effectiveModelId: string().nullable(),
15768
+ /** Number of cameras whose node-scoped overrides were cleared. */
15769
+ clearedCameraOverrides: number()
15770
+ }), {
15771
+ kind: "mutation",
15772
+ auth: "admin"
15855
15773
  }), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
15856
15774
  deviceId: number(),
15857
15775
  addonId: string(),
@@ -15896,22 +15814,131 @@ method(object({
15896
15814
  kind: "mutation",
15897
15815
  auth: "admin"
15898
15816
  });
15899
- var RegisteredStreamSchema = object({
15900
- streamId: string(),
15901
- label: string().optional(),
15902
- codec: string(),
15903
- type: _enum(["video", "audio"]),
15904
- sourceUrl: string()
15817
+ /**
15818
+ * server-management — per-NODE singleton capability for a node's ROOT
15819
+ * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
15820
+ * agents).
15821
+ *
15822
+ * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
15823
+ * on agents) carries the whole software stack in its npm dep tree, so ONE
15824
+ * version describes the node. Updates install into
15825
+ * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
15826
+ * starter (probation boot + auto-rollback to N-1).
15827
+ *
15828
+ * Providers:
15829
+ * - HUB: `ServerUpdateService` behind the `server-provided` mount
15830
+ * (`buildServerProviders` in trpc.router.ts) — the default target for
15831
+ * unpinned calls.
15832
+ * - AGENT: `AgentUpdateService` registered by the agent bootstrap under
15833
+ * the synthetic `agent-runtime` addonId and declared in the agent's
15834
+ * `$hub.registerNode` manifest.
15835
+ *
15836
+ * Node routing: singleton caps get the codegen/runtime-builder `nodeId`
15837
+ * injection on every method — `input.nodeId` (or `nodePin(nodeId)` from the
15838
+ * SDK) routes the call to that node's provider via the standard remote
15839
+ * proxy (`createCapabilityProxy` → `$agent-cap-fwd` → the agent's
15840
+ * in-process provider lookup). No `nodeId` → the hub's own provider.
15841
+ *
15842
+ * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md
15843
+ */
15844
+ /**
15845
+ * Where the running hub's code was loaded from:
15846
+ * - `workspace` — dev checkout (tsx / workspace dist); the starter defers to
15847
+ * plain resolution and runtime updates are refused.
15848
+ * - `baked` — the immutable image seed closure (no data-dir root active).
15849
+ * - `data-root` — the runtime-updatable `<dataDir>/server-root` closure.
15850
+ */
15851
+ var ServerBootModeSchema = _enum([
15852
+ "workspace",
15853
+ "baked",
15854
+ "data-root"
15855
+ ]);
15856
+ /**
15857
+ * Update lifecycle state:
15858
+ * - `idle` / `checking` / `staging` — steady / in-flight registry work.
15859
+ * - `pending-restart` — a version is staged and the node has NOT yet
15860
+ * restarted onto it (still running the OLD version).
15861
+ * - `awaiting-confirmation` — the node HAS restarted onto the staged version
15862
+ * (it is the active probation boot) and is waiting to confirm boot-health.
15863
+ * Apply/rollback are refused in this state and the node must NOT be
15864
+ * manually restarted, or the probation boot auto-rolls-back.
15865
+ */
15866
+ var ServerUpdateStateSchema = _enum([
15867
+ "idle",
15868
+ "checking",
15869
+ "staging",
15870
+ "pending-restart",
15871
+ "awaiting-confirmation"
15872
+ ]);
15873
+ var ServerRollbackInfoSchema = object({
15874
+ /** The version that failed (or was manually rolled back). */
15875
+ fromVersion: string(),
15876
+ /** The version rolled back to; null = the baked seed. */
15877
+ toVersion: string().nullable(),
15878
+ atMs: number(),
15879
+ reason: string()
15905
15880
  });
15906
- var ExposedResourceSchema = object({
15907
- streamId: string(),
15908
- format: string(),
15909
- value: string()
15881
+ var ServerPackageStatusSchema = object({
15882
+ /** Root package name (`@camstack/server` on the hub). */
15883
+ packageName: string(),
15884
+ /** Version of the code the running process ACTUALLY loaded. */
15885
+ runningVersion: string().nullable(),
15886
+ /** Node.js runtime version the node's process runs on (`process.versions.node`). */
15887
+ nodeRuntimeVersion: string().nullable(),
15888
+ /** Active data-dir root version; null when booted from seed/workspace. */
15889
+ activeVersion: string().nullable(),
15890
+ /** N-1 version kept for rollback; null when no previous version exists. */
15891
+ previousVersion: string().nullable(),
15892
+ /** Version of the immutable baked seed closure (image fallback). */
15893
+ seedVersion: string().nullable(),
15894
+ /** Latest registry version from the most recent check (null = never checked). */
15895
+ latestVersion: string().nullable(),
15896
+ updateAvailable: boolean(),
15897
+ bootMode: ServerBootModeSchema,
15898
+ updateState: ServerUpdateStateSchema,
15899
+ /** Version staged + awaiting its probation boot, when one is pending. */
15900
+ pendingVersion: string().nullable(),
15901
+ /** Set when the last freshly-activated version failed its boot health-check. */
15902
+ rolledBack: ServerRollbackInfoSchema.nullable(),
15903
+ /**
15904
+ * True when `server-root/state.json` EXISTS but is unreadable/corrupt — the
15905
+ * hub is running from the baked seed (or workspace) while installed data-dir
15906
+ * versions are being IGNORED. Surfaced as a warning in the UI.
15907
+ */
15908
+ stateFileCorrupt: boolean(),
15909
+ lastCheckedAtMs: number().nullable()
15910
+ });
15911
+ var ServerUpdateCheckResultSchema = object({
15912
+ packageName: string(),
15913
+ runningVersion: string().nullable(),
15914
+ latestVersion: string().nullable(),
15915
+ updateAvailable: boolean(),
15916
+ checkedAtMs: number(),
15917
+ /** Non-null when the registry lookup failed (offline, bad registry, …). */
15918
+ error: string().nullable()
15919
+ });
15920
+ var ServerUpdateActionResultSchema = object({
15921
+ accepted: boolean(),
15922
+ targetVersion: string().nullable(),
15923
+ /** True when a graceful restart was scheduled to apply the change. */
15924
+ restarting: boolean(),
15925
+ message: string()
15926
+ });
15927
+ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
15928
+ kind: "mutation",
15929
+ auth: "admin"
15930
+ }), method(object({
15931
+ /** Explicit target version; omitted = latest from the registry. */
15932
+ version: string().optional() }), ServerUpdateActionResultSchema, {
15933
+ kind: "mutation",
15934
+ auth: "admin"
15935
+ }), method(_void(), ServerUpdateActionResultSchema, {
15936
+ kind: "mutation",
15937
+ auth: "admin"
15938
+ }), method(_void(), ServerUpdateActionResultSchema, {
15939
+ kind: "mutation",
15940
+ auth: "admin"
15910
15941
  });
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
15942
  /**
15916
15943
  * Query filter for settings-store collections.
15917
15944
  */
@@ -16064,9 +16091,9 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
16064
16091
  /**
16065
16092
  * A single device snapshot returned as base64 JPEG/PNG.
16066
16093
  *
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.
16094
+ * The `SnapshotAddon` wrapper returns this shape whether the frame came from
16095
+ * the device-native provider (onboard capture) or from the stream-broker
16096
+ * prebuffer fallback.
16070
16097
  */
16071
16098
  var SnapshotImageSchema = object({
16072
16099
  base64: string(),
@@ -16098,10 +16125,6 @@ DeviceType.Camera, method(object({
16098
16125
  kind: "mutation",
16099
16126
  auth: "admin"
16100
16127
  });
16101
- method(object({ deviceId: number() }), boolean()), method(object({
16102
- deviceId: number(),
16103
- streamId: string().optional()
16104
- }), SnapshotImageSchema.nullable());
16105
16128
  /**
16106
16129
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
16107
16130
  * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
@@ -16462,9 +16485,10 @@ method(object({
16462
16485
  auth: "admin"
16463
16486
  });
16464
16487
  /**
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.
16488
+ * Optional client-side hints sent at session creation to help the provider
16489
+ * pick the best native source. All fields optional — a viewer that knows
16490
+ * nothing still gets a sane default. (Relocated from the retired `webrtc`
16491
+ * collection cap; this `webrtc-session` cap is the live signaling surface.)
16468
16492
  */
16469
16493
  var webrtcClientHintsSchema = object({
16470
16494
  viewportWidth: number().int().positive().optional(),
@@ -16475,22 +16499,6 @@ var webrtcClientHintsSchema = object({
16475
16499
  /** Hard tier override; takes precedence over scoring when registered. */
16476
16500
  prefersTier: string().optional()
16477
16501
  }).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
16502
  /**
16495
16503
  * Discriminated target for a WebRTC session. The client sends this
16496
16504
  * structured object instead of building / parsing brokerId strings;
@@ -17918,6 +17926,16 @@ var TopologyCategorySchema = object({
17918
17926
  healthy: number(),
17919
17927
  addons: array(TopologyCategoryAddonSchema).readonly()
17920
17928
  });
17929
+ /**
17930
+ * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
17931
+ * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
17932
+ * version visibility for the Server management surface. Nullable: offline
17933
+ * rows and pre-phase-2 nodes report none.
17934
+ */
17935
+ var TopologyRootPackageSchema = object({
17936
+ name: string(),
17937
+ version: string()
17938
+ });
17921
17939
  var TopologyNodeSchema = object({
17922
17940
  id: string(),
17923
17941
  name: string(),
@@ -17941,7 +17959,8 @@ var TopologyNodeSchema = object({
17941
17959
  status: string()
17942
17960
  })).readonly(),
17943
17961
  processes: array(TopologyProcessSchema).readonly(),
17944
- categories: array(TopologyCategorySchema).readonly()
17962
+ categories: array(TopologyCategorySchema).readonly(),
17963
+ rootPackage: TopologyRootPackageSchema.nullable()
17945
17964
  });
17946
17965
  var CapUsageEdgeSchema = object({
17947
17966
  callerAddonId: string(),
@@ -20741,6 +20760,12 @@ Object.freeze({
20741
20760
  addonId: null,
20742
20761
  access: "create"
20743
20762
  },
20763
+ "loginMethod.getLoginMethods": {
20764
+ capName: "login-method",
20765
+ capScope: "system",
20766
+ addonId: null,
20767
+ access: "view"
20768
+ },
20744
20769
  "mediaPlayer.next": {
20745
20770
  capName: "media-player",
20746
20771
  capScope: "device",
@@ -21371,23 +21396,23 @@ Object.freeze({
21371
21396
  addonId: null,
21372
21397
  access: "create"
21373
21398
  },
21374
- "pipelineExecutor.deleteModel": {
21399
+ "pipelineExecutor.clearDeviceOverrides": {
21375
21400
  capName: "pipeline-executor",
21376
21401
  capScope: "system",
21377
21402
  addonId: null,
21378
21403
  access: "delete"
21379
21404
  },
21380
- "pipelineExecutor.deleteTemplate": {
21405
+ "pipelineExecutor.deleteModel": {
21381
21406
  capName: "pipeline-executor",
21382
21407
  capScope: "system",
21383
21408
  addonId: null,
21384
21409
  access: "delete"
21385
21410
  },
21386
- "pipelineExecutor.detect": {
21411
+ "pipelineExecutor.deleteTemplate": {
21387
21412
  capName: "pipeline-executor",
21388
21413
  capScope: "system",
21389
21414
  addonId: null,
21390
- access: "view"
21415
+ access: "delete"
21391
21416
  },
21392
21417
  "pipelineExecutor.downloadModel": {
21393
21418
  capName: "pipeline-executor",
@@ -21581,13 +21606,13 @@ Object.freeze({
21581
21606
  addonId: null,
21582
21607
  access: "create"
21583
21608
  },
21584
- "pipelineOrchestrator.assignAudio": {
21585
- capName: "pipeline-orchestrator",
21609
+ "pipelineExecutor.validatePipeline": {
21610
+ capName: "pipeline-executor",
21586
21611
  capScope: "system",
21587
21612
  addonId: null,
21588
- access: "create"
21613
+ access: "view"
21589
21614
  },
21590
- "pipelineOrchestrator.assignDecoder": {
21615
+ "pipelineOrchestrator.assignAudio": {
21591
21616
  capName: "pipeline-orchestrator",
21592
21617
  capScope: "system",
21593
21618
  addonId: null,
@@ -21671,19 +21696,13 @@ Object.freeze({
21671
21696
  addonId: null,
21672
21697
  access: "view"
21673
21698
  },
21674
- "pipelineOrchestrator.getDecoderAssignment": {
21675
- capName: "pipeline-orchestrator",
21676
- capScope: "system",
21677
- addonId: null,
21678
- access: "view"
21679
- },
21680
- "pipelineOrchestrator.getDecoderAssignments": {
21699
+ "pipelineOrchestrator.getGlobalMetrics": {
21681
21700
  capName: "pipeline-orchestrator",
21682
21701
  capScope: "system",
21683
21702
  addonId: null,
21684
21703
  access: "view"
21685
21704
  },
21686
- "pipelineOrchestrator.getGlobalMetrics": {
21705
+ "pipelineOrchestrator.getIngestOwner": {
21687
21706
  capName: "pipeline-orchestrator",
21688
21707
  capScope: "system",
21689
21708
  addonId: null,
@@ -21725,6 +21744,12 @@ Object.freeze({
21725
21744
  addonId: null,
21726
21745
  access: "delete"
21727
21746
  },
21747
+ "pipelineOrchestrator.resetNodePipelineDefaults": {
21748
+ capName: "pipeline-orchestrator",
21749
+ capScope: "system",
21750
+ addonId: null,
21751
+ access: "delete"
21752
+ },
21728
21753
  "pipelineOrchestrator.resolvePipeline": {
21729
21754
  capName: "pipeline-orchestrator",
21730
21755
  capScope: "system",
@@ -21761,37 +21786,37 @@ Object.freeze({
21761
21786
  addonId: null,
21762
21787
  access: "create"
21763
21788
  },
21764
- "pipelineOrchestrator.setCameraPipelineForAgent": {
21789
+ "pipelineOrchestrator.setAgentReachableHost": {
21765
21790
  capName: "pipeline-orchestrator",
21766
21791
  capScope: "system",
21767
21792
  addonId: null,
21768
21793
  access: "create"
21769
21794
  },
21770
- "pipelineOrchestrator.setCameraStepOverride": {
21795
+ "pipelineOrchestrator.setCameraPipelineForAgent": {
21771
21796
  capName: "pipeline-orchestrator",
21772
21797
  capScope: "system",
21773
21798
  addonId: null,
21774
21799
  access: "create"
21775
21800
  },
21776
- "pipelineOrchestrator.setCameraStepToggle": {
21801
+ "pipelineOrchestrator.setCameraStepOverride": {
21777
21802
  capName: "pipeline-orchestrator",
21778
21803
  capScope: "system",
21779
21804
  addonId: null,
21780
21805
  access: "create"
21781
21806
  },
21782
- "pipelineOrchestrator.setCapabilityBinding": {
21807
+ "pipelineOrchestrator.setCameraStepToggle": {
21783
21808
  capName: "pipeline-orchestrator",
21784
21809
  capScope: "system",
21785
21810
  addonId: null,
21786
21811
  access: "create"
21787
21812
  },
21788
- "pipelineOrchestrator.unassignAudio": {
21813
+ "pipelineOrchestrator.setCapabilityBinding": {
21789
21814
  capName: "pipeline-orchestrator",
21790
21815
  capScope: "system",
21791
21816
  addonId: null,
21792
21817
  access: "create"
21793
21818
  },
21794
- "pipelineOrchestrator.unassignDecoder": {
21819
+ "pipelineOrchestrator.unassignAudio": {
21795
21820
  capName: "pipeline-orchestrator",
21796
21821
  capScope: "system",
21797
21822
  addonId: null,
@@ -22091,33 +22116,45 @@ Object.freeze({
22091
22116
  addonId: null,
22092
22117
  access: "create"
22093
22118
  },
22094
- "restreamer.getExposedResources": {
22095
- capName: "restreamer",
22119
+ "scriptRunner.run": {
22120
+ capName: "script-runner",
22121
+ capScope: "device",
22122
+ addonId: null,
22123
+ access: "create"
22124
+ },
22125
+ "scriptRunner.stop": {
22126
+ capName: "script-runner",
22127
+ capScope: "device",
22128
+ addonId: null,
22129
+ access: "create"
22130
+ },
22131
+ "serverManagement.applyServerUpdate": {
22132
+ capName: "server-management",
22096
22133
  capScope: "system",
22097
22134
  addonId: null,
22098
- access: "view"
22135
+ access: "create"
22099
22136
  },
22100
- "restreamer.registerDevice": {
22101
- capName: "restreamer",
22137
+ "serverManagement.checkServerUpdate": {
22138
+ capName: "server-management",
22102
22139
  capScope: "system",
22103
22140
  addonId: null,
22104
22141
  access: "create"
22105
22142
  },
22106
- "restreamer.unregisterDevice": {
22107
- capName: "restreamer",
22143
+ "serverManagement.getServerPackageStatus": {
22144
+ capName: "server-management",
22108
22145
  capScope: "system",
22109
22146
  addonId: null,
22110
- access: "delete"
22147
+ access: "view"
22111
22148
  },
22112
- "scriptRunner.run": {
22113
- capName: "script-runner",
22114
- capScope: "device",
22149
+ "serverManagement.restartServer": {
22150
+ capName: "server-management",
22151
+ capScope: "system",
22115
22152
  addonId: null,
22116
22153
  access: "create"
22117
22154
  },
22118
- "scriptRunner.stop": {
22119
- capName: "script-runner",
22120
- capScope: "device",
22155
+ "serverManagement.rollbackServerUpdate": {
22156
+ capName: "server-management",
22157
+ capScope: "system",
22121
22158
  addonId: null,
22122
22159
  access: "create"
22123
22160
  },
@@ -22211,18 +22248,6 @@ Object.freeze({
22211
22248
  addonId: null,
22212
22249
  access: "create"
22213
22250
  },
22214
- "snapshotProvider.getSnapshot": {
22215
- capName: "snapshot-provider",
22216
- capScope: "system",
22217
- addonId: null,
22218
- access: "view"
22219
- },
22220
- "snapshotProvider.supportsDevice": {
22221
- capName: "snapshot-provider",
22222
- capScope: "system",
22223
- addonId: null,
22224
- access: "view"
22225
- },
22226
22251
  "ssoBridge.signBridgeToken": {
22227
22252
  capName: "sso-bridge",
22228
22253
  capScope: "system",
@@ -22649,30 +22674,6 @@ Object.freeze({
22649
22674
  addonId: null,
22650
22675
  access: "view"
22651
22676
  },
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
22677
  "streamParams.getConfigSchema": {
22677
22678
  capName: "stream-params",
22678
22679
  capScope: "device",
@@ -23021,6 +23022,18 @@ Object.freeze({
23021
23022
  addonId: null,
23022
23023
  access: "view"
23023
23024
  },
23025
+ "viewerUi.getStaticDir": {
23026
+ capName: "viewer-ui",
23027
+ capScope: "system",
23028
+ addonId: null,
23029
+ access: "view"
23030
+ },
23031
+ "viewerUi.getVersion": {
23032
+ capName: "viewer-ui",
23033
+ capScope: "system",
23034
+ addonId: null,
23035
+ access: "view"
23036
+ },
23024
23037
  "waterHeater.setAway": {
23025
23038
  capName: "water-heater",
23026
23039
  capScope: "device",
@@ -23039,54 +23052,6 @@ Object.freeze({
23039
23052
  addonId: null,
23040
23053
  access: "create"
23041
23054
  },
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
23055
  "webrtcSession.addIceCandidate": {
23091
23056
  capName: "webrtc-session",
23092
23057
  capScope: "device",
@@ -23935,7 +23900,7 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
23935
23900
  if (typeof this.config.deviceId === "number") seedParts.push(String(this.config.deviceId));
23936
23901
  if (typeof this.config.tag === "string" && this.config.tag.length > 0) seedParts.push(this.config.tag);
23937
23902
  const seed = seedParts.length > 0 ? seedParts.join(":") : "anon";
23938
- this.frameRingSink = new DecoderFrameRingSink({
23903
+ this.frameRingSink = new _camstack_shm_ring.DecoderFrameRingSink({
23939
23904
  seed,
23940
23905
  logger: this.logger,
23941
23906
  nodeId: this.nodeId
@@ -25789,7 +25754,7 @@ var DecoderNodeAvAddon = class extends BaseAddon {
25789
25754
  return registrations;
25790
25755
  }
25791
25756
  this.ctx.logger.info("node-av decoder addon initialized", { meta: { selectedBackend: backend } });
25792
- const purged = purgeOrphanSegments(SEGMENT_NAME_PREFIX);
25757
+ const purged = purgeOrphanSegments(_camstack_shm_ring.SEGMENT_NAME_PREFIX);
25793
25758
  if (purged.removed > 0) this.ctx.logger.warn("node-av decoder: reclaimed orphaned shm segments at startup", { meta: {
25794
25759
  removed: purged.removed,
25795
25760
  scanned: purged.scanned
@@ -26050,7 +26015,7 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26050
26015
  return {
26051
26016
  sessionId: input.sessionId,
26052
26017
  ...stats,
26053
- budgetMb: RING_BUDGET_MB,
26018
+ budgetMb: _camstack_shm_ring.RING_BUDGET_MB,
26054
26019
  getFrameHits: this.getFrameHits,
26055
26020
  getFrameMisses: this.getFrameMisses
26056
26021
  };
@@ -26076,7 +26041,12 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26076
26041
  }
26077
26042
  };
26078
26043
  //#endregion
26079
- exports.DecoderFrameRingSink = DecoderFrameRingSink;
26044
+ Object.defineProperty(exports, "DecoderFrameRingSink", {
26045
+ enumerable: true,
26046
+ get: function() {
26047
+ return _camstack_shm_ring.DecoderFrameRingSink;
26048
+ }
26049
+ });
26080
26050
  exports.DecoderNodeAvAddon = DecoderNodeAvAddon;
26081
26051
  exports.NodeAvAudioCodecProvider = NodeAvAudioCodecProvider;
26082
26052
  exports.NodeAvAudioDecodeSession = NodeAvAudioDecodeSession;
@@ -26084,5 +26054,10 @@ exports.NodeAvAudioEncodeSession = NodeAvAudioEncodeSession;
26084
26054
  exports.NodeAvDecoderSession = NodeAvDecoderSession;
26085
26055
  exports.default = DecoderNodeAvAddon;
26086
26056
  exports.loadNodeAvRuntime = loadNodeAvRuntime;
26087
- exports.makeSegmentName = makeSegmentName;
26057
+ Object.defineProperty(exports, "makeSegmentName", {
26058
+ enumerable: true,
26059
+ get: function() {
26060
+ return _camstack_shm_ring.makeSegmentName;
26061
+ }
26062
+ });
26088
26063
  exports.peekNodeAvRuntime = peekNodeAvRuntime;