@camstack/addon-cloudflare 1.2.19 → 1.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.
@@ -1,4 +1,4 @@
1
- //#region ../types/dist/event-category-Bxo5yJjt.mjs
1
+ //#region ../types/dist/event-category-XfKNtfCc.mjs
2
2
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
3
  EventCategory["SystemBoot"] = "system.boot";
4
4
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -15,9 +15,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
15
15
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
16
16
  /**
17
17
  * A newer addon or server-root package version was found by the
18
- * authoritative registry check. Emitted once per
19
- * `(target, packageName, currentVersion, latestVersion)` transition; repeated
20
- * polling of the same result is deduplicated by the checker.
18
+ * authoritative registry check. Emitted once when any observed
19
+ * `latestVersion` changes (or a package/node first appears behind);
20
+ * the payload carries the full currently-available list. Repeated
21
+ * polling of the same latests is silent.
21
22
  */
22
23
  EventCategory["UpdateAvailable"] = "update.available";
23
24
  /**
@@ -36,6 +37,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
36
37
  EventCategory["AddonInstalled"] = "addon.installed";
37
38
  EventCategory["AddonUninstalled"] = "addon.uninstalled";
38
39
  EventCategory["AddonCrashed"] = "addon.crashed";
40
+ /**
41
+ * A RUNNER the D6 crash circuit-breaker gave up on — terminal.
42
+ *
43
+ * `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
44
+ * respawned"); this is the one that never resolves itself. Emitted exactly
45
+ * once per trip, by `process-service.ts`, carrying the node, the runner, the
46
+ * addons it hosted and the crash count that tripped the breaker.
47
+ *
48
+ * It exists because a runner marked terminally `failed` used to be SILENT:
49
+ * on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
50
+ * errors, the breaker stopped respawning it (correctly), `/health` kept
51
+ * returning 200, and fleet-wide recording was gone for 3h15 before the
52
+ * operator's phone told him. The Notification Center's system-event intake
53
+ * consumes this category and maps it to the `addon-crash-loop` kind.
54
+ */
55
+ EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
39
56
  EventCategory["AddonError"] = "addon.error";
40
57
  EventCategory["AddonPageReady"] = "addon.page-ready";
41
58
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -7531,6 +7548,10 @@ var RecordingBandSchema = object({
7531
7548
  preBufferSec: number().min(0).optional(),
7532
7549
  postBufferSec: number().min(0).optional()
7533
7550
  });
7551
+ ({
7552
+ preBufferSec: 10,
7553
+ postBufferSec: 30
7554
+ }).postBufferSec * 1e3;
7534
7555
  /**
7535
7556
  * Per-device retention overrides. Every field is optional; an unset or `0`
7536
7557
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -7578,6 +7599,12 @@ var RecordingConfigSchema = object({
7578
7599
  /** DERIVED summary of `bands`, stamped by the recorder on every save.
7579
7600
  * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7580
7601
  mode: RecordingStorageModeSchema.optional(),
7602
+ /**
7603
+ * Which assigned broker slots to record. Absent / empty = {@link
7604
+ * DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
7605
+ * camera's currently assigned slots — never `mid` unless the operator
7606
+ * picks it, and never a slot the broker has not assigned.
7607
+ */
7581
7608
  profiles: array(CamProfileSchema).optional(),
7582
7609
  segmentSeconds: number().int().positive().optional(),
7583
7610
  /**
@@ -7658,7 +7685,11 @@ var RelocateFootageInputSchema = object({
7658
7685
  profiles: array(string()).optional(),
7659
7686
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7660
7687
  * never allowed to starve live writers. */
7661
- throttleMbps: number().min(1).max(1e3).optional()
7688
+ throttleMbps: number().min(1).max(1e3).optional(),
7689
+ /** Move only segments whose startMs is >= this. Absent = the whole source
7690
+ * pile. Used when a full drain is too expensive and the operator only
7691
+ * wants the recent window on the new disk. */
7692
+ sinceMs: number().int().optional()
7662
7693
  });
7663
7694
  /** Internal, lease-scoped participant operation. It is intentionally separate
7664
7695
  * from persistent recording settings: a migration never changes
@@ -14260,6 +14291,7 @@ var NcSystemEventKindSchema = _enum([
14260
14291
  "node-offline",
14261
14292
  "node-inference-unavailable",
14262
14293
  "detection-blind",
14294
+ "addon-crash-loop",
14263
14295
  "addon-update-available",
14264
14296
  "server-update-available",
14265
14297
  "alarm-triggered",
@@ -14267,6 +14299,9 @@ var NcSystemEventKindSchema = _enum([
14267
14299
  "alarm-disarmed",
14268
14300
  "alarm-arming",
14269
14301
  "alarm-arm-refused",
14302
+ "addon-updated",
14303
+ "server-updated",
14304
+ "export-completed",
14270
14305
  "camera-online",
14271
14306
  "camera-offline",
14272
14307
  "camera-disabled",
@@ -16160,13 +16195,19 @@ var RetrainStatusSchema = _enum([
16160
16195
  * "never marked" from "already trained" must read `retrainStatus`.
16161
16196
  *
16162
16197
  * `debug` does NOT pin; it is attention, not durability.
16198
+ *
16199
+ * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
16200
+ * A favourited track is skipped by retention the same way `staging` is, but
16201
+ * it does not enter `none|staging|trained` and has no staging budget.
16163
16202
  */
16164
16203
  var TrackFlagFields = {
16165
16204
  /** Operator marked this track as training material — i.e. `retrainStatus` is
16166
16205
  * `'staging'`. */
16167
16206
  markForTrain: boolean().optional(),
16168
16207
  /** Operator marked this track for diagnostic attention. */
16169
- debug: boolean().optional()
16208
+ debug: boolean().optional(),
16209
+ /** Operator favourited this track. Pins it against pruning. */
16210
+ favourited: boolean().optional()
16170
16211
  };
16171
16212
  /**
16172
16213
  * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
@@ -16191,6 +16232,7 @@ var TrackFlagsSchema = object({
16191
16232
  trackId: string(),
16192
16233
  markForTrain: boolean(),
16193
16234
  debug: boolean(),
16235
+ favourited: boolean(),
16194
16236
  /** The lifecycle state the boolean was derived from. Required here (unlike on
16195
16237
  * a track row) because this shape is only ever produced by the write body,
16196
16238
  * which always knows it — and a surface that has just written needs to render
@@ -16823,6 +16865,12 @@ var TrackCascadeCountsSchema = object({
16823
16865
  /** Per-track CLIP search vectors removed (best-effort). */
16824
16866
  embeddings: number().int()
16825
16867
  });
16868
+ /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
16869
+ var DiskReconcileCountsSchema = object({
16870
+ mediaDropped: number().int(),
16871
+ tracks: number().int(),
16872
+ events: number().int()
16873
+ });
16826
16874
  /** Event-store footprint for one camera. */
16827
16875
  var EventStoreDeviceFootprintSchema = object({
16828
16876
  deviceId: number(),
@@ -16999,6 +17047,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16999
17047
  }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
17000
17048
  kind: "mutation",
17001
17049
  auth: "admin"
17050
+ }), method(object({ deviceId: number() }), DiskReconcileCountsSchema, {
17051
+ kind: "mutation",
17052
+ auth: "admin"
17002
17053
  }), method(object({
17003
17054
  deviceId: number(),
17004
17055
  trackIds: array(string()).min(1)
@@ -18509,6 +18560,24 @@ var CameraStatusDegradationSchema = object({
18509
18560
  *
18510
18561
  * See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
18511
18562
  */
18563
+ var DiskReconcileJobSchema = object({
18564
+ state: _enum([
18565
+ "idle",
18566
+ "running",
18567
+ "done",
18568
+ "error"
18569
+ ]),
18570
+ total: number().int().nonnegative(),
18571
+ completed: number().int().nonnegative(),
18572
+ currentDeviceId: number().int().nullable(),
18573
+ failed: array(number().int()).readonly(),
18574
+ mediaDropped: number().int().nonnegative(),
18575
+ tracks: number().int().nonnegative(),
18576
+ events: number().int().nonnegative(),
18577
+ startedAtMs: number().int().nullable(),
18578
+ finishedAtMs: number().int().nullable(),
18579
+ error: string().nullable()
18580
+ });
18512
18581
  var CameraStatusSchema = object({
18513
18582
  deviceId: number(),
18514
18583
  assignment: CameraAssignmentStatusSchema,
@@ -18740,7 +18809,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
18740
18809
  }), CameraSwitchGroupSchema, {
18741
18810
  kind: "mutation",
18742
18811
  auth: "admin"
18743
- }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18812
+ }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
18813
+ kind: "mutation",
18814
+ auth: "admin"
18815
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18744
18816
  name: string(),
18745
18817
  description: string().optional(),
18746
18818
  config: CameraPipelineConfigSchema
@@ -19836,20 +19908,6 @@ var VectorStatsResultSchema = object({
19836
19908
  exact: boolean()
19837
19909
  });
19838
19910
  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);
19839
- /**
19840
- * `videoclips` — the unified, navigable-clip surface for a camera.
19841
- *
19842
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
19843
- * provider per device, substitutable. The DEFAULT provider (registered by
19844
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
19845
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
19846
- * is a time-WINDOW over existing footage, never a separate file. A camera that
19847
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
19848
- * wrapper provider for its device and serve its own clip catalog + URLs.
19849
- *
19850
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
19851
- * temporal overlap, so the API never decides `continuous` vs `events`.
19852
- */
19853
19911
  var ClipSchema = object({
19854
19912
  /** Opaque, provider-namespaced id. The default provider encodes the time
19855
19913
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -19866,8 +19924,65 @@ var ClipSchema = object({
19866
19924
  startMs: number(),
19867
19925
  endMs: number()
19868
19926
  }),
19869
- /** Thumbnail URL (lazy; e.g. analytics `getEventMedia`). Never inlined. */
19870
- thumbnail: string().optional()
19927
+ /**
19928
+ * Distinct object classes attached to this visit (`person`, `car`, …),
19929
+ * dominant first, capped at {@link MAX_CLIP_LABELS}. The ribbon renders these
19930
+ * instead of the bare kind — "Object" tells the operator nothing a colour bar
19931
+ * did not. Absent (never empty) when the visit attached no classified object
19932
+ * event, so a motion/audio-only visit keeps its kind label.
19933
+ */
19934
+ labels: array(string()).max(3).optional(),
19935
+ /**
19936
+ * Lazy thumbnail URL, never inlined.
19937
+ *
19938
+ * Recording-derived clips (events-mode keep-window, and the prepared
19939
+ * continuous event+fragment visit) MUST use the snapshot of the **main
19940
+ * event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
19941
+ * of the event that owns `kind` (object > motion > audio). Do not extract
19942
+ * a keyframe from the recorded segments. Other providers (onboard, HKSV)
19943
+ * mint their own stills.
19944
+ *
19945
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
19946
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
19947
+ * Absent is meaningful — "this visit has no event still" — never "we did not
19948
+ * look". Stamping it from a URL template made 35% of one camera's clips point
19949
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
19950
+ * A read that FAILS drops the claim; it never invents it.
19951
+ */
19952
+ thumbnail: string().optional(),
19953
+ /**
19954
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
19955
+ * can be decoded. Present whenever the visit came from recorded availability;
19956
+ * absent on a per-event padded window (there is no footage to promise).
19957
+ *
19958
+ * This is not a thumbnail and not a second byte path: it is the argument to
19959
+ * the recorder's existing still route. The surface — never the provider —
19960
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
19961
+ * contiguous range, not of the visit: a visit spans its holes by
19962
+ * construction, so a naive midpoint lands in dead air.
19963
+ */
19964
+ stillAtMs: number().optional(),
19965
+ /**
19966
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
19967
+ * first within kind (object → motion → audio), capped at
19968
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
19969
+ *
19970
+ * Bounded because it is not a payload the surface pages through: one visit on
19971
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
19972
+ * camera-day. Read {@link eventCount} for the true total.
19973
+ */
19974
+ eventIds: array(string()).optional(),
19975
+ /** How many analytics events actually overlap this visit. Differs from
19976
+ * `eventIds.length` exactly when the sample was capped — so a truncated
19977
+ * list is never mistaken for a quiet visit. */
19978
+ eventCount: number().int().nonnegative().optional(),
19979
+ /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
19980
+ * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
19981
+ * bar keeps showing them via `recording.getAvailability`. */
19982
+ holes: array(object({
19983
+ startMs: number(),
19984
+ endMs: number()
19985
+ })).optional()
19871
19986
  });
19872
19987
  var ClipPlaybackSchema = object({
19873
19988
  /** HLS master URL through the hub data-plane (Range + token in path). */
@@ -20331,7 +20446,14 @@ var SearchResultSchema = object({
20331
20446
  });
20332
20447
  var AutoUpdateSettingsSchema = object({
20333
20448
  channel: ChannelSchema,
20334
- intervalSeconds: number()
20449
+ intervalSeconds: number(),
20450
+ /**
20451
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
20452
+ * `channel`: the poller runs while auto-apply is `off`, because being told
20453
+ * about a publish and installing it are different decisions. Clamped
20454
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
20455
+ */
20456
+ updateCheckIntervalSeconds: number()
20335
20457
  });
20336
20458
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
20337
20459
  var RestartAddonResultSchema = unknown();
@@ -20472,7 +20594,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
20472
20594
  auth: "admin"
20473
20595
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
20474
20596
  channel: ChannelSchema,
20475
- intervalSeconds: number().min(300).max(86400).optional()
20597
+ intervalSeconds: number().min(300).max(86400).optional(),
20598
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
20599
+ updateCheckIntervalSeconds: number().min(900).max(604800).optional()
20476
20600
  }), unknown(), {
20477
20601
  kind: "mutation",
20478
20602
  auth: "admin"
@@ -24465,7 +24589,9 @@ var ExportOptionsSchema = object({
24465
24589
  includeAudio: boolean(),
24466
24590
  maxLifeMs: number().int().positive(),
24467
24591
  deleteAfterDownload: boolean(),
24468
- title: string().max(200).optional()
24592
+ title: string().max(200).optional(),
24593
+ /** Notification-output target ids to ping when this export becomes ready. */
24594
+ notifyTargetIds: array(string().min(1)).max(20).optional()
24469
24595
  }).superRefine((v, ctx) => {
24470
24596
  if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
24471
24597
  code: ZodIssueCode.custom,
@@ -24524,10 +24650,18 @@ var ExportBytesSchema = object({
24524
24650
  });
24525
24651
  method(object({
24526
24652
  deviceId: number(),
24527
- profile: string(),
24653
+ /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
24654
+ profile: string().optional(),
24655
+ profiles: array(string()).min(1).optional(),
24528
24656
  fromMs: number(),
24529
24657
  toMs: number(),
24530
24658
  options: ExportOptionsSchema
24659
+ }).superRefine((v, ctx) => {
24660
+ if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
24661
+ code: ZodIssueCode.custom,
24662
+ message: "pass profiles[] (min 1) or legacy profile",
24663
+ path: ["profiles"]
24664
+ });
24531
24665
  }), ExportRecordSchema, {
24532
24666
  kind: "mutation",
24533
24667
  auth: "protected"
@@ -29127,6 +29261,12 @@ Object.freeze({
29127
29261
  addonId: null,
29128
29262
  access: "create"
29129
29263
  },
29264
+ "pipelineAnalytics.reconcileFromDisk": {
29265
+ capName: "pipeline-analytics",
29266
+ capScope: "device",
29267
+ addonId: null,
29268
+ access: "create"
29269
+ },
29130
29270
  "pipelineAnalytics.refreshStorageLocationsForMigration": {
29131
29271
  capName: "pipeline-analytics",
29132
29272
  capScope: "device",
@@ -29529,6 +29669,12 @@ Object.freeze({
29529
29669
  addonId: null,
29530
29670
  access: "view"
29531
29671
  },
29672
+ "pipelineOrchestrator.getReconcileFromDiskStatus": {
29673
+ capName: "pipeline-orchestrator",
29674
+ capScope: "system",
29675
+ addonId: null,
29676
+ access: "view"
29677
+ },
29532
29678
  "pipelineOrchestrator.listAgentSettings": {
29533
29679
  capName: "pipeline-orchestrator",
29534
29680
  capScope: "system",
@@ -29553,6 +29699,12 @@ Object.freeze({
29553
29699
  addonId: null,
29554
29700
  access: "create"
29555
29701
  },
29702
+ "pipelineOrchestrator.reconcileFromDisk": {
29703
+ capName: "pipeline-orchestrator",
29704
+ capScope: "system",
29705
+ addonId: null,
29706
+ access: "create"
29707
+ },
29556
29708
  "pipelineOrchestrator.removeAgentSettings": {
29557
29709
  capName: "pipeline-orchestrator",
29558
29710
  capScope: "system",
@@ -32544,6 +32696,11 @@ Object.freeze({
32544
32696
  form: "single",
32545
32697
  optional: true
32546
32698
  }],
32699
+ "pipelineAnalytics.reconcileFromDisk": [{
32700
+ name: "deviceId",
32701
+ form: "single",
32702
+ optional: false
32703
+ }],
32547
32704
  "pipelineAnalytics.restageRetrainTrack": [{
32548
32705
  name: "deviceId",
32549
32706
  form: "single",
@@ -33609,6 +33766,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
33609
33766
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
33610
33767
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
33611
33768
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
33769
+ var MB = 1024 * 1024;
33770
+ 1024 * MB, 3072 * MB;
33612
33771
  //#endregion
33613
33772
  Object.defineProperty(exports, "BaseAddon", {
33614
33773
  enumerable: true,
@@ -1,4 +1,4 @@
1
- //#region ../types/dist/event-category-Bxo5yJjt.mjs
1
+ //#region ../types/dist/event-category-XfKNtfCc.mjs
2
2
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
3
  EventCategory["SystemBoot"] = "system.boot";
4
4
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -15,9 +15,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
15
15
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
16
16
  /**
17
17
  * A newer addon or server-root package version was found by the
18
- * authoritative registry check. Emitted once per
19
- * `(target, packageName, currentVersion, latestVersion)` transition; repeated
20
- * polling of the same result is deduplicated by the checker.
18
+ * authoritative registry check. Emitted once when any observed
19
+ * `latestVersion` changes (or a package/node first appears behind);
20
+ * the payload carries the full currently-available list. Repeated
21
+ * polling of the same latests is silent.
21
22
  */
22
23
  EventCategory["UpdateAvailable"] = "update.available";
23
24
  /**
@@ -36,6 +37,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
36
37
  EventCategory["AddonInstalled"] = "addon.installed";
37
38
  EventCategory["AddonUninstalled"] = "addon.uninstalled";
38
39
  EventCategory["AddonCrashed"] = "addon.crashed";
40
+ /**
41
+ * A RUNNER the D6 crash circuit-breaker gave up on — terminal.
42
+ *
43
+ * `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
44
+ * respawned"); this is the one that never resolves itself. Emitted exactly
45
+ * once per trip, by `process-service.ts`, carrying the node, the runner, the
46
+ * addons it hosted and the crash count that tripped the breaker.
47
+ *
48
+ * It exists because a runner marked terminally `failed` used to be SILENT:
49
+ * on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
50
+ * errors, the breaker stopped respawning it (correctly), `/health` kept
51
+ * returning 200, and fleet-wide recording was gone for 3h15 before the
52
+ * operator's phone told him. The Notification Center's system-event intake
53
+ * consumes this category and maps it to the `addon-crash-loop` kind.
54
+ */
55
+ EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
39
56
  EventCategory["AddonError"] = "addon.error";
40
57
  EventCategory["AddonPageReady"] = "addon.page-ready";
41
58
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -7531,6 +7548,10 @@ var RecordingBandSchema = object({
7531
7548
  preBufferSec: number().min(0).optional(),
7532
7549
  postBufferSec: number().min(0).optional()
7533
7550
  });
7551
+ ({
7552
+ preBufferSec: 10,
7553
+ postBufferSec: 30
7554
+ }).postBufferSec * 1e3;
7534
7555
  /**
7535
7556
  * Per-device retention overrides. Every field is optional; an unset or `0`
7536
7557
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -7578,6 +7599,12 @@ var RecordingConfigSchema = object({
7578
7599
  /** DERIVED summary of `bands`, stamped by the recorder on every save.
7579
7600
  * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7580
7601
  mode: RecordingStorageModeSchema.optional(),
7602
+ /**
7603
+ * Which assigned broker slots to record. Absent / empty = {@link
7604
+ * DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
7605
+ * camera's currently assigned slots — never `mid` unless the operator
7606
+ * picks it, and never a slot the broker has not assigned.
7607
+ */
7581
7608
  profiles: array(CamProfileSchema).optional(),
7582
7609
  segmentSeconds: number().int().positive().optional(),
7583
7610
  /**
@@ -7658,7 +7685,11 @@ var RelocateFootageInputSchema = object({
7658
7685
  profiles: array(string()).optional(),
7659
7686
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7660
7687
  * never allowed to starve live writers. */
7661
- throttleMbps: number().min(1).max(1e3).optional()
7688
+ throttleMbps: number().min(1).max(1e3).optional(),
7689
+ /** Move only segments whose startMs is >= this. Absent = the whole source
7690
+ * pile. Used when a full drain is too expensive and the operator only
7691
+ * wants the recent window on the new disk. */
7692
+ sinceMs: number().int().optional()
7662
7693
  });
7663
7694
  /** Internal, lease-scoped participant operation. It is intentionally separate
7664
7695
  * from persistent recording settings: a migration never changes
@@ -14260,6 +14291,7 @@ var NcSystemEventKindSchema = _enum([
14260
14291
  "node-offline",
14261
14292
  "node-inference-unavailable",
14262
14293
  "detection-blind",
14294
+ "addon-crash-loop",
14263
14295
  "addon-update-available",
14264
14296
  "server-update-available",
14265
14297
  "alarm-triggered",
@@ -14267,6 +14299,9 @@ var NcSystemEventKindSchema = _enum([
14267
14299
  "alarm-disarmed",
14268
14300
  "alarm-arming",
14269
14301
  "alarm-arm-refused",
14302
+ "addon-updated",
14303
+ "server-updated",
14304
+ "export-completed",
14270
14305
  "camera-online",
14271
14306
  "camera-offline",
14272
14307
  "camera-disabled",
@@ -16160,13 +16195,19 @@ var RetrainStatusSchema = _enum([
16160
16195
  * "never marked" from "already trained" must read `retrainStatus`.
16161
16196
  *
16162
16197
  * `debug` does NOT pin; it is attention, not durability.
16198
+ *
16199
+ * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
16200
+ * A favourited track is skipped by retention the same way `staging` is, but
16201
+ * it does not enter `none|staging|trained` and has no staging budget.
16163
16202
  */
16164
16203
  var TrackFlagFields = {
16165
16204
  /** Operator marked this track as training material — i.e. `retrainStatus` is
16166
16205
  * `'staging'`. */
16167
16206
  markForTrain: boolean().optional(),
16168
16207
  /** Operator marked this track for diagnostic attention. */
16169
- debug: boolean().optional()
16208
+ debug: boolean().optional(),
16209
+ /** Operator favourited this track. Pins it against pruning. */
16210
+ favourited: boolean().optional()
16170
16211
  };
16171
16212
  /**
16172
16213
  * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
@@ -16191,6 +16232,7 @@ var TrackFlagsSchema = object({
16191
16232
  trackId: string(),
16192
16233
  markForTrain: boolean(),
16193
16234
  debug: boolean(),
16235
+ favourited: boolean(),
16194
16236
  /** The lifecycle state the boolean was derived from. Required here (unlike on
16195
16237
  * a track row) because this shape is only ever produced by the write body,
16196
16238
  * which always knows it — and a surface that has just written needs to render
@@ -16823,6 +16865,12 @@ var TrackCascadeCountsSchema = object({
16823
16865
  /** Per-track CLIP search vectors removed (best-effort). */
16824
16866
  embeddings: number().int()
16825
16867
  });
16868
+ /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
16869
+ var DiskReconcileCountsSchema = object({
16870
+ mediaDropped: number().int(),
16871
+ tracks: number().int(),
16872
+ events: number().int()
16873
+ });
16826
16874
  /** Event-store footprint for one camera. */
16827
16875
  var EventStoreDeviceFootprintSchema = object({
16828
16876
  deviceId: number(),
@@ -16999,6 +17047,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16999
17047
  }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
17000
17048
  kind: "mutation",
17001
17049
  auth: "admin"
17050
+ }), method(object({ deviceId: number() }), DiskReconcileCountsSchema, {
17051
+ kind: "mutation",
17052
+ auth: "admin"
17002
17053
  }), method(object({
17003
17054
  deviceId: number(),
17004
17055
  trackIds: array(string()).min(1)
@@ -18509,6 +18560,24 @@ var CameraStatusDegradationSchema = object({
18509
18560
  *
18510
18561
  * See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
18511
18562
  */
18563
+ var DiskReconcileJobSchema = object({
18564
+ state: _enum([
18565
+ "idle",
18566
+ "running",
18567
+ "done",
18568
+ "error"
18569
+ ]),
18570
+ total: number().int().nonnegative(),
18571
+ completed: number().int().nonnegative(),
18572
+ currentDeviceId: number().int().nullable(),
18573
+ failed: array(number().int()).readonly(),
18574
+ mediaDropped: number().int().nonnegative(),
18575
+ tracks: number().int().nonnegative(),
18576
+ events: number().int().nonnegative(),
18577
+ startedAtMs: number().int().nullable(),
18578
+ finishedAtMs: number().int().nullable(),
18579
+ error: string().nullable()
18580
+ });
18512
18581
  var CameraStatusSchema = object({
18513
18582
  deviceId: number(),
18514
18583
  assignment: CameraAssignmentStatusSchema,
@@ -18740,7 +18809,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
18740
18809
  }), CameraSwitchGroupSchema, {
18741
18810
  kind: "mutation",
18742
18811
  auth: "admin"
18743
- }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18812
+ }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
18813
+ kind: "mutation",
18814
+ auth: "admin"
18815
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18744
18816
  name: string(),
18745
18817
  description: string().optional(),
18746
18818
  config: CameraPipelineConfigSchema
@@ -19836,20 +19908,6 @@ var VectorStatsResultSchema = object({
19836
19908
  exact: boolean()
19837
19909
  });
19838
19910
  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);
19839
- /**
19840
- * `videoclips` — the unified, navigable-clip surface for a camera.
19841
- *
19842
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
19843
- * provider per device, substitutable. The DEFAULT provider (registered by
19844
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
19845
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
19846
- * is a time-WINDOW over existing footage, never a separate file. A camera that
19847
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
19848
- * wrapper provider for its device and serve its own clip catalog + URLs.
19849
- *
19850
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
19851
- * temporal overlap, so the API never decides `continuous` vs `events`.
19852
- */
19853
19911
  var ClipSchema = object({
19854
19912
  /** Opaque, provider-namespaced id. The default provider encodes the time
19855
19913
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -19866,8 +19924,65 @@ var ClipSchema = object({
19866
19924
  startMs: number(),
19867
19925
  endMs: number()
19868
19926
  }),
19869
- /** Thumbnail URL (lazy; e.g. analytics `getEventMedia`). Never inlined. */
19870
- thumbnail: string().optional()
19927
+ /**
19928
+ * Distinct object classes attached to this visit (`person`, `car`, …),
19929
+ * dominant first, capped at {@link MAX_CLIP_LABELS}. The ribbon renders these
19930
+ * instead of the bare kind — "Object" tells the operator nothing a colour bar
19931
+ * did not. Absent (never empty) when the visit attached no classified object
19932
+ * event, so a motion/audio-only visit keeps its kind label.
19933
+ */
19934
+ labels: array(string()).max(3).optional(),
19935
+ /**
19936
+ * Lazy thumbnail URL, never inlined.
19937
+ *
19938
+ * Recording-derived clips (events-mode keep-window, and the prepared
19939
+ * continuous event+fragment visit) MUST use the snapshot of the **main
19940
+ * event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
19941
+ * of the event that owns `kind` (object > motion > audio). Do not extract
19942
+ * a keyframe from the recorded segments. Other providers (onboard, HKSV)
19943
+ * mint their own stills.
19944
+ *
19945
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
19946
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
19947
+ * Absent is meaningful — "this visit has no event still" — never "we did not
19948
+ * look". Stamping it from a URL template made 35% of one camera's clips point
19949
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
19950
+ * A read that FAILS drops the claim; it never invents it.
19951
+ */
19952
+ thumbnail: string().optional(),
19953
+ /**
19954
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
19955
+ * can be decoded. Present whenever the visit came from recorded availability;
19956
+ * absent on a per-event padded window (there is no footage to promise).
19957
+ *
19958
+ * This is not a thumbnail and not a second byte path: it is the argument to
19959
+ * the recorder's existing still route. The surface — never the provider —
19960
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
19961
+ * contiguous range, not of the visit: a visit spans its holes by
19962
+ * construction, so a naive midpoint lands in dead air.
19963
+ */
19964
+ stillAtMs: number().optional(),
19965
+ /**
19966
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
19967
+ * first within kind (object → motion → audio), capped at
19968
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
19969
+ *
19970
+ * Bounded because it is not a payload the surface pages through: one visit on
19971
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
19972
+ * camera-day. Read {@link eventCount} for the true total.
19973
+ */
19974
+ eventIds: array(string()).optional(),
19975
+ /** How many analytics events actually overlap this visit. Differs from
19976
+ * `eventIds.length` exactly when the sample was capped — so a truncated
19977
+ * list is never mistaken for a quiet visit. */
19978
+ eventCount: number().int().nonnegative().optional(),
19979
+ /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
19980
+ * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
19981
+ * bar keeps showing them via `recording.getAvailability`. */
19982
+ holes: array(object({
19983
+ startMs: number(),
19984
+ endMs: number()
19985
+ })).optional()
19871
19986
  });
19872
19987
  var ClipPlaybackSchema = object({
19873
19988
  /** HLS master URL through the hub data-plane (Range + token in path). */
@@ -20331,7 +20446,14 @@ var SearchResultSchema = object({
20331
20446
  });
20332
20447
  var AutoUpdateSettingsSchema = object({
20333
20448
  channel: ChannelSchema,
20334
- intervalSeconds: number()
20449
+ intervalSeconds: number(),
20450
+ /**
20451
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
20452
+ * `channel`: the poller runs while auto-apply is `off`, because being told
20453
+ * about a publish and installing it are different decisions. Clamped
20454
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
20455
+ */
20456
+ updateCheckIntervalSeconds: number()
20335
20457
  });
20336
20458
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
20337
20459
  var RestartAddonResultSchema = unknown();
@@ -20472,7 +20594,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
20472
20594
  auth: "admin"
20473
20595
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
20474
20596
  channel: ChannelSchema,
20475
- intervalSeconds: number().min(300).max(86400).optional()
20597
+ intervalSeconds: number().min(300).max(86400).optional(),
20598
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
20599
+ updateCheckIntervalSeconds: number().min(900).max(604800).optional()
20476
20600
  }), unknown(), {
20477
20601
  kind: "mutation",
20478
20602
  auth: "admin"
@@ -24465,7 +24589,9 @@ var ExportOptionsSchema = object({
24465
24589
  includeAudio: boolean(),
24466
24590
  maxLifeMs: number().int().positive(),
24467
24591
  deleteAfterDownload: boolean(),
24468
- title: string().max(200).optional()
24592
+ title: string().max(200).optional(),
24593
+ /** Notification-output target ids to ping when this export becomes ready. */
24594
+ notifyTargetIds: array(string().min(1)).max(20).optional()
24469
24595
  }).superRefine((v, ctx) => {
24470
24596
  if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
24471
24597
  code: ZodIssueCode.custom,
@@ -24524,10 +24650,18 @@ var ExportBytesSchema = object({
24524
24650
  });
24525
24651
  method(object({
24526
24652
  deviceId: number(),
24527
- profile: string(),
24653
+ /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
24654
+ profile: string().optional(),
24655
+ profiles: array(string()).min(1).optional(),
24528
24656
  fromMs: number(),
24529
24657
  toMs: number(),
24530
24658
  options: ExportOptionsSchema
24659
+ }).superRefine((v, ctx) => {
24660
+ if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
24661
+ code: ZodIssueCode.custom,
24662
+ message: "pass profiles[] (min 1) or legacy profile",
24663
+ path: ["profiles"]
24664
+ });
24531
24665
  }), ExportRecordSchema, {
24532
24666
  kind: "mutation",
24533
24667
  auth: "protected"
@@ -29127,6 +29261,12 @@ Object.freeze({
29127
29261
  addonId: null,
29128
29262
  access: "create"
29129
29263
  },
29264
+ "pipelineAnalytics.reconcileFromDisk": {
29265
+ capName: "pipeline-analytics",
29266
+ capScope: "device",
29267
+ addonId: null,
29268
+ access: "create"
29269
+ },
29130
29270
  "pipelineAnalytics.refreshStorageLocationsForMigration": {
29131
29271
  capName: "pipeline-analytics",
29132
29272
  capScope: "device",
@@ -29529,6 +29669,12 @@ Object.freeze({
29529
29669
  addonId: null,
29530
29670
  access: "view"
29531
29671
  },
29672
+ "pipelineOrchestrator.getReconcileFromDiskStatus": {
29673
+ capName: "pipeline-orchestrator",
29674
+ capScope: "system",
29675
+ addonId: null,
29676
+ access: "view"
29677
+ },
29532
29678
  "pipelineOrchestrator.listAgentSettings": {
29533
29679
  capName: "pipeline-orchestrator",
29534
29680
  capScope: "system",
@@ -29553,6 +29699,12 @@ Object.freeze({
29553
29699
  addonId: null,
29554
29700
  access: "create"
29555
29701
  },
29702
+ "pipelineOrchestrator.reconcileFromDisk": {
29703
+ capName: "pipeline-orchestrator",
29704
+ capScope: "system",
29705
+ addonId: null,
29706
+ access: "create"
29707
+ },
29556
29708
  "pipelineOrchestrator.removeAgentSettings": {
29557
29709
  capName: "pipeline-orchestrator",
29558
29710
  capScope: "system",
@@ -32544,6 +32696,11 @@ Object.freeze({
32544
32696
  form: "single",
32545
32697
  optional: true
32546
32698
  }],
32699
+ "pipelineAnalytics.reconcileFromDisk": [{
32700
+ name: "deviceId",
32701
+ form: "single",
32702
+ optional: false
32703
+ }],
32547
32704
  "pipelineAnalytics.restageRetrainTrack": [{
32548
32705
  name: "deviceId",
32549
32706
  form: "single",
@@ -33609,5 +33766,7 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
33609
33766
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
33610
33767
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
33611
33768
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
33769
+ var MB = 1024 * 1024;
33770
+ 1024 * MB, 3072 * MB;
33612
33771
  //#endregion
33613
33772
  export { BaseAddon as a, boolean as c, number as d, object as f, turnProviderCapability as i, discriminatedUnion as l, EventCategory as m, defineCustomActions as n, _enum as o, string as p, networkAccessCapability as r, array as s, customAction as t, literal as u };
@@ -21,7 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- const require_dist = require("../dist-DJIQkocy.js");
24
+ const require_dist = require("../dist-CfYr3SWQ.js");
25
25
  let node_crypto = require("node:crypto");
26
26
  let node_path = require("node:path");
27
27
  node_path = __toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { a as BaseAddon, c as boolean, d as number, f as object, m as EventCategory, n as defineCustomActions, o as _enum, p as string, r as networkAccessCapability, s as array, t as customAction, u as literal } from "../dist-BmXs2LPS.mjs";
1
+ import { a as BaseAddon, c as boolean, d as number, f as object, m as EventCategory, n as defineCustomActions, o as _enum, p as string, r as networkAccessCapability, s as array, t as customAction, u as literal } from "../dist-DP9CmDJx.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as path from "node:path";
4
4
  import { spawn } from "node:child_process";
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("../dist-DJIQkocy.js");
2
+ const require_dist = require("../dist-CfYr3SWQ.js");
3
3
  //#region src/turn/cloudflare-turn.ts
4
4
  /**
5
5
  * Cloudflare returns ICE servers in several flavours depending on which
@@ -1,4 +1,4 @@
1
- import { a as BaseAddon, c as boolean, d as number, f as object, i as turnProviderCapability, l as discriminatedUnion, n as defineCustomActions, p as string, t as customAction, u as literal } from "../dist-BmXs2LPS.mjs";
1
+ import { a as BaseAddon, c as boolean, d as number, f as object, i as turnProviderCapability, l as discriminatedUnion, n as defineCustomActions, p as string, t as customAction, u as literal } from "../dist-DP9CmDJx.mjs";
2
2
  //#region src/turn/cloudflare-turn.ts
3
3
  /**
4
4
  * Cloudflare returns ICE servers in several flavours depending on which
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-cloudflare",
3
- "version": "1.2.19",
3
+ "version": "1.2.21",
4
4
  "description": "Cloudflare bundle — Tunnel (network-access) + TURN relay (turn-provider). Multi-entry npm package shipping 2 addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",