@camstack/addon-provider-dreo 0.2.19 → 0.2.21
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.js +187 -45
- package/dist/addon.mjs +187 -45
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -35,7 +35,7 @@ var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["modu
|
|
|
35
35
|
//#endregion
|
|
36
36
|
let crypto$1 = require("crypto");
|
|
37
37
|
let events = require("events");
|
|
38
|
-
//#region ../types/dist/event-category-
|
|
38
|
+
//#region ../types/dist/event-category-XfKNtfCc.mjs
|
|
39
39
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
40
40
|
EventCategory["SystemBoot"] = "system.boot";
|
|
41
41
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -52,9 +52,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
52
52
|
EventCategory["SystemRestartCompleted"] = "system.restart-completed";
|
|
53
53
|
/**
|
|
54
54
|
* A newer addon or server-root package version was found by the
|
|
55
|
-
* authoritative registry check. Emitted once
|
|
56
|
-
* `(
|
|
57
|
-
*
|
|
55
|
+
* authoritative registry check. Emitted once when any observed
|
|
56
|
+
* `latestVersion` changes (or a package/node first appears behind);
|
|
57
|
+
* the payload carries the full currently-available list. Repeated
|
|
58
|
+
* polling of the same latests is silent.
|
|
58
59
|
*/
|
|
59
60
|
EventCategory["UpdateAvailable"] = "update.available";
|
|
60
61
|
/**
|
|
@@ -73,6 +74,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
73
74
|
EventCategory["AddonInstalled"] = "addon.installed";
|
|
74
75
|
EventCategory["AddonUninstalled"] = "addon.uninstalled";
|
|
75
76
|
EventCategory["AddonCrashed"] = "addon.crashed";
|
|
77
|
+
/**
|
|
78
|
+
* A RUNNER the D6 crash circuit-breaker gave up on — terminal.
|
|
79
|
+
*
|
|
80
|
+
* `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
|
|
81
|
+
* respawned"); this is the one that never resolves itself. Emitted exactly
|
|
82
|
+
* once per trip, by `process-service.ts`, carrying the node, the runner, the
|
|
83
|
+
* addons it hosted and the crash count that tripped the breaker.
|
|
84
|
+
*
|
|
85
|
+
* It exists because a runner marked terminally `failed` used to be SILENT:
|
|
86
|
+
* on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
|
|
87
|
+
* errors, the breaker stopped respawning it (correctly), `/health` kept
|
|
88
|
+
* returning 200, and fleet-wide recording was gone for 3h15 before the
|
|
89
|
+
* operator's phone told him. The Notification Center's system-event intake
|
|
90
|
+
* consumes this category and maps it to the `addon-crash-loop` kind.
|
|
91
|
+
*/
|
|
92
|
+
EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
|
|
76
93
|
EventCategory["AddonError"] = "addon.error";
|
|
77
94
|
EventCategory["AddonPageReady"] = "addon.page-ready";
|
|
78
95
|
EventCategory["AddonWidgetReady"] = "addon.widget-ready";
|
|
@@ -7584,6 +7601,10 @@ var RecordingBandSchema = object({
|
|
|
7584
7601
|
preBufferSec: number().min(0).optional(),
|
|
7585
7602
|
postBufferSec: number().min(0).optional()
|
|
7586
7603
|
});
|
|
7604
|
+
({
|
|
7605
|
+
preBufferSec: 10,
|
|
7606
|
+
postBufferSec: 30
|
|
7607
|
+
}).postBufferSec * 1e3;
|
|
7587
7608
|
/**
|
|
7588
7609
|
* Per-device retention overrides. Every field is optional; an unset or `0`
|
|
7589
7610
|
* value inherits the node-wide recorder default. Only footage-lifetime limits
|
|
@@ -7631,6 +7652,12 @@ var RecordingConfigSchema = object({
|
|
|
7631
7652
|
/** DERIVED summary of `bands`, stamped by the recorder on every save.
|
|
7632
7653
|
* Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
|
|
7633
7654
|
mode: RecordingStorageModeSchema.optional(),
|
|
7655
|
+
/**
|
|
7656
|
+
* Which assigned broker slots to record. Absent / empty = {@link
|
|
7657
|
+
* DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
|
|
7658
|
+
* camera's currently assigned slots — never `mid` unless the operator
|
|
7659
|
+
* picks it, and never a slot the broker has not assigned.
|
|
7660
|
+
*/
|
|
7634
7661
|
profiles: array(CamProfileSchema).optional(),
|
|
7635
7662
|
segmentSeconds: number().int().positive().optional(),
|
|
7636
7663
|
/**
|
|
@@ -7711,7 +7738,11 @@ var RelocateFootageInputSchema = object({
|
|
|
7711
7738
|
profiles: array(string()).optional(),
|
|
7712
7739
|
/** Copy throttle in MB/s (default 40) — the drain is a background chore,
|
|
7713
7740
|
* never allowed to starve live writers. */
|
|
7714
|
-
throttleMbps: number().min(1).max(1e3).optional()
|
|
7741
|
+
throttleMbps: number().min(1).max(1e3).optional(),
|
|
7742
|
+
/** Move only segments whose startMs is >= this. Absent = the whole source
|
|
7743
|
+
* pile. Used when a full drain is too expensive and the operator only
|
|
7744
|
+
* wants the recent window on the new disk. */
|
|
7745
|
+
sinceMs: number().int().optional()
|
|
7715
7746
|
});
|
|
7716
7747
|
/** Internal, lease-scoped participant operation. It is intentionally separate
|
|
7717
7748
|
* from persistent recording settings: a migration never changes
|
|
@@ -14593,6 +14624,7 @@ var NcSystemEventKindSchema = _enum([
|
|
|
14593
14624
|
"node-offline",
|
|
14594
14625
|
"node-inference-unavailable",
|
|
14595
14626
|
"detection-blind",
|
|
14627
|
+
"addon-crash-loop",
|
|
14596
14628
|
"addon-update-available",
|
|
14597
14629
|
"server-update-available",
|
|
14598
14630
|
"alarm-triggered",
|
|
@@ -14600,6 +14632,9 @@ var NcSystemEventKindSchema = _enum([
|
|
|
14600
14632
|
"alarm-disarmed",
|
|
14601
14633
|
"alarm-arming",
|
|
14602
14634
|
"alarm-arm-refused",
|
|
14635
|
+
"addon-updated",
|
|
14636
|
+
"server-updated",
|
|
14637
|
+
"export-completed",
|
|
14603
14638
|
"camera-online",
|
|
14604
14639
|
"camera-offline",
|
|
14605
14640
|
"camera-disabled",
|
|
@@ -16493,13 +16528,19 @@ var RetrainStatusSchema = _enum([
|
|
|
16493
16528
|
* "never marked" from "already trained" must read `retrainStatus`.
|
|
16494
16529
|
*
|
|
16495
16530
|
* `debug` does NOT pin; it is attention, not durability.
|
|
16531
|
+
*
|
|
16532
|
+
* `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
|
|
16533
|
+
* A favourited track is skipped by retention the same way `staging` is, but
|
|
16534
|
+
* it does not enter `none|staging|trained` and has no staging budget.
|
|
16496
16535
|
*/
|
|
16497
16536
|
var TrackFlagFields = {
|
|
16498
16537
|
/** Operator marked this track as training material — i.e. `retrainStatus` is
|
|
16499
16538
|
* `'staging'`. */
|
|
16500
16539
|
markForTrain: boolean().optional(),
|
|
16501
16540
|
/** Operator marked this track for diagnostic attention. */
|
|
16502
|
-
debug: boolean().optional()
|
|
16541
|
+
debug: boolean().optional(),
|
|
16542
|
+
/** Operator favourited this track. Pins it against pruning. */
|
|
16543
|
+
favourited: boolean().optional()
|
|
16503
16544
|
};
|
|
16504
16545
|
/**
|
|
16505
16546
|
* The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
|
|
@@ -16524,6 +16565,7 @@ var TrackFlagsSchema = object({
|
|
|
16524
16565
|
trackId: string(),
|
|
16525
16566
|
markForTrain: boolean(),
|
|
16526
16567
|
debug: boolean(),
|
|
16568
|
+
favourited: boolean(),
|
|
16527
16569
|
/** The lifecycle state the boolean was derived from. Required here (unlike on
|
|
16528
16570
|
* a track row) because this shape is only ever produced by the write body,
|
|
16529
16571
|
* which always knows it — and a surface that has just written needs to render
|
|
@@ -17156,6 +17198,12 @@ var TrackCascadeCountsSchema = object({
|
|
|
17156
17198
|
/** Per-track CLIP search vectors removed (best-effort). */
|
|
17157
17199
|
embeddings: number().int()
|
|
17158
17200
|
});
|
|
17201
|
+
/** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
|
|
17202
|
+
var DiskReconcileCountsSchema = object({
|
|
17203
|
+
mediaDropped: number().int(),
|
|
17204
|
+
tracks: number().int(),
|
|
17205
|
+
events: number().int()
|
|
17206
|
+
});
|
|
17159
17207
|
/** Event-store footprint for one camera. */
|
|
17160
17208
|
var EventStoreDeviceFootprintSchema = object({
|
|
17161
17209
|
deviceId: number(),
|
|
@@ -17332,6 +17380,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
17332
17380
|
}), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
|
|
17333
17381
|
kind: "mutation",
|
|
17334
17382
|
auth: "admin"
|
|
17383
|
+
}), method(object({ deviceId: number() }), DiskReconcileCountsSchema, {
|
|
17384
|
+
kind: "mutation",
|
|
17385
|
+
auth: "admin"
|
|
17335
17386
|
}), method(object({
|
|
17336
17387
|
deviceId: number(),
|
|
17337
17388
|
trackIds: array(string()).min(1)
|
|
@@ -18882,6 +18933,24 @@ var CameraStatusDegradationSchema = object({
|
|
|
18882
18933
|
*
|
|
18883
18934
|
* See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
|
|
18884
18935
|
*/
|
|
18936
|
+
var DiskReconcileJobSchema = object({
|
|
18937
|
+
state: _enum([
|
|
18938
|
+
"idle",
|
|
18939
|
+
"running",
|
|
18940
|
+
"done",
|
|
18941
|
+
"error"
|
|
18942
|
+
]),
|
|
18943
|
+
total: number().int().nonnegative(),
|
|
18944
|
+
completed: number().int().nonnegative(),
|
|
18945
|
+
currentDeviceId: number().int().nullable(),
|
|
18946
|
+
failed: array(number().int()).readonly(),
|
|
18947
|
+
mediaDropped: number().int().nonnegative(),
|
|
18948
|
+
tracks: number().int().nonnegative(),
|
|
18949
|
+
events: number().int().nonnegative(),
|
|
18950
|
+
startedAtMs: number().int().nullable(),
|
|
18951
|
+
finishedAtMs: number().int().nullable(),
|
|
18952
|
+
error: string().nullable()
|
|
18953
|
+
});
|
|
18885
18954
|
var CameraStatusSchema = object({
|
|
18886
18955
|
deviceId: number(),
|
|
18887
18956
|
assignment: CameraAssignmentStatusSchema,
|
|
@@ -19113,7 +19182,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
|
|
|
19113
19182
|
}), CameraSwitchGroupSchema, {
|
|
19114
19183
|
kind: "mutation",
|
|
19115
19184
|
auth: "admin"
|
|
19116
|
-
}), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(),
|
|
19185
|
+
}), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
|
|
19186
|
+
kind: "mutation",
|
|
19187
|
+
auth: "admin"
|
|
19188
|
+
}), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
|
|
19117
19189
|
name: string(),
|
|
19118
19190
|
description: string().optional(),
|
|
19119
19191
|
config: CameraPipelineConfigSchema
|
|
@@ -19518,7 +19590,14 @@ targets: array(object({
|
|
|
19518
19590
|
"sleeping",
|
|
19519
19591
|
"unreachable",
|
|
19520
19592
|
"waking"
|
|
19521
|
-
]).nullable()
|
|
19593
|
+
]).nullable(),
|
|
19594
|
+
/** A battery camera (whatever its current state). An AWAKE battery
|
|
19595
|
+
* camera is deliberately NOT recaptured on the poll cadence — every
|
|
19596
|
+
* capture is a camera hit that would keep it out of sleep — so its
|
|
19597
|
+
* cached frame legitimately ages past the currency ceiling while
|
|
19598
|
+
* nothing is streaming. A surface must keep painting it (a fresh
|
|
19599
|
+
* frame is captured at each wake), not blank to "unavailable". */
|
|
19600
|
+
battery: boolean()
|
|
19522
19601
|
})));
|
|
19523
19602
|
/**
|
|
19524
19603
|
* `sso-bridge` — internal hub-only cap that lets SSO-style auth
|
|
@@ -20179,20 +20258,6 @@ var VectorStatsResultSchema = object({
|
|
|
20179
20258
|
exact: boolean()
|
|
20180
20259
|
});
|
|
20181
20260
|
method(VectorDeclareIndexInputSchema, _void(), { kind: "mutation" }), method(VectorUpsertInputSchema, VectorUpsertResultSchema, { kind: "mutation" }), method(VectorQueryInputSchema, VectorQueryResultSchema), method(VectorGetInputSchema, VectorGetResultSchema), method(VectorDeleteInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorStatsInputSchema, VectorStatsResultSchema);
|
|
20182
|
-
/**
|
|
20183
|
-
* `videoclips` — the unified, navigable-clip surface for a camera.
|
|
20184
|
-
*
|
|
20185
|
-
* A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
|
|
20186
|
-
* provider per device, substitutable. The DEFAULT provider (registered by
|
|
20187
|
-
* `addon-post-analysis`, `defaultActive: true`) composes the analytics event
|
|
20188
|
-
* log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
|
|
20189
|
-
* is a time-WINDOW over existing footage, never a separate file. A camera that
|
|
20190
|
-
* exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
|
|
20191
|
-
* wrapper provider for its device and serve its own clip catalog + URLs.
|
|
20192
|
-
*
|
|
20193
|
-
* A `Clip` is purely time-based (subtree-blind): playback resolves segments by
|
|
20194
|
-
* temporal overlap, so the API never decides `continuous` vs `events`.
|
|
20195
|
-
*/
|
|
20196
20261
|
var ClipSchema = object({
|
|
20197
20262
|
/** Opaque, provider-namespaced id. The default provider encodes the time
|
|
20198
20263
|
* window so `getClipPlayback` is self-contained (no event re-query). */
|
|
@@ -20209,8 +20274,57 @@ var ClipSchema = object({
|
|
|
20209
20274
|
startMs: number(),
|
|
20210
20275
|
endMs: number()
|
|
20211
20276
|
}),
|
|
20212
|
-
/**
|
|
20213
|
-
thumbnail
|
|
20277
|
+
/**
|
|
20278
|
+
* Lazy thumbnail URL, never inlined.
|
|
20279
|
+
*
|
|
20280
|
+
* Recording-derived clips (events-mode keep-window, and the prepared
|
|
20281
|
+
* continuous event+fragment visit) MUST use the snapshot of the **main
|
|
20282
|
+
* event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
|
|
20283
|
+
* of the event that owns `kind` (object > motion > audio). Do not extract
|
|
20284
|
+
* a keyframe from the recorded segments. Other providers (onboard, HKSV)
|
|
20285
|
+
* mint their own stills.
|
|
20286
|
+
*
|
|
20287
|
+
* **VOUCHED, never fabricated.** A provider emits this only for an event it
|
|
20288
|
+
* has CONFIRMED owns at least one media row (its own, or its owning track's).
|
|
20289
|
+
* Absent is meaningful — "this visit has no event still" — never "we did not
|
|
20290
|
+
* look". Stamping it from a URL template made 35% of one camera's clips point
|
|
20291
|
+
* at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
|
|
20292
|
+
* A read that FAILS drops the claim; it never invents it.
|
|
20293
|
+
*/
|
|
20294
|
+
thumbnail: string().optional(),
|
|
20295
|
+
/**
|
|
20296
|
+
* An instant INSIDE this visit's footage, hole-safe, where a recorded still
|
|
20297
|
+
* can be decoded. Present whenever the visit came from recorded availability;
|
|
20298
|
+
* absent on a per-event padded window (there is no footage to promise).
|
|
20299
|
+
*
|
|
20300
|
+
* This is not a thumbnail and not a second byte path: it is the argument to
|
|
20301
|
+
* the recorder's existing still route. The surface — never the provider —
|
|
20302
|
+
* decides whether to use it. It is the midpoint of the visit's LONGEST
|
|
20303
|
+
* contiguous range, not of the visit: a visit spans its holes by
|
|
20304
|
+
* construction, so a naive midpoint lands in dead air.
|
|
20305
|
+
*/
|
|
20306
|
+
stillAtMs: number().optional(),
|
|
20307
|
+
/**
|
|
20308
|
+
* Analytics event ids that overlap this visit — a BOUNDED sample, newest
|
|
20309
|
+
* first within kind (object → motion → audio), capped at
|
|
20310
|
+
* {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
|
|
20311
|
+
*
|
|
20312
|
+
* Bounded because it is not a payload the surface pages through: one visit on
|
|
20313
|
+
* device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
|
|
20314
|
+
* camera-day. Read {@link eventCount} for the true total.
|
|
20315
|
+
*/
|
|
20316
|
+
eventIds: array(string()).optional(),
|
|
20317
|
+
/** How many analytics events actually overlap this visit. Differs from
|
|
20318
|
+
* `eventIds.length` exactly when the sample was capped — so a truncated
|
|
20319
|
+
* list is never mistaken for a quiet visit. */
|
|
20320
|
+
eventCount: number().int().nonnegative().optional(),
|
|
20321
|
+
/** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
|
|
20322
|
+
* than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
|
|
20323
|
+
* bar keeps showing them via `recording.getAvailability`. */
|
|
20324
|
+
holes: array(object({
|
|
20325
|
+
startMs: number(),
|
|
20326
|
+
endMs: number()
|
|
20327
|
+
})).optional()
|
|
20214
20328
|
});
|
|
20215
20329
|
var ClipPlaybackSchema = object({
|
|
20216
20330
|
/** HLS master URL through the hub data-plane (Range + token in path). */
|
|
@@ -20674,7 +20788,14 @@ var SearchResultSchema = object({
|
|
|
20674
20788
|
});
|
|
20675
20789
|
var AutoUpdateSettingsSchema = object({
|
|
20676
20790
|
channel: ChannelSchema,
|
|
20677
|
-
intervalSeconds: number()
|
|
20791
|
+
intervalSeconds: number(),
|
|
20792
|
+
/**
|
|
20793
|
+
* Cadence of the "an update exists" POLLER, in seconds. Independent of
|
|
20794
|
+
* `channel`: the poller runs while auto-apply is `off`, because being told
|
|
20795
|
+
* about a publish and installing it are different decisions. Clamped
|
|
20796
|
+
* server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
|
|
20797
|
+
*/
|
|
20798
|
+
updateCheckIntervalSeconds: number()
|
|
20678
20799
|
});
|
|
20679
20800
|
var AddonAutoUpdateSchema = ChannelWithInheritSchema;
|
|
20680
20801
|
var RestartAddonResultSchema = unknown();
|
|
@@ -20815,7 +20936,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
|
|
|
20815
20936
|
auth: "admin"
|
|
20816
20937
|
}), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
|
|
20817
20938
|
channel: ChannelSchema,
|
|
20818
|
-
intervalSeconds: number().min(300).max(86400).optional()
|
|
20939
|
+
intervalSeconds: number().min(300).max(86400).optional(),
|
|
20940
|
+
/** Availability-poll cadence; see AutoUpdateSettingsSchema. */
|
|
20941
|
+
updateCheckIntervalSeconds: number().min(900).max(604800).optional()
|
|
20819
20942
|
}), unknown(), {
|
|
20820
20943
|
kind: "mutation",
|
|
20821
20944
|
auth: "admin"
|
|
@@ -26504,7 +26627,9 @@ var ExportOptionsSchema = object({
|
|
|
26504
26627
|
includeAudio: boolean(),
|
|
26505
26628
|
maxLifeMs: number().int().positive(),
|
|
26506
26629
|
deleteAfterDownload: boolean(),
|
|
26507
|
-
title: string().max(200).optional()
|
|
26630
|
+
title: string().max(200).optional(),
|
|
26631
|
+
/** Notification-output target ids to ping when this export becomes ready. */
|
|
26632
|
+
notifyTargetIds: array(string().min(1)).max(20).optional()
|
|
26508
26633
|
}).superRefine((v, ctx) => {
|
|
26509
26634
|
if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
|
|
26510
26635
|
code: ZodIssueCode.custom,
|
|
@@ -26563,10 +26688,18 @@ var ExportBytesSchema = object({
|
|
|
26563
26688
|
});
|
|
26564
26689
|
method(object({
|
|
26565
26690
|
deviceId: number(),
|
|
26566
|
-
|
|
26691
|
+
/** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
|
|
26692
|
+
profile: string().optional(),
|
|
26693
|
+
profiles: array(string()).min(1).optional(),
|
|
26567
26694
|
fromMs: number(),
|
|
26568
26695
|
toMs: number(),
|
|
26569
26696
|
options: ExportOptionsSchema
|
|
26697
|
+
}).superRefine((v, ctx) => {
|
|
26698
|
+
if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
|
|
26699
|
+
code: ZodIssueCode.custom,
|
|
26700
|
+
message: "pass profiles[] (min 1) or legacy profile",
|
|
26701
|
+
path: ["profiles"]
|
|
26702
|
+
});
|
|
26570
26703
|
}), ExportRecordSchema, {
|
|
26571
26704
|
kind: "mutation",
|
|
26572
26705
|
auth: "protected"
|
|
@@ -29508,15 +29641,6 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
29508
29641
|
labels: ["probe not implemented"]
|
|
29509
29642
|
};
|
|
29510
29643
|
}
|
|
29511
|
-
/**
|
|
29512
|
-
* Top-level devices restored at once in {@link onRestoreDevices}.
|
|
29513
|
-
*
|
|
29514
|
-
* Four covers the fleets this ships to without turning a boot into a burst a
|
|
29515
|
-
* camera NVR answers with a refusal. A provider whose upstream is a single
|
|
29516
|
-
* session with a serial command channel (a Baichuan hub, an NVR that
|
|
29517
|
-
* serialises ISAPI) should lower it; nothing needs to raise it.
|
|
29518
|
-
*/
|
|
29519
|
-
restoreConcurrency = 4;
|
|
29520
29644
|
async restoreDevices(savedDevices) {
|
|
29521
29645
|
await this.onRestoreDevices(savedDevices);
|
|
29522
29646
|
if (savedDevices.length > 0) this.ctx.logger.info(`Restored ${savedDevices.length} ${this.providerName} device(s)`);
|
|
@@ -29571,14 +29695,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
29571
29695
|
});
|
|
29572
29696
|
}
|
|
29573
29697
|
};
|
|
29574
|
-
|
|
29575
|
-
await Promise.all(Array.from({ length: Math.min(Math.max(1, this.restoreConcurrency), topLevel.length) }, async () => {
|
|
29576
|
-
for (;;) {
|
|
29577
|
-
const saved = topLevel[nextTopLevel++];
|
|
29578
|
-
if (saved === void 0) return;
|
|
29579
|
-
await restoreOne(saved);
|
|
29580
|
-
}
|
|
29581
|
-
}));
|
|
29698
|
+
await Promise.all(topLevel.map((saved) => restoreOne(saved)));
|
|
29582
29699
|
const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
|
|
29583
29700
|
for (const saved of childRows) {
|
|
29584
29701
|
const Class = this.deviceClasses[saved.type];
|
|
@@ -32851,6 +32968,12 @@ Object.freeze({
|
|
|
32851
32968
|
addonId: null,
|
|
32852
32969
|
access: "create"
|
|
32853
32970
|
},
|
|
32971
|
+
"pipelineAnalytics.reconcileFromDisk": {
|
|
32972
|
+
capName: "pipeline-analytics",
|
|
32973
|
+
capScope: "device",
|
|
32974
|
+
addonId: null,
|
|
32975
|
+
access: "create"
|
|
32976
|
+
},
|
|
32854
32977
|
"pipelineAnalytics.refreshStorageLocationsForMigration": {
|
|
32855
32978
|
capName: "pipeline-analytics",
|
|
32856
32979
|
capScope: "device",
|
|
@@ -33253,6 +33376,12 @@ Object.freeze({
|
|
|
33253
33376
|
addonId: null,
|
|
33254
33377
|
access: "view"
|
|
33255
33378
|
},
|
|
33379
|
+
"pipelineOrchestrator.getReconcileFromDiskStatus": {
|
|
33380
|
+
capName: "pipeline-orchestrator",
|
|
33381
|
+
capScope: "system",
|
|
33382
|
+
addonId: null,
|
|
33383
|
+
access: "view"
|
|
33384
|
+
},
|
|
33256
33385
|
"pipelineOrchestrator.listAgentSettings": {
|
|
33257
33386
|
capName: "pipeline-orchestrator",
|
|
33258
33387
|
capScope: "system",
|
|
@@ -33277,6 +33406,12 @@ Object.freeze({
|
|
|
33277
33406
|
addonId: null,
|
|
33278
33407
|
access: "create"
|
|
33279
33408
|
},
|
|
33409
|
+
"pipelineOrchestrator.reconcileFromDisk": {
|
|
33410
|
+
capName: "pipeline-orchestrator",
|
|
33411
|
+
capScope: "system",
|
|
33412
|
+
addonId: null,
|
|
33413
|
+
access: "create"
|
|
33414
|
+
},
|
|
33280
33415
|
"pipelineOrchestrator.removeAgentSettings": {
|
|
33281
33416
|
capName: "pipeline-orchestrator",
|
|
33282
33417
|
capScope: "system",
|
|
@@ -36268,6 +36403,11 @@ Object.freeze({
|
|
|
36268
36403
|
form: "single",
|
|
36269
36404
|
optional: true
|
|
36270
36405
|
}],
|
|
36406
|
+
"pipelineAnalytics.reconcileFromDisk": [{
|
|
36407
|
+
name: "deviceId",
|
|
36408
|
+
form: "single",
|
|
36409
|
+
optional: false
|
|
36410
|
+
}],
|
|
36271
36411
|
"pipelineAnalytics.restageRetrainTrack": [{
|
|
36272
36412
|
name: "deviceId",
|
|
36273
36413
|
form: "single",
|
|
@@ -37333,6 +37473,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
|
|
|
37333
37473
|
DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
|
|
37334
37474
|
DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
|
|
37335
37475
|
DEFAULT_NATIVE_LEASE_SETTINGS.admission;
|
|
37476
|
+
var MB = 1024 * 1024;
|
|
37477
|
+
1024 * MB, 3072 * MB;
|
|
37336
37478
|
//#endregion
|
|
37337
37479
|
//#region src/config.ts
|
|
37338
37480
|
/**
|
package/dist/addon.mjs
CHANGED
|
@@ -36,7 +36,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
36
36
|
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
37
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
38
38
|
//#endregion
|
|
39
|
-
//#region ../types/dist/event-category-
|
|
39
|
+
//#region ../types/dist/event-category-XfKNtfCc.mjs
|
|
40
40
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
41
41
|
EventCategory["SystemBoot"] = "system.boot";
|
|
42
42
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -53,9 +53,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
53
53
|
EventCategory["SystemRestartCompleted"] = "system.restart-completed";
|
|
54
54
|
/**
|
|
55
55
|
* A newer addon or server-root package version was found by the
|
|
56
|
-
* authoritative registry check. Emitted once
|
|
57
|
-
* `(
|
|
58
|
-
*
|
|
56
|
+
* authoritative registry check. Emitted once when any observed
|
|
57
|
+
* `latestVersion` changes (or a package/node first appears behind);
|
|
58
|
+
* the payload carries the full currently-available list. Repeated
|
|
59
|
+
* polling of the same latests is silent.
|
|
59
60
|
*/
|
|
60
61
|
EventCategory["UpdateAvailable"] = "update.available";
|
|
61
62
|
/**
|
|
@@ -74,6 +75,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
74
75
|
EventCategory["AddonInstalled"] = "addon.installed";
|
|
75
76
|
EventCategory["AddonUninstalled"] = "addon.uninstalled";
|
|
76
77
|
EventCategory["AddonCrashed"] = "addon.crashed";
|
|
78
|
+
/**
|
|
79
|
+
* A RUNNER the D6 crash circuit-breaker gave up on — terminal.
|
|
80
|
+
*
|
|
81
|
+
* `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
|
|
82
|
+
* respawned"); this is the one that never resolves itself. Emitted exactly
|
|
83
|
+
* once per trip, by `process-service.ts`, carrying the node, the runner, the
|
|
84
|
+
* addons it hosted and the crash count that tripped the breaker.
|
|
85
|
+
*
|
|
86
|
+
* It exists because a runner marked terminally `failed` used to be SILENT:
|
|
87
|
+
* on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
|
|
88
|
+
* errors, the breaker stopped respawning it (correctly), `/health` kept
|
|
89
|
+
* returning 200, and fleet-wide recording was gone for 3h15 before the
|
|
90
|
+
* operator's phone told him. The Notification Center's system-event intake
|
|
91
|
+
* consumes this category and maps it to the `addon-crash-loop` kind.
|
|
92
|
+
*/
|
|
93
|
+
EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
|
|
77
94
|
EventCategory["AddonError"] = "addon.error";
|
|
78
95
|
EventCategory["AddonPageReady"] = "addon.page-ready";
|
|
79
96
|
EventCategory["AddonWidgetReady"] = "addon.widget-ready";
|
|
@@ -7585,6 +7602,10 @@ var RecordingBandSchema = object({
|
|
|
7585
7602
|
preBufferSec: number().min(0).optional(),
|
|
7586
7603
|
postBufferSec: number().min(0).optional()
|
|
7587
7604
|
});
|
|
7605
|
+
({
|
|
7606
|
+
preBufferSec: 10,
|
|
7607
|
+
postBufferSec: 30
|
|
7608
|
+
}).postBufferSec * 1e3;
|
|
7588
7609
|
/**
|
|
7589
7610
|
* Per-device retention overrides. Every field is optional; an unset or `0`
|
|
7590
7611
|
* value inherits the node-wide recorder default. Only footage-lifetime limits
|
|
@@ -7632,6 +7653,12 @@ var RecordingConfigSchema = object({
|
|
|
7632
7653
|
/** DERIVED summary of `bands`, stamped by the recorder on every save.
|
|
7633
7654
|
* Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
|
|
7634
7655
|
mode: RecordingStorageModeSchema.optional(),
|
|
7656
|
+
/**
|
|
7657
|
+
* Which assigned broker slots to record. Absent / empty = {@link
|
|
7658
|
+
* DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
|
|
7659
|
+
* camera's currently assigned slots — never `mid` unless the operator
|
|
7660
|
+
* picks it, and never a slot the broker has not assigned.
|
|
7661
|
+
*/
|
|
7635
7662
|
profiles: array(CamProfileSchema).optional(),
|
|
7636
7663
|
segmentSeconds: number().int().positive().optional(),
|
|
7637
7664
|
/**
|
|
@@ -7712,7 +7739,11 @@ var RelocateFootageInputSchema = object({
|
|
|
7712
7739
|
profiles: array(string()).optional(),
|
|
7713
7740
|
/** Copy throttle in MB/s (default 40) — the drain is a background chore,
|
|
7714
7741
|
* never allowed to starve live writers. */
|
|
7715
|
-
throttleMbps: number().min(1).max(1e3).optional()
|
|
7742
|
+
throttleMbps: number().min(1).max(1e3).optional(),
|
|
7743
|
+
/** Move only segments whose startMs is >= this. Absent = the whole source
|
|
7744
|
+
* pile. Used when a full drain is too expensive and the operator only
|
|
7745
|
+
* wants the recent window on the new disk. */
|
|
7746
|
+
sinceMs: number().int().optional()
|
|
7716
7747
|
});
|
|
7717
7748
|
/** Internal, lease-scoped participant operation. It is intentionally separate
|
|
7718
7749
|
* from persistent recording settings: a migration never changes
|
|
@@ -14594,6 +14625,7 @@ var NcSystemEventKindSchema = _enum([
|
|
|
14594
14625
|
"node-offline",
|
|
14595
14626
|
"node-inference-unavailable",
|
|
14596
14627
|
"detection-blind",
|
|
14628
|
+
"addon-crash-loop",
|
|
14597
14629
|
"addon-update-available",
|
|
14598
14630
|
"server-update-available",
|
|
14599
14631
|
"alarm-triggered",
|
|
@@ -14601,6 +14633,9 @@ var NcSystemEventKindSchema = _enum([
|
|
|
14601
14633
|
"alarm-disarmed",
|
|
14602
14634
|
"alarm-arming",
|
|
14603
14635
|
"alarm-arm-refused",
|
|
14636
|
+
"addon-updated",
|
|
14637
|
+
"server-updated",
|
|
14638
|
+
"export-completed",
|
|
14604
14639
|
"camera-online",
|
|
14605
14640
|
"camera-offline",
|
|
14606
14641
|
"camera-disabled",
|
|
@@ -16494,13 +16529,19 @@ var RetrainStatusSchema = _enum([
|
|
|
16494
16529
|
* "never marked" from "already trained" must read `retrainStatus`.
|
|
16495
16530
|
*
|
|
16496
16531
|
* `debug` does NOT pin; it is attention, not durability.
|
|
16532
|
+
*
|
|
16533
|
+
* `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
|
|
16534
|
+
* A favourited track is skipped by retention the same way `staging` is, but
|
|
16535
|
+
* it does not enter `none|staging|trained` and has no staging budget.
|
|
16497
16536
|
*/
|
|
16498
16537
|
var TrackFlagFields = {
|
|
16499
16538
|
/** Operator marked this track as training material — i.e. `retrainStatus` is
|
|
16500
16539
|
* `'staging'`. */
|
|
16501
16540
|
markForTrain: boolean().optional(),
|
|
16502
16541
|
/** Operator marked this track for diagnostic attention. */
|
|
16503
|
-
debug: boolean().optional()
|
|
16542
|
+
debug: boolean().optional(),
|
|
16543
|
+
/** Operator favourited this track. Pins it against pruning. */
|
|
16544
|
+
favourited: boolean().optional()
|
|
16504
16545
|
};
|
|
16505
16546
|
/**
|
|
16506
16547
|
* The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
|
|
@@ -16525,6 +16566,7 @@ var TrackFlagsSchema = object({
|
|
|
16525
16566
|
trackId: string(),
|
|
16526
16567
|
markForTrain: boolean(),
|
|
16527
16568
|
debug: boolean(),
|
|
16569
|
+
favourited: boolean(),
|
|
16528
16570
|
/** The lifecycle state the boolean was derived from. Required here (unlike on
|
|
16529
16571
|
* a track row) because this shape is only ever produced by the write body,
|
|
16530
16572
|
* which always knows it — and a surface that has just written needs to render
|
|
@@ -17157,6 +17199,12 @@ var TrackCascadeCountsSchema = object({
|
|
|
17157
17199
|
/** Per-track CLIP search vectors removed (best-effort). */
|
|
17158
17200
|
embeddings: number().int()
|
|
17159
17201
|
});
|
|
17202
|
+
/** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
|
|
17203
|
+
var DiskReconcileCountsSchema = object({
|
|
17204
|
+
mediaDropped: number().int(),
|
|
17205
|
+
tracks: number().int(),
|
|
17206
|
+
events: number().int()
|
|
17207
|
+
});
|
|
17160
17208
|
/** Event-store footprint for one camera. */
|
|
17161
17209
|
var EventStoreDeviceFootprintSchema = object({
|
|
17162
17210
|
deviceId: number(),
|
|
@@ -17333,6 +17381,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
17333
17381
|
}), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
|
|
17334
17382
|
kind: "mutation",
|
|
17335
17383
|
auth: "admin"
|
|
17384
|
+
}), method(object({ deviceId: number() }), DiskReconcileCountsSchema, {
|
|
17385
|
+
kind: "mutation",
|
|
17386
|
+
auth: "admin"
|
|
17336
17387
|
}), method(object({
|
|
17337
17388
|
deviceId: number(),
|
|
17338
17389
|
trackIds: array(string()).min(1)
|
|
@@ -18883,6 +18934,24 @@ var CameraStatusDegradationSchema = object({
|
|
|
18883
18934
|
*
|
|
18884
18935
|
* See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
|
|
18885
18936
|
*/
|
|
18937
|
+
var DiskReconcileJobSchema = object({
|
|
18938
|
+
state: _enum([
|
|
18939
|
+
"idle",
|
|
18940
|
+
"running",
|
|
18941
|
+
"done",
|
|
18942
|
+
"error"
|
|
18943
|
+
]),
|
|
18944
|
+
total: number().int().nonnegative(),
|
|
18945
|
+
completed: number().int().nonnegative(),
|
|
18946
|
+
currentDeviceId: number().int().nullable(),
|
|
18947
|
+
failed: array(number().int()).readonly(),
|
|
18948
|
+
mediaDropped: number().int().nonnegative(),
|
|
18949
|
+
tracks: number().int().nonnegative(),
|
|
18950
|
+
events: number().int().nonnegative(),
|
|
18951
|
+
startedAtMs: number().int().nullable(),
|
|
18952
|
+
finishedAtMs: number().int().nullable(),
|
|
18953
|
+
error: string().nullable()
|
|
18954
|
+
});
|
|
18886
18955
|
var CameraStatusSchema = object({
|
|
18887
18956
|
deviceId: number(),
|
|
18888
18957
|
assignment: CameraAssignmentStatusSchema,
|
|
@@ -19114,7 +19183,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
|
|
|
19114
19183
|
}), CameraSwitchGroupSchema, {
|
|
19115
19184
|
kind: "mutation",
|
|
19116
19185
|
auth: "admin"
|
|
19117
|
-
}), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(),
|
|
19186
|
+
}), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
|
|
19187
|
+
kind: "mutation",
|
|
19188
|
+
auth: "admin"
|
|
19189
|
+
}), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
|
|
19118
19190
|
name: string(),
|
|
19119
19191
|
description: string().optional(),
|
|
19120
19192
|
config: CameraPipelineConfigSchema
|
|
@@ -19519,7 +19591,14 @@ targets: array(object({
|
|
|
19519
19591
|
"sleeping",
|
|
19520
19592
|
"unreachable",
|
|
19521
19593
|
"waking"
|
|
19522
|
-
]).nullable()
|
|
19594
|
+
]).nullable(),
|
|
19595
|
+
/** A battery camera (whatever its current state). An AWAKE battery
|
|
19596
|
+
* camera is deliberately NOT recaptured on the poll cadence — every
|
|
19597
|
+
* capture is a camera hit that would keep it out of sleep — so its
|
|
19598
|
+
* cached frame legitimately ages past the currency ceiling while
|
|
19599
|
+
* nothing is streaming. A surface must keep painting it (a fresh
|
|
19600
|
+
* frame is captured at each wake), not blank to "unavailable". */
|
|
19601
|
+
battery: boolean()
|
|
19523
19602
|
})));
|
|
19524
19603
|
/**
|
|
19525
19604
|
* `sso-bridge` — internal hub-only cap that lets SSO-style auth
|
|
@@ -20180,20 +20259,6 @@ var VectorStatsResultSchema = object({
|
|
|
20180
20259
|
exact: boolean()
|
|
20181
20260
|
});
|
|
20182
20261
|
method(VectorDeclareIndexInputSchema, _void(), { kind: "mutation" }), method(VectorUpsertInputSchema, VectorUpsertResultSchema, { kind: "mutation" }), method(VectorQueryInputSchema, VectorQueryResultSchema), method(VectorGetInputSchema, VectorGetResultSchema), method(VectorDeleteInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorStatsInputSchema, VectorStatsResultSchema);
|
|
20183
|
-
/**
|
|
20184
|
-
* `videoclips` — the unified, navigable-clip surface for a camera.
|
|
20185
|
-
*
|
|
20186
|
-
* A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
|
|
20187
|
-
* provider per device, substitutable. The DEFAULT provider (registered by
|
|
20188
|
-
* `addon-post-analysis`, `defaultActive: true`) composes the analytics event
|
|
20189
|
-
* log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
|
|
20190
|
-
* is a time-WINDOW over existing footage, never a separate file. A camera that
|
|
20191
|
-
* exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
|
|
20192
|
-
* wrapper provider for its device and serve its own clip catalog + URLs.
|
|
20193
|
-
*
|
|
20194
|
-
* A `Clip` is purely time-based (subtree-blind): playback resolves segments by
|
|
20195
|
-
* temporal overlap, so the API never decides `continuous` vs `events`.
|
|
20196
|
-
*/
|
|
20197
20262
|
var ClipSchema = object({
|
|
20198
20263
|
/** Opaque, provider-namespaced id. The default provider encodes the time
|
|
20199
20264
|
* window so `getClipPlayback` is self-contained (no event re-query). */
|
|
@@ -20210,8 +20275,57 @@ var ClipSchema = object({
|
|
|
20210
20275
|
startMs: number(),
|
|
20211
20276
|
endMs: number()
|
|
20212
20277
|
}),
|
|
20213
|
-
/**
|
|
20214
|
-
thumbnail
|
|
20278
|
+
/**
|
|
20279
|
+
* Lazy thumbnail URL, never inlined.
|
|
20280
|
+
*
|
|
20281
|
+
* Recording-derived clips (events-mode keep-window, and the prepared
|
|
20282
|
+
* continuous event+fragment visit) MUST use the snapshot of the **main
|
|
20283
|
+
* event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
|
|
20284
|
+
* of the event that owns `kind` (object > motion > audio). Do not extract
|
|
20285
|
+
* a keyframe from the recorded segments. Other providers (onboard, HKSV)
|
|
20286
|
+
* mint their own stills.
|
|
20287
|
+
*
|
|
20288
|
+
* **VOUCHED, never fabricated.** A provider emits this only for an event it
|
|
20289
|
+
* has CONFIRMED owns at least one media row (its own, or its owning track's).
|
|
20290
|
+
* Absent is meaningful — "this visit has no event still" — never "we did not
|
|
20291
|
+
* look". Stamping it from a URL template made 35% of one camera's clips point
|
|
20292
|
+
* at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
|
|
20293
|
+
* A read that FAILS drops the claim; it never invents it.
|
|
20294
|
+
*/
|
|
20295
|
+
thumbnail: string().optional(),
|
|
20296
|
+
/**
|
|
20297
|
+
* An instant INSIDE this visit's footage, hole-safe, where a recorded still
|
|
20298
|
+
* can be decoded. Present whenever the visit came from recorded availability;
|
|
20299
|
+
* absent on a per-event padded window (there is no footage to promise).
|
|
20300
|
+
*
|
|
20301
|
+
* This is not a thumbnail and not a second byte path: it is the argument to
|
|
20302
|
+
* the recorder's existing still route. The surface — never the provider —
|
|
20303
|
+
* decides whether to use it. It is the midpoint of the visit's LONGEST
|
|
20304
|
+
* contiguous range, not of the visit: a visit spans its holes by
|
|
20305
|
+
* construction, so a naive midpoint lands in dead air.
|
|
20306
|
+
*/
|
|
20307
|
+
stillAtMs: number().optional(),
|
|
20308
|
+
/**
|
|
20309
|
+
* Analytics event ids that overlap this visit — a BOUNDED sample, newest
|
|
20310
|
+
* first within kind (object → motion → audio), capped at
|
|
20311
|
+
* {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
|
|
20312
|
+
*
|
|
20313
|
+
* Bounded because it is not a payload the surface pages through: one visit on
|
|
20314
|
+
* device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
|
|
20315
|
+
* camera-day. Read {@link eventCount} for the true total.
|
|
20316
|
+
*/
|
|
20317
|
+
eventIds: array(string()).optional(),
|
|
20318
|
+
/** How many analytics events actually overlap this visit. Differs from
|
|
20319
|
+
* `eventIds.length` exactly when the sample was capped — so a truncated
|
|
20320
|
+
* list is never mistaken for a quiet visit. */
|
|
20321
|
+
eventCount: number().int().nonnegative().optional(),
|
|
20322
|
+
/** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
|
|
20323
|
+
* than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
|
|
20324
|
+
* bar keeps showing them via `recording.getAvailability`. */
|
|
20325
|
+
holes: array(object({
|
|
20326
|
+
startMs: number(),
|
|
20327
|
+
endMs: number()
|
|
20328
|
+
})).optional()
|
|
20215
20329
|
});
|
|
20216
20330
|
var ClipPlaybackSchema = object({
|
|
20217
20331
|
/** HLS master URL through the hub data-plane (Range + token in path). */
|
|
@@ -20675,7 +20789,14 @@ var SearchResultSchema = object({
|
|
|
20675
20789
|
});
|
|
20676
20790
|
var AutoUpdateSettingsSchema = object({
|
|
20677
20791
|
channel: ChannelSchema,
|
|
20678
|
-
intervalSeconds: number()
|
|
20792
|
+
intervalSeconds: number(),
|
|
20793
|
+
/**
|
|
20794
|
+
* Cadence of the "an update exists" POLLER, in seconds. Independent of
|
|
20795
|
+
* `channel`: the poller runs while auto-apply is `off`, because being told
|
|
20796
|
+
* about a publish and installing it are different decisions. Clamped
|
|
20797
|
+
* server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
|
|
20798
|
+
*/
|
|
20799
|
+
updateCheckIntervalSeconds: number()
|
|
20679
20800
|
});
|
|
20680
20801
|
var AddonAutoUpdateSchema = ChannelWithInheritSchema;
|
|
20681
20802
|
var RestartAddonResultSchema = unknown();
|
|
@@ -20816,7 +20937,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
|
|
|
20816
20937
|
auth: "admin"
|
|
20817
20938
|
}), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
|
|
20818
20939
|
channel: ChannelSchema,
|
|
20819
|
-
intervalSeconds: number().min(300).max(86400).optional()
|
|
20940
|
+
intervalSeconds: number().min(300).max(86400).optional(),
|
|
20941
|
+
/** Availability-poll cadence; see AutoUpdateSettingsSchema. */
|
|
20942
|
+
updateCheckIntervalSeconds: number().min(900).max(604800).optional()
|
|
20820
20943
|
}), unknown(), {
|
|
20821
20944
|
kind: "mutation",
|
|
20822
20945
|
auth: "admin"
|
|
@@ -26505,7 +26628,9 @@ var ExportOptionsSchema = object({
|
|
|
26505
26628
|
includeAudio: boolean(),
|
|
26506
26629
|
maxLifeMs: number().int().positive(),
|
|
26507
26630
|
deleteAfterDownload: boolean(),
|
|
26508
|
-
title: string().max(200).optional()
|
|
26631
|
+
title: string().max(200).optional(),
|
|
26632
|
+
/** Notification-output target ids to ping when this export becomes ready. */
|
|
26633
|
+
notifyTargetIds: array(string().min(1)).max(20).optional()
|
|
26509
26634
|
}).superRefine((v, ctx) => {
|
|
26510
26635
|
if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
|
|
26511
26636
|
code: ZodIssueCode.custom,
|
|
@@ -26564,10 +26689,18 @@ var ExportBytesSchema = object({
|
|
|
26564
26689
|
});
|
|
26565
26690
|
method(object({
|
|
26566
26691
|
deviceId: number(),
|
|
26567
|
-
|
|
26692
|
+
/** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
|
|
26693
|
+
profile: string().optional(),
|
|
26694
|
+
profiles: array(string()).min(1).optional(),
|
|
26568
26695
|
fromMs: number(),
|
|
26569
26696
|
toMs: number(),
|
|
26570
26697
|
options: ExportOptionsSchema
|
|
26698
|
+
}).superRefine((v, ctx) => {
|
|
26699
|
+
if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
|
|
26700
|
+
code: ZodIssueCode.custom,
|
|
26701
|
+
message: "pass profiles[] (min 1) or legacy profile",
|
|
26702
|
+
path: ["profiles"]
|
|
26703
|
+
});
|
|
26571
26704
|
}), ExportRecordSchema, {
|
|
26572
26705
|
kind: "mutation",
|
|
26573
26706
|
auth: "protected"
|
|
@@ -29509,15 +29642,6 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
29509
29642
|
labels: ["probe not implemented"]
|
|
29510
29643
|
};
|
|
29511
29644
|
}
|
|
29512
|
-
/**
|
|
29513
|
-
* Top-level devices restored at once in {@link onRestoreDevices}.
|
|
29514
|
-
*
|
|
29515
|
-
* Four covers the fleets this ships to without turning a boot into a burst a
|
|
29516
|
-
* camera NVR answers with a refusal. A provider whose upstream is a single
|
|
29517
|
-
* session with a serial command channel (a Baichuan hub, an NVR that
|
|
29518
|
-
* serialises ISAPI) should lower it; nothing needs to raise it.
|
|
29519
|
-
*/
|
|
29520
|
-
restoreConcurrency = 4;
|
|
29521
29645
|
async restoreDevices(savedDevices) {
|
|
29522
29646
|
await this.onRestoreDevices(savedDevices);
|
|
29523
29647
|
if (savedDevices.length > 0) this.ctx.logger.info(`Restored ${savedDevices.length} ${this.providerName} device(s)`);
|
|
@@ -29572,14 +29696,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
29572
29696
|
});
|
|
29573
29697
|
}
|
|
29574
29698
|
};
|
|
29575
|
-
|
|
29576
|
-
await Promise.all(Array.from({ length: Math.min(Math.max(1, this.restoreConcurrency), topLevel.length) }, async () => {
|
|
29577
|
-
for (;;) {
|
|
29578
|
-
const saved = topLevel[nextTopLevel++];
|
|
29579
|
-
if (saved === void 0) return;
|
|
29580
|
-
await restoreOne(saved);
|
|
29581
|
-
}
|
|
29582
|
-
}));
|
|
29699
|
+
await Promise.all(topLevel.map((saved) => restoreOne(saved)));
|
|
29583
29700
|
const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
|
|
29584
29701
|
for (const saved of childRows) {
|
|
29585
29702
|
const Class = this.deviceClasses[saved.type];
|
|
@@ -32852,6 +32969,12 @@ Object.freeze({
|
|
|
32852
32969
|
addonId: null,
|
|
32853
32970
|
access: "create"
|
|
32854
32971
|
},
|
|
32972
|
+
"pipelineAnalytics.reconcileFromDisk": {
|
|
32973
|
+
capName: "pipeline-analytics",
|
|
32974
|
+
capScope: "device",
|
|
32975
|
+
addonId: null,
|
|
32976
|
+
access: "create"
|
|
32977
|
+
},
|
|
32855
32978
|
"pipelineAnalytics.refreshStorageLocationsForMigration": {
|
|
32856
32979
|
capName: "pipeline-analytics",
|
|
32857
32980
|
capScope: "device",
|
|
@@ -33254,6 +33377,12 @@ Object.freeze({
|
|
|
33254
33377
|
addonId: null,
|
|
33255
33378
|
access: "view"
|
|
33256
33379
|
},
|
|
33380
|
+
"pipelineOrchestrator.getReconcileFromDiskStatus": {
|
|
33381
|
+
capName: "pipeline-orchestrator",
|
|
33382
|
+
capScope: "system",
|
|
33383
|
+
addonId: null,
|
|
33384
|
+
access: "view"
|
|
33385
|
+
},
|
|
33257
33386
|
"pipelineOrchestrator.listAgentSettings": {
|
|
33258
33387
|
capName: "pipeline-orchestrator",
|
|
33259
33388
|
capScope: "system",
|
|
@@ -33278,6 +33407,12 @@ Object.freeze({
|
|
|
33278
33407
|
addonId: null,
|
|
33279
33408
|
access: "create"
|
|
33280
33409
|
},
|
|
33410
|
+
"pipelineOrchestrator.reconcileFromDisk": {
|
|
33411
|
+
capName: "pipeline-orchestrator",
|
|
33412
|
+
capScope: "system",
|
|
33413
|
+
addonId: null,
|
|
33414
|
+
access: "create"
|
|
33415
|
+
},
|
|
33281
33416
|
"pipelineOrchestrator.removeAgentSettings": {
|
|
33282
33417
|
capName: "pipeline-orchestrator",
|
|
33283
33418
|
capScope: "system",
|
|
@@ -36269,6 +36404,11 @@ Object.freeze({
|
|
|
36269
36404
|
form: "single",
|
|
36270
36405
|
optional: true
|
|
36271
36406
|
}],
|
|
36407
|
+
"pipelineAnalytics.reconcileFromDisk": [{
|
|
36408
|
+
name: "deviceId",
|
|
36409
|
+
form: "single",
|
|
36410
|
+
optional: false
|
|
36411
|
+
}],
|
|
36272
36412
|
"pipelineAnalytics.restageRetrainTrack": [{
|
|
36273
36413
|
name: "deviceId",
|
|
36274
36414
|
form: "single",
|
|
@@ -37334,6 +37474,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
|
|
|
37334
37474
|
DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
|
|
37335
37475
|
DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
|
|
37336
37476
|
DEFAULT_NATIVE_LEASE_SETTINGS.admission;
|
|
37477
|
+
var MB = 1024 * 1024;
|
|
37478
|
+
1024 * MB, 3072 * MB;
|
|
37337
37479
|
//#endregion
|
|
37338
37480
|
//#region src/config.ts
|
|
37339
37481
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-dreo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.21",
|
|
4
4
|
"description": "Dreo smart-device (fan / air-circulator / purifier / heater / humidifier) device-provider addon for CamStack — wraps the @apocaliss92/nodedreo Dreo cloud client (REST + WebSocket)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|