@camstack/addon-ai 0.1.5 → 0.1.7

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
@@ -43,7 +43,7 @@ let node_fs_promises = require("node:fs/promises");
43
43
  node_fs_promises = __toESM(node_fs_promises);
44
44
  let node_child_process = require("node:child_process");
45
45
  let node_net = require("node:net");
46
- //#region ../types/dist/event-category-H4AVePnn.mjs
46
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
47
47
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
48
48
  EventCategory["SystemBoot"] = "system.boot";
49
49
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -196,6 +196,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
196
196
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
197
197
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
198
198
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
199
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
200
+ * progress bar the client reconciles via `recordingExport.getExport`. */
201
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
202
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
203
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
199
204
  EventCategory["DetectionEvent"] = "detection.event";
200
205
  EventCategory["SessionTrackNew"] = "session.track.new";
201
206
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -485,6 +490,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
485
490
  */
486
491
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
487
492
  /**
493
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
494
+ * a package zone — a newly-appeared stationary object of a package class
495
+ * that cleared the class / zone / dwell / size gates. Payload:
496
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
497
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
498
+ * Telemetry (D8): the durable record is the `package-events` store row;
499
+ * this bus topic drives notifier rules + live UI. See
500
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
501
+ */
502
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
503
+ /**
504
+ * Fired by `addon-post-analysis` when a previously-delivered package
505
+ * leaves its zone (the stationary entry departed — moved or swept).
506
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
507
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
508
+ * Telemetry (D8). See package-zones-design §5.2.
509
+ */
510
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
511
+ /**
488
512
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
489
513
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
490
514
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5223,6 +5247,25 @@ function _instanceof(cls, params = {}) {
5223
5247
  };
5224
5248
  return inst;
5225
5249
  }
5250
+ //#endregion
5251
+ //#region ../../node_modules/zod/v4/classic/compat.js
5252
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5253
+ var ZodIssueCode = {
5254
+ invalid_type: "invalid_type",
5255
+ too_big: "too_big",
5256
+ too_small: "too_small",
5257
+ invalid_format: "invalid_format",
5258
+ not_multiple_of: "not_multiple_of",
5259
+ unrecognized_keys: "unrecognized_keys",
5260
+ invalid_union: "invalid_union",
5261
+ invalid_key: "invalid_key",
5262
+ invalid_element: "invalid_element",
5263
+ invalid_value: "invalid_value",
5264
+ custom: "custom"
5265
+ };
5266
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5267
+ var ZodFirstPartyTypeKind;
5268
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5226
5269
  Object.fromEntries([
5227
5270
  {
5228
5271
  id: "overview",
@@ -7329,6 +7372,62 @@ var RecordingConfigSchema = object({
7329
7372
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7330
7373
  });
7331
7374
  /**
7375
+ * Ops-log — the durable, append-only operations audit shared by the
7376
+ * recordings and events management surfaces.
7377
+ *
7378
+ * ONE row shape is reused for both domains so a single "Activity" view can
7379
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7380
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7381
+ * management operation, WHY it ran (reason), and its measurable effect
7382
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7383
+ * never fail the operation it records.
7384
+ */
7385
+ /** Which management domain the operation belongs to. */
7386
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7387
+ /** The kind of management operation performed. */
7388
+ var OpsLogOpSchema = _enum([
7389
+ "prune",
7390
+ "manual-delete",
7391
+ "rescan",
7392
+ "retention-run"
7393
+ ]);
7394
+ /** Why the operation ran. */
7395
+ var OpsLogReasonSchema = _enum([
7396
+ "retention",
7397
+ "quota",
7398
+ "manual",
7399
+ "operator"
7400
+ ]);
7401
+ /** One audit row, shared verbatim by both domains. */
7402
+ var OpsLogEntrySchema = object({
7403
+ /** Unique row id. */
7404
+ id: string(),
7405
+ /** Epoch ms the operation completed. */
7406
+ at: number(),
7407
+ domain: OpsLogDomainSchema,
7408
+ op: OpsLogOpSchema,
7409
+ reason: OpsLogReasonSchema,
7410
+ /** The camera the op targeted; null for a cluster/global op. */
7411
+ deviceId: number().nullable(),
7412
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7413
+ nodeId: string(),
7414
+ /** Buckets / rows deleted (op-specific unit). */
7415
+ itemsAffected: number(),
7416
+ /** Bytes reclaimed by the op (0 when not measurable). */
7417
+ bytesReclaimed: number(),
7418
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7419
+ detail: string().nullable(),
7420
+ /** Who/what triggered the op. */
7421
+ actor: string()
7422
+ });
7423
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7424
+ var OpsLogQueryInputSchema = object({
7425
+ /** Restrict to a single camera; omit for every row. */
7426
+ deviceId: number().optional(),
7427
+ /** Max rows returned, newest-first. */
7428
+ limit: number().int().min(1).max(1e3).optional()
7429
+ });
7430
+ /**
7332
7431
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7333
7432
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7334
7433
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -12608,7 +12707,11 @@ array(ZoneRuleSchema).readonly();
12608
12707
  * Extend the enum here when a new gating consumer comes online (audio
12609
12708
  * gating, alert filtering, …) — no other surface needs to change.
12610
12709
  */
12611
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
12710
+ var ZoneRuleStageEnum = _enum([
12711
+ "motion",
12712
+ "detection",
12713
+ "package"
12714
+ ]);
12612
12715
  DeviceType.Camera, method(object({
12613
12716
  deviceId: number(),
12614
12717
  stage: ZoneRuleStageEnum
@@ -12621,7 +12724,8 @@ DeviceType.Camera, method(object({
12621
12724
  auth: "admin"
12622
12725
  }), object({
12623
12726
  motion: array(ZoneRuleSchema).readonly(),
12624
- detection: array(ZoneRuleSchema).readonly()
12727
+ detection: array(ZoneRuleSchema).readonly(),
12728
+ package: array(ZoneRuleSchema).readonly()
12625
12729
  });
12626
12730
  var ProviderStatusSchema = object({
12627
12731
  connected: boolean(),
@@ -15849,6 +15953,7 @@ var EventKindIconSchema = _enum([
15849
15953
  "smoke",
15850
15954
  "water",
15851
15955
  "button",
15956
+ "package",
15852
15957
  "generic"
15853
15958
  ]);
15854
15959
  var EventKindCategorySchema = _enum([
@@ -15856,7 +15961,8 @@ var EventKindCategorySchema = _enum([
15856
15961
  "audio",
15857
15962
  "detection",
15858
15963
  "sensor",
15859
- "custom"
15964
+ "custom",
15965
+ "package"
15860
15966
  ]);
15861
15967
  var EventKindDescriptorSchema = object({
15862
15968
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -16197,6 +16303,26 @@ var TrackCascadeCountsSchema = object({
16197
16303
  /** Per-track CLIP search vectors removed (best-effort). */
16198
16304
  embeddings: number().int()
16199
16305
  });
16306
+ /** Event-store footprint for one camera. */
16307
+ var EventStoreDeviceFootprintSchema = object({
16308
+ deviceId: number(),
16309
+ /** Persisted event rows (motion + object + audio) for the camera. */
16310
+ rows: number().int(),
16311
+ /** Event-owned media bytes on disk for the camera. */
16312
+ bytes: number().int()
16313
+ });
16314
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16315
+ var EventStoreFootprintSchema = object({
16316
+ totalRows: number().int(),
16317
+ totalBytes: number().int(),
16318
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16319
+ });
16320
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16321
+ var EventPruneCountsSchema = object({
16322
+ motion: number().int(),
16323
+ object: number().int(),
16324
+ audio: number().int()
16325
+ });
16200
16326
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16201
16327
  deviceId: number(),
16202
16328
  trackId: string()
@@ -16260,6 +16386,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16260
16386
  }), {
16261
16387
  kind: "mutation",
16262
16388
  auth: "admin"
16389
+ }), method(object({}), EventStoreFootprintSchema, {
16390
+ kind: "query",
16391
+ auth: "admin"
16392
+ }), method(object({
16393
+ olderThanMs: number(),
16394
+ reason: OpsLogReasonSchema.optional()
16395
+ }), EventPruneCountsSchema, {
16396
+ kind: "mutation",
16397
+ auth: "admin"
16398
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16399
+ kind: "mutation",
16400
+ auth: "admin"
16401
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16402
+ kind: "query",
16403
+ auth: "admin"
16263
16404
  }), method(object({
16264
16405
  eventId: string(),
16265
16406
  kind: MediaFileKindEnum.optional()
@@ -19493,13 +19634,131 @@ method(object({
19493
19634
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19494
19635
  kind: "mutation",
19495
19636
  auth: "admin"
19496
- }), method(object({ deviceId: number() }), object({
19637
+ }), method(object({
19638
+ deviceId: number(),
19639
+ reason: OpsLogReasonSchema.optional()
19640
+ }), object({
19497
19641
  floorMs: number().nullable(),
19498
19642
  deletedBuckets: number().int(),
19499
19643
  reclaimedBytes: number().int()
19500
19644
  }), {
19501
19645
  kind: "mutation",
19502
19646
  auth: "admin"
19647
+ }), method(object({
19648
+ deviceId: number(),
19649
+ fromMs: number().optional(),
19650
+ toMs: number().optional()
19651
+ }), object({
19652
+ deletedBuckets: number().int(),
19653
+ reclaimedBytes: number().int()
19654
+ }), {
19655
+ kind: "mutation",
19656
+ auth: "admin"
19657
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19658
+ kind: "query",
19659
+ auth: "admin"
19660
+ });
19661
+ /**
19662
+ * `recordingExport` cap — render a footage time range into a single downloadable
19663
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
19664
+ * bounded lifetime with a durable history, auto-expiry, and optional
19665
+ * delete-after-download.
19666
+ *
19667
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
19668
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
19669
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
19670
+ * input and dispatch to that single provider; the render runs on the node that
19671
+ * owns the footage (no cross-node segment transfer). Download rides the
19672
+ * framework addon data-plane. State persists in a DurableState blob (NOT
19673
+ * SQLite); history rows survive file deletion for audit.
19674
+ */
19675
+ /** Playback-speed multiplier for the render (1 = realtime). */
19676
+ var ExportSpeedSchema = number().min(.25).max(32);
19677
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
19678
+ var ExportTimelapseSchema = object({
19679
+ everyMs: number().int().positive(),
19680
+ outputFps: number().int().min(1).max(60).optional()
19681
+ });
19682
+ /**
19683
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
19684
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
19685
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
19686
+ * `deleteAfterDownload` removes it shortly after the first complete download.
19687
+ */
19688
+ var ExportOptionsSchema = object({
19689
+ speed: ExportSpeedSchema.optional(),
19690
+ timelapse: ExportTimelapseSchema.optional(),
19691
+ includeAudio: boolean(),
19692
+ maxLifeMs: number().int().positive(),
19693
+ deleteAfterDownload: boolean(),
19694
+ title: string().max(200).optional()
19695
+ }).superRefine((v, ctx) => {
19696
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
19697
+ code: ZodIssueCode.custom,
19698
+ message: "speed and timelapse are mutually exclusive",
19699
+ path: ["timelapse"]
19700
+ });
19701
+ });
19702
+ var ExportStateSchema = _enum([
19703
+ "queued",
19704
+ "rendering",
19705
+ "ready",
19706
+ "failed",
19707
+ "expired",
19708
+ "deleted"
19709
+ ]);
19710
+ /** One export job / history row. */
19711
+ var ExportRecordSchema = object({
19712
+ id: string(),
19713
+ deviceId: number(),
19714
+ profile: string(),
19715
+ fromMs: number(),
19716
+ toMs: number(),
19717
+ options: ExportOptionsSchema,
19718
+ state: ExportStateSchema,
19719
+ /** 0–100 while rendering; null otherwise. */
19720
+ progressPct: number().nullable(),
19721
+ /** File size once ready; null before. */
19722
+ fileBytes: number().nullable(),
19723
+ expiresAt: number(),
19724
+ deleteAfterDownload: boolean(),
19725
+ /** Epoch of the first complete download; null until then. */
19726
+ downloadedAt: number().nullable(),
19727
+ createdAt: number(),
19728
+ /** User id/name that requested the export. */
19729
+ createdBy: string(),
19730
+ /** Failure reason when state is 'failed'; null otherwise. */
19731
+ error: string().nullable()
19732
+ });
19733
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
19734
+ var ExportDownloadSchema = object({
19735
+ url: string(),
19736
+ endpoints: array(string())
19737
+ });
19738
+ method(object({
19739
+ deviceId: number(),
19740
+ profile: string(),
19741
+ fromMs: number(),
19742
+ toMs: number(),
19743
+ options: ExportOptionsSchema
19744
+ }), ExportRecordSchema, {
19745
+ kind: "mutation",
19746
+ auth: "protected"
19747
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
19748
+ kind: "query",
19749
+ auth: "protected"
19750
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19751
+ kind: "query",
19752
+ auth: "protected"
19753
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19754
+ kind: "mutation",
19755
+ auth: "protected"
19756
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19757
+ kind: "mutation",
19758
+ auth: "protected"
19759
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
19760
+ kind: "query",
19761
+ auth: "protected"
19503
19762
  });
19504
19763
  /**
19505
19764
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
@@ -22519,6 +22778,12 @@ Object.freeze({
22519
22778
  addonId: null,
22520
22779
  access: "delete"
22521
22780
  },
22781
+ "pipelineAnalytics.deleteDeviceEvents": {
22782
+ capName: "pipeline-analytics",
22783
+ capScope: "device",
22784
+ addonId: null,
22785
+ access: "delete"
22786
+ },
22522
22787
  "pipelineAnalytics.deleteTracks": {
22523
22788
  capName: "pipeline-analytics",
22524
22789
  capScope: "device",
@@ -22549,6 +22814,12 @@ Object.freeze({
22549
22814
  addonId: null,
22550
22815
  access: "view"
22551
22816
  },
22817
+ "pipelineAnalytics.getEventStoreFootprint": {
22818
+ capName: "pipeline-analytics",
22819
+ capScope: "device",
22820
+ addonId: null,
22821
+ access: "view"
22822
+ },
22552
22823
  "pipelineAnalytics.getKeyEvents": {
22553
22824
  capName: "pipeline-analytics",
22554
22825
  capScope: "device",
@@ -22591,6 +22862,12 @@ Object.freeze({
22591
22862
  addonId: null,
22592
22863
  access: "view"
22593
22864
  },
22865
+ "pipelineAnalytics.listOpsLog": {
22866
+ capName: "pipeline-analytics",
22867
+ capScope: "device",
22868
+ addonId: null,
22869
+ access: "view"
22870
+ },
22594
22871
  "pipelineAnalytics.listRecentTracks": {
22595
22872
  capName: "pipeline-analytics",
22596
22873
  capScope: "device",
@@ -22603,6 +22880,12 @@ Object.freeze({
22603
22880
  addonId: null,
22604
22881
  access: "view"
22605
22882
  },
22883
+ "pipelineAnalytics.pruneEvents": {
22884
+ capName: "pipeline-analytics",
22885
+ capScope: "device",
22886
+ addonId: null,
22887
+ access: "create"
22888
+ },
22606
22889
  "pipelineAnalytics.pruneEventsBefore": {
22607
22890
  capName: "pipeline-analytics",
22608
22891
  capScope: "device",
@@ -23365,6 +23648,12 @@ Object.freeze({
23365
23648
  addonId: null,
23366
23649
  access: "create"
23367
23650
  },
23651
+ "recording.deleteFootprint": {
23652
+ capName: "recording",
23653
+ capScope: "system",
23654
+ addonId: null,
23655
+ access: "delete"
23656
+ },
23368
23657
  "recording.getAvailability": {
23369
23658
  capName: "recording",
23370
23659
  capScope: "system",
@@ -23395,6 +23684,12 @@ Object.freeze({
23395
23684
  addonId: null,
23396
23685
  access: "view"
23397
23686
  },
23687
+ "recording.listOpsLog": {
23688
+ capName: "recording",
23689
+ capScope: "system",
23690
+ addonId: null,
23691
+ access: "view"
23692
+ },
23398
23693
  "recording.locateSegment": {
23399
23694
  capName: "recording",
23400
23695
  capScope: "system",
@@ -23425,6 +23720,42 @@ Object.freeze({
23425
23720
  addonId: null,
23426
23721
  access: "create"
23427
23722
  },
23723
+ "recordingExport.cancelExport": {
23724
+ capName: "recordingExport",
23725
+ capScope: "system",
23726
+ addonId: null,
23727
+ access: "create"
23728
+ },
23729
+ "recordingExport.createExport": {
23730
+ capName: "recordingExport",
23731
+ capScope: "system",
23732
+ addonId: null,
23733
+ access: "create"
23734
+ },
23735
+ "recordingExport.deleteExport": {
23736
+ capName: "recordingExport",
23737
+ capScope: "system",
23738
+ addonId: null,
23739
+ access: "delete"
23740
+ },
23741
+ "recordingExport.getDownloadUrl": {
23742
+ capName: "recordingExport",
23743
+ capScope: "system",
23744
+ addonId: null,
23745
+ access: "view"
23746
+ },
23747
+ "recordingExport.getExport": {
23748
+ capName: "recordingExport",
23749
+ capScope: "system",
23750
+ addonId: null,
23751
+ access: "view"
23752
+ },
23753
+ "recordingExport.listExports": {
23754
+ capName: "recordingExport",
23755
+ capScope: "system",
23756
+ addonId: null,
23757
+ access: "view"
23758
+ },
23428
23759
  "sceneMonitor.captureReference": {
23429
23760
  capName: "scene-monitor",
23430
23761
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -5,7 +5,7 @@ import { createReadStream } from "node:fs";
5
5
  import * as fsp from "node:fs/promises";
6
6
  import { spawn } from "node:child_process";
7
7
  import { createServer } from "node:net";
8
- //#region ../types/dist/event-category-H4AVePnn.mjs
8
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
9
9
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
10
10
  EventCategory["SystemBoot"] = "system.boot";
11
11
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -158,6 +158,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
158
158
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
159
159
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
160
160
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
161
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
162
+ * progress bar the client reconciles via `recordingExport.getExport`. */
163
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
164
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
165
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
161
166
  EventCategory["DetectionEvent"] = "detection.event";
162
167
  EventCategory["SessionTrackNew"] = "session.track.new";
163
168
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -447,6 +452,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
447
452
  */
448
453
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
449
454
  /**
455
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
456
+ * a package zone — a newly-appeared stationary object of a package class
457
+ * that cleared the class / zone / dwell / size gates. Payload:
458
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
459
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
460
+ * Telemetry (D8): the durable record is the `package-events` store row;
461
+ * this bus topic drives notifier rules + live UI. See
462
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
463
+ */
464
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
465
+ /**
466
+ * Fired by `addon-post-analysis` when a previously-delivered package
467
+ * leaves its zone (the stationary entry departed — moved or swept).
468
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
469
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
470
+ * Telemetry (D8). See package-zones-design §5.2.
471
+ */
472
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
473
+ /**
450
474
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
451
475
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
452
476
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5185,6 +5209,25 @@ function _instanceof(cls, params = {}) {
5185
5209
  };
5186
5210
  return inst;
5187
5211
  }
5212
+ //#endregion
5213
+ //#region ../../node_modules/zod/v4/classic/compat.js
5214
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5215
+ var ZodIssueCode = {
5216
+ invalid_type: "invalid_type",
5217
+ too_big: "too_big",
5218
+ too_small: "too_small",
5219
+ invalid_format: "invalid_format",
5220
+ not_multiple_of: "not_multiple_of",
5221
+ unrecognized_keys: "unrecognized_keys",
5222
+ invalid_union: "invalid_union",
5223
+ invalid_key: "invalid_key",
5224
+ invalid_element: "invalid_element",
5225
+ invalid_value: "invalid_value",
5226
+ custom: "custom"
5227
+ };
5228
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5229
+ var ZodFirstPartyTypeKind;
5230
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5188
5231
  Object.fromEntries([
5189
5232
  {
5190
5233
  id: "overview",
@@ -7291,6 +7334,62 @@ var RecordingConfigSchema = object({
7291
7334
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7292
7335
  });
7293
7336
  /**
7337
+ * Ops-log — the durable, append-only operations audit shared by the
7338
+ * recordings and events management surfaces.
7339
+ *
7340
+ * ONE row shape is reused for both domains so a single "Activity" view can
7341
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7342
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7343
+ * management operation, WHY it ran (reason), and its measurable effect
7344
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7345
+ * never fail the operation it records.
7346
+ */
7347
+ /** Which management domain the operation belongs to. */
7348
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7349
+ /** The kind of management operation performed. */
7350
+ var OpsLogOpSchema = _enum([
7351
+ "prune",
7352
+ "manual-delete",
7353
+ "rescan",
7354
+ "retention-run"
7355
+ ]);
7356
+ /** Why the operation ran. */
7357
+ var OpsLogReasonSchema = _enum([
7358
+ "retention",
7359
+ "quota",
7360
+ "manual",
7361
+ "operator"
7362
+ ]);
7363
+ /** One audit row, shared verbatim by both domains. */
7364
+ var OpsLogEntrySchema = object({
7365
+ /** Unique row id. */
7366
+ id: string(),
7367
+ /** Epoch ms the operation completed. */
7368
+ at: number(),
7369
+ domain: OpsLogDomainSchema,
7370
+ op: OpsLogOpSchema,
7371
+ reason: OpsLogReasonSchema,
7372
+ /** The camera the op targeted; null for a cluster/global op. */
7373
+ deviceId: number().nullable(),
7374
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7375
+ nodeId: string(),
7376
+ /** Buckets / rows deleted (op-specific unit). */
7377
+ itemsAffected: number(),
7378
+ /** Bytes reclaimed by the op (0 when not measurable). */
7379
+ bytesReclaimed: number(),
7380
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7381
+ detail: string().nullable(),
7382
+ /** Who/what triggered the op. */
7383
+ actor: string()
7384
+ });
7385
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7386
+ var OpsLogQueryInputSchema = object({
7387
+ /** Restrict to a single camera; omit for every row. */
7388
+ deviceId: number().optional(),
7389
+ /** Max rows returned, newest-first. */
7390
+ limit: number().int().min(1).max(1e3).optional()
7391
+ });
7392
+ /**
7294
7393
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7295
7394
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7296
7395
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -12570,7 +12669,11 @@ array(ZoneRuleSchema).readonly();
12570
12669
  * Extend the enum here when a new gating consumer comes online (audio
12571
12670
  * gating, alert filtering, …) — no other surface needs to change.
12572
12671
  */
12573
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
12672
+ var ZoneRuleStageEnum = _enum([
12673
+ "motion",
12674
+ "detection",
12675
+ "package"
12676
+ ]);
12574
12677
  DeviceType.Camera, method(object({
12575
12678
  deviceId: number(),
12576
12679
  stage: ZoneRuleStageEnum
@@ -12583,7 +12686,8 @@ DeviceType.Camera, method(object({
12583
12686
  auth: "admin"
12584
12687
  }), object({
12585
12688
  motion: array(ZoneRuleSchema).readonly(),
12586
- detection: array(ZoneRuleSchema).readonly()
12689
+ detection: array(ZoneRuleSchema).readonly(),
12690
+ package: array(ZoneRuleSchema).readonly()
12587
12691
  });
12588
12692
  var ProviderStatusSchema = object({
12589
12693
  connected: boolean(),
@@ -15811,6 +15915,7 @@ var EventKindIconSchema = _enum([
15811
15915
  "smoke",
15812
15916
  "water",
15813
15917
  "button",
15918
+ "package",
15814
15919
  "generic"
15815
15920
  ]);
15816
15921
  var EventKindCategorySchema = _enum([
@@ -15818,7 +15923,8 @@ var EventKindCategorySchema = _enum([
15818
15923
  "audio",
15819
15924
  "detection",
15820
15925
  "sensor",
15821
- "custom"
15926
+ "custom",
15927
+ "package"
15822
15928
  ]);
15823
15929
  var EventKindDescriptorSchema = object({
15824
15930
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -16159,6 +16265,26 @@ var TrackCascadeCountsSchema = object({
16159
16265
  /** Per-track CLIP search vectors removed (best-effort). */
16160
16266
  embeddings: number().int()
16161
16267
  });
16268
+ /** Event-store footprint for one camera. */
16269
+ var EventStoreDeviceFootprintSchema = object({
16270
+ deviceId: number(),
16271
+ /** Persisted event rows (motion + object + audio) for the camera. */
16272
+ rows: number().int(),
16273
+ /** Event-owned media bytes on disk for the camera. */
16274
+ bytes: number().int()
16275
+ });
16276
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16277
+ var EventStoreFootprintSchema = object({
16278
+ totalRows: number().int(),
16279
+ totalBytes: number().int(),
16280
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16281
+ });
16282
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16283
+ var EventPruneCountsSchema = object({
16284
+ motion: number().int(),
16285
+ object: number().int(),
16286
+ audio: number().int()
16287
+ });
16162
16288
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16163
16289
  deviceId: number(),
16164
16290
  trackId: string()
@@ -16222,6 +16348,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16222
16348
  }), {
16223
16349
  kind: "mutation",
16224
16350
  auth: "admin"
16351
+ }), method(object({}), EventStoreFootprintSchema, {
16352
+ kind: "query",
16353
+ auth: "admin"
16354
+ }), method(object({
16355
+ olderThanMs: number(),
16356
+ reason: OpsLogReasonSchema.optional()
16357
+ }), EventPruneCountsSchema, {
16358
+ kind: "mutation",
16359
+ auth: "admin"
16360
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16361
+ kind: "mutation",
16362
+ auth: "admin"
16363
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16364
+ kind: "query",
16365
+ auth: "admin"
16225
16366
  }), method(object({
16226
16367
  eventId: string(),
16227
16368
  kind: MediaFileKindEnum.optional()
@@ -19455,13 +19596,131 @@ method(object({
19455
19596
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19456
19597
  kind: "mutation",
19457
19598
  auth: "admin"
19458
- }), method(object({ deviceId: number() }), object({
19599
+ }), method(object({
19600
+ deviceId: number(),
19601
+ reason: OpsLogReasonSchema.optional()
19602
+ }), object({
19459
19603
  floorMs: number().nullable(),
19460
19604
  deletedBuckets: number().int(),
19461
19605
  reclaimedBytes: number().int()
19462
19606
  }), {
19463
19607
  kind: "mutation",
19464
19608
  auth: "admin"
19609
+ }), method(object({
19610
+ deviceId: number(),
19611
+ fromMs: number().optional(),
19612
+ toMs: number().optional()
19613
+ }), object({
19614
+ deletedBuckets: number().int(),
19615
+ reclaimedBytes: number().int()
19616
+ }), {
19617
+ kind: "mutation",
19618
+ auth: "admin"
19619
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19620
+ kind: "query",
19621
+ auth: "admin"
19622
+ });
19623
+ /**
19624
+ * `recordingExport` cap — render a footage time range into a single downloadable
19625
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
19626
+ * bounded lifetime with a durable history, auto-expiry, and optional
19627
+ * delete-after-download.
19628
+ *
19629
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
19630
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
19631
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
19632
+ * input and dispatch to that single provider; the render runs on the node that
19633
+ * owns the footage (no cross-node segment transfer). Download rides the
19634
+ * framework addon data-plane. State persists in a DurableState blob (NOT
19635
+ * SQLite); history rows survive file deletion for audit.
19636
+ */
19637
+ /** Playback-speed multiplier for the render (1 = realtime). */
19638
+ var ExportSpeedSchema = number().min(.25).max(32);
19639
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
19640
+ var ExportTimelapseSchema = object({
19641
+ everyMs: number().int().positive(),
19642
+ outputFps: number().int().min(1).max(60).optional()
19643
+ });
19644
+ /**
19645
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
19646
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
19647
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
19648
+ * `deleteAfterDownload` removes it shortly after the first complete download.
19649
+ */
19650
+ var ExportOptionsSchema = object({
19651
+ speed: ExportSpeedSchema.optional(),
19652
+ timelapse: ExportTimelapseSchema.optional(),
19653
+ includeAudio: boolean(),
19654
+ maxLifeMs: number().int().positive(),
19655
+ deleteAfterDownload: boolean(),
19656
+ title: string().max(200).optional()
19657
+ }).superRefine((v, ctx) => {
19658
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
19659
+ code: ZodIssueCode.custom,
19660
+ message: "speed and timelapse are mutually exclusive",
19661
+ path: ["timelapse"]
19662
+ });
19663
+ });
19664
+ var ExportStateSchema = _enum([
19665
+ "queued",
19666
+ "rendering",
19667
+ "ready",
19668
+ "failed",
19669
+ "expired",
19670
+ "deleted"
19671
+ ]);
19672
+ /** One export job / history row. */
19673
+ var ExportRecordSchema = object({
19674
+ id: string(),
19675
+ deviceId: number(),
19676
+ profile: string(),
19677
+ fromMs: number(),
19678
+ toMs: number(),
19679
+ options: ExportOptionsSchema,
19680
+ state: ExportStateSchema,
19681
+ /** 0–100 while rendering; null otherwise. */
19682
+ progressPct: number().nullable(),
19683
+ /** File size once ready; null before. */
19684
+ fileBytes: number().nullable(),
19685
+ expiresAt: number(),
19686
+ deleteAfterDownload: boolean(),
19687
+ /** Epoch of the first complete download; null until then. */
19688
+ downloadedAt: number().nullable(),
19689
+ createdAt: number(),
19690
+ /** User id/name that requested the export. */
19691
+ createdBy: string(),
19692
+ /** Failure reason when state is 'failed'; null otherwise. */
19693
+ error: string().nullable()
19694
+ });
19695
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
19696
+ var ExportDownloadSchema = object({
19697
+ url: string(),
19698
+ endpoints: array(string())
19699
+ });
19700
+ method(object({
19701
+ deviceId: number(),
19702
+ profile: string(),
19703
+ fromMs: number(),
19704
+ toMs: number(),
19705
+ options: ExportOptionsSchema
19706
+ }), ExportRecordSchema, {
19707
+ kind: "mutation",
19708
+ auth: "protected"
19709
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
19710
+ kind: "query",
19711
+ auth: "protected"
19712
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19713
+ kind: "query",
19714
+ auth: "protected"
19715
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19716
+ kind: "mutation",
19717
+ auth: "protected"
19718
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19719
+ kind: "mutation",
19720
+ auth: "protected"
19721
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
19722
+ kind: "query",
19723
+ auth: "protected"
19465
19724
  });
19466
19725
  /**
19467
19726
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
@@ -22481,6 +22740,12 @@ Object.freeze({
22481
22740
  addonId: null,
22482
22741
  access: "delete"
22483
22742
  },
22743
+ "pipelineAnalytics.deleteDeviceEvents": {
22744
+ capName: "pipeline-analytics",
22745
+ capScope: "device",
22746
+ addonId: null,
22747
+ access: "delete"
22748
+ },
22484
22749
  "pipelineAnalytics.deleteTracks": {
22485
22750
  capName: "pipeline-analytics",
22486
22751
  capScope: "device",
@@ -22511,6 +22776,12 @@ Object.freeze({
22511
22776
  addonId: null,
22512
22777
  access: "view"
22513
22778
  },
22779
+ "pipelineAnalytics.getEventStoreFootprint": {
22780
+ capName: "pipeline-analytics",
22781
+ capScope: "device",
22782
+ addonId: null,
22783
+ access: "view"
22784
+ },
22514
22785
  "pipelineAnalytics.getKeyEvents": {
22515
22786
  capName: "pipeline-analytics",
22516
22787
  capScope: "device",
@@ -22553,6 +22824,12 @@ Object.freeze({
22553
22824
  addonId: null,
22554
22825
  access: "view"
22555
22826
  },
22827
+ "pipelineAnalytics.listOpsLog": {
22828
+ capName: "pipeline-analytics",
22829
+ capScope: "device",
22830
+ addonId: null,
22831
+ access: "view"
22832
+ },
22556
22833
  "pipelineAnalytics.listRecentTracks": {
22557
22834
  capName: "pipeline-analytics",
22558
22835
  capScope: "device",
@@ -22565,6 +22842,12 @@ Object.freeze({
22565
22842
  addonId: null,
22566
22843
  access: "view"
22567
22844
  },
22845
+ "pipelineAnalytics.pruneEvents": {
22846
+ capName: "pipeline-analytics",
22847
+ capScope: "device",
22848
+ addonId: null,
22849
+ access: "create"
22850
+ },
22568
22851
  "pipelineAnalytics.pruneEventsBefore": {
22569
22852
  capName: "pipeline-analytics",
22570
22853
  capScope: "device",
@@ -23327,6 +23610,12 @@ Object.freeze({
23327
23610
  addonId: null,
23328
23611
  access: "create"
23329
23612
  },
23613
+ "recording.deleteFootprint": {
23614
+ capName: "recording",
23615
+ capScope: "system",
23616
+ addonId: null,
23617
+ access: "delete"
23618
+ },
23330
23619
  "recording.getAvailability": {
23331
23620
  capName: "recording",
23332
23621
  capScope: "system",
@@ -23357,6 +23646,12 @@ Object.freeze({
23357
23646
  addonId: null,
23358
23647
  access: "view"
23359
23648
  },
23649
+ "recording.listOpsLog": {
23650
+ capName: "recording",
23651
+ capScope: "system",
23652
+ addonId: null,
23653
+ access: "view"
23654
+ },
23360
23655
  "recording.locateSegment": {
23361
23656
  capName: "recording",
23362
23657
  capScope: "system",
@@ -23387,6 +23682,42 @@ Object.freeze({
23387
23682
  addonId: null,
23388
23683
  access: "create"
23389
23684
  },
23685
+ "recordingExport.cancelExport": {
23686
+ capName: "recordingExport",
23687
+ capScope: "system",
23688
+ addonId: null,
23689
+ access: "create"
23690
+ },
23691
+ "recordingExport.createExport": {
23692
+ capName: "recordingExport",
23693
+ capScope: "system",
23694
+ addonId: null,
23695
+ access: "create"
23696
+ },
23697
+ "recordingExport.deleteExport": {
23698
+ capName: "recordingExport",
23699
+ capScope: "system",
23700
+ addonId: null,
23701
+ access: "delete"
23702
+ },
23703
+ "recordingExport.getDownloadUrl": {
23704
+ capName: "recordingExport",
23705
+ capScope: "system",
23706
+ addonId: null,
23707
+ access: "view"
23708
+ },
23709
+ "recordingExport.getExport": {
23710
+ capName: "recordingExport",
23711
+ capScope: "system",
23712
+ addonId: null,
23713
+ access: "view"
23714
+ },
23715
+ "recordingExport.listExports": {
23716
+ capName: "recordingExport",
23717
+ capScope: "system",
23718
+ addonId: null,
23719
+ access: "view"
23720
+ },
23390
23721
  "sceneMonitor.captureReference": {
23391
23722
  capName: "scene-monitor",
23392
23723
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
5
5
  "keywords": [
6
6
  "camstack",