@camstack/addon-provider-dreo 0.1.21 → 0.1.23

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
@@ -7380,6 +7380,62 @@ var RecordingConfigSchema = object({
7380
7380
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7381
7381
  });
7382
7382
  /**
7383
+ * Ops-log — the durable, append-only operations audit shared by the
7384
+ * recordings and events management surfaces.
7385
+ *
7386
+ * ONE row shape is reused for both domains so a single "Activity" view can
7387
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7388
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7389
+ * management operation, WHY it ran (reason), and its measurable effect
7390
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7391
+ * never fail the operation it records.
7392
+ */
7393
+ /** Which management domain the operation belongs to. */
7394
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7395
+ /** The kind of management operation performed. */
7396
+ var OpsLogOpSchema = _enum([
7397
+ "prune",
7398
+ "manual-delete",
7399
+ "rescan",
7400
+ "retention-run"
7401
+ ]);
7402
+ /** Why the operation ran. */
7403
+ var OpsLogReasonSchema = _enum([
7404
+ "retention",
7405
+ "quota",
7406
+ "manual",
7407
+ "operator"
7408
+ ]);
7409
+ /** One audit row, shared verbatim by both domains. */
7410
+ var OpsLogEntrySchema = object({
7411
+ /** Unique row id. */
7412
+ id: string(),
7413
+ /** Epoch ms the operation completed. */
7414
+ at: number(),
7415
+ domain: OpsLogDomainSchema,
7416
+ op: OpsLogOpSchema,
7417
+ reason: OpsLogReasonSchema,
7418
+ /** The camera the op targeted; null for a cluster/global op. */
7419
+ deviceId: number().nullable(),
7420
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7421
+ nodeId: string(),
7422
+ /** Buckets / rows deleted (op-specific unit). */
7423
+ itemsAffected: number(),
7424
+ /** Bytes reclaimed by the op (0 when not measurable). */
7425
+ bytesReclaimed: number(),
7426
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7427
+ detail: string().nullable(),
7428
+ /** Who/what triggered the op. */
7429
+ actor: string()
7430
+ });
7431
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7432
+ var OpsLogQueryInputSchema = object({
7433
+ /** Restrict to a single camera; omit for every row. */
7434
+ deviceId: number().optional(),
7435
+ /** Max rows returned, newest-first. */
7436
+ limit: number().int().min(1).max(1e3).optional()
7437
+ });
7438
+ /**
7383
7439
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7384
7440
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7385
7441
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7623,6 +7679,160 @@ var EncodeProfileSchema = object({
7623
7679
  */
7624
7680
  outputArgs: array(string()).optional()
7625
7681
  });
7682
+ var COCO_TO_MACRO = {
7683
+ mapping: {
7684
+ person: "person",
7685
+ bicycle: "vehicle",
7686
+ car: "vehicle",
7687
+ motorcycle: "vehicle",
7688
+ airplane: "vehicle",
7689
+ bus: "vehicle",
7690
+ train: "vehicle",
7691
+ truck: "vehicle",
7692
+ boat: "vehicle",
7693
+ bird: "animal",
7694
+ cat: "animal",
7695
+ dog: "animal",
7696
+ horse: "animal",
7697
+ sheep: "animal",
7698
+ cow: "animal",
7699
+ elephant: "animal",
7700
+ bear: "animal",
7701
+ zebra: "animal",
7702
+ giraffe: "animal",
7703
+ suitcase: "package",
7704
+ backpack: "package",
7705
+ handbag: "package"
7706
+ },
7707
+ preserveOriginal: false
7708
+ };
7709
+ var AUDIO_MACRO_LABELS = [
7710
+ {
7711
+ id: "speech",
7712
+ name: "Speech",
7713
+ icon: "🗣️"
7714
+ },
7715
+ {
7716
+ id: "scream",
7717
+ name: "Scream / Shout",
7718
+ icon: "😱"
7719
+ },
7720
+ {
7721
+ id: "crying",
7722
+ name: "Crying / Baby",
7723
+ icon: "😢"
7724
+ },
7725
+ {
7726
+ id: "laughter",
7727
+ name: "Laughter",
7728
+ icon: "😂"
7729
+ },
7730
+ {
7731
+ id: "music",
7732
+ name: "Music",
7733
+ icon: "🎵"
7734
+ },
7735
+ {
7736
+ id: "dog",
7737
+ name: "Dog",
7738
+ icon: "🐕"
7739
+ },
7740
+ {
7741
+ id: "cat",
7742
+ name: "Cat",
7743
+ icon: "🐈"
7744
+ },
7745
+ {
7746
+ id: "bird",
7747
+ name: "Bird",
7748
+ icon: "🐦"
7749
+ },
7750
+ {
7751
+ id: "animal",
7752
+ name: "Animal (other)",
7753
+ icon: "🐾"
7754
+ },
7755
+ {
7756
+ id: "alarm",
7757
+ name: "Alarm / Siren",
7758
+ icon: "🚨"
7759
+ },
7760
+ {
7761
+ id: "doorbell",
7762
+ name: "Doorbell / Knock",
7763
+ icon: "🔔"
7764
+ },
7765
+ {
7766
+ id: "glass_breaking",
7767
+ name: "Glass Breaking",
7768
+ icon: "💥"
7769
+ },
7770
+ {
7771
+ id: "gunshot",
7772
+ name: "Gunshot / Explosion",
7773
+ icon: "💣"
7774
+ },
7775
+ {
7776
+ id: "vehicle",
7777
+ name: "Vehicle",
7778
+ icon: "🚗"
7779
+ },
7780
+ {
7781
+ id: "siren",
7782
+ name: "Emergency Siren",
7783
+ icon: "🚑"
7784
+ },
7785
+ {
7786
+ id: "fire",
7787
+ name: "Fire / Smoke",
7788
+ icon: "🔥"
7789
+ },
7790
+ {
7791
+ id: "water",
7792
+ name: "Water",
7793
+ icon: "💧"
7794
+ },
7795
+ {
7796
+ id: "wind",
7797
+ name: "Wind / Weather",
7798
+ icon: "🌬️"
7799
+ },
7800
+ {
7801
+ id: "door",
7802
+ name: "Door",
7803
+ icon: "🚪"
7804
+ },
7805
+ {
7806
+ id: "footsteps",
7807
+ name: "Footsteps",
7808
+ icon: "👣"
7809
+ },
7810
+ {
7811
+ id: "crowd",
7812
+ name: "Crowd / Chatter",
7813
+ icon: "👥"
7814
+ },
7815
+ {
7816
+ id: "telephone",
7817
+ name: "Telephone",
7818
+ icon: "📞"
7819
+ },
7820
+ {
7821
+ id: "engine",
7822
+ name: "Engine / Motor",
7823
+ icon: "⚙️"
7824
+ },
7825
+ {
7826
+ id: "tools",
7827
+ name: "Tools / Construction",
7828
+ icon: "🔨"
7829
+ },
7830
+ {
7831
+ id: "silence",
7832
+ name: "Silence",
7833
+ icon: "🤫"
7834
+ }
7835
+ ];
7626
7836
  var YAMNET_TO_MACRO = {
7627
7837
  mapping: {
7628
7838
  Speech: "speech",
@@ -7889,6 +8099,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7889
8099
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7890
8100
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7891
8101
  /**
8102
+ * Unified event-kind taxonomy — THE single source of truth for
8103
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8104
+ * icon }`.
8105
+ *
8106
+ * This dictionary folds together what used to be scattered across four
8107
+ * copies:
8108
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8109
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8110
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8111
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8112
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8113
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8114
+ *
8115
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8116
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8117
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8118
+ * color or an icon: they read this dictionary (server descriptors carry the
8119
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8120
+ *
8121
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8122
+ */
8123
+ var TAXONOMY_COLORS = {
8124
+ motion: "#f59e0b",
8125
+ audio: "#06b6d4",
8126
+ person: "#22c55e",
8127
+ vehicle: "#3b82f6",
8128
+ animal: "#f97316",
8129
+ package: "#a855f7",
8130
+ sensor: "#8b5cf6",
8131
+ control: "#10b981",
8132
+ genericDetection: "#64748b"
8133
+ };
8134
+ var DETECTION_SUB_COLORS = {
8135
+ car: "#f59e0b",
8136
+ truck: "#d97706",
8137
+ bus: "#b45309",
8138
+ motorcycle: "#eab308",
8139
+ bicycle: "#ca8a04",
8140
+ airplane: "#60a5fa",
8141
+ boat: "#2563eb",
8142
+ train: "#1d4ed8",
8143
+ bird: "#14b8a6",
8144
+ dog: "#84cc16",
8145
+ cat: "#f97316",
8146
+ horse: "#a16207",
8147
+ sheep: "#a3a3a3",
8148
+ cow: "#78716c",
8149
+ elephant: "#6b7280",
8150
+ bear: "#7c2d12",
8151
+ zebra: "#404040",
8152
+ giraffe: "#d4a373"
8153
+ };
8154
+ function titleCase(id) {
8155
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8156
+ }
8157
+ var entries = /* @__PURE__ */ new Map();
8158
+ function macro(kind, category, color, iconId, label) {
8159
+ entries.set(kind, {
8160
+ kind,
8161
+ parentKind: null,
8162
+ level: "macro",
8163
+ category,
8164
+ color,
8165
+ iconId,
8166
+ labelKey: `eventKind.${kind}`,
8167
+ label
8168
+ });
8169
+ }
8170
+ function sub(kind, parentKind, category, color, iconId, label) {
8171
+ entries.set(kind, {
8172
+ kind,
8173
+ parentKind,
8174
+ level: "sub",
8175
+ category,
8176
+ color,
8177
+ iconId,
8178
+ labelKey: `eventKind.${kind}`,
8179
+ label
8180
+ });
8181
+ }
8182
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8183
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8184
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8185
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8186
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8187
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8188
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8189
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8190
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8191
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8192
+ if (entries.has(cocoClass)) continue;
8193
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8194
+ }
8195
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8196
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8197
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8198
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8199
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8200
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8201
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8202
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8203
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8204
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8205
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8206
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8207
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8208
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8209
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8210
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8211
+ sub("button", "control", "control", "#10b981", "button", "Button");
8212
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8213
+ for (const l of AUDIO_MACRO_LABELS) {
8214
+ const kind = `audio-${l.id}`;
8215
+ if (entries.has(kind)) continue;
8216
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8217
+ }
8218
+ /** The complete taxonomy dictionary, keyed by kind. */
8219
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8220
+ /**
7892
8221
  * Error types for the safe expression engine. Two distinct classes so callers
7893
8222
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7894
8223
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14843,9 +15172,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14843
15172
  * `package` backs the package-drop detector — a package zone is a
14844
15173
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14845
15174
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14846
- * The orchestrator provider that writes this stage lands in a later
14847
- * slice; until then the mirror carries only `{motion, detection}` and
14848
- * consumers read `package` as absent (treat as `[]`).
15175
+ * The orchestrator provider writes this stage as a first-class slice
15176
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15177
+ * package}` shape, so consumers read the current package rules directly
15178
+ * off `device.state.zoneRules.value.package`.
14849
15179
  */
14850
15180
  runtimeState: object({
14851
15181
  motion: array(ZoneRuleSchema).readonly(),
@@ -18837,17 +19167,30 @@ var EventKindCategorySchema = _enum([
18837
19167
  "audio",
18838
19168
  "detection",
18839
19169
  "sensor",
19170
+ "control",
18840
19171
  "custom",
18841
19172
  "package"
18842
19173
  ]);
19174
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19175
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18843
19176
  var EventKindDescriptorSchema = object({
18844
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19177
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18845
19178
  kind: string(),
19179
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19180
+ labelKey: string(),
19181
+ /** English fallback label (kept for clients that don't translate). */
18846
19182
  label: string(),
18847
19183
  /** Hex color for timeline/legend rendering. */
18848
19184
  color: string(),
19185
+ /** Dictionary id → lucide component on the UI side. */
19186
+ iconId: string(),
19187
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18849
19188
  icon: EventKindIconSchema,
18850
19189
  category: EventKindCategorySchema,
19190
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19191
+ parentKind: string().nullable(),
19192
+ /** Derived from `parentKind`, explicit for the client tree. */
19193
+ level: EventKindLevelSchema,
18851
19194
  /** Which cap + device contributes this kind. For built-ins the camera
18852
19195
  * itself; for sensor kinds the LINKED source device. */
18853
19196
  source: object({
@@ -18920,11 +19263,21 @@ var TrackAudioLabelSchema = object({
18920
19263
  firstAt: number(),
18921
19264
  lastAt: number()
18922
19265
  });
19266
+ /**
19267
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19268
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19269
+ * linked sensor/control state change (no positions; carries a snapshot). The
19270
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19271
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19272
+ */
19273
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
18923
19274
  var TrackSchema = object({
18924
19275
  trackId: string(),
18925
19276
  deviceId: number(),
18926
19277
  className: string(),
18927
19278
  label: string().optional(),
19279
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19280
+ source: TrackSourceSchema.optional(),
18928
19281
  firstSeen: number(),
18929
19282
  lastSeen: number(),
18930
19283
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19179,6 +19532,26 @@ var TrackCascadeCountsSchema = object({
19179
19532
  /** Per-track CLIP search vectors removed (best-effort). */
19180
19533
  embeddings: number().int()
19181
19534
  });
19535
+ /** Event-store footprint for one camera. */
19536
+ var EventStoreDeviceFootprintSchema = object({
19537
+ deviceId: number(),
19538
+ /** Persisted event rows (motion + object + audio) for the camera. */
19539
+ rows: number().int(),
19540
+ /** Event-owned media bytes on disk for the camera. */
19541
+ bytes: number().int()
19542
+ });
19543
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19544
+ var EventStoreFootprintSchema = object({
19545
+ totalRows: number().int(),
19546
+ totalBytes: number().int(),
19547
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19548
+ });
19549
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19550
+ var EventPruneCountsSchema = object({
19551
+ motion: number().int(),
19552
+ object: number().int(),
19553
+ audio: number().int()
19554
+ });
19182
19555
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19183
19556
  deviceId: number(),
19184
19557
  trackId: string()
@@ -19242,6 +19615,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19242
19615
  }), {
19243
19616
  kind: "mutation",
19244
19617
  auth: "admin"
19618
+ }), method(object({}), EventStoreFootprintSchema, {
19619
+ kind: "query",
19620
+ auth: "admin"
19621
+ }), method(object({
19622
+ olderThanMs: number(),
19623
+ reason: OpsLogReasonSchema.optional()
19624
+ }), EventPruneCountsSchema, {
19625
+ kind: "mutation",
19626
+ auth: "admin"
19627
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19628
+ kind: "mutation",
19629
+ auth: "admin"
19630
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19631
+ kind: "query",
19632
+ auth: "admin"
19245
19633
  }), method(object({
19246
19634
  eventId: string(),
19247
19635
  kind: MediaFileKindEnum.optional()
@@ -19266,6 +19654,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19266
19654
  eventId: string(),
19267
19655
  timestamp: number()
19268
19656
  });
19657
+ /**
19658
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19659
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19660
+ * caps into per-camera event-kind descriptors.
19661
+ *
19662
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19663
+ * is NOT duplicated here — every entry is derived from the single
19664
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19665
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19666
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19667
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19668
+ * eventful cap is missing.
19669
+ */
19670
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19671
+ var LEGACY_ICON = {
19672
+ motion: "motion",
19673
+ audio: "audio",
19674
+ person: "person",
19675
+ vehicle: "vehicle",
19676
+ animal: "animal",
19677
+ package: "package",
19678
+ door: "door",
19679
+ pir: "pir",
19680
+ smoke: "smoke",
19681
+ water: "water",
19682
+ button: "button",
19683
+ generic: "generic",
19684
+ gas: "smoke",
19685
+ vibration: "generic",
19686
+ tamper: "generic",
19687
+ presence: "person",
19688
+ lock: "generic",
19689
+ siren: "generic",
19690
+ switch: "generic",
19691
+ doorbell: "button"
19692
+ };
19693
+ function legacyIcon(iconId) {
19694
+ return LEGACY_ICON[iconId] ?? "generic";
19695
+ }
19696
+ /**
19697
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19698
+ * The anti-drift guard cross-checks this against the eventful caps declared
19699
+ * in `packages/types/src/capabilities/*.cap.ts`.
19700
+ */
19701
+ var CAP_TO_KIND = {
19702
+ contact: "contact",
19703
+ motion: "motion-sensor",
19704
+ smoke: "smoke",
19705
+ flood: "flood",
19706
+ gas: "gas",
19707
+ "carbon-monoxide": "carbon-monoxide",
19708
+ vibration: "vibration",
19709
+ tamper: "tamper",
19710
+ presence: "presence",
19711
+ "enum-sensor": "enum-sensor",
19712
+ "event-emitter": "device-event",
19713
+ "lock-control": "lock",
19714
+ switch: "switch",
19715
+ button: "button",
19716
+ doorbell: "doorbell"
19717
+ };
19718
+ function buildDescriptor(capName, kind) {
19719
+ const t = EVENT_TAXONOMY[kind];
19720
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19721
+ return {
19722
+ ...t,
19723
+ icon: legacyIcon(t.iconId)
19724
+ };
19725
+ }
19726
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19269
19727
  var CameraPipelineConfigSchema = object({
19270
19728
  engine: PipelineEngineChoiceSchema.optional(),
19271
19729
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22475,13 +22933,29 @@ method(object({
22475
22933
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22476
22934
  kind: "mutation",
22477
22935
  auth: "admin"
22478
- }), method(object({ deviceId: number() }), object({
22936
+ }), method(object({
22937
+ deviceId: number(),
22938
+ reason: OpsLogReasonSchema.optional()
22939
+ }), object({
22479
22940
  floorMs: number().nullable(),
22480
22941
  deletedBuckets: number().int(),
22481
22942
  reclaimedBytes: number().int()
22482
22943
  }), {
22483
22944
  kind: "mutation",
22484
22945
  auth: "admin"
22946
+ }), method(object({
22947
+ deviceId: number(),
22948
+ fromMs: number().optional(),
22949
+ toMs: number().optional()
22950
+ }), object({
22951
+ deletedBuckets: number().int(),
22952
+ reclaimedBytes: number().int()
22953
+ }), {
22954
+ kind: "mutation",
22955
+ auth: "admin"
22956
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22957
+ kind: "query",
22958
+ auth: "admin"
22485
22959
  });
22486
22960
  /**
22487
22961
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25603,6 +26077,12 @@ Object.freeze({
25603
26077
  addonId: null,
25604
26078
  access: "delete"
25605
26079
  },
26080
+ "pipelineAnalytics.deleteDeviceEvents": {
26081
+ capName: "pipeline-analytics",
26082
+ capScope: "device",
26083
+ addonId: null,
26084
+ access: "delete"
26085
+ },
25606
26086
  "pipelineAnalytics.deleteTracks": {
25607
26087
  capName: "pipeline-analytics",
25608
26088
  capScope: "device",
@@ -25633,6 +26113,12 @@ Object.freeze({
25633
26113
  addonId: null,
25634
26114
  access: "view"
25635
26115
  },
26116
+ "pipelineAnalytics.getEventStoreFootprint": {
26117
+ capName: "pipeline-analytics",
26118
+ capScope: "device",
26119
+ addonId: null,
26120
+ access: "view"
26121
+ },
25636
26122
  "pipelineAnalytics.getKeyEvents": {
25637
26123
  capName: "pipeline-analytics",
25638
26124
  capScope: "device",
@@ -25675,6 +26161,12 @@ Object.freeze({
25675
26161
  addonId: null,
25676
26162
  access: "view"
25677
26163
  },
26164
+ "pipelineAnalytics.listOpsLog": {
26165
+ capName: "pipeline-analytics",
26166
+ capScope: "device",
26167
+ addonId: null,
26168
+ access: "view"
26169
+ },
25678
26170
  "pipelineAnalytics.listRecentTracks": {
25679
26171
  capName: "pipeline-analytics",
25680
26172
  capScope: "device",
@@ -25687,6 +26179,12 @@ Object.freeze({
25687
26179
  addonId: null,
25688
26180
  access: "view"
25689
26181
  },
26182
+ "pipelineAnalytics.pruneEvents": {
26183
+ capName: "pipeline-analytics",
26184
+ capScope: "device",
26185
+ addonId: null,
26186
+ access: "create"
26187
+ },
25690
26188
  "pipelineAnalytics.pruneEventsBefore": {
25691
26189
  capName: "pipeline-analytics",
25692
26190
  capScope: "device",
@@ -26449,6 +26947,12 @@ Object.freeze({
26449
26947
  addonId: null,
26450
26948
  access: "create"
26451
26949
  },
26950
+ "recording.deleteFootprint": {
26951
+ capName: "recording",
26952
+ capScope: "system",
26953
+ addonId: null,
26954
+ access: "delete"
26955
+ },
26452
26956
  "recording.getAvailability": {
26453
26957
  capName: "recording",
26454
26958
  capScope: "system",
@@ -26479,6 +26983,12 @@ Object.freeze({
26479
26983
  addonId: null,
26480
26984
  access: "view"
26481
26985
  },
26986
+ "recording.listOpsLog": {
26987
+ capName: "recording",
26988
+ capScope: "system",
26989
+ addonId: null,
26990
+ access: "view"
26991
+ },
26482
26992
  "recording.locateSegment": {
26483
26993
  capName: "recording",
26484
26994
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7381,6 +7381,62 @@ var RecordingConfigSchema = object({
7381
7381
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7382
7382
  });
7383
7383
  /**
7384
+ * Ops-log — the durable, append-only operations audit shared by the
7385
+ * recordings and events management surfaces.
7386
+ *
7387
+ * ONE row shape is reused for both domains so a single "Activity" view can
7388
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7389
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7390
+ * management operation, WHY it ran (reason), and its measurable effect
7391
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7392
+ * never fail the operation it records.
7393
+ */
7394
+ /** Which management domain the operation belongs to. */
7395
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7396
+ /** The kind of management operation performed. */
7397
+ var OpsLogOpSchema = _enum([
7398
+ "prune",
7399
+ "manual-delete",
7400
+ "rescan",
7401
+ "retention-run"
7402
+ ]);
7403
+ /** Why the operation ran. */
7404
+ var OpsLogReasonSchema = _enum([
7405
+ "retention",
7406
+ "quota",
7407
+ "manual",
7408
+ "operator"
7409
+ ]);
7410
+ /** One audit row, shared verbatim by both domains. */
7411
+ var OpsLogEntrySchema = object({
7412
+ /** Unique row id. */
7413
+ id: string(),
7414
+ /** Epoch ms the operation completed. */
7415
+ at: number(),
7416
+ domain: OpsLogDomainSchema,
7417
+ op: OpsLogOpSchema,
7418
+ reason: OpsLogReasonSchema,
7419
+ /** The camera the op targeted; null for a cluster/global op. */
7420
+ deviceId: number().nullable(),
7421
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7422
+ nodeId: string(),
7423
+ /** Buckets / rows deleted (op-specific unit). */
7424
+ itemsAffected: number(),
7425
+ /** Bytes reclaimed by the op (0 when not measurable). */
7426
+ bytesReclaimed: number(),
7427
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7428
+ detail: string().nullable(),
7429
+ /** Who/what triggered the op. */
7430
+ actor: string()
7431
+ });
7432
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7433
+ var OpsLogQueryInputSchema = object({
7434
+ /** Restrict to a single camera; omit for every row. */
7435
+ deviceId: number().optional(),
7436
+ /** Max rows returned, newest-first. */
7437
+ limit: number().int().min(1).max(1e3).optional()
7438
+ });
7439
+ /**
7384
7440
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7385
7441
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7386
7442
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7624,6 +7680,160 @@ var EncodeProfileSchema = object({
7624
7680
  */
7625
7681
  outputArgs: array(string()).optional()
7626
7682
  });
7683
+ var COCO_TO_MACRO = {
7684
+ mapping: {
7685
+ person: "person",
7686
+ bicycle: "vehicle",
7687
+ car: "vehicle",
7688
+ motorcycle: "vehicle",
7689
+ airplane: "vehicle",
7690
+ bus: "vehicle",
7691
+ train: "vehicle",
7692
+ truck: "vehicle",
7693
+ boat: "vehicle",
7694
+ bird: "animal",
7695
+ cat: "animal",
7696
+ dog: "animal",
7697
+ horse: "animal",
7698
+ sheep: "animal",
7699
+ cow: "animal",
7700
+ elephant: "animal",
7701
+ bear: "animal",
7702
+ zebra: "animal",
7703
+ giraffe: "animal",
7704
+ suitcase: "package",
7705
+ backpack: "package",
7706
+ handbag: "package"
7707
+ },
7708
+ preserveOriginal: false
7709
+ };
7710
+ var AUDIO_MACRO_LABELS = [
7711
+ {
7712
+ id: "speech",
7713
+ name: "Speech",
7714
+ icon: "🗣️"
7715
+ },
7716
+ {
7717
+ id: "scream",
7718
+ name: "Scream / Shout",
7719
+ icon: "😱"
7720
+ },
7721
+ {
7722
+ id: "crying",
7723
+ name: "Crying / Baby",
7724
+ icon: "😢"
7725
+ },
7726
+ {
7727
+ id: "laughter",
7728
+ name: "Laughter",
7729
+ icon: "😂"
7730
+ },
7731
+ {
7732
+ id: "music",
7733
+ name: "Music",
7734
+ icon: "🎵"
7735
+ },
7736
+ {
7737
+ id: "dog",
7738
+ name: "Dog",
7739
+ icon: "🐕"
7740
+ },
7741
+ {
7742
+ id: "cat",
7743
+ name: "Cat",
7744
+ icon: "🐈"
7745
+ },
7746
+ {
7747
+ id: "bird",
7748
+ name: "Bird",
7749
+ icon: "🐦"
7750
+ },
7751
+ {
7752
+ id: "animal",
7753
+ name: "Animal (other)",
7754
+ icon: "🐾"
7755
+ },
7756
+ {
7757
+ id: "alarm",
7758
+ name: "Alarm / Siren",
7759
+ icon: "🚨"
7760
+ },
7761
+ {
7762
+ id: "doorbell",
7763
+ name: "Doorbell / Knock",
7764
+ icon: "🔔"
7765
+ },
7766
+ {
7767
+ id: "glass_breaking",
7768
+ name: "Glass Breaking",
7769
+ icon: "💥"
7770
+ },
7771
+ {
7772
+ id: "gunshot",
7773
+ name: "Gunshot / Explosion",
7774
+ icon: "💣"
7775
+ },
7776
+ {
7777
+ id: "vehicle",
7778
+ name: "Vehicle",
7779
+ icon: "🚗"
7780
+ },
7781
+ {
7782
+ id: "siren",
7783
+ name: "Emergency Siren",
7784
+ icon: "🚑"
7785
+ },
7786
+ {
7787
+ id: "fire",
7788
+ name: "Fire / Smoke",
7789
+ icon: "🔥"
7790
+ },
7791
+ {
7792
+ id: "water",
7793
+ name: "Water",
7794
+ icon: "💧"
7795
+ },
7796
+ {
7797
+ id: "wind",
7798
+ name: "Wind / Weather",
7799
+ icon: "🌬️"
7800
+ },
7801
+ {
7802
+ id: "door",
7803
+ name: "Door",
7804
+ icon: "🚪"
7805
+ },
7806
+ {
7807
+ id: "footsteps",
7808
+ name: "Footsteps",
7809
+ icon: "👣"
7810
+ },
7811
+ {
7812
+ id: "crowd",
7813
+ name: "Crowd / Chatter",
7814
+ icon: "👥"
7815
+ },
7816
+ {
7817
+ id: "telephone",
7818
+ name: "Telephone",
7819
+ icon: "📞"
7820
+ },
7821
+ {
7822
+ id: "engine",
7823
+ name: "Engine / Motor",
7824
+ icon: "⚙️"
7825
+ },
7826
+ {
7827
+ id: "tools",
7828
+ name: "Tools / Construction",
7829
+ icon: "🔨"
7830
+ },
7831
+ {
7832
+ id: "silence",
7833
+ name: "Silence",
7834
+ icon: "🤫"
7835
+ }
7836
+ ];
7627
7837
  var YAMNET_TO_MACRO = {
7628
7838
  mapping: {
7629
7839
  Speech: "speech",
@@ -7890,6 +8100,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7890
8100
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7891
8101
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7892
8102
  /**
8103
+ * Unified event-kind taxonomy — THE single source of truth for
8104
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8105
+ * icon }`.
8106
+ *
8107
+ * This dictionary folds together what used to be scattered across four
8108
+ * copies:
8109
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8110
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8111
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8112
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8113
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8114
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8115
+ *
8116
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8117
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8118
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8119
+ * color or an icon: they read this dictionary (server descriptors carry the
8120
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8121
+ *
8122
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8123
+ */
8124
+ var TAXONOMY_COLORS = {
8125
+ motion: "#f59e0b",
8126
+ audio: "#06b6d4",
8127
+ person: "#22c55e",
8128
+ vehicle: "#3b82f6",
8129
+ animal: "#f97316",
8130
+ package: "#a855f7",
8131
+ sensor: "#8b5cf6",
8132
+ control: "#10b981",
8133
+ genericDetection: "#64748b"
8134
+ };
8135
+ var DETECTION_SUB_COLORS = {
8136
+ car: "#f59e0b",
8137
+ truck: "#d97706",
8138
+ bus: "#b45309",
8139
+ motorcycle: "#eab308",
8140
+ bicycle: "#ca8a04",
8141
+ airplane: "#60a5fa",
8142
+ boat: "#2563eb",
8143
+ train: "#1d4ed8",
8144
+ bird: "#14b8a6",
8145
+ dog: "#84cc16",
8146
+ cat: "#f97316",
8147
+ horse: "#a16207",
8148
+ sheep: "#a3a3a3",
8149
+ cow: "#78716c",
8150
+ elephant: "#6b7280",
8151
+ bear: "#7c2d12",
8152
+ zebra: "#404040",
8153
+ giraffe: "#d4a373"
8154
+ };
8155
+ function titleCase(id) {
8156
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8157
+ }
8158
+ var entries = /* @__PURE__ */ new Map();
8159
+ function macro(kind, category, color, iconId, label) {
8160
+ entries.set(kind, {
8161
+ kind,
8162
+ parentKind: null,
8163
+ level: "macro",
8164
+ category,
8165
+ color,
8166
+ iconId,
8167
+ labelKey: `eventKind.${kind}`,
8168
+ label
8169
+ });
8170
+ }
8171
+ function sub(kind, parentKind, category, color, iconId, label) {
8172
+ entries.set(kind, {
8173
+ kind,
8174
+ parentKind,
8175
+ level: "sub",
8176
+ category,
8177
+ color,
8178
+ iconId,
8179
+ labelKey: `eventKind.${kind}`,
8180
+ label
8181
+ });
8182
+ }
8183
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8184
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8185
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8186
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8187
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8188
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8189
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8190
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8191
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8192
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8193
+ if (entries.has(cocoClass)) continue;
8194
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8195
+ }
8196
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8197
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8198
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8199
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8200
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8201
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8202
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8203
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8204
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8205
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8206
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8207
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8208
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8209
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8210
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8211
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8212
+ sub("button", "control", "control", "#10b981", "button", "Button");
8213
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8214
+ for (const l of AUDIO_MACRO_LABELS) {
8215
+ const kind = `audio-${l.id}`;
8216
+ if (entries.has(kind)) continue;
8217
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8218
+ }
8219
+ /** The complete taxonomy dictionary, keyed by kind. */
8220
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8221
+ /**
7893
8222
  * Error types for the safe expression engine. Two distinct classes so callers
7894
8223
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7895
8224
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14844,9 +15173,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14844
15173
  * `package` backs the package-drop detector — a package zone is a
14845
15174
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14846
15175
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14847
- * The orchestrator provider that writes this stage lands in a later
14848
- * slice; until then the mirror carries only `{motion, detection}` and
14849
- * consumers read `package` as absent (treat as `[]`).
15176
+ * The orchestrator provider writes this stage as a first-class slice
15177
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15178
+ * package}` shape, so consumers read the current package rules directly
15179
+ * off `device.state.zoneRules.value.package`.
14850
15180
  */
14851
15181
  runtimeState: object({
14852
15182
  motion: array(ZoneRuleSchema).readonly(),
@@ -18838,17 +19168,30 @@ var EventKindCategorySchema = _enum([
18838
19168
  "audio",
18839
19169
  "detection",
18840
19170
  "sensor",
19171
+ "control",
18841
19172
  "custom",
18842
19173
  "package"
18843
19174
  ]);
19175
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19176
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18844
19177
  var EventKindDescriptorSchema = object({
18845
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19178
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18846
19179
  kind: string(),
19180
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19181
+ labelKey: string(),
19182
+ /** English fallback label (kept for clients that don't translate). */
18847
19183
  label: string(),
18848
19184
  /** Hex color for timeline/legend rendering. */
18849
19185
  color: string(),
19186
+ /** Dictionary id → lucide component on the UI side. */
19187
+ iconId: string(),
19188
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18850
19189
  icon: EventKindIconSchema,
18851
19190
  category: EventKindCategorySchema,
19191
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19192
+ parentKind: string().nullable(),
19193
+ /** Derived from `parentKind`, explicit for the client tree. */
19194
+ level: EventKindLevelSchema,
18852
19195
  /** Which cap + device contributes this kind. For built-ins the camera
18853
19196
  * itself; for sensor kinds the LINKED source device. */
18854
19197
  source: object({
@@ -18921,11 +19264,21 @@ var TrackAudioLabelSchema = object({
18921
19264
  firstAt: number(),
18922
19265
  lastAt: number()
18923
19266
  });
19267
+ /**
19268
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19269
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19270
+ * linked sensor/control state change (no positions; carries a snapshot). The
19271
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19272
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19273
+ */
19274
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
18924
19275
  var TrackSchema = object({
18925
19276
  trackId: string(),
18926
19277
  deviceId: number(),
18927
19278
  className: string(),
18928
19279
  label: string().optional(),
19280
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19281
+ source: TrackSourceSchema.optional(),
18929
19282
  firstSeen: number(),
18930
19283
  lastSeen: number(),
18931
19284
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19180,6 +19533,26 @@ var TrackCascadeCountsSchema = object({
19180
19533
  /** Per-track CLIP search vectors removed (best-effort). */
19181
19534
  embeddings: number().int()
19182
19535
  });
19536
+ /** Event-store footprint for one camera. */
19537
+ var EventStoreDeviceFootprintSchema = object({
19538
+ deviceId: number(),
19539
+ /** Persisted event rows (motion + object + audio) for the camera. */
19540
+ rows: number().int(),
19541
+ /** Event-owned media bytes on disk for the camera. */
19542
+ bytes: number().int()
19543
+ });
19544
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19545
+ var EventStoreFootprintSchema = object({
19546
+ totalRows: number().int(),
19547
+ totalBytes: number().int(),
19548
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19549
+ });
19550
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19551
+ var EventPruneCountsSchema = object({
19552
+ motion: number().int(),
19553
+ object: number().int(),
19554
+ audio: number().int()
19555
+ });
19183
19556
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19184
19557
  deviceId: number(),
19185
19558
  trackId: string()
@@ -19243,6 +19616,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19243
19616
  }), {
19244
19617
  kind: "mutation",
19245
19618
  auth: "admin"
19619
+ }), method(object({}), EventStoreFootprintSchema, {
19620
+ kind: "query",
19621
+ auth: "admin"
19622
+ }), method(object({
19623
+ olderThanMs: number(),
19624
+ reason: OpsLogReasonSchema.optional()
19625
+ }), EventPruneCountsSchema, {
19626
+ kind: "mutation",
19627
+ auth: "admin"
19628
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19629
+ kind: "mutation",
19630
+ auth: "admin"
19631
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19632
+ kind: "query",
19633
+ auth: "admin"
19246
19634
  }), method(object({
19247
19635
  eventId: string(),
19248
19636
  kind: MediaFileKindEnum.optional()
@@ -19267,6 +19655,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19267
19655
  eventId: string(),
19268
19656
  timestamp: number()
19269
19657
  });
19658
+ /**
19659
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19660
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19661
+ * caps into per-camera event-kind descriptors.
19662
+ *
19663
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19664
+ * is NOT duplicated here — every entry is derived from the single
19665
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19666
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19667
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19668
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19669
+ * eventful cap is missing.
19670
+ */
19671
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19672
+ var LEGACY_ICON = {
19673
+ motion: "motion",
19674
+ audio: "audio",
19675
+ person: "person",
19676
+ vehicle: "vehicle",
19677
+ animal: "animal",
19678
+ package: "package",
19679
+ door: "door",
19680
+ pir: "pir",
19681
+ smoke: "smoke",
19682
+ water: "water",
19683
+ button: "button",
19684
+ generic: "generic",
19685
+ gas: "smoke",
19686
+ vibration: "generic",
19687
+ tamper: "generic",
19688
+ presence: "person",
19689
+ lock: "generic",
19690
+ siren: "generic",
19691
+ switch: "generic",
19692
+ doorbell: "button"
19693
+ };
19694
+ function legacyIcon(iconId) {
19695
+ return LEGACY_ICON[iconId] ?? "generic";
19696
+ }
19697
+ /**
19698
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19699
+ * The anti-drift guard cross-checks this against the eventful caps declared
19700
+ * in `packages/types/src/capabilities/*.cap.ts`.
19701
+ */
19702
+ var CAP_TO_KIND = {
19703
+ contact: "contact",
19704
+ motion: "motion-sensor",
19705
+ smoke: "smoke",
19706
+ flood: "flood",
19707
+ gas: "gas",
19708
+ "carbon-monoxide": "carbon-monoxide",
19709
+ vibration: "vibration",
19710
+ tamper: "tamper",
19711
+ presence: "presence",
19712
+ "enum-sensor": "enum-sensor",
19713
+ "event-emitter": "device-event",
19714
+ "lock-control": "lock",
19715
+ switch: "switch",
19716
+ button: "button",
19717
+ doorbell: "doorbell"
19718
+ };
19719
+ function buildDescriptor(capName, kind) {
19720
+ const t = EVENT_TAXONOMY[kind];
19721
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19722
+ return {
19723
+ ...t,
19724
+ icon: legacyIcon(t.iconId)
19725
+ };
19726
+ }
19727
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19270
19728
  var CameraPipelineConfigSchema = object({
19271
19729
  engine: PipelineEngineChoiceSchema.optional(),
19272
19730
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22476,13 +22934,29 @@ method(object({
22476
22934
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22477
22935
  kind: "mutation",
22478
22936
  auth: "admin"
22479
- }), method(object({ deviceId: number() }), object({
22937
+ }), method(object({
22938
+ deviceId: number(),
22939
+ reason: OpsLogReasonSchema.optional()
22940
+ }), object({
22480
22941
  floorMs: number().nullable(),
22481
22942
  deletedBuckets: number().int(),
22482
22943
  reclaimedBytes: number().int()
22483
22944
  }), {
22484
22945
  kind: "mutation",
22485
22946
  auth: "admin"
22947
+ }), method(object({
22948
+ deviceId: number(),
22949
+ fromMs: number().optional(),
22950
+ toMs: number().optional()
22951
+ }), object({
22952
+ deletedBuckets: number().int(),
22953
+ reclaimedBytes: number().int()
22954
+ }), {
22955
+ kind: "mutation",
22956
+ auth: "admin"
22957
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22958
+ kind: "query",
22959
+ auth: "admin"
22486
22960
  });
22487
22961
  /**
22488
22962
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25604,6 +26078,12 @@ Object.freeze({
25604
26078
  addonId: null,
25605
26079
  access: "delete"
25606
26080
  },
26081
+ "pipelineAnalytics.deleteDeviceEvents": {
26082
+ capName: "pipeline-analytics",
26083
+ capScope: "device",
26084
+ addonId: null,
26085
+ access: "delete"
26086
+ },
25607
26087
  "pipelineAnalytics.deleteTracks": {
25608
26088
  capName: "pipeline-analytics",
25609
26089
  capScope: "device",
@@ -25634,6 +26114,12 @@ Object.freeze({
25634
26114
  addonId: null,
25635
26115
  access: "view"
25636
26116
  },
26117
+ "pipelineAnalytics.getEventStoreFootprint": {
26118
+ capName: "pipeline-analytics",
26119
+ capScope: "device",
26120
+ addonId: null,
26121
+ access: "view"
26122
+ },
25637
26123
  "pipelineAnalytics.getKeyEvents": {
25638
26124
  capName: "pipeline-analytics",
25639
26125
  capScope: "device",
@@ -25676,6 +26162,12 @@ Object.freeze({
25676
26162
  addonId: null,
25677
26163
  access: "view"
25678
26164
  },
26165
+ "pipelineAnalytics.listOpsLog": {
26166
+ capName: "pipeline-analytics",
26167
+ capScope: "device",
26168
+ addonId: null,
26169
+ access: "view"
26170
+ },
25679
26171
  "pipelineAnalytics.listRecentTracks": {
25680
26172
  capName: "pipeline-analytics",
25681
26173
  capScope: "device",
@@ -25688,6 +26180,12 @@ Object.freeze({
25688
26180
  addonId: null,
25689
26181
  access: "view"
25690
26182
  },
26183
+ "pipelineAnalytics.pruneEvents": {
26184
+ capName: "pipeline-analytics",
26185
+ capScope: "device",
26186
+ addonId: null,
26187
+ access: "create"
26188
+ },
25691
26189
  "pipelineAnalytics.pruneEventsBefore": {
25692
26190
  capName: "pipeline-analytics",
25693
26191
  capScope: "device",
@@ -26450,6 +26948,12 @@ Object.freeze({
26450
26948
  addonId: null,
26451
26949
  access: "create"
26452
26950
  },
26951
+ "recording.deleteFootprint": {
26952
+ capName: "recording",
26953
+ capScope: "system",
26954
+ addonId: null,
26955
+ access: "delete"
26956
+ },
26453
26957
  "recording.getAvailability": {
26454
26958
  capName: "recording",
26455
26959
  capScope: "system",
@@ -26480,6 +26984,12 @@ Object.freeze({
26480
26984
  addonId: null,
26481
26985
  access: "view"
26482
26986
  },
26987
+ "recording.listOpsLog": {
26988
+ capName: "recording",
26989
+ capScope: "system",
26990
+ addonId: null,
26991
+ access: "view"
26992
+ },
26483
26993
  "recording.locateSegment": {
26484
26994
  capName: "recording",
26485
26995
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreo",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Dreo smart-device (fan / air-circulator / purifier / heater / humidifier) device-provider addon for CamStack — wraps the @apocaliss92/nodedreo Dreo cloud client (REST + WebSocket)",
5
5
  "keywords": [
6
6
  "camstack",