@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.
- package/assets/viewer/.viewer-current-files.json +10 -10
- package/assets/viewer/.viewer-version +1 -1
- package/assets/viewer/_expo/static/js/web/Clipboard-6206d4c0d423d26e04715ebf58c315df.js +8 -0
- package/assets/viewer/_expo/static/js/web/Haptics-49c17326a5ec4b2b140b1449cdbb7f34.js +3 -0
- package/assets/viewer/_expo/static/js/web/{index-f5bda3803ae44f8b74784fe8b002bf5e.js → index-ccd23c8462e4121de32d780726261a91.js} +741 -740
- package/assets/viewer/_expo/static/js/web/node-fs-5b88ad96e86d7063ccdbe32db825729f.js +1 -0
- package/assets/viewer/_expo/static/js/web/node-path-716b5d0b761c62e0b4296f79e29f89d2.js +1 -0
- package/assets/viewer/embed/assets/{MaskShapeCanvas-DI4BY7W2-C0fGaeA-.js → MaskShapeCanvas-DI4BY7W2-2CDlk-HT.js} +1 -1
- package/assets/viewer/embed/assets/{MotionZonesSettings-7c3yiT5E-9PMziv1F.js → MotionZonesSettings-7c3yiT5E-B6oGxKku.js} +1 -1
- package/assets/viewer/embed/assets/{PrivacyMaskSettings-PkYs077O-DXkWrrAk.js → PrivacyMaskSettings-PkYs077O-B-RISboY.js} +1 -1
- package/assets/viewer/embed/assets/{SceneMonitorEditor-BvDy3x3k-B9lXS5ns.js → SceneMonitorEditor-BvDy3x3k-DBWukNQx.js} +1 -1
- package/assets/viewer/embed/assets/index-H9aL2Im_.js +115 -0
- package/assets/viewer/embed/index.html +1 -1
- package/assets/viewer/index.html +1 -1
- package/dist/_stub.js +2 -2
- 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
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D7D-UPVq.mjs +26 -0
- 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
- package/dist/{hostInit-BiEedqeH.mjs → hostInit-Cm5QAJnG.mjs} +2 -2
- package/dist/index.js +663 -16
- package/dist/index.mjs +663 -16
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/assets/viewer/embed/assets/index-Btt6Cej-.js +0 -115
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Cm0B7Kaq.mjs +0 -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.mjs
CHANGED
|
@@ -5363,7 +5363,7 @@ var ZodIssueCode = {
|
|
|
5363
5363
|
var ZodFirstPartyTypeKind;
|
|
5364
5364
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5365
5365
|
//#endregion
|
|
5366
|
-
//#region ../types/dist/sleep-
|
|
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(
|
|
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, {
|
|
@@ -24331,10 +24337,9 @@ var ServerBootModeSchema = _enum([
|
|
|
24331
24337
|
* - `idle` / `checking` / `staging` — steady / in-flight registry work.
|
|
24332
24338
|
* - `pending-restart` — a version is staged and the node has NOT yet
|
|
24333
24339
|
* restarted onto it (still running the OLD version).
|
|
24334
|
-
* - `awaiting-confirmation` —
|
|
24335
|
-
*
|
|
24336
|
-
*
|
|
24337
|
-
* manually restarted, or the probation boot auto-rolls-back.
|
|
24340
|
+
* - `awaiting-confirmation` — RETIRED with the probation boot (single-copy
|
|
24341
|
+
* collapse, 2026-07-18). Kept in the enum so an older client still parses;
|
|
24342
|
+
* no provider emits it.
|
|
24338
24343
|
*/
|
|
24339
24344
|
var ServerUpdateStateSchema = _enum([
|
|
24340
24345
|
"idle",
|
|
@@ -24423,12 +24428,35 @@ var ServerRollbackInfoSchema = object({
|
|
|
24423
24428
|
var ServerPackageStatusSchema = object({
|
|
24424
24429
|
/** Root package name (`@camstack/server` on the hub). */
|
|
24425
24430
|
packageName: string(),
|
|
24426
|
-
/**
|
|
24431
|
+
/**
|
|
24432
|
+
* Version of the code THIS PROCESS loaded — captured once, at boot, from the
|
|
24433
|
+
* package.json of the copy the code was loaded from, and never re-read. A
|
|
24434
|
+
* copy replaced on disk under a live process must not move this number;
|
|
24435
|
+
* that divergence is what `installedVersion` is for. Before 2026-09-20 this
|
|
24436
|
+
* was a lazy disk read that would have followed such a swap silently.
|
|
24437
|
+
*/
|
|
24427
24438
|
runningVersion: string().nullable(),
|
|
24428
24439
|
/** Node.js runtime version the node's process runs on (`process.versions.node`). */
|
|
24429
24440
|
nodeRuntimeVersion: string().nullable(),
|
|
24430
24441
|
/** Active data-dir root version; null when booted from seed/workspace. */
|
|
24431
24442
|
activeVersion: string().nullable(),
|
|
24443
|
+
/**
|
|
24444
|
+
* Version of the root package in the data-root `current/` copy RIGHT NOW —
|
|
24445
|
+
* what the next boot would load if nothing is staged. Normally equal to
|
|
24446
|
+
* `runningVersion`; differs only when the copy on disk changed under the
|
|
24447
|
+
* live process. `null` when there is no data-root copy (baked seed or
|
|
24448
|
+
* workspace boot). Optional for version skew: an older provider omits it.
|
|
24449
|
+
*/
|
|
24450
|
+
installedVersion: string().nullable().optional(),
|
|
24451
|
+
/**
|
|
24452
|
+
* True when the code that would run after a restart is NOT the code running
|
|
24453
|
+
* now: a version is staged (`pendingVersion`), or `installedVersion` differs
|
|
24454
|
+
* from `runningVersion`. Derived on every read, never stored. This is the
|
|
24455
|
+
* one field that lets an operator or a script say "1.2.353 is installed and
|
|
24456
|
+
* will run after a restart" instead of reading it off a number that means
|
|
24457
|
+
* "installed". Optional for version skew.
|
|
24458
|
+
*/
|
|
24459
|
+
restartRequired: boolean().optional(),
|
|
24432
24460
|
/** N-1 version kept for rollback; null when no previous version exists. */
|
|
24433
24461
|
previousVersion: string().nullable(),
|
|
24434
24462
|
/** Version of the immutable baked seed closure (image fallback). */
|
|
@@ -24463,19 +24491,71 @@ var ServerPackageStatusSchema = object({
|
|
|
24463
24491
|
});
|
|
24464
24492
|
var ServerUpdateCheckResultSchema = object({
|
|
24465
24493
|
packageName: string(),
|
|
24494
|
+
/** Same meaning as on the status payload: what THIS process loaded. */
|
|
24466
24495
|
runningVersion: string().nullable(),
|
|
24467
24496
|
latestVersion: string().nullable(),
|
|
24497
|
+
/**
|
|
24498
|
+
* `latestVersion` is newer than `runningVersion` AND is not the version
|
|
24499
|
+
* already staged on this node. A staged-and-waiting version is not "an
|
|
24500
|
+
* update available" — applying it again is refused (`already-staged`), and
|
|
24501
|
+
* a button that announced "updating…" on this boolean then got refused
|
|
24502
|
+
* would be lying. It is reported through `pendingVersion` instead.
|
|
24503
|
+
*/
|
|
24468
24504
|
updateAvailable: boolean(),
|
|
24469
24505
|
checkedAtMs: number(),
|
|
24470
24506
|
/** Non-null when the registry lookup failed (offline, bad registry, …). */
|
|
24471
|
-
error: string().nullable()
|
|
24507
|
+
error: string().nullable(),
|
|
24508
|
+
/**
|
|
24509
|
+
* Version staged on this node and awaiting its restart, when one is. Before
|
|
24510
|
+
* 2026-09-20 a check answered `{runningVersion, updateAvailable:false}` for a
|
|
24511
|
+
* node whose staged closure had never booted, and the caller had no way to
|
|
24512
|
+
* see the difference. Optional for version skew.
|
|
24513
|
+
*/
|
|
24514
|
+
pendingVersion: string().nullable().optional(),
|
|
24515
|
+
/** See the status payload: staged, or installed-on-disk ≠ running. Optional for skew. */
|
|
24516
|
+
restartRequired: boolean().optional()
|
|
24472
24517
|
});
|
|
24518
|
+
/**
|
|
24519
|
+
* What an action DID — the discriminant `accepted` cannot carry. Before
|
|
24520
|
+
* 2026-09-20 `applyServerUpdate` answered `accepted:false` both for a refusal
|
|
24521
|
+
* where nothing was wrong (already current, already staged) and for a staging
|
|
24522
|
+
* install that died on `npm error code ECONNRESET`; the only difference was
|
|
24523
|
+
* prose in `message`, and an operator restarted a container on the strength of
|
|
24524
|
+
* it, booting the previous version.
|
|
24525
|
+
*
|
|
24526
|
+
* - `staged` — the closure is installed and validated; the node
|
|
24527
|
+
* restarts to apply it (`restarting:true`). NOTHING
|
|
24528
|
+
* runs the new code until that restart completes.
|
|
24529
|
+
* - `restart-scheduled` — a plain, version-preserving restart was accepted.
|
|
24530
|
+
* - `already-current` — the target IS the running version; nothing done.
|
|
24531
|
+
* - `refused` — dropped by a gate; `reason` names which.
|
|
24532
|
+
* - `failed` — attempted and did not land; `error` says why.
|
|
24533
|
+
*/
|
|
24534
|
+
var ServerUpdateOutcomeSchema = _enum([
|
|
24535
|
+
"staged",
|
|
24536
|
+
"restart-scheduled",
|
|
24537
|
+
"already-current",
|
|
24538
|
+
"refused",
|
|
24539
|
+
"failed"
|
|
24540
|
+
]);
|
|
24473
24541
|
var ServerUpdateActionResultSchema = object({
|
|
24474
24542
|
accepted: boolean(),
|
|
24475
24543
|
targetVersion: string().nullable(),
|
|
24476
24544
|
/** True when a graceful restart was scheduled to apply the change. */
|
|
24477
24545
|
restarting: boolean(),
|
|
24478
|
-
message: string()
|
|
24546
|
+
message: string(),
|
|
24547
|
+
/** See {@link ServerUpdateOutcomeSchema}. Optional for version skew. */
|
|
24548
|
+
outcome: ServerUpdateOutcomeSchema.optional(),
|
|
24549
|
+
/**
|
|
24550
|
+
* Stable, greppable cause for `refused` / `failed` (the engine's
|
|
24551
|
+
* `UpdateRefusalReason`, `staging-failed`, …). `null` on success.
|
|
24552
|
+
*/
|
|
24553
|
+
reason: string().nullable().optional(),
|
|
24554
|
+
/**
|
|
24555
|
+
* The failure itself, verbatim enough to act on (the npm output for a
|
|
24556
|
+
* staging install). Non-null only for `failed`.
|
|
24557
|
+
*/
|
|
24558
|
+
error: string().nullable().optional()
|
|
24479
24559
|
});
|
|
24480
24560
|
method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
|
|
24481
24561
|
kind: "mutation",
|
|
@@ -25584,6 +25664,18 @@ method(VectorDeclareIndexInputSchema, _void(), {
|
|
|
25584
25664
|
kind: "mutation",
|
|
25585
25665
|
auth: "admin"
|
|
25586
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"]);
|
|
25587
25679
|
var ClipSchema = object({
|
|
25588
25680
|
/** Opaque, provider-namespaced id. The default provider encodes the time
|
|
25589
25681
|
* window so `getClipPlayback` is self-contained (no event re-query). */
|
|
@@ -25658,29 +25750,243 @@ var ClipSchema = object({
|
|
|
25658
25750
|
holes: array(object({
|
|
25659
25751
|
startMs: number(),
|
|
25660
25752
|
endMs: number()
|
|
25661
|
-
})).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()
|
|
25662
25848
|
});
|
|
25663
25849
|
var ClipPlaybackSchema = object({
|
|
25664
|
-
/**
|
|
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
|
+
*/
|
|
25665
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(),
|
|
25666
25872
|
/** Optional LAN/remote alternates for the same clip. */
|
|
25667
25873
|
playbackEndpoints: array(string()).optional(),
|
|
25668
25874
|
token: string().optional()
|
|
25669
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
|
+
});
|
|
25670
25942
|
DeviceType.Camera, method(object({
|
|
25671
25943
|
deviceId: number(),
|
|
25672
25944
|
since: number(),
|
|
25673
25945
|
until: number(),
|
|
25674
|
-
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()
|
|
25675
25965
|
}), array(ClipSchema).readonly(), {
|
|
25676
25966
|
kind: "query",
|
|
25677
|
-
auth: "
|
|
25967
|
+
auth: "protected"
|
|
25968
|
+
}), method(object({ deviceId: number() }), array(ClipSourceSchema).readonly(), {
|
|
25969
|
+
kind: "query",
|
|
25970
|
+
auth: "protected"
|
|
25678
25971
|
}), method(object({
|
|
25679
25972
|
deviceId: number(),
|
|
25680
|
-
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()
|
|
25681
25987
|
}), ClipPlaybackSchema, {
|
|
25682
25988
|
kind: "query",
|
|
25683
|
-
auth: "
|
|
25989
|
+
auth: "protected"
|
|
25684
25990
|
});
|
|
25685
25991
|
/**
|
|
25686
25992
|
* Optional client-side hints sent at session creation to help the provider
|
|
@@ -37173,6 +37479,12 @@ Object.freeze({
|
|
|
37173
37479
|
addonId: null,
|
|
37174
37480
|
access: "view"
|
|
37175
37481
|
},
|
|
37482
|
+
"pipelineAnalytics.ownersWithMedia": {
|
|
37483
|
+
capName: "pipeline-analytics",
|
|
37484
|
+
capScope: "device",
|
|
37485
|
+
addonId: null,
|
|
37486
|
+
access: "view"
|
|
37487
|
+
},
|
|
37176
37488
|
"pipelineAnalytics.pauseForStorageMigration": {
|
|
37177
37489
|
capName: "pipeline-analytics",
|
|
37178
37490
|
capScope: "device",
|
|
@@ -39765,6 +40077,12 @@ Object.freeze({
|
|
|
39765
40077
|
addonId: null,
|
|
39766
40078
|
access: "view"
|
|
39767
40079
|
},
|
|
40080
|
+
"videoclips.listSources": {
|
|
40081
|
+
capName: "videoclips",
|
|
40082
|
+
capScope: "device",
|
|
40083
|
+
addonId: null,
|
|
40084
|
+
access: "view"
|
|
40085
|
+
},
|
|
39768
40086
|
"viewerUi.getStaticDir": {
|
|
39769
40087
|
capName: "viewer-ui",
|
|
39770
40088
|
capScope: "system",
|
|
@@ -41067,6 +41385,11 @@ Object.freeze({
|
|
|
41067
41385
|
form: "single",
|
|
41068
41386
|
optional: false
|
|
41069
41387
|
}],
|
|
41388
|
+
"pipelineAnalytics.ownersWithMedia": [{
|
|
41389
|
+
name: "deviceId",
|
|
41390
|
+
form: "single",
|
|
41391
|
+
optional: false
|
|
41392
|
+
}],
|
|
41070
41393
|
"pipelineAnalytics.proposeRetrainAnnotations": [{
|
|
41071
41394
|
name: "deviceId",
|
|
41072
41395
|
form: "single",
|
|
@@ -41773,6 +42096,11 @@ Object.freeze({
|
|
|
41773
42096
|
form: "single",
|
|
41774
42097
|
optional: false
|
|
41775
42098
|
}],
|
|
42099
|
+
"videoclips.listSources": [{
|
|
42100
|
+
name: "deviceId",
|
|
42101
|
+
form: "single",
|
|
42102
|
+
optional: false
|
|
42103
|
+
}],
|
|
41776
42104
|
"waterHeater.setAway": [{
|
|
41777
42105
|
name: "deviceId",
|
|
41778
42106
|
form: "single",
|
|
@@ -53007,6 +53335,289 @@ var RepeatWarnSampler = class {
|
|
|
53007
53335
|
}
|
|
53008
53336
|
};
|
|
53009
53337
|
//#endregion
|
|
53338
|
+
//#region src/occupancy-recheck-controller.ts
|
|
53339
|
+
/**
|
|
53340
|
+
* How long a recheck registration lives once the burst was accepted. The
|
|
53341
|
+
* runner settles a burst at `OCCUPANCY_BURST_SAFETY_TIMEOUT_MS` (8 s) after
|
|
53342
|
+
* the dial; the dial itself is bounded by the decode-spawn gate. 15 s covers
|
|
53343
|
+
* both on this fleet and is short enough that a camera is never held for a
|
|
53344
|
+
* meaningful share of its 300 s cadence.
|
|
53345
|
+
*/
|
|
53346
|
+
var RECHECK_BURST_WINDOW_MS = 15e3;
|
|
53347
|
+
/** The `reason` every orchestrator recheck carries into the runner's log. */
|
|
53348
|
+
var RECHECK_REASON = "orchestrator-recheck";
|
|
53349
|
+
/** A found burst resets the streak; two consecutive empty ones disarm. */
|
|
53350
|
+
function foldBurstOutcome(streak, found) {
|
|
53351
|
+
if (found) return {
|
|
53352
|
+
emptyStreak: 0,
|
|
53353
|
+
armed: true
|
|
53354
|
+
};
|
|
53355
|
+
const emptyStreak = streak.emptyStreak + 1;
|
|
53356
|
+
return {
|
|
53357
|
+
emptyStreak,
|
|
53358
|
+
armed: emptyStreak < 2
|
|
53359
|
+
};
|
|
53360
|
+
}
|
|
53361
|
+
/**
|
|
53362
|
+
* The registration a recheck rides on, derived from the SESSION config so the
|
|
53363
|
+
* stream, steps, zones and root detector are the camera's own.
|
|
53364
|
+
*/
|
|
53365
|
+
function buildRecheckAttachConfig(session) {
|
|
53366
|
+
return {
|
|
53367
|
+
...session,
|
|
53368
|
+
detectionMode: "on-motion",
|
|
53369
|
+
motionSources: [],
|
|
53370
|
+
audioMode: "disabled",
|
|
53371
|
+
occupancyRecheckEnabled: false,
|
|
53372
|
+
preRollInferenceEnabled: false
|
|
53373
|
+
};
|
|
53374
|
+
}
|
|
53375
|
+
var OccupancyRecheckController = class {
|
|
53376
|
+
deps;
|
|
53377
|
+
devices = /* @__PURE__ */ new Map();
|
|
53378
|
+
constructor(deps) {
|
|
53379
|
+
this.deps = deps;
|
|
53380
|
+
}
|
|
53381
|
+
/**
|
|
53382
|
+
* A session was dispatched: remember where and with what, cancel any pending
|
|
53383
|
+
* tick (the session sees the scene itself), and supersede a burst in flight
|
|
53384
|
+
* on the same node — the session attach replaced its registration.
|
|
53385
|
+
*/
|
|
53386
|
+
noteSessionDispatched(deviceId, nodeId, config) {
|
|
53387
|
+
const state = this.stateFor(deviceId, nodeId, config);
|
|
53388
|
+
state.lastNodeId = nodeId;
|
|
53389
|
+
state.lastConfig = config;
|
|
53390
|
+
if (state.burst !== null && state.burst.nodeId === nodeId) {
|
|
53391
|
+
state.burst.superseded = true;
|
|
53392
|
+
this.deps.logger.debug("occupancy recheck: burst superseded by a session on the same node", {
|
|
53393
|
+
tags: {
|
|
53394
|
+
deviceId,
|
|
53395
|
+
nodeId
|
|
53396
|
+
},
|
|
53397
|
+
meta: {
|
|
53398
|
+
deviceId,
|
|
53399
|
+
nodeId
|
|
53400
|
+
}
|
|
53401
|
+
});
|
|
53402
|
+
}
|
|
53403
|
+
}
|
|
53404
|
+
/** A session closed: arm the cadence iff it saw a subject and the camera
|
|
53405
|
+
* has the recheck enabled. Re-evaluated from scratch on every close. */
|
|
53406
|
+
noteSessionClosed(deviceId, close) {
|
|
53407
|
+
const state = this.devices.get(deviceId);
|
|
53408
|
+
if (state === void 0) return;
|
|
53409
|
+
const cfg = state.lastConfig;
|
|
53410
|
+
const enabled = cfg.occupancyRecheckEnabled === true && (cfg.occupancyRecheckSec ?? 0) > 0;
|
|
53411
|
+
if (close.sawSubject) state.emptyStreak = 0;
|
|
53412
|
+
state.armed = state.armed || enabled && close.sawSubject;
|
|
53413
|
+
if (!state.armed) {
|
|
53414
|
+
this.deps.logger.debug("occupancy recheck: not armed", {
|
|
53415
|
+
tags: { deviceId },
|
|
53416
|
+
meta: {
|
|
53417
|
+
deviceId,
|
|
53418
|
+
enabled,
|
|
53419
|
+
sawSubject: close.sawSubject
|
|
53420
|
+
}
|
|
53421
|
+
});
|
|
53422
|
+
return;
|
|
53423
|
+
}
|
|
53424
|
+
if (state.timer === null) this.schedule(deviceId, state);
|
|
53425
|
+
}
|
|
53426
|
+
/** Every inference result the orchestrator sees; a burst result with
|
|
53427
|
+
* detections is what makes a burst "found". */
|
|
53428
|
+
noteInferenceResult(deviceId, occasion, detectionCount) {
|
|
53429
|
+
const state = this.devices.get(deviceId);
|
|
53430
|
+
if (state === void 0 || state.burst === null) return;
|
|
53431
|
+
if (occasion === "burst" && detectionCount > 0) state.burst.found = true;
|
|
53432
|
+
}
|
|
53433
|
+
/** The device left the orchestrator's knowledge. */
|
|
53434
|
+
forget(deviceId) {
|
|
53435
|
+
const state = this.devices.get(deviceId);
|
|
53436
|
+
if (state === void 0) return;
|
|
53437
|
+
this.cancelTimer(state);
|
|
53438
|
+
this.devices.delete(deviceId);
|
|
53439
|
+
}
|
|
53440
|
+
shutdown() {
|
|
53441
|
+
for (const state of this.devices.values()) this.cancelTimer(state);
|
|
53442
|
+
this.devices.clear();
|
|
53443
|
+
}
|
|
53444
|
+
stateFor(deviceId, nodeId, config) {
|
|
53445
|
+
let state = this.devices.get(deviceId);
|
|
53446
|
+
if (state === void 0) {
|
|
53447
|
+
state = {
|
|
53448
|
+
lastNodeId: nodeId,
|
|
53449
|
+
lastConfig: config,
|
|
53450
|
+
armed: false,
|
|
53451
|
+
emptyStreak: 0,
|
|
53452
|
+
timer: null,
|
|
53453
|
+
burst: null
|
|
53454
|
+
};
|
|
53455
|
+
this.devices.set(deviceId, state);
|
|
53456
|
+
}
|
|
53457
|
+
return state;
|
|
53458
|
+
}
|
|
53459
|
+
cancelTimer(state) {
|
|
53460
|
+
if (state.timer !== null) {
|
|
53461
|
+
clearTimeout(state.timer);
|
|
53462
|
+
state.timer = null;
|
|
53463
|
+
}
|
|
53464
|
+
}
|
|
53465
|
+
schedule(deviceId, state) {
|
|
53466
|
+
this.cancelTimer(state);
|
|
53467
|
+
const delayMs = (state.lastConfig.occupancyRecheckSec ?? 0) * 1e3;
|
|
53468
|
+
const timer = setTimeout(() => {
|
|
53469
|
+
state.timer = null;
|
|
53470
|
+
this.tick(deviceId).catch((err) => {
|
|
53471
|
+
this.deps.logger.warn("occupancy recheck: tick failed", {
|
|
53472
|
+
tags: { deviceId },
|
|
53473
|
+
meta: {
|
|
53474
|
+
deviceId,
|
|
53475
|
+
error: errMsg$1(err)
|
|
53476
|
+
}
|
|
53477
|
+
});
|
|
53478
|
+
});
|
|
53479
|
+
}, delayMs);
|
|
53480
|
+
timer.unref?.();
|
|
53481
|
+
state.timer = timer;
|
|
53482
|
+
}
|
|
53483
|
+
async tick(deviceId) {
|
|
53484
|
+
const state = this.devices.get(deviceId);
|
|
53485
|
+
if (state === void 0 || !state.armed) return;
|
|
53486
|
+
if (this.deps.isSessionActive(deviceId)) {
|
|
53487
|
+
this.deps.logger.debug("occupancy recheck: skipped — a session is active", {
|
|
53488
|
+
tags: { deviceId },
|
|
53489
|
+
meta: { deviceId }
|
|
53490
|
+
});
|
|
53491
|
+
this.schedule(deviceId, state);
|
|
53492
|
+
return;
|
|
53493
|
+
}
|
|
53494
|
+
if (state.burst !== null) {
|
|
53495
|
+
this.schedule(deviceId, state);
|
|
53496
|
+
return;
|
|
53497
|
+
}
|
|
53498
|
+
const nodeId = state.lastNodeId;
|
|
53499
|
+
const config = buildRecheckAttachConfig(state.lastConfig);
|
|
53500
|
+
const frames = Math.max(1, state.lastConfig.occupancyRecheckFrames ?? 4);
|
|
53501
|
+
const burst = {
|
|
53502
|
+
nodeId,
|
|
53503
|
+
superseded: false,
|
|
53504
|
+
found: false
|
|
53505
|
+
};
|
|
53506
|
+
state.burst = burst;
|
|
53507
|
+
try {
|
|
53508
|
+
await this.deps.attachOn(nodeId, config);
|
|
53509
|
+
} catch (err) {
|
|
53510
|
+
state.burst = null;
|
|
53511
|
+
this.deps.logger.warn("occupancy recheck: attach failed — retried next tick", {
|
|
53512
|
+
tags: {
|
|
53513
|
+
deviceId,
|
|
53514
|
+
nodeId
|
|
53515
|
+
},
|
|
53516
|
+
meta: {
|
|
53517
|
+
deviceId,
|
|
53518
|
+
nodeId,
|
|
53519
|
+
error: errMsg$1(err)
|
|
53520
|
+
}
|
|
53521
|
+
});
|
|
53522
|
+
this.schedule(deviceId, state);
|
|
53523
|
+
return;
|
|
53524
|
+
}
|
|
53525
|
+
const answer = await this.deps.requestBurstOn(nodeId, deviceId, frames, RECHECK_REASON).catch((err) => ({
|
|
53526
|
+
started: false,
|
|
53527
|
+
error: errMsg$1(err)
|
|
53528
|
+
}));
|
|
53529
|
+
if (!answer.started) {
|
|
53530
|
+
const refusedBecause = "refusedBecause" in answer ? answer.refusedBecause : void 0;
|
|
53531
|
+
const error = "error" in answer ? answer.error : void 0;
|
|
53532
|
+
await this.release(deviceId, state, burst);
|
|
53533
|
+
const asleep = refusedBecause === "battery-asleep";
|
|
53534
|
+
if (asleep) state.armed = false;
|
|
53535
|
+
this.deps.logger.info("occupancy recheck: burst refused", {
|
|
53536
|
+
tags: {
|
|
53537
|
+
deviceId,
|
|
53538
|
+
nodeId
|
|
53539
|
+
},
|
|
53540
|
+
meta: {
|
|
53541
|
+
deviceId,
|
|
53542
|
+
nodeId,
|
|
53543
|
+
refusedBecause: refusedBecause ?? null,
|
|
53544
|
+
error: error ?? null,
|
|
53545
|
+
rearmed: !asleep
|
|
53546
|
+
}
|
|
53547
|
+
});
|
|
53548
|
+
if (!asleep) this.schedule(deviceId, state);
|
|
53549
|
+
return;
|
|
53550
|
+
}
|
|
53551
|
+
this.deps.logger.info("occupancy recheck: burst dispatched", {
|
|
53552
|
+
tags: {
|
|
53553
|
+
deviceId,
|
|
53554
|
+
nodeId
|
|
53555
|
+
},
|
|
53556
|
+
meta: {
|
|
53557
|
+
deviceId,
|
|
53558
|
+
nodeId,
|
|
53559
|
+
frames,
|
|
53560
|
+
recheckSec: state.lastConfig.occupancyRecheckSec ?? null
|
|
53561
|
+
}
|
|
53562
|
+
});
|
|
53563
|
+
setTimeout(() => {
|
|
53564
|
+
this.settle(deviceId, state, burst).catch((err) => {
|
|
53565
|
+
this.deps.logger.warn("occupancy recheck: settle failed", {
|
|
53566
|
+
tags: {
|
|
53567
|
+
deviceId,
|
|
53568
|
+
nodeId
|
|
53569
|
+
},
|
|
53570
|
+
meta: {
|
|
53571
|
+
deviceId,
|
|
53572
|
+
nodeId,
|
|
53573
|
+
error: errMsg$1(err)
|
|
53574
|
+
}
|
|
53575
|
+
});
|
|
53576
|
+
});
|
|
53577
|
+
}, RECHECK_BURST_WINDOW_MS).unref?.();
|
|
53578
|
+
}
|
|
53579
|
+
async settle(deviceId, state, burst) {
|
|
53580
|
+
await this.release(deviceId, state, burst);
|
|
53581
|
+
const fold = foldBurstOutcome({ emptyStreak: state.emptyStreak }, burst.found);
|
|
53582
|
+
state.emptyStreak = fold.emptyStreak;
|
|
53583
|
+
state.armed = fold.armed;
|
|
53584
|
+
this.deps.logger.info("occupancy recheck: burst complete", {
|
|
53585
|
+
tags: {
|
|
53586
|
+
deviceId,
|
|
53587
|
+
nodeId: burst.nodeId
|
|
53588
|
+
},
|
|
53589
|
+
meta: {
|
|
53590
|
+
deviceId,
|
|
53591
|
+
nodeId: burst.nodeId,
|
|
53592
|
+
found: burst.found,
|
|
53593
|
+
emptyStreak: fold.emptyStreak,
|
|
53594
|
+
armed: fold.armed,
|
|
53595
|
+
superseded: burst.superseded
|
|
53596
|
+
}
|
|
53597
|
+
});
|
|
53598
|
+
if (state.armed && !this.deps.isSessionActive(deviceId)) this.schedule(deviceId, state);
|
|
53599
|
+
else if (state.armed) state.armed = false;
|
|
53600
|
+
}
|
|
53601
|
+
/** Detach the recheck registration unless a session took it over. */
|
|
53602
|
+
async release(deviceId, state, burst) {
|
|
53603
|
+
if (state.burst === burst) state.burst = null;
|
|
53604
|
+
if (burst.superseded) return;
|
|
53605
|
+
await this.deps.detachOn(burst.nodeId, deviceId).catch((err) => {
|
|
53606
|
+
this.deps.logger.warn("occupancy recheck: detach failed", {
|
|
53607
|
+
tags: {
|
|
53608
|
+
deviceId,
|
|
53609
|
+
nodeId: burst.nodeId
|
|
53610
|
+
},
|
|
53611
|
+
meta: {
|
|
53612
|
+
deviceId,
|
|
53613
|
+
nodeId: burst.nodeId,
|
|
53614
|
+
error: errMsg$1(err)
|
|
53615
|
+
}
|
|
53616
|
+
});
|
|
53617
|
+
});
|
|
53618
|
+
}
|
|
53619
|
+
};
|
|
53620
|
+
//#endregion
|
|
53010
53621
|
//#region src/balancer-input-cache.ts
|
|
53011
53622
|
/**
|
|
53012
53623
|
* Short-TTL memo for the reads a session dispatch needs BEFORE it can choose a
|
|
@@ -53433,8 +54044,32 @@ var SessionDispatchController = class {
|
|
|
53433
54044
|
inferenceDeviceCaps = new TtlMemo({ load: (nodeId) => this.deps.getInferenceDeviceCaps(nodeId) });
|
|
53434
54045
|
/** Background refresher for {@link agentLoads} et al. Null until first use. */
|
|
53435
54046
|
balancerRefreshTimer = null;
|
|
54047
|
+
/**
|
|
54048
|
+
* The occupancy recheck for session-dispatched cameras (D547). The runner's
|
|
54049
|
+
* own recheck lives on an `on-motion` registration no camera receives; this
|
|
54050
|
+
* one is armed by the session close, which only this controller sees.
|
|
54051
|
+
*/
|
|
54052
|
+
recheck;
|
|
53436
54053
|
constructor(deps) {
|
|
53437
54054
|
this.deps = deps;
|
|
54055
|
+
const requestBurstOn = deps.requestBurstOn;
|
|
54056
|
+
if (requestBurstOn === void 0) {
|
|
54057
|
+
this.recheck = null;
|
|
54058
|
+
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" } });
|
|
54059
|
+
} else this.recheck = new OccupancyRecheckController({
|
|
54060
|
+
attachOn: (nodeId, config) => deps.attachOn(nodeId, config),
|
|
54061
|
+
detachOn: (nodeId, deviceId) => deps.detachOn(nodeId, deviceId),
|
|
54062
|
+
requestBurstOn,
|
|
54063
|
+
isSessionActive: (deviceId) => this.sessionRegistry.has(deviceId),
|
|
54064
|
+
logger: deps.logger
|
|
54065
|
+
});
|
|
54066
|
+
}
|
|
54067
|
+
/**
|
|
54068
|
+
* Every inference result the orchestrator sees, for the recheck's
|
|
54069
|
+
* found/empty verdict: a `'burst'` result with detections is a found burst.
|
|
54070
|
+
*/
|
|
54071
|
+
noteInferenceResult(deviceId, occasion, detectionCount) {
|
|
54072
|
+
this.recheck?.noteInferenceResult(deviceId, occasion, detectionCount);
|
|
53438
54073
|
}
|
|
53439
54074
|
/**
|
|
53440
54075
|
* Placement changed — this controller just attached or detached something, so
|
|
@@ -53827,6 +54462,7 @@ var SessionDispatchController = class {
|
|
|
53827
54462
|
attachedAt = Date.now();
|
|
53828
54463
|
this.deps.recordAssignment(deviceId, targetNodeId, decision.reason, decision.reason === "manual");
|
|
53829
54464
|
this.sessionRegistry.register(deviceId, targetNodeId, Date.now(), deviceKey);
|
|
54465
|
+
this.recheck?.noteSessionDispatched(deviceId, targetNodeId, sessionConfig);
|
|
53830
54466
|
log.info("detection session: dispatched on motion", {
|
|
53831
54467
|
tags: {
|
|
53832
54468
|
deviceId,
|
|
@@ -54003,6 +54639,7 @@ var SessionDispatchController = class {
|
|
|
54003
54639
|
this.activeRefireCountByDevice.delete(deviceId);
|
|
54004
54640
|
this.notePlacementChanged();
|
|
54005
54641
|
const { openForMs, sinceSubjectSeenMs, maxHoldMs } = observed;
|
|
54642
|
+
this.recheck?.noteSessionClosed(deviceId, { sawSubject: sinceSubjectSeenMs !== null });
|
|
54006
54643
|
const subjectPresent = sinceSubjectSeenMs !== null && sinceSubjectSeenMs < 4e3;
|
|
54007
54644
|
const meta = {
|
|
54008
54645
|
reason,
|
|
@@ -54084,6 +54721,7 @@ var SessionDispatchController = class {
|
|
|
54084
54721
|
}
|
|
54085
54722
|
this.deps.dropActiveDetection(deviceId);
|
|
54086
54723
|
this.lastSubjectSeenAt.delete(deviceId);
|
|
54724
|
+
this.recheck?.forget(deviceId);
|
|
54087
54725
|
});
|
|
54088
54726
|
const watchNodeId = this.motionWatchByDevice.get(deviceId);
|
|
54089
54727
|
if (watchNodeId) {
|
|
@@ -54119,6 +54757,7 @@ var SessionDispatchController = class {
|
|
|
54119
54757
|
*/
|
|
54120
54758
|
shutdown() {
|
|
54121
54759
|
this.shuttingDown = true;
|
|
54760
|
+
this.recheck?.shutdown();
|
|
54122
54761
|
this.sessionRegistry.clear();
|
|
54123
54762
|
this.motionWatchByDevice.clear();
|
|
54124
54763
|
this.lastSubjectSeenAt.clear();
|
|
@@ -55113,6 +55752,11 @@ async function buildOrchestratorControllers(deps) {
|
|
|
55113
55752
|
settingsStore,
|
|
55114
55753
|
attachOn: (nodeId, config) => deps.attachOn(nodeId, config),
|
|
55115
55754
|
detachOn: (nodeId, deviceId) => deps.detachOn(nodeId, deviceId),
|
|
55755
|
+
requestBurstOn: async (nodeId, deviceId, frames, reason) => deps.ctx().api.pipelineRunner.requestOccupancyBurst.mutate({
|
|
55756
|
+
deviceId,
|
|
55757
|
+
frames,
|
|
55758
|
+
reason
|
|
55759
|
+
}, nodePin(nodeId)),
|
|
55116
55760
|
recordAssignment: (deviceId, agentNodeId, reason, pinned) => ledger.recordAssignment(deviceId, agentNodeId, reason, pinned),
|
|
55117
55761
|
dropAssignment: (deviceId, emit) => ledger.dropAssignment(deviceId, emit),
|
|
55118
55762
|
reconcilePlacementFromRunners: () => reconcile.reconcilePlacementFromRunners(),
|
|
@@ -55417,7 +56061,10 @@ async function buildOrchestratorControllers(deps) {
|
|
|
55417
56061
|
handleDeviceUnregistered: (deviceId) => deps.handleDeviceUnregistered(deviceId),
|
|
55418
56062
|
handleSettingsChanged: (deviceId) => deps.handleSettingsChanged(deviceId),
|
|
55419
56063
|
handlePipelineCameraUpdated: (deviceId, config) => deps.handlePipelineCameraUpdated(deviceId, config),
|
|
55420
|
-
handleInferenceResult: (payload) =>
|
|
56064
|
+
handleInferenceResult: (payload) => {
|
|
56065
|
+
session.noteInferenceResult(payload.deviceId, payload.occasion, payload.frame.detections?.length ?? 0);
|
|
56066
|
+
return deps.handleInferenceResult(payload);
|
|
56067
|
+
},
|
|
55421
56068
|
noteMetrics: (deviceId, nodeId, metrics) => {
|
|
55422
56069
|
loadShed.noteMetrics(deviceId, nodeId, metrics);
|
|
55423
56070
|
nodeStress.noteMetrics(deviceId, nodeId, metrics);
|