@camstack/addon-export-hap 1.2.31 → 1.2.32

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.
@@ -84,7 +84,7 @@ function carryForward(base, existing, keys) {
84
84
  return out;
85
85
  }
86
86
  //#endregion
87
- //#region ../types/dist/event-category-Bxo5yJjt.mjs
87
+ //#region ../types/dist/event-category-XfKNtfCc.mjs
88
88
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
89
89
  EventCategory["SystemBoot"] = "system.boot";
90
90
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -101,9 +101,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
101
101
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
102
102
  /**
103
103
  * A newer addon or server-root package version was found by the
104
- * authoritative registry check. Emitted once per
105
- * `(target, packageName, currentVersion, latestVersion)` transition; repeated
106
- * polling of the same result is deduplicated by the checker.
104
+ * authoritative registry check. Emitted once when any observed
105
+ * `latestVersion` changes (or a package/node first appears behind);
106
+ * the payload carries the full currently-available list. Repeated
107
+ * polling of the same latests is silent.
107
108
  */
108
109
  EventCategory["UpdateAvailable"] = "update.available";
109
110
  /**
@@ -122,6 +123,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
122
123
  EventCategory["AddonInstalled"] = "addon.installed";
123
124
  EventCategory["AddonUninstalled"] = "addon.uninstalled";
124
125
  EventCategory["AddonCrashed"] = "addon.crashed";
126
+ /**
127
+ * A RUNNER the D6 crash circuit-breaker gave up on — terminal.
128
+ *
129
+ * `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
130
+ * respawned"); this is the one that never resolves itself. Emitted exactly
131
+ * once per trip, by `process-service.ts`, carrying the node, the runner, the
132
+ * addons it hosted and the crash count that tripped the breaker.
133
+ *
134
+ * It exists because a runner marked terminally `failed` used to be SILENT:
135
+ * on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
136
+ * errors, the breaker stopped respawning it (correctly), `/health` kept
137
+ * returning 200, and fleet-wide recording was gone for 3h15 before the
138
+ * operator's phone told him. The Notification Center's system-event intake
139
+ * consumes this category and maps it to the `addon-crash-loop` kind.
140
+ */
141
+ EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
125
142
  EventCategory["AddonError"] = "addon.error";
126
143
  EventCategory["AddonPageReady"] = "addon.page-ready";
127
144
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -8214,6 +8231,10 @@ var RecordingBandSchema = object({
8214
8231
  preBufferSec: number().min(0).optional(),
8215
8232
  postBufferSec: number().min(0).optional()
8216
8233
  });
8234
+ ({
8235
+ preBufferSec: 10,
8236
+ postBufferSec: 30
8237
+ }).postBufferSec * 1e3;
8217
8238
  /**
8218
8239
  * Per-device retention overrides. Every field is optional; an unset or `0`
8219
8240
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -8261,6 +8282,12 @@ var RecordingConfigSchema = object({
8261
8282
  /** DERIVED summary of `bands`, stamped by the recorder on every save.
8262
8283
  * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
8263
8284
  mode: RecordingStorageModeSchema.optional(),
8285
+ /**
8286
+ * Which assigned broker slots to record. Absent / empty = {@link
8287
+ * DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
8288
+ * camera's currently assigned slots — never `mid` unless the operator
8289
+ * picks it, and never a slot the broker has not assigned.
8290
+ */
8264
8291
  profiles: array(CamProfileSchema).optional(),
8265
8292
  segmentSeconds: number().int().positive().optional(),
8266
8293
  /**
@@ -8341,7 +8368,11 @@ var RelocateFootageInputSchema = object({
8341
8368
  profiles: array(string()).optional(),
8342
8369
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
8343
8370
  * never allowed to starve live writers. */
8344
- throttleMbps: number().min(1).max(1e3).optional()
8371
+ throttleMbps: number().min(1).max(1e3).optional(),
8372
+ /** Move only segments whose startMs is >= this. Absent = the whole source
8373
+ * pile. Used when a full drain is too expensive and the operator only
8374
+ * wants the recent window on the new disk. */
8375
+ sinceMs: number().int().optional()
8345
8376
  });
8346
8377
  /** Internal, lease-scoped participant operation. It is intentionally separate
8347
8378
  * from persistent recording settings: a migration never changes
@@ -15039,6 +15070,7 @@ var NcSystemEventKindSchema = _enum([
15039
15070
  "node-offline",
15040
15071
  "node-inference-unavailable",
15041
15072
  "detection-blind",
15073
+ "addon-crash-loop",
15042
15074
  "addon-update-available",
15043
15075
  "server-update-available",
15044
15076
  "alarm-triggered",
@@ -15046,6 +15078,9 @@ var NcSystemEventKindSchema = _enum([
15046
15078
  "alarm-disarmed",
15047
15079
  "alarm-arming",
15048
15080
  "alarm-arm-refused",
15081
+ "addon-updated",
15082
+ "server-updated",
15083
+ "export-completed",
15049
15084
  "camera-online",
15050
15085
  "camera-offline",
15051
15086
  "camera-disabled",
@@ -16939,13 +16974,19 @@ var RetrainStatusSchema = _enum([
16939
16974
  * "never marked" from "already trained" must read `retrainStatus`.
16940
16975
  *
16941
16976
  * `debug` does NOT pin; it is attention, not durability.
16977
+ *
16978
+ * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
16979
+ * A favourited track is skipped by retention the same way `staging` is, but
16980
+ * it does not enter `none|staging|trained` and has no staging budget.
16942
16981
  */
16943
16982
  var TrackFlagFields = {
16944
16983
  /** Operator marked this track as training material — i.e. `retrainStatus` is
16945
16984
  * `'staging'`. */
16946
16985
  markForTrain: boolean().optional(),
16947
16986
  /** Operator marked this track for diagnostic attention. */
16948
- debug: boolean().optional()
16987
+ debug: boolean().optional(),
16988
+ /** Operator favourited this track. Pins it against pruning. */
16989
+ favourited: boolean().optional()
16949
16990
  };
16950
16991
  /**
16951
16992
  * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
@@ -16970,6 +17011,7 @@ var TrackFlagsSchema = object({
16970
17011
  trackId: string(),
16971
17012
  markForTrain: boolean(),
16972
17013
  debug: boolean(),
17014
+ favourited: boolean(),
16973
17015
  /** The lifecycle state the boolean was derived from. Required here (unlike on
16974
17016
  * a track row) because this shape is only ever produced by the write body,
16975
17017
  * which always knows it — and a surface that has just written needs to render
@@ -17602,6 +17644,12 @@ var TrackCascadeCountsSchema = object({
17602
17644
  /** Per-track CLIP search vectors removed (best-effort). */
17603
17645
  embeddings: number().int()
17604
17646
  });
17647
+ /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17648
+ var DiskReconcileCountsSchema = object({
17649
+ mediaDropped: number().int(),
17650
+ tracks: number().int(),
17651
+ events: number().int()
17652
+ });
17605
17653
  /** Event-store footprint for one camera. */
17606
17654
  var EventStoreDeviceFootprintSchema = object({
17607
17655
  deviceId: number(),
@@ -17778,6 +17826,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17778
17826
  }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
17779
17827
  kind: "mutation",
17780
17828
  auth: "admin"
17829
+ }), method(object({ deviceId: number() }), DiskReconcileCountsSchema, {
17830
+ kind: "mutation",
17831
+ auth: "admin"
17781
17832
  }), method(object({
17782
17833
  deviceId: number(),
17783
17834
  trackIds: array(string()).min(1)
@@ -19288,6 +19339,24 @@ var CameraStatusDegradationSchema = object({
19288
19339
  *
19289
19340
  * See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
19290
19341
  */
19342
+ var DiskReconcileJobSchema = object({
19343
+ state: _enum([
19344
+ "idle",
19345
+ "running",
19346
+ "done",
19347
+ "error"
19348
+ ]),
19349
+ total: number().int().nonnegative(),
19350
+ completed: number().int().nonnegative(),
19351
+ currentDeviceId: number().int().nullable(),
19352
+ failed: array(number().int()).readonly(),
19353
+ mediaDropped: number().int().nonnegative(),
19354
+ tracks: number().int().nonnegative(),
19355
+ events: number().int().nonnegative(),
19356
+ startedAtMs: number().int().nullable(),
19357
+ finishedAtMs: number().int().nullable(),
19358
+ error: string().nullable()
19359
+ });
19291
19360
  var CameraStatusSchema = object({
19292
19361
  deviceId: number(),
19293
19362
  assignment: CameraAssignmentStatusSchema,
@@ -19519,7 +19588,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
19519
19588
  }), CameraSwitchGroupSchema, {
19520
19589
  kind: "mutation",
19521
19590
  auth: "admin"
19522
- }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
19591
+ }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
19592
+ kind: "mutation",
19593
+ auth: "admin"
19594
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
19523
19595
  name: string(),
19524
19596
  description: string().optional(),
19525
19597
  config: CameraPipelineConfigSchema
@@ -20592,20 +20664,6 @@ var VectorStatsResultSchema = object({
20592
20664
  exact: boolean()
20593
20665
  });
20594
20666
  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);
20595
- /**
20596
- * `videoclips` — the unified, navigable-clip surface for a camera.
20597
- *
20598
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
20599
- * provider per device, substitutable. The DEFAULT provider (registered by
20600
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
20601
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
20602
- * is a time-WINDOW over existing footage, never a separate file. A camera that
20603
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
20604
- * wrapper provider for its device and serve its own clip catalog + URLs.
20605
- *
20606
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
20607
- * temporal overlap, so the API never decides `continuous` vs `events`.
20608
- */
20609
20667
  var ClipSchema = object({
20610
20668
  /** Opaque, provider-namespaced id. The default provider encodes the time
20611
20669
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -20622,8 +20680,57 @@ var ClipSchema = object({
20622
20680
  startMs: number(),
20623
20681
  endMs: number()
20624
20682
  }),
20625
- /** Thumbnail URL (lazy; e.g. analytics `getEventMedia`). Never inlined. */
20626
- thumbnail: string().optional()
20683
+ /**
20684
+ * Lazy thumbnail URL, never inlined.
20685
+ *
20686
+ * Recording-derived clips (events-mode keep-window, and the prepared
20687
+ * continuous event+fragment visit) MUST use the snapshot of the **main
20688
+ * event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
20689
+ * of the event that owns `kind` (object > motion > audio). Do not extract
20690
+ * a keyframe from the recorded segments. Other providers (onboard, HKSV)
20691
+ * mint their own stills.
20692
+ *
20693
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
20694
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
20695
+ * Absent is meaningful — "this visit has no event still" — never "we did not
20696
+ * look". Stamping it from a URL template made 35% of one camera's clips point
20697
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
20698
+ * A read that FAILS drops the claim; it never invents it.
20699
+ */
20700
+ thumbnail: string().optional(),
20701
+ /**
20702
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
20703
+ * can be decoded. Present whenever the visit came from recorded availability;
20704
+ * absent on a per-event padded window (there is no footage to promise).
20705
+ *
20706
+ * This is not a thumbnail and not a second byte path: it is the argument to
20707
+ * the recorder's existing still route. The surface — never the provider —
20708
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
20709
+ * contiguous range, not of the visit: a visit spans its holes by
20710
+ * construction, so a naive midpoint lands in dead air.
20711
+ */
20712
+ stillAtMs: number().optional(),
20713
+ /**
20714
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
20715
+ * first within kind (object → motion → audio), capped at
20716
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
20717
+ *
20718
+ * Bounded because it is not a payload the surface pages through: one visit on
20719
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
20720
+ * camera-day. Read {@link eventCount} for the true total.
20721
+ */
20722
+ eventIds: array(string()).optional(),
20723
+ /** How many analytics events actually overlap this visit. Differs from
20724
+ * `eventIds.length` exactly when the sample was capped — so a truncated
20725
+ * list is never mistaken for a quiet visit. */
20726
+ eventCount: number().int().nonnegative().optional(),
20727
+ /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
20728
+ * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
20729
+ * bar keeps showing them via `recording.getAvailability`. */
20730
+ holes: array(object({
20731
+ startMs: number(),
20732
+ endMs: number()
20733
+ })).optional()
20627
20734
  });
20628
20735
  var ClipPlaybackSchema = object({
20629
20736
  /** HLS master URL through the hub data-plane (Range + token in path). */
@@ -21087,7 +21194,14 @@ var SearchResultSchema = object({
21087
21194
  });
21088
21195
  var AutoUpdateSettingsSchema = object({
21089
21196
  channel: ChannelSchema,
21090
- intervalSeconds: number()
21197
+ intervalSeconds: number(),
21198
+ /**
21199
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
21200
+ * `channel`: the poller runs while auto-apply is `off`, because being told
21201
+ * about a publish and installing it are different decisions. Clamped
21202
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
21203
+ */
21204
+ updateCheckIntervalSeconds: number()
21091
21205
  });
21092
21206
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
21093
21207
  var RestartAddonResultSchema = unknown();
@@ -21228,7 +21342,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
21228
21342
  auth: "admin"
21229
21343
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
21230
21344
  channel: ChannelSchema,
21231
- intervalSeconds: number().min(300).max(86400).optional()
21345
+ intervalSeconds: number().min(300).max(86400).optional(),
21346
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
21347
+ updateCheckIntervalSeconds: number().min(900).max(604800).optional()
21232
21348
  }), unknown(), {
21233
21349
  kind: "mutation",
21234
21350
  auth: "admin"
@@ -25269,7 +25385,9 @@ var ExportOptionsSchema = object({
25269
25385
  includeAudio: boolean(),
25270
25386
  maxLifeMs: number().int().positive(),
25271
25387
  deleteAfterDownload: boolean(),
25272
- title: string().max(200).optional()
25388
+ title: string().max(200).optional(),
25389
+ /** Notification-output target ids to ping when this export becomes ready. */
25390
+ notifyTargetIds: array(string().min(1)).max(20).optional()
25273
25391
  }).superRefine((v, ctx) => {
25274
25392
  if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
25275
25393
  code: ZodIssueCode.custom,
@@ -25328,10 +25446,18 @@ var ExportBytesSchema = object({
25328
25446
  });
25329
25447
  method(object({
25330
25448
  deviceId: number(),
25331
- profile: string(),
25449
+ /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
25450
+ profile: string().optional(),
25451
+ profiles: array(string()).min(1).optional(),
25332
25452
  fromMs: number(),
25333
25453
  toMs: number(),
25334
25454
  options: ExportOptionsSchema
25455
+ }).superRefine((v, ctx) => {
25456
+ if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
25457
+ code: ZodIssueCode.custom,
25458
+ message: "pass profiles[] (min 1) or legacy profile",
25459
+ path: ["profiles"]
25460
+ });
25335
25461
  }), ExportRecordSchema, {
25336
25462
  kind: "mutation",
25337
25463
  auth: "protected"
@@ -29956,6 +30082,12 @@ Object.freeze({
29956
30082
  addonId: null,
29957
30083
  access: "create"
29958
30084
  },
30085
+ "pipelineAnalytics.reconcileFromDisk": {
30086
+ capName: "pipeline-analytics",
30087
+ capScope: "device",
30088
+ addonId: null,
30089
+ access: "create"
30090
+ },
29959
30091
  "pipelineAnalytics.refreshStorageLocationsForMigration": {
29960
30092
  capName: "pipeline-analytics",
29961
30093
  capScope: "device",
@@ -30358,6 +30490,12 @@ Object.freeze({
30358
30490
  addonId: null,
30359
30491
  access: "view"
30360
30492
  },
30493
+ "pipelineOrchestrator.getReconcileFromDiskStatus": {
30494
+ capName: "pipeline-orchestrator",
30495
+ capScope: "system",
30496
+ addonId: null,
30497
+ access: "view"
30498
+ },
30361
30499
  "pipelineOrchestrator.listAgentSettings": {
30362
30500
  capName: "pipeline-orchestrator",
30363
30501
  capScope: "system",
@@ -30382,6 +30520,12 @@ Object.freeze({
30382
30520
  addonId: null,
30383
30521
  access: "create"
30384
30522
  },
30523
+ "pipelineOrchestrator.reconcileFromDisk": {
30524
+ capName: "pipeline-orchestrator",
30525
+ capScope: "system",
30526
+ addonId: null,
30527
+ access: "create"
30528
+ },
30385
30529
  "pipelineOrchestrator.removeAgentSettings": {
30386
30530
  capName: "pipeline-orchestrator",
30387
30531
  capScope: "system",
@@ -33373,6 +33517,11 @@ Object.freeze({
33373
33517
  form: "single",
33374
33518
  optional: true
33375
33519
  }],
33520
+ "pipelineAnalytics.reconcileFromDisk": [{
33521
+ name: "deviceId",
33522
+ form: "single",
33523
+ optional: false
33524
+ }],
33376
33525
  "pipelineAnalytics.restageRetrainTrack": [{
33377
33526
  name: "deviceId",
33378
33527
  form: "single",
@@ -34438,6 +34587,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
34438
34587
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
34439
34588
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
34440
34589
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
34590
+ var MB = 1024 * 1024;
34591
+ 1024 * MB, 3072 * MB;
34441
34592
  /**
34442
34593
  * Compute the stable 64-char lowercase-hex fingerprint of a device's
34443
34594
  * export-relevant shape. Two structurally-equal shapes (any feature order,
@@ -72,7 +72,7 @@ function carryForward(base, existing, keys) {
72
72
  return out;
73
73
  }
74
74
  //#endregion
75
- //#region ../types/dist/event-category-Bxo5yJjt.mjs
75
+ //#region ../types/dist/event-category-XfKNtfCc.mjs
76
76
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
77
77
  EventCategory["SystemBoot"] = "system.boot";
78
78
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -89,9 +89,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
89
89
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
90
90
  /**
91
91
  * A newer addon or server-root package version was found by the
92
- * authoritative registry check. Emitted once per
93
- * `(target, packageName, currentVersion, latestVersion)` transition; repeated
94
- * polling of the same result is deduplicated by the checker.
92
+ * authoritative registry check. Emitted once when any observed
93
+ * `latestVersion` changes (or a package/node first appears behind);
94
+ * the payload carries the full currently-available list. Repeated
95
+ * polling of the same latests is silent.
95
96
  */
96
97
  EventCategory["UpdateAvailable"] = "update.available";
97
98
  /**
@@ -110,6 +111,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
110
111
  EventCategory["AddonInstalled"] = "addon.installed";
111
112
  EventCategory["AddonUninstalled"] = "addon.uninstalled";
112
113
  EventCategory["AddonCrashed"] = "addon.crashed";
114
+ /**
115
+ * A RUNNER the D6 crash circuit-breaker gave up on — terminal.
116
+ *
117
+ * `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
118
+ * respawned"); this is the one that never resolves itself. Emitted exactly
119
+ * once per trip, by `process-service.ts`, carrying the node, the runner, the
120
+ * addons it hosted and the crash count that tripped the breaker.
121
+ *
122
+ * It exists because a runner marked terminally `failed` used to be SILENT:
123
+ * on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
124
+ * errors, the breaker stopped respawning it (correctly), `/health` kept
125
+ * returning 200, and fleet-wide recording was gone for 3h15 before the
126
+ * operator's phone told him. The Notification Center's system-event intake
127
+ * consumes this category and maps it to the `addon-crash-loop` kind.
128
+ */
129
+ EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
113
130
  EventCategory["AddonError"] = "addon.error";
114
131
  EventCategory["AddonPageReady"] = "addon.page-ready";
115
132
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -8202,6 +8219,10 @@ var RecordingBandSchema = object({
8202
8219
  preBufferSec: number().min(0).optional(),
8203
8220
  postBufferSec: number().min(0).optional()
8204
8221
  });
8222
+ ({
8223
+ preBufferSec: 10,
8224
+ postBufferSec: 30
8225
+ }).postBufferSec * 1e3;
8205
8226
  /**
8206
8227
  * Per-device retention overrides. Every field is optional; an unset or `0`
8207
8228
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -8249,6 +8270,12 @@ var RecordingConfigSchema = object({
8249
8270
  /** DERIVED summary of `bands`, stamped by the recorder on every save.
8250
8271
  * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
8251
8272
  mode: RecordingStorageModeSchema.optional(),
8273
+ /**
8274
+ * Which assigned broker slots to record. Absent / empty = {@link
8275
+ * DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
8276
+ * camera's currently assigned slots — never `mid` unless the operator
8277
+ * picks it, and never a slot the broker has not assigned.
8278
+ */
8252
8279
  profiles: array(CamProfileSchema).optional(),
8253
8280
  segmentSeconds: number().int().positive().optional(),
8254
8281
  /**
@@ -8329,7 +8356,11 @@ var RelocateFootageInputSchema = object({
8329
8356
  profiles: array(string()).optional(),
8330
8357
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
8331
8358
  * never allowed to starve live writers. */
8332
- throttleMbps: number().min(1).max(1e3).optional()
8359
+ throttleMbps: number().min(1).max(1e3).optional(),
8360
+ /** Move only segments whose startMs is >= this. Absent = the whole source
8361
+ * pile. Used when a full drain is too expensive and the operator only
8362
+ * wants the recent window on the new disk. */
8363
+ sinceMs: number().int().optional()
8333
8364
  });
8334
8365
  /** Internal, lease-scoped participant operation. It is intentionally separate
8335
8366
  * from persistent recording settings: a migration never changes
@@ -15027,6 +15058,7 @@ var NcSystemEventKindSchema = _enum([
15027
15058
  "node-offline",
15028
15059
  "node-inference-unavailable",
15029
15060
  "detection-blind",
15061
+ "addon-crash-loop",
15030
15062
  "addon-update-available",
15031
15063
  "server-update-available",
15032
15064
  "alarm-triggered",
@@ -15034,6 +15066,9 @@ var NcSystemEventKindSchema = _enum([
15034
15066
  "alarm-disarmed",
15035
15067
  "alarm-arming",
15036
15068
  "alarm-arm-refused",
15069
+ "addon-updated",
15070
+ "server-updated",
15071
+ "export-completed",
15037
15072
  "camera-online",
15038
15073
  "camera-offline",
15039
15074
  "camera-disabled",
@@ -16927,13 +16962,19 @@ var RetrainStatusSchema = _enum([
16927
16962
  * "never marked" from "already trained" must read `retrainStatus`.
16928
16963
  *
16929
16964
  * `debug` does NOT pin; it is attention, not durability.
16965
+ *
16966
+ * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
16967
+ * A favourited track is skipped by retention the same way `staging` is, but
16968
+ * it does not enter `none|staging|trained` and has no staging budget.
16930
16969
  */
16931
16970
  var TrackFlagFields = {
16932
16971
  /** Operator marked this track as training material — i.e. `retrainStatus` is
16933
16972
  * `'staging'`. */
16934
16973
  markForTrain: boolean().optional(),
16935
16974
  /** Operator marked this track for diagnostic attention. */
16936
- debug: boolean().optional()
16975
+ debug: boolean().optional(),
16976
+ /** Operator favourited this track. Pins it against pruning. */
16977
+ favourited: boolean().optional()
16937
16978
  };
16938
16979
  /**
16939
16980
  * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
@@ -16958,6 +16999,7 @@ var TrackFlagsSchema = object({
16958
16999
  trackId: string(),
16959
17000
  markForTrain: boolean(),
16960
17001
  debug: boolean(),
17002
+ favourited: boolean(),
16961
17003
  /** The lifecycle state the boolean was derived from. Required here (unlike on
16962
17004
  * a track row) because this shape is only ever produced by the write body,
16963
17005
  * which always knows it — and a surface that has just written needs to render
@@ -17590,6 +17632,12 @@ var TrackCascadeCountsSchema = object({
17590
17632
  /** Per-track CLIP search vectors removed (best-effort). */
17591
17633
  embeddings: number().int()
17592
17634
  });
17635
+ /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17636
+ var DiskReconcileCountsSchema = object({
17637
+ mediaDropped: number().int(),
17638
+ tracks: number().int(),
17639
+ events: number().int()
17640
+ });
17593
17641
  /** Event-store footprint for one camera. */
17594
17642
  var EventStoreDeviceFootprintSchema = object({
17595
17643
  deviceId: number(),
@@ -17766,6 +17814,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17766
17814
  }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
17767
17815
  kind: "mutation",
17768
17816
  auth: "admin"
17817
+ }), method(object({ deviceId: number() }), DiskReconcileCountsSchema, {
17818
+ kind: "mutation",
17819
+ auth: "admin"
17769
17820
  }), method(object({
17770
17821
  deviceId: number(),
17771
17822
  trackIds: array(string()).min(1)
@@ -19276,6 +19327,24 @@ var CameraStatusDegradationSchema = object({
19276
19327
  *
19277
19328
  * See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
19278
19329
  */
19330
+ var DiskReconcileJobSchema = object({
19331
+ state: _enum([
19332
+ "idle",
19333
+ "running",
19334
+ "done",
19335
+ "error"
19336
+ ]),
19337
+ total: number().int().nonnegative(),
19338
+ completed: number().int().nonnegative(),
19339
+ currentDeviceId: number().int().nullable(),
19340
+ failed: array(number().int()).readonly(),
19341
+ mediaDropped: number().int().nonnegative(),
19342
+ tracks: number().int().nonnegative(),
19343
+ events: number().int().nonnegative(),
19344
+ startedAtMs: number().int().nullable(),
19345
+ finishedAtMs: number().int().nullable(),
19346
+ error: string().nullable()
19347
+ });
19279
19348
  var CameraStatusSchema = object({
19280
19349
  deviceId: number(),
19281
19350
  assignment: CameraAssignmentStatusSchema,
@@ -19507,7 +19576,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
19507
19576
  }), CameraSwitchGroupSchema, {
19508
19577
  kind: "mutation",
19509
19578
  auth: "admin"
19510
- }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
19579
+ }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
19580
+ kind: "mutation",
19581
+ auth: "admin"
19582
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
19511
19583
  name: string(),
19512
19584
  description: string().optional(),
19513
19585
  config: CameraPipelineConfigSchema
@@ -20580,20 +20652,6 @@ var VectorStatsResultSchema = object({
20580
20652
  exact: boolean()
20581
20653
  });
20582
20654
  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);
20583
- /**
20584
- * `videoclips` — the unified, navigable-clip surface for a camera.
20585
- *
20586
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
20587
- * provider per device, substitutable. The DEFAULT provider (registered by
20588
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
20589
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
20590
- * is a time-WINDOW over existing footage, never a separate file. A camera that
20591
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
20592
- * wrapper provider for its device and serve its own clip catalog + URLs.
20593
- *
20594
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
20595
- * temporal overlap, so the API never decides `continuous` vs `events`.
20596
- */
20597
20655
  var ClipSchema = object({
20598
20656
  /** Opaque, provider-namespaced id. The default provider encodes the time
20599
20657
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -20610,8 +20668,57 @@ var ClipSchema = object({
20610
20668
  startMs: number(),
20611
20669
  endMs: number()
20612
20670
  }),
20613
- /** Thumbnail URL (lazy; e.g. analytics `getEventMedia`). Never inlined. */
20614
- thumbnail: string().optional()
20671
+ /**
20672
+ * Lazy thumbnail URL, never inlined.
20673
+ *
20674
+ * Recording-derived clips (events-mode keep-window, and the prepared
20675
+ * continuous event+fragment visit) MUST use the snapshot of the **main
20676
+ * event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
20677
+ * of the event that owns `kind` (object > motion > audio). Do not extract
20678
+ * a keyframe from the recorded segments. Other providers (onboard, HKSV)
20679
+ * mint their own stills.
20680
+ *
20681
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
20682
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
20683
+ * Absent is meaningful — "this visit has no event still" — never "we did not
20684
+ * look". Stamping it from a URL template made 35% of one camera's clips point
20685
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
20686
+ * A read that FAILS drops the claim; it never invents it.
20687
+ */
20688
+ thumbnail: string().optional(),
20689
+ /**
20690
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
20691
+ * can be decoded. Present whenever the visit came from recorded availability;
20692
+ * absent on a per-event padded window (there is no footage to promise).
20693
+ *
20694
+ * This is not a thumbnail and not a second byte path: it is the argument to
20695
+ * the recorder's existing still route. The surface — never the provider —
20696
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
20697
+ * contiguous range, not of the visit: a visit spans its holes by
20698
+ * construction, so a naive midpoint lands in dead air.
20699
+ */
20700
+ stillAtMs: number().optional(),
20701
+ /**
20702
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
20703
+ * first within kind (object → motion → audio), capped at
20704
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
20705
+ *
20706
+ * Bounded because it is not a payload the surface pages through: one visit on
20707
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
20708
+ * camera-day. Read {@link eventCount} for the true total.
20709
+ */
20710
+ eventIds: array(string()).optional(),
20711
+ /** How many analytics events actually overlap this visit. Differs from
20712
+ * `eventIds.length` exactly when the sample was capped — so a truncated
20713
+ * list is never mistaken for a quiet visit. */
20714
+ eventCount: number().int().nonnegative().optional(),
20715
+ /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
20716
+ * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
20717
+ * bar keeps showing them via `recording.getAvailability`. */
20718
+ holes: array(object({
20719
+ startMs: number(),
20720
+ endMs: number()
20721
+ })).optional()
20615
20722
  });
20616
20723
  var ClipPlaybackSchema = object({
20617
20724
  /** HLS master URL through the hub data-plane (Range + token in path). */
@@ -21075,7 +21182,14 @@ var SearchResultSchema = object({
21075
21182
  });
21076
21183
  var AutoUpdateSettingsSchema = object({
21077
21184
  channel: ChannelSchema,
21078
- intervalSeconds: number()
21185
+ intervalSeconds: number(),
21186
+ /**
21187
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
21188
+ * `channel`: the poller runs while auto-apply is `off`, because being told
21189
+ * about a publish and installing it are different decisions. Clamped
21190
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
21191
+ */
21192
+ updateCheckIntervalSeconds: number()
21079
21193
  });
21080
21194
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
21081
21195
  var RestartAddonResultSchema = unknown();
@@ -21216,7 +21330,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
21216
21330
  auth: "admin"
21217
21331
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
21218
21332
  channel: ChannelSchema,
21219
- intervalSeconds: number().min(300).max(86400).optional()
21333
+ intervalSeconds: number().min(300).max(86400).optional(),
21334
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
21335
+ updateCheckIntervalSeconds: number().min(900).max(604800).optional()
21220
21336
  }), unknown(), {
21221
21337
  kind: "mutation",
21222
21338
  auth: "admin"
@@ -25257,7 +25373,9 @@ var ExportOptionsSchema = object({
25257
25373
  includeAudio: boolean(),
25258
25374
  maxLifeMs: number().int().positive(),
25259
25375
  deleteAfterDownload: boolean(),
25260
- title: string().max(200).optional()
25376
+ title: string().max(200).optional(),
25377
+ /** Notification-output target ids to ping when this export becomes ready. */
25378
+ notifyTargetIds: array(string().min(1)).max(20).optional()
25261
25379
  }).superRefine((v, ctx) => {
25262
25380
  if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
25263
25381
  code: ZodIssueCode.custom,
@@ -25316,10 +25434,18 @@ var ExportBytesSchema = object({
25316
25434
  });
25317
25435
  method(object({
25318
25436
  deviceId: number(),
25319
- profile: string(),
25437
+ /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
25438
+ profile: string().optional(),
25439
+ profiles: array(string()).min(1).optional(),
25320
25440
  fromMs: number(),
25321
25441
  toMs: number(),
25322
25442
  options: ExportOptionsSchema
25443
+ }).superRefine((v, ctx) => {
25444
+ if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
25445
+ code: ZodIssueCode.custom,
25446
+ message: "pass profiles[] (min 1) or legacy profile",
25447
+ path: ["profiles"]
25448
+ });
25323
25449
  }), ExportRecordSchema, {
25324
25450
  kind: "mutation",
25325
25451
  auth: "protected"
@@ -29944,6 +30070,12 @@ Object.freeze({
29944
30070
  addonId: null,
29945
30071
  access: "create"
29946
30072
  },
30073
+ "pipelineAnalytics.reconcileFromDisk": {
30074
+ capName: "pipeline-analytics",
30075
+ capScope: "device",
30076
+ addonId: null,
30077
+ access: "create"
30078
+ },
29947
30079
  "pipelineAnalytics.refreshStorageLocationsForMigration": {
29948
30080
  capName: "pipeline-analytics",
29949
30081
  capScope: "device",
@@ -30346,6 +30478,12 @@ Object.freeze({
30346
30478
  addonId: null,
30347
30479
  access: "view"
30348
30480
  },
30481
+ "pipelineOrchestrator.getReconcileFromDiskStatus": {
30482
+ capName: "pipeline-orchestrator",
30483
+ capScope: "system",
30484
+ addonId: null,
30485
+ access: "view"
30486
+ },
30349
30487
  "pipelineOrchestrator.listAgentSettings": {
30350
30488
  capName: "pipeline-orchestrator",
30351
30489
  capScope: "system",
@@ -30370,6 +30508,12 @@ Object.freeze({
30370
30508
  addonId: null,
30371
30509
  access: "create"
30372
30510
  },
30511
+ "pipelineOrchestrator.reconcileFromDisk": {
30512
+ capName: "pipeline-orchestrator",
30513
+ capScope: "system",
30514
+ addonId: null,
30515
+ access: "create"
30516
+ },
30373
30517
  "pipelineOrchestrator.removeAgentSettings": {
30374
30518
  capName: "pipeline-orchestrator",
30375
30519
  capScope: "system",
@@ -33361,6 +33505,11 @@ Object.freeze({
33361
33505
  form: "single",
33362
33506
  optional: true
33363
33507
  }],
33508
+ "pipelineAnalytics.reconcileFromDisk": [{
33509
+ name: "deviceId",
33510
+ form: "single",
33511
+ optional: false
33512
+ }],
33364
33513
  "pipelineAnalytics.restageRetrainTrack": [{
33365
33514
  name: "deviceId",
33366
33515
  form: "single",
@@ -34426,6 +34575,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
34426
34575
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
34427
34576
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
34428
34577
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
34578
+ var MB = 1024 * 1024;
34579
+ 1024 * MB, 3072 * MB;
34429
34580
  /**
34430
34581
  * Compute the stable 64-char lowercase-hex fingerprint of a device's
34431
34582
  * export-relevant shape. Two structurally-equal shapes (any feature order,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-hap",
3
- "version": "1.2.31",
3
+ "version": "1.2.32",
4
4
  "description": "HomeKit (HAP) exporter for CamStack devices. Publishes each exposed device as its own HomeKit accessory: cameras and doorbells with SRTP streaming, HomeKit Secure Video, motion, two-way audio, PTZ and battery; switches, lights, locks and sensors through a capability→service table.",
5
5
  "keywords": [
6
6
  "camstack",