@camstack/addon-decoder-nodeav 1.2.18 → 1.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +186 -28
  2. package/dist/index.mjs +186 -28
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  let node_crypto = require("node:crypto");
6
- //#region ../types/dist/event-category-Bxo5yJjt.mjs
6
+ //#region ../types/dist/event-category-XfKNtfCc.mjs
7
7
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
8
8
  EventCategory["SystemBoot"] = "system.boot";
9
9
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -20,9 +20,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
20
20
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
21
21
  /**
22
22
  * A newer addon or server-root package version was found by the
23
- * authoritative registry check. Emitted once per
24
- * `(target, packageName, currentVersion, latestVersion)` transition; repeated
25
- * polling of the same result is deduplicated by the checker.
23
+ * authoritative registry check. Emitted once when any observed
24
+ * `latestVersion` changes (or a package/node first appears behind);
25
+ * the payload carries the full currently-available list. Repeated
26
+ * polling of the same latests is silent.
26
27
  */
27
28
  EventCategory["UpdateAvailable"] = "update.available";
28
29
  /**
@@ -41,6 +42,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
41
42
  EventCategory["AddonInstalled"] = "addon.installed";
42
43
  EventCategory["AddonUninstalled"] = "addon.uninstalled";
43
44
  EventCategory["AddonCrashed"] = "addon.crashed";
45
+ /**
46
+ * A RUNNER the D6 crash circuit-breaker gave up on — terminal.
47
+ *
48
+ * `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
49
+ * respawned"); this is the one that never resolves itself. Emitted exactly
50
+ * once per trip, by `process-service.ts`, carrying the node, the runner, the
51
+ * addons it hosted and the crash count that tripped the breaker.
52
+ *
53
+ * It exists because a runner marked terminally `failed` used to be SILENT:
54
+ * on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
55
+ * errors, the breaker stopped respawning it (correctly), `/health` kept
56
+ * returning 200, and fleet-wide recording was gone for 3h15 before the
57
+ * operator's phone told him. The Notification Center's system-event intake
58
+ * consumes this category and maps it to the `addon-crash-loop` kind.
59
+ */
60
+ EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
44
61
  EventCategory["AddonError"] = "addon.error";
45
62
  EventCategory["AddonPageReady"] = "addon.page-ready";
46
63
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -7548,6 +7565,10 @@ var RecordingBandSchema = object({
7548
7565
  preBufferSec: number().min(0).optional(),
7549
7566
  postBufferSec: number().min(0).optional()
7550
7567
  });
7568
+ ({
7569
+ preBufferSec: 10,
7570
+ postBufferSec: 30
7571
+ }).postBufferSec * 1e3;
7551
7572
  /**
7552
7573
  * Per-device retention overrides. Every field is optional; an unset or `0`
7553
7574
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -7595,6 +7616,12 @@ var RecordingConfigSchema = object({
7595
7616
  /** DERIVED summary of `bands`, stamped by the recorder on every save.
7596
7617
  * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7597
7618
  mode: RecordingStorageModeSchema.optional(),
7619
+ /**
7620
+ * Which assigned broker slots to record. Absent / empty = {@link
7621
+ * DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
7622
+ * camera's currently assigned slots — never `mid` unless the operator
7623
+ * picks it, and never a slot the broker has not assigned.
7624
+ */
7598
7625
  profiles: array(CamProfileSchema).optional(),
7599
7626
  segmentSeconds: number().int().positive().optional(),
7600
7627
  /**
@@ -7675,7 +7702,11 @@ var RelocateFootageInputSchema = object({
7675
7702
  profiles: array(string()).optional(),
7676
7703
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7677
7704
  * never allowed to starve live writers. */
7678
- throttleMbps: number().min(1).max(1e3).optional()
7705
+ throttleMbps: number().min(1).max(1e3).optional(),
7706
+ /** Move only segments whose startMs is >= this. Absent = the whole source
7707
+ * pile. Used when a full drain is too expensive and the operator only
7708
+ * wants the recent window on the new disk. */
7709
+ sinceMs: number().int().optional()
7679
7710
  });
7680
7711
  /** Internal, lease-scoped participant operation. It is intentionally separate
7681
7712
  * from persistent recording settings: a migration never changes
@@ -14368,6 +14399,7 @@ var NcSystemEventKindSchema = _enum([
14368
14399
  "node-offline",
14369
14400
  "node-inference-unavailable",
14370
14401
  "detection-blind",
14402
+ "addon-crash-loop",
14371
14403
  "addon-update-available",
14372
14404
  "server-update-available",
14373
14405
  "alarm-triggered",
@@ -14375,6 +14407,9 @@ var NcSystemEventKindSchema = _enum([
14375
14407
  "alarm-disarmed",
14376
14408
  "alarm-arming",
14377
14409
  "alarm-arm-refused",
14410
+ "addon-updated",
14411
+ "server-updated",
14412
+ "export-completed",
14378
14413
  "camera-online",
14379
14414
  "camera-offline",
14380
14415
  "camera-disabled",
@@ -16268,13 +16303,19 @@ var RetrainStatusSchema = _enum([
16268
16303
  * "never marked" from "already trained" must read `retrainStatus`.
16269
16304
  *
16270
16305
  * `debug` does NOT pin; it is attention, not durability.
16306
+ *
16307
+ * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
16308
+ * A favourited track is skipped by retention the same way `staging` is, but
16309
+ * it does not enter `none|staging|trained` and has no staging budget.
16271
16310
  */
16272
16311
  var TrackFlagFields = {
16273
16312
  /** Operator marked this track as training material — i.e. `retrainStatus` is
16274
16313
  * `'staging'`. */
16275
16314
  markForTrain: boolean().optional(),
16276
16315
  /** Operator marked this track for diagnostic attention. */
16277
- debug: boolean().optional()
16316
+ debug: boolean().optional(),
16317
+ /** Operator favourited this track. Pins it against pruning. */
16318
+ favourited: boolean().optional()
16278
16319
  };
16279
16320
  /**
16280
16321
  * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
@@ -16299,6 +16340,7 @@ var TrackFlagsSchema = object({
16299
16340
  trackId: string(),
16300
16341
  markForTrain: boolean(),
16301
16342
  debug: boolean(),
16343
+ favourited: boolean(),
16302
16344
  /** The lifecycle state the boolean was derived from. Required here (unlike on
16303
16345
  * a track row) because this shape is only ever produced by the write body,
16304
16346
  * which always knows it — and a surface that has just written needs to render
@@ -16931,6 +16973,12 @@ var TrackCascadeCountsSchema = object({
16931
16973
  /** Per-track CLIP search vectors removed (best-effort). */
16932
16974
  embeddings: number().int()
16933
16975
  });
16976
+ /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
16977
+ var DiskReconcileCountsSchema = object({
16978
+ mediaDropped: number().int(),
16979
+ tracks: number().int(),
16980
+ events: number().int()
16981
+ });
16934
16982
  /** Event-store footprint for one camera. */
16935
16983
  var EventStoreDeviceFootprintSchema = object({
16936
16984
  deviceId: number(),
@@ -17107,6 +17155,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17107
17155
  }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
17108
17156
  kind: "mutation",
17109
17157
  auth: "admin"
17158
+ }), method(object({ deviceId: number() }), DiskReconcileCountsSchema, {
17159
+ kind: "mutation",
17160
+ auth: "admin"
17110
17161
  }), method(object({
17111
17162
  deviceId: number(),
17112
17163
  trackIds: array(string()).min(1)
@@ -18617,6 +18668,24 @@ var CameraStatusDegradationSchema = object({
18617
18668
  *
18618
18669
  * See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
18619
18670
  */
18671
+ var DiskReconcileJobSchema = object({
18672
+ state: _enum([
18673
+ "idle",
18674
+ "running",
18675
+ "done",
18676
+ "error"
18677
+ ]),
18678
+ total: number().int().nonnegative(),
18679
+ completed: number().int().nonnegative(),
18680
+ currentDeviceId: number().int().nullable(),
18681
+ failed: array(number().int()).readonly(),
18682
+ mediaDropped: number().int().nonnegative(),
18683
+ tracks: number().int().nonnegative(),
18684
+ events: number().int().nonnegative(),
18685
+ startedAtMs: number().int().nullable(),
18686
+ finishedAtMs: number().int().nullable(),
18687
+ error: string().nullable()
18688
+ });
18620
18689
  var CameraStatusSchema = object({
18621
18690
  deviceId: number(),
18622
18691
  assignment: CameraAssignmentStatusSchema,
@@ -18848,7 +18917,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
18848
18917
  }), CameraSwitchGroupSchema, {
18849
18918
  kind: "mutation",
18850
18919
  auth: "admin"
18851
- }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18920
+ }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
18921
+ kind: "mutation",
18922
+ auth: "admin"
18923
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18852
18924
  name: string(),
18853
18925
  description: string().optional(),
18854
18926
  config: CameraPipelineConfigSchema
@@ -19253,7 +19325,14 @@ targets: array(object({
19253
19325
  "sleeping",
19254
19326
  "unreachable",
19255
19327
  "waking"
19256
- ]).nullable()
19328
+ ]).nullable(),
19329
+ /** A battery camera (whatever its current state). An AWAKE battery
19330
+ * camera is deliberately NOT recaptured on the poll cadence — every
19331
+ * capture is a camera hit that would keep it out of sleep — so its
19332
+ * cached frame legitimately ages past the currency ceiling while
19333
+ * nothing is streaming. A surface must keep painting it (a fresh
19334
+ * frame is captured at each wake), not blank to "unavailable". */
19335
+ battery: boolean()
19257
19336
  })));
19258
19337
  /**
19259
19338
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -19914,20 +19993,6 @@ var VectorStatsResultSchema = object({
19914
19993
  exact: boolean()
19915
19994
  });
19916
19995
  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);
19917
- /**
19918
- * `videoclips` — the unified, navigable-clip surface for a camera.
19919
- *
19920
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
19921
- * provider per device, substitutable. The DEFAULT provider (registered by
19922
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
19923
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
19924
- * is a time-WINDOW over existing footage, never a separate file. A camera that
19925
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
19926
- * wrapper provider for its device and serve its own clip catalog + URLs.
19927
- *
19928
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
19929
- * temporal overlap, so the API never decides `continuous` vs `events`.
19930
- */
19931
19996
  var ClipSchema = object({
19932
19997
  /** Opaque, provider-namespaced id. The default provider encodes the time
19933
19998
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -19944,8 +20009,57 @@ var ClipSchema = object({
19944
20009
  startMs: number(),
19945
20010
  endMs: number()
19946
20011
  }),
19947
- /** Thumbnail URL (lazy; e.g. analytics `getEventMedia`). Never inlined. */
19948
- thumbnail: string().optional()
20012
+ /**
20013
+ * Lazy thumbnail URL, never inlined.
20014
+ *
20015
+ * Recording-derived clips (events-mode keep-window, and the prepared
20016
+ * continuous event+fragment visit) MUST use the snapshot of the **main
20017
+ * event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
20018
+ * of the event that owns `kind` (object > motion > audio). Do not extract
20019
+ * a keyframe from the recorded segments. Other providers (onboard, HKSV)
20020
+ * mint their own stills.
20021
+ *
20022
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
20023
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
20024
+ * Absent is meaningful — "this visit has no event still" — never "we did not
20025
+ * look". Stamping it from a URL template made 35% of one camera's clips point
20026
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
20027
+ * A read that FAILS drops the claim; it never invents it.
20028
+ */
20029
+ thumbnail: string().optional(),
20030
+ /**
20031
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
20032
+ * can be decoded. Present whenever the visit came from recorded availability;
20033
+ * absent on a per-event padded window (there is no footage to promise).
20034
+ *
20035
+ * This is not a thumbnail and not a second byte path: it is the argument to
20036
+ * the recorder's existing still route. The surface — never the provider —
20037
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
20038
+ * contiguous range, not of the visit: a visit spans its holes by
20039
+ * construction, so a naive midpoint lands in dead air.
20040
+ */
20041
+ stillAtMs: number().optional(),
20042
+ /**
20043
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
20044
+ * first within kind (object → motion → audio), capped at
20045
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
20046
+ *
20047
+ * Bounded because it is not a payload the surface pages through: one visit on
20048
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
20049
+ * camera-day. Read {@link eventCount} for the true total.
20050
+ */
20051
+ eventIds: array(string()).optional(),
20052
+ /** How many analytics events actually overlap this visit. Differs from
20053
+ * `eventIds.length` exactly when the sample was capped — so a truncated
20054
+ * list is never mistaken for a quiet visit. */
20055
+ eventCount: number().int().nonnegative().optional(),
20056
+ /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
20057
+ * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
20058
+ * bar keeps showing them via `recording.getAvailability`. */
20059
+ holes: array(object({
20060
+ startMs: number(),
20061
+ endMs: number()
20062
+ })).optional()
19949
20063
  });
19950
20064
  var ClipPlaybackSchema = object({
19951
20065
  /** HLS master URL through the hub data-plane (Range + token in path). */
@@ -20409,7 +20523,14 @@ var SearchResultSchema = object({
20409
20523
  });
20410
20524
  var AutoUpdateSettingsSchema = object({
20411
20525
  channel: ChannelSchema,
20412
- intervalSeconds: number()
20526
+ intervalSeconds: number(),
20527
+ /**
20528
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
20529
+ * `channel`: the poller runs while auto-apply is `off`, because being told
20530
+ * about a publish and installing it are different decisions. Clamped
20531
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
20532
+ */
20533
+ updateCheckIntervalSeconds: number()
20413
20534
  });
20414
20535
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
20415
20536
  var RestartAddonResultSchema = unknown();
@@ -20550,7 +20671,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
20550
20671
  auth: "admin"
20551
20672
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
20552
20673
  channel: ChannelSchema,
20553
- intervalSeconds: number().min(300).max(86400).optional()
20674
+ intervalSeconds: number().min(300).max(86400).optional(),
20675
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
20676
+ updateCheckIntervalSeconds: number().min(900).max(604800).optional()
20554
20677
  }), unknown(), {
20555
20678
  kind: "mutation",
20556
20679
  auth: "admin"
@@ -24543,7 +24666,9 @@ var ExportOptionsSchema = object({
24543
24666
  includeAudio: boolean(),
24544
24667
  maxLifeMs: number().int().positive(),
24545
24668
  deleteAfterDownload: boolean(),
24546
- title: string().max(200).optional()
24669
+ title: string().max(200).optional(),
24670
+ /** Notification-output target ids to ping when this export becomes ready. */
24671
+ notifyTargetIds: array(string().min(1)).max(20).optional()
24547
24672
  }).superRefine((v, ctx) => {
24548
24673
  if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
24549
24674
  code: ZodIssueCode.custom,
@@ -24602,10 +24727,18 @@ var ExportBytesSchema = object({
24602
24727
  });
24603
24728
  method(object({
24604
24729
  deviceId: number(),
24605
- profile: string(),
24730
+ /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
24731
+ profile: string().optional(),
24732
+ profiles: array(string()).min(1).optional(),
24606
24733
  fromMs: number(),
24607
24734
  toMs: number(),
24608
24735
  options: ExportOptionsSchema
24736
+ }).superRefine((v, ctx) => {
24737
+ if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
24738
+ code: ZodIssueCode.custom,
24739
+ message: "pass profiles[] (min 1) or legacy profile",
24740
+ path: ["profiles"]
24741
+ });
24609
24742
  }), ExportRecordSchema, {
24610
24743
  kind: "mutation",
24611
24744
  auth: "protected"
@@ -29205,6 +29338,12 @@ Object.freeze({
29205
29338
  addonId: null,
29206
29339
  access: "create"
29207
29340
  },
29341
+ "pipelineAnalytics.reconcileFromDisk": {
29342
+ capName: "pipeline-analytics",
29343
+ capScope: "device",
29344
+ addonId: null,
29345
+ access: "create"
29346
+ },
29208
29347
  "pipelineAnalytics.refreshStorageLocationsForMigration": {
29209
29348
  capName: "pipeline-analytics",
29210
29349
  capScope: "device",
@@ -29607,6 +29746,12 @@ Object.freeze({
29607
29746
  addonId: null,
29608
29747
  access: "view"
29609
29748
  },
29749
+ "pipelineOrchestrator.getReconcileFromDiskStatus": {
29750
+ capName: "pipeline-orchestrator",
29751
+ capScope: "system",
29752
+ addonId: null,
29753
+ access: "view"
29754
+ },
29610
29755
  "pipelineOrchestrator.listAgentSettings": {
29611
29756
  capName: "pipeline-orchestrator",
29612
29757
  capScope: "system",
@@ -29631,6 +29776,12 @@ Object.freeze({
29631
29776
  addonId: null,
29632
29777
  access: "create"
29633
29778
  },
29779
+ "pipelineOrchestrator.reconcileFromDisk": {
29780
+ capName: "pipeline-orchestrator",
29781
+ capScope: "system",
29782
+ addonId: null,
29783
+ access: "create"
29784
+ },
29634
29785
  "pipelineOrchestrator.removeAgentSettings": {
29635
29786
  capName: "pipeline-orchestrator",
29636
29787
  capScope: "system",
@@ -32622,6 +32773,11 @@ Object.freeze({
32622
32773
  form: "single",
32623
32774
  optional: true
32624
32775
  }],
32776
+ "pipelineAnalytics.reconcileFromDisk": [{
32777
+ name: "deviceId",
32778
+ form: "single",
32779
+ optional: false
32780
+ }],
32625
32781
  "pipelineAnalytics.restageRetrainTrack": [{
32626
32782
  name: "deviceId",
32627
32783
  form: "single",
@@ -33687,6 +33843,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
33687
33843
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
33688
33844
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
33689
33845
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
33846
+ var MB = 1024 * 1024;
33847
+ 1024 * MB, 3072 * MB;
33690
33848
  /**
33691
33849
  * Fixed-capacity ring buffer. When full, push() overwrites the oldest entry.
33692
33850
  * drain() returns up to maxCount items in FIFO order and removes them.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- //#region ../types/dist/event-category-Bxo5yJjt.mjs
2
+ //#region ../types/dist/event-category-XfKNtfCc.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -16,9 +16,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
16
16
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
17
17
  /**
18
18
  * A newer addon or server-root package version was found by the
19
- * authoritative registry check. Emitted once per
20
- * `(target, packageName, currentVersion, latestVersion)` transition; repeated
21
- * polling of the same result is deduplicated by the checker.
19
+ * authoritative registry check. Emitted once when any observed
20
+ * `latestVersion` changes (or a package/node first appears behind);
21
+ * the payload carries the full currently-available list. Repeated
22
+ * polling of the same latests is silent.
22
23
  */
23
24
  EventCategory["UpdateAvailable"] = "update.available";
24
25
  /**
@@ -37,6 +38,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
37
38
  EventCategory["AddonInstalled"] = "addon.installed";
38
39
  EventCategory["AddonUninstalled"] = "addon.uninstalled";
39
40
  EventCategory["AddonCrashed"] = "addon.crashed";
41
+ /**
42
+ * A RUNNER the D6 crash circuit-breaker gave up on — terminal.
43
+ *
44
+ * `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
45
+ * respawned"); this is the one that never resolves itself. Emitted exactly
46
+ * once per trip, by `process-service.ts`, carrying the node, the runner, the
47
+ * addons it hosted and the crash count that tripped the breaker.
48
+ *
49
+ * It exists because a runner marked terminally `failed` used to be SILENT:
50
+ * on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
51
+ * errors, the breaker stopped respawning it (correctly), `/health` kept
52
+ * returning 200, and fleet-wide recording was gone for 3h15 before the
53
+ * operator's phone told him. The Notification Center's system-event intake
54
+ * consumes this category and maps it to the `addon-crash-loop` kind.
55
+ */
56
+ EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
40
57
  EventCategory["AddonError"] = "addon.error";
41
58
  EventCategory["AddonPageReady"] = "addon.page-ready";
42
59
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -7544,6 +7561,10 @@ var RecordingBandSchema = object({
7544
7561
  preBufferSec: number().min(0).optional(),
7545
7562
  postBufferSec: number().min(0).optional()
7546
7563
  });
7564
+ ({
7565
+ preBufferSec: 10,
7566
+ postBufferSec: 30
7567
+ }).postBufferSec * 1e3;
7547
7568
  /**
7548
7569
  * Per-device retention overrides. Every field is optional; an unset or `0`
7549
7570
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -7591,6 +7612,12 @@ var RecordingConfigSchema = object({
7591
7612
  /** DERIVED summary of `bands`, stamped by the recorder on every save.
7592
7613
  * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7593
7614
  mode: RecordingStorageModeSchema.optional(),
7615
+ /**
7616
+ * Which assigned broker slots to record. Absent / empty = {@link
7617
+ * DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
7618
+ * camera's currently assigned slots — never `mid` unless the operator
7619
+ * picks it, and never a slot the broker has not assigned.
7620
+ */
7594
7621
  profiles: array(CamProfileSchema).optional(),
7595
7622
  segmentSeconds: number().int().positive().optional(),
7596
7623
  /**
@@ -7671,7 +7698,11 @@ var RelocateFootageInputSchema = object({
7671
7698
  profiles: array(string()).optional(),
7672
7699
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7673
7700
  * never allowed to starve live writers. */
7674
- throttleMbps: number().min(1).max(1e3).optional()
7701
+ throttleMbps: number().min(1).max(1e3).optional(),
7702
+ /** Move only segments whose startMs is >= this. Absent = the whole source
7703
+ * pile. Used when a full drain is too expensive and the operator only
7704
+ * wants the recent window on the new disk. */
7705
+ sinceMs: number().int().optional()
7675
7706
  });
7676
7707
  /** Internal, lease-scoped participant operation. It is intentionally separate
7677
7708
  * from persistent recording settings: a migration never changes
@@ -14364,6 +14395,7 @@ var NcSystemEventKindSchema = _enum([
14364
14395
  "node-offline",
14365
14396
  "node-inference-unavailable",
14366
14397
  "detection-blind",
14398
+ "addon-crash-loop",
14367
14399
  "addon-update-available",
14368
14400
  "server-update-available",
14369
14401
  "alarm-triggered",
@@ -14371,6 +14403,9 @@ var NcSystemEventKindSchema = _enum([
14371
14403
  "alarm-disarmed",
14372
14404
  "alarm-arming",
14373
14405
  "alarm-arm-refused",
14406
+ "addon-updated",
14407
+ "server-updated",
14408
+ "export-completed",
14374
14409
  "camera-online",
14375
14410
  "camera-offline",
14376
14411
  "camera-disabled",
@@ -16264,13 +16299,19 @@ var RetrainStatusSchema = _enum([
16264
16299
  * "never marked" from "already trained" must read `retrainStatus`.
16265
16300
  *
16266
16301
  * `debug` does NOT pin; it is attention, not durability.
16302
+ *
16303
+ * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
16304
+ * A favourited track is skipped by retention the same way `staging` is, but
16305
+ * it does not enter `none|staging|trained` and has no staging budget.
16267
16306
  */
16268
16307
  var TrackFlagFields = {
16269
16308
  /** Operator marked this track as training material — i.e. `retrainStatus` is
16270
16309
  * `'staging'`. */
16271
16310
  markForTrain: boolean().optional(),
16272
16311
  /** Operator marked this track for diagnostic attention. */
16273
- debug: boolean().optional()
16312
+ debug: boolean().optional(),
16313
+ /** Operator favourited this track. Pins it against pruning. */
16314
+ favourited: boolean().optional()
16274
16315
  };
16275
16316
  /**
16276
16317
  * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
@@ -16295,6 +16336,7 @@ var TrackFlagsSchema = object({
16295
16336
  trackId: string(),
16296
16337
  markForTrain: boolean(),
16297
16338
  debug: boolean(),
16339
+ favourited: boolean(),
16298
16340
  /** The lifecycle state the boolean was derived from. Required here (unlike on
16299
16341
  * a track row) because this shape is only ever produced by the write body,
16300
16342
  * which always knows it — and a surface that has just written needs to render
@@ -16927,6 +16969,12 @@ var TrackCascadeCountsSchema = object({
16927
16969
  /** Per-track CLIP search vectors removed (best-effort). */
16928
16970
  embeddings: number().int()
16929
16971
  });
16972
+ /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
16973
+ var DiskReconcileCountsSchema = object({
16974
+ mediaDropped: number().int(),
16975
+ tracks: number().int(),
16976
+ events: number().int()
16977
+ });
16930
16978
  /** Event-store footprint for one camera. */
16931
16979
  var EventStoreDeviceFootprintSchema = object({
16932
16980
  deviceId: number(),
@@ -17103,6 +17151,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17103
17151
  }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
17104
17152
  kind: "mutation",
17105
17153
  auth: "admin"
17154
+ }), method(object({ deviceId: number() }), DiskReconcileCountsSchema, {
17155
+ kind: "mutation",
17156
+ auth: "admin"
17106
17157
  }), method(object({
17107
17158
  deviceId: number(),
17108
17159
  trackIds: array(string()).min(1)
@@ -18613,6 +18664,24 @@ var CameraStatusDegradationSchema = object({
18613
18664
  *
18614
18665
  * See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
18615
18666
  */
18667
+ var DiskReconcileJobSchema = object({
18668
+ state: _enum([
18669
+ "idle",
18670
+ "running",
18671
+ "done",
18672
+ "error"
18673
+ ]),
18674
+ total: number().int().nonnegative(),
18675
+ completed: number().int().nonnegative(),
18676
+ currentDeviceId: number().int().nullable(),
18677
+ failed: array(number().int()).readonly(),
18678
+ mediaDropped: number().int().nonnegative(),
18679
+ tracks: number().int().nonnegative(),
18680
+ events: number().int().nonnegative(),
18681
+ startedAtMs: number().int().nullable(),
18682
+ finishedAtMs: number().int().nullable(),
18683
+ error: string().nullable()
18684
+ });
18616
18685
  var CameraStatusSchema = object({
18617
18686
  deviceId: number(),
18618
18687
  assignment: CameraAssignmentStatusSchema,
@@ -18844,7 +18913,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
18844
18913
  }), CameraSwitchGroupSchema, {
18845
18914
  kind: "mutation",
18846
18915
  auth: "admin"
18847
- }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18916
+ }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
18917
+ kind: "mutation",
18918
+ auth: "admin"
18919
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18848
18920
  name: string(),
18849
18921
  description: string().optional(),
18850
18922
  config: CameraPipelineConfigSchema
@@ -19249,7 +19321,14 @@ targets: array(object({
19249
19321
  "sleeping",
19250
19322
  "unreachable",
19251
19323
  "waking"
19252
- ]).nullable()
19324
+ ]).nullable(),
19325
+ /** A battery camera (whatever its current state). An AWAKE battery
19326
+ * camera is deliberately NOT recaptured on the poll cadence — every
19327
+ * capture is a camera hit that would keep it out of sleep — so its
19328
+ * cached frame legitimately ages past the currency ceiling while
19329
+ * nothing is streaming. A surface must keep painting it (a fresh
19330
+ * frame is captured at each wake), not blank to "unavailable". */
19331
+ battery: boolean()
19253
19332
  })));
19254
19333
  /**
19255
19334
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -19910,20 +19989,6 @@ var VectorStatsResultSchema = object({
19910
19989
  exact: boolean()
19911
19990
  });
19912
19991
  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);
19913
- /**
19914
- * `videoclips` — the unified, navigable-clip surface for a camera.
19915
- *
19916
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
19917
- * provider per device, substitutable. The DEFAULT provider (registered by
19918
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
19919
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
19920
- * is a time-WINDOW over existing footage, never a separate file. A camera that
19921
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
19922
- * wrapper provider for its device and serve its own clip catalog + URLs.
19923
- *
19924
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
19925
- * temporal overlap, so the API never decides `continuous` vs `events`.
19926
- */
19927
19992
  var ClipSchema = object({
19928
19993
  /** Opaque, provider-namespaced id. The default provider encodes the time
19929
19994
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -19940,8 +20005,57 @@ var ClipSchema = object({
19940
20005
  startMs: number(),
19941
20006
  endMs: number()
19942
20007
  }),
19943
- /** Thumbnail URL (lazy; e.g. analytics `getEventMedia`). Never inlined. */
19944
- thumbnail: string().optional()
20008
+ /**
20009
+ * Lazy thumbnail URL, never inlined.
20010
+ *
20011
+ * Recording-derived clips (events-mode keep-window, and the prepared
20012
+ * continuous event+fragment visit) MUST use the snapshot of the **main
20013
+ * event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
20014
+ * of the event that owns `kind` (object > motion > audio). Do not extract
20015
+ * a keyframe from the recorded segments. Other providers (onboard, HKSV)
20016
+ * mint their own stills.
20017
+ *
20018
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
20019
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
20020
+ * Absent is meaningful — "this visit has no event still" — never "we did not
20021
+ * look". Stamping it from a URL template made 35% of one camera's clips point
20022
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
20023
+ * A read that FAILS drops the claim; it never invents it.
20024
+ */
20025
+ thumbnail: string().optional(),
20026
+ /**
20027
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
20028
+ * can be decoded. Present whenever the visit came from recorded availability;
20029
+ * absent on a per-event padded window (there is no footage to promise).
20030
+ *
20031
+ * This is not a thumbnail and not a second byte path: it is the argument to
20032
+ * the recorder's existing still route. The surface — never the provider —
20033
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
20034
+ * contiguous range, not of the visit: a visit spans its holes by
20035
+ * construction, so a naive midpoint lands in dead air.
20036
+ */
20037
+ stillAtMs: number().optional(),
20038
+ /**
20039
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
20040
+ * first within kind (object → motion → audio), capped at
20041
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
20042
+ *
20043
+ * Bounded because it is not a payload the surface pages through: one visit on
20044
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
20045
+ * camera-day. Read {@link eventCount} for the true total.
20046
+ */
20047
+ eventIds: array(string()).optional(),
20048
+ /** How many analytics events actually overlap this visit. Differs from
20049
+ * `eventIds.length` exactly when the sample was capped — so a truncated
20050
+ * list is never mistaken for a quiet visit. */
20051
+ eventCount: number().int().nonnegative().optional(),
20052
+ /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
20053
+ * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
20054
+ * bar keeps showing them via `recording.getAvailability`. */
20055
+ holes: array(object({
20056
+ startMs: number(),
20057
+ endMs: number()
20058
+ })).optional()
19945
20059
  });
19946
20060
  var ClipPlaybackSchema = object({
19947
20061
  /** HLS master URL through the hub data-plane (Range + token in path). */
@@ -20405,7 +20519,14 @@ var SearchResultSchema = object({
20405
20519
  });
20406
20520
  var AutoUpdateSettingsSchema = object({
20407
20521
  channel: ChannelSchema,
20408
- intervalSeconds: number()
20522
+ intervalSeconds: number(),
20523
+ /**
20524
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
20525
+ * `channel`: the poller runs while auto-apply is `off`, because being told
20526
+ * about a publish and installing it are different decisions. Clamped
20527
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
20528
+ */
20529
+ updateCheckIntervalSeconds: number()
20409
20530
  });
20410
20531
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
20411
20532
  var RestartAddonResultSchema = unknown();
@@ -20546,7 +20667,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
20546
20667
  auth: "admin"
20547
20668
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
20548
20669
  channel: ChannelSchema,
20549
- intervalSeconds: number().min(300).max(86400).optional()
20670
+ intervalSeconds: number().min(300).max(86400).optional(),
20671
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
20672
+ updateCheckIntervalSeconds: number().min(900).max(604800).optional()
20550
20673
  }), unknown(), {
20551
20674
  kind: "mutation",
20552
20675
  auth: "admin"
@@ -24539,7 +24662,9 @@ var ExportOptionsSchema = object({
24539
24662
  includeAudio: boolean(),
24540
24663
  maxLifeMs: number().int().positive(),
24541
24664
  deleteAfterDownload: boolean(),
24542
- title: string().max(200).optional()
24665
+ title: string().max(200).optional(),
24666
+ /** Notification-output target ids to ping when this export becomes ready. */
24667
+ notifyTargetIds: array(string().min(1)).max(20).optional()
24543
24668
  }).superRefine((v, ctx) => {
24544
24669
  if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
24545
24670
  code: ZodIssueCode.custom,
@@ -24598,10 +24723,18 @@ var ExportBytesSchema = object({
24598
24723
  });
24599
24724
  method(object({
24600
24725
  deviceId: number(),
24601
- profile: string(),
24726
+ /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
24727
+ profile: string().optional(),
24728
+ profiles: array(string()).min(1).optional(),
24602
24729
  fromMs: number(),
24603
24730
  toMs: number(),
24604
24731
  options: ExportOptionsSchema
24732
+ }).superRefine((v, ctx) => {
24733
+ if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
24734
+ code: ZodIssueCode.custom,
24735
+ message: "pass profiles[] (min 1) or legacy profile",
24736
+ path: ["profiles"]
24737
+ });
24605
24738
  }), ExportRecordSchema, {
24606
24739
  kind: "mutation",
24607
24740
  auth: "protected"
@@ -29201,6 +29334,12 @@ Object.freeze({
29201
29334
  addonId: null,
29202
29335
  access: "create"
29203
29336
  },
29337
+ "pipelineAnalytics.reconcileFromDisk": {
29338
+ capName: "pipeline-analytics",
29339
+ capScope: "device",
29340
+ addonId: null,
29341
+ access: "create"
29342
+ },
29204
29343
  "pipelineAnalytics.refreshStorageLocationsForMigration": {
29205
29344
  capName: "pipeline-analytics",
29206
29345
  capScope: "device",
@@ -29603,6 +29742,12 @@ Object.freeze({
29603
29742
  addonId: null,
29604
29743
  access: "view"
29605
29744
  },
29745
+ "pipelineOrchestrator.getReconcileFromDiskStatus": {
29746
+ capName: "pipeline-orchestrator",
29747
+ capScope: "system",
29748
+ addonId: null,
29749
+ access: "view"
29750
+ },
29606
29751
  "pipelineOrchestrator.listAgentSettings": {
29607
29752
  capName: "pipeline-orchestrator",
29608
29753
  capScope: "system",
@@ -29627,6 +29772,12 @@ Object.freeze({
29627
29772
  addonId: null,
29628
29773
  access: "create"
29629
29774
  },
29775
+ "pipelineOrchestrator.reconcileFromDisk": {
29776
+ capName: "pipeline-orchestrator",
29777
+ capScope: "system",
29778
+ addonId: null,
29779
+ access: "create"
29780
+ },
29630
29781
  "pipelineOrchestrator.removeAgentSettings": {
29631
29782
  capName: "pipeline-orchestrator",
29632
29783
  capScope: "system",
@@ -32618,6 +32769,11 @@ Object.freeze({
32618
32769
  form: "single",
32619
32770
  optional: true
32620
32771
  }],
32772
+ "pipelineAnalytics.reconcileFromDisk": [{
32773
+ name: "deviceId",
32774
+ form: "single",
32775
+ optional: false
32776
+ }],
32621
32777
  "pipelineAnalytics.restageRetrainTrack": [{
32622
32778
  name: "deviceId",
32623
32779
  form: "single",
@@ -33683,6 +33839,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
33683
33839
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
33684
33840
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
33685
33841
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
33842
+ var MB = 1024 * 1024;
33843
+ 1024 * MB, 3072 * MB;
33686
33844
  /**
33687
33845
  * Fixed-capacity ring buffer. When full, push() overwrites the oldest entry.
33688
33846
  * drain() returns up to maxCount items in FIFO order and removes them.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-decoder-nodeav",
3
- "version": "1.2.18",
3
+ "version": "1.2.20",
4
4
  "description": "Standalone in-process node-av decoder addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",