@camstack/addon-pipeline 1.2.79 → 1.2.81
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/dist/{addon-utils-BZQxAJ1G.js → addon-utils-CQFlZXSn.js} +1 -1
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +2 -2
- package/dist/{dist-BUIpqu3E.js → dist-B57MztMq.js} +276 -14
- package/dist/{dist-BfTf6doa.mjs → dist-DGp0dRiq.mjs} +259 -15
- package/dist/{event-loop-stall-monitor-CmrHj0i9.mjs → event-loop-stall-monitor-8vYH44kg.mjs} +1 -1
- package/dist/{event-loop-stall-monitor-CGLQP0by.js → event-loop-stall-monitor-DPA6Pu_h.js} +1 -1
- package/dist/{lazy-sharp-BZVhL9Lw.js → lazy-sharp-BMgRSV9x.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +26 -4
- package/dist/pipeline-runner/index.mjs +25 -3
- package/dist/recorder/index.js +62 -5
- package/dist/recorder/index.mjs +61 -4
- package/dist/session-decode/decode-worker-child.js +2 -2
- package/dist/session-decode/decode-worker-child.mjs +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BohtrVou.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Cb8ynph1.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B2scqOH7.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dxr14lIu.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DEps-9qh.mjs} +1 -1
- package/dist/stream-broker/{hostInit-pDANS0x0.mjs → hostInit-GJipMxBe.mjs} +2 -2
- package/dist/stream-broker/index.js +224 -1
- package/dist/stream-broker/index.mjs +224 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-BaLcYcOo.js → worker-protocol-4L4SbSAo.js} +1 -1
- package/dist/{worker-protocol-BdOv9Ujl.mjs → worker-protocol-_e84lGuR.mjs} +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-y8ehJPoY.mjs +0 -26
|
@@ -13704,6 +13704,18 @@ var LlmGenerateBaseInputSchema = object({
|
|
|
13704
13704
|
* Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
|
|
13705
13705
|
* watchdog — operator decision #3).
|
|
13706
13706
|
*/
|
|
13707
|
+
/**
|
|
13708
|
+
* A companion artifact that MUST land beside the main GGUF: the `mmproj`
|
|
13709
|
+
* projector of a vision model, or shards 2..N of a split GGUF. Carried on the
|
|
13710
|
+
* REF rather than looked up at install time, so what the operator approved in
|
|
13711
|
+
* the preview is exactly what the node downloads.
|
|
13712
|
+
*/
|
|
13713
|
+
var ManagedModelExtraFileSchema = object({
|
|
13714
|
+
url: string(),
|
|
13715
|
+
filename: string(),
|
|
13716
|
+
sizeBytes: number(),
|
|
13717
|
+
sha256: string().optional()
|
|
13718
|
+
});
|
|
13707
13719
|
var ManagedModelRefSchema = discriminatedUnion("kind", [
|
|
13708
13720
|
object({
|
|
13709
13721
|
kind: literal("catalog"),
|
|
@@ -13712,7 +13724,11 @@ var ManagedModelRefSchema = discriminatedUnion("kind", [
|
|
|
13712
13724
|
object({
|
|
13713
13725
|
kind: literal("url"),
|
|
13714
13726
|
url: string(),
|
|
13715
|
-
sha256: string().optional()
|
|
13727
|
+
sha256: string().optional(),
|
|
13728
|
+
/** Picker/status label; the file basename when absent. */
|
|
13729
|
+
label: string().optional(),
|
|
13730
|
+
sizeBytes: number().optional(),
|
|
13731
|
+
extraFiles: array(ManagedModelExtraFileSchema).optional()
|
|
13716
13732
|
}),
|
|
13717
13733
|
object({
|
|
13718
13734
|
kind: literal("path"),
|
|
@@ -13773,11 +13789,39 @@ var ManagedRuntimeConfigSchema = object({
|
|
|
13773
13789
|
"q4_1",
|
|
13774
13790
|
"q4_0"
|
|
13775
13791
|
]).optional(),
|
|
13792
|
+
/**
|
|
13793
|
+
* Escape hatch for llama-server flags this schema does NOT model — `--jinja`
|
|
13794
|
+
* (which most vision chat templates need and some language-only models
|
|
13795
|
+
* dislike), `--cont-batching`, `--rope-scaling`, …
|
|
13796
|
+
*
|
|
13797
|
+
* It is NOT a second place to set the flags above. A token that collides
|
|
13798
|
+
* with a typed field is REJECTED at start, naming the field that owns it
|
|
13799
|
+
* (`assertNoOwnedFlags`), because two knobs writing the same argv is exactly
|
|
13800
|
+
* the "two switches that disagree" failure this repo has already shipped
|
|
13801
|
+
* twice (D62).
|
|
13802
|
+
*/
|
|
13803
|
+
extraArgs: array(string()).default([]),
|
|
13776
13804
|
/** Else lazy: first generate boots it. */
|
|
13777
13805
|
autoStart: boolean().default(false),
|
|
13778
13806
|
/** 0 = never; frees RAM after quiet periods. */
|
|
13779
13807
|
idleStopMinutes: number().int().default(30)
|
|
13780
13808
|
});
|
|
13809
|
+
/**
|
|
13810
|
+
* Where a multi-GB install currently is. A single 0..1 fraction cannot answer
|
|
13811
|
+
* "is it stuck?" for an install that is three files (shards + mmproj) followed
|
|
13812
|
+
* by a sha256 pass over 22 GB — during which the fraction sat at 1.0 and the
|
|
13813
|
+
* node looked hung. Phase + file + bytes is the smallest shape that does.
|
|
13814
|
+
*/
|
|
13815
|
+
var LlmDownloadProgressSchema = object({
|
|
13816
|
+
phase: _enum(["downloading", "verifying"]),
|
|
13817
|
+
/** The artifact currently moving, e.g. `mmproj-F16.gguf`. */
|
|
13818
|
+
file: string(),
|
|
13819
|
+
fileIndex: number().int(),
|
|
13820
|
+
fileCount: number().int(),
|
|
13821
|
+
/** Across the WHOLE install, not the current file. */
|
|
13822
|
+
downloadedBytes: number(),
|
|
13823
|
+
totalBytes: number().optional()
|
|
13824
|
+
});
|
|
13781
13825
|
var LlmRuntimeStatusSchema = object({
|
|
13782
13826
|
/** Status is ALWAYS node-qualified. */
|
|
13783
13827
|
nodeId: string(),
|
|
@@ -13794,6 +13838,8 @@ var LlmRuntimeStatusSchema = object({
|
|
|
13794
13838
|
modelPath: string().optional(),
|
|
13795
13839
|
modelId: string().optional(),
|
|
13796
13840
|
downloadProgress: number().min(0).max(1).optional(),
|
|
13841
|
+
/** Detail behind `downloadProgress`; present for the same lifetime. */
|
|
13842
|
+
download: LlmDownloadProgressSchema.optional(),
|
|
13797
13843
|
lastError: string().optional(),
|
|
13798
13844
|
crashesInWindow: number(),
|
|
13799
13845
|
/** Child RSS (sampled best-effort). */
|
|
@@ -13804,7 +13850,14 @@ var LlmNodeModelSchema = object({
|
|
|
13804
13850
|
file: string(),
|
|
13805
13851
|
sizeBytes: number(),
|
|
13806
13852
|
catalogId: string().optional(),
|
|
13807
|
-
installedAt: number().optional()
|
|
13853
|
+
installedAt: number().optional(),
|
|
13854
|
+
/**
|
|
13855
|
+
* Absolute path on the node. Present so a file that is on disk but matches
|
|
13856
|
+
* no catalog entry — a custom Hugging Face install, or a GGUF the operator
|
|
13857
|
+
* copied in by hand — is still SELECTABLE, as a `{kind:'path'}` ref. Without
|
|
13858
|
+
* it the picker could list such a file and do nothing with it.
|
|
13859
|
+
*/
|
|
13860
|
+
path: string().optional()
|
|
13808
13861
|
});
|
|
13809
13862
|
var LlmRuntimeDiskUsageSchema = object({
|
|
13810
13863
|
nodeId: string(),
|
|
@@ -13950,6 +14003,36 @@ var ManagedModelCatalogEntrySchema = object({
|
|
|
13950
14003
|
/** Vision models: companion projector file. */
|
|
13951
14004
|
mmprojUrl: string().optional()
|
|
13952
14005
|
});
|
|
14006
|
+
/**
|
|
14007
|
+
* The outcome of turning one operator-typed Hugging Face reference into a
|
|
14008
|
+
* download plan. A RESULT, never a throw: "this repo has 24 quantizations and
|
|
14009
|
+
* I will not pick for you" is a normal answer the UI has to render, not an
|
|
14010
|
+
* exception.
|
|
14011
|
+
*
|
|
14012
|
+
* `candidates` is the whole reason the refusal is usable — every string in it
|
|
14013
|
+
* is a tag that resolves when pasted back as `<org>/<repo>:<TAG>`.
|
|
14014
|
+
*/
|
|
14015
|
+
var HfModelResolutionSchema = discriminatedUnion("ok", [object({
|
|
14016
|
+
ok: literal(true),
|
|
14017
|
+
/** Ready to hand to `installModel` unchanged. */
|
|
14018
|
+
model: ManagedModelRefSchema,
|
|
14019
|
+
label: string(),
|
|
14020
|
+
repo: string(),
|
|
14021
|
+
quantization: string(),
|
|
14022
|
+
purpose: _enum(["text", "vision"]),
|
|
14023
|
+
totalBytes: number(),
|
|
14024
|
+
/** mmproj + shards, for the preview: an operator approving 23 GB should
|
|
14025
|
+
* see that 0.9 GB of it is a projector they did not name. */
|
|
14026
|
+
extraFilenames: array(string())
|
|
14027
|
+
}), object({
|
|
14028
|
+
ok: literal(false),
|
|
14029
|
+
code: string(),
|
|
14030
|
+
message: string(),
|
|
14031
|
+
candidates: array(string()).optional(),
|
|
14032
|
+
/** Set when the refusal was only the ceiling: re-calling with
|
|
14033
|
+
* `maxBytes: requiredBytes` is the operator's explicit override. */
|
|
14034
|
+
requiredBytes: number().optional()
|
|
14035
|
+
})]);
|
|
13953
14036
|
var LlmRuntimeNodeSchema = object({
|
|
13954
14037
|
nodeId: string(),
|
|
13955
14038
|
reachable: boolean(),
|
|
@@ -13986,6 +14069,15 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
|
|
|
13986
14069
|
consumer: string().optional(),
|
|
13987
14070
|
profileId: string().optional()
|
|
13988
14071
|
}), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
|
|
14072
|
+
/** `https://huggingface.co/<org>/<repo>/resolve/main/<f>.gguf`,
|
|
14073
|
+
* `<org>/<repo>/<f>.gguf`, `<org>/<repo>` or `<org>/<repo>:<QUANT>`. */
|
|
14074
|
+
ref: string(),
|
|
14075
|
+
/** Explicit ceiling override, in bytes. Absent = the built-in ceiling. */
|
|
14076
|
+
maxBytes: number().positive().optional()
|
|
14077
|
+
}), HfModelResolutionSchema, {
|
|
14078
|
+
kind: "mutation",
|
|
14079
|
+
auth: "admin"
|
|
14080
|
+
}), method(object({
|
|
13989
14081
|
nodeId: string(),
|
|
13990
14082
|
model: ManagedModelRefSchema
|
|
13991
14083
|
}), _void(), {
|
|
@@ -19613,6 +19705,17 @@ var maxSessionHoldMsField = {
|
|
|
19613
19705
|
default: 12e4,
|
|
19614
19706
|
step: 5e3
|
|
19615
19707
|
};
|
|
19708
|
+
/**
|
|
19709
|
+
* Quiet period that closes an `audioMode: 'on-motion'` audio window. Floor of
|
|
19710
|
+
* 5s so a rearm can never degenerate into per-event stream churn; default 90s
|
|
19711
|
+
* comfortably outlives the gap between two PIR wakes on a battery camera.
|
|
19712
|
+
*/
|
|
19713
|
+
var audioMotionWindowMsField = {
|
|
19714
|
+
min: 5e3,
|
|
19715
|
+
max: 6e5,
|
|
19716
|
+
default: 9e4,
|
|
19717
|
+
step: 5e3
|
|
19718
|
+
};
|
|
19616
19719
|
var motionFpsField = {
|
|
19617
19720
|
min: 1,
|
|
19618
19721
|
max: 30,
|
|
@@ -19789,6 +19892,27 @@ var RunnerCameraConfigSchema = object({
|
|
|
19789
19892
|
* resolved `CameraDetectionConfig`.
|
|
19790
19893
|
*/
|
|
19791
19894
|
maxSessionHoldMs: number().min(maxSessionHoldMsField.min).max(maxSessionHoldMsField.max).optional(),
|
|
19895
|
+
/**
|
|
19896
|
+
* Orchestrator-side quiet period (ms) that closes an `audioMode:
|
|
19897
|
+
* 'on-motion'` audio window, measured from the LAST motion event.
|
|
19898
|
+
*
|
|
19899
|
+
* This exists because the falling edge cannot be relied on. Camera-native
|
|
19900
|
+
* providers emit motion as a RISING EDGE ONLY (Reolink's Baichuan push and
|
|
19901
|
+
* its email-push SMTP path both emit `detected: true` and never the
|
|
19902
|
+
* counterpart); only the frame-diff analyzer emits falls. So on an
|
|
19903
|
+
* onboard-only camera a window that closed only on `detected: false` never
|
|
19904
|
+
* closed at all, and `on-motion` silently behaved as `always-on` — on a
|
|
19905
|
+
* battery camera, the one failure mode the mode exists to prevent.
|
|
19906
|
+
*
|
|
19907
|
+
* Every motion event rearms this timer WITHOUT restarting the stream, so a
|
|
19908
|
+
* burst of re-fires costs nothing. A falling edge, when one does arrive,
|
|
19909
|
+
* still closes earlier via `motionCooldownMs` — whichever comes first wins.
|
|
19910
|
+
*
|
|
19911
|
+
* Not consumed by the runner: carried here so it shares the per-camera
|
|
19912
|
+
* device-settings surface with `motionCooldownMs`, exactly like
|
|
19913
|
+
* `maxSessionHoldMs`.
|
|
19914
|
+
*/
|
|
19915
|
+
audioMotionWindowMs: number().min(audioMotionWindowMsField.min).max(audioMotionWindowMsField.max).optional(),
|
|
19792
19916
|
motionFps: number().min(motionFpsField.min).max(motionFpsField.max).default(motionFpsField.default),
|
|
19793
19917
|
detectionFps: number().min(detectionFpsField.min).max(detectionFpsField.max).default(detectionFpsField.default),
|
|
19794
19918
|
motionStreamId: string(),
|
|
@@ -19884,7 +20008,7 @@ var RunnerCameraConfigSchema = object({
|
|
|
19884
20008
|
*/
|
|
19885
20009
|
inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
|
|
19886
20010
|
});
|
|
19887
|
-
motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
|
|
20011
|
+
motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, audioMotionWindowMsField.min, audioMotionWindowMsField.max, audioMotionWindowMsField.step, audioMotionWindowMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
|
|
19888
20012
|
/**
|
|
19889
20013
|
* Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
|
|
19890
20014
|
* load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
|
|
@@ -22659,6 +22783,25 @@ var BatteryStatusSchema = object({
|
|
|
22659
22783
|
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
22660
22784
|
lastUpdated: number(),
|
|
22661
22785
|
/**
|
|
22786
|
+
* Ms epoch of the last time the device PROVED it was reachable — a
|
|
22787
|
+
* completed firmware round-trip, an observed wake, or an inbound push
|
|
22788
|
+
* (firmware event, email). `0`/absent = never since this slice was born.
|
|
22789
|
+
*
|
|
22790
|
+
* This is the ONLY input that separates "asleep" from "gone", and it is
|
|
22791
|
+
* fed exclusively by PASSIVE signals: nothing may write it by reaching
|
|
22792
|
+
* for the radio, because a poll that confirms reachability is the same
|
|
22793
|
+
* poll that drains the battery. See {@link deriveBatteryPresence} — the
|
|
22794
|
+
* single derivation every consumer must use; no surface computes its own.
|
|
22795
|
+
*
|
|
22796
|
+
* It is deliberately NOT a clock in the
|
|
22797
|
+
* `scripts/check-runtime-state-durability.ts` sense: it is the
|
|
22798
|
+
* observation itself, and it is the only thing a 30-hour silence is
|
|
22799
|
+
* visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
|
|
22800
|
+
* Reolink provider) so a value that means "recently" cannot cost a
|
|
22801
|
+
* SQLite commit per round-trip.
|
|
22802
|
+
*/
|
|
22803
|
+
lastContactAt: number().optional(),
|
|
22804
|
+
/**
|
|
22662
22805
|
* True when the source is a BINARY low-battery indicator (HA
|
|
22663
22806
|
* `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
|
|
22664
22807
|
* charge level — `percentage` is then a coarse stand-in (100 = normal,
|
|
@@ -22667,17 +22810,84 @@ var BatteryStatusSchema = object({
|
|
|
22667
22810
|
*/
|
|
22668
22811
|
binary: boolean().optional()
|
|
22669
22812
|
});
|
|
22670
|
-
|
|
22671
|
-
|
|
22672
|
-
|
|
22673
|
-
|
|
22674
|
-
|
|
22675
|
-
|
|
22676
|
-
|
|
22677
|
-
|
|
22678
|
-
|
|
22679
|
-
|
|
22680
|
-
|
|
22813
|
+
var batteryCapability = {
|
|
22814
|
+
name: "battery",
|
|
22815
|
+
scope: "device",
|
|
22816
|
+
deviceNative: true,
|
|
22817
|
+
mode: "singleton",
|
|
22818
|
+
deviceTypes: [
|
|
22819
|
+
DeviceType.Camera,
|
|
22820
|
+
DeviceType.Sensor,
|
|
22821
|
+
DeviceType.Button,
|
|
22822
|
+
DeviceType.Switch
|
|
22823
|
+
],
|
|
22824
|
+
methods: {
|
|
22825
|
+
/**
|
|
22826
|
+
* Explicitly wake the camera from low-power sleep ahead of a
|
|
22827
|
+
* streaming session start. Consumers that initiate a stream
|
|
22828
|
+
* against a sleeping battery cam (HomeKit Secure Video, Alexa
|
|
22829
|
+
* RTCSession, snapshot wrappers) call this with a short timeout
|
|
22830
|
+
* before establishing the media pipeline — the broker's own
|
|
22831
|
+
* passive wake-on-dial works but adds 5–7 seconds to first-frame,
|
|
22832
|
+
* during which the consumer renders a black screen. Pre-waking
|
|
22833
|
+
* compresses that gap.
|
|
22834
|
+
*
|
|
22835
|
+
* Returns `awoke: true` when the firmware acknowledged the wake
|
|
22836
|
+
* before `timeoutMs`. Returns `awoke: false` when it timed out OR
|
|
22837
|
+
* the cap surface is unavailable (no Baichuan / firmware
|
|
22838
|
+
* channel); the caller should still attempt the stream — the
|
|
22839
|
+
* passive broker wake remains as fallback.
|
|
22840
|
+
*/
|
|
22841
|
+
wakeForStream: method(object({
|
|
22842
|
+
deviceId: number(),
|
|
22843
|
+
/** Bound on the wait. Sensible range 3000–10000ms. */
|
|
22844
|
+
timeoutMs: number().int().min(500).max(3e4).default(8e3)
|
|
22845
|
+
}), object({
|
|
22846
|
+
awoke: boolean(),
|
|
22847
|
+
durationMs: number()
|
|
22848
|
+
}), { kind: "mutation" }) },
|
|
22849
|
+
events: {
|
|
22850
|
+
/**
|
|
22851
|
+
* Emitted whenever the cached status changes (firmware push OR
|
|
22852
|
+
* poll observes a delta). The DeviceEventPropagator mirrors this
|
|
22853
|
+
* event on the parent chain — subscribing to a camera's source
|
|
22854
|
+
* receives battery events from child accessories automatically.
|
|
22855
|
+
*/
|
|
22856
|
+
onStatusChanged: { data: object({
|
|
22857
|
+
deviceId: number(),
|
|
22858
|
+
status: BatteryStatusSchema
|
|
22859
|
+
}) } },
|
|
22860
|
+
status: {
|
|
22861
|
+
schema: BatteryStatusSchema,
|
|
22862
|
+
kind: "push",
|
|
22863
|
+
empty: {
|
|
22864
|
+
percentage: 0,
|
|
22865
|
+
charging: "none",
|
|
22866
|
+
sleeping: false,
|
|
22867
|
+
lastUpdated: 0
|
|
22868
|
+
}
|
|
22869
|
+
},
|
|
22870
|
+
/**
|
|
22871
|
+
* Runtime-state slice — every provider that registers this cap
|
|
22872
|
+
* stores the same shape under `device.runtimeState[battery]`.
|
|
22873
|
+
* Cross-provider uniformity: a Reolink Argus, a Frigate sensor
|
|
22874
|
+
* proxy, an ONVIF battery cam all read/write the same keys.
|
|
22875
|
+
* Consumers (BatteryBadge, snapshot wrapper sleep gate) read once
|
|
22876
|
+
* via `device.runtimeState.getCapState('battery')` regardless of
|
|
22877
|
+
* the underlying driver.
|
|
22878
|
+
*/
|
|
22879
|
+
runtimeState: BatteryStatusSchema,
|
|
22880
|
+
/**
|
|
22881
|
+
* Runtime-state durability: **restored** — a sleeping battery camera may not report for hours; the restored percentage is the only thing the UI and the sleep gate have. Zero churn once `lastUpdated` is excluded — 526 writes, 0 value changes, in 25 minutes.
|
|
22882
|
+
*
|
|
22883
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
22884
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
22885
|
+
*/
|
|
22886
|
+
durability: "restored",
|
|
22887
|
+
/** Clock fields: written, but excluded from the compare that decides
|
|
22888
|
+
* whether persisting is worth a SQLite commit. */
|
|
22889
|
+
volatileStateFields: ["lastUpdated"]
|
|
22890
|
+
};
|
|
22681
22891
|
object({
|
|
22682
22892
|
on: boolean(),
|
|
22683
22893
|
/** Ms epoch of the last transition. 0 if never observed. */
|
|
@@ -27088,6 +27298,16 @@ var CamStreamDescriptorSchema = object({
|
|
|
27088
27298
|
/** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
|
|
27089
27299
|
metadata: record(string(), unknown()).optional()
|
|
27090
27300
|
});
|
|
27301
|
+
object({
|
|
27302
|
+
/** The descriptors as last built from a real camera response. Never a guess:
|
|
27303
|
+
* a failed or refused build writes NOTHING, so a restored catalog is always
|
|
27304
|
+
* one the camera itself once produced. */
|
|
27305
|
+
descriptors: array(CamStreamDescriptorSchema),
|
|
27306
|
+
/** Ms epoch of the build that produced {@link descriptors}. Lets the wake
|
|
27307
|
+
* path decide whether the camera's own awake window is worth spending on a
|
|
27308
|
+
* re-read. */
|
|
27309
|
+
lastFetchedAt: number()
|
|
27310
|
+
});
|
|
27091
27311
|
DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly());
|
|
27092
27312
|
/** One of the camera's stream profiles. */
|
|
27093
27313
|
var StreamProfileSchema = _enum([
|
|
@@ -27972,6 +28192,24 @@ AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, Accessor
|
|
|
27972
28192
|
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
|
|
27973
28193
|
new Set(Object.values(DeviceType));
|
|
27974
28194
|
DeviceFeature.BatteryOperated;
|
|
28195
|
+
/**
|
|
28196
|
+
* THE derivation. One crop rectangle, one presence verdict — see D52 for why
|
|
28197
|
+
* this repo insists a derived value has exactly one implementation.
|
|
28198
|
+
*
|
|
28199
|
+
* `undefined` status ⇒ `sleeping`. A device with no slice has made no claim,
|
|
28200
|
+
* and the caller decides separately whether it is even battery-operated
|
|
28201
|
+
* (`DeviceFeature.BatteryOperated`); this function never answers that
|
|
28202
|
+
* question, only what a battery device is doing.
|
|
28203
|
+
*/
|
|
28204
|
+
function deriveBatteryPresence(input) {
|
|
28205
|
+
const status = input.status;
|
|
28206
|
+
if (!status) return "sleeping";
|
|
28207
|
+
if (status.sleeping !== true) return "awake";
|
|
28208
|
+
const lastContactAt = status.lastContactAt;
|
|
28209
|
+
if (typeof lastContactAt !== "number" || lastContactAt <= 0) return "sleeping";
|
|
28210
|
+
const budget = input.unreachableAfterMs ?? 216e5;
|
|
28211
|
+
return input.nowMs - lastContactAt > budget ? "unreachable" : "sleeping";
|
|
28212
|
+
}
|
|
27975
28213
|
Object.freeze({
|
|
27976
28214
|
"accessories.setChildHidden": {
|
|
27977
28215
|
capName: "accessories",
|
|
@@ -30121,6 +30359,12 @@ Object.freeze({
|
|
|
30121
30359
|
addonId: null,
|
|
30122
30360
|
access: "view"
|
|
30123
30361
|
},
|
|
30362
|
+
"llm.resolveModelRef": {
|
|
30363
|
+
capName: "llm",
|
|
30364
|
+
capScope: "system",
|
|
30365
|
+
addonId: null,
|
|
30366
|
+
access: "create"
|
|
30367
|
+
},
|
|
30124
30368
|
"llm.setDefault": {
|
|
30125
30369
|
capName: "llm",
|
|
30126
30370
|
capScope: "system",
|
|
@@ -36277,4 +36521,4 @@ function enumerateInferenceDevices(hw) {
|
|
|
36277
36521
|
return out;
|
|
36278
36522
|
}
|
|
36279
36523
|
//#endregion
|
|
36280
|
-
export {
|
|
36524
|
+
export { runtimeDevices as $, createHwAccelCache as A, number as At, enumerateInferenceDevices as B, RingBuffer as C, sleep as Ct, audioAnalyzerCapability as D, discriminatedUnion as Dt, audioAnalysisCapability as E, boolean as Et, deriveDetailCropRect as F, EventCategory as Ft, motionDetectionCapability as G, hfModelUrl as H, deriveRecordingMode as I, pipelineExecutorCapability as J, pickDetailCropConvention as K, detectionPipelineCapability as L, defaultDeviceFor as M, record as Mt, defineCustomActions as N, string as Nt, batteryCapability as O, lazy as Ot, deriveBatteryPresence as P, union as Pt, resolveEgressDecodeHwAccel as Q, egressTranscodeSharingKey as R, RecordingConfigSchema as S, selectAssignedProfileSlots as St, addonWidgetsSourceCapability as T, array as Tt, mapAudioLabelToMacro as U, evaluateZoneRules as V, maskUrlCredentials as W, recordingCapability as X, pipelineRunnerCapability as Y, recordingExportCapability as Z, NativeLeaseSettingsSchema as _, makeProfileBrokerId as _t, COCO_80_LABELS as a, AUDIO_PRESETS as at, RATE_CONTROL_TIGHT as b, parseJsonUnknown as bt, DEFAULT_DETAIL_CROP_CONVENTION as c, invocationFromEncodeProfile as ct, DEVICE_BACKEND_TO_FORMAT as d, CAM_PROFILE_ORDER as dt, storageEvictableCapability as et, EVENT_PAD_MS as f, DeviceFeature as ft, NativeLeaseAdmissionSchema as g, isEvent as gt, HF_BASE_URL as h, hydrateSchema as ht, BatteryStatusSchema as i, errMsg as it, customAction as j, object as jt, cameraStreamsCapability as k, literal as kt, DEFAULT_EVENTS_BAND_BUFFER_SEC as l, isSoftwareDecode as lt, ExportRecordSchema as m, createEvent as mt, AUDIO_BACKEND_CHOICES as n, supportedRuntimes as nt, COCO_TO_MACRO as o, Fmp4BoxSplitter as ot, EncodeProfileSchema as p, DeviceType as pt, pickNativeLeaseOverride as q, AUDIO_MACRO_LABELS as r, webrtcSessionCapability as rt, DEFAULT_AUDIO_ANALYZER_CONFIG as s, buildFfmpegArgs as st, APPLE_SA_TO_MACRO as t, streamBrokerCapability as tt, DEFAULT_NATIVE_LEASE_SETTINGS as u, BaseAddon as ut, OpsLogEntrySchema as v, makeSourceBrokerId as vt, YAMNET_TO_MACRO as w, _enum as wt, RECORDING_EXPORT_MAX_READ_BYTES as x, parseProfileBrokerId as xt, RATE_CONTROL_RELAXED as y, nodePin as yt, egressTransportFromRequest as z };
|
package/dist/{event-loop-stall-monitor-CmrHj0i9.mjs → event-loop-stall-monitor-8vYH44kg.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { H as hfModelUrl, a as COCO_80_LABELS, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-DGp0dRiq.mjs";
|
|
2
2
|
import { PerformanceObserver } from "node:perf_hooks";
|
|
3
3
|
//#region src/detection-pipeline/registry/model-catalogs.ts
|
|
4
4
|
var HF_REPO = "camstack/camstack-models";
|
|
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
const require_lazy_sharp = require("../lazy-sharp-
|
|
5
|
+
const require_dist = require("../dist-B57MztMq.js");
|
|
6
|
+
const require_lazy_sharp = require("../lazy-sharp-BMgRSV9x.js");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as motionDetectionCapability, V as evaluateZoneRules, ht as hydrateSchema, pt as DeviceType, ut as BaseAddon } from "../dist-DGp0dRiq.mjs";
|
|
2
2
|
import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
@@ -2,11 +2,11 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-
|
|
7
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
5
|
+
const require_dist = require("../dist-B57MztMq.js");
|
|
6
|
+
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-DPA6Pu_h.js");
|
|
7
|
+
const require_worker_protocol = require("../worker-protocol-4L4SbSAo.js");
|
|
8
8
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
9
|
-
const require_lazy_sharp = require("../lazy-sharp-
|
|
9
|
+
const require_lazy_sharp = require("../lazy-sharp-BMgRSV9x.js");
|
|
10
10
|
const require_remote_restream = require("../remote-restream-BYbAsgUf.js");
|
|
11
11
|
let sharp = require("sharp");
|
|
12
12
|
sharp = require_dist.__toESM(sharp);
|
|
@@ -4350,6 +4350,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4350
4350
|
nodeId = "unknown";
|
|
4351
4351
|
metricsSnapshotTimer = null;
|
|
4352
4352
|
unsubMotionEvents = null;
|
|
4353
|
+
/** Devices we have positively observed reporting `battery.sleeping: true`.
|
|
4354
|
+
* Absent = unknown or awake; both mean "do not gate" (D49). Fed only by
|
|
4355
|
+
* `BatteryOnStatusChanged`, so consulting it costs nothing and can never
|
|
4356
|
+
* wake the camera it protects. See `enqueueOccupancyBurst`. */
|
|
4357
|
+
sleepingBatteryDevices = /* @__PURE__ */ new Set();
|
|
4358
|
+
unsubBatteryEvents = null;
|
|
4353
4359
|
/** Last analyzer-detected state per device — gates the
|
|
4354
4360
|
* `MotionOnMotionChanged` emit in `runMotionAnalysis` to transitions
|
|
4355
4361
|
* only (otherwise we'd emit on every analyzer frame). */
|
|
@@ -4557,6 +4563,13 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4557
4563
|
if (!attachment.config.motionSources.includes(source)) return;
|
|
4558
4564
|
this.runner?.reportMotion(deviceId, data.detected, source, data.regions ? [...data.regions] : void 0);
|
|
4559
4565
|
});
|
|
4566
|
+
if (this.ctx.eventBus) this.unsubBatteryEvents = this.ctx.eventBus.subscribe({ category: require_dist.EventCategory.BatteryOnStatusChanged }, (event) => {
|
|
4567
|
+
const data = event.data;
|
|
4568
|
+
const deviceId = data.deviceId;
|
|
4569
|
+
if (typeof deviceId !== "number") return;
|
|
4570
|
+
if (data.status?.sleeping === true) this.sleepingBatteryDevices.add(deviceId);
|
|
4571
|
+
else this.sleepingBatteryDevices.delete(deviceId);
|
|
4572
|
+
});
|
|
4560
4573
|
this.metricsSnapshotTimer = setInterval(() => this.emitMetricsSnapshot(), METRICS_SNAPSHOT_INTERVAL_MS);
|
|
4561
4574
|
return {
|
|
4562
4575
|
providers: [{
|
|
@@ -4590,6 +4603,11 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4590
4603
|
this.unsubMotionEvents();
|
|
4591
4604
|
this.unsubMotionEvents = null;
|
|
4592
4605
|
}
|
|
4606
|
+
if (this.unsubBatteryEvents) {
|
|
4607
|
+
this.unsubBatteryEvents();
|
|
4608
|
+
this.unsubBatteryEvents = null;
|
|
4609
|
+
}
|
|
4610
|
+
this.sleepingBatteryDevices.clear();
|
|
4593
4611
|
this.lastAnalyzerDetected.clear();
|
|
4594
4612
|
this.packageGovernor.reset();
|
|
4595
4613
|
this.packageGateSkips.clear();
|
|
@@ -6660,6 +6678,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6660
6678
|
* parallel decoder dials.
|
|
6661
6679
|
*/
|
|
6662
6680
|
enqueueOccupancyBurst(deviceId, frames) {
|
|
6681
|
+
if (this.sleepingBatteryDevices.has(deviceId)) {
|
|
6682
|
+
this.ctx.logger.debug("occupancy recheck skipped — battery camera is sleeping", { tags: { deviceId } });
|
|
6683
|
+
return;
|
|
6684
|
+
}
|
|
6663
6685
|
this.occupancyFrames.set(deviceId, frames);
|
|
6664
6686
|
if (this.occupancyBurstInFlight.has(deviceId) || this.occupancyQueued.has(deviceId)) return;
|
|
6665
6687
|
this.occupancyQueued.add(deviceId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { At as
|
|
2
|
-
import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-
|
|
3
|
-
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-
|
|
1
|
+
import { At as number, Et as boolean, F as deriveDetailCropRect, Ft as EventCategory, K as pickDetailCropConvention, N as defineCustomActions, Nt as string, Ot as lazy, Tt as array, Y as pipelineRunnerCapability, c as DEFAULT_DETAIL_CROP_CONVENTION, d as DEVICE_BACKEND_TO_FORMAT, it as errMsg, j as customAction, jt as object, kt as literal, mt as createEvent, q as pickNativeLeaseOverride, ut as BaseAddon, wt as _enum, yt as nodePin } from "../dist-DGp0dRiq.mjs";
|
|
2
|
+
import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-8vYH44kg.mjs";
|
|
3
|
+
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-_e84lGuR.mjs";
|
|
4
4
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
6
6
|
import { i as withDetectionIntent, n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-Ci7RXNGb.mjs";
|
|
@@ -4343,6 +4343,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4343
4343
|
nodeId = "unknown";
|
|
4344
4344
|
metricsSnapshotTimer = null;
|
|
4345
4345
|
unsubMotionEvents = null;
|
|
4346
|
+
/** Devices we have positively observed reporting `battery.sleeping: true`.
|
|
4347
|
+
* Absent = unknown or awake; both mean "do not gate" (D49). Fed only by
|
|
4348
|
+
* `BatteryOnStatusChanged`, so consulting it costs nothing and can never
|
|
4349
|
+
* wake the camera it protects. See `enqueueOccupancyBurst`. */
|
|
4350
|
+
sleepingBatteryDevices = /* @__PURE__ */ new Set();
|
|
4351
|
+
unsubBatteryEvents = null;
|
|
4346
4352
|
/** Last analyzer-detected state per device — gates the
|
|
4347
4353
|
* `MotionOnMotionChanged` emit in `runMotionAnalysis` to transitions
|
|
4348
4354
|
* only (otherwise we'd emit on every analyzer frame). */
|
|
@@ -4550,6 +4556,13 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4550
4556
|
if (!attachment.config.motionSources.includes(source)) return;
|
|
4551
4557
|
this.runner?.reportMotion(deviceId, data.detected, source, data.regions ? [...data.regions] : void 0);
|
|
4552
4558
|
});
|
|
4559
|
+
if (this.ctx.eventBus) this.unsubBatteryEvents = this.ctx.eventBus.subscribe({ category: EventCategory.BatteryOnStatusChanged }, (event) => {
|
|
4560
|
+
const data = event.data;
|
|
4561
|
+
const deviceId = data.deviceId;
|
|
4562
|
+
if (typeof deviceId !== "number") return;
|
|
4563
|
+
if (data.status?.sleeping === true) this.sleepingBatteryDevices.add(deviceId);
|
|
4564
|
+
else this.sleepingBatteryDevices.delete(deviceId);
|
|
4565
|
+
});
|
|
4553
4566
|
this.metricsSnapshotTimer = setInterval(() => this.emitMetricsSnapshot(), METRICS_SNAPSHOT_INTERVAL_MS);
|
|
4554
4567
|
return {
|
|
4555
4568
|
providers: [{
|
|
@@ -4583,6 +4596,11 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4583
4596
|
this.unsubMotionEvents();
|
|
4584
4597
|
this.unsubMotionEvents = null;
|
|
4585
4598
|
}
|
|
4599
|
+
if (this.unsubBatteryEvents) {
|
|
4600
|
+
this.unsubBatteryEvents();
|
|
4601
|
+
this.unsubBatteryEvents = null;
|
|
4602
|
+
}
|
|
4603
|
+
this.sleepingBatteryDevices.clear();
|
|
4586
4604
|
this.lastAnalyzerDetected.clear();
|
|
4587
4605
|
this.packageGovernor.reset();
|
|
4588
4606
|
this.packageGateSkips.clear();
|
|
@@ -6653,6 +6671,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
6653
6671
|
* parallel decoder dials.
|
|
6654
6672
|
*/
|
|
6655
6673
|
enqueueOccupancyBurst(deviceId, frames) {
|
|
6674
|
+
if (this.sleepingBatteryDevices.has(deviceId)) {
|
|
6675
|
+
this.ctx.logger.debug("occupancy recheck skipped — battery camera is sleeping", { tags: { deviceId } });
|
|
6676
|
+
return;
|
|
6677
|
+
}
|
|
6656
6678
|
this.occupancyFrames.set(deviceId, frames);
|
|
6657
6679
|
if (this.occupancyBurstInFlight.has(deviceId) || this.occupancyQueued.has(deviceId)) return;
|
|
6658
6680
|
this.occupancyQueued.add(deviceId);
|
package/dist/recorder/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const require_dist = require("../dist-
|
|
1
|
+
const require_dist = require("../dist-B57MztMq.js");
|
|
2
2
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
3
|
-
const require_addon_utils = require("../addon-utils-
|
|
3
|
+
const require_addon_utils = require("../addon-utils-CQFlZXSn.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_child_process = require("node:child_process");
|
|
6
6
|
let node_path = require("node:path");
|
|
@@ -3764,6 +3764,7 @@ async function buildPlaybackManifest(deps, codecCache, deviceId, fromMs, toMs) {
|
|
|
3764
3764
|
if (base === null) return noPlayback(deviceId);
|
|
3765
3765
|
const readPrefix = deps.readFilePrefix ?? readPrefixFromDisk;
|
|
3766
3766
|
const playbackDir = playbackDirFor(deps.dataDir, deviceId);
|
|
3767
|
+
if (deps.index.hydrationOf(deviceId, fromMs, toMs) !== "hydrated") await deps.hydrateWindow?.(deviceId, fromMs, toMs);
|
|
3767
3768
|
const profiles = [...new Set(deps.index.segments(deviceId).map((s) => s.profile))];
|
|
3768
3769
|
const variants = [];
|
|
3769
3770
|
for (const profile of profiles) {
|
|
@@ -5016,6 +5017,61 @@ async function hydrateDeviceFromStorage(_api, index, deviceId, locations, logger
|
|
|
5016
5017
|
}
|
|
5017
5018
|
/** Entries classified between yields in {@link hydrateDeviceFromStorage}. */
|
|
5018
5019
|
var HYDRATE_CHUNK = 2e4;
|
|
5020
|
+
/**
|
|
5021
|
+
* Device walks in flight at once in {@link hydrateDevicesFromStorage}.
|
|
5022
|
+
*
|
|
5023
|
+
* The walk is `readdir` latency on a network/FUSE share, not CPU: measured on
|
|
5024
|
+
* the live hub, 24 cold hour directories cost 31 s one at a time and 16.7 s
|
|
5025
|
+
* eight at a time. Four is the conservative point on that curve — enough to
|
|
5026
|
+
* hide most of the latency, few enough that the boot walk never becomes the
|
|
5027
|
+
* reason a live SegmentWriter waits. libuv's default threadpool is 4, so a
|
|
5028
|
+
* higher number here mostly queues.
|
|
5029
|
+
*/
|
|
5030
|
+
var HYDRATE_DEVICE_CONCURRENCY = 4;
|
|
5031
|
+
/**
|
|
5032
|
+
* Walk the archive for a whole fleet: bounded-parallel, and each camera becomes
|
|
5033
|
+
* authoritative the moment ITS OWN walk lands (D167).
|
|
5034
|
+
*
|
|
5035
|
+
* Two things this fixes, both measured on the live hub on 2026-08-15:
|
|
5036
|
+
*
|
|
5037
|
+
* - The caller was `for (const id of ids) await hydrateDeviceFromStorage(...)`,
|
|
5038
|
+
* so seven cameras' 357 s of `readdir` WAS the 362 s wall clock. Nothing in
|
|
5039
|
+
* the walk contends for CPU — it is share latency — so overlapping is free.
|
|
5040
|
+
* - `markHydratedAll` ran in a SECOND loop after every device. A camera whose
|
|
5041
|
+
* walk finished at minute 1 still reported `hydrationOf → 'unknown'` until
|
|
5042
|
+
* minute 6, and every read of it in that window paid for an on-demand walk
|
|
5043
|
+
* over rows already in the index, competing with the walk still running for
|
|
5044
|
+
* the others.
|
|
5045
|
+
*
|
|
5046
|
+
* A device whose walk throws is NOT marked: `unknown` is the honest answer, and
|
|
5047
|
+
* it is what keeps the read path walking that window on demand.
|
|
5048
|
+
*/
|
|
5049
|
+
async function hydrateDevicesFromStorage(api, index, deviceIds, locations, logger, options = {}) {
|
|
5050
|
+
const concurrency = Math.max(1, options.concurrency ?? HYDRATE_DEVICE_CONCURRENCY);
|
|
5051
|
+
const queue = [...deviceIds];
|
|
5052
|
+
const walkOptions = {
|
|
5053
|
+
...options.yieldBetween ? { yieldBetween: options.yieldBetween } : {},
|
|
5054
|
+
...options.chunkSize !== void 0 ? { chunkSize: options.chunkSize } : {}
|
|
5055
|
+
};
|
|
5056
|
+
const lane = async () => {
|
|
5057
|
+
for (;;) {
|
|
5058
|
+
const deviceId = queue.shift();
|
|
5059
|
+
if (deviceId === void 0) return;
|
|
5060
|
+
try {
|
|
5061
|
+
options.onDeviceStarted?.(deviceId);
|
|
5062
|
+
await hydrateDeviceFromStorage(api, index, deviceId, locations, logger, walkOptions);
|
|
5063
|
+
index.markHydratedAll(deviceId);
|
|
5064
|
+
options.onDeviceHydrated?.(deviceId);
|
|
5065
|
+
} catch (err) {
|
|
5066
|
+
logger.warn("recorder: device hydrate failed — window stays unknown, reads will walk it", {
|
|
5067
|
+
tags: { deviceId },
|
|
5068
|
+
meta: { error: require_dist.errMsg(err) }
|
|
5069
|
+
});
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
};
|
|
5073
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, lane));
|
|
5074
|
+
}
|
|
5019
5075
|
var macrotask$1 = () => new Promise((resolve) => {
|
|
5020
5076
|
setImmediate(resolve);
|
|
5021
5077
|
});
|
|
@@ -8108,11 +8164,12 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
8108
8164
|
return;
|
|
8109
8165
|
}
|
|
8110
8166
|
this.warmToday([...configs.keys()]);
|
|
8111
|
-
|
|
8112
|
-
|
|
8167
|
+
const hydrateStartedMs = Date.now();
|
|
8168
|
+
await hydrateDevicesFromStorage(this.ctx.api, this.index, [...configs.keys()], this.resolvedLocations, this.ctx.logger);
|
|
8113
8169
|
this.ctx.logger.info("recorder: hydrated index from storage", { meta: {
|
|
8114
8170
|
deviceCount: configs.size,
|
|
8115
|
-
locationCount: this.resolvedLocations.length
|
|
8171
|
+
locationCount: this.resolvedLocations.length,
|
|
8172
|
+
ms: Date.now() - hydrateStartedMs
|
|
8116
8173
|
} });
|
|
8117
8174
|
const loggedDomains = /* @__PURE__ */ new Set();
|
|
8118
8175
|
for (const loc of this.resolvedLocations) {
|