@camstack/addon-provider-velux 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region ../types/dist/event-category-H4AVePnn.mjs
2
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -152,6 +152,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
152
152
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
153
153
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
154
154
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
155
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
156
+ * progress bar the client reconciles via `recordingExport.getExport`. */
157
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
158
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
159
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
155
160
  EventCategory["DetectionEvent"] = "detection.event";
156
161
  EventCategory["SessionTrackNew"] = "session.track.new";
157
162
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -441,6 +446,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
441
446
  */
442
447
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
443
448
  /**
449
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
450
+ * a package zone — a newly-appeared stationary object of a package class
451
+ * that cleared the class / zone / dwell / size gates. Payload:
452
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
453
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
454
+ * Telemetry (D8): the durable record is the `package-events` store row;
455
+ * this bus topic drives notifier rules + live UI. See
456
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
457
+ */
458
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
459
+ /**
460
+ * Fired by `addon-post-analysis` when a previously-delivered package
461
+ * leaves its zone (the stationary entry departed — moved or swept).
462
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
463
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
464
+ * Telemetry (D8). See package-zones-design §5.2.
465
+ */
466
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
467
+ /**
444
468
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
445
469
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
446
470
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5193,6 +5217,25 @@ function preprocess(fn, schema) {
5193
5217
  out: schema
5194
5218
  });
5195
5219
  }
5220
+ //#endregion
5221
+ //#region ../../node_modules/zod/v4/classic/compat.js
5222
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5223
+ var ZodIssueCode = {
5224
+ invalid_type: "invalid_type",
5225
+ too_big: "too_big",
5226
+ too_small: "too_small",
5227
+ invalid_format: "invalid_format",
5228
+ not_multiple_of: "not_multiple_of",
5229
+ unrecognized_keys: "unrecognized_keys",
5230
+ invalid_union: "invalid_union",
5231
+ invalid_key: "invalid_key",
5232
+ invalid_element: "invalid_element",
5233
+ invalid_value: "invalid_value",
5234
+ custom: "custom"
5235
+ };
5236
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5237
+ var ZodFirstPartyTypeKind;
5238
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5196
5239
  Object.fromEntries([
5197
5240
  {
5198
5241
  id: "overview",
@@ -7315,6 +7358,62 @@ var RecordingConfigSchema = object({
7315
7358
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7316
7359
  });
7317
7360
  /**
7361
+ * Ops-log — the durable, append-only operations audit shared by the
7362
+ * recordings and events management surfaces.
7363
+ *
7364
+ * ONE row shape is reused for both domains so a single "Activity" view can
7365
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7366
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7367
+ * management operation, WHY it ran (reason), and its measurable effect
7368
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7369
+ * never fail the operation it records.
7370
+ */
7371
+ /** Which management domain the operation belongs to. */
7372
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7373
+ /** The kind of management operation performed. */
7374
+ var OpsLogOpSchema = _enum([
7375
+ "prune",
7376
+ "manual-delete",
7377
+ "rescan",
7378
+ "retention-run"
7379
+ ]);
7380
+ /** Why the operation ran. */
7381
+ var OpsLogReasonSchema = _enum([
7382
+ "retention",
7383
+ "quota",
7384
+ "manual",
7385
+ "operator"
7386
+ ]);
7387
+ /** One audit row, shared verbatim by both domains. */
7388
+ var OpsLogEntrySchema = object({
7389
+ /** Unique row id. */
7390
+ id: string(),
7391
+ /** Epoch ms the operation completed. */
7392
+ at: number(),
7393
+ domain: OpsLogDomainSchema,
7394
+ op: OpsLogOpSchema,
7395
+ reason: OpsLogReasonSchema,
7396
+ /** The camera the op targeted; null for a cluster/global op. */
7397
+ deviceId: number().nullable(),
7398
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7399
+ nodeId: string(),
7400
+ /** Buckets / rows deleted (op-specific unit). */
7401
+ itemsAffected: number(),
7402
+ /** Bytes reclaimed by the op (0 when not measurable). */
7403
+ bytesReclaimed: number(),
7404
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7405
+ detail: string().nullable(),
7406
+ /** Who/what triggered the op. */
7407
+ actor: string()
7408
+ });
7409
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7410
+ var OpsLogQueryInputSchema = object({
7411
+ /** Restrict to a single camera; omit for every row. */
7412
+ deviceId: number().optional(),
7413
+ /** Max rows returned, newest-first. */
7414
+ limit: number().int().min(1).max(1e3).optional()
7415
+ });
7416
+ /**
7318
7417
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7319
7418
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7320
7419
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -14670,7 +14769,11 @@ array(ZoneRuleSchema).readonly();
14670
14769
  * Extend the enum here when a new gating consumer comes online (audio
14671
14770
  * gating, alert filtering, …) — no other surface needs to change.
14672
14771
  */
14673
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
14772
+ var ZoneRuleStageEnum = _enum([
14773
+ "motion",
14774
+ "detection",
14775
+ "package"
14776
+ ]);
14674
14777
  /**
14675
14778
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
14676
14779
  * `state` getter looks up the cap definition here to construct a
@@ -14764,16 +14867,25 @@ var DEVICE_LOCAL_STATE_CAPS = {
14764
14867
  })
14765
14868
  },
14766
14869
  /**
14767
- * Runtime-state slice — both stages mirrored together so consumers
14870
+ * Runtime-state slice — every stage mirrored together so consumers
14768
14871
  * see one reactive handle (`device.state.zoneRules.value`) instead
14769
- * of two. Bulk-replace mutations on either stage write the full
14770
- * `{motion, detection}` shape, so subscribers always get the
14872
+ * of one per stage. Bulk-replace mutations on any stage write the full
14873
+ * `{motion, detection, package}` shape, so subscribers always get the
14771
14874
  * complete current set. Consumers that only care about one stage
14772
14875
  * just read the matching property.
14876
+ *
14877
+ * `package` backs the package-drop detector — a package zone is a
14878
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
14879
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14880
+ * The orchestrator provider writes this stage as a first-class slice
14881
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
14882
+ * package}` shape, so consumers read the current package rules directly
14883
+ * off `device.state.zoneRules.value.package`.
14773
14884
  */
14774
14885
  runtimeState: object({
14775
14886
  motion: array(ZoneRuleSchema).readonly(),
14776
- detection: array(ZoneRuleSchema).readonly()
14887
+ detection: array(ZoneRuleSchema).readonly(),
14888
+ package: array(ZoneRuleSchema).readonly()
14777
14889
  })
14778
14890
  },
14779
14891
  zones: zonesCapability
@@ -18735,6 +18847,7 @@ var EventKindIconSchema = _enum([
18735
18847
  "smoke",
18736
18848
  "water",
18737
18849
  "button",
18850
+ "package",
18738
18851
  "generic"
18739
18852
  ]);
18740
18853
  var EventKindCategorySchema = _enum([
@@ -18742,7 +18855,8 @@ var EventKindCategorySchema = _enum([
18742
18855
  "audio",
18743
18856
  "detection",
18744
18857
  "sensor",
18745
- "custom"
18858
+ "custom",
18859
+ "package"
18746
18860
  ]);
18747
18861
  var EventKindDescriptorSchema = object({
18748
18862
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -19083,6 +19197,26 @@ var TrackCascadeCountsSchema = object({
19083
19197
  /** Per-track CLIP search vectors removed (best-effort). */
19084
19198
  embeddings: number().int()
19085
19199
  });
19200
+ /** Event-store footprint for one camera. */
19201
+ var EventStoreDeviceFootprintSchema = object({
19202
+ deviceId: number(),
19203
+ /** Persisted event rows (motion + object + audio) for the camera. */
19204
+ rows: number().int(),
19205
+ /** Event-owned media bytes on disk for the camera. */
19206
+ bytes: number().int()
19207
+ });
19208
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19209
+ var EventStoreFootprintSchema = object({
19210
+ totalRows: number().int(),
19211
+ totalBytes: number().int(),
19212
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19213
+ });
19214
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19215
+ var EventPruneCountsSchema = object({
19216
+ motion: number().int(),
19217
+ object: number().int(),
19218
+ audio: number().int()
19219
+ });
19086
19220
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19087
19221
  deviceId: number(),
19088
19222
  trackId: string()
@@ -19146,6 +19280,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19146
19280
  }), {
19147
19281
  kind: "mutation",
19148
19282
  auth: "admin"
19283
+ }), method(object({}), EventStoreFootprintSchema, {
19284
+ kind: "query",
19285
+ auth: "admin"
19286
+ }), method(object({
19287
+ olderThanMs: number(),
19288
+ reason: OpsLogReasonSchema.optional()
19289
+ }), EventPruneCountsSchema, {
19290
+ kind: "mutation",
19291
+ auth: "admin"
19292
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19293
+ kind: "mutation",
19294
+ auth: "admin"
19295
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19296
+ kind: "query",
19297
+ auth: "admin"
19149
19298
  }), method(object({
19150
19299
  eventId: string(),
19151
19300
  kind: MediaFileKindEnum.optional()
@@ -22379,13 +22528,131 @@ method(object({
22379
22528
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22380
22529
  kind: "mutation",
22381
22530
  auth: "admin"
22382
- }), method(object({ deviceId: number() }), object({
22531
+ }), method(object({
22532
+ deviceId: number(),
22533
+ reason: OpsLogReasonSchema.optional()
22534
+ }), object({
22383
22535
  floorMs: number().nullable(),
22384
22536
  deletedBuckets: number().int(),
22385
22537
  reclaimedBytes: number().int()
22386
22538
  }), {
22387
22539
  kind: "mutation",
22388
22540
  auth: "admin"
22541
+ }), method(object({
22542
+ deviceId: number(),
22543
+ fromMs: number().optional(),
22544
+ toMs: number().optional()
22545
+ }), object({
22546
+ deletedBuckets: number().int(),
22547
+ reclaimedBytes: number().int()
22548
+ }), {
22549
+ kind: "mutation",
22550
+ auth: "admin"
22551
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22552
+ kind: "query",
22553
+ auth: "admin"
22554
+ });
22555
+ /**
22556
+ * `recordingExport` cap — render a footage time range into a single downloadable
22557
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
22558
+ * bounded lifetime with a durable history, auto-expiry, and optional
22559
+ * delete-after-download.
22560
+ *
22561
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
22562
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
22563
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
22564
+ * input and dispatch to that single provider; the render runs on the node that
22565
+ * owns the footage (no cross-node segment transfer). Download rides the
22566
+ * framework addon data-plane. State persists in a DurableState blob (NOT
22567
+ * SQLite); history rows survive file deletion for audit.
22568
+ */
22569
+ /** Playback-speed multiplier for the render (1 = realtime). */
22570
+ var ExportSpeedSchema = number().min(.25).max(32);
22571
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
22572
+ var ExportTimelapseSchema = object({
22573
+ everyMs: number().int().positive(),
22574
+ outputFps: number().int().min(1).max(60).optional()
22575
+ });
22576
+ /**
22577
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
22578
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
22579
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
22580
+ * `deleteAfterDownload` removes it shortly after the first complete download.
22581
+ */
22582
+ var ExportOptionsSchema = object({
22583
+ speed: ExportSpeedSchema.optional(),
22584
+ timelapse: ExportTimelapseSchema.optional(),
22585
+ includeAudio: boolean(),
22586
+ maxLifeMs: number().int().positive(),
22587
+ deleteAfterDownload: boolean(),
22588
+ title: string().max(200).optional()
22589
+ }).superRefine((v, ctx) => {
22590
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
22591
+ code: ZodIssueCode.custom,
22592
+ message: "speed and timelapse are mutually exclusive",
22593
+ path: ["timelapse"]
22594
+ });
22595
+ });
22596
+ var ExportStateSchema = _enum([
22597
+ "queued",
22598
+ "rendering",
22599
+ "ready",
22600
+ "failed",
22601
+ "expired",
22602
+ "deleted"
22603
+ ]);
22604
+ /** One export job / history row. */
22605
+ var ExportRecordSchema = object({
22606
+ id: string(),
22607
+ deviceId: number(),
22608
+ profile: string(),
22609
+ fromMs: number(),
22610
+ toMs: number(),
22611
+ options: ExportOptionsSchema,
22612
+ state: ExportStateSchema,
22613
+ /** 0–100 while rendering; null otherwise. */
22614
+ progressPct: number().nullable(),
22615
+ /** File size once ready; null before. */
22616
+ fileBytes: number().nullable(),
22617
+ expiresAt: number(),
22618
+ deleteAfterDownload: boolean(),
22619
+ /** Epoch of the first complete download; null until then. */
22620
+ downloadedAt: number().nullable(),
22621
+ createdAt: number(),
22622
+ /** User id/name that requested the export. */
22623
+ createdBy: string(),
22624
+ /** Failure reason when state is 'failed'; null otherwise. */
22625
+ error: string().nullable()
22626
+ });
22627
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
22628
+ var ExportDownloadSchema = object({
22629
+ url: string(),
22630
+ endpoints: array(string())
22631
+ });
22632
+ method(object({
22633
+ deviceId: number(),
22634
+ profile: string(),
22635
+ fromMs: number(),
22636
+ toMs: number(),
22637
+ options: ExportOptionsSchema
22638
+ }), ExportRecordSchema, {
22639
+ kind: "mutation",
22640
+ auth: "protected"
22641
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
22642
+ kind: "query",
22643
+ auth: "protected"
22644
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22645
+ kind: "query",
22646
+ auth: "protected"
22647
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22648
+ kind: "mutation",
22649
+ auth: "protected"
22650
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22651
+ kind: "mutation",
22652
+ auth: "protected"
22653
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
22654
+ kind: "query",
22655
+ auth: "protected"
22389
22656
  });
22390
22657
  /**
22391
22658
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
@@ -25405,6 +25672,12 @@ Object.freeze({
25405
25672
  addonId: null,
25406
25673
  access: "delete"
25407
25674
  },
25675
+ "pipelineAnalytics.deleteDeviceEvents": {
25676
+ capName: "pipeline-analytics",
25677
+ capScope: "device",
25678
+ addonId: null,
25679
+ access: "delete"
25680
+ },
25408
25681
  "pipelineAnalytics.deleteTracks": {
25409
25682
  capName: "pipeline-analytics",
25410
25683
  capScope: "device",
@@ -25435,6 +25708,12 @@ Object.freeze({
25435
25708
  addonId: null,
25436
25709
  access: "view"
25437
25710
  },
25711
+ "pipelineAnalytics.getEventStoreFootprint": {
25712
+ capName: "pipeline-analytics",
25713
+ capScope: "device",
25714
+ addonId: null,
25715
+ access: "view"
25716
+ },
25438
25717
  "pipelineAnalytics.getKeyEvents": {
25439
25718
  capName: "pipeline-analytics",
25440
25719
  capScope: "device",
@@ -25477,6 +25756,12 @@ Object.freeze({
25477
25756
  addonId: null,
25478
25757
  access: "view"
25479
25758
  },
25759
+ "pipelineAnalytics.listOpsLog": {
25760
+ capName: "pipeline-analytics",
25761
+ capScope: "device",
25762
+ addonId: null,
25763
+ access: "view"
25764
+ },
25480
25765
  "pipelineAnalytics.listRecentTracks": {
25481
25766
  capName: "pipeline-analytics",
25482
25767
  capScope: "device",
@@ -25489,6 +25774,12 @@ Object.freeze({
25489
25774
  addonId: null,
25490
25775
  access: "view"
25491
25776
  },
25777
+ "pipelineAnalytics.pruneEvents": {
25778
+ capName: "pipeline-analytics",
25779
+ capScope: "device",
25780
+ addonId: null,
25781
+ access: "create"
25782
+ },
25492
25783
  "pipelineAnalytics.pruneEventsBefore": {
25493
25784
  capName: "pipeline-analytics",
25494
25785
  capScope: "device",
@@ -26251,6 +26542,12 @@ Object.freeze({
26251
26542
  addonId: null,
26252
26543
  access: "create"
26253
26544
  },
26545
+ "recording.deleteFootprint": {
26546
+ capName: "recording",
26547
+ capScope: "system",
26548
+ addonId: null,
26549
+ access: "delete"
26550
+ },
26254
26551
  "recording.getAvailability": {
26255
26552
  capName: "recording",
26256
26553
  capScope: "system",
@@ -26281,6 +26578,12 @@ Object.freeze({
26281
26578
  addonId: null,
26282
26579
  access: "view"
26283
26580
  },
26581
+ "recording.listOpsLog": {
26582
+ capName: "recording",
26583
+ capScope: "system",
26584
+ addonId: null,
26585
+ access: "view"
26586
+ },
26284
26587
  "recording.locateSegment": {
26285
26588
  capName: "recording",
26286
26589
  capScope: "system",
@@ -26311,6 +26614,42 @@ Object.freeze({
26311
26614
  addonId: null,
26312
26615
  access: "create"
26313
26616
  },
26617
+ "recordingExport.cancelExport": {
26618
+ capName: "recordingExport",
26619
+ capScope: "system",
26620
+ addonId: null,
26621
+ access: "create"
26622
+ },
26623
+ "recordingExport.createExport": {
26624
+ capName: "recordingExport",
26625
+ capScope: "system",
26626
+ addonId: null,
26627
+ access: "create"
26628
+ },
26629
+ "recordingExport.deleteExport": {
26630
+ capName: "recordingExport",
26631
+ capScope: "system",
26632
+ addonId: null,
26633
+ access: "delete"
26634
+ },
26635
+ "recordingExport.getDownloadUrl": {
26636
+ capName: "recordingExport",
26637
+ capScope: "system",
26638
+ addonId: null,
26639
+ access: "view"
26640
+ },
26641
+ "recordingExport.getExport": {
26642
+ capName: "recordingExport",
26643
+ capScope: "system",
26644
+ addonId: null,
26645
+ access: "view"
26646
+ },
26647
+ "recordingExport.listExports": {
26648
+ capName: "recordingExport",
26649
+ capScope: "system",
26650
+ addonId: null,
26651
+ access: "view"
26652
+ },
26314
26653
  "sceneMonitor.captureReference": {
26315
26654
  capName: "scene-monitor",
26316
26655
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../types/dist/event-category-H4AVePnn.mjs
1
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
2
2
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
3
  EventCategory["SystemBoot"] = "system.boot";
4
4
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -151,6 +151,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
151
151
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
152
152
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
153
153
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
154
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
155
+ * progress bar the client reconciles via `recordingExport.getExport`. */
156
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
157
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
158
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
154
159
  EventCategory["DetectionEvent"] = "detection.event";
155
160
  EventCategory["SessionTrackNew"] = "session.track.new";
156
161
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -440,6 +445,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
440
445
  */
441
446
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
442
447
  /**
448
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
449
+ * a package zone — a newly-appeared stationary object of a package class
450
+ * that cleared the class / zone / dwell / size gates. Payload:
451
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
452
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
453
+ * Telemetry (D8): the durable record is the `package-events` store row;
454
+ * this bus topic drives notifier rules + live UI. See
455
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
456
+ */
457
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
458
+ /**
459
+ * Fired by `addon-post-analysis` when a previously-delivered package
460
+ * leaves its zone (the stationary entry departed — moved or swept).
461
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
462
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
463
+ * Telemetry (D8). See package-zones-design §5.2.
464
+ */
465
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
466
+ /**
443
467
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
444
468
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
445
469
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5192,6 +5216,25 @@ function preprocess(fn, schema) {
5192
5216
  out: schema
5193
5217
  });
5194
5218
  }
5219
+ //#endregion
5220
+ //#region ../../node_modules/zod/v4/classic/compat.js
5221
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5222
+ var ZodIssueCode = {
5223
+ invalid_type: "invalid_type",
5224
+ too_big: "too_big",
5225
+ too_small: "too_small",
5226
+ invalid_format: "invalid_format",
5227
+ not_multiple_of: "not_multiple_of",
5228
+ unrecognized_keys: "unrecognized_keys",
5229
+ invalid_union: "invalid_union",
5230
+ invalid_key: "invalid_key",
5231
+ invalid_element: "invalid_element",
5232
+ invalid_value: "invalid_value",
5233
+ custom: "custom"
5234
+ };
5235
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5236
+ var ZodFirstPartyTypeKind;
5237
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5195
5238
  Object.fromEntries([
5196
5239
  {
5197
5240
  id: "overview",
@@ -7314,6 +7357,62 @@ var RecordingConfigSchema = object({
7314
7357
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7315
7358
  });
7316
7359
  /**
7360
+ * Ops-log — the durable, append-only operations audit shared by the
7361
+ * recordings and events management surfaces.
7362
+ *
7363
+ * ONE row shape is reused for both domains so a single "Activity" view can
7364
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7365
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7366
+ * management operation, WHY it ran (reason), and its measurable effect
7367
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7368
+ * never fail the operation it records.
7369
+ */
7370
+ /** Which management domain the operation belongs to. */
7371
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7372
+ /** The kind of management operation performed. */
7373
+ var OpsLogOpSchema = _enum([
7374
+ "prune",
7375
+ "manual-delete",
7376
+ "rescan",
7377
+ "retention-run"
7378
+ ]);
7379
+ /** Why the operation ran. */
7380
+ var OpsLogReasonSchema = _enum([
7381
+ "retention",
7382
+ "quota",
7383
+ "manual",
7384
+ "operator"
7385
+ ]);
7386
+ /** One audit row, shared verbatim by both domains. */
7387
+ var OpsLogEntrySchema = object({
7388
+ /** Unique row id. */
7389
+ id: string(),
7390
+ /** Epoch ms the operation completed. */
7391
+ at: number(),
7392
+ domain: OpsLogDomainSchema,
7393
+ op: OpsLogOpSchema,
7394
+ reason: OpsLogReasonSchema,
7395
+ /** The camera the op targeted; null for a cluster/global op. */
7396
+ deviceId: number().nullable(),
7397
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7398
+ nodeId: string(),
7399
+ /** Buckets / rows deleted (op-specific unit). */
7400
+ itemsAffected: number(),
7401
+ /** Bytes reclaimed by the op (0 when not measurable). */
7402
+ bytesReclaimed: number(),
7403
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7404
+ detail: string().nullable(),
7405
+ /** Who/what triggered the op. */
7406
+ actor: string()
7407
+ });
7408
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7409
+ var OpsLogQueryInputSchema = object({
7410
+ /** Restrict to a single camera; omit for every row. */
7411
+ deviceId: number().optional(),
7412
+ /** Max rows returned, newest-first. */
7413
+ limit: number().int().min(1).max(1e3).optional()
7414
+ });
7415
+ /**
7317
7416
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7318
7417
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7319
7418
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -14669,7 +14768,11 @@ array(ZoneRuleSchema).readonly();
14669
14768
  * Extend the enum here when a new gating consumer comes online (audio
14670
14769
  * gating, alert filtering, …) — no other surface needs to change.
14671
14770
  */
14672
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
14771
+ var ZoneRuleStageEnum = _enum([
14772
+ "motion",
14773
+ "detection",
14774
+ "package"
14775
+ ]);
14673
14776
  /**
14674
14777
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
14675
14778
  * `state` getter looks up the cap definition here to construct a
@@ -14763,16 +14866,25 @@ var DEVICE_LOCAL_STATE_CAPS = {
14763
14866
  })
14764
14867
  },
14765
14868
  /**
14766
- * Runtime-state slice — both stages mirrored together so consumers
14869
+ * Runtime-state slice — every stage mirrored together so consumers
14767
14870
  * see one reactive handle (`device.state.zoneRules.value`) instead
14768
- * of two. Bulk-replace mutations on either stage write the full
14769
- * `{motion, detection}` shape, so subscribers always get the
14871
+ * of one per stage. Bulk-replace mutations on any stage write the full
14872
+ * `{motion, detection, package}` shape, so subscribers always get the
14770
14873
  * complete current set. Consumers that only care about one stage
14771
14874
  * just read the matching property.
14875
+ *
14876
+ * `package` backs the package-drop detector — a package zone is a
14877
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
14878
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14879
+ * The orchestrator provider writes this stage as a first-class slice
14880
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
14881
+ * package}` shape, so consumers read the current package rules directly
14882
+ * off `device.state.zoneRules.value.package`.
14772
14883
  */
14773
14884
  runtimeState: object({
14774
14885
  motion: array(ZoneRuleSchema).readonly(),
14775
- detection: array(ZoneRuleSchema).readonly()
14886
+ detection: array(ZoneRuleSchema).readonly(),
14887
+ package: array(ZoneRuleSchema).readonly()
14776
14888
  })
14777
14889
  },
14778
14890
  zones: zonesCapability
@@ -18734,6 +18846,7 @@ var EventKindIconSchema = _enum([
18734
18846
  "smoke",
18735
18847
  "water",
18736
18848
  "button",
18849
+ "package",
18737
18850
  "generic"
18738
18851
  ]);
18739
18852
  var EventKindCategorySchema = _enum([
@@ -18741,7 +18854,8 @@ var EventKindCategorySchema = _enum([
18741
18854
  "audio",
18742
18855
  "detection",
18743
18856
  "sensor",
18744
- "custom"
18857
+ "custom",
18858
+ "package"
18745
18859
  ]);
18746
18860
  var EventKindDescriptorSchema = object({
18747
18861
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -19082,6 +19196,26 @@ var TrackCascadeCountsSchema = object({
19082
19196
  /** Per-track CLIP search vectors removed (best-effort). */
19083
19197
  embeddings: number().int()
19084
19198
  });
19199
+ /** Event-store footprint for one camera. */
19200
+ var EventStoreDeviceFootprintSchema = object({
19201
+ deviceId: number(),
19202
+ /** Persisted event rows (motion + object + audio) for the camera. */
19203
+ rows: number().int(),
19204
+ /** Event-owned media bytes on disk for the camera. */
19205
+ bytes: number().int()
19206
+ });
19207
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19208
+ var EventStoreFootprintSchema = object({
19209
+ totalRows: number().int(),
19210
+ totalBytes: number().int(),
19211
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19212
+ });
19213
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19214
+ var EventPruneCountsSchema = object({
19215
+ motion: number().int(),
19216
+ object: number().int(),
19217
+ audio: number().int()
19218
+ });
19085
19219
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19086
19220
  deviceId: number(),
19087
19221
  trackId: string()
@@ -19145,6 +19279,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19145
19279
  }), {
19146
19280
  kind: "mutation",
19147
19281
  auth: "admin"
19282
+ }), method(object({}), EventStoreFootprintSchema, {
19283
+ kind: "query",
19284
+ auth: "admin"
19285
+ }), method(object({
19286
+ olderThanMs: number(),
19287
+ reason: OpsLogReasonSchema.optional()
19288
+ }), EventPruneCountsSchema, {
19289
+ kind: "mutation",
19290
+ auth: "admin"
19291
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19292
+ kind: "mutation",
19293
+ auth: "admin"
19294
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19295
+ kind: "query",
19296
+ auth: "admin"
19148
19297
  }), method(object({
19149
19298
  eventId: string(),
19150
19299
  kind: MediaFileKindEnum.optional()
@@ -22378,13 +22527,131 @@ method(object({
22378
22527
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22379
22528
  kind: "mutation",
22380
22529
  auth: "admin"
22381
- }), method(object({ deviceId: number() }), object({
22530
+ }), method(object({
22531
+ deviceId: number(),
22532
+ reason: OpsLogReasonSchema.optional()
22533
+ }), object({
22382
22534
  floorMs: number().nullable(),
22383
22535
  deletedBuckets: number().int(),
22384
22536
  reclaimedBytes: number().int()
22385
22537
  }), {
22386
22538
  kind: "mutation",
22387
22539
  auth: "admin"
22540
+ }), method(object({
22541
+ deviceId: number(),
22542
+ fromMs: number().optional(),
22543
+ toMs: number().optional()
22544
+ }), object({
22545
+ deletedBuckets: number().int(),
22546
+ reclaimedBytes: number().int()
22547
+ }), {
22548
+ kind: "mutation",
22549
+ auth: "admin"
22550
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22551
+ kind: "query",
22552
+ auth: "admin"
22553
+ });
22554
+ /**
22555
+ * `recordingExport` cap — render a footage time range into a single downloadable
22556
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
22557
+ * bounded lifetime with a durable history, auto-expiry, and optional
22558
+ * delete-after-download.
22559
+ *
22560
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
22561
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
22562
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
22563
+ * input and dispatch to that single provider; the render runs on the node that
22564
+ * owns the footage (no cross-node segment transfer). Download rides the
22565
+ * framework addon data-plane. State persists in a DurableState blob (NOT
22566
+ * SQLite); history rows survive file deletion for audit.
22567
+ */
22568
+ /** Playback-speed multiplier for the render (1 = realtime). */
22569
+ var ExportSpeedSchema = number().min(.25).max(32);
22570
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
22571
+ var ExportTimelapseSchema = object({
22572
+ everyMs: number().int().positive(),
22573
+ outputFps: number().int().min(1).max(60).optional()
22574
+ });
22575
+ /**
22576
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
22577
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
22578
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
22579
+ * `deleteAfterDownload` removes it shortly after the first complete download.
22580
+ */
22581
+ var ExportOptionsSchema = object({
22582
+ speed: ExportSpeedSchema.optional(),
22583
+ timelapse: ExportTimelapseSchema.optional(),
22584
+ includeAudio: boolean(),
22585
+ maxLifeMs: number().int().positive(),
22586
+ deleteAfterDownload: boolean(),
22587
+ title: string().max(200).optional()
22588
+ }).superRefine((v, ctx) => {
22589
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
22590
+ code: ZodIssueCode.custom,
22591
+ message: "speed and timelapse are mutually exclusive",
22592
+ path: ["timelapse"]
22593
+ });
22594
+ });
22595
+ var ExportStateSchema = _enum([
22596
+ "queued",
22597
+ "rendering",
22598
+ "ready",
22599
+ "failed",
22600
+ "expired",
22601
+ "deleted"
22602
+ ]);
22603
+ /** One export job / history row. */
22604
+ var ExportRecordSchema = object({
22605
+ id: string(),
22606
+ deviceId: number(),
22607
+ profile: string(),
22608
+ fromMs: number(),
22609
+ toMs: number(),
22610
+ options: ExportOptionsSchema,
22611
+ state: ExportStateSchema,
22612
+ /** 0–100 while rendering; null otherwise. */
22613
+ progressPct: number().nullable(),
22614
+ /** File size once ready; null before. */
22615
+ fileBytes: number().nullable(),
22616
+ expiresAt: number(),
22617
+ deleteAfterDownload: boolean(),
22618
+ /** Epoch of the first complete download; null until then. */
22619
+ downloadedAt: number().nullable(),
22620
+ createdAt: number(),
22621
+ /** User id/name that requested the export. */
22622
+ createdBy: string(),
22623
+ /** Failure reason when state is 'failed'; null otherwise. */
22624
+ error: string().nullable()
22625
+ });
22626
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
22627
+ var ExportDownloadSchema = object({
22628
+ url: string(),
22629
+ endpoints: array(string())
22630
+ });
22631
+ method(object({
22632
+ deviceId: number(),
22633
+ profile: string(),
22634
+ fromMs: number(),
22635
+ toMs: number(),
22636
+ options: ExportOptionsSchema
22637
+ }), ExportRecordSchema, {
22638
+ kind: "mutation",
22639
+ auth: "protected"
22640
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
22641
+ kind: "query",
22642
+ auth: "protected"
22643
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22644
+ kind: "query",
22645
+ auth: "protected"
22646
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22647
+ kind: "mutation",
22648
+ auth: "protected"
22649
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22650
+ kind: "mutation",
22651
+ auth: "protected"
22652
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
22653
+ kind: "query",
22654
+ auth: "protected"
22388
22655
  });
22389
22656
  /**
22390
22657
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
@@ -25404,6 +25671,12 @@ Object.freeze({
25404
25671
  addonId: null,
25405
25672
  access: "delete"
25406
25673
  },
25674
+ "pipelineAnalytics.deleteDeviceEvents": {
25675
+ capName: "pipeline-analytics",
25676
+ capScope: "device",
25677
+ addonId: null,
25678
+ access: "delete"
25679
+ },
25407
25680
  "pipelineAnalytics.deleteTracks": {
25408
25681
  capName: "pipeline-analytics",
25409
25682
  capScope: "device",
@@ -25434,6 +25707,12 @@ Object.freeze({
25434
25707
  addonId: null,
25435
25708
  access: "view"
25436
25709
  },
25710
+ "pipelineAnalytics.getEventStoreFootprint": {
25711
+ capName: "pipeline-analytics",
25712
+ capScope: "device",
25713
+ addonId: null,
25714
+ access: "view"
25715
+ },
25437
25716
  "pipelineAnalytics.getKeyEvents": {
25438
25717
  capName: "pipeline-analytics",
25439
25718
  capScope: "device",
@@ -25476,6 +25755,12 @@ Object.freeze({
25476
25755
  addonId: null,
25477
25756
  access: "view"
25478
25757
  },
25758
+ "pipelineAnalytics.listOpsLog": {
25759
+ capName: "pipeline-analytics",
25760
+ capScope: "device",
25761
+ addonId: null,
25762
+ access: "view"
25763
+ },
25479
25764
  "pipelineAnalytics.listRecentTracks": {
25480
25765
  capName: "pipeline-analytics",
25481
25766
  capScope: "device",
@@ -25488,6 +25773,12 @@ Object.freeze({
25488
25773
  addonId: null,
25489
25774
  access: "view"
25490
25775
  },
25776
+ "pipelineAnalytics.pruneEvents": {
25777
+ capName: "pipeline-analytics",
25778
+ capScope: "device",
25779
+ addonId: null,
25780
+ access: "create"
25781
+ },
25491
25782
  "pipelineAnalytics.pruneEventsBefore": {
25492
25783
  capName: "pipeline-analytics",
25493
25784
  capScope: "device",
@@ -26250,6 +26541,12 @@ Object.freeze({
26250
26541
  addonId: null,
26251
26542
  access: "create"
26252
26543
  },
26544
+ "recording.deleteFootprint": {
26545
+ capName: "recording",
26546
+ capScope: "system",
26547
+ addonId: null,
26548
+ access: "delete"
26549
+ },
26253
26550
  "recording.getAvailability": {
26254
26551
  capName: "recording",
26255
26552
  capScope: "system",
@@ -26280,6 +26577,12 @@ Object.freeze({
26280
26577
  addonId: null,
26281
26578
  access: "view"
26282
26579
  },
26580
+ "recording.listOpsLog": {
26581
+ capName: "recording",
26582
+ capScope: "system",
26583
+ addonId: null,
26584
+ access: "view"
26585
+ },
26283
26586
  "recording.locateSegment": {
26284
26587
  capName: "recording",
26285
26588
  capScope: "system",
@@ -26310,6 +26613,42 @@ Object.freeze({
26310
26613
  addonId: null,
26311
26614
  access: "create"
26312
26615
  },
26616
+ "recordingExport.cancelExport": {
26617
+ capName: "recordingExport",
26618
+ capScope: "system",
26619
+ addonId: null,
26620
+ access: "create"
26621
+ },
26622
+ "recordingExport.createExport": {
26623
+ capName: "recordingExport",
26624
+ capScope: "system",
26625
+ addonId: null,
26626
+ access: "create"
26627
+ },
26628
+ "recordingExport.deleteExport": {
26629
+ capName: "recordingExport",
26630
+ capScope: "system",
26631
+ addonId: null,
26632
+ access: "delete"
26633
+ },
26634
+ "recordingExport.getDownloadUrl": {
26635
+ capName: "recordingExport",
26636
+ capScope: "system",
26637
+ addonId: null,
26638
+ access: "view"
26639
+ },
26640
+ "recordingExport.getExport": {
26641
+ capName: "recordingExport",
26642
+ capScope: "system",
26643
+ addonId: null,
26644
+ access: "view"
26645
+ },
26646
+ "recordingExport.listExports": {
26647
+ capName: "recordingExport",
26648
+ capScope: "system",
26649
+ addonId: null,
26650
+ access: "view"
26651
+ },
26313
26652
  "sceneMonitor.captureReference": {
26314
26653
  capName: "scene-monitor",
26315
26654
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-velux",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Velux KLF-200 device-provider addon for CamStack — local-gateway window + shutter covers (positional, no tilt)",
5
5
  "keywords": [
6
6
  "camstack",