@camstack/addon-provider-homematic 1.1.24 → 1.1.26

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
@@ -8,7 +8,7 @@ let http = require("http");
8
8
  let crypto$1 = require("crypto");
9
9
  let fs_promises = require("fs/promises");
10
10
  let path = require("path");
11
- //#region ../types/dist/event-category-H4AVePnn.mjs
11
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
12
12
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
13
13
  EventCategory["SystemBoot"] = "system.boot";
14
14
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -161,6 +161,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
161
161
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
162
162
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
163
163
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
164
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
165
+ * progress bar the client reconciles via `recordingExport.getExport`. */
166
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
167
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
168
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
164
169
  EventCategory["DetectionEvent"] = "detection.event";
165
170
  EventCategory["SessionTrackNew"] = "session.track.new";
166
171
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -450,6 +455,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
450
455
  */
451
456
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
452
457
  /**
458
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
459
+ * a package zone — a newly-appeared stationary object of a package class
460
+ * that cleared the class / zone / dwell / size gates. Payload:
461
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
462
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
463
+ * Telemetry (D8): the durable record is the `package-events` store row;
464
+ * this bus topic drives notifier rules + live UI. See
465
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
466
+ */
467
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
468
+ /**
469
+ * Fired by `addon-post-analysis` when a previously-delivered package
470
+ * leaves its zone (the stationary entry departed — moved or swept).
471
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
472
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
473
+ * Telemetry (D8). See package-zones-design §5.2.
474
+ */
475
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
476
+ /**
453
477
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
454
478
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
455
479
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5202,6 +5226,25 @@ function preprocess(fn, schema) {
5202
5226
  out: schema
5203
5227
  });
5204
5228
  }
5229
+ //#endregion
5230
+ //#region ../../node_modules/zod/v4/classic/compat.js
5231
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5232
+ var ZodIssueCode = {
5233
+ invalid_type: "invalid_type",
5234
+ too_big: "too_big",
5235
+ too_small: "too_small",
5236
+ invalid_format: "invalid_format",
5237
+ not_multiple_of: "not_multiple_of",
5238
+ unrecognized_keys: "unrecognized_keys",
5239
+ invalid_union: "invalid_union",
5240
+ invalid_key: "invalid_key",
5241
+ invalid_element: "invalid_element",
5242
+ invalid_value: "invalid_value",
5243
+ custom: "custom"
5244
+ };
5245
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5246
+ var ZodFirstPartyTypeKind;
5247
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5205
5248
  Object.fromEntries([
5206
5249
  {
5207
5250
  id: "overview",
@@ -7324,6 +7367,62 @@ var RecordingConfigSchema = object({
7324
7367
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7325
7368
  });
7326
7369
  /**
7370
+ * Ops-log — the durable, append-only operations audit shared by the
7371
+ * recordings and events management surfaces.
7372
+ *
7373
+ * ONE row shape is reused for both domains so a single "Activity" view can
7374
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7375
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7376
+ * management operation, WHY it ran (reason), and its measurable effect
7377
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7378
+ * never fail the operation it records.
7379
+ */
7380
+ /** Which management domain the operation belongs to. */
7381
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7382
+ /** The kind of management operation performed. */
7383
+ var OpsLogOpSchema = _enum([
7384
+ "prune",
7385
+ "manual-delete",
7386
+ "rescan",
7387
+ "retention-run"
7388
+ ]);
7389
+ /** Why the operation ran. */
7390
+ var OpsLogReasonSchema = _enum([
7391
+ "retention",
7392
+ "quota",
7393
+ "manual",
7394
+ "operator"
7395
+ ]);
7396
+ /** One audit row, shared verbatim by both domains. */
7397
+ var OpsLogEntrySchema = object({
7398
+ /** Unique row id. */
7399
+ id: string(),
7400
+ /** Epoch ms the operation completed. */
7401
+ at: number(),
7402
+ domain: OpsLogDomainSchema,
7403
+ op: OpsLogOpSchema,
7404
+ reason: OpsLogReasonSchema,
7405
+ /** The camera the op targeted; null for a cluster/global op. */
7406
+ deviceId: number().nullable(),
7407
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7408
+ nodeId: string(),
7409
+ /** Buckets / rows deleted (op-specific unit). */
7410
+ itemsAffected: number(),
7411
+ /** Bytes reclaimed by the op (0 when not measurable). */
7412
+ bytesReclaimed: number(),
7413
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7414
+ detail: string().nullable(),
7415
+ /** Who/what triggered the op. */
7416
+ actor: string()
7417
+ });
7418
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7419
+ var OpsLogQueryInputSchema = object({
7420
+ /** Restrict to a single camera; omit for every row. */
7421
+ deviceId: number().optional(),
7422
+ /** Max rows returned, newest-first. */
7423
+ limit: number().int().min(1).max(1e3).optional()
7424
+ });
7425
+ /**
7327
7426
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7328
7427
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7329
7428
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -14679,7 +14778,11 @@ array(ZoneRuleSchema).readonly();
14679
14778
  * Extend the enum here when a new gating consumer comes online (audio
14680
14779
  * gating, alert filtering, …) — no other surface needs to change.
14681
14780
  */
14682
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
14781
+ var ZoneRuleStageEnum = _enum([
14782
+ "motion",
14783
+ "detection",
14784
+ "package"
14785
+ ]);
14683
14786
  /**
14684
14787
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
14685
14788
  * `state` getter looks up the cap definition here to construct a
@@ -14773,16 +14876,25 @@ var DEVICE_LOCAL_STATE_CAPS = {
14773
14876
  })
14774
14877
  },
14775
14878
  /**
14776
- * Runtime-state slice — both stages mirrored together so consumers
14879
+ * Runtime-state slice — every stage mirrored together so consumers
14777
14880
  * see one reactive handle (`device.state.zoneRules.value`) instead
14778
- * of two. Bulk-replace mutations on either stage write the full
14779
- * `{motion, detection}` shape, so subscribers always get the
14881
+ * of one per stage. Bulk-replace mutations on any stage write the full
14882
+ * `{motion, detection, package}` shape, so subscribers always get the
14780
14883
  * complete current set. Consumers that only care about one stage
14781
14884
  * just read the matching property.
14885
+ *
14886
+ * `package` backs the package-drop detector — a package zone is a
14887
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
14888
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14889
+ * The orchestrator provider writes this stage as a first-class slice
14890
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
14891
+ * package}` shape, so consumers read the current package rules directly
14892
+ * off `device.state.zoneRules.value.package`.
14782
14893
  */
14783
14894
  runtimeState: object({
14784
14895
  motion: array(ZoneRuleSchema).readonly(),
14785
- detection: array(ZoneRuleSchema).readonly()
14896
+ detection: array(ZoneRuleSchema).readonly(),
14897
+ package: array(ZoneRuleSchema).readonly()
14786
14898
  })
14787
14899
  },
14788
14900
  zones: zonesCapability
@@ -18810,6 +18922,7 @@ var EventKindIconSchema = _enum([
18810
18922
  "smoke",
18811
18923
  "water",
18812
18924
  "button",
18925
+ "package",
18813
18926
  "generic"
18814
18927
  ]);
18815
18928
  var EventKindCategorySchema = _enum([
@@ -18817,7 +18930,8 @@ var EventKindCategorySchema = _enum([
18817
18930
  "audio",
18818
18931
  "detection",
18819
18932
  "sensor",
18820
- "custom"
18933
+ "custom",
18934
+ "package"
18821
18935
  ]);
18822
18936
  var EventKindDescriptorSchema = object({
18823
18937
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -19158,6 +19272,26 @@ var TrackCascadeCountsSchema = object({
19158
19272
  /** Per-track CLIP search vectors removed (best-effort). */
19159
19273
  embeddings: number().int()
19160
19274
  });
19275
+ /** Event-store footprint for one camera. */
19276
+ var EventStoreDeviceFootprintSchema = object({
19277
+ deviceId: number(),
19278
+ /** Persisted event rows (motion + object + audio) for the camera. */
19279
+ rows: number().int(),
19280
+ /** Event-owned media bytes on disk for the camera. */
19281
+ bytes: number().int()
19282
+ });
19283
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19284
+ var EventStoreFootprintSchema = object({
19285
+ totalRows: number().int(),
19286
+ totalBytes: number().int(),
19287
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19288
+ });
19289
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19290
+ var EventPruneCountsSchema = object({
19291
+ motion: number().int(),
19292
+ object: number().int(),
19293
+ audio: number().int()
19294
+ });
19161
19295
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19162
19296
  deviceId: number(),
19163
19297
  trackId: string()
@@ -19221,6 +19355,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19221
19355
  }), {
19222
19356
  kind: "mutation",
19223
19357
  auth: "admin"
19358
+ }), method(object({}), EventStoreFootprintSchema, {
19359
+ kind: "query",
19360
+ auth: "admin"
19361
+ }), method(object({
19362
+ olderThanMs: number(),
19363
+ reason: OpsLogReasonSchema.optional()
19364
+ }), EventPruneCountsSchema, {
19365
+ kind: "mutation",
19366
+ auth: "admin"
19367
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19368
+ kind: "mutation",
19369
+ auth: "admin"
19370
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19371
+ kind: "query",
19372
+ auth: "admin"
19224
19373
  }), method(object({
19225
19374
  eventId: string(),
19226
19375
  kind: MediaFileKindEnum.optional()
@@ -22454,13 +22603,131 @@ method(object({
22454
22603
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22455
22604
  kind: "mutation",
22456
22605
  auth: "admin"
22457
- }), method(object({ deviceId: number() }), object({
22606
+ }), method(object({
22607
+ deviceId: number(),
22608
+ reason: OpsLogReasonSchema.optional()
22609
+ }), object({
22458
22610
  floorMs: number().nullable(),
22459
22611
  deletedBuckets: number().int(),
22460
22612
  reclaimedBytes: number().int()
22461
22613
  }), {
22462
22614
  kind: "mutation",
22463
22615
  auth: "admin"
22616
+ }), method(object({
22617
+ deviceId: number(),
22618
+ fromMs: number().optional(),
22619
+ toMs: number().optional()
22620
+ }), object({
22621
+ deletedBuckets: number().int(),
22622
+ reclaimedBytes: number().int()
22623
+ }), {
22624
+ kind: "mutation",
22625
+ auth: "admin"
22626
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22627
+ kind: "query",
22628
+ auth: "admin"
22629
+ });
22630
+ /**
22631
+ * `recordingExport` cap — render a footage time range into a single downloadable
22632
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
22633
+ * bounded lifetime with a durable history, auto-expiry, and optional
22634
+ * delete-after-download.
22635
+ *
22636
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
22637
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
22638
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
22639
+ * input and dispatch to that single provider; the render runs on the node that
22640
+ * owns the footage (no cross-node segment transfer). Download rides the
22641
+ * framework addon data-plane. State persists in a DurableState blob (NOT
22642
+ * SQLite); history rows survive file deletion for audit.
22643
+ */
22644
+ /** Playback-speed multiplier for the render (1 = realtime). */
22645
+ var ExportSpeedSchema = number().min(.25).max(32);
22646
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
22647
+ var ExportTimelapseSchema = object({
22648
+ everyMs: number().int().positive(),
22649
+ outputFps: number().int().min(1).max(60).optional()
22650
+ });
22651
+ /**
22652
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
22653
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
22654
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
22655
+ * `deleteAfterDownload` removes it shortly after the first complete download.
22656
+ */
22657
+ var ExportOptionsSchema = object({
22658
+ speed: ExportSpeedSchema.optional(),
22659
+ timelapse: ExportTimelapseSchema.optional(),
22660
+ includeAudio: boolean(),
22661
+ maxLifeMs: number().int().positive(),
22662
+ deleteAfterDownload: boolean(),
22663
+ title: string().max(200).optional()
22664
+ }).superRefine((v, ctx) => {
22665
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
22666
+ code: ZodIssueCode.custom,
22667
+ message: "speed and timelapse are mutually exclusive",
22668
+ path: ["timelapse"]
22669
+ });
22670
+ });
22671
+ var ExportStateSchema = _enum([
22672
+ "queued",
22673
+ "rendering",
22674
+ "ready",
22675
+ "failed",
22676
+ "expired",
22677
+ "deleted"
22678
+ ]);
22679
+ /** One export job / history row. */
22680
+ var ExportRecordSchema = object({
22681
+ id: string(),
22682
+ deviceId: number(),
22683
+ profile: string(),
22684
+ fromMs: number(),
22685
+ toMs: number(),
22686
+ options: ExportOptionsSchema,
22687
+ state: ExportStateSchema,
22688
+ /** 0–100 while rendering; null otherwise. */
22689
+ progressPct: number().nullable(),
22690
+ /** File size once ready; null before. */
22691
+ fileBytes: number().nullable(),
22692
+ expiresAt: number(),
22693
+ deleteAfterDownload: boolean(),
22694
+ /** Epoch of the first complete download; null until then. */
22695
+ downloadedAt: number().nullable(),
22696
+ createdAt: number(),
22697
+ /** User id/name that requested the export. */
22698
+ createdBy: string(),
22699
+ /** Failure reason when state is 'failed'; null otherwise. */
22700
+ error: string().nullable()
22701
+ });
22702
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
22703
+ var ExportDownloadSchema = object({
22704
+ url: string(),
22705
+ endpoints: array(string())
22706
+ });
22707
+ method(object({
22708
+ deviceId: number(),
22709
+ profile: string(),
22710
+ fromMs: number(),
22711
+ toMs: number(),
22712
+ options: ExportOptionsSchema
22713
+ }), ExportRecordSchema, {
22714
+ kind: "mutation",
22715
+ auth: "protected"
22716
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
22717
+ kind: "query",
22718
+ auth: "protected"
22719
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22720
+ kind: "query",
22721
+ auth: "protected"
22722
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22723
+ kind: "mutation",
22724
+ auth: "protected"
22725
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22726
+ kind: "mutation",
22727
+ auth: "protected"
22728
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
22729
+ kind: "query",
22730
+ auth: "protected"
22464
22731
  });
22465
22732
  /**
22466
22733
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
@@ -25480,6 +25747,12 @@ Object.freeze({
25480
25747
  addonId: null,
25481
25748
  access: "delete"
25482
25749
  },
25750
+ "pipelineAnalytics.deleteDeviceEvents": {
25751
+ capName: "pipeline-analytics",
25752
+ capScope: "device",
25753
+ addonId: null,
25754
+ access: "delete"
25755
+ },
25483
25756
  "pipelineAnalytics.deleteTracks": {
25484
25757
  capName: "pipeline-analytics",
25485
25758
  capScope: "device",
@@ -25510,6 +25783,12 @@ Object.freeze({
25510
25783
  addonId: null,
25511
25784
  access: "view"
25512
25785
  },
25786
+ "pipelineAnalytics.getEventStoreFootprint": {
25787
+ capName: "pipeline-analytics",
25788
+ capScope: "device",
25789
+ addonId: null,
25790
+ access: "view"
25791
+ },
25513
25792
  "pipelineAnalytics.getKeyEvents": {
25514
25793
  capName: "pipeline-analytics",
25515
25794
  capScope: "device",
@@ -25552,6 +25831,12 @@ Object.freeze({
25552
25831
  addonId: null,
25553
25832
  access: "view"
25554
25833
  },
25834
+ "pipelineAnalytics.listOpsLog": {
25835
+ capName: "pipeline-analytics",
25836
+ capScope: "device",
25837
+ addonId: null,
25838
+ access: "view"
25839
+ },
25555
25840
  "pipelineAnalytics.listRecentTracks": {
25556
25841
  capName: "pipeline-analytics",
25557
25842
  capScope: "device",
@@ -25564,6 +25849,12 @@ Object.freeze({
25564
25849
  addonId: null,
25565
25850
  access: "view"
25566
25851
  },
25852
+ "pipelineAnalytics.pruneEvents": {
25853
+ capName: "pipeline-analytics",
25854
+ capScope: "device",
25855
+ addonId: null,
25856
+ access: "create"
25857
+ },
25567
25858
  "pipelineAnalytics.pruneEventsBefore": {
25568
25859
  capName: "pipeline-analytics",
25569
25860
  capScope: "device",
@@ -26326,6 +26617,12 @@ Object.freeze({
26326
26617
  addonId: null,
26327
26618
  access: "create"
26328
26619
  },
26620
+ "recording.deleteFootprint": {
26621
+ capName: "recording",
26622
+ capScope: "system",
26623
+ addonId: null,
26624
+ access: "delete"
26625
+ },
26329
26626
  "recording.getAvailability": {
26330
26627
  capName: "recording",
26331
26628
  capScope: "system",
@@ -26356,6 +26653,12 @@ Object.freeze({
26356
26653
  addonId: null,
26357
26654
  access: "view"
26358
26655
  },
26656
+ "recording.listOpsLog": {
26657
+ capName: "recording",
26658
+ capScope: "system",
26659
+ addonId: null,
26660
+ access: "view"
26661
+ },
26359
26662
  "recording.locateSegment": {
26360
26663
  capName: "recording",
26361
26664
  capScope: "system",
@@ -26386,6 +26689,42 @@ Object.freeze({
26386
26689
  addonId: null,
26387
26690
  access: "create"
26388
26691
  },
26692
+ "recordingExport.cancelExport": {
26693
+ capName: "recordingExport",
26694
+ capScope: "system",
26695
+ addonId: null,
26696
+ access: "create"
26697
+ },
26698
+ "recordingExport.createExport": {
26699
+ capName: "recordingExport",
26700
+ capScope: "system",
26701
+ addonId: null,
26702
+ access: "create"
26703
+ },
26704
+ "recordingExport.deleteExport": {
26705
+ capName: "recordingExport",
26706
+ capScope: "system",
26707
+ addonId: null,
26708
+ access: "delete"
26709
+ },
26710
+ "recordingExport.getDownloadUrl": {
26711
+ capName: "recordingExport",
26712
+ capScope: "system",
26713
+ addonId: null,
26714
+ access: "view"
26715
+ },
26716
+ "recordingExport.getExport": {
26717
+ capName: "recordingExport",
26718
+ capScope: "system",
26719
+ addonId: null,
26720
+ access: "view"
26721
+ },
26722
+ "recordingExport.listExports": {
26723
+ capName: "recordingExport",
26724
+ capScope: "system",
26725
+ addonId: null,
26726
+ access: "view"
26727
+ },
26389
26728
  "sceneMonitor.captureReference": {
26390
26729
  capName: "scene-monitor",
26391
26730
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -9,7 +9,7 @@ import { join } from "path";
9
9
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
10
10
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
11
11
  //#endregion
12
- //#region ../types/dist/event-category-H4AVePnn.mjs
12
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
13
13
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
14
14
  EventCategory["SystemBoot"] = "system.boot";
15
15
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -162,6 +162,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
162
162
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
163
163
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
164
164
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
165
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
166
+ * progress bar the client reconciles via `recordingExport.getExport`. */
167
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
168
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
169
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
165
170
  EventCategory["DetectionEvent"] = "detection.event";
166
171
  EventCategory["SessionTrackNew"] = "session.track.new";
167
172
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -451,6 +456,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
451
456
  */
452
457
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
453
458
  /**
459
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
460
+ * a package zone — a newly-appeared stationary object of a package class
461
+ * that cleared the class / zone / dwell / size gates. Payload:
462
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
463
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
464
+ * Telemetry (D8): the durable record is the `package-events` store row;
465
+ * this bus topic drives notifier rules + live UI. See
466
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
467
+ */
468
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
469
+ /**
470
+ * Fired by `addon-post-analysis` when a previously-delivered package
471
+ * leaves its zone (the stationary entry departed — moved or swept).
472
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
473
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
474
+ * Telemetry (D8). See package-zones-design §5.2.
475
+ */
476
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
477
+ /**
454
478
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
455
479
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
456
480
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5203,6 +5227,25 @@ function preprocess(fn, schema) {
5203
5227
  out: schema
5204
5228
  });
5205
5229
  }
5230
+ //#endregion
5231
+ //#region ../../node_modules/zod/v4/classic/compat.js
5232
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5233
+ var ZodIssueCode = {
5234
+ invalid_type: "invalid_type",
5235
+ too_big: "too_big",
5236
+ too_small: "too_small",
5237
+ invalid_format: "invalid_format",
5238
+ not_multiple_of: "not_multiple_of",
5239
+ unrecognized_keys: "unrecognized_keys",
5240
+ invalid_union: "invalid_union",
5241
+ invalid_key: "invalid_key",
5242
+ invalid_element: "invalid_element",
5243
+ invalid_value: "invalid_value",
5244
+ custom: "custom"
5245
+ };
5246
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5247
+ var ZodFirstPartyTypeKind;
5248
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5206
5249
  Object.fromEntries([
5207
5250
  {
5208
5251
  id: "overview",
@@ -7325,6 +7368,62 @@ var RecordingConfigSchema = object({
7325
7368
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7326
7369
  });
7327
7370
  /**
7371
+ * Ops-log — the durable, append-only operations audit shared by the
7372
+ * recordings and events management surfaces.
7373
+ *
7374
+ * ONE row shape is reused for both domains so a single "Activity" view can
7375
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7376
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7377
+ * management operation, WHY it ran (reason), and its measurable effect
7378
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7379
+ * never fail the operation it records.
7380
+ */
7381
+ /** Which management domain the operation belongs to. */
7382
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7383
+ /** The kind of management operation performed. */
7384
+ var OpsLogOpSchema = _enum([
7385
+ "prune",
7386
+ "manual-delete",
7387
+ "rescan",
7388
+ "retention-run"
7389
+ ]);
7390
+ /** Why the operation ran. */
7391
+ var OpsLogReasonSchema = _enum([
7392
+ "retention",
7393
+ "quota",
7394
+ "manual",
7395
+ "operator"
7396
+ ]);
7397
+ /** One audit row, shared verbatim by both domains. */
7398
+ var OpsLogEntrySchema = object({
7399
+ /** Unique row id. */
7400
+ id: string(),
7401
+ /** Epoch ms the operation completed. */
7402
+ at: number(),
7403
+ domain: OpsLogDomainSchema,
7404
+ op: OpsLogOpSchema,
7405
+ reason: OpsLogReasonSchema,
7406
+ /** The camera the op targeted; null for a cluster/global op. */
7407
+ deviceId: number().nullable(),
7408
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7409
+ nodeId: string(),
7410
+ /** Buckets / rows deleted (op-specific unit). */
7411
+ itemsAffected: number(),
7412
+ /** Bytes reclaimed by the op (0 when not measurable). */
7413
+ bytesReclaimed: number(),
7414
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7415
+ detail: string().nullable(),
7416
+ /** Who/what triggered the op. */
7417
+ actor: string()
7418
+ });
7419
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7420
+ var OpsLogQueryInputSchema = object({
7421
+ /** Restrict to a single camera; omit for every row. */
7422
+ deviceId: number().optional(),
7423
+ /** Max rows returned, newest-first. */
7424
+ limit: number().int().min(1).max(1e3).optional()
7425
+ });
7426
+ /**
7328
7427
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7329
7428
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7330
7429
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -14680,7 +14779,11 @@ array(ZoneRuleSchema).readonly();
14680
14779
  * Extend the enum here when a new gating consumer comes online (audio
14681
14780
  * gating, alert filtering, …) — no other surface needs to change.
14682
14781
  */
14683
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
14782
+ var ZoneRuleStageEnum = _enum([
14783
+ "motion",
14784
+ "detection",
14785
+ "package"
14786
+ ]);
14684
14787
  /**
14685
14788
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
14686
14789
  * `state` getter looks up the cap definition here to construct a
@@ -14774,16 +14877,25 @@ var DEVICE_LOCAL_STATE_CAPS = {
14774
14877
  })
14775
14878
  },
14776
14879
  /**
14777
- * Runtime-state slice — both stages mirrored together so consumers
14880
+ * Runtime-state slice — every stage mirrored together so consumers
14778
14881
  * see one reactive handle (`device.state.zoneRules.value`) instead
14779
- * of two. Bulk-replace mutations on either stage write the full
14780
- * `{motion, detection}` shape, so subscribers always get the
14882
+ * of one per stage. Bulk-replace mutations on any stage write the full
14883
+ * `{motion, detection, package}` shape, so subscribers always get the
14781
14884
  * complete current set. Consumers that only care about one stage
14782
14885
  * just read the matching property.
14886
+ *
14887
+ * `package` backs the package-drop detector — a package zone is a
14888
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
14889
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14890
+ * The orchestrator provider writes this stage as a first-class slice
14891
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
14892
+ * package}` shape, so consumers read the current package rules directly
14893
+ * off `device.state.zoneRules.value.package`.
14783
14894
  */
14784
14895
  runtimeState: object({
14785
14896
  motion: array(ZoneRuleSchema).readonly(),
14786
- detection: array(ZoneRuleSchema).readonly()
14897
+ detection: array(ZoneRuleSchema).readonly(),
14898
+ package: array(ZoneRuleSchema).readonly()
14787
14899
  })
14788
14900
  },
14789
14901
  zones: zonesCapability
@@ -18811,6 +18923,7 @@ var EventKindIconSchema = _enum([
18811
18923
  "smoke",
18812
18924
  "water",
18813
18925
  "button",
18926
+ "package",
18814
18927
  "generic"
18815
18928
  ]);
18816
18929
  var EventKindCategorySchema = _enum([
@@ -18818,7 +18931,8 @@ var EventKindCategorySchema = _enum([
18818
18931
  "audio",
18819
18932
  "detection",
18820
18933
  "sensor",
18821
- "custom"
18934
+ "custom",
18935
+ "package"
18822
18936
  ]);
18823
18937
  var EventKindDescriptorSchema = object({
18824
18938
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -19159,6 +19273,26 @@ var TrackCascadeCountsSchema = object({
19159
19273
  /** Per-track CLIP search vectors removed (best-effort). */
19160
19274
  embeddings: number().int()
19161
19275
  });
19276
+ /** Event-store footprint for one camera. */
19277
+ var EventStoreDeviceFootprintSchema = object({
19278
+ deviceId: number(),
19279
+ /** Persisted event rows (motion + object + audio) for the camera. */
19280
+ rows: number().int(),
19281
+ /** Event-owned media bytes on disk for the camera. */
19282
+ bytes: number().int()
19283
+ });
19284
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19285
+ var EventStoreFootprintSchema = object({
19286
+ totalRows: number().int(),
19287
+ totalBytes: number().int(),
19288
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19289
+ });
19290
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19291
+ var EventPruneCountsSchema = object({
19292
+ motion: number().int(),
19293
+ object: number().int(),
19294
+ audio: number().int()
19295
+ });
19162
19296
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19163
19297
  deviceId: number(),
19164
19298
  trackId: string()
@@ -19222,6 +19356,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19222
19356
  }), {
19223
19357
  kind: "mutation",
19224
19358
  auth: "admin"
19359
+ }), method(object({}), EventStoreFootprintSchema, {
19360
+ kind: "query",
19361
+ auth: "admin"
19362
+ }), method(object({
19363
+ olderThanMs: number(),
19364
+ reason: OpsLogReasonSchema.optional()
19365
+ }), EventPruneCountsSchema, {
19366
+ kind: "mutation",
19367
+ auth: "admin"
19368
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19369
+ kind: "mutation",
19370
+ auth: "admin"
19371
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19372
+ kind: "query",
19373
+ auth: "admin"
19225
19374
  }), method(object({
19226
19375
  eventId: string(),
19227
19376
  kind: MediaFileKindEnum.optional()
@@ -22455,13 +22604,131 @@ method(object({
22455
22604
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22456
22605
  kind: "mutation",
22457
22606
  auth: "admin"
22458
- }), method(object({ deviceId: number() }), object({
22607
+ }), method(object({
22608
+ deviceId: number(),
22609
+ reason: OpsLogReasonSchema.optional()
22610
+ }), object({
22459
22611
  floorMs: number().nullable(),
22460
22612
  deletedBuckets: number().int(),
22461
22613
  reclaimedBytes: number().int()
22462
22614
  }), {
22463
22615
  kind: "mutation",
22464
22616
  auth: "admin"
22617
+ }), method(object({
22618
+ deviceId: number(),
22619
+ fromMs: number().optional(),
22620
+ toMs: number().optional()
22621
+ }), object({
22622
+ deletedBuckets: number().int(),
22623
+ reclaimedBytes: number().int()
22624
+ }), {
22625
+ kind: "mutation",
22626
+ auth: "admin"
22627
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22628
+ kind: "query",
22629
+ auth: "admin"
22630
+ });
22631
+ /**
22632
+ * `recordingExport` cap — render a footage time range into a single downloadable
22633
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
22634
+ * bounded lifetime with a durable history, auto-expiry, and optional
22635
+ * delete-after-download.
22636
+ *
22637
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
22638
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
22639
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
22640
+ * input and dispatch to that single provider; the render runs on the node that
22641
+ * owns the footage (no cross-node segment transfer). Download rides the
22642
+ * framework addon data-plane. State persists in a DurableState blob (NOT
22643
+ * SQLite); history rows survive file deletion for audit.
22644
+ */
22645
+ /** Playback-speed multiplier for the render (1 = realtime). */
22646
+ var ExportSpeedSchema = number().min(.25).max(32);
22647
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
22648
+ var ExportTimelapseSchema = object({
22649
+ everyMs: number().int().positive(),
22650
+ outputFps: number().int().min(1).max(60).optional()
22651
+ });
22652
+ /**
22653
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
22654
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
22655
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
22656
+ * `deleteAfterDownload` removes it shortly after the first complete download.
22657
+ */
22658
+ var ExportOptionsSchema = object({
22659
+ speed: ExportSpeedSchema.optional(),
22660
+ timelapse: ExportTimelapseSchema.optional(),
22661
+ includeAudio: boolean(),
22662
+ maxLifeMs: number().int().positive(),
22663
+ deleteAfterDownload: boolean(),
22664
+ title: string().max(200).optional()
22665
+ }).superRefine((v, ctx) => {
22666
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
22667
+ code: ZodIssueCode.custom,
22668
+ message: "speed and timelapse are mutually exclusive",
22669
+ path: ["timelapse"]
22670
+ });
22671
+ });
22672
+ var ExportStateSchema = _enum([
22673
+ "queued",
22674
+ "rendering",
22675
+ "ready",
22676
+ "failed",
22677
+ "expired",
22678
+ "deleted"
22679
+ ]);
22680
+ /** One export job / history row. */
22681
+ var ExportRecordSchema = object({
22682
+ id: string(),
22683
+ deviceId: number(),
22684
+ profile: string(),
22685
+ fromMs: number(),
22686
+ toMs: number(),
22687
+ options: ExportOptionsSchema,
22688
+ state: ExportStateSchema,
22689
+ /** 0–100 while rendering; null otherwise. */
22690
+ progressPct: number().nullable(),
22691
+ /** File size once ready; null before. */
22692
+ fileBytes: number().nullable(),
22693
+ expiresAt: number(),
22694
+ deleteAfterDownload: boolean(),
22695
+ /** Epoch of the first complete download; null until then. */
22696
+ downloadedAt: number().nullable(),
22697
+ createdAt: number(),
22698
+ /** User id/name that requested the export. */
22699
+ createdBy: string(),
22700
+ /** Failure reason when state is 'failed'; null otherwise. */
22701
+ error: string().nullable()
22702
+ });
22703
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
22704
+ var ExportDownloadSchema = object({
22705
+ url: string(),
22706
+ endpoints: array(string())
22707
+ });
22708
+ method(object({
22709
+ deviceId: number(),
22710
+ profile: string(),
22711
+ fromMs: number(),
22712
+ toMs: number(),
22713
+ options: ExportOptionsSchema
22714
+ }), ExportRecordSchema, {
22715
+ kind: "mutation",
22716
+ auth: "protected"
22717
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
22718
+ kind: "query",
22719
+ auth: "protected"
22720
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22721
+ kind: "query",
22722
+ auth: "protected"
22723
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22724
+ kind: "mutation",
22725
+ auth: "protected"
22726
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22727
+ kind: "mutation",
22728
+ auth: "protected"
22729
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
22730
+ kind: "query",
22731
+ auth: "protected"
22465
22732
  });
22466
22733
  /**
22467
22734
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
@@ -25481,6 +25748,12 @@ Object.freeze({
25481
25748
  addonId: null,
25482
25749
  access: "delete"
25483
25750
  },
25751
+ "pipelineAnalytics.deleteDeviceEvents": {
25752
+ capName: "pipeline-analytics",
25753
+ capScope: "device",
25754
+ addonId: null,
25755
+ access: "delete"
25756
+ },
25484
25757
  "pipelineAnalytics.deleteTracks": {
25485
25758
  capName: "pipeline-analytics",
25486
25759
  capScope: "device",
@@ -25511,6 +25784,12 @@ Object.freeze({
25511
25784
  addonId: null,
25512
25785
  access: "view"
25513
25786
  },
25787
+ "pipelineAnalytics.getEventStoreFootprint": {
25788
+ capName: "pipeline-analytics",
25789
+ capScope: "device",
25790
+ addonId: null,
25791
+ access: "view"
25792
+ },
25514
25793
  "pipelineAnalytics.getKeyEvents": {
25515
25794
  capName: "pipeline-analytics",
25516
25795
  capScope: "device",
@@ -25553,6 +25832,12 @@ Object.freeze({
25553
25832
  addonId: null,
25554
25833
  access: "view"
25555
25834
  },
25835
+ "pipelineAnalytics.listOpsLog": {
25836
+ capName: "pipeline-analytics",
25837
+ capScope: "device",
25838
+ addonId: null,
25839
+ access: "view"
25840
+ },
25556
25841
  "pipelineAnalytics.listRecentTracks": {
25557
25842
  capName: "pipeline-analytics",
25558
25843
  capScope: "device",
@@ -25565,6 +25850,12 @@ Object.freeze({
25565
25850
  addonId: null,
25566
25851
  access: "view"
25567
25852
  },
25853
+ "pipelineAnalytics.pruneEvents": {
25854
+ capName: "pipeline-analytics",
25855
+ capScope: "device",
25856
+ addonId: null,
25857
+ access: "create"
25858
+ },
25568
25859
  "pipelineAnalytics.pruneEventsBefore": {
25569
25860
  capName: "pipeline-analytics",
25570
25861
  capScope: "device",
@@ -26327,6 +26618,12 @@ Object.freeze({
26327
26618
  addonId: null,
26328
26619
  access: "create"
26329
26620
  },
26621
+ "recording.deleteFootprint": {
26622
+ capName: "recording",
26623
+ capScope: "system",
26624
+ addonId: null,
26625
+ access: "delete"
26626
+ },
26330
26627
  "recording.getAvailability": {
26331
26628
  capName: "recording",
26332
26629
  capScope: "system",
@@ -26357,6 +26654,12 @@ Object.freeze({
26357
26654
  addonId: null,
26358
26655
  access: "view"
26359
26656
  },
26657
+ "recording.listOpsLog": {
26658
+ capName: "recording",
26659
+ capScope: "system",
26660
+ addonId: null,
26661
+ access: "view"
26662
+ },
26360
26663
  "recording.locateSegment": {
26361
26664
  capName: "recording",
26362
26665
  capScope: "system",
@@ -26387,6 +26690,42 @@ Object.freeze({
26387
26690
  addonId: null,
26388
26691
  access: "create"
26389
26692
  },
26693
+ "recordingExport.cancelExport": {
26694
+ capName: "recordingExport",
26695
+ capScope: "system",
26696
+ addonId: null,
26697
+ access: "create"
26698
+ },
26699
+ "recordingExport.createExport": {
26700
+ capName: "recordingExport",
26701
+ capScope: "system",
26702
+ addonId: null,
26703
+ access: "create"
26704
+ },
26705
+ "recordingExport.deleteExport": {
26706
+ capName: "recordingExport",
26707
+ capScope: "system",
26708
+ addonId: null,
26709
+ access: "delete"
26710
+ },
26711
+ "recordingExport.getDownloadUrl": {
26712
+ capName: "recordingExport",
26713
+ capScope: "system",
26714
+ addonId: null,
26715
+ access: "view"
26716
+ },
26717
+ "recordingExport.getExport": {
26718
+ capName: "recordingExport",
26719
+ capScope: "system",
26720
+ addonId: null,
26721
+ access: "view"
26722
+ },
26723
+ "recordingExport.listExports": {
26724
+ capName: "recordingExport",
26725
+ capScope: "system",
26726
+ addonId: null,
26727
+ access: "view"
26728
+ },
26390
26729
  "sceneMonitor.captureReference": {
26391
26730
  capName: "scene-monitor",
26392
26731
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homematic",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
4
4
  "description": "Homematic / HomematicIP (CCU3 / RaspberryMatic) device-provider addon for CamStack — wraps the nodehomematic library",
5
5
  "keywords": [
6
6
  "camstack",