@camstack/addon-provider-homeassistant 1.1.29 → 1.1.31

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,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let node_crypto = require("node:crypto");
3
3
  let node_zlib = require("node:zlib");
4
- //#region ../types/dist/event-category-H4AVePnn.mjs
4
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
5
5
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
6
6
  EventCategory["SystemBoot"] = "system.boot";
7
7
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -154,6 +154,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
154
154
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
155
155
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
156
156
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
157
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
158
+ * progress bar the client reconciles via `recordingExport.getExport`. */
159
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
160
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
161
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
157
162
  EventCategory["DetectionEvent"] = "detection.event";
158
163
  EventCategory["SessionTrackNew"] = "session.track.new";
159
164
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -443,6 +448,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
443
448
  */
444
449
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
445
450
  /**
451
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
452
+ * a package zone — a newly-appeared stationary object of a package class
453
+ * that cleared the class / zone / dwell / size gates. Payload:
454
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
455
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
456
+ * Telemetry (D8): the durable record is the `package-events` store row;
457
+ * this bus topic drives notifier rules + live UI. See
458
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
459
+ */
460
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
461
+ /**
462
+ * Fired by `addon-post-analysis` when a previously-delivered package
463
+ * leaves its zone (the stationary entry departed — moved or swept).
464
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
465
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
466
+ * Telemetry (D8). See package-zones-design §5.2.
467
+ */
468
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
469
+ /**
446
470
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
447
471
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
448
472
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5181,6 +5205,25 @@ function _instanceof(cls, params = {}) {
5181
5205
  };
5182
5206
  return inst;
5183
5207
  }
5208
+ //#endregion
5209
+ //#region ../../node_modules/zod/v4/classic/compat.js
5210
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5211
+ var ZodIssueCode = {
5212
+ invalid_type: "invalid_type",
5213
+ too_big: "too_big",
5214
+ too_small: "too_small",
5215
+ invalid_format: "invalid_format",
5216
+ not_multiple_of: "not_multiple_of",
5217
+ unrecognized_keys: "unrecognized_keys",
5218
+ invalid_union: "invalid_union",
5219
+ invalid_key: "invalid_key",
5220
+ invalid_element: "invalid_element",
5221
+ invalid_value: "invalid_value",
5222
+ custom: "custom"
5223
+ };
5224
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5225
+ var ZodFirstPartyTypeKind;
5226
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5184
5227
  Object.fromEntries([
5185
5228
  {
5186
5229
  id: "overview",
@@ -7420,6 +7463,62 @@ var RecordingConfigSchema = object({
7420
7463
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7421
7464
  });
7422
7465
  /**
7466
+ * Ops-log — the durable, append-only operations audit shared by the
7467
+ * recordings and events management surfaces.
7468
+ *
7469
+ * ONE row shape is reused for both domains so a single "Activity" view can
7470
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7471
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7472
+ * management operation, WHY it ran (reason), and its measurable effect
7473
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7474
+ * never fail the operation it records.
7475
+ */
7476
+ /** Which management domain the operation belongs to. */
7477
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7478
+ /** The kind of management operation performed. */
7479
+ var OpsLogOpSchema = _enum([
7480
+ "prune",
7481
+ "manual-delete",
7482
+ "rescan",
7483
+ "retention-run"
7484
+ ]);
7485
+ /** Why the operation ran. */
7486
+ var OpsLogReasonSchema = _enum([
7487
+ "retention",
7488
+ "quota",
7489
+ "manual",
7490
+ "operator"
7491
+ ]);
7492
+ /** One audit row, shared verbatim by both domains. */
7493
+ var OpsLogEntrySchema = object({
7494
+ /** Unique row id. */
7495
+ id: string(),
7496
+ /** Epoch ms the operation completed. */
7497
+ at: number(),
7498
+ domain: OpsLogDomainSchema,
7499
+ op: OpsLogOpSchema,
7500
+ reason: OpsLogReasonSchema,
7501
+ /** The camera the op targeted; null for a cluster/global op. */
7502
+ deviceId: number().nullable(),
7503
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7504
+ nodeId: string(),
7505
+ /** Buckets / rows deleted (op-specific unit). */
7506
+ itemsAffected: number(),
7507
+ /** Bytes reclaimed by the op (0 when not measurable). */
7508
+ bytesReclaimed: number(),
7509
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7510
+ detail: string().nullable(),
7511
+ /** Who/what triggered the op. */
7512
+ actor: string()
7513
+ });
7514
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7515
+ var OpsLogQueryInputSchema = object({
7516
+ /** Restrict to a single camera; omit for every row. */
7517
+ deviceId: number().optional(),
7518
+ /** Max rows returned, newest-first. */
7519
+ limit: number().int().min(1).max(1e3).optional()
7520
+ });
7521
+ /**
7423
7522
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7424
7523
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7425
7524
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -15013,7 +15112,11 @@ array(ZoneRuleSchema).readonly();
15013
15112
  * Extend the enum here when a new gating consumer comes online (audio
15014
15113
  * gating, alert filtering, …) — no other surface needs to change.
15015
15114
  */
15016
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
15115
+ var ZoneRuleStageEnum = _enum([
15116
+ "motion",
15117
+ "detection",
15118
+ "package"
15119
+ ]);
15017
15120
  /**
15018
15121
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
15019
15122
  * `state` getter looks up the cap definition here to construct a
@@ -15107,16 +15210,25 @@ var DEVICE_LOCAL_STATE_CAPS = {
15107
15210
  })
15108
15211
  },
15109
15212
  /**
15110
- * Runtime-state slice — both stages mirrored together so consumers
15213
+ * Runtime-state slice — every stage mirrored together so consumers
15111
15214
  * see one reactive handle (`device.state.zoneRules.value`) instead
15112
- * of two. Bulk-replace mutations on either stage write the full
15113
- * `{motion, detection}` shape, so subscribers always get the
15215
+ * of one per stage. Bulk-replace mutations on any stage write the full
15216
+ * `{motion, detection, package}` shape, so subscribers always get the
15114
15217
  * complete current set. Consumers that only care about one stage
15115
15218
  * just read the matching property.
15219
+ *
15220
+ * `package` backs the package-drop detector — a package zone is a
15221
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
15222
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15223
+ * The orchestrator provider writes this stage as a first-class slice
15224
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15225
+ * package}` shape, so consumers read the current package rules directly
15226
+ * off `device.state.zoneRules.value.package`.
15116
15227
  */
15117
15228
  runtimeState: object({
15118
15229
  motion: array(ZoneRuleSchema).readonly(),
15119
- detection: array(ZoneRuleSchema).readonly()
15230
+ detection: array(ZoneRuleSchema).readonly(),
15231
+ package: array(ZoneRuleSchema).readonly()
15120
15232
  })
15121
15233
  },
15122
15234
  zones: zonesCapability
@@ -19177,6 +19289,7 @@ var EventKindIconSchema = _enum([
19177
19289
  "smoke",
19178
19290
  "water",
19179
19291
  "button",
19292
+ "package",
19180
19293
  "generic"
19181
19294
  ]);
19182
19295
  var EventKindCategorySchema = _enum([
@@ -19184,7 +19297,8 @@ var EventKindCategorySchema = _enum([
19184
19297
  "audio",
19185
19298
  "detection",
19186
19299
  "sensor",
19187
- "custom"
19300
+ "custom",
19301
+ "package"
19188
19302
  ]);
19189
19303
  var EventKindDescriptorSchema = object({
19190
19304
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -19525,6 +19639,26 @@ var TrackCascadeCountsSchema = object({
19525
19639
  /** Per-track CLIP search vectors removed (best-effort). */
19526
19640
  embeddings: number().int()
19527
19641
  });
19642
+ /** Event-store footprint for one camera. */
19643
+ var EventStoreDeviceFootprintSchema = object({
19644
+ deviceId: number(),
19645
+ /** Persisted event rows (motion + object + audio) for the camera. */
19646
+ rows: number().int(),
19647
+ /** Event-owned media bytes on disk for the camera. */
19648
+ bytes: number().int()
19649
+ });
19650
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19651
+ var EventStoreFootprintSchema = object({
19652
+ totalRows: number().int(),
19653
+ totalBytes: number().int(),
19654
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19655
+ });
19656
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19657
+ var EventPruneCountsSchema = object({
19658
+ motion: number().int(),
19659
+ object: number().int(),
19660
+ audio: number().int()
19661
+ });
19528
19662
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19529
19663
  deviceId: number(),
19530
19664
  trackId: string()
@@ -19588,6 +19722,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19588
19722
  }), {
19589
19723
  kind: "mutation",
19590
19724
  auth: "admin"
19725
+ }), method(object({}), EventStoreFootprintSchema, {
19726
+ kind: "query",
19727
+ auth: "admin"
19728
+ }), method(object({
19729
+ olderThanMs: number(),
19730
+ reason: OpsLogReasonSchema.optional()
19731
+ }), EventPruneCountsSchema, {
19732
+ kind: "mutation",
19733
+ auth: "admin"
19734
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19735
+ kind: "mutation",
19736
+ auth: "admin"
19737
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19738
+ kind: "query",
19739
+ auth: "admin"
19591
19740
  }), method(object({
19592
19741
  eventId: string(),
19593
19742
  kind: MediaFileKindEnum.optional()
@@ -22910,13 +23059,131 @@ method(object({
22910
23059
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22911
23060
  kind: "mutation",
22912
23061
  auth: "admin"
22913
- }), method(object({ deviceId: number() }), object({
23062
+ }), method(object({
23063
+ deviceId: number(),
23064
+ reason: OpsLogReasonSchema.optional()
23065
+ }), object({
22914
23066
  floorMs: number().nullable(),
22915
23067
  deletedBuckets: number().int(),
22916
23068
  reclaimedBytes: number().int()
22917
23069
  }), {
22918
23070
  kind: "mutation",
22919
23071
  auth: "admin"
23072
+ }), method(object({
23073
+ deviceId: number(),
23074
+ fromMs: number().optional(),
23075
+ toMs: number().optional()
23076
+ }), object({
23077
+ deletedBuckets: number().int(),
23078
+ reclaimedBytes: number().int()
23079
+ }), {
23080
+ kind: "mutation",
23081
+ auth: "admin"
23082
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23083
+ kind: "query",
23084
+ auth: "admin"
23085
+ });
23086
+ /**
23087
+ * `recordingExport` cap — render a footage time range into a single downloadable
23088
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
23089
+ * bounded lifetime with a durable history, auto-expiry, and optional
23090
+ * delete-after-download.
23091
+ *
23092
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
23093
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
23094
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
23095
+ * input and dispatch to that single provider; the render runs on the node that
23096
+ * owns the footage (no cross-node segment transfer). Download rides the
23097
+ * framework addon data-plane. State persists in a DurableState blob (NOT
23098
+ * SQLite); history rows survive file deletion for audit.
23099
+ */
23100
+ /** Playback-speed multiplier for the render (1 = realtime). */
23101
+ var ExportSpeedSchema = number().min(.25).max(32);
23102
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
23103
+ var ExportTimelapseSchema = object({
23104
+ everyMs: number().int().positive(),
23105
+ outputFps: number().int().min(1).max(60).optional()
23106
+ });
23107
+ /**
23108
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
23109
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
23110
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
23111
+ * `deleteAfterDownload` removes it shortly after the first complete download.
23112
+ */
23113
+ var ExportOptionsSchema = object({
23114
+ speed: ExportSpeedSchema.optional(),
23115
+ timelapse: ExportTimelapseSchema.optional(),
23116
+ includeAudio: boolean(),
23117
+ maxLifeMs: number().int().positive(),
23118
+ deleteAfterDownload: boolean(),
23119
+ title: string().max(200).optional()
23120
+ }).superRefine((v, ctx) => {
23121
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
23122
+ code: ZodIssueCode.custom,
23123
+ message: "speed and timelapse are mutually exclusive",
23124
+ path: ["timelapse"]
23125
+ });
23126
+ });
23127
+ var ExportStateSchema = _enum([
23128
+ "queued",
23129
+ "rendering",
23130
+ "ready",
23131
+ "failed",
23132
+ "expired",
23133
+ "deleted"
23134
+ ]);
23135
+ /** One export job / history row. */
23136
+ var ExportRecordSchema = object({
23137
+ id: string(),
23138
+ deviceId: number(),
23139
+ profile: string(),
23140
+ fromMs: number(),
23141
+ toMs: number(),
23142
+ options: ExportOptionsSchema,
23143
+ state: ExportStateSchema,
23144
+ /** 0–100 while rendering; null otherwise. */
23145
+ progressPct: number().nullable(),
23146
+ /** File size once ready; null before. */
23147
+ fileBytes: number().nullable(),
23148
+ expiresAt: number(),
23149
+ deleteAfterDownload: boolean(),
23150
+ /** Epoch of the first complete download; null until then. */
23151
+ downloadedAt: number().nullable(),
23152
+ createdAt: number(),
23153
+ /** User id/name that requested the export. */
23154
+ createdBy: string(),
23155
+ /** Failure reason when state is 'failed'; null otherwise. */
23156
+ error: string().nullable()
23157
+ });
23158
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
23159
+ var ExportDownloadSchema = object({
23160
+ url: string(),
23161
+ endpoints: array(string())
23162
+ });
23163
+ method(object({
23164
+ deviceId: number(),
23165
+ profile: string(),
23166
+ fromMs: number(),
23167
+ toMs: number(),
23168
+ options: ExportOptionsSchema
23169
+ }), ExportRecordSchema, {
23170
+ kind: "mutation",
23171
+ auth: "protected"
23172
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
23173
+ kind: "query",
23174
+ auth: "protected"
23175
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
23176
+ kind: "query",
23177
+ auth: "protected"
23178
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
23179
+ kind: "mutation",
23180
+ auth: "protected"
23181
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
23182
+ kind: "mutation",
23183
+ auth: "protected"
23184
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
23185
+ kind: "query",
23186
+ auth: "protected"
22920
23187
  });
22921
23188
  /**
22922
23189
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
@@ -25936,6 +26203,12 @@ Object.freeze({
25936
26203
  addonId: null,
25937
26204
  access: "delete"
25938
26205
  },
26206
+ "pipelineAnalytics.deleteDeviceEvents": {
26207
+ capName: "pipeline-analytics",
26208
+ capScope: "device",
26209
+ addonId: null,
26210
+ access: "delete"
26211
+ },
25939
26212
  "pipelineAnalytics.deleteTracks": {
25940
26213
  capName: "pipeline-analytics",
25941
26214
  capScope: "device",
@@ -25966,6 +26239,12 @@ Object.freeze({
25966
26239
  addonId: null,
25967
26240
  access: "view"
25968
26241
  },
26242
+ "pipelineAnalytics.getEventStoreFootprint": {
26243
+ capName: "pipeline-analytics",
26244
+ capScope: "device",
26245
+ addonId: null,
26246
+ access: "view"
26247
+ },
25969
26248
  "pipelineAnalytics.getKeyEvents": {
25970
26249
  capName: "pipeline-analytics",
25971
26250
  capScope: "device",
@@ -26008,6 +26287,12 @@ Object.freeze({
26008
26287
  addonId: null,
26009
26288
  access: "view"
26010
26289
  },
26290
+ "pipelineAnalytics.listOpsLog": {
26291
+ capName: "pipeline-analytics",
26292
+ capScope: "device",
26293
+ addonId: null,
26294
+ access: "view"
26295
+ },
26011
26296
  "pipelineAnalytics.listRecentTracks": {
26012
26297
  capName: "pipeline-analytics",
26013
26298
  capScope: "device",
@@ -26020,6 +26305,12 @@ Object.freeze({
26020
26305
  addonId: null,
26021
26306
  access: "view"
26022
26307
  },
26308
+ "pipelineAnalytics.pruneEvents": {
26309
+ capName: "pipeline-analytics",
26310
+ capScope: "device",
26311
+ addonId: null,
26312
+ access: "create"
26313
+ },
26023
26314
  "pipelineAnalytics.pruneEventsBefore": {
26024
26315
  capName: "pipeline-analytics",
26025
26316
  capScope: "device",
@@ -26782,6 +27073,12 @@ Object.freeze({
26782
27073
  addonId: null,
26783
27074
  access: "create"
26784
27075
  },
27076
+ "recording.deleteFootprint": {
27077
+ capName: "recording",
27078
+ capScope: "system",
27079
+ addonId: null,
27080
+ access: "delete"
27081
+ },
26785
27082
  "recording.getAvailability": {
26786
27083
  capName: "recording",
26787
27084
  capScope: "system",
@@ -26812,6 +27109,12 @@ Object.freeze({
26812
27109
  addonId: null,
26813
27110
  access: "view"
26814
27111
  },
27112
+ "recording.listOpsLog": {
27113
+ capName: "recording",
27114
+ capScope: "system",
27115
+ addonId: null,
27116
+ access: "view"
27117
+ },
26815
27118
  "recording.locateSegment": {
26816
27119
  capName: "recording",
26817
27120
  capScope: "system",
@@ -26842,6 +27145,42 @@ Object.freeze({
26842
27145
  addonId: null,
26843
27146
  access: "create"
26844
27147
  },
27148
+ "recordingExport.cancelExport": {
27149
+ capName: "recordingExport",
27150
+ capScope: "system",
27151
+ addonId: null,
27152
+ access: "create"
27153
+ },
27154
+ "recordingExport.createExport": {
27155
+ capName: "recordingExport",
27156
+ capScope: "system",
27157
+ addonId: null,
27158
+ access: "create"
27159
+ },
27160
+ "recordingExport.deleteExport": {
27161
+ capName: "recordingExport",
27162
+ capScope: "system",
27163
+ addonId: null,
27164
+ access: "delete"
27165
+ },
27166
+ "recordingExport.getDownloadUrl": {
27167
+ capName: "recordingExport",
27168
+ capScope: "system",
27169
+ addonId: null,
27170
+ access: "view"
27171
+ },
27172
+ "recordingExport.getExport": {
27173
+ capName: "recordingExport",
27174
+ capScope: "system",
27175
+ addonId: null,
27176
+ access: "view"
27177
+ },
27178
+ "recordingExport.listExports": {
27179
+ capName: "recordingExport",
27180
+ capScope: "system",
27181
+ addonId: null,
27182
+ access: "view"
27183
+ },
26845
27184
  "sceneMonitor.captureReference": {
26846
27185
  capName: "scene-monitor",
26847
27186
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
3
- //#region ../types/dist/event-category-H4AVePnn.mjs
3
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
4
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
5
5
  EventCategory["SystemBoot"] = "system.boot";
6
6
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -153,6 +153,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
153
153
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
154
154
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
155
155
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
156
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
157
+ * progress bar the client reconciles via `recordingExport.getExport`. */
158
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
159
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
160
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
156
161
  EventCategory["DetectionEvent"] = "detection.event";
157
162
  EventCategory["SessionTrackNew"] = "session.track.new";
158
163
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -442,6 +447,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
442
447
  */
443
448
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
444
449
  /**
450
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
451
+ * a package zone — a newly-appeared stationary object of a package class
452
+ * that cleared the class / zone / dwell / size gates. Payload:
453
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
454
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
455
+ * Telemetry (D8): the durable record is the `package-events` store row;
456
+ * this bus topic drives notifier rules + live UI. See
457
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
458
+ */
459
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
460
+ /**
461
+ * Fired by `addon-post-analysis` when a previously-delivered package
462
+ * leaves its zone (the stationary entry departed — moved or swept).
463
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
464
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
465
+ * Telemetry (D8). See package-zones-design §5.2.
466
+ */
467
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
468
+ /**
445
469
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
446
470
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
447
471
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5180,6 +5204,25 @@ function _instanceof(cls, params = {}) {
5180
5204
  };
5181
5205
  return inst;
5182
5206
  }
5207
+ //#endregion
5208
+ //#region ../../node_modules/zod/v4/classic/compat.js
5209
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5210
+ var ZodIssueCode = {
5211
+ invalid_type: "invalid_type",
5212
+ too_big: "too_big",
5213
+ too_small: "too_small",
5214
+ invalid_format: "invalid_format",
5215
+ not_multiple_of: "not_multiple_of",
5216
+ unrecognized_keys: "unrecognized_keys",
5217
+ invalid_union: "invalid_union",
5218
+ invalid_key: "invalid_key",
5219
+ invalid_element: "invalid_element",
5220
+ invalid_value: "invalid_value",
5221
+ custom: "custom"
5222
+ };
5223
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5224
+ var ZodFirstPartyTypeKind;
5225
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5183
5226
  Object.fromEntries([
5184
5227
  {
5185
5228
  id: "overview",
@@ -7419,6 +7462,62 @@ var RecordingConfigSchema = object({
7419
7462
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7420
7463
  });
7421
7464
  /**
7465
+ * Ops-log — the durable, append-only operations audit shared by the
7466
+ * recordings and events management surfaces.
7467
+ *
7468
+ * ONE row shape is reused for both domains so a single "Activity" view can
7469
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7470
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7471
+ * management operation, WHY it ran (reason), and its measurable effect
7472
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7473
+ * never fail the operation it records.
7474
+ */
7475
+ /** Which management domain the operation belongs to. */
7476
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7477
+ /** The kind of management operation performed. */
7478
+ var OpsLogOpSchema = _enum([
7479
+ "prune",
7480
+ "manual-delete",
7481
+ "rescan",
7482
+ "retention-run"
7483
+ ]);
7484
+ /** Why the operation ran. */
7485
+ var OpsLogReasonSchema = _enum([
7486
+ "retention",
7487
+ "quota",
7488
+ "manual",
7489
+ "operator"
7490
+ ]);
7491
+ /** One audit row, shared verbatim by both domains. */
7492
+ var OpsLogEntrySchema = object({
7493
+ /** Unique row id. */
7494
+ id: string(),
7495
+ /** Epoch ms the operation completed. */
7496
+ at: number(),
7497
+ domain: OpsLogDomainSchema,
7498
+ op: OpsLogOpSchema,
7499
+ reason: OpsLogReasonSchema,
7500
+ /** The camera the op targeted; null for a cluster/global op. */
7501
+ deviceId: number().nullable(),
7502
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7503
+ nodeId: string(),
7504
+ /** Buckets / rows deleted (op-specific unit). */
7505
+ itemsAffected: number(),
7506
+ /** Bytes reclaimed by the op (0 when not measurable). */
7507
+ bytesReclaimed: number(),
7508
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7509
+ detail: string().nullable(),
7510
+ /** Who/what triggered the op. */
7511
+ actor: string()
7512
+ });
7513
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7514
+ var OpsLogQueryInputSchema = object({
7515
+ /** Restrict to a single camera; omit for every row. */
7516
+ deviceId: number().optional(),
7517
+ /** Max rows returned, newest-first. */
7518
+ limit: number().int().min(1).max(1e3).optional()
7519
+ });
7520
+ /**
7422
7521
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7423
7522
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7424
7523
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -15012,7 +15111,11 @@ array(ZoneRuleSchema).readonly();
15012
15111
  * Extend the enum here when a new gating consumer comes online (audio
15013
15112
  * gating, alert filtering, …) — no other surface needs to change.
15014
15113
  */
15015
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
15114
+ var ZoneRuleStageEnum = _enum([
15115
+ "motion",
15116
+ "detection",
15117
+ "package"
15118
+ ]);
15016
15119
  /**
15017
15120
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
15018
15121
  * `state` getter looks up the cap definition here to construct a
@@ -15106,16 +15209,25 @@ var DEVICE_LOCAL_STATE_CAPS = {
15106
15209
  })
15107
15210
  },
15108
15211
  /**
15109
- * Runtime-state slice — both stages mirrored together so consumers
15212
+ * Runtime-state slice — every stage mirrored together so consumers
15110
15213
  * see one reactive handle (`device.state.zoneRules.value`) instead
15111
- * of two. Bulk-replace mutations on either stage write the full
15112
- * `{motion, detection}` shape, so subscribers always get the
15214
+ * of one per stage. Bulk-replace mutations on any stage write the full
15215
+ * `{motion, detection, package}` shape, so subscribers always get the
15113
15216
  * complete current set. Consumers that only care about one stage
15114
15217
  * just read the matching property.
15218
+ *
15219
+ * `package` backs the package-drop detector — a package zone is a
15220
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
15221
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15222
+ * The orchestrator provider writes this stage as a first-class slice
15223
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15224
+ * package}` shape, so consumers read the current package rules directly
15225
+ * off `device.state.zoneRules.value.package`.
15115
15226
  */
15116
15227
  runtimeState: object({
15117
15228
  motion: array(ZoneRuleSchema).readonly(),
15118
- detection: array(ZoneRuleSchema).readonly()
15229
+ detection: array(ZoneRuleSchema).readonly(),
15230
+ package: array(ZoneRuleSchema).readonly()
15119
15231
  })
15120
15232
  },
15121
15233
  zones: zonesCapability
@@ -19176,6 +19288,7 @@ var EventKindIconSchema = _enum([
19176
19288
  "smoke",
19177
19289
  "water",
19178
19290
  "button",
19291
+ "package",
19179
19292
  "generic"
19180
19293
  ]);
19181
19294
  var EventKindCategorySchema = _enum([
@@ -19183,7 +19296,8 @@ var EventKindCategorySchema = _enum([
19183
19296
  "audio",
19184
19297
  "detection",
19185
19298
  "sensor",
19186
- "custom"
19299
+ "custom",
19300
+ "package"
19187
19301
  ]);
19188
19302
  var EventKindDescriptorSchema = object({
19189
19303
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -19524,6 +19638,26 @@ var TrackCascadeCountsSchema = object({
19524
19638
  /** Per-track CLIP search vectors removed (best-effort). */
19525
19639
  embeddings: number().int()
19526
19640
  });
19641
+ /** Event-store footprint for one camera. */
19642
+ var EventStoreDeviceFootprintSchema = object({
19643
+ deviceId: number(),
19644
+ /** Persisted event rows (motion + object + audio) for the camera. */
19645
+ rows: number().int(),
19646
+ /** Event-owned media bytes on disk for the camera. */
19647
+ bytes: number().int()
19648
+ });
19649
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19650
+ var EventStoreFootprintSchema = object({
19651
+ totalRows: number().int(),
19652
+ totalBytes: number().int(),
19653
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19654
+ });
19655
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19656
+ var EventPruneCountsSchema = object({
19657
+ motion: number().int(),
19658
+ object: number().int(),
19659
+ audio: number().int()
19660
+ });
19527
19661
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19528
19662
  deviceId: number(),
19529
19663
  trackId: string()
@@ -19587,6 +19721,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19587
19721
  }), {
19588
19722
  kind: "mutation",
19589
19723
  auth: "admin"
19724
+ }), method(object({}), EventStoreFootprintSchema, {
19725
+ kind: "query",
19726
+ auth: "admin"
19727
+ }), method(object({
19728
+ olderThanMs: number(),
19729
+ reason: OpsLogReasonSchema.optional()
19730
+ }), EventPruneCountsSchema, {
19731
+ kind: "mutation",
19732
+ auth: "admin"
19733
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19734
+ kind: "mutation",
19735
+ auth: "admin"
19736
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19737
+ kind: "query",
19738
+ auth: "admin"
19590
19739
  }), method(object({
19591
19740
  eventId: string(),
19592
19741
  kind: MediaFileKindEnum.optional()
@@ -22909,13 +23058,131 @@ method(object({
22909
23058
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22910
23059
  kind: "mutation",
22911
23060
  auth: "admin"
22912
- }), method(object({ deviceId: number() }), object({
23061
+ }), method(object({
23062
+ deviceId: number(),
23063
+ reason: OpsLogReasonSchema.optional()
23064
+ }), object({
22913
23065
  floorMs: number().nullable(),
22914
23066
  deletedBuckets: number().int(),
22915
23067
  reclaimedBytes: number().int()
22916
23068
  }), {
22917
23069
  kind: "mutation",
22918
23070
  auth: "admin"
23071
+ }), method(object({
23072
+ deviceId: number(),
23073
+ fromMs: number().optional(),
23074
+ toMs: number().optional()
23075
+ }), object({
23076
+ deletedBuckets: number().int(),
23077
+ reclaimedBytes: number().int()
23078
+ }), {
23079
+ kind: "mutation",
23080
+ auth: "admin"
23081
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23082
+ kind: "query",
23083
+ auth: "admin"
23084
+ });
23085
+ /**
23086
+ * `recordingExport` cap — render a footage time range into a single downloadable
23087
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
23088
+ * bounded lifetime with a durable history, auto-expiry, and optional
23089
+ * delete-after-download.
23090
+ *
23091
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
23092
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
23093
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
23094
+ * input and dispatch to that single provider; the render runs on the node that
23095
+ * owns the footage (no cross-node segment transfer). Download rides the
23096
+ * framework addon data-plane. State persists in a DurableState blob (NOT
23097
+ * SQLite); history rows survive file deletion for audit.
23098
+ */
23099
+ /** Playback-speed multiplier for the render (1 = realtime). */
23100
+ var ExportSpeedSchema = number().min(.25).max(32);
23101
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
23102
+ var ExportTimelapseSchema = object({
23103
+ everyMs: number().int().positive(),
23104
+ outputFps: number().int().min(1).max(60).optional()
23105
+ });
23106
+ /**
23107
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
23108
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
23109
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
23110
+ * `deleteAfterDownload` removes it shortly after the first complete download.
23111
+ */
23112
+ var ExportOptionsSchema = object({
23113
+ speed: ExportSpeedSchema.optional(),
23114
+ timelapse: ExportTimelapseSchema.optional(),
23115
+ includeAudio: boolean(),
23116
+ maxLifeMs: number().int().positive(),
23117
+ deleteAfterDownload: boolean(),
23118
+ title: string().max(200).optional()
23119
+ }).superRefine((v, ctx) => {
23120
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
23121
+ code: ZodIssueCode.custom,
23122
+ message: "speed and timelapse are mutually exclusive",
23123
+ path: ["timelapse"]
23124
+ });
23125
+ });
23126
+ var ExportStateSchema = _enum([
23127
+ "queued",
23128
+ "rendering",
23129
+ "ready",
23130
+ "failed",
23131
+ "expired",
23132
+ "deleted"
23133
+ ]);
23134
+ /** One export job / history row. */
23135
+ var ExportRecordSchema = object({
23136
+ id: string(),
23137
+ deviceId: number(),
23138
+ profile: string(),
23139
+ fromMs: number(),
23140
+ toMs: number(),
23141
+ options: ExportOptionsSchema,
23142
+ state: ExportStateSchema,
23143
+ /** 0–100 while rendering; null otherwise. */
23144
+ progressPct: number().nullable(),
23145
+ /** File size once ready; null before. */
23146
+ fileBytes: number().nullable(),
23147
+ expiresAt: number(),
23148
+ deleteAfterDownload: boolean(),
23149
+ /** Epoch of the first complete download; null until then. */
23150
+ downloadedAt: number().nullable(),
23151
+ createdAt: number(),
23152
+ /** User id/name that requested the export. */
23153
+ createdBy: string(),
23154
+ /** Failure reason when state is 'failed'; null otherwise. */
23155
+ error: string().nullable()
23156
+ });
23157
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
23158
+ var ExportDownloadSchema = object({
23159
+ url: string(),
23160
+ endpoints: array(string())
23161
+ });
23162
+ method(object({
23163
+ deviceId: number(),
23164
+ profile: string(),
23165
+ fromMs: number(),
23166
+ toMs: number(),
23167
+ options: ExportOptionsSchema
23168
+ }), ExportRecordSchema, {
23169
+ kind: "mutation",
23170
+ auth: "protected"
23171
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
23172
+ kind: "query",
23173
+ auth: "protected"
23174
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
23175
+ kind: "query",
23176
+ auth: "protected"
23177
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
23178
+ kind: "mutation",
23179
+ auth: "protected"
23180
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
23181
+ kind: "mutation",
23182
+ auth: "protected"
23183
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
23184
+ kind: "query",
23185
+ auth: "protected"
22919
23186
  });
22920
23187
  /**
22921
23188
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
@@ -25935,6 +26202,12 @@ Object.freeze({
25935
26202
  addonId: null,
25936
26203
  access: "delete"
25937
26204
  },
26205
+ "pipelineAnalytics.deleteDeviceEvents": {
26206
+ capName: "pipeline-analytics",
26207
+ capScope: "device",
26208
+ addonId: null,
26209
+ access: "delete"
26210
+ },
25938
26211
  "pipelineAnalytics.deleteTracks": {
25939
26212
  capName: "pipeline-analytics",
25940
26213
  capScope: "device",
@@ -25965,6 +26238,12 @@ Object.freeze({
25965
26238
  addonId: null,
25966
26239
  access: "view"
25967
26240
  },
26241
+ "pipelineAnalytics.getEventStoreFootprint": {
26242
+ capName: "pipeline-analytics",
26243
+ capScope: "device",
26244
+ addonId: null,
26245
+ access: "view"
26246
+ },
25968
26247
  "pipelineAnalytics.getKeyEvents": {
25969
26248
  capName: "pipeline-analytics",
25970
26249
  capScope: "device",
@@ -26007,6 +26286,12 @@ Object.freeze({
26007
26286
  addonId: null,
26008
26287
  access: "view"
26009
26288
  },
26289
+ "pipelineAnalytics.listOpsLog": {
26290
+ capName: "pipeline-analytics",
26291
+ capScope: "device",
26292
+ addonId: null,
26293
+ access: "view"
26294
+ },
26010
26295
  "pipelineAnalytics.listRecentTracks": {
26011
26296
  capName: "pipeline-analytics",
26012
26297
  capScope: "device",
@@ -26019,6 +26304,12 @@ Object.freeze({
26019
26304
  addonId: null,
26020
26305
  access: "view"
26021
26306
  },
26307
+ "pipelineAnalytics.pruneEvents": {
26308
+ capName: "pipeline-analytics",
26309
+ capScope: "device",
26310
+ addonId: null,
26311
+ access: "create"
26312
+ },
26022
26313
  "pipelineAnalytics.pruneEventsBefore": {
26023
26314
  capName: "pipeline-analytics",
26024
26315
  capScope: "device",
@@ -26781,6 +27072,12 @@ Object.freeze({
26781
27072
  addonId: null,
26782
27073
  access: "create"
26783
27074
  },
27075
+ "recording.deleteFootprint": {
27076
+ capName: "recording",
27077
+ capScope: "system",
27078
+ addonId: null,
27079
+ access: "delete"
27080
+ },
26784
27081
  "recording.getAvailability": {
26785
27082
  capName: "recording",
26786
27083
  capScope: "system",
@@ -26811,6 +27108,12 @@ Object.freeze({
26811
27108
  addonId: null,
26812
27109
  access: "view"
26813
27110
  },
27111
+ "recording.listOpsLog": {
27112
+ capName: "recording",
27113
+ capScope: "system",
27114
+ addonId: null,
27115
+ access: "view"
27116
+ },
26814
27117
  "recording.locateSegment": {
26815
27118
  capName: "recording",
26816
27119
  capScope: "system",
@@ -26841,6 +27144,42 @@ Object.freeze({
26841
27144
  addonId: null,
26842
27145
  access: "create"
26843
27146
  },
27147
+ "recordingExport.cancelExport": {
27148
+ capName: "recordingExport",
27149
+ capScope: "system",
27150
+ addonId: null,
27151
+ access: "create"
27152
+ },
27153
+ "recordingExport.createExport": {
27154
+ capName: "recordingExport",
27155
+ capScope: "system",
27156
+ addonId: null,
27157
+ access: "create"
27158
+ },
27159
+ "recordingExport.deleteExport": {
27160
+ capName: "recordingExport",
27161
+ capScope: "system",
27162
+ addonId: null,
27163
+ access: "delete"
27164
+ },
27165
+ "recordingExport.getDownloadUrl": {
27166
+ capName: "recordingExport",
27167
+ capScope: "system",
27168
+ addonId: null,
27169
+ access: "view"
27170
+ },
27171
+ "recordingExport.getExport": {
27172
+ capName: "recordingExport",
27173
+ capScope: "system",
27174
+ addonId: null,
27175
+ access: "view"
27176
+ },
27177
+ "recordingExport.listExports": {
27178
+ capName: "recordingExport",
27179
+ capScope: "system",
27180
+ addonId: null,
27181
+ access: "view"
27182
+ },
26844
27183
  "sceneMonitor.captureReference": {
26845
27184
  capName: "scene-monitor",
26846
27185
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.1.29",
3
+ "version": "1.1.31",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",