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