@camstack/addon-provider-onvif 1.1.18 → 1.1.20

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 +193 -186
  2. package/dist/addon.mjs +193 -186
  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,28 @@ 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 / reduced-input variant)
6941
+ * — the slot the future performance variants plug into.
6942
+ */
6943
+ optimization: _enum(["standard", "fast"]).optional()
6944
+ });
6911
6945
  var ModelCatalogEntrySchema = object({
6912
6946
  id: string(),
6913
6947
  name: string(),
@@ -6937,7 +6971,43 @@ var ModelCatalogEntrySchema = object({
6937
6971
  * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
6938
6972
  * Downloaded into the same modelsDir alongside the model file.
6939
6973
  */
6940
- extraFiles: array(ModelExtraFileSchema).readonly().optional()
6974
+ extraFiles: array(ModelExtraFileSchema).readonly().optional(),
6975
+ /**
6976
+ * LEGACY entry — retained in the catalog so a persisted operator selection
6977
+ * still RESOLVES (and can be re-activated), but hidden from the selectable
6978
+ * model list and excluded from the auto format-default pick. Set on the
6979
+ * superseded / consolidated models (older lineages, redundant fp16 IRs) so
6980
+ * the active lineup stays the coherent curated ladder without deleting a
6981
+ * model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
6982
+ * an explicit legacy id that has a build for the node's format.
6983
+ */
6984
+ legacy: boolean().optional(),
6985
+ /**
6986
+ * Measured quality/latency metadata — populated from the benchmark addon on
6987
+ * the real node classes. Absent = not yet measured (most entries today; the
6988
+ * catalog historically carried only `sizeMB`, a poor cross-architecture
6989
+ * speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
6990
+ */
6991
+ metrics: object({
6992
+ map50: number().optional(),
6993
+ p95LatencyMs: record(string(), number()).optional()
6994
+ }).optional(),
6995
+ /**
6996
+ * SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
6997
+ * YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
6998
+ * the retraining addon and any future commercial distribution.
6999
+ */
7000
+ license: string().optional(),
7001
+ /**
7002
+ * Variant-selector grouping. The UI groups models by `family` + `tier` and
7003
+ * offers `precision` / `optimization` as variant axes WITHIN a tier — so all
7004
+ * of a family's sizes and quantizations collapse into one grouped picker
7005
+ * instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
7006
+ * (legacy / custom models) — never shown in the grouped selector. The flat
7007
+ * `id` stays the source of truth for resolution/download/persistence; grouping
7008
+ * is a presentation overlay resolved back to an `id`.
7009
+ */
7010
+ group: ModelVariantGroupSchema.optional()
6941
7011
  });
6942
7012
  var ConvertTargetSchema = discriminatedUnion("format", [object({
6943
7013
  format: literal("openvino"),
@@ -6998,8 +7068,8 @@ var RecordingModeSchema = _enum([
6998
7068
  "onAudioThreshold"
6999
7069
  ]);
7000
7070
  /**
7001
- * First-class, authoritative per-camera storage mode — the netta choice the UI
7002
- * reads directly (never inferred from `rules`):
7071
+ * First-class, authoritative per-camera storage mode — the explicit choice the
7072
+ * UI reads directly (never inferred from `rules`):
7003
7073
  * - `off` — not recording.
7004
7074
  * - `events` — record only around triggers (motion / audio threshold),
7005
7075
  * with pre/post-buffer.
@@ -8751,26 +8821,13 @@ DeviceType.Light, method(object({
8751
8821
  percentage: number().min(0).max(100),
8752
8822
  lastChangedAt: number()
8753
8823
  });
8824
+ /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8754
8825
  var StreamFormatSchema = _enum([
8755
8826
  "webrtc",
8756
8827
  "hls",
8757
8828
  "mjpeg",
8758
8829
  "rtsp"
8759
8830
  ]);
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
8831
  var RtspRestreamEntrySchema = object({
8775
8832
  brokerId: string(),
8776
8833
  url: string(),
@@ -9435,7 +9492,7 @@ var ConsumablesStatusSchema = object({
9435
9492
  })),
9436
9493
  lastChangedAt: number()
9437
9494
  });
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({
9495
+ Object.values(DeviceType), method(object({
9439
9496
  deviceId: number().int().nonnegative(),
9440
9497
  key: string().min(1)
9441
9498
  }), _void(), {
@@ -10485,7 +10542,6 @@ var PipelineDefaultStepSchema = lazy(() => object({
10485
10542
  enabled: boolean(),
10486
10543
  modelId: string(),
10487
10544
  children: array(PipelineDefaultStepSchema).readonly(),
10488
- engine: PipelineEngineChoiceSchema.optional(),
10489
10545
  group: string().optional(),
10490
10546
  settings: record(string(), unknown()).optional()
10491
10547
  }));
@@ -10510,7 +10566,9 @@ var PipelineModelOptionSchema = object({
10510
10566
  formats: record(string(), object({
10511
10567
  downloaded: boolean(),
10512
10568
  sizeMB: number()
10513
- }))
10569
+ })),
10570
+ group: ModelVariantGroupSchema.optional(),
10571
+ legacy: boolean().optional()
10514
10572
  });
10515
10573
  var ConfigFieldBridge = custom();
10516
10574
  var PipelineAddonSchemaSchema = object({
@@ -10561,15 +10619,42 @@ var EngineProvisioningSchema = object({
10561
10619
  ]),
10562
10620
  progress: number().optional(),
10563
10621
  error: string().optional(),
10564
- nextRetryAt: number().optional()
10622
+ nextRetryAt: number().optional(),
10623
+ /**
10624
+ * Gate A (config-correctness gate at engine change): human-readable
10625
+ * config issues surfaced EAGERLY when the node's engine changes — model
10626
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
10627
+ * has a <format> build"). Additive/optional: informational only, never
10628
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
10629
+ * Absent/empty when the node-default tree resolves cleanly.
10630
+ */
10631
+ configIssues: array(string()).optional()
10565
10632
  });
10566
10633
  var PipelineStepInputSchema = lazy(() => object({
10567
10634
  addonId: string(),
10568
- modelId: string(),
10635
+ modelId: string().optional(),
10569
10636
  enabled: boolean().default(true),
10570
10637
  children: array(PipelineStepInputSchema).optional(),
10571
10638
  settings: record(string(), unknown()).optional()
10572
10639
  }));
10640
+ var ModelSubstitutionSchema = object({
10641
+ addonId: string(),
10642
+ chosen: string(),
10643
+ running: string(),
10644
+ format: string()
10645
+ });
10646
+ var PipelineValidationIssueSchema = object({
10647
+ addonId: string(),
10648
+ kind: _enum(["unknown-addon", "no-format-build"]),
10649
+ detail: string()
10650
+ });
10651
+ var PipelineValidationResultSchema = object({
10652
+ ok: boolean(),
10653
+ issues: array(PipelineValidationIssueSchema).readonly(),
10654
+ substitutions: array(ModelSubstitutionSchema).readonly(),
10655
+ /** The node's `currentEngine.format` this validation ran against. */
10656
+ format: string()
10657
+ });
10573
10658
  var ReferenceImageEntrySchema = object({
10574
10659
  filename: string(),
10575
10660
  stepIds: array(string()).readonly().optional()
@@ -10640,7 +10725,7 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10640
10725
  })) }), object({ success: literal(true) }), {
10641
10726
  kind: "mutation",
10642
10727
  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({
10728
+ }), 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
10729
  name: string(),
10645
10730
  steps: array(PipelineTemplateStepSchema).readonly(),
10646
10731
  engine: PipelineEngineChoiceSchema
@@ -10900,6 +10985,13 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
10900
10985
  kind: literal("remote-restream"),
10901
10986
  /** The camera's source-owner node (slice 1: always the hub). */
10902
10987
  ownerNodeId: string(),
10988
+ /**
10989
+ * The owner's LAN-reachable host, resolved by the orchestrator from the
10990
+ * per-node `reachableHost` override (Cluster UI). When present the runner
10991
+ * dials THIS host for the owner's restream, in preference to the
10992
+ * `CAMSTACK_HUB_URL`-derived default. Absent → auto-detect fallback.
10993
+ */
10994
+ ownerReachableHost: string().optional(),
10903
10995
  /** Operator override for the owner host the runner dials. */
10904
10996
  hubHostnameOverride: string().optional()
10905
10997
  })]).describe("Per-camera frame-source mode for the runner (P2c)");
@@ -10908,13 +11000,11 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
10908
11000
  * specific runner instance via `attachCamera`. Carries everything the
10909
11001
  * runner needs to subscribe to the local broker and execute inference.
10910
11002
  *
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.
11003
+ * Stateless-pipeline model: the pipeline content (`steps`, optional
11004
+ * `audio`) travels with the attach payload. The runner keeps it in RAM
11005
+ * for the lifetime of the attach — on rebalance, edit, or restart the
11006
+ * orchestrator re-sends the latest snapshot. Engine is NOT carried: it is
11007
+ * node-local, resolved by the executing runner at dispatch time.
10918
11008
  */
10919
11009
  var RunnerCameraConfigSchema = object({
10920
11010
  deviceId: number(),
@@ -10965,14 +11055,11 @@ var RunnerCameraConfigSchema = object({
10965
11055
  */
10966
11056
  motionSources: MotionSourcesSchema.default(["analyzer"]),
10967
11057
  pipelineEnabled: boolean().default(true),
10968
- /** Engine choice for video steps (runtime+backend+format). */
10969
- engine: PipelineEngineChoiceSchema.optional(),
10970
11058
  /** Ordered tree of video steps. Absent → runner skips video detection. */
10971
11059
  steps: array(PipelineStepInputSchema).readonly().optional(),
10972
11060
  /** Audio classification branch. `enabled:false` disables, null skips. */
10973
11061
  audio: object({
10974
- engine: PipelineEngineChoiceSchema,
10975
- modelId: string(),
11062
+ modelId: string().optional(),
10976
11063
  enabled: boolean()
10977
11064
  }).nullable().optional(),
10978
11065
  /**
@@ -15492,11 +15579,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15492
15579
  timestamp: number()
15493
15580
  });
15494
15581
  var CameraPipelineConfigSchema = object({
15495
- engine: PipelineEngineChoiceSchema,
15582
+ engine: PipelineEngineChoiceSchema.optional(),
15496
15583
  steps: array(PipelineStepInputSchema).readonly(),
15497
15584
  audio: object({
15498
- engine: PipelineEngineChoiceSchema,
15499
- modelId: string(),
15585
+ engine: PipelineEngineChoiceSchema.optional(),
15586
+ modelId: string().optional(),
15500
15587
  enabled: boolean(),
15501
15588
  settings: record(string(), unknown()).readonly().optional()
15502
15589
  }).nullable().optional()
@@ -15511,7 +15598,7 @@ var PipelineTemplateSchema = object({
15511
15598
  });
15512
15599
  var AgentAddonConfigSchema = object({
15513
15600
  enabled: boolean(),
15514
- modelId: string(),
15601
+ modelId: string().optional(),
15515
15602
  settings: record(string(), unknown()).readonly()
15516
15603
  });
15517
15604
  var AgentPipelineSettingsSchema = object({
@@ -15526,7 +15613,15 @@ var AgentPipelineSettingsSchema = object({
15526
15613
  /** Node is eligible to run audio-analyzer sessions. */
15527
15614
  audio: boolean().optional(),
15528
15615
  /** Node is eligible to be the ingest / source-owner (serve the restream). */
15529
- ingest: boolean().optional()
15616
+ ingest: boolean().optional(),
15617
+ /**
15618
+ * Operator override for the LAN host a cross-node decoder dials to reach
15619
+ * THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
15620
+ * falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
15621
+ * it already uses to reach the hub). Set this only when the auto-detected
15622
+ * address is wrong (multi-homed host, NAT, custom interface).
15623
+ */
15624
+ reachableHost: string().optional()
15530
15625
  });
15531
15626
  var CameraPipelineForAgentSchema = object({
15532
15627
  steps: array(PipelineStepInputSchema).readonly(),
@@ -15632,6 +15727,15 @@ var GlobalMetricsSchema = object({
15632
15727
  * capability providers.
15633
15728
  */
15634
15729
  var CapabilityBindingsSchema = record(string(), string());
15730
+ /**
15731
+ * The cluster's single camera-source owner (`clusterRoles.ingestNode`) plus
15732
+ * its LAN-reachable host, if one is registered. See `getIngestOwner`.
15733
+ */
15734
+ var IngestOwnerSchema = object({
15735
+ ownerNodeId: string(),
15736
+ reachableHost: string().optional(),
15737
+ configIssue: string().optional()
15738
+ });
15635
15739
  /** Source block — always present; derives from the stream catalog. */
15636
15740
  var CameraSourceStatusSchema = object({ streams: array(object({
15637
15741
  camStreamId: string(),
@@ -15646,6 +15750,14 @@ var CameraAssignmentStatusSchema = object({
15646
15750
  detectionNodeId: string().nullable(),
15647
15751
  decoderNodeId: string().nullable(),
15648
15752
  audioNodeId: string().nullable(),
15753
+ /**
15754
+ * The node that OWNS this camera's physical source pull (dials the RTSP and
15755
+ * hosts the broker/restream) — the cluster ingest owner today
15756
+ * (`clusterRoles.ingestNode`), per-camera once source assignment lands. Lets
15757
+ * the UI show WHERE a camera is sourced without SSH/logs, and is the node the
15758
+ * broker block below was read from (pinned). Nullable only pre-wiring.
15759
+ */
15760
+ sourceNodeId: string().nullable(),
15649
15761
  pinned: object({
15650
15762
  detection: boolean(),
15651
15763
  decoder: boolean(),
@@ -15778,7 +15890,7 @@ method(object({
15778
15890
  }), object({ success: literal(true) }), {
15779
15891
  kind: "mutation",
15780
15892
  auth: "admin"
15781
- }), method(object({
15893
+ }), method(_void(), IngestOwnerSchema), method(object({
15782
15894
  deviceId: number(),
15783
15895
  nodeId: string()
15784
15896
  }), _void(), {
@@ -15847,6 +15959,12 @@ method(object({
15847
15959
  }), object({ success: literal(true) }), {
15848
15960
  kind: "mutation",
15849
15961
  auth: "admin"
15962
+ }), method(object({
15963
+ agentNodeId: string(),
15964
+ reachableHost: string().nullable()
15965
+ }), object({ success: literal(true) }), {
15966
+ kind: "mutation",
15967
+ auth: "admin"
15850
15968
  }), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
15851
15969
  deviceId: number(),
15852
15970
  addonId: string(),
@@ -15891,22 +16009,6 @@ method(object({
15891
16009
  kind: "mutation",
15892
16010
  auth: "admin"
15893
16011
  });
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
16012
  /**
15911
16013
  * Query filter for settings-store collections.
15912
16014
  */
@@ -16059,9 +16161,9 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
16059
16161
  /**
16060
16162
  * A single device snapshot returned as base64 JPEG/PNG.
16061
16163
  *
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.
16164
+ * The `SnapshotAddon` wrapper returns this shape whether the frame came from
16165
+ * the device-native provider (onboard capture) or from the stream-broker
16166
+ * prebuffer fallback.
16065
16167
  */
16066
16168
  var SnapshotImageSchema = object({
16067
16169
  base64: string(),
@@ -16136,10 +16238,6 @@ var snapshotCapability = {
16136
16238
  kind: "poll"
16137
16239
  }
16138
16240
  };
16139
- method(object({ deviceId: number() }), boolean()), method(object({
16140
- deviceId: number(),
16141
- streamId: string().optional()
16142
- }), SnapshotImageSchema.nullable());
16143
16241
  /**
16144
16242
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
16145
16243
  * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
@@ -16500,9 +16598,10 @@ method(object({
16500
16598
  auth: "admin"
16501
16599
  });
16502
16600
  /**
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.
16601
+ * Optional client-side hints sent at session creation to help the provider
16602
+ * pick the best native source. All fields optional — a viewer that knows
16603
+ * nothing still gets a sane default. (Relocated from the retired `webrtc`
16604
+ * collection cap; this `webrtc-session` cap is the live signaling surface.)
16506
16605
  */
16507
16606
  var webrtcClientHintsSchema = object({
16508
16607
  viewportWidth: number().int().positive().optional(),
@@ -16513,22 +16612,6 @@ var webrtcClientHintsSchema = object({
16513
16612
  /** Hard tier override; takes precedence over scoring when registered. */
16514
16613
  prefersTier: string().optional()
16515
16614
  }).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
16615
  /**
16533
16616
  * Discriminated target for a WebRTC session. The client sends this
16534
16617
  * structured object instead of building / parsing brokerId strings;
@@ -17015,7 +17098,15 @@ var FrameworkPackageStatusSchema = object({
17015
17098
  latestVersion: string().nullable(),
17016
17099
  hasUpdate: boolean(),
17017
17100
  /** Optional manifest description for the row tooltip. */
17018
- description: string().optional()
17101
+ description: string().optional(),
17102
+ /**
17103
+ * Content build-id (md5 of the resolved `dist/` tree) of the code the hub
17104
+ * ACTUALLY loaded. Framework packages ship code changes without always
17105
+ * bumping `currentVersion`, so semver alone hides "same version, new code".
17106
+ * `null` when the dist can't be hashed (not installed / empty). The admin-UI
17107
+ * surfaces this so a stale-code hub is visible even at an unchanged version.
17108
+ */
17109
+ buildId: string().nullable()
17019
17110
  });
17020
17111
  var LogStreamEntrySchema = object({
17021
17112
  timestamp: string(),
@@ -21653,6 +21744,12 @@ Object.freeze({
21653
21744
  addonId: null,
21654
21745
  access: "create"
21655
21746
  },
21747
+ "pipelineExecutor.validatePipeline": {
21748
+ capName: "pipeline-executor",
21749
+ capScope: "system",
21750
+ addonId: null,
21751
+ access: "view"
21752
+ },
21656
21753
  "pipelineOrchestrator.assignAudio": {
21657
21754
  capName: "pipeline-orchestrator",
21658
21755
  capScope: "system",
@@ -21761,6 +21858,12 @@ Object.freeze({
21761
21858
  addonId: null,
21762
21859
  access: "view"
21763
21860
  },
21861
+ "pipelineOrchestrator.getIngestOwner": {
21862
+ capName: "pipeline-orchestrator",
21863
+ capScope: "system",
21864
+ addonId: null,
21865
+ access: "view"
21866
+ },
21764
21867
  "pipelineOrchestrator.getPipelineAssignment": {
21765
21868
  capName: "pipeline-orchestrator",
21766
21869
  capScope: "system",
@@ -21833,6 +21936,12 @@ Object.freeze({
21833
21936
  addonId: null,
21834
21937
  access: "create"
21835
21938
  },
21939
+ "pipelineOrchestrator.setAgentReachableHost": {
21940
+ capName: "pipeline-orchestrator",
21941
+ capScope: "system",
21942
+ addonId: null,
21943
+ access: "create"
21944
+ },
21836
21945
  "pipelineOrchestrator.setCameraPipelineForAgent": {
21837
21946
  capName: "pipeline-orchestrator",
21838
21947
  capScope: "system",
@@ -22163,24 +22272,6 @@ Object.freeze({
22163
22272
  addonId: null,
22164
22273
  access: "create"
22165
22274
  },
22166
- "restreamer.getExposedResources": {
22167
- capName: "restreamer",
22168
- capScope: "system",
22169
- addonId: null,
22170
- access: "view"
22171
- },
22172
- "restreamer.registerDevice": {
22173
- capName: "restreamer",
22174
- capScope: "system",
22175
- addonId: null,
22176
- access: "create"
22177
- },
22178
- "restreamer.unregisterDevice": {
22179
- capName: "restreamer",
22180
- capScope: "system",
22181
- addonId: null,
22182
- access: "delete"
22183
- },
22184
22275
  "scriptRunner.run": {
22185
22276
  capName: "script-runner",
22186
22277
  capScope: "device",
@@ -22283,18 +22374,6 @@ Object.freeze({
22283
22374
  addonId: null,
22284
22375
  access: "create"
22285
22376
  },
22286
- "snapshotProvider.getSnapshot": {
22287
- capName: "snapshot-provider",
22288
- capScope: "system",
22289
- addonId: null,
22290
- access: "view"
22291
- },
22292
- "snapshotProvider.supportsDevice": {
22293
- capName: "snapshot-provider",
22294
- capScope: "system",
22295
- addonId: null,
22296
- access: "view"
22297
- },
22298
22377
  "ssoBridge.signBridgeToken": {
22299
22378
  capName: "sso-bridge",
22300
22379
  capScope: "system",
@@ -22721,30 +22800,6 @@ Object.freeze({
22721
22800
  addonId: null,
22722
22801
  access: "view"
22723
22802
  },
22724
- "streamingEngine.getStreamUrl": {
22725
- capName: "streaming-engine",
22726
- capScope: "system",
22727
- addonId: null,
22728
- access: "view"
22729
- },
22730
- "streamingEngine.listStreams": {
22731
- capName: "streaming-engine",
22732
- capScope: "system",
22733
- addonId: null,
22734
- access: "view"
22735
- },
22736
- "streamingEngine.registerStream": {
22737
- capName: "streaming-engine",
22738
- capScope: "system",
22739
- addonId: null,
22740
- access: "create"
22741
- },
22742
- "streamingEngine.unregisterStream": {
22743
- capName: "streaming-engine",
22744
- capScope: "system",
22745
- addonId: null,
22746
- access: "delete"
22747
- },
22748
22803
  "streamParams.getConfigSchema": {
22749
22804
  capName: "stream-params",
22750
22805
  capScope: "device",
@@ -23111,54 +23166,6 @@ Object.freeze({
23111
23166
  addonId: null,
23112
23167
  access: "create"
23113
23168
  },
23114
- "webrtc.closeSession": {
23115
- capName: "webrtc",
23116
- capScope: "system",
23117
- addonId: null,
23118
- access: "create"
23119
- },
23120
- "webrtc.createSession": {
23121
- capName: "webrtc",
23122
- capScope: "system",
23123
- addonId: null,
23124
- access: "create"
23125
- },
23126
- "webrtc.handleAnswer": {
23127
- capName: "webrtc",
23128
- capScope: "system",
23129
- addonId: null,
23130
- access: "create"
23131
- },
23132
- "webrtc.handleOffer": {
23133
- capName: "webrtc",
23134
- capScope: "system",
23135
- addonId: null,
23136
- access: "create"
23137
- },
23138
- "webrtc.hasAdaptiveBitrate": {
23139
- capName: "webrtc",
23140
- capScope: "system",
23141
- addonId: null,
23142
- access: "view"
23143
- },
23144
- "webrtc.registerStream": {
23145
- capName: "webrtc",
23146
- capScope: "system",
23147
- addonId: null,
23148
- access: "create"
23149
- },
23150
- "webrtc.supportsStream": {
23151
- capName: "webrtc",
23152
- capScope: "system",
23153
- addonId: null,
23154
- access: "view"
23155
- },
23156
- "webrtc.unregisterStream": {
23157
- capName: "webrtc",
23158
- capScope: "system",
23159
- addonId: null,
23160
- access: "delete"
23161
- },
23162
23169
  "webrtcSession.addIceCandidate": {
23163
23170
  capName: "webrtc-session",
23164
23171
  capScope: "device",
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,28 @@ 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 / reduced-input variant)
6942
+ * — the slot the future performance variants plug into.
6943
+ */
6944
+ optimization: _enum(["standard", "fast"]).optional()
6945
+ });
6912
6946
  var ModelCatalogEntrySchema = object({
6913
6947
  id: string(),
6914
6948
  name: string(),
@@ -6938,7 +6972,43 @@ var ModelCatalogEntrySchema = object({
6938
6972
  * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
6939
6973
  * Downloaded into the same modelsDir alongside the model file.
6940
6974
  */
6941
- extraFiles: array(ModelExtraFileSchema).readonly().optional()
6975
+ extraFiles: array(ModelExtraFileSchema).readonly().optional(),
6976
+ /**
6977
+ * LEGACY entry — retained in the catalog so a persisted operator selection
6978
+ * still RESOLVES (and can be re-activated), but hidden from the selectable
6979
+ * model list and excluded from the auto format-default pick. Set on the
6980
+ * superseded / consolidated models (older lineages, redundant fp16 IRs) so
6981
+ * the active lineup stays the coherent curated ladder without deleting a
6982
+ * model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
6983
+ * an explicit legacy id that has a build for the node's format.
6984
+ */
6985
+ legacy: boolean().optional(),
6986
+ /**
6987
+ * Measured quality/latency metadata — populated from the benchmark addon on
6988
+ * the real node classes. Absent = not yet measured (most entries today; the
6989
+ * catalog historically carried only `sizeMB`, a poor cross-architecture
6990
+ * speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
6991
+ */
6992
+ metrics: object({
6993
+ map50: number().optional(),
6994
+ p95LatencyMs: record(string(), number()).optional()
6995
+ }).optional(),
6996
+ /**
6997
+ * SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
6998
+ * YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
6999
+ * the retraining addon and any future commercial distribution.
7000
+ */
7001
+ license: string().optional(),
7002
+ /**
7003
+ * Variant-selector grouping. The UI groups models by `family` + `tier` and
7004
+ * offers `precision` / `optimization` as variant axes WITHIN a tier — so all
7005
+ * of a family's sizes and quantizations collapse into one grouped picker
7006
+ * instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
7007
+ * (legacy / custom models) — never shown in the grouped selector. The flat
7008
+ * `id` stays the source of truth for resolution/download/persistence; grouping
7009
+ * is a presentation overlay resolved back to an `id`.
7010
+ */
7011
+ group: ModelVariantGroupSchema.optional()
6942
7012
  });
6943
7013
  var ConvertTargetSchema = discriminatedUnion("format", [object({
6944
7014
  format: literal("openvino"),
@@ -6999,8 +7069,8 @@ var RecordingModeSchema = _enum([
6999
7069
  "onAudioThreshold"
7000
7070
  ]);
7001
7071
  /**
7002
- * First-class, authoritative per-camera storage mode — the netta choice the UI
7003
- * reads directly (never inferred from `rules`):
7072
+ * First-class, authoritative per-camera storage mode — the explicit choice the
7073
+ * UI reads directly (never inferred from `rules`):
7004
7074
  * - `off` — not recording.
7005
7075
  * - `events` — record only around triggers (motion / audio threshold),
7006
7076
  * with pre/post-buffer.
@@ -8752,26 +8822,13 @@ DeviceType.Light, method(object({
8752
8822
  percentage: number().min(0).max(100),
8753
8823
  lastChangedAt: number()
8754
8824
  });
8825
+ /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8755
8826
  var StreamFormatSchema = _enum([
8756
8827
  "webrtc",
8757
8828
  "hls",
8758
8829
  "mjpeg",
8759
8830
  "rtsp"
8760
8831
  ]);
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
8832
  var RtspRestreamEntrySchema = object({
8776
8833
  brokerId: string(),
8777
8834
  url: string(),
@@ -9436,7 +9493,7 @@ var ConsumablesStatusSchema = object({
9436
9493
  })),
9437
9494
  lastChangedAt: number()
9438
9495
  });
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({
9496
+ Object.values(DeviceType), method(object({
9440
9497
  deviceId: number().int().nonnegative(),
9441
9498
  key: string().min(1)
9442
9499
  }), _void(), {
@@ -10486,7 +10543,6 @@ var PipelineDefaultStepSchema = lazy(() => object({
10486
10543
  enabled: boolean(),
10487
10544
  modelId: string(),
10488
10545
  children: array(PipelineDefaultStepSchema).readonly(),
10489
- engine: PipelineEngineChoiceSchema.optional(),
10490
10546
  group: string().optional(),
10491
10547
  settings: record(string(), unknown()).optional()
10492
10548
  }));
@@ -10511,7 +10567,9 @@ var PipelineModelOptionSchema = object({
10511
10567
  formats: record(string(), object({
10512
10568
  downloaded: boolean(),
10513
10569
  sizeMB: number()
10514
- }))
10570
+ })),
10571
+ group: ModelVariantGroupSchema.optional(),
10572
+ legacy: boolean().optional()
10515
10573
  });
10516
10574
  var ConfigFieldBridge = custom();
10517
10575
  var PipelineAddonSchemaSchema = object({
@@ -10562,15 +10620,42 @@ var EngineProvisioningSchema = object({
10562
10620
  ]),
10563
10621
  progress: number().optional(),
10564
10622
  error: string().optional(),
10565
- nextRetryAt: number().optional()
10623
+ nextRetryAt: number().optional(),
10624
+ /**
10625
+ * Gate A (config-correctness gate at engine change): human-readable
10626
+ * config issues surfaced EAGERLY when the node's engine changes — model
10627
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
10628
+ * has a <format> build"). Additive/optional: informational only, never
10629
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
10630
+ * Absent/empty when the node-default tree resolves cleanly.
10631
+ */
10632
+ configIssues: array(string()).optional()
10566
10633
  });
10567
10634
  var PipelineStepInputSchema = lazy(() => object({
10568
10635
  addonId: string(),
10569
- modelId: string(),
10636
+ modelId: string().optional(),
10570
10637
  enabled: boolean().default(true),
10571
10638
  children: array(PipelineStepInputSchema).optional(),
10572
10639
  settings: record(string(), unknown()).optional()
10573
10640
  }));
10641
+ var ModelSubstitutionSchema = object({
10642
+ addonId: string(),
10643
+ chosen: string(),
10644
+ running: string(),
10645
+ format: string()
10646
+ });
10647
+ var PipelineValidationIssueSchema = object({
10648
+ addonId: string(),
10649
+ kind: _enum(["unknown-addon", "no-format-build"]),
10650
+ detail: string()
10651
+ });
10652
+ var PipelineValidationResultSchema = object({
10653
+ ok: boolean(),
10654
+ issues: array(PipelineValidationIssueSchema).readonly(),
10655
+ substitutions: array(ModelSubstitutionSchema).readonly(),
10656
+ /** The node's `currentEngine.format` this validation ran against. */
10657
+ format: string()
10658
+ });
10574
10659
  var ReferenceImageEntrySchema = object({
10575
10660
  filename: string(),
10576
10661
  stepIds: array(string()).readonly().optional()
@@ -10641,7 +10726,7 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10641
10726
  })) }), object({ success: literal(true) }), {
10642
10727
  kind: "mutation",
10643
10728
  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({
10729
+ }), 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
10730
  name: string(),
10646
10731
  steps: array(PipelineTemplateStepSchema).readonly(),
10647
10732
  engine: PipelineEngineChoiceSchema
@@ -10901,6 +10986,13 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
10901
10986
  kind: literal("remote-restream"),
10902
10987
  /** The camera's source-owner node (slice 1: always the hub). */
10903
10988
  ownerNodeId: string(),
10989
+ /**
10990
+ * The owner's LAN-reachable host, resolved by the orchestrator from the
10991
+ * per-node `reachableHost` override (Cluster UI). When present the runner
10992
+ * dials THIS host for the owner's restream, in preference to the
10993
+ * `CAMSTACK_HUB_URL`-derived default. Absent → auto-detect fallback.
10994
+ */
10995
+ ownerReachableHost: string().optional(),
10904
10996
  /** Operator override for the owner host the runner dials. */
10905
10997
  hubHostnameOverride: string().optional()
10906
10998
  })]).describe("Per-camera frame-source mode for the runner (P2c)");
@@ -10909,13 +11001,11 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
10909
11001
  * specific runner instance via `attachCamera`. Carries everything the
10910
11002
  * runner needs to subscribe to the local broker and execute inference.
10911
11003
  *
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.
11004
+ * Stateless-pipeline model: the pipeline content (`steps`, optional
11005
+ * `audio`) travels with the attach payload. The runner keeps it in RAM
11006
+ * for the lifetime of the attach — on rebalance, edit, or restart the
11007
+ * orchestrator re-sends the latest snapshot. Engine is NOT carried: it is
11008
+ * node-local, resolved by the executing runner at dispatch time.
10919
11009
  */
10920
11010
  var RunnerCameraConfigSchema = object({
10921
11011
  deviceId: number(),
@@ -10966,14 +11056,11 @@ var RunnerCameraConfigSchema = object({
10966
11056
  */
10967
11057
  motionSources: MotionSourcesSchema.default(["analyzer"]),
10968
11058
  pipelineEnabled: boolean().default(true),
10969
- /** Engine choice for video steps (runtime+backend+format). */
10970
- engine: PipelineEngineChoiceSchema.optional(),
10971
11059
  /** Ordered tree of video steps. Absent → runner skips video detection. */
10972
11060
  steps: array(PipelineStepInputSchema).readonly().optional(),
10973
11061
  /** Audio classification branch. `enabled:false` disables, null skips. */
10974
11062
  audio: object({
10975
- engine: PipelineEngineChoiceSchema,
10976
- modelId: string(),
11063
+ modelId: string().optional(),
10977
11064
  enabled: boolean()
10978
11065
  }).nullable().optional(),
10979
11066
  /**
@@ -15493,11 +15580,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15493
15580
  timestamp: number()
15494
15581
  });
15495
15582
  var CameraPipelineConfigSchema = object({
15496
- engine: PipelineEngineChoiceSchema,
15583
+ engine: PipelineEngineChoiceSchema.optional(),
15497
15584
  steps: array(PipelineStepInputSchema).readonly(),
15498
15585
  audio: object({
15499
- engine: PipelineEngineChoiceSchema,
15500
- modelId: string(),
15586
+ engine: PipelineEngineChoiceSchema.optional(),
15587
+ modelId: string().optional(),
15501
15588
  enabled: boolean(),
15502
15589
  settings: record(string(), unknown()).readonly().optional()
15503
15590
  }).nullable().optional()
@@ -15512,7 +15599,7 @@ var PipelineTemplateSchema = object({
15512
15599
  });
15513
15600
  var AgentAddonConfigSchema = object({
15514
15601
  enabled: boolean(),
15515
- modelId: string(),
15602
+ modelId: string().optional(),
15516
15603
  settings: record(string(), unknown()).readonly()
15517
15604
  });
15518
15605
  var AgentPipelineSettingsSchema = object({
@@ -15527,7 +15614,15 @@ var AgentPipelineSettingsSchema = object({
15527
15614
  /** Node is eligible to run audio-analyzer sessions. */
15528
15615
  audio: boolean().optional(),
15529
15616
  /** Node is eligible to be the ingest / source-owner (serve the restream). */
15530
- ingest: boolean().optional()
15617
+ ingest: boolean().optional(),
15618
+ /**
15619
+ * Operator override for the LAN host a cross-node decoder dials to reach
15620
+ * THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
15621
+ * falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
15622
+ * it already uses to reach the hub). Set this only when the auto-detected
15623
+ * address is wrong (multi-homed host, NAT, custom interface).
15624
+ */
15625
+ reachableHost: string().optional()
15531
15626
  });
15532
15627
  var CameraPipelineForAgentSchema = object({
15533
15628
  steps: array(PipelineStepInputSchema).readonly(),
@@ -15633,6 +15728,15 @@ var GlobalMetricsSchema = object({
15633
15728
  * capability providers.
15634
15729
  */
15635
15730
  var CapabilityBindingsSchema = record(string(), string());
15731
+ /**
15732
+ * The cluster's single camera-source owner (`clusterRoles.ingestNode`) plus
15733
+ * its LAN-reachable host, if one is registered. See `getIngestOwner`.
15734
+ */
15735
+ var IngestOwnerSchema = object({
15736
+ ownerNodeId: string(),
15737
+ reachableHost: string().optional(),
15738
+ configIssue: string().optional()
15739
+ });
15636
15740
  /** Source block — always present; derives from the stream catalog. */
15637
15741
  var CameraSourceStatusSchema = object({ streams: array(object({
15638
15742
  camStreamId: string(),
@@ -15647,6 +15751,14 @@ var CameraAssignmentStatusSchema = object({
15647
15751
  detectionNodeId: string().nullable(),
15648
15752
  decoderNodeId: string().nullable(),
15649
15753
  audioNodeId: string().nullable(),
15754
+ /**
15755
+ * The node that OWNS this camera's physical source pull (dials the RTSP and
15756
+ * hosts the broker/restream) — the cluster ingest owner today
15757
+ * (`clusterRoles.ingestNode`), per-camera once source assignment lands. Lets
15758
+ * the UI show WHERE a camera is sourced without SSH/logs, and is the node the
15759
+ * broker block below was read from (pinned). Nullable only pre-wiring.
15760
+ */
15761
+ sourceNodeId: string().nullable(),
15650
15762
  pinned: object({
15651
15763
  detection: boolean(),
15652
15764
  decoder: boolean(),
@@ -15779,7 +15891,7 @@ method(object({
15779
15891
  }), object({ success: literal(true) }), {
15780
15892
  kind: "mutation",
15781
15893
  auth: "admin"
15782
- }), method(object({
15894
+ }), method(_void(), IngestOwnerSchema), method(object({
15783
15895
  deviceId: number(),
15784
15896
  nodeId: string()
15785
15897
  }), _void(), {
@@ -15848,6 +15960,12 @@ method(object({
15848
15960
  }), object({ success: literal(true) }), {
15849
15961
  kind: "mutation",
15850
15962
  auth: "admin"
15963
+ }), method(object({
15964
+ agentNodeId: string(),
15965
+ reachableHost: string().nullable()
15966
+ }), object({ success: literal(true) }), {
15967
+ kind: "mutation",
15968
+ auth: "admin"
15851
15969
  }), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
15852
15970
  deviceId: number(),
15853
15971
  addonId: string(),
@@ -15892,22 +16010,6 @@ method(object({
15892
16010
  kind: "mutation",
15893
16011
  auth: "admin"
15894
16012
  });
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
16013
  /**
15912
16014
  * Query filter for settings-store collections.
15913
16015
  */
@@ -16060,9 +16162,9 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
16060
16162
  /**
16061
16163
  * A single device snapshot returned as base64 JPEG/PNG.
16062
16164
  *
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.
16165
+ * The `SnapshotAddon` wrapper returns this shape whether the frame came from
16166
+ * the device-native provider (onboard capture) or from the stream-broker
16167
+ * prebuffer fallback.
16066
16168
  */
16067
16169
  var SnapshotImageSchema = object({
16068
16170
  base64: string(),
@@ -16137,10 +16239,6 @@ var snapshotCapability = {
16137
16239
  kind: "poll"
16138
16240
  }
16139
16241
  };
16140
- method(object({ deviceId: number() }), boolean()), method(object({
16141
- deviceId: number(),
16142
- streamId: string().optional()
16143
- }), SnapshotImageSchema.nullable());
16144
16242
  /**
16145
16243
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
16146
16244
  * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
@@ -16501,9 +16599,10 @@ method(object({
16501
16599
  auth: "admin"
16502
16600
  });
16503
16601
  /**
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.
16602
+ * Optional client-side hints sent at session creation to help the provider
16603
+ * pick the best native source. All fields optional — a viewer that knows
16604
+ * nothing still gets a sane default. (Relocated from the retired `webrtc`
16605
+ * collection cap; this `webrtc-session` cap is the live signaling surface.)
16507
16606
  */
16508
16607
  var webrtcClientHintsSchema = object({
16509
16608
  viewportWidth: number().int().positive().optional(),
@@ -16514,22 +16613,6 @@ var webrtcClientHintsSchema = object({
16514
16613
  /** Hard tier override; takes precedence over scoring when registered. */
16515
16614
  prefersTier: string().optional()
16516
16615
  }).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
16616
  /**
16534
16617
  * Discriminated target for a WebRTC session. The client sends this
16535
16618
  * structured object instead of building / parsing brokerId strings;
@@ -17016,7 +17099,15 @@ var FrameworkPackageStatusSchema = object({
17016
17099
  latestVersion: string().nullable(),
17017
17100
  hasUpdate: boolean(),
17018
17101
  /** Optional manifest description for the row tooltip. */
17019
- description: string().optional()
17102
+ description: string().optional(),
17103
+ /**
17104
+ * Content build-id (md5 of the resolved `dist/` tree) of the code the hub
17105
+ * ACTUALLY loaded. Framework packages ship code changes without always
17106
+ * bumping `currentVersion`, so semver alone hides "same version, new code".
17107
+ * `null` when the dist can't be hashed (not installed / empty). The admin-UI
17108
+ * surfaces this so a stale-code hub is visible even at an unchanged version.
17109
+ */
17110
+ buildId: string().nullable()
17020
17111
  });
17021
17112
  var LogStreamEntrySchema = object({
17022
17113
  timestamp: string(),
@@ -21654,6 +21745,12 @@ Object.freeze({
21654
21745
  addonId: null,
21655
21746
  access: "create"
21656
21747
  },
21748
+ "pipelineExecutor.validatePipeline": {
21749
+ capName: "pipeline-executor",
21750
+ capScope: "system",
21751
+ addonId: null,
21752
+ access: "view"
21753
+ },
21657
21754
  "pipelineOrchestrator.assignAudio": {
21658
21755
  capName: "pipeline-orchestrator",
21659
21756
  capScope: "system",
@@ -21762,6 +21859,12 @@ Object.freeze({
21762
21859
  addonId: null,
21763
21860
  access: "view"
21764
21861
  },
21862
+ "pipelineOrchestrator.getIngestOwner": {
21863
+ capName: "pipeline-orchestrator",
21864
+ capScope: "system",
21865
+ addonId: null,
21866
+ access: "view"
21867
+ },
21765
21868
  "pipelineOrchestrator.getPipelineAssignment": {
21766
21869
  capName: "pipeline-orchestrator",
21767
21870
  capScope: "system",
@@ -21834,6 +21937,12 @@ Object.freeze({
21834
21937
  addonId: null,
21835
21938
  access: "create"
21836
21939
  },
21940
+ "pipelineOrchestrator.setAgentReachableHost": {
21941
+ capName: "pipeline-orchestrator",
21942
+ capScope: "system",
21943
+ addonId: null,
21944
+ access: "create"
21945
+ },
21837
21946
  "pipelineOrchestrator.setCameraPipelineForAgent": {
21838
21947
  capName: "pipeline-orchestrator",
21839
21948
  capScope: "system",
@@ -22164,24 +22273,6 @@ Object.freeze({
22164
22273
  addonId: null,
22165
22274
  access: "create"
22166
22275
  },
22167
- "restreamer.getExposedResources": {
22168
- capName: "restreamer",
22169
- capScope: "system",
22170
- addonId: null,
22171
- access: "view"
22172
- },
22173
- "restreamer.registerDevice": {
22174
- capName: "restreamer",
22175
- capScope: "system",
22176
- addonId: null,
22177
- access: "create"
22178
- },
22179
- "restreamer.unregisterDevice": {
22180
- capName: "restreamer",
22181
- capScope: "system",
22182
- addonId: null,
22183
- access: "delete"
22184
- },
22185
22276
  "scriptRunner.run": {
22186
22277
  capName: "script-runner",
22187
22278
  capScope: "device",
@@ -22284,18 +22375,6 @@ Object.freeze({
22284
22375
  addonId: null,
22285
22376
  access: "create"
22286
22377
  },
22287
- "snapshotProvider.getSnapshot": {
22288
- capName: "snapshot-provider",
22289
- capScope: "system",
22290
- addonId: null,
22291
- access: "view"
22292
- },
22293
- "snapshotProvider.supportsDevice": {
22294
- capName: "snapshot-provider",
22295
- capScope: "system",
22296
- addonId: null,
22297
- access: "view"
22298
- },
22299
22378
  "ssoBridge.signBridgeToken": {
22300
22379
  capName: "sso-bridge",
22301
22380
  capScope: "system",
@@ -22722,30 +22801,6 @@ Object.freeze({
22722
22801
  addonId: null,
22723
22802
  access: "view"
22724
22803
  },
22725
- "streamingEngine.getStreamUrl": {
22726
- capName: "streaming-engine",
22727
- capScope: "system",
22728
- addonId: null,
22729
- access: "view"
22730
- },
22731
- "streamingEngine.listStreams": {
22732
- capName: "streaming-engine",
22733
- capScope: "system",
22734
- addonId: null,
22735
- access: "view"
22736
- },
22737
- "streamingEngine.registerStream": {
22738
- capName: "streaming-engine",
22739
- capScope: "system",
22740
- addonId: null,
22741
- access: "create"
22742
- },
22743
- "streamingEngine.unregisterStream": {
22744
- capName: "streaming-engine",
22745
- capScope: "system",
22746
- addonId: null,
22747
- access: "delete"
22748
- },
22749
22804
  "streamParams.getConfigSchema": {
22750
22805
  capName: "stream-params",
22751
22806
  capScope: "device",
@@ -23112,54 +23167,6 @@ Object.freeze({
23112
23167
  addonId: null,
23113
23168
  access: "create"
23114
23169
  },
23115
- "webrtc.closeSession": {
23116
- capName: "webrtc",
23117
- capScope: "system",
23118
- addonId: null,
23119
- access: "create"
23120
- },
23121
- "webrtc.createSession": {
23122
- capName: "webrtc",
23123
- capScope: "system",
23124
- addonId: null,
23125
- access: "create"
23126
- },
23127
- "webrtc.handleAnswer": {
23128
- capName: "webrtc",
23129
- capScope: "system",
23130
- addonId: null,
23131
- access: "create"
23132
- },
23133
- "webrtc.handleOffer": {
23134
- capName: "webrtc",
23135
- capScope: "system",
23136
- addonId: null,
23137
- access: "create"
23138
- },
23139
- "webrtc.hasAdaptiveBitrate": {
23140
- capName: "webrtc",
23141
- capScope: "system",
23142
- addonId: null,
23143
- access: "view"
23144
- },
23145
- "webrtc.registerStream": {
23146
- capName: "webrtc",
23147
- capScope: "system",
23148
- addonId: null,
23149
- access: "create"
23150
- },
23151
- "webrtc.supportsStream": {
23152
- capName: "webrtc",
23153
- capScope: "system",
23154
- addonId: null,
23155
- access: "view"
23156
- },
23157
- "webrtc.unregisterStream": {
23158
- capName: "webrtc",
23159
- capScope: "system",
23160
- addonId: null,
23161
- access: "delete"
23162
- },
23163
23170
  "webrtcSession.addIceCandidate": {
23164
23171
  capName: "webrtc-session",
23165
23172
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-onvif",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
4
4
  "description": "ONVIF camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",
@@ -44,9 +44,6 @@
44
44
  "capabilities": [
45
45
  {
46
46
  "name": "device-provider"
47
- },
48
- {
49
- "name": "snapshot-provider"
50
47
  }
51
48
  ]
52
49
  }