@camstack/addon-provider-onvif 1.1.19 → 1.1.21

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/addon.js +298 -185
  2. package/dist/addon.mjs +298 -185
  3. package/package.json +1 -4
package/dist/addon.mjs CHANGED
@@ -4632,7 +4632,7 @@ function _instanceof(cls, params = {}) {
4632
4632
  return inst;
4633
4633
  }
4634
4634
  //#endregion
4635
- //#region ../types/dist/sleep-CZDdRBua.mjs
4635
+ //#region ../types/dist/sleep-Cc14_yxc.mjs
4636
4636
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4637
4637
  EventCategory["SystemBoot"] = "system.boot";
4638
4638
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -4818,6 +4818,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
4818
4818
  */
4819
4819
  EventCategory["PipelineCameraUpdated"] = "pipeline.camera-updated";
4820
4820
  /**
4821
+ * The cluster camera-source OWNER changed (`clusterRoles.ingestNode`).
4822
+ * Emitted by addon-pipeline-orchestrator whenever it (re)derives node
4823
+ * capabilities — at boot, on agent online/offline, and on an ingest-node
4824
+ * flip. Carries the resolved `ownerNodeId`. The stream-broker consumes it to
4825
+ * keep its ingest-owner-gate decision current WITHOUT a per-`ensureBroker`
4826
+ * cross-process `getIngestOwner` query (push the authority's decision instead
4827
+ * of polling it on the hot path). Idempotent state — re-emitted on every
4828
+ * topology change, so a dropped event self-heals on the next one (plus the
4829
+ * broker's long backstop reconcile query).
4830
+ */
4831
+ EventCategory["PipelineIngestOwnerChanged"] = "pipeline.ingest-owner-changed";
4832
+ /**
4821
4833
  * Periodic snapshot of per-node pipeline-runner load
4822
4834
  * (`RunnerLocalLoad`). Emitted ~1Hz by every runner so UI dashboards
4823
4835
  * subscribe instead of polling `pipelineRunner.getLocalLoad`.
@@ -6909,6 +6921,36 @@ var ModelFormatsSchema = object({
6909
6921
  tflite: ModelFormatEntrySchema.optional(),
6910
6922
  pt: ModelFormatEntrySchema.optional()
6911
6923
  });
6924
+ /**
6925
+ * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
6926
+ * the reduced `PipelineModelOption` returned in `pipeline.getSchema()` so the
6927
+ * grouped Family→Tier→Variant picker renders identically in the config UI and
6928
+ * in the pipeline/device steppers. The flat `id` stays the source of truth for
6929
+ * resolution/download/persistence; this is a presentation overlay resolved back
6930
+ * to an `id`.
6931
+ */
6932
+ var ModelVariantGroupSchema = object({
6933
+ /** Top-level family, e.g. `yolo26` (later `d-fine`, `rf-detr`). */
6934
+ family: string(),
6935
+ /** Size within the family, e.g. `n` | `s` | `m` | `l`. */
6936
+ tier: string(),
6937
+ /** Quantization axis. Omit ⇒ the fp32 base build. */
6938
+ precision: _enum(["fp32", "int8"]).optional(),
6939
+ /**
6940
+ * Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
6941
+ * latency-optimized export (e.g. ReLU-activation variant) — the slot the
6942
+ * future performance variants plug into.
6943
+ */
6944
+ optimization: _enum(["standard", "fast"]).optional(),
6945
+ /**
6946
+ * Input-resolution axis (square input side, px). Omit ⇒ the family's native
6947
+ * resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
6948
+ * cheap latency lever — especially on Apple ANE and the Intel N100 — at a
6949
+ * small-object accuracy cost. Mirrors the model's `inputSize` but lifted onto
6950
+ * the group so the selector can offer it as a variant axis.
6951
+ */
6952
+ resolution: number().int().positive().optional()
6953
+ });
6912
6954
  var ModelCatalogEntrySchema = object({
6913
6955
  id: string(),
6914
6956
  name: string(),
@@ -6938,7 +6980,43 @@ var ModelCatalogEntrySchema = object({
6938
6980
  * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
6939
6981
  * Downloaded into the same modelsDir alongside the model file.
6940
6982
  */
6941
- extraFiles: array(ModelExtraFileSchema).readonly().optional()
6983
+ extraFiles: array(ModelExtraFileSchema).readonly().optional(),
6984
+ /**
6985
+ * LEGACY entry — retained in the catalog so a persisted operator selection
6986
+ * still RESOLVES (and can be re-activated), but hidden from the selectable
6987
+ * model list and excluded from the auto format-default pick. Set on the
6988
+ * superseded / consolidated models (older lineages, redundant fp16 IRs) so
6989
+ * the active lineup stays the coherent curated ladder without deleting a
6990
+ * model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
6991
+ * an explicit legacy id that has a build for the node's format.
6992
+ */
6993
+ legacy: boolean().optional(),
6994
+ /**
6995
+ * Measured quality/latency metadata — populated from the benchmark addon on
6996
+ * the real node classes. Absent = not yet measured (most entries today; the
6997
+ * catalog historically carried only `sizeMB`, a poor cross-architecture
6998
+ * speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
6999
+ */
7000
+ metrics: object({
7001
+ map50: number().optional(),
7002
+ p95LatencyMs: record(string(), number()).optional()
7003
+ }).optional(),
7004
+ /**
7005
+ * SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
7006
+ * YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
7007
+ * the retraining addon and any future commercial distribution.
7008
+ */
7009
+ license: string().optional(),
7010
+ /**
7011
+ * Variant-selector grouping. The UI groups models by `family` + `tier` and
7012
+ * offers `precision` / `optimization` as variant axes WITHIN a tier — so all
7013
+ * of a family's sizes and quantizations collapse into one grouped picker
7014
+ * instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
7015
+ * (legacy / custom models) — never shown in the grouped selector. The flat
7016
+ * `id` stays the source of truth for resolution/download/persistence; grouping
7017
+ * is a presentation overlay resolved back to an `id`.
7018
+ */
7019
+ group: ModelVariantGroupSchema.optional()
6942
7020
  });
6943
7021
  var ConvertTargetSchema = discriminatedUnion("format", [object({
6944
7022
  format: literal("openvino"),
@@ -6999,8 +7077,8 @@ var RecordingModeSchema = _enum([
6999
7077
  "onAudioThreshold"
7000
7078
  ]);
7001
7079
  /**
7002
- * First-class, authoritative per-camera storage mode — the netta choice the UI
7003
- * reads directly (never inferred from `rules`):
7080
+ * First-class, authoritative per-camera storage mode — the explicit choice the
7081
+ * UI reads directly (never inferred from `rules`):
7004
7082
  * - `off` — not recording.
7005
7083
  * - `events` — record only around triggers (motion / audio threshold),
7006
7084
  * with pre/post-buffer.
@@ -8361,6 +8439,72 @@ var DeviceConfig = class DeviceConfig {
8361
8439
  }));
8362
8440
  }
8363
8441
  };
8442
+ /** Reject after `ms`; always clears its own timer. */
8443
+ async function withTimeout(promise, ms, label) {
8444
+ let timer;
8445
+ const timeout = new Promise((_resolve, reject) => {
8446
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`${label} timed out after ${String(ms)}ms`)), ms);
8447
+ });
8448
+ try {
8449
+ return await Promise.race([promise, timeout]);
8450
+ } finally {
8451
+ if (timer !== void 0) clearTimeout(timer);
8452
+ }
8453
+ }
8454
+ /**
8455
+ * Start the reachability poll loop. Returns a handle whose `stop()` clears the
8456
+ * timer and prevents any further ticks. Start on device activation, stop on
8457
+ * device teardown (`removeDevice`) so no timer leaks.
8458
+ */
8459
+ function startReachabilityPoll(options) {
8460
+ const intervalMs = options.intervalMs ?? 3e4;
8461
+ const failuresToOffline = options.failuresToOffline ?? 3;
8462
+ const probeTimeoutMs = options.probeTimeoutMs ?? 1e4;
8463
+ const runImmediately = options.runImmediately ?? true;
8464
+ let stopped = false;
8465
+ let running = false;
8466
+ let consecutiveFailures = 0;
8467
+ let timer;
8468
+ const tick = async () => {
8469
+ if (stopped) return;
8470
+ if (running) return;
8471
+ if (options.isEnabled && !options.isEnabled()) return;
8472
+ running = true;
8473
+ try {
8474
+ const reachable = await withTimeout(Promise.resolve().then(options.probe), probeTimeoutMs, "reachability probe");
8475
+ if (stopped) return;
8476
+ if (reachable) {
8477
+ consecutiveFailures = 0;
8478
+ options.setOnline(true);
8479
+ } else registerFailure("probe resolved unreachable");
8480
+ } catch (error) {
8481
+ if (stopped) return;
8482
+ registerFailure(error instanceof Error ? error.message : "probe threw");
8483
+ } finally {
8484
+ running = false;
8485
+ }
8486
+ };
8487
+ const registerFailure = (reason) => {
8488
+ consecutiveFailures += 1;
8489
+ options.logger?.debug("reachability probe failed", {
8490
+ reason,
8491
+ consecutiveFailures,
8492
+ failuresToOffline
8493
+ });
8494
+ if (consecutiveFailures >= failuresToOffline) options.setOnline(false);
8495
+ };
8496
+ timer = setInterval(() => {
8497
+ tick();
8498
+ }, intervalMs);
8499
+ if (typeof timer === "object" && timer !== null && "unref" in timer) timer.unref();
8500
+ if (runImmediately) tick();
8501
+ return { stop: () => {
8502
+ if (stopped) return;
8503
+ stopped = true;
8504
+ if (timer !== void 0) clearInterval(timer);
8505
+ timer = void 0;
8506
+ } };
8507
+ }
8364
8508
  /**
8365
8509
  * Generic device-level status snapshot. Auto-registered by `BaseDevice`
8366
8510
  * for every device, regardless of provider — the kernel needs a uniform
@@ -8752,26 +8896,13 @@ DeviceType.Light, method(object({
8752
8896
  percentage: number().min(0).max(100),
8753
8897
  lastChangedAt: number()
8754
8898
  });
8899
+ /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8755
8900
  var StreamFormatSchema = _enum([
8756
8901
  "webrtc",
8757
8902
  "hls",
8758
8903
  "mjpeg",
8759
8904
  "rtsp"
8760
8905
  ]);
8761
- var StreamInfoSchema = object({
8762
- streamId: string(),
8763
- format: StreamFormatSchema,
8764
- url: string().nullable(),
8765
- active: boolean()
8766
- });
8767
- method(object({
8768
- streamId: string(),
8769
- sourceUrl: string(),
8770
- codec: string().optional()
8771
- }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
8772
- streamId: string(),
8773
- format: StreamFormatSchema
8774
- }), string().nullable()), method(_void(), array(StreamInfoSchema));
8775
8906
  var RtspRestreamEntrySchema = object({
8776
8907
  brokerId: string(),
8777
8908
  url: string(),
@@ -9436,7 +9567,7 @@ var ConsumablesStatusSchema = object({
9436
9567
  })),
9437
9568
  lastChangedAt: number()
9438
9569
  });
9439
- 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({
9570
+ Object.values(DeviceType), method(object({
9440
9571
  deviceId: number().int().nonnegative(),
9441
9572
  key: string().min(1)
9442
9573
  }), _void(), {
@@ -10486,7 +10617,6 @@ var PipelineDefaultStepSchema = lazy(() => object({
10486
10617
  enabled: boolean(),
10487
10618
  modelId: string(),
10488
10619
  children: array(PipelineDefaultStepSchema).readonly(),
10489
- engine: PipelineEngineChoiceSchema.optional(),
10490
10620
  group: string().optional(),
10491
10621
  settings: record(string(), unknown()).optional()
10492
10622
  }));
@@ -10511,7 +10641,9 @@ var PipelineModelOptionSchema = object({
10511
10641
  formats: record(string(), object({
10512
10642
  downloaded: boolean(),
10513
10643
  sizeMB: number()
10514
- }))
10644
+ })),
10645
+ group: ModelVariantGroupSchema.optional(),
10646
+ legacy: boolean().optional()
10515
10647
  });
10516
10648
  var ConfigFieldBridge = custom();
10517
10649
  var PipelineAddonSchemaSchema = object({
@@ -10562,15 +10694,42 @@ var EngineProvisioningSchema = object({
10562
10694
  ]),
10563
10695
  progress: number().optional(),
10564
10696
  error: string().optional(),
10565
- nextRetryAt: number().optional()
10697
+ nextRetryAt: number().optional(),
10698
+ /**
10699
+ * Gate A (config-correctness gate at engine change): human-readable
10700
+ * config issues surfaced EAGERLY when the node's engine changes — model
10701
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
10702
+ * has a <format> build"). Additive/optional: informational only, never
10703
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
10704
+ * Absent/empty when the node-default tree resolves cleanly.
10705
+ */
10706
+ configIssues: array(string()).optional()
10566
10707
  });
10567
10708
  var PipelineStepInputSchema = lazy(() => object({
10568
10709
  addonId: string(),
10569
- modelId: string(),
10710
+ modelId: string().optional(),
10570
10711
  enabled: boolean().default(true),
10571
10712
  children: array(PipelineStepInputSchema).optional(),
10572
10713
  settings: record(string(), unknown()).optional()
10573
10714
  }));
10715
+ var ModelSubstitutionSchema = object({
10716
+ addonId: string(),
10717
+ chosen: string(),
10718
+ running: string(),
10719
+ format: string()
10720
+ });
10721
+ var PipelineValidationIssueSchema = object({
10722
+ addonId: string(),
10723
+ kind: _enum(["unknown-addon", "no-format-build"]),
10724
+ detail: string()
10725
+ });
10726
+ var PipelineValidationResultSchema = object({
10727
+ ok: boolean(),
10728
+ issues: array(PipelineValidationIssueSchema).readonly(),
10729
+ substitutions: array(ModelSubstitutionSchema).readonly(),
10730
+ /** The node's `currentEngine.format` this validation ran against. */
10731
+ format: string()
10732
+ });
10574
10733
  var ReferenceImageEntrySchema = object({
10575
10734
  filename: string(),
10576
10735
  stepIds: array(string()).readonly().optional()
@@ -10641,7 +10800,13 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10641
10800
  })) }), object({ success: literal(true) }), {
10642
10801
  kind: "mutation",
10643
10802
  auth: "admin"
10644
- }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
10803
+ }), method(object({ nodeId: string() }), object({
10804
+ success: literal(true),
10805
+ regeneratedModelId: string().nullable()
10806
+ }), {
10807
+ kind: "mutation",
10808
+ auth: "admin"
10809
+ }), 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({
10645
10810
  name: string(),
10646
10811
  steps: array(PipelineTemplateStepSchema).readonly(),
10647
10812
  engine: PipelineEngineChoiceSchema
@@ -10901,6 +11066,13 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
10901
11066
  kind: literal("remote-restream"),
10902
11067
  /** The camera's source-owner node (slice 1: always the hub). */
10903
11068
  ownerNodeId: string(),
11069
+ /**
11070
+ * The owner's LAN-reachable host, resolved by the orchestrator from the
11071
+ * per-node `reachableHost` override (Cluster UI). When present the runner
11072
+ * dials THIS host for the owner's restream, in preference to the
11073
+ * `CAMSTACK_HUB_URL`-derived default. Absent → auto-detect fallback.
11074
+ */
11075
+ ownerReachableHost: string().optional(),
10904
11076
  /** Operator override for the owner host the runner dials. */
10905
11077
  hubHostnameOverride: string().optional()
10906
11078
  })]).describe("Per-camera frame-source mode for the runner (P2c)");
@@ -10909,13 +11081,11 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
10909
11081
  * specific runner instance via `attachCamera`. Carries everything the
10910
11082
  * runner needs to subscribe to the local broker and execute inference.
10911
11083
  *
10912
- * Stateless-pipeline model: the full pipeline content (`engine`, `steps`,
10913
- * optional `audio`) travels with the attach payload. The runner keeps it
10914
- * in RAM for the lifetime of the attach — on rebalance, edit, or
10915
- * restart the orchestrator re-sends the latest snapshot.
10916
- *
10917
- * `engine`/`steps`/`audio` are optional during the additive migration
10918
- * window; once orchestrator + UI are migrated they become required.
11084
+ * Stateless-pipeline model: the pipeline content (`steps`, optional
11085
+ * `audio`) travels with the attach payload. The runner keeps it in RAM
11086
+ * for the lifetime of the attach — on rebalance, edit, or restart the
11087
+ * orchestrator re-sends the latest snapshot. Engine is NOT carried: it is
11088
+ * node-local, resolved by the executing runner at dispatch time.
10919
11089
  */
10920
11090
  var RunnerCameraConfigSchema = object({
10921
11091
  deviceId: number(),
@@ -10966,14 +11136,11 @@ var RunnerCameraConfigSchema = object({
10966
11136
  */
10967
11137
  motionSources: MotionSourcesSchema.default(["analyzer"]),
10968
11138
  pipelineEnabled: boolean().default(true),
10969
- /** Engine choice for video steps (runtime+backend+format). */
10970
- engine: PipelineEngineChoiceSchema.optional(),
10971
11139
  /** Ordered tree of video steps. Absent → runner skips video detection. */
10972
11140
  steps: array(PipelineStepInputSchema).readonly().optional(),
10973
11141
  /** Audio classification branch. `enabled:false` disables, null skips. */
10974
11142
  audio: object({
10975
- engine: PipelineEngineChoiceSchema,
10976
- modelId: string(),
11143
+ modelId: string().optional(),
10977
11144
  enabled: boolean()
10978
11145
  }).nullable().optional(),
10979
11146
  /**
@@ -15493,11 +15660,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15493
15660
  timestamp: number()
15494
15661
  });
15495
15662
  var CameraPipelineConfigSchema = object({
15496
- engine: PipelineEngineChoiceSchema,
15663
+ engine: PipelineEngineChoiceSchema.optional(),
15497
15664
  steps: array(PipelineStepInputSchema).readonly(),
15498
15665
  audio: object({
15499
- engine: PipelineEngineChoiceSchema,
15500
- modelId: string(),
15666
+ engine: PipelineEngineChoiceSchema.optional(),
15667
+ modelId: string().optional(),
15501
15668
  enabled: boolean(),
15502
15669
  settings: record(string(), unknown()).readonly().optional()
15503
15670
  }).nullable().optional()
@@ -15512,7 +15679,7 @@ var PipelineTemplateSchema = object({
15512
15679
  });
15513
15680
  var AgentAddonConfigSchema = object({
15514
15681
  enabled: boolean(),
15515
- modelId: string(),
15682
+ modelId: string().optional(),
15516
15683
  settings: record(string(), unknown()).readonly()
15517
15684
  });
15518
15685
  var AgentPipelineSettingsSchema = object({
@@ -15527,7 +15694,15 @@ var AgentPipelineSettingsSchema = object({
15527
15694
  /** Node is eligible to run audio-analyzer sessions. */
15528
15695
  audio: boolean().optional(),
15529
15696
  /** Node is eligible to be the ingest / source-owner (serve the restream). */
15530
- ingest: boolean().optional()
15697
+ ingest: boolean().optional(),
15698
+ /**
15699
+ * Operator override for the LAN host a cross-node decoder dials to reach
15700
+ * THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
15701
+ * falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
15702
+ * it already uses to reach the hub). Set this only when the auto-detected
15703
+ * address is wrong (multi-homed host, NAT, custom interface).
15704
+ */
15705
+ reachableHost: string().optional()
15531
15706
  });
15532
15707
  var CameraPipelineForAgentSchema = object({
15533
15708
  steps: array(PipelineStepInputSchema).readonly(),
@@ -15633,6 +15808,15 @@ var GlobalMetricsSchema = object({
15633
15808
  * capability providers.
15634
15809
  */
15635
15810
  var CapabilityBindingsSchema = record(string(), string());
15811
+ /**
15812
+ * The cluster's single camera-source owner (`clusterRoles.ingestNode`) plus
15813
+ * its LAN-reachable host, if one is registered. See `getIngestOwner`.
15814
+ */
15815
+ var IngestOwnerSchema = object({
15816
+ ownerNodeId: string(),
15817
+ reachableHost: string().optional(),
15818
+ configIssue: string().optional()
15819
+ });
15636
15820
  /** Source block — always present; derives from the stream catalog. */
15637
15821
  var CameraSourceStatusSchema = object({ streams: array(object({
15638
15822
  camStreamId: string(),
@@ -15647,6 +15831,14 @@ var CameraAssignmentStatusSchema = object({
15647
15831
  detectionNodeId: string().nullable(),
15648
15832
  decoderNodeId: string().nullable(),
15649
15833
  audioNodeId: string().nullable(),
15834
+ /**
15835
+ * The node that OWNS this camera's physical source pull (dials the RTSP and
15836
+ * hosts the broker/restream) — the cluster ingest owner today
15837
+ * (`clusterRoles.ingestNode`), per-camera once source assignment lands. Lets
15838
+ * the UI show WHERE a camera is sourced without SSH/logs, and is the node the
15839
+ * broker block below was read from (pinned). Nullable only pre-wiring.
15840
+ */
15841
+ sourceNodeId: string().nullable(),
15650
15842
  pinned: object({
15651
15843
  detection: boolean(),
15652
15844
  decoder: boolean(),
@@ -15779,7 +15971,7 @@ method(object({
15779
15971
  }), object({ success: literal(true) }), {
15780
15972
  kind: "mutation",
15781
15973
  auth: "admin"
15782
- }), method(object({
15974
+ }), method(_void(), IngestOwnerSchema), method(object({
15783
15975
  deviceId: number(),
15784
15976
  nodeId: string()
15785
15977
  }), _void(), {
@@ -15848,6 +16040,12 @@ method(object({
15848
16040
  }), object({ success: literal(true) }), {
15849
16041
  kind: "mutation",
15850
16042
  auth: "admin"
16043
+ }), method(object({
16044
+ agentNodeId: string(),
16045
+ reachableHost: string().nullable()
16046
+ }), object({ success: literal(true) }), {
16047
+ kind: "mutation",
16048
+ auth: "admin"
15851
16049
  }), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
15852
16050
  deviceId: number(),
15853
16051
  addonId: string(),
@@ -15892,22 +16090,6 @@ method(object({
15892
16090
  kind: "mutation",
15893
16091
  auth: "admin"
15894
16092
  });
15895
- var RegisteredStreamSchema = object({
15896
- streamId: string(),
15897
- label: string().optional(),
15898
- codec: string(),
15899
- type: _enum(["video", "audio"]),
15900
- sourceUrl: string()
15901
- });
15902
- var ExposedResourceSchema = object({
15903
- streamId: string(),
15904
- format: string(),
15905
- value: string()
15906
- });
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
16093
  /**
15912
16094
  * Query filter for settings-store collections.
15913
16095
  */
@@ -16060,9 +16242,9 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
16060
16242
  /**
16061
16243
  * A single device snapshot returned as base64 JPEG/PNG.
16062
16244
  *
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.
16245
+ * The `SnapshotAddon` wrapper returns this shape whether the frame came from
16246
+ * the device-native provider (onboard capture) or from the stream-broker
16247
+ * prebuffer fallback.
16066
16248
  */
16067
16249
  var SnapshotImageSchema = object({
16068
16250
  base64: string(),
@@ -16137,10 +16319,6 @@ var snapshotCapability = {
16137
16319
  kind: "poll"
16138
16320
  }
16139
16321
  };
16140
- method(object({ deviceId: number() }), boolean()), method(object({
16141
- deviceId: number(),
16142
- streamId: string().optional()
16143
- }), SnapshotImageSchema.nullable());
16144
16322
  /**
16145
16323
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
16146
16324
  * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
@@ -16501,9 +16679,10 @@ method(object({
16501
16679
  auth: "admin"
16502
16680
  });
16503
16681
  /**
16504
- * Optional client-side hints sent at session creation to help the
16505
- * provider pick the best native source. All fields are optional —
16506
- * a viewer that knows nothing still gets a sane default.
16682
+ * Optional client-side hints sent at session creation to help the provider
16683
+ * pick the best native source. All fields optional — a viewer that knows
16684
+ * nothing still gets a sane default. (Relocated from the retired `webrtc`
16685
+ * collection cap; this `webrtc-session` cap is the live signaling surface.)
16507
16686
  */
16508
16687
  var webrtcClientHintsSchema = object({
16509
16688
  viewportWidth: number().int().positive().optional(),
@@ -16514,22 +16693,6 @@ var webrtcClientHintsSchema = object({
16514
16693
  /** Hard tier override; takes precedence over scoring when registered. */
16515
16694
  prefersTier: string().optional()
16516
16695
  }).partial();
16517
- method(object({
16518
- streamId: string(),
16519
- sdpOffer: string()
16520
- }), string(), { kind: "mutation" }), method(object({ streamId: string() }), boolean()), method(object({
16521
- streamId: string(),
16522
- codec: string()
16523
- }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
16524
- streamId: string(),
16525
- hints: webrtcClientHintsSchema.optional()
16526
- }), object({
16527
- sessionId: string(),
16528
- sdpOffer: string()
16529
- }), { kind: "mutation" }), method(object({
16530
- sessionId: string(),
16531
- sdpAnswer: string()
16532
- }), _void(), { kind: "mutation" }), method(object({ sessionId: string() }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), boolean());
16533
16696
  /**
16534
16697
  * Discriminated target for a WebRTC session. The client sends this
16535
16698
  * structured object instead of building / parsing brokerId strings;
@@ -21614,6 +21777,12 @@ Object.freeze({
21614
21777
  addonId: null,
21615
21778
  access: "create"
21616
21779
  },
21780
+ "pipelineExecutor.resetToDefault": {
21781
+ capName: "pipeline-executor",
21782
+ capScope: "system",
21783
+ addonId: null,
21784
+ access: "delete"
21785
+ },
21617
21786
  "pipelineExecutor.runAudioTest": {
21618
21787
  capName: "pipeline-executor",
21619
21788
  capScope: "system",
@@ -21662,6 +21831,12 @@ Object.freeze({
21662
21831
  addonId: null,
21663
21832
  access: "create"
21664
21833
  },
21834
+ "pipelineExecutor.validatePipeline": {
21835
+ capName: "pipeline-executor",
21836
+ capScope: "system",
21837
+ addonId: null,
21838
+ access: "view"
21839
+ },
21665
21840
  "pipelineOrchestrator.assignAudio": {
21666
21841
  capName: "pipeline-orchestrator",
21667
21842
  capScope: "system",
@@ -21770,6 +21945,12 @@ Object.freeze({
21770
21945
  addonId: null,
21771
21946
  access: "view"
21772
21947
  },
21948
+ "pipelineOrchestrator.getIngestOwner": {
21949
+ capName: "pipeline-orchestrator",
21950
+ capScope: "system",
21951
+ addonId: null,
21952
+ access: "view"
21953
+ },
21773
21954
  "pipelineOrchestrator.getPipelineAssignment": {
21774
21955
  capName: "pipeline-orchestrator",
21775
21956
  capScope: "system",
@@ -21842,6 +22023,12 @@ Object.freeze({
21842
22023
  addonId: null,
21843
22024
  access: "create"
21844
22025
  },
22026
+ "pipelineOrchestrator.setAgentReachableHost": {
22027
+ capName: "pipeline-orchestrator",
22028
+ capScope: "system",
22029
+ addonId: null,
22030
+ access: "create"
22031
+ },
21845
22032
  "pipelineOrchestrator.setCameraPipelineForAgent": {
21846
22033
  capName: "pipeline-orchestrator",
21847
22034
  capScope: "system",
@@ -22172,24 +22359,6 @@ Object.freeze({
22172
22359
  addonId: null,
22173
22360
  access: "create"
22174
22361
  },
22175
- "restreamer.getExposedResources": {
22176
- capName: "restreamer",
22177
- capScope: "system",
22178
- addonId: null,
22179
- access: "view"
22180
- },
22181
- "restreamer.registerDevice": {
22182
- capName: "restreamer",
22183
- capScope: "system",
22184
- addonId: null,
22185
- access: "create"
22186
- },
22187
- "restreamer.unregisterDevice": {
22188
- capName: "restreamer",
22189
- capScope: "system",
22190
- addonId: null,
22191
- access: "delete"
22192
- },
22193
22362
  "scriptRunner.run": {
22194
22363
  capName: "script-runner",
22195
22364
  capScope: "device",
@@ -22292,18 +22461,6 @@ Object.freeze({
22292
22461
  addonId: null,
22293
22462
  access: "create"
22294
22463
  },
22295
- "snapshotProvider.getSnapshot": {
22296
- capName: "snapshot-provider",
22297
- capScope: "system",
22298
- addonId: null,
22299
- access: "view"
22300
- },
22301
- "snapshotProvider.supportsDevice": {
22302
- capName: "snapshot-provider",
22303
- capScope: "system",
22304
- addonId: null,
22305
- access: "view"
22306
- },
22307
22464
  "ssoBridge.signBridgeToken": {
22308
22465
  capName: "sso-bridge",
22309
22466
  capScope: "system",
@@ -22730,30 +22887,6 @@ Object.freeze({
22730
22887
  addonId: null,
22731
22888
  access: "view"
22732
22889
  },
22733
- "streamingEngine.getStreamUrl": {
22734
- capName: "streaming-engine",
22735
- capScope: "system",
22736
- addonId: null,
22737
- access: "view"
22738
- },
22739
- "streamingEngine.listStreams": {
22740
- capName: "streaming-engine",
22741
- capScope: "system",
22742
- addonId: null,
22743
- access: "view"
22744
- },
22745
- "streamingEngine.registerStream": {
22746
- capName: "streaming-engine",
22747
- capScope: "system",
22748
- addonId: null,
22749
- access: "create"
22750
- },
22751
- "streamingEngine.unregisterStream": {
22752
- capName: "streaming-engine",
22753
- capScope: "system",
22754
- addonId: null,
22755
- access: "delete"
22756
- },
22757
22890
  "streamParams.getConfigSchema": {
22758
22891
  capName: "stream-params",
22759
22892
  capScope: "device",
@@ -23120,54 +23253,6 @@ Object.freeze({
23120
23253
  addonId: null,
23121
23254
  access: "create"
23122
23255
  },
23123
- "webrtc.closeSession": {
23124
- capName: "webrtc",
23125
- capScope: "system",
23126
- addonId: null,
23127
- access: "create"
23128
- },
23129
- "webrtc.createSession": {
23130
- capName: "webrtc",
23131
- capScope: "system",
23132
- addonId: null,
23133
- access: "create"
23134
- },
23135
- "webrtc.handleAnswer": {
23136
- capName: "webrtc",
23137
- capScope: "system",
23138
- addonId: null,
23139
- access: "create"
23140
- },
23141
- "webrtc.handleOffer": {
23142
- capName: "webrtc",
23143
- capScope: "system",
23144
- addonId: null,
23145
- access: "create"
23146
- },
23147
- "webrtc.hasAdaptiveBitrate": {
23148
- capName: "webrtc",
23149
- capScope: "system",
23150
- addonId: null,
23151
- access: "view"
23152
- },
23153
- "webrtc.registerStream": {
23154
- capName: "webrtc",
23155
- capScope: "system",
23156
- addonId: null,
23157
- access: "create"
23158
- },
23159
- "webrtc.supportsStream": {
23160
- capName: "webrtc",
23161
- capScope: "system",
23162
- addonId: null,
23163
- access: "view"
23164
- },
23165
- "webrtc.unregisterStream": {
23166
- capName: "webrtc",
23167
- capScope: "system",
23168
- addonId: null,
23169
- access: "delete"
23170
- },
23171
23256
  "webrtcSession.addIceCandidate": {
23172
23257
  capName: "webrtc-session",
23173
23258
  capScope: "device",
@@ -23380,6 +23465,10 @@ var OnvifCamera = class {
23380
23465
  * `null` when the camera is being restored from DB without a live connection.
23381
23466
  */
23382
23467
  client;
23468
+ /** Control-plane reachability poll — drives `online` from ONVIF
23469
+ * `getDeviceInformation` liveness, decoupled from stream-broker video
23470
+ * health. Started on `attachClient`, stopped on `removeDevice`. */
23471
+ reachabilityPoll = null;
23383
23472
  constructor(ctx, initialData, client = null) {
23384
23473
  this.ctx = ctx;
23385
23474
  this.id = ctx.id;
@@ -23564,8 +23653,32 @@ var OnvifCamera = class {
23564
23653
  attachClient(client) {
23565
23654
  this.client = client;
23566
23655
  this.online = true;
23656
+ this.startReachabilityPolling();
23657
+ }
23658
+ /** Start the control-plane reachability poll: an ONVIF
23659
+ * `getDeviceInformation` round-trip every 30s drives `online`, with
23660
+ * hysteresis. Replaces the old stream-health→online coupling so a
23661
+ * reachable on-demand camera still reports ONLINE. Re-armed on each
23662
+ * `attachClient` (reconnect) — the prior poll is stopped first. */
23663
+ startReachabilityPolling() {
23664
+ this.reachabilityPoll?.stop();
23665
+ this.reachabilityPoll = startReachabilityPoll({
23666
+ probe: async () => {
23667
+ const client = this.client;
23668
+ if (!client) return false;
23669
+ await client.getDeviceInfo();
23670
+ return true;
23671
+ },
23672
+ setOnline: (online) => {
23673
+ this.markOnline(online);
23674
+ },
23675
+ isEnabled: () => !this.disabled && this.client !== null,
23676
+ logger: this.ctx.logger
23677
+ });
23567
23678
  }
23568
23679
  async removeDevice() {
23680
+ this.reachabilityPoll?.stop();
23681
+ this.reachabilityPoll = null;
23569
23682
  this.client?.disconnect();
23570
23683
  this.client = null;
23571
23684
  this.online = false;