@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.js
CHANGED
|
@@ -5391,7 +5391,7 @@ var ZodIssueCode = {
|
|
|
5391
5391
|
var ZodFirstPartyTypeKind;
|
|
5392
5392
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5393
5393
|
//#endregion
|
|
5394
|
-
//#region ../types/dist/sleep-
|
|
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(
|
|
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` —
|
|
24363
|
-
*
|
|
24364
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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: "
|
|
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: "
|
|
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) =>
|
|
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);
|