@camstack/addon-pipeline-orchestrator 1.2.231 → 1.2.232

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 (26) hide show
  1. package/assets/viewer/.viewer-current-files.json +10 -10
  2. package/assets/viewer/.viewer-version +1 -1
  3. package/assets/viewer/_expo/static/js/web/Clipboard-6206d4c0d423d26e04715ebf58c315df.js +8 -0
  4. package/assets/viewer/_expo/static/js/web/Haptics-49c17326a5ec4b2b140b1449cdbb7f34.js +3 -0
  5. package/assets/viewer/_expo/static/js/web/{index-f5bda3803ae44f8b74784fe8b002bf5e.js → index-ccd23c8462e4121de32d780726261a91.js} +741 -740
  6. package/assets/viewer/_expo/static/js/web/node-fs-5b88ad96e86d7063ccdbe32db825729f.js +1 -0
  7. package/assets/viewer/_expo/static/js/web/node-path-716b5d0b761c62e0b4296f79e29f89d2.js +1 -0
  8. package/assets/viewer/embed/assets/{MaskShapeCanvas-DI4BY7W2-C0fGaeA-.js → MaskShapeCanvas-DI4BY7W2-2CDlk-HT.js} +1 -1
  9. package/assets/viewer/embed/assets/{MotionZonesSettings-7c3yiT5E-9PMziv1F.js → MotionZonesSettings-7c3yiT5E-B6oGxKku.js} +1 -1
  10. package/assets/viewer/embed/assets/{PrivacyMaskSettings-PkYs077O-DXkWrrAk.js → PrivacyMaskSettings-PkYs077O-B-RISboY.js} +1 -1
  11. package/assets/viewer/embed/assets/{SceneMonitorEditor-BvDy3x3k-B9lXS5ns.js → SceneMonitorEditor-BvDy3x3k-DBWukNQx.js} +1 -1
  12. package/assets/viewer/embed/assets/index-H9aL2Im_.js +115 -0
  13. package/assets/viewer/embed/index.html +1 -1
  14. package/assets/viewer/index.html +1 -1
  15. package/dist/_stub.js +2 -2
  16. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-BuaOnnUl.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-BGdkIv4D.mjs} +2 -2
  17. package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D7D-UPVq.mjs +26 -0
  18. package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CuMONLDJ.mjs +26 -0
  19. package/dist/{hostInit-BvAUQGbR.mjs → hostInit-Cm5QAJnG.mjs} +2 -2
  20. package/dist/index.js +262 -8
  21. package/dist/index.mjs +262 -8
  22. package/dist/remoteEntry.js +1 -1
  23. package/package.json +1 -1
  24. package/assets/viewer/embed/assets/index-Btt6Cej-.js +0 -115
  25. package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D0ZIY3QM.mjs +0 -26
  26. package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-lkkSZHH0.mjs +0 -26
package/dist/index.js CHANGED
@@ -5391,7 +5391,7 @@ var ZodIssueCode = {
5391
5391
  var ZodFirstPartyTypeKind;
5392
5392
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5393
5393
  //#endregion
5394
- //#region ../types/dist/sleep-CiR4z7r9.mjs
5394
+ //#region ../types/dist/sleep-CaB_hL5g.mjs
5395
5395
  /**
5396
5396
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5397
5397
  * window to float samples (D455).
@@ -7984,6 +7984,8 @@ var EVENT_OWNER_TYPES = [
7984
7984
  * nothing failing until a caller asked.
7985
7985
  */
7986
7986
  var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7987
+ /** The same list as a Zod enum, for the cap input that carries it. */
7988
+ var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
7987
7989
  new Set(EVENT_OWNER_TYPES);
7988
7990
  var EncodeProfileSchema = object({
7989
7991
  video: object({
@@ -22161,7 +22163,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
22161
22163
  ownerType: EventOwnerTypeSchema,
22162
22164
  eventId: number().int(),
22163
22165
  deviceId: number()
22164
- }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
22166
+ }), array(MediaFileInfoSchema).readonly()), method(object({
22167
+ deviceId: number(),
22168
+ ownerKind: MediaPresenceOwnerKindSchema,
22169
+ ownerIds: array(string()).max(5e3)
22170
+ }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
22165
22171
  kind: "mutation",
22166
22172
  auth: "admin"
22167
22173
  }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
@@ -25686,6 +25692,18 @@ method(VectorDeclareIndexInputSchema, _void(), {
25686
25692
  kind: "mutation",
25687
25693
  auth: "admin"
25688
25694
  }), method(VectorStatsInputSchema, VectorStatsResultSchema, { auth: "admin" });
25695
+ _enum([
25696
+ "queue-full",
25697
+ "camera-backoff",
25698
+ "sleeping",
25699
+ "camera-refused",
25700
+ "no-keyframe",
25701
+ "no-catalog-row",
25702
+ "unsupported",
25703
+ "unknown-device",
25704
+ "uid-missing"
25705
+ ]);
25706
+ _enum(["deferred", "final"]);
25689
25707
  var ClipSchema = object({
25690
25708
  /** Opaque, provider-namespaced id. The default provider encodes the time
25691
25709
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -25760,29 +25778,243 @@ var ClipSchema = object({
25760
25778
  holes: array(object({
25761
25779
  startMs: number(),
25762
25780
  endMs: number()
25763
- })).optional()
25781
+ })).optional(),
25782
+ /**
25783
+ * A URL that MAY produce this clip's still, asked only for rows actually on
25784
+ * screen. The opposite of {@link ClipSchema.thumbnail}: that one is VOUCHED
25785
+ * (a JPEG is already on disk), this one is an offer. The route answers 200
25786
+ * with the image, or 204 with `x-camstack-reason` when it could not mint one
25787
+ * — a surface latches that refusal to the instant rather than retrying.
25788
+ *
25789
+ * Never both: a clip with a vouched `thumbnail` needs no mint.
25790
+ */
25791
+ thumbnailMint: string().optional(),
25792
+ /**
25793
+ * Why no still will be produced for this clip RIGHT NOW — set when the
25794
+ * provider already knows, so the surface draws the glyph and the reason
25795
+ * instead of firing a mint that cannot succeed.
25796
+ *
25797
+ * `sleeping` — a standalone battery camera; a read would be a wake (D549 4).
25798
+ * `camera-refused` — the camera answered the CoverPreview with a refusal.
25799
+ * `no-keyframe` — the window holds no decodable I-frame (an event that sits
25800
+ * inside no file is the measured case).
25801
+ * `unsupported` — this source cannot mint stills at all.
25802
+ */
25803
+ thumbnailUnavailable: object({ reason: _enum([
25804
+ "sleeping",
25805
+ "camera-refused",
25806
+ "no-keyframe",
25807
+ "unsupported"
25808
+ ]) }).optional(),
25809
+ /**
25810
+ * When the catalog this row came from was last CONFIRMED against the device.
25811
+ * Absent means "this row was read live". A persisted catalog served while a
25812
+ * camera sleeps carries the age it really has — a cached list is never drawn
25813
+ * as current (D549 13).
25814
+ */
25815
+ catalogAsOf: number().optional(),
25816
+ /**
25817
+ * The camera's OWN type strings for this clip, all of them, unmapped
25818
+ * (`md`, `people`, `dog_cat`, `sched`, …). Kept beside {@link labels}
25819
+ * because a firmware inventing a type must not vanish: the mapping into our
25820
+ * filter vocabulary is lossy on purpose and this is the lossless copy
25821
+ * (D549 20).
25822
+ */
25823
+ nativeTypes: array(string()).optional(),
25824
+ /**
25825
+ * What the subject DID — `crossline`, `intrude`, `loitering`. A behaviour
25826
+ * travels BESIDE a class, never instead of one, and the class filter ignores
25827
+ * it: "a person crossed a line" is still a person (D549 20).
25828
+ */
25829
+ behaviours: array(string()).optional(),
25830
+ /**
25831
+ * The same recording as two files — the sub twin (what the row and its
25832
+ * thumbnail are) and its main twin, matched at listing time so a quality
25833
+ * change never re-searches the camera (D549 15). `getClipPlayback`'s
25834
+ * `profile` picks between them: `low | mid` → sub, `high` → main.
25835
+ */
25836
+ streams: object({
25837
+ sub: object({
25838
+ id: string(),
25839
+ bytes: number().optional()
25840
+ }).optional(),
25841
+ main: object({
25842
+ id: string(),
25843
+ bytes: number().optional()
25844
+ }).optional()
25845
+ }).optional(),
25846
+ /** The camera says it holds a sub-stream copy of this recording. */
25847
+ supportSub: boolean().optional(),
25848
+ /**
25849
+ * Whether this row has bytes behind it. ABSENT means yes — every clip that
25850
+ * IS a file is playable, and only a source that lists EVENTS can produce a
25851
+ * row with nothing to play (the measured case: 1 of 29 hub events on 3628
25852
+ * fell inside no file). Such a row is shown, never dropped and never offered
25853
+ * as playable-then-failing (D549 19).
25854
+ */
25855
+ playable: boolean().optional(),
25856
+ /** Why {@link playable} is false, verbatim (`no-file-for-window`). */
25857
+ unplayableReason: string().optional(),
25858
+ /**
25859
+ * This clip is STILL BEING WRITTEN, so {@link ClipSchema.timeRange}`.endMs`
25860
+ * is not its end.
25861
+ *
25862
+ * Absent — the common case — means the row is closed and its `endMs` is the
25863
+ * end of the recording. Present and `true` means the source told us the file
25864
+ * has no end yet, and the `endMs` we carry is whatever the camera's index
25865
+ * entry happened to hold: on a Reolink E1 Outdoor PoE (592, measured
25866
+ * 2026-09-20) the newest file `…_192927_000000_…_0.mp4` reported an `endTime`
25867
+ * of 19:29:56 and STILL reported it eight minutes later, while the file went
25868
+ * on growing. A surface that drew 29 s there was lying about a clip that
25869
+ * plays for minutes, on the one row the operator looks at first.
25870
+ *
25871
+ * `endMs` is deliberately still a number: it is the best bound anything has
25872
+ * for a mint window or a byte fetch, and every consumer already requires it.
25873
+ * This flag says what it is WORTH, and a duration is not drawn from it.
25874
+ */
25875
+ inProgress: boolean().optional()
25764
25876
  });
25765
25877
  var ClipPlaybackSchema = object({
25766
- /** HLS master URL through the hub data-plane (Range + token in path). */
25878
+ /**
25879
+ * A media URL through the hub data-plane. {@link ClipPlaybackSchema.format}
25880
+ * says what kind — an HLS master playlist for a recording-derived clip, a
25881
+ * progressive MP4 for a native file the vendor addon muxed and serves with
25882
+ * `Range`. The URL is NOT a credential: both planes are registered
25883
+ * `access: 'authenticated'` and the hub's `/addon/<id>/<prefix>` proxy takes
25884
+ * the session cookie, so nothing is appended to it (D549 22).
25885
+ */
25767
25886
  playbackUrl: string(),
25887
+ /**
25888
+ * How to play {@link playbackUrl}. Absent means `hls` — the shape every
25889
+ * existing consumer already assumes. A player that branches on this is the
25890
+ * one change a native clip needs; a player that ignores it will hand an MP4
25891
+ * to hls.js and fail parsing it as a manifest.
25892
+ */
25893
+ format: _enum(["hls", "mp4"]).optional(),
25894
+ /**
25895
+ * Which twin was actually served. A clip that holds only one stream answers
25896
+ * with the one it has, and the surface SAYS so — a missing main twin is
25897
+ * never served silently as if it were the asked-for quality (D549 15).
25898
+ */
25899
+ served: CamProfileSchema.optional(),
25768
25900
  /** Optional LAN/remote alternates for the same clip. */
25769
25901
  playbackEndpoints: array(string()).optional(),
25770
25902
  token: string().optional()
25771
25903
  });
25904
+ /**
25905
+ * Why a source cannot answer right now — per SOURCE, never fleet-wide.
25906
+ *
25907
+ * `ok` is the only state whose clip list may be read as complete. The other
25908
+ * three exist because an empty list from a sleeping camera reads as "this
25909
+ * camera has no recordings", which is the defect this whole line of work is
25910
+ * about: a battery camera nobody woke (`sleeping`), a camera that could not be
25911
+ * reached at all (`unreachable`, also what a provider that THREW reports), a
25912
+ * camera whose SD card is not mounted (`no-storage`, measured on 640 —
25913
+ * `HddInfo mount 0`, honestly nothing to list rather than "no clips"), and a
25914
+ * camera whose OWN index disagrees with its OWN calendar (`index-empty`,
25915
+ * measured on 618: the calendar marks 3–20 September, days 6–20 list zero
25916
+ * files on both streams and both filters, with 177 GB free). That last one is
25917
+ * a defect ON THE CAMERA, and the only honest thing a provider can do is say
25918
+ * which days it asked for and got nothing — "no clips" would be a lie about a
25919
+ * card that is full of them.
25920
+ */
25921
+ var ClipSourceAvailabilitySchema = object({
25922
+ state: _enum([
25923
+ "ok",
25924
+ "sleeping",
25925
+ "unreachable",
25926
+ "no-storage",
25927
+ "index-empty"
25928
+ ]),
25929
+ /** Free text, shown verbatim. Names the camera's own refusal when there is one. */
25930
+ reason: string().optional(),
25931
+ /** When this source's catalog was last CONFIRMED. A cached list is never
25932
+ * drawn as current: the surface shows the age whenever it is older than the
25933
+ * refresh interval. */
25934
+ catalogAsOf: number().optional()
25935
+ });
25936
+ /**
25937
+ * One SOURCE of clips for a camera — a row of the picker, and the namespace
25938
+ * every clip id from it is prefixed with (`analytics`,
25939
+ * `native:reolink:onboard`, `hksv`, …).
25940
+ *
25941
+ * A provider lists the sources IT serves for that device, and answers for each
25942
+ * of them whether it can answer at all. A provider with nothing to offer on a
25943
+ * camera returns `[]` — it is not that camera's business.
25944
+ */
25945
+ var ClipSourceSchema = object({
25946
+ /** The value this source stamps on {@link ClipSchema.source}, and the prefix
25947
+ * of every clip id it mints. `getClipPlayback` routes on it. */
25948
+ source: string(),
25949
+ /** Operator-facing name of the source ("CamStack recordings", "SD card"). */
25950
+ label: string(),
25951
+ /**
25952
+ * The addon that SERVES this row.
25953
+ *
25954
+ * A surface cannot otherwise resolve a source to who answers it, and the
25955
+ * alternative — a `native:reolink:* → provider-reolink` table inside the
25956
+ * widget — is a second authority on provider identity living in the one
25957
+ * package with no business knowing it, wrong the day a third source appears
25958
+ * (D557). One addon may serve SEVERAL sources, which is why the per-device
25959
+ * switch is keyed by SOURCE and not by this (D555) — `addon-provider-reolink`
25960
+ * served a hub child two of them until the two views were measured to be one
25961
+ * store read twice (D555) and then read ONE way (D565).
25962
+ *
25963
+ * Optional for version skew only. The collection dispatcher stamps it from
25964
+ * the registry, so a row that travelled through the fan-out carries the
25965
+ * authoritative id whatever the provider filled in.
25966
+ */
25967
+ addonId: string().optional(),
25968
+ availability: ClipSourceAvailabilitySchema
25969
+ });
25772
25970
  DeviceType.Camera, method(object({
25773
25971
  deviceId: number(),
25774
25972
  since: number(),
25775
25973
  until: number(),
25776
- limit: number().int().positive().optional()
25974
+ limit: number().int().positive().optional(),
25975
+ /**
25976
+ * WHICH provider to ask — the `addonId` a {@link ClipSourceSchema} row
25977
+ * carries, never a source id and never a list.
25978
+ *
25979
+ * Absent means the device's BOUND provider, which is CamStack on every
25980
+ * camera today because that is what `deviceManager.getBindings`
25981
+ * answers with. A provider the device is not bound to is refused by
25982
+ * name rather than answered by another one (D552's
25983
+ * `rejectUnresolvedAddonPin` rule).
25984
+ *
25985
+ * It replaced `sources?: string[]`, a VIEW filter over source ids that
25986
+ * assumed the answer was a fan-out over everything a camera has. The
25987
+ * operator settled otherwise on 2026-09-20 — *"il get videoclips
25988
+ * prendere un provider opzionale, di default va su camstack"*,
25989
+ * *"l'utilizzatore è uno solo"* — so the list is asked of one provider
25990
+ * at a time and there is nothing to filter out of it.
25991
+ */
25992
+ provider: string().optional()
25777
25993
  }), array(ClipSchema).readonly(), {
25778
25994
  kind: "query",
25779
- auth: "admin"
25995
+ auth: "protected"
25996
+ }), method(object({ deviceId: number() }), array(ClipSourceSchema).readonly(), {
25997
+ kind: "query",
25998
+ auth: "protected"
25780
25999
  }), method(object({
25781
26000
  deviceId: number(),
25782
- clipId: string()
26001
+ clipId: string(),
26002
+ /**
26003
+ * Which twin to serve, on the ONE quality scale the system already has
26004
+ * (`CamProfileSchema`). `low | mid` → the sub file, `high` → the main
26005
+ * twin; both ids are already on the row so this never re-searches the
26006
+ * camera. Absent means the provider's own default (the sub file, which
26007
+ * every source is measured to hold).
26008
+ *
26009
+ * `auto` is deliberately NOT accepted here: a stored file has no
26010
+ * broker session, so the adaptive tier cannot be resolved for it. The
26011
+ * viewer resolves `auto` to a profile the same way live does, before
26012
+ * it calls (D549 15).
26013
+ */
26014
+ profile: CamProfileSchema.optional()
25783
26015
  }), ClipPlaybackSchema, {
25784
26016
  kind: "query",
25785
- auth: "admin"
26017
+ auth: "protected"
25786
26018
  });
25787
26019
  /**
25788
26020
  * Optional client-side hints sent at session creation to help the provider
@@ -37275,6 +37507,12 @@ Object.freeze({
37275
37507
  addonId: null,
37276
37508
  access: "view"
37277
37509
  },
37510
+ "pipelineAnalytics.ownersWithMedia": {
37511
+ capName: "pipeline-analytics",
37512
+ capScope: "device",
37513
+ addonId: null,
37514
+ access: "view"
37515
+ },
37278
37516
  "pipelineAnalytics.pauseForStorageMigration": {
37279
37517
  capName: "pipeline-analytics",
37280
37518
  capScope: "device",
@@ -39867,6 +40105,12 @@ Object.freeze({
39867
40105
  addonId: null,
39868
40106
  access: "view"
39869
40107
  },
40108
+ "videoclips.listSources": {
40109
+ capName: "videoclips",
40110
+ capScope: "device",
40111
+ addonId: null,
40112
+ access: "view"
40113
+ },
39870
40114
  "viewerUi.getStaticDir": {
39871
40115
  capName: "viewer-ui",
39872
40116
  capScope: "system",
@@ -41169,6 +41413,11 @@ Object.freeze({
41169
41413
  form: "single",
41170
41414
  optional: false
41171
41415
  }],
41416
+ "pipelineAnalytics.ownersWithMedia": [{
41417
+ name: "deviceId",
41418
+ form: "single",
41419
+ optional: false
41420
+ }],
41172
41421
  "pipelineAnalytics.proposeRetrainAnnotations": [{
41173
41422
  name: "deviceId",
41174
41423
  form: "single",
@@ -41875,6 +42124,11 @@ Object.freeze({
41875
42124
  form: "single",
41876
42125
  optional: false
41877
42126
  }],
42127
+ "videoclips.listSources": [{
42128
+ name: "deviceId",
42129
+ form: "single",
42130
+ optional: false
42131
+ }],
41878
42132
  "waterHeater.setAway": [{
41879
42133
  name: "deviceId",
41880
42134
  form: "single",
package/dist/index.mjs CHANGED
@@ -5363,7 +5363,7 @@ var ZodIssueCode = {
5363
5363
  var ZodFirstPartyTypeKind;
5364
5364
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5365
5365
  //#endregion
5366
- //#region ../types/dist/sleep-CiR4z7r9.mjs
5366
+ //#region ../types/dist/sleep-CaB_hL5g.mjs
5367
5367
  /**
5368
5368
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5369
5369
  * window to float samples (D455).
@@ -7956,6 +7956,8 @@ var EVENT_OWNER_TYPES = [
7956
7956
  * nothing failing until a caller asked.
7957
7957
  */
7958
7958
  var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7959
+ /** The same list as a Zod enum, for the cap input that carries it. */
7960
+ var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
7959
7961
  new Set(EVENT_OWNER_TYPES);
7960
7962
  var EncodeProfileSchema = object({
7961
7963
  video: object({
@@ -22133,7 +22135,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
22133
22135
  ownerType: EventOwnerTypeSchema,
22134
22136
  eventId: number().int(),
22135
22137
  deviceId: number()
22136
- }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
22138
+ }), array(MediaFileInfoSchema).readonly()), method(object({
22139
+ deviceId: number(),
22140
+ ownerKind: MediaPresenceOwnerKindSchema,
22141
+ ownerIds: array(string()).max(5e3)
22142
+ }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
22137
22143
  kind: "mutation",
22138
22144
  auth: "admin"
22139
22145
  }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
@@ -25658,6 +25664,18 @@ method(VectorDeclareIndexInputSchema, _void(), {
25658
25664
  kind: "mutation",
25659
25665
  auth: "admin"
25660
25666
  }), method(VectorStatsInputSchema, VectorStatsResultSchema, { auth: "admin" });
25667
+ _enum([
25668
+ "queue-full",
25669
+ "camera-backoff",
25670
+ "sleeping",
25671
+ "camera-refused",
25672
+ "no-keyframe",
25673
+ "no-catalog-row",
25674
+ "unsupported",
25675
+ "unknown-device",
25676
+ "uid-missing"
25677
+ ]);
25678
+ _enum(["deferred", "final"]);
25661
25679
  var ClipSchema = object({
25662
25680
  /** Opaque, provider-namespaced id. The default provider encodes the time
25663
25681
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -25732,29 +25750,243 @@ var ClipSchema = object({
25732
25750
  holes: array(object({
25733
25751
  startMs: number(),
25734
25752
  endMs: number()
25735
- })).optional()
25753
+ })).optional(),
25754
+ /**
25755
+ * A URL that MAY produce this clip's still, asked only for rows actually on
25756
+ * screen. The opposite of {@link ClipSchema.thumbnail}: that one is VOUCHED
25757
+ * (a JPEG is already on disk), this one is an offer. The route answers 200
25758
+ * with the image, or 204 with `x-camstack-reason` when it could not mint one
25759
+ * — a surface latches that refusal to the instant rather than retrying.
25760
+ *
25761
+ * Never both: a clip with a vouched `thumbnail` needs no mint.
25762
+ */
25763
+ thumbnailMint: string().optional(),
25764
+ /**
25765
+ * Why no still will be produced for this clip RIGHT NOW — set when the
25766
+ * provider already knows, so the surface draws the glyph and the reason
25767
+ * instead of firing a mint that cannot succeed.
25768
+ *
25769
+ * `sleeping` — a standalone battery camera; a read would be a wake (D549 4).
25770
+ * `camera-refused` — the camera answered the CoverPreview with a refusal.
25771
+ * `no-keyframe` — the window holds no decodable I-frame (an event that sits
25772
+ * inside no file is the measured case).
25773
+ * `unsupported` — this source cannot mint stills at all.
25774
+ */
25775
+ thumbnailUnavailable: object({ reason: _enum([
25776
+ "sleeping",
25777
+ "camera-refused",
25778
+ "no-keyframe",
25779
+ "unsupported"
25780
+ ]) }).optional(),
25781
+ /**
25782
+ * When the catalog this row came from was last CONFIRMED against the device.
25783
+ * Absent means "this row was read live". A persisted catalog served while a
25784
+ * camera sleeps carries the age it really has — a cached list is never drawn
25785
+ * as current (D549 13).
25786
+ */
25787
+ catalogAsOf: number().optional(),
25788
+ /**
25789
+ * The camera's OWN type strings for this clip, all of them, unmapped
25790
+ * (`md`, `people`, `dog_cat`, `sched`, …). Kept beside {@link labels}
25791
+ * because a firmware inventing a type must not vanish: the mapping into our
25792
+ * filter vocabulary is lossy on purpose and this is the lossless copy
25793
+ * (D549 20).
25794
+ */
25795
+ nativeTypes: array(string()).optional(),
25796
+ /**
25797
+ * What the subject DID — `crossline`, `intrude`, `loitering`. A behaviour
25798
+ * travels BESIDE a class, never instead of one, and the class filter ignores
25799
+ * it: "a person crossed a line" is still a person (D549 20).
25800
+ */
25801
+ behaviours: array(string()).optional(),
25802
+ /**
25803
+ * The same recording as two files — the sub twin (what the row and its
25804
+ * thumbnail are) and its main twin, matched at listing time so a quality
25805
+ * change never re-searches the camera (D549 15). `getClipPlayback`'s
25806
+ * `profile` picks between them: `low | mid` → sub, `high` → main.
25807
+ */
25808
+ streams: object({
25809
+ sub: object({
25810
+ id: string(),
25811
+ bytes: number().optional()
25812
+ }).optional(),
25813
+ main: object({
25814
+ id: string(),
25815
+ bytes: number().optional()
25816
+ }).optional()
25817
+ }).optional(),
25818
+ /** The camera says it holds a sub-stream copy of this recording. */
25819
+ supportSub: boolean().optional(),
25820
+ /**
25821
+ * Whether this row has bytes behind it. ABSENT means yes — every clip that
25822
+ * IS a file is playable, and only a source that lists EVENTS can produce a
25823
+ * row with nothing to play (the measured case: 1 of 29 hub events on 3628
25824
+ * fell inside no file). Such a row is shown, never dropped and never offered
25825
+ * as playable-then-failing (D549 19).
25826
+ */
25827
+ playable: boolean().optional(),
25828
+ /** Why {@link playable} is false, verbatim (`no-file-for-window`). */
25829
+ unplayableReason: string().optional(),
25830
+ /**
25831
+ * This clip is STILL BEING WRITTEN, so {@link ClipSchema.timeRange}`.endMs`
25832
+ * is not its end.
25833
+ *
25834
+ * Absent — the common case — means the row is closed and its `endMs` is the
25835
+ * end of the recording. Present and `true` means the source told us the file
25836
+ * has no end yet, and the `endMs` we carry is whatever the camera's index
25837
+ * entry happened to hold: on a Reolink E1 Outdoor PoE (592, measured
25838
+ * 2026-09-20) the newest file `…_192927_000000_…_0.mp4` reported an `endTime`
25839
+ * of 19:29:56 and STILL reported it eight minutes later, while the file went
25840
+ * on growing. A surface that drew 29 s there was lying about a clip that
25841
+ * plays for minutes, on the one row the operator looks at first.
25842
+ *
25843
+ * `endMs` is deliberately still a number: it is the best bound anything has
25844
+ * for a mint window or a byte fetch, and every consumer already requires it.
25845
+ * This flag says what it is WORTH, and a duration is not drawn from it.
25846
+ */
25847
+ inProgress: boolean().optional()
25736
25848
  });
25737
25849
  var ClipPlaybackSchema = object({
25738
- /** HLS master URL through the hub data-plane (Range + token in path). */
25850
+ /**
25851
+ * A media URL through the hub data-plane. {@link ClipPlaybackSchema.format}
25852
+ * says what kind — an HLS master playlist for a recording-derived clip, a
25853
+ * progressive MP4 for a native file the vendor addon muxed and serves with
25854
+ * `Range`. The URL is NOT a credential: both planes are registered
25855
+ * `access: 'authenticated'` and the hub's `/addon/<id>/<prefix>` proxy takes
25856
+ * the session cookie, so nothing is appended to it (D549 22).
25857
+ */
25739
25858
  playbackUrl: string(),
25859
+ /**
25860
+ * How to play {@link playbackUrl}. Absent means `hls` — the shape every
25861
+ * existing consumer already assumes. A player that branches on this is the
25862
+ * one change a native clip needs; a player that ignores it will hand an MP4
25863
+ * to hls.js and fail parsing it as a manifest.
25864
+ */
25865
+ format: _enum(["hls", "mp4"]).optional(),
25866
+ /**
25867
+ * Which twin was actually served. A clip that holds only one stream answers
25868
+ * with the one it has, and the surface SAYS so — a missing main twin is
25869
+ * never served silently as if it were the asked-for quality (D549 15).
25870
+ */
25871
+ served: CamProfileSchema.optional(),
25740
25872
  /** Optional LAN/remote alternates for the same clip. */
25741
25873
  playbackEndpoints: array(string()).optional(),
25742
25874
  token: string().optional()
25743
25875
  });
25876
+ /**
25877
+ * Why a source cannot answer right now — per SOURCE, never fleet-wide.
25878
+ *
25879
+ * `ok` is the only state whose clip list may be read as complete. The other
25880
+ * three exist because an empty list from a sleeping camera reads as "this
25881
+ * camera has no recordings", which is the defect this whole line of work is
25882
+ * about: a battery camera nobody woke (`sleeping`), a camera that could not be
25883
+ * reached at all (`unreachable`, also what a provider that THREW reports), a
25884
+ * camera whose SD card is not mounted (`no-storage`, measured on 640 —
25885
+ * `HddInfo mount 0`, honestly nothing to list rather than "no clips"), and a
25886
+ * camera whose OWN index disagrees with its OWN calendar (`index-empty`,
25887
+ * measured on 618: the calendar marks 3–20 September, days 6–20 list zero
25888
+ * files on both streams and both filters, with 177 GB free). That last one is
25889
+ * a defect ON THE CAMERA, and the only honest thing a provider can do is say
25890
+ * which days it asked for and got nothing — "no clips" would be a lie about a
25891
+ * card that is full of them.
25892
+ */
25893
+ var ClipSourceAvailabilitySchema = object({
25894
+ state: _enum([
25895
+ "ok",
25896
+ "sleeping",
25897
+ "unreachable",
25898
+ "no-storage",
25899
+ "index-empty"
25900
+ ]),
25901
+ /** Free text, shown verbatim. Names the camera's own refusal when there is one. */
25902
+ reason: string().optional(),
25903
+ /** When this source's catalog was last CONFIRMED. A cached list is never
25904
+ * drawn as current: the surface shows the age whenever it is older than the
25905
+ * refresh interval. */
25906
+ catalogAsOf: number().optional()
25907
+ });
25908
+ /**
25909
+ * One SOURCE of clips for a camera — a row of the picker, and the namespace
25910
+ * every clip id from it is prefixed with (`analytics`,
25911
+ * `native:reolink:onboard`, `hksv`, …).
25912
+ *
25913
+ * A provider lists the sources IT serves for that device, and answers for each
25914
+ * of them whether it can answer at all. A provider with nothing to offer on a
25915
+ * camera returns `[]` — it is not that camera's business.
25916
+ */
25917
+ var ClipSourceSchema = object({
25918
+ /** The value this source stamps on {@link ClipSchema.source}, and the prefix
25919
+ * of every clip id it mints. `getClipPlayback` routes on it. */
25920
+ source: string(),
25921
+ /** Operator-facing name of the source ("CamStack recordings", "SD card"). */
25922
+ label: string(),
25923
+ /**
25924
+ * The addon that SERVES this row.
25925
+ *
25926
+ * A surface cannot otherwise resolve a source to who answers it, and the
25927
+ * alternative — a `native:reolink:* → provider-reolink` table inside the
25928
+ * widget — is a second authority on provider identity living in the one
25929
+ * package with no business knowing it, wrong the day a third source appears
25930
+ * (D557). One addon may serve SEVERAL sources, which is why the per-device
25931
+ * switch is keyed by SOURCE and not by this (D555) — `addon-provider-reolink`
25932
+ * served a hub child two of them until the two views were measured to be one
25933
+ * store read twice (D555) and then read ONE way (D565).
25934
+ *
25935
+ * Optional for version skew only. The collection dispatcher stamps it from
25936
+ * the registry, so a row that travelled through the fan-out carries the
25937
+ * authoritative id whatever the provider filled in.
25938
+ */
25939
+ addonId: string().optional(),
25940
+ availability: ClipSourceAvailabilitySchema
25941
+ });
25744
25942
  DeviceType.Camera, method(object({
25745
25943
  deviceId: number(),
25746
25944
  since: number(),
25747
25945
  until: number(),
25748
- limit: number().int().positive().optional()
25946
+ limit: number().int().positive().optional(),
25947
+ /**
25948
+ * WHICH provider to ask — the `addonId` a {@link ClipSourceSchema} row
25949
+ * carries, never a source id and never a list.
25950
+ *
25951
+ * Absent means the device's BOUND provider, which is CamStack on every
25952
+ * camera today because that is what `deviceManager.getBindings`
25953
+ * answers with. A provider the device is not bound to is refused by
25954
+ * name rather than answered by another one (D552's
25955
+ * `rejectUnresolvedAddonPin` rule).
25956
+ *
25957
+ * It replaced `sources?: string[]`, a VIEW filter over source ids that
25958
+ * assumed the answer was a fan-out over everything a camera has. The
25959
+ * operator settled otherwise on 2026-09-20 — *"il get videoclips
25960
+ * prendere un provider opzionale, di default va su camstack"*,
25961
+ * *"l'utilizzatore è uno solo"* — so the list is asked of one provider
25962
+ * at a time and there is nothing to filter out of it.
25963
+ */
25964
+ provider: string().optional()
25749
25965
  }), array(ClipSchema).readonly(), {
25750
25966
  kind: "query",
25751
- auth: "admin"
25967
+ auth: "protected"
25968
+ }), method(object({ deviceId: number() }), array(ClipSourceSchema).readonly(), {
25969
+ kind: "query",
25970
+ auth: "protected"
25752
25971
  }), method(object({
25753
25972
  deviceId: number(),
25754
- clipId: string()
25973
+ clipId: string(),
25974
+ /**
25975
+ * Which twin to serve, on the ONE quality scale the system already has
25976
+ * (`CamProfileSchema`). `low | mid` → the sub file, `high` → the main
25977
+ * twin; both ids are already on the row so this never re-searches the
25978
+ * camera. Absent means the provider's own default (the sub file, which
25979
+ * every source is measured to hold).
25980
+ *
25981
+ * `auto` is deliberately NOT accepted here: a stored file has no
25982
+ * broker session, so the adaptive tier cannot be resolved for it. The
25983
+ * viewer resolves `auto` to a profile the same way live does, before
25984
+ * it calls (D549 15).
25985
+ */
25986
+ profile: CamProfileSchema.optional()
25755
25987
  }), ClipPlaybackSchema, {
25756
25988
  kind: "query",
25757
- auth: "admin"
25989
+ auth: "protected"
25758
25990
  });
25759
25991
  /**
25760
25992
  * Optional client-side hints sent at session creation to help the provider
@@ -37247,6 +37479,12 @@ Object.freeze({
37247
37479
  addonId: null,
37248
37480
  access: "view"
37249
37481
  },
37482
+ "pipelineAnalytics.ownersWithMedia": {
37483
+ capName: "pipeline-analytics",
37484
+ capScope: "device",
37485
+ addonId: null,
37486
+ access: "view"
37487
+ },
37250
37488
  "pipelineAnalytics.pauseForStorageMigration": {
37251
37489
  capName: "pipeline-analytics",
37252
37490
  capScope: "device",
@@ -39839,6 +40077,12 @@ Object.freeze({
39839
40077
  addonId: null,
39840
40078
  access: "view"
39841
40079
  },
40080
+ "videoclips.listSources": {
40081
+ capName: "videoclips",
40082
+ capScope: "device",
40083
+ addonId: null,
40084
+ access: "view"
40085
+ },
39842
40086
  "viewerUi.getStaticDir": {
39843
40087
  capName: "viewer-ui",
39844
40088
  capScope: "system",
@@ -41141,6 +41385,11 @@ Object.freeze({
41141
41385
  form: "single",
41142
41386
  optional: false
41143
41387
  }],
41388
+ "pipelineAnalytics.ownersWithMedia": [{
41389
+ name: "deviceId",
41390
+ form: "single",
41391
+ optional: false
41392
+ }],
41144
41393
  "pipelineAnalytics.proposeRetrainAnnotations": [{
41145
41394
  name: "deviceId",
41146
41395
  form: "single",
@@ -41847,6 +42096,11 @@ Object.freeze({
41847
42096
  form: "single",
41848
42097
  optional: false
41849
42098
  }],
42099
+ "videoclips.listSources": [{
42100
+ name: "deviceId",
42101
+ form: "single",
42102
+ optional: false
42103
+ }],
41850
42104
  "waterHeater.setAway": [{
41851
42105
  name: "deviceId",
41852
42106
  form: "single",