@camstack/addon-pipeline-orchestrator 1.2.230 → 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-Cva58zcK.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-BiEedqeH.mjs → hostInit-Cm5QAJnG.mjs} +2 -2
  20. package/dist/index.js +663 -16
  21. package/dist/index.mjs +663 -16
  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-Cm0B7Kaq.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, {
@@ -24359,10 +24365,9 @@ var ServerBootModeSchema = _enum([
24359
24365
  * - `idle` / `checking` / `staging` — steady / in-flight registry work.
24360
24366
  * - `pending-restart` — a version is staged and the node has NOT yet
24361
24367
  * restarted onto it (still running the OLD version).
24362
- * - `awaiting-confirmation` — the node HAS restarted onto the staged version
24363
- * (it is the active probation boot) and is waiting to confirm boot-health.
24364
- * Apply/rollback are refused in this state and the node must NOT be
24365
- * manually restarted, or the probation boot auto-rolls-back.
24368
+ * - `awaiting-confirmation` — RETIRED with the probation boot (single-copy
24369
+ * collapse, 2026-07-18). Kept in the enum so an older client still parses;
24370
+ * no provider emits it.
24366
24371
  */
24367
24372
  var ServerUpdateStateSchema = _enum([
24368
24373
  "idle",
@@ -24451,12 +24456,35 @@ var ServerRollbackInfoSchema = object({
24451
24456
  var ServerPackageStatusSchema = object({
24452
24457
  /** Root package name (`@camstack/server` on the hub). */
24453
24458
  packageName: string(),
24454
- /** Version of the code the running process ACTUALLY loaded. */
24459
+ /**
24460
+ * Version of the code THIS PROCESS loaded — captured once, at boot, from the
24461
+ * package.json of the copy the code was loaded from, and never re-read. A
24462
+ * copy replaced on disk under a live process must not move this number;
24463
+ * that divergence is what `installedVersion` is for. Before 2026-09-20 this
24464
+ * was a lazy disk read that would have followed such a swap silently.
24465
+ */
24455
24466
  runningVersion: string().nullable(),
24456
24467
  /** Node.js runtime version the node's process runs on (`process.versions.node`). */
24457
24468
  nodeRuntimeVersion: string().nullable(),
24458
24469
  /** Active data-dir root version; null when booted from seed/workspace. */
24459
24470
  activeVersion: string().nullable(),
24471
+ /**
24472
+ * Version of the root package in the data-root `current/` copy RIGHT NOW —
24473
+ * what the next boot would load if nothing is staged. Normally equal to
24474
+ * `runningVersion`; differs only when the copy on disk changed under the
24475
+ * live process. `null` when there is no data-root copy (baked seed or
24476
+ * workspace boot). Optional for version skew: an older provider omits it.
24477
+ */
24478
+ installedVersion: string().nullable().optional(),
24479
+ /**
24480
+ * True when the code that would run after a restart is NOT the code running
24481
+ * now: a version is staged (`pendingVersion`), or `installedVersion` differs
24482
+ * from `runningVersion`. Derived on every read, never stored. This is the
24483
+ * one field that lets an operator or a script say "1.2.353 is installed and
24484
+ * will run after a restart" instead of reading it off a number that means
24485
+ * "installed". Optional for version skew.
24486
+ */
24487
+ restartRequired: boolean().optional(),
24460
24488
  /** N-1 version kept for rollback; null when no previous version exists. */
24461
24489
  previousVersion: string().nullable(),
24462
24490
  /** Version of the immutable baked seed closure (image fallback). */
@@ -24491,19 +24519,71 @@ var ServerPackageStatusSchema = object({
24491
24519
  });
24492
24520
  var ServerUpdateCheckResultSchema = object({
24493
24521
  packageName: string(),
24522
+ /** Same meaning as on the status payload: what THIS process loaded. */
24494
24523
  runningVersion: string().nullable(),
24495
24524
  latestVersion: string().nullable(),
24525
+ /**
24526
+ * `latestVersion` is newer than `runningVersion` AND is not the version
24527
+ * already staged on this node. A staged-and-waiting version is not "an
24528
+ * update available" — applying it again is refused (`already-staged`), and
24529
+ * a button that announced "updating…" on this boolean then got refused
24530
+ * would be lying. It is reported through `pendingVersion` instead.
24531
+ */
24496
24532
  updateAvailable: boolean(),
24497
24533
  checkedAtMs: number(),
24498
24534
  /** Non-null when the registry lookup failed (offline, bad registry, …). */
24499
- error: string().nullable()
24535
+ error: string().nullable(),
24536
+ /**
24537
+ * Version staged on this node and awaiting its restart, when one is. Before
24538
+ * 2026-09-20 a check answered `{runningVersion, updateAvailable:false}` for a
24539
+ * node whose staged closure had never booted, and the caller had no way to
24540
+ * see the difference. Optional for version skew.
24541
+ */
24542
+ pendingVersion: string().nullable().optional(),
24543
+ /** See the status payload: staged, or installed-on-disk ≠ running. Optional for skew. */
24544
+ restartRequired: boolean().optional()
24500
24545
  });
24546
+ /**
24547
+ * What an action DID — the discriminant `accepted` cannot carry. Before
24548
+ * 2026-09-20 `applyServerUpdate` answered `accepted:false` both for a refusal
24549
+ * where nothing was wrong (already current, already staged) and for a staging
24550
+ * install that died on `npm error code ECONNRESET`; the only difference was
24551
+ * prose in `message`, and an operator restarted a container on the strength of
24552
+ * it, booting the previous version.
24553
+ *
24554
+ * - `staged` — the closure is installed and validated; the node
24555
+ * restarts to apply it (`restarting:true`). NOTHING
24556
+ * runs the new code until that restart completes.
24557
+ * - `restart-scheduled` — a plain, version-preserving restart was accepted.
24558
+ * - `already-current` — the target IS the running version; nothing done.
24559
+ * - `refused` — dropped by a gate; `reason` names which.
24560
+ * - `failed` — attempted and did not land; `error` says why.
24561
+ */
24562
+ var ServerUpdateOutcomeSchema = _enum([
24563
+ "staged",
24564
+ "restart-scheduled",
24565
+ "already-current",
24566
+ "refused",
24567
+ "failed"
24568
+ ]);
24501
24569
  var ServerUpdateActionResultSchema = object({
24502
24570
  accepted: boolean(),
24503
24571
  targetVersion: string().nullable(),
24504
24572
  /** True when a graceful restart was scheduled to apply the change. */
24505
24573
  restarting: boolean(),
24506
- message: string()
24574
+ message: string(),
24575
+ /** See {@link ServerUpdateOutcomeSchema}. Optional for version skew. */
24576
+ outcome: ServerUpdateOutcomeSchema.optional(),
24577
+ /**
24578
+ * Stable, greppable cause for `refused` / `failed` (the engine's
24579
+ * `UpdateRefusalReason`, `staging-failed`, …). `null` on success.
24580
+ */
24581
+ reason: string().nullable().optional(),
24582
+ /**
24583
+ * The failure itself, verbatim enough to act on (the npm output for a
24584
+ * staging install). Non-null only for `failed`.
24585
+ */
24586
+ error: string().nullable().optional()
24507
24587
  });
24508
24588
  method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
24509
24589
  kind: "mutation",
@@ -25612,6 +25692,18 @@ method(VectorDeclareIndexInputSchema, _void(), {
25612
25692
  kind: "mutation",
25613
25693
  auth: "admin"
25614
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"]);
25615
25707
  var ClipSchema = object({
25616
25708
  /** Opaque, provider-namespaced id. The default provider encodes the time
25617
25709
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -25686,29 +25778,243 @@ var ClipSchema = object({
25686
25778
  holes: array(object({
25687
25779
  startMs: number(),
25688
25780
  endMs: number()
25689
- })).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()
25690
25876
  });
25691
25877
  var ClipPlaybackSchema = object({
25692
- /** 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
+ */
25693
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(),
25694
25900
  /** Optional LAN/remote alternates for the same clip. */
25695
25901
  playbackEndpoints: array(string()).optional(),
25696
25902
  token: string().optional()
25697
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
+ });
25698
25970
  DeviceType.Camera, method(object({
25699
25971
  deviceId: number(),
25700
25972
  since: number(),
25701
25973
  until: number(),
25702
- 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()
25703
25993
  }), array(ClipSchema).readonly(), {
25704
25994
  kind: "query",
25705
- auth: "admin"
25995
+ auth: "protected"
25996
+ }), method(object({ deviceId: number() }), array(ClipSourceSchema).readonly(), {
25997
+ kind: "query",
25998
+ auth: "protected"
25706
25999
  }), method(object({
25707
26000
  deviceId: number(),
25708
- 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()
25709
26015
  }), ClipPlaybackSchema, {
25710
26016
  kind: "query",
25711
- auth: "admin"
26017
+ auth: "protected"
25712
26018
  });
25713
26019
  /**
25714
26020
  * Optional client-side hints sent at session creation to help the provider
@@ -37201,6 +37507,12 @@ Object.freeze({
37201
37507
  addonId: null,
37202
37508
  access: "view"
37203
37509
  },
37510
+ "pipelineAnalytics.ownersWithMedia": {
37511
+ capName: "pipeline-analytics",
37512
+ capScope: "device",
37513
+ addonId: null,
37514
+ access: "view"
37515
+ },
37204
37516
  "pipelineAnalytics.pauseForStorageMigration": {
37205
37517
  capName: "pipeline-analytics",
37206
37518
  capScope: "device",
@@ -39793,6 +40105,12 @@ Object.freeze({
39793
40105
  addonId: null,
39794
40106
  access: "view"
39795
40107
  },
40108
+ "videoclips.listSources": {
40109
+ capName: "videoclips",
40110
+ capScope: "device",
40111
+ addonId: null,
40112
+ access: "view"
40113
+ },
39796
40114
  "viewerUi.getStaticDir": {
39797
40115
  capName: "viewer-ui",
39798
40116
  capScope: "system",
@@ -41095,6 +41413,11 @@ Object.freeze({
41095
41413
  form: "single",
41096
41414
  optional: false
41097
41415
  }],
41416
+ "pipelineAnalytics.ownersWithMedia": [{
41417
+ name: "deviceId",
41418
+ form: "single",
41419
+ optional: false
41420
+ }],
41098
41421
  "pipelineAnalytics.proposeRetrainAnnotations": [{
41099
41422
  name: "deviceId",
41100
41423
  form: "single",
@@ -41801,6 +42124,11 @@ Object.freeze({
41801
42124
  form: "single",
41802
42125
  optional: false
41803
42126
  }],
42127
+ "videoclips.listSources": [{
42128
+ name: "deviceId",
42129
+ form: "single",
42130
+ optional: false
42131
+ }],
41804
42132
  "waterHeater.setAway": [{
41805
42133
  name: "deviceId",
41806
42134
  form: "single",
@@ -53035,6 +53363,289 @@ var RepeatWarnSampler = class {
53035
53363
  }
53036
53364
  };
53037
53365
  //#endregion
53366
+ //#region src/occupancy-recheck-controller.ts
53367
+ /**
53368
+ * How long a recheck registration lives once the burst was accepted. The
53369
+ * runner settles a burst at `OCCUPANCY_BURST_SAFETY_TIMEOUT_MS` (8 s) after
53370
+ * the dial; the dial itself is bounded by the decode-spawn gate. 15 s covers
53371
+ * both on this fleet and is short enough that a camera is never held for a
53372
+ * meaningful share of its 300 s cadence.
53373
+ */
53374
+ var RECHECK_BURST_WINDOW_MS = 15e3;
53375
+ /** The `reason` every orchestrator recheck carries into the runner's log. */
53376
+ var RECHECK_REASON = "orchestrator-recheck";
53377
+ /** A found burst resets the streak; two consecutive empty ones disarm. */
53378
+ function foldBurstOutcome(streak, found) {
53379
+ if (found) return {
53380
+ emptyStreak: 0,
53381
+ armed: true
53382
+ };
53383
+ const emptyStreak = streak.emptyStreak + 1;
53384
+ return {
53385
+ emptyStreak,
53386
+ armed: emptyStreak < 2
53387
+ };
53388
+ }
53389
+ /**
53390
+ * The registration a recheck rides on, derived from the SESSION config so the
53391
+ * stream, steps, zones and root detector are the camera's own.
53392
+ */
53393
+ function buildRecheckAttachConfig(session) {
53394
+ return {
53395
+ ...session,
53396
+ detectionMode: "on-motion",
53397
+ motionSources: [],
53398
+ audioMode: "disabled",
53399
+ occupancyRecheckEnabled: false,
53400
+ preRollInferenceEnabled: false
53401
+ };
53402
+ }
53403
+ var OccupancyRecheckController = class {
53404
+ deps;
53405
+ devices = /* @__PURE__ */ new Map();
53406
+ constructor(deps) {
53407
+ this.deps = deps;
53408
+ }
53409
+ /**
53410
+ * A session was dispatched: remember where and with what, cancel any pending
53411
+ * tick (the session sees the scene itself), and supersede a burst in flight
53412
+ * on the same node — the session attach replaced its registration.
53413
+ */
53414
+ noteSessionDispatched(deviceId, nodeId, config) {
53415
+ const state = this.stateFor(deviceId, nodeId, config);
53416
+ state.lastNodeId = nodeId;
53417
+ state.lastConfig = config;
53418
+ if (state.burst !== null && state.burst.nodeId === nodeId) {
53419
+ state.burst.superseded = true;
53420
+ this.deps.logger.debug("occupancy recheck: burst superseded by a session on the same node", {
53421
+ tags: {
53422
+ deviceId,
53423
+ nodeId
53424
+ },
53425
+ meta: {
53426
+ deviceId,
53427
+ nodeId
53428
+ }
53429
+ });
53430
+ }
53431
+ }
53432
+ /** A session closed: arm the cadence iff it saw a subject and the camera
53433
+ * has the recheck enabled. Re-evaluated from scratch on every close. */
53434
+ noteSessionClosed(deviceId, close) {
53435
+ const state = this.devices.get(deviceId);
53436
+ if (state === void 0) return;
53437
+ const cfg = state.lastConfig;
53438
+ const enabled = cfg.occupancyRecheckEnabled === true && (cfg.occupancyRecheckSec ?? 0) > 0;
53439
+ if (close.sawSubject) state.emptyStreak = 0;
53440
+ state.armed = state.armed || enabled && close.sawSubject;
53441
+ if (!state.armed) {
53442
+ this.deps.logger.debug("occupancy recheck: not armed", {
53443
+ tags: { deviceId },
53444
+ meta: {
53445
+ deviceId,
53446
+ enabled,
53447
+ sawSubject: close.sawSubject
53448
+ }
53449
+ });
53450
+ return;
53451
+ }
53452
+ if (state.timer === null) this.schedule(deviceId, state);
53453
+ }
53454
+ /** Every inference result the orchestrator sees; a burst result with
53455
+ * detections is what makes a burst "found". */
53456
+ noteInferenceResult(deviceId, occasion, detectionCount) {
53457
+ const state = this.devices.get(deviceId);
53458
+ if (state === void 0 || state.burst === null) return;
53459
+ if (occasion === "burst" && detectionCount > 0) state.burst.found = true;
53460
+ }
53461
+ /** The device left the orchestrator's knowledge. */
53462
+ forget(deviceId) {
53463
+ const state = this.devices.get(deviceId);
53464
+ if (state === void 0) return;
53465
+ this.cancelTimer(state);
53466
+ this.devices.delete(deviceId);
53467
+ }
53468
+ shutdown() {
53469
+ for (const state of this.devices.values()) this.cancelTimer(state);
53470
+ this.devices.clear();
53471
+ }
53472
+ stateFor(deviceId, nodeId, config) {
53473
+ let state = this.devices.get(deviceId);
53474
+ if (state === void 0) {
53475
+ state = {
53476
+ lastNodeId: nodeId,
53477
+ lastConfig: config,
53478
+ armed: false,
53479
+ emptyStreak: 0,
53480
+ timer: null,
53481
+ burst: null
53482
+ };
53483
+ this.devices.set(deviceId, state);
53484
+ }
53485
+ return state;
53486
+ }
53487
+ cancelTimer(state) {
53488
+ if (state.timer !== null) {
53489
+ clearTimeout(state.timer);
53490
+ state.timer = null;
53491
+ }
53492
+ }
53493
+ schedule(deviceId, state) {
53494
+ this.cancelTimer(state);
53495
+ const delayMs = (state.lastConfig.occupancyRecheckSec ?? 0) * 1e3;
53496
+ const timer = setTimeout(() => {
53497
+ state.timer = null;
53498
+ this.tick(deviceId).catch((err) => {
53499
+ this.deps.logger.warn("occupancy recheck: tick failed", {
53500
+ tags: { deviceId },
53501
+ meta: {
53502
+ deviceId,
53503
+ error: errMsg$1(err)
53504
+ }
53505
+ });
53506
+ });
53507
+ }, delayMs);
53508
+ timer.unref?.();
53509
+ state.timer = timer;
53510
+ }
53511
+ async tick(deviceId) {
53512
+ const state = this.devices.get(deviceId);
53513
+ if (state === void 0 || !state.armed) return;
53514
+ if (this.deps.isSessionActive(deviceId)) {
53515
+ this.deps.logger.debug("occupancy recheck: skipped — a session is active", {
53516
+ tags: { deviceId },
53517
+ meta: { deviceId }
53518
+ });
53519
+ this.schedule(deviceId, state);
53520
+ return;
53521
+ }
53522
+ if (state.burst !== null) {
53523
+ this.schedule(deviceId, state);
53524
+ return;
53525
+ }
53526
+ const nodeId = state.lastNodeId;
53527
+ const config = buildRecheckAttachConfig(state.lastConfig);
53528
+ const frames = Math.max(1, state.lastConfig.occupancyRecheckFrames ?? 4);
53529
+ const burst = {
53530
+ nodeId,
53531
+ superseded: false,
53532
+ found: false
53533
+ };
53534
+ state.burst = burst;
53535
+ try {
53536
+ await this.deps.attachOn(nodeId, config);
53537
+ } catch (err) {
53538
+ state.burst = null;
53539
+ this.deps.logger.warn("occupancy recheck: attach failed — retried next tick", {
53540
+ tags: {
53541
+ deviceId,
53542
+ nodeId
53543
+ },
53544
+ meta: {
53545
+ deviceId,
53546
+ nodeId,
53547
+ error: errMsg$1(err)
53548
+ }
53549
+ });
53550
+ this.schedule(deviceId, state);
53551
+ return;
53552
+ }
53553
+ const answer = await this.deps.requestBurstOn(nodeId, deviceId, frames, RECHECK_REASON).catch((err) => ({
53554
+ started: false,
53555
+ error: errMsg$1(err)
53556
+ }));
53557
+ if (!answer.started) {
53558
+ const refusedBecause = "refusedBecause" in answer ? answer.refusedBecause : void 0;
53559
+ const error = "error" in answer ? answer.error : void 0;
53560
+ await this.release(deviceId, state, burst);
53561
+ const asleep = refusedBecause === "battery-asleep";
53562
+ if (asleep) state.armed = false;
53563
+ this.deps.logger.info("occupancy recheck: burst refused", {
53564
+ tags: {
53565
+ deviceId,
53566
+ nodeId
53567
+ },
53568
+ meta: {
53569
+ deviceId,
53570
+ nodeId,
53571
+ refusedBecause: refusedBecause ?? null,
53572
+ error: error ?? null,
53573
+ rearmed: !asleep
53574
+ }
53575
+ });
53576
+ if (!asleep) this.schedule(deviceId, state);
53577
+ return;
53578
+ }
53579
+ this.deps.logger.info("occupancy recheck: burst dispatched", {
53580
+ tags: {
53581
+ deviceId,
53582
+ nodeId
53583
+ },
53584
+ meta: {
53585
+ deviceId,
53586
+ nodeId,
53587
+ frames,
53588
+ recheckSec: state.lastConfig.occupancyRecheckSec ?? null
53589
+ }
53590
+ });
53591
+ setTimeout(() => {
53592
+ this.settle(deviceId, state, burst).catch((err) => {
53593
+ this.deps.logger.warn("occupancy recheck: settle failed", {
53594
+ tags: {
53595
+ deviceId,
53596
+ nodeId
53597
+ },
53598
+ meta: {
53599
+ deviceId,
53600
+ nodeId,
53601
+ error: errMsg$1(err)
53602
+ }
53603
+ });
53604
+ });
53605
+ }, RECHECK_BURST_WINDOW_MS).unref?.();
53606
+ }
53607
+ async settle(deviceId, state, burst) {
53608
+ await this.release(deviceId, state, burst);
53609
+ const fold = foldBurstOutcome({ emptyStreak: state.emptyStreak }, burst.found);
53610
+ state.emptyStreak = fold.emptyStreak;
53611
+ state.armed = fold.armed;
53612
+ this.deps.logger.info("occupancy recheck: burst complete", {
53613
+ tags: {
53614
+ deviceId,
53615
+ nodeId: burst.nodeId
53616
+ },
53617
+ meta: {
53618
+ deviceId,
53619
+ nodeId: burst.nodeId,
53620
+ found: burst.found,
53621
+ emptyStreak: fold.emptyStreak,
53622
+ armed: fold.armed,
53623
+ superseded: burst.superseded
53624
+ }
53625
+ });
53626
+ if (state.armed && !this.deps.isSessionActive(deviceId)) this.schedule(deviceId, state);
53627
+ else if (state.armed) state.armed = false;
53628
+ }
53629
+ /** Detach the recheck registration unless a session took it over. */
53630
+ async release(deviceId, state, burst) {
53631
+ if (state.burst === burst) state.burst = null;
53632
+ if (burst.superseded) return;
53633
+ await this.deps.detachOn(burst.nodeId, deviceId).catch((err) => {
53634
+ this.deps.logger.warn("occupancy recheck: detach failed", {
53635
+ tags: {
53636
+ deviceId,
53637
+ nodeId: burst.nodeId
53638
+ },
53639
+ meta: {
53640
+ deviceId,
53641
+ nodeId: burst.nodeId,
53642
+ error: errMsg$1(err)
53643
+ }
53644
+ });
53645
+ });
53646
+ }
53647
+ };
53648
+ //#endregion
53038
53649
  //#region src/balancer-input-cache.ts
53039
53650
  /**
53040
53651
  * Short-TTL memo for the reads a session dispatch needs BEFORE it can choose a
@@ -53461,8 +54072,32 @@ var SessionDispatchController = class {
53461
54072
  inferenceDeviceCaps = new TtlMemo({ load: (nodeId) => this.deps.getInferenceDeviceCaps(nodeId) });
53462
54073
  /** Background refresher for {@link agentLoads} et al. Null until first use. */
53463
54074
  balancerRefreshTimer = null;
54075
+ /**
54076
+ * The occupancy recheck for session-dispatched cameras (D547). The runner's
54077
+ * own recheck lives on an `on-motion` registration no camera receives; this
54078
+ * one is armed by the session close, which only this controller sees.
54079
+ */
54080
+ recheck;
53464
54081
  constructor(deps) {
53465
54082
  this.deps = deps;
54083
+ const requestBurstOn = deps.requestBurstOn;
54084
+ if (requestBurstOn === void 0) {
54085
+ this.recheck = null;
54086
+ deps.logger.info("occupancy recheck has no burst producer on this build — no session-dispatched camera will be re-sampled", { meta: { why: "SessionDispatchControllerDeps.requestBurstOn absent" } });
54087
+ } else this.recheck = new OccupancyRecheckController({
54088
+ attachOn: (nodeId, config) => deps.attachOn(nodeId, config),
54089
+ detachOn: (nodeId, deviceId) => deps.detachOn(nodeId, deviceId),
54090
+ requestBurstOn,
54091
+ isSessionActive: (deviceId) => this.sessionRegistry.has(deviceId),
54092
+ logger: deps.logger
54093
+ });
54094
+ }
54095
+ /**
54096
+ * Every inference result the orchestrator sees, for the recheck's
54097
+ * found/empty verdict: a `'burst'` result with detections is a found burst.
54098
+ */
54099
+ noteInferenceResult(deviceId, occasion, detectionCount) {
54100
+ this.recheck?.noteInferenceResult(deviceId, occasion, detectionCount);
53466
54101
  }
53467
54102
  /**
53468
54103
  * Placement changed — this controller just attached or detached something, so
@@ -53855,6 +54490,7 @@ var SessionDispatchController = class {
53855
54490
  attachedAt = Date.now();
53856
54491
  this.deps.recordAssignment(deviceId, targetNodeId, decision.reason, decision.reason === "manual");
53857
54492
  this.sessionRegistry.register(deviceId, targetNodeId, Date.now(), deviceKey);
54493
+ this.recheck?.noteSessionDispatched(deviceId, targetNodeId, sessionConfig);
53858
54494
  log.info("detection session: dispatched on motion", {
53859
54495
  tags: {
53860
54496
  deviceId,
@@ -54031,6 +54667,7 @@ var SessionDispatchController = class {
54031
54667
  this.activeRefireCountByDevice.delete(deviceId);
54032
54668
  this.notePlacementChanged();
54033
54669
  const { openForMs, sinceSubjectSeenMs, maxHoldMs } = observed;
54670
+ this.recheck?.noteSessionClosed(deviceId, { sawSubject: sinceSubjectSeenMs !== null });
54034
54671
  const subjectPresent = sinceSubjectSeenMs !== null && sinceSubjectSeenMs < 4e3;
54035
54672
  const meta = {
54036
54673
  reason,
@@ -54112,6 +54749,7 @@ var SessionDispatchController = class {
54112
54749
  }
54113
54750
  this.deps.dropActiveDetection(deviceId);
54114
54751
  this.lastSubjectSeenAt.delete(deviceId);
54752
+ this.recheck?.forget(deviceId);
54115
54753
  });
54116
54754
  const watchNodeId = this.motionWatchByDevice.get(deviceId);
54117
54755
  if (watchNodeId) {
@@ -54147,6 +54785,7 @@ var SessionDispatchController = class {
54147
54785
  */
54148
54786
  shutdown() {
54149
54787
  this.shuttingDown = true;
54788
+ this.recheck?.shutdown();
54150
54789
  this.sessionRegistry.clear();
54151
54790
  this.motionWatchByDevice.clear();
54152
54791
  this.lastSubjectSeenAt.clear();
@@ -55141,6 +55780,11 @@ async function buildOrchestratorControllers(deps) {
55141
55780
  settingsStore,
55142
55781
  attachOn: (nodeId, config) => deps.attachOn(nodeId, config),
55143
55782
  detachOn: (nodeId, deviceId) => deps.detachOn(nodeId, deviceId),
55783
+ requestBurstOn: async (nodeId, deviceId, frames, reason) => deps.ctx().api.pipelineRunner.requestOccupancyBurst.mutate({
55784
+ deviceId,
55785
+ frames,
55786
+ reason
55787
+ }, nodePin(nodeId)),
55144
55788
  recordAssignment: (deviceId, agentNodeId, reason, pinned) => ledger.recordAssignment(deviceId, agentNodeId, reason, pinned),
55145
55789
  dropAssignment: (deviceId, emit) => ledger.dropAssignment(deviceId, emit),
55146
55790
  reconcilePlacementFromRunners: () => reconcile.reconcilePlacementFromRunners(),
@@ -55445,7 +56089,10 @@ async function buildOrchestratorControllers(deps) {
55445
56089
  handleDeviceUnregistered: (deviceId) => deps.handleDeviceUnregistered(deviceId),
55446
56090
  handleSettingsChanged: (deviceId) => deps.handleSettingsChanged(deviceId),
55447
56091
  handlePipelineCameraUpdated: (deviceId, config) => deps.handlePipelineCameraUpdated(deviceId, config),
55448
- handleInferenceResult: (payload) => deps.handleInferenceResult(payload),
56092
+ handleInferenceResult: (payload) => {
56093
+ session.noteInferenceResult(payload.deviceId, payload.occasion, payload.frame.detections?.length ?? 0);
56094
+ return deps.handleInferenceResult(payload);
56095
+ },
55449
56096
  noteMetrics: (deviceId, nodeId, metrics) => {
55450
56097
  loadShed.noteMetrics(deviceId, nodeId, metrics);
55451
56098
  nodeStress.noteMetrics(deviceId, nodeId, metrics);