@camstack/addon-ai 0.1.6 → 0.1.8

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
@@ -7372,6 +7372,62 @@ var RecordingConfigSchema = object({
7372
7372
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7373
7373
  });
7374
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
+ /**
7375
7431
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7376
7432
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7377
7433
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7645,6 +7701,160 @@ var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
7645
7701
  function nodePin(nodeId) {
7646
7702
  return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: nodeId } };
7647
7703
  }
7704
+ var COCO_TO_MACRO = {
7705
+ mapping: {
7706
+ person: "person",
7707
+ bicycle: "vehicle",
7708
+ car: "vehicle",
7709
+ motorcycle: "vehicle",
7710
+ airplane: "vehicle",
7711
+ bus: "vehicle",
7712
+ train: "vehicle",
7713
+ truck: "vehicle",
7714
+ boat: "vehicle",
7715
+ bird: "animal",
7716
+ cat: "animal",
7717
+ dog: "animal",
7718
+ horse: "animal",
7719
+ sheep: "animal",
7720
+ cow: "animal",
7721
+ elephant: "animal",
7722
+ bear: "animal",
7723
+ zebra: "animal",
7724
+ giraffe: "animal",
7725
+ suitcase: "package",
7726
+ backpack: "package",
7727
+ handbag: "package"
7728
+ },
7729
+ preserveOriginal: false
7730
+ };
7731
+ var AUDIO_MACRO_LABELS = [
7732
+ {
7733
+ id: "speech",
7734
+ name: "Speech",
7735
+ icon: "🗣️"
7736
+ },
7737
+ {
7738
+ id: "scream",
7739
+ name: "Scream / Shout",
7740
+ icon: "😱"
7741
+ },
7742
+ {
7743
+ id: "crying",
7744
+ name: "Crying / Baby",
7745
+ icon: "😢"
7746
+ },
7747
+ {
7748
+ id: "laughter",
7749
+ name: "Laughter",
7750
+ icon: "😂"
7751
+ },
7752
+ {
7753
+ id: "music",
7754
+ name: "Music",
7755
+ icon: "🎵"
7756
+ },
7757
+ {
7758
+ id: "dog",
7759
+ name: "Dog",
7760
+ icon: "🐕"
7761
+ },
7762
+ {
7763
+ id: "cat",
7764
+ name: "Cat",
7765
+ icon: "🐈"
7766
+ },
7767
+ {
7768
+ id: "bird",
7769
+ name: "Bird",
7770
+ icon: "🐦"
7771
+ },
7772
+ {
7773
+ id: "animal",
7774
+ name: "Animal (other)",
7775
+ icon: "🐾"
7776
+ },
7777
+ {
7778
+ id: "alarm",
7779
+ name: "Alarm / Siren",
7780
+ icon: "🚨"
7781
+ },
7782
+ {
7783
+ id: "doorbell",
7784
+ name: "Doorbell / Knock",
7785
+ icon: "🔔"
7786
+ },
7787
+ {
7788
+ id: "glass_breaking",
7789
+ name: "Glass Breaking",
7790
+ icon: "💥"
7791
+ },
7792
+ {
7793
+ id: "gunshot",
7794
+ name: "Gunshot / Explosion",
7795
+ icon: "💣"
7796
+ },
7797
+ {
7798
+ id: "vehicle",
7799
+ name: "Vehicle",
7800
+ icon: "🚗"
7801
+ },
7802
+ {
7803
+ id: "siren",
7804
+ name: "Emergency Siren",
7805
+ icon: "🚑"
7806
+ },
7807
+ {
7808
+ id: "fire",
7809
+ name: "Fire / Smoke",
7810
+ icon: "🔥"
7811
+ },
7812
+ {
7813
+ id: "water",
7814
+ name: "Water",
7815
+ icon: "💧"
7816
+ },
7817
+ {
7818
+ id: "wind",
7819
+ name: "Wind / Weather",
7820
+ icon: "🌬️"
7821
+ },
7822
+ {
7823
+ id: "door",
7824
+ name: "Door",
7825
+ icon: "🚪"
7826
+ },
7827
+ {
7828
+ id: "footsteps",
7829
+ name: "Footsteps",
7830
+ icon: "👣"
7831
+ },
7832
+ {
7833
+ id: "crowd",
7834
+ name: "Crowd / Chatter",
7835
+ icon: "👥"
7836
+ },
7837
+ {
7838
+ id: "telephone",
7839
+ name: "Telephone",
7840
+ icon: "📞"
7841
+ },
7842
+ {
7843
+ id: "engine",
7844
+ name: "Engine / Motor",
7845
+ icon: "⚙️"
7846
+ },
7847
+ {
7848
+ id: "tools",
7849
+ name: "Tools / Construction",
7850
+ icon: "🔨"
7851
+ },
7852
+ {
7853
+ id: "silence",
7854
+ name: "Silence",
7855
+ icon: "🤫"
7856
+ }
7857
+ ];
7648
7858
  var YAMNET_TO_MACRO = {
7649
7859
  mapping: {
7650
7860
  Speech: "speech",
@@ -7911,6 +8121,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7911
8121
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7912
8122
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7913
8123
  /**
8124
+ * Unified event-kind taxonomy — THE single source of truth for
8125
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8126
+ * icon }`.
8127
+ *
8128
+ * This dictionary folds together what used to be scattered across four
8129
+ * copies:
8130
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8131
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8132
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8133
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8134
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8135
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8136
+ *
8137
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8138
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8139
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8140
+ * color or an icon: they read this dictionary (server descriptors carry the
8141
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8142
+ *
8143
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8144
+ */
8145
+ var TAXONOMY_COLORS = {
8146
+ motion: "#f59e0b",
8147
+ audio: "#06b6d4",
8148
+ person: "#22c55e",
8149
+ vehicle: "#3b82f6",
8150
+ animal: "#f97316",
8151
+ package: "#a855f7",
8152
+ sensor: "#8b5cf6",
8153
+ control: "#10b981",
8154
+ genericDetection: "#64748b"
8155
+ };
8156
+ var DETECTION_SUB_COLORS = {
8157
+ car: "#f59e0b",
8158
+ truck: "#d97706",
8159
+ bus: "#b45309",
8160
+ motorcycle: "#eab308",
8161
+ bicycle: "#ca8a04",
8162
+ airplane: "#60a5fa",
8163
+ boat: "#2563eb",
8164
+ train: "#1d4ed8",
8165
+ bird: "#14b8a6",
8166
+ dog: "#84cc16",
8167
+ cat: "#f97316",
8168
+ horse: "#a16207",
8169
+ sheep: "#a3a3a3",
8170
+ cow: "#78716c",
8171
+ elephant: "#6b7280",
8172
+ bear: "#7c2d12",
8173
+ zebra: "#404040",
8174
+ giraffe: "#d4a373"
8175
+ };
8176
+ function titleCase(id) {
8177
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8178
+ }
8179
+ var entries = /* @__PURE__ */ new Map();
8180
+ function macro(kind, category, color, iconId, label) {
8181
+ entries.set(kind, {
8182
+ kind,
8183
+ parentKind: null,
8184
+ level: "macro",
8185
+ category,
8186
+ color,
8187
+ iconId,
8188
+ labelKey: `eventKind.${kind}`,
8189
+ label
8190
+ });
8191
+ }
8192
+ function sub(kind, parentKind, category, color, iconId, label) {
8193
+ entries.set(kind, {
8194
+ kind,
8195
+ parentKind,
8196
+ level: "sub",
8197
+ category,
8198
+ color,
8199
+ iconId,
8200
+ labelKey: `eventKind.${kind}`,
8201
+ label
8202
+ });
8203
+ }
8204
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8205
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8206
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8207
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8208
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8209
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8210
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8211
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8212
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8213
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8214
+ if (entries.has(cocoClass)) continue;
8215
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8216
+ }
8217
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8218
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8219
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8220
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8221
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8222
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8223
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8224
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8225
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8226
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8227
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8228
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8229
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8230
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8231
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8232
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8233
+ sub("button", "control", "control", "#10b981", "button", "Button");
8234
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8235
+ for (const l of AUDIO_MACRO_LABELS) {
8236
+ const kind = `audio-${l.id}`;
8237
+ if (entries.has(kind)) continue;
8238
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8239
+ }
8240
+ /** The complete taxonomy dictionary, keyed by kind. */
8241
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8242
+ /**
7914
8243
  * Error types for the safe expression engine. Two distinct classes so callers
7915
8244
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7916
8245
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15905,17 +16234,30 @@ var EventKindCategorySchema = _enum([
15905
16234
  "audio",
15906
16235
  "detection",
15907
16236
  "sensor",
16237
+ "control",
15908
16238
  "custom",
15909
16239
  "package"
15910
16240
  ]);
16241
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16242
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15911
16243
  var EventKindDescriptorSchema = object({
15912
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16244
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15913
16245
  kind: string(),
16246
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16247
+ labelKey: string(),
16248
+ /** English fallback label (kept for clients that don't translate). */
15914
16249
  label: string(),
15915
16250
  /** Hex color for timeline/legend rendering. */
15916
16251
  color: string(),
16252
+ /** Dictionary id → lucide component on the UI side. */
16253
+ iconId: string(),
16254
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15917
16255
  icon: EventKindIconSchema,
15918
16256
  category: EventKindCategorySchema,
16257
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16258
+ parentKind: string().nullable(),
16259
+ /** Derived from `parentKind`, explicit for the client tree. */
16260
+ level: EventKindLevelSchema,
15919
16261
  /** Which cap + device contributes this kind. For built-ins the camera
15920
16262
  * itself; for sensor kinds the LINKED source device. */
15921
16263
  source: object({
@@ -15988,11 +16330,21 @@ var TrackAudioLabelSchema = object({
15988
16330
  firstAt: number(),
15989
16331
  lastAt: number()
15990
16332
  });
16333
+ /**
16334
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16335
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16336
+ * linked sensor/control state change (no positions; carries a snapshot). The
16337
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16338
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16339
+ */
16340
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15991
16341
  var TrackSchema = object({
15992
16342
  trackId: string(),
15993
16343
  deviceId: number(),
15994
16344
  className: string(),
15995
16345
  label: string().optional(),
16346
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16347
+ source: TrackSourceSchema.optional(),
15996
16348
  firstSeen: number(),
15997
16349
  lastSeen: number(),
15998
16350
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16247,6 +16599,26 @@ var TrackCascadeCountsSchema = object({
16247
16599
  /** Per-track CLIP search vectors removed (best-effort). */
16248
16600
  embeddings: number().int()
16249
16601
  });
16602
+ /** Event-store footprint for one camera. */
16603
+ var EventStoreDeviceFootprintSchema = object({
16604
+ deviceId: number(),
16605
+ /** Persisted event rows (motion + object + audio) for the camera. */
16606
+ rows: number().int(),
16607
+ /** Event-owned media bytes on disk for the camera. */
16608
+ bytes: number().int()
16609
+ });
16610
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16611
+ var EventStoreFootprintSchema = object({
16612
+ totalRows: number().int(),
16613
+ totalBytes: number().int(),
16614
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16615
+ });
16616
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16617
+ var EventPruneCountsSchema = object({
16618
+ motion: number().int(),
16619
+ object: number().int(),
16620
+ audio: number().int()
16621
+ });
16250
16622
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16251
16623
  deviceId: number(),
16252
16624
  trackId: string()
@@ -16310,6 +16682,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16310
16682
  }), {
16311
16683
  kind: "mutation",
16312
16684
  auth: "admin"
16685
+ }), method(object({}), EventStoreFootprintSchema, {
16686
+ kind: "query",
16687
+ auth: "admin"
16688
+ }), method(object({
16689
+ olderThanMs: number(),
16690
+ reason: OpsLogReasonSchema.optional()
16691
+ }), EventPruneCountsSchema, {
16692
+ kind: "mutation",
16693
+ auth: "admin"
16694
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16695
+ kind: "mutation",
16696
+ auth: "admin"
16697
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16698
+ kind: "query",
16699
+ auth: "admin"
16313
16700
  }), method(object({
16314
16701
  eventId: string(),
16315
16702
  kind: MediaFileKindEnum.optional()
@@ -16334,6 +16721,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16334
16721
  eventId: string(),
16335
16722
  timestamp: number()
16336
16723
  });
16724
+ /**
16725
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16726
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16727
+ * caps into per-camera event-kind descriptors.
16728
+ *
16729
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16730
+ * is NOT duplicated here — every entry is derived from the single
16731
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16732
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16733
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16734
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16735
+ * eventful cap is missing.
16736
+ */
16737
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16738
+ var LEGACY_ICON = {
16739
+ motion: "motion",
16740
+ audio: "audio",
16741
+ person: "person",
16742
+ vehicle: "vehicle",
16743
+ animal: "animal",
16744
+ package: "package",
16745
+ door: "door",
16746
+ pir: "pir",
16747
+ smoke: "smoke",
16748
+ water: "water",
16749
+ button: "button",
16750
+ generic: "generic",
16751
+ gas: "smoke",
16752
+ vibration: "generic",
16753
+ tamper: "generic",
16754
+ presence: "person",
16755
+ lock: "generic",
16756
+ siren: "generic",
16757
+ switch: "generic",
16758
+ doorbell: "button"
16759
+ };
16760
+ function legacyIcon(iconId) {
16761
+ return LEGACY_ICON[iconId] ?? "generic";
16762
+ }
16763
+ /**
16764
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16765
+ * The anti-drift guard cross-checks this against the eventful caps declared
16766
+ * in `packages/types/src/capabilities/*.cap.ts`.
16767
+ */
16768
+ var CAP_TO_KIND = {
16769
+ contact: "contact",
16770
+ motion: "motion-sensor",
16771
+ smoke: "smoke",
16772
+ flood: "flood",
16773
+ gas: "gas",
16774
+ "carbon-monoxide": "carbon-monoxide",
16775
+ vibration: "vibration",
16776
+ tamper: "tamper",
16777
+ presence: "presence",
16778
+ "enum-sensor": "enum-sensor",
16779
+ "event-emitter": "device-event",
16780
+ "lock-control": "lock",
16781
+ switch: "switch",
16782
+ button: "button",
16783
+ doorbell: "doorbell"
16784
+ };
16785
+ function buildDescriptor(capName, kind) {
16786
+ const t = EVENT_TAXONOMY[kind];
16787
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16788
+ return {
16789
+ ...t,
16790
+ icon: legacyIcon(t.iconId)
16791
+ };
16792
+ }
16793
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16337
16794
  var CameraPipelineConfigSchema = object({
16338
16795
  engine: PipelineEngineChoiceSchema.optional(),
16339
16796
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19543,13 +20000,29 @@ method(object({
19543
20000
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19544
20001
  kind: "mutation",
19545
20002
  auth: "admin"
19546
- }), method(object({ deviceId: number() }), object({
20003
+ }), method(object({
20004
+ deviceId: number(),
20005
+ reason: OpsLogReasonSchema.optional()
20006
+ }), object({
19547
20007
  floorMs: number().nullable(),
19548
20008
  deletedBuckets: number().int(),
19549
20009
  reclaimedBytes: number().int()
19550
20010
  }), {
19551
20011
  kind: "mutation",
19552
20012
  auth: "admin"
20013
+ }), method(object({
20014
+ deviceId: number(),
20015
+ fromMs: number().optional(),
20016
+ toMs: number().optional()
20017
+ }), object({
20018
+ deletedBuckets: number().int(),
20019
+ reclaimedBytes: number().int()
20020
+ }), {
20021
+ kind: "mutation",
20022
+ auth: "admin"
20023
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20024
+ kind: "query",
20025
+ auth: "admin"
19553
20026
  });
19554
20027
  /**
19555
20028
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22671,6 +23144,12 @@ Object.freeze({
22671
23144
  addonId: null,
22672
23145
  access: "delete"
22673
23146
  },
23147
+ "pipelineAnalytics.deleteDeviceEvents": {
23148
+ capName: "pipeline-analytics",
23149
+ capScope: "device",
23150
+ addonId: null,
23151
+ access: "delete"
23152
+ },
22674
23153
  "pipelineAnalytics.deleteTracks": {
22675
23154
  capName: "pipeline-analytics",
22676
23155
  capScope: "device",
@@ -22701,6 +23180,12 @@ Object.freeze({
22701
23180
  addonId: null,
22702
23181
  access: "view"
22703
23182
  },
23183
+ "pipelineAnalytics.getEventStoreFootprint": {
23184
+ capName: "pipeline-analytics",
23185
+ capScope: "device",
23186
+ addonId: null,
23187
+ access: "view"
23188
+ },
22704
23189
  "pipelineAnalytics.getKeyEvents": {
22705
23190
  capName: "pipeline-analytics",
22706
23191
  capScope: "device",
@@ -22743,6 +23228,12 @@ Object.freeze({
22743
23228
  addonId: null,
22744
23229
  access: "view"
22745
23230
  },
23231
+ "pipelineAnalytics.listOpsLog": {
23232
+ capName: "pipeline-analytics",
23233
+ capScope: "device",
23234
+ addonId: null,
23235
+ access: "view"
23236
+ },
22746
23237
  "pipelineAnalytics.listRecentTracks": {
22747
23238
  capName: "pipeline-analytics",
22748
23239
  capScope: "device",
@@ -22755,6 +23246,12 @@ Object.freeze({
22755
23246
  addonId: null,
22756
23247
  access: "view"
22757
23248
  },
23249
+ "pipelineAnalytics.pruneEvents": {
23250
+ capName: "pipeline-analytics",
23251
+ capScope: "device",
23252
+ addonId: null,
23253
+ access: "create"
23254
+ },
22758
23255
  "pipelineAnalytics.pruneEventsBefore": {
22759
23256
  capName: "pipeline-analytics",
22760
23257
  capScope: "device",
@@ -23517,6 +24014,12 @@ Object.freeze({
23517
24014
  addonId: null,
23518
24015
  access: "create"
23519
24016
  },
24017
+ "recording.deleteFootprint": {
24018
+ capName: "recording",
24019
+ capScope: "system",
24020
+ addonId: null,
24021
+ access: "delete"
24022
+ },
23520
24023
  "recording.getAvailability": {
23521
24024
  capName: "recording",
23522
24025
  capScope: "system",
@@ -23547,6 +24050,12 @@ Object.freeze({
23547
24050
  addonId: null,
23548
24051
  access: "view"
23549
24052
  },
24053
+ "recording.listOpsLog": {
24054
+ capName: "recording",
24055
+ capScope: "system",
24056
+ addonId: null,
24057
+ access: "view"
24058
+ },
23550
24059
  "recording.locateSegment": {
23551
24060
  capName: "recording",
23552
24061
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7334,6 +7334,62 @@ var RecordingConfigSchema = object({
7334
7334
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7335
7335
  });
7336
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
+ /**
7337
7393
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7338
7394
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7339
7395
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7607,6 +7663,160 @@ var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
7607
7663
  function nodePin(nodeId) {
7608
7664
  return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: nodeId } };
7609
7665
  }
7666
+ var COCO_TO_MACRO = {
7667
+ mapping: {
7668
+ person: "person",
7669
+ bicycle: "vehicle",
7670
+ car: "vehicle",
7671
+ motorcycle: "vehicle",
7672
+ airplane: "vehicle",
7673
+ bus: "vehicle",
7674
+ train: "vehicle",
7675
+ truck: "vehicle",
7676
+ boat: "vehicle",
7677
+ bird: "animal",
7678
+ cat: "animal",
7679
+ dog: "animal",
7680
+ horse: "animal",
7681
+ sheep: "animal",
7682
+ cow: "animal",
7683
+ elephant: "animal",
7684
+ bear: "animal",
7685
+ zebra: "animal",
7686
+ giraffe: "animal",
7687
+ suitcase: "package",
7688
+ backpack: "package",
7689
+ handbag: "package"
7690
+ },
7691
+ preserveOriginal: false
7692
+ };
7693
+ var AUDIO_MACRO_LABELS = [
7694
+ {
7695
+ id: "speech",
7696
+ name: "Speech",
7697
+ icon: "🗣️"
7698
+ },
7699
+ {
7700
+ id: "scream",
7701
+ name: "Scream / Shout",
7702
+ icon: "😱"
7703
+ },
7704
+ {
7705
+ id: "crying",
7706
+ name: "Crying / Baby",
7707
+ icon: "😢"
7708
+ },
7709
+ {
7710
+ id: "laughter",
7711
+ name: "Laughter",
7712
+ icon: "😂"
7713
+ },
7714
+ {
7715
+ id: "music",
7716
+ name: "Music",
7717
+ icon: "🎵"
7718
+ },
7719
+ {
7720
+ id: "dog",
7721
+ name: "Dog",
7722
+ icon: "🐕"
7723
+ },
7724
+ {
7725
+ id: "cat",
7726
+ name: "Cat",
7727
+ icon: "🐈"
7728
+ },
7729
+ {
7730
+ id: "bird",
7731
+ name: "Bird",
7732
+ icon: "🐦"
7733
+ },
7734
+ {
7735
+ id: "animal",
7736
+ name: "Animal (other)",
7737
+ icon: "🐾"
7738
+ },
7739
+ {
7740
+ id: "alarm",
7741
+ name: "Alarm / Siren",
7742
+ icon: "🚨"
7743
+ },
7744
+ {
7745
+ id: "doorbell",
7746
+ name: "Doorbell / Knock",
7747
+ icon: "🔔"
7748
+ },
7749
+ {
7750
+ id: "glass_breaking",
7751
+ name: "Glass Breaking",
7752
+ icon: "💥"
7753
+ },
7754
+ {
7755
+ id: "gunshot",
7756
+ name: "Gunshot / Explosion",
7757
+ icon: "💣"
7758
+ },
7759
+ {
7760
+ id: "vehicle",
7761
+ name: "Vehicle",
7762
+ icon: "🚗"
7763
+ },
7764
+ {
7765
+ id: "siren",
7766
+ name: "Emergency Siren",
7767
+ icon: "🚑"
7768
+ },
7769
+ {
7770
+ id: "fire",
7771
+ name: "Fire / Smoke",
7772
+ icon: "🔥"
7773
+ },
7774
+ {
7775
+ id: "water",
7776
+ name: "Water",
7777
+ icon: "💧"
7778
+ },
7779
+ {
7780
+ id: "wind",
7781
+ name: "Wind / Weather",
7782
+ icon: "🌬️"
7783
+ },
7784
+ {
7785
+ id: "door",
7786
+ name: "Door",
7787
+ icon: "🚪"
7788
+ },
7789
+ {
7790
+ id: "footsteps",
7791
+ name: "Footsteps",
7792
+ icon: "👣"
7793
+ },
7794
+ {
7795
+ id: "crowd",
7796
+ name: "Crowd / Chatter",
7797
+ icon: "👥"
7798
+ },
7799
+ {
7800
+ id: "telephone",
7801
+ name: "Telephone",
7802
+ icon: "📞"
7803
+ },
7804
+ {
7805
+ id: "engine",
7806
+ name: "Engine / Motor",
7807
+ icon: "⚙️"
7808
+ },
7809
+ {
7810
+ id: "tools",
7811
+ name: "Tools / Construction",
7812
+ icon: "🔨"
7813
+ },
7814
+ {
7815
+ id: "silence",
7816
+ name: "Silence",
7817
+ icon: "🤫"
7818
+ }
7819
+ ];
7610
7820
  var YAMNET_TO_MACRO = {
7611
7821
  mapping: {
7612
7822
  Speech: "speech",
@@ -7873,6 +8083,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7873
8083
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7874
8084
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7875
8085
  /**
8086
+ * Unified event-kind taxonomy — THE single source of truth for
8087
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8088
+ * icon }`.
8089
+ *
8090
+ * This dictionary folds together what used to be scattered across four
8091
+ * copies:
8092
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8093
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8094
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8095
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8096
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8097
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8098
+ *
8099
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8100
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8101
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8102
+ * color or an icon: they read this dictionary (server descriptors carry the
8103
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8104
+ *
8105
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8106
+ */
8107
+ var TAXONOMY_COLORS = {
8108
+ motion: "#f59e0b",
8109
+ audio: "#06b6d4",
8110
+ person: "#22c55e",
8111
+ vehicle: "#3b82f6",
8112
+ animal: "#f97316",
8113
+ package: "#a855f7",
8114
+ sensor: "#8b5cf6",
8115
+ control: "#10b981",
8116
+ genericDetection: "#64748b"
8117
+ };
8118
+ var DETECTION_SUB_COLORS = {
8119
+ car: "#f59e0b",
8120
+ truck: "#d97706",
8121
+ bus: "#b45309",
8122
+ motorcycle: "#eab308",
8123
+ bicycle: "#ca8a04",
8124
+ airplane: "#60a5fa",
8125
+ boat: "#2563eb",
8126
+ train: "#1d4ed8",
8127
+ bird: "#14b8a6",
8128
+ dog: "#84cc16",
8129
+ cat: "#f97316",
8130
+ horse: "#a16207",
8131
+ sheep: "#a3a3a3",
8132
+ cow: "#78716c",
8133
+ elephant: "#6b7280",
8134
+ bear: "#7c2d12",
8135
+ zebra: "#404040",
8136
+ giraffe: "#d4a373"
8137
+ };
8138
+ function titleCase(id) {
8139
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8140
+ }
8141
+ var entries = /* @__PURE__ */ new Map();
8142
+ function macro(kind, category, color, iconId, label) {
8143
+ entries.set(kind, {
8144
+ kind,
8145
+ parentKind: null,
8146
+ level: "macro",
8147
+ category,
8148
+ color,
8149
+ iconId,
8150
+ labelKey: `eventKind.${kind}`,
8151
+ label
8152
+ });
8153
+ }
8154
+ function sub(kind, parentKind, category, color, iconId, label) {
8155
+ entries.set(kind, {
8156
+ kind,
8157
+ parentKind,
8158
+ level: "sub",
8159
+ category,
8160
+ color,
8161
+ iconId,
8162
+ labelKey: `eventKind.${kind}`,
8163
+ label
8164
+ });
8165
+ }
8166
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8167
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8168
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8169
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8170
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8171
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8172
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8173
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8174
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8175
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8176
+ if (entries.has(cocoClass)) continue;
8177
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8178
+ }
8179
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8180
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8181
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8182
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8183
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8184
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8185
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8186
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8187
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8188
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8189
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8190
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8191
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8192
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8193
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8194
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8195
+ sub("button", "control", "control", "#10b981", "button", "Button");
8196
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8197
+ for (const l of AUDIO_MACRO_LABELS) {
8198
+ const kind = `audio-${l.id}`;
8199
+ if (entries.has(kind)) continue;
8200
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8201
+ }
8202
+ /** The complete taxonomy dictionary, keyed by kind. */
8203
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8204
+ /**
7876
8205
  * Error types for the safe expression engine. Two distinct classes so callers
7877
8206
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7878
8207
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15867,17 +16196,30 @@ var EventKindCategorySchema = _enum([
15867
16196
  "audio",
15868
16197
  "detection",
15869
16198
  "sensor",
16199
+ "control",
15870
16200
  "custom",
15871
16201
  "package"
15872
16202
  ]);
16203
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16204
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15873
16205
  var EventKindDescriptorSchema = object({
15874
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16206
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15875
16207
  kind: string(),
16208
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16209
+ labelKey: string(),
16210
+ /** English fallback label (kept for clients that don't translate). */
15876
16211
  label: string(),
15877
16212
  /** Hex color for timeline/legend rendering. */
15878
16213
  color: string(),
16214
+ /** Dictionary id → lucide component on the UI side. */
16215
+ iconId: string(),
16216
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15879
16217
  icon: EventKindIconSchema,
15880
16218
  category: EventKindCategorySchema,
16219
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16220
+ parentKind: string().nullable(),
16221
+ /** Derived from `parentKind`, explicit for the client tree. */
16222
+ level: EventKindLevelSchema,
15881
16223
  /** Which cap + device contributes this kind. For built-ins the camera
15882
16224
  * itself; for sensor kinds the LINKED source device. */
15883
16225
  source: object({
@@ -15950,11 +16292,21 @@ var TrackAudioLabelSchema = object({
15950
16292
  firstAt: number(),
15951
16293
  lastAt: number()
15952
16294
  });
16295
+ /**
16296
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16297
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16298
+ * linked sensor/control state change (no positions; carries a snapshot). The
16299
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16300
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16301
+ */
16302
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15953
16303
  var TrackSchema = object({
15954
16304
  trackId: string(),
15955
16305
  deviceId: number(),
15956
16306
  className: string(),
15957
16307
  label: string().optional(),
16308
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16309
+ source: TrackSourceSchema.optional(),
15958
16310
  firstSeen: number(),
15959
16311
  lastSeen: number(),
15960
16312
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16209,6 +16561,26 @@ var TrackCascadeCountsSchema = object({
16209
16561
  /** Per-track CLIP search vectors removed (best-effort). */
16210
16562
  embeddings: number().int()
16211
16563
  });
16564
+ /** Event-store footprint for one camera. */
16565
+ var EventStoreDeviceFootprintSchema = object({
16566
+ deviceId: number(),
16567
+ /** Persisted event rows (motion + object + audio) for the camera. */
16568
+ rows: number().int(),
16569
+ /** Event-owned media bytes on disk for the camera. */
16570
+ bytes: number().int()
16571
+ });
16572
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16573
+ var EventStoreFootprintSchema = object({
16574
+ totalRows: number().int(),
16575
+ totalBytes: number().int(),
16576
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16577
+ });
16578
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16579
+ var EventPruneCountsSchema = object({
16580
+ motion: number().int(),
16581
+ object: number().int(),
16582
+ audio: number().int()
16583
+ });
16212
16584
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16213
16585
  deviceId: number(),
16214
16586
  trackId: string()
@@ -16272,6 +16644,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16272
16644
  }), {
16273
16645
  kind: "mutation",
16274
16646
  auth: "admin"
16647
+ }), method(object({}), EventStoreFootprintSchema, {
16648
+ kind: "query",
16649
+ auth: "admin"
16650
+ }), method(object({
16651
+ olderThanMs: number(),
16652
+ reason: OpsLogReasonSchema.optional()
16653
+ }), EventPruneCountsSchema, {
16654
+ kind: "mutation",
16655
+ auth: "admin"
16656
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16657
+ kind: "mutation",
16658
+ auth: "admin"
16659
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16660
+ kind: "query",
16661
+ auth: "admin"
16275
16662
  }), method(object({
16276
16663
  eventId: string(),
16277
16664
  kind: MediaFileKindEnum.optional()
@@ -16296,6 +16683,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16296
16683
  eventId: string(),
16297
16684
  timestamp: number()
16298
16685
  });
16686
+ /**
16687
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16688
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16689
+ * caps into per-camera event-kind descriptors.
16690
+ *
16691
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16692
+ * is NOT duplicated here — every entry is derived from the single
16693
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16694
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16695
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16696
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16697
+ * eventful cap is missing.
16698
+ */
16699
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16700
+ var LEGACY_ICON = {
16701
+ motion: "motion",
16702
+ audio: "audio",
16703
+ person: "person",
16704
+ vehicle: "vehicle",
16705
+ animal: "animal",
16706
+ package: "package",
16707
+ door: "door",
16708
+ pir: "pir",
16709
+ smoke: "smoke",
16710
+ water: "water",
16711
+ button: "button",
16712
+ generic: "generic",
16713
+ gas: "smoke",
16714
+ vibration: "generic",
16715
+ tamper: "generic",
16716
+ presence: "person",
16717
+ lock: "generic",
16718
+ siren: "generic",
16719
+ switch: "generic",
16720
+ doorbell: "button"
16721
+ };
16722
+ function legacyIcon(iconId) {
16723
+ return LEGACY_ICON[iconId] ?? "generic";
16724
+ }
16725
+ /**
16726
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16727
+ * The anti-drift guard cross-checks this against the eventful caps declared
16728
+ * in `packages/types/src/capabilities/*.cap.ts`.
16729
+ */
16730
+ var CAP_TO_KIND = {
16731
+ contact: "contact",
16732
+ motion: "motion-sensor",
16733
+ smoke: "smoke",
16734
+ flood: "flood",
16735
+ gas: "gas",
16736
+ "carbon-monoxide": "carbon-monoxide",
16737
+ vibration: "vibration",
16738
+ tamper: "tamper",
16739
+ presence: "presence",
16740
+ "enum-sensor": "enum-sensor",
16741
+ "event-emitter": "device-event",
16742
+ "lock-control": "lock",
16743
+ switch: "switch",
16744
+ button: "button",
16745
+ doorbell: "doorbell"
16746
+ };
16747
+ function buildDescriptor(capName, kind) {
16748
+ const t = EVENT_TAXONOMY[kind];
16749
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16750
+ return {
16751
+ ...t,
16752
+ icon: legacyIcon(t.iconId)
16753
+ };
16754
+ }
16755
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16299
16756
  var CameraPipelineConfigSchema = object({
16300
16757
  engine: PipelineEngineChoiceSchema.optional(),
16301
16758
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19505,13 +19962,29 @@ method(object({
19505
19962
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19506
19963
  kind: "mutation",
19507
19964
  auth: "admin"
19508
- }), method(object({ deviceId: number() }), object({
19965
+ }), method(object({
19966
+ deviceId: number(),
19967
+ reason: OpsLogReasonSchema.optional()
19968
+ }), object({
19509
19969
  floorMs: number().nullable(),
19510
19970
  deletedBuckets: number().int(),
19511
19971
  reclaimedBytes: number().int()
19512
19972
  }), {
19513
19973
  kind: "mutation",
19514
19974
  auth: "admin"
19975
+ }), method(object({
19976
+ deviceId: number(),
19977
+ fromMs: number().optional(),
19978
+ toMs: number().optional()
19979
+ }), object({
19980
+ deletedBuckets: number().int(),
19981
+ reclaimedBytes: number().int()
19982
+ }), {
19983
+ kind: "mutation",
19984
+ auth: "admin"
19985
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19986
+ kind: "query",
19987
+ auth: "admin"
19515
19988
  });
19516
19989
  /**
19517
19990
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22633,6 +23106,12 @@ Object.freeze({
22633
23106
  addonId: null,
22634
23107
  access: "delete"
22635
23108
  },
23109
+ "pipelineAnalytics.deleteDeviceEvents": {
23110
+ capName: "pipeline-analytics",
23111
+ capScope: "device",
23112
+ addonId: null,
23113
+ access: "delete"
23114
+ },
22636
23115
  "pipelineAnalytics.deleteTracks": {
22637
23116
  capName: "pipeline-analytics",
22638
23117
  capScope: "device",
@@ -22663,6 +23142,12 @@ Object.freeze({
22663
23142
  addonId: null,
22664
23143
  access: "view"
22665
23144
  },
23145
+ "pipelineAnalytics.getEventStoreFootprint": {
23146
+ capName: "pipeline-analytics",
23147
+ capScope: "device",
23148
+ addonId: null,
23149
+ access: "view"
23150
+ },
22666
23151
  "pipelineAnalytics.getKeyEvents": {
22667
23152
  capName: "pipeline-analytics",
22668
23153
  capScope: "device",
@@ -22705,6 +23190,12 @@ Object.freeze({
22705
23190
  addonId: null,
22706
23191
  access: "view"
22707
23192
  },
23193
+ "pipelineAnalytics.listOpsLog": {
23194
+ capName: "pipeline-analytics",
23195
+ capScope: "device",
23196
+ addonId: null,
23197
+ access: "view"
23198
+ },
22708
23199
  "pipelineAnalytics.listRecentTracks": {
22709
23200
  capName: "pipeline-analytics",
22710
23201
  capScope: "device",
@@ -22717,6 +23208,12 @@ Object.freeze({
22717
23208
  addonId: null,
22718
23209
  access: "view"
22719
23210
  },
23211
+ "pipelineAnalytics.pruneEvents": {
23212
+ capName: "pipeline-analytics",
23213
+ capScope: "device",
23214
+ addonId: null,
23215
+ access: "create"
23216
+ },
22720
23217
  "pipelineAnalytics.pruneEventsBefore": {
22721
23218
  capName: "pipeline-analytics",
22722
23219
  capScope: "device",
@@ -23479,6 +23976,12 @@ Object.freeze({
23479
23976
  addonId: null,
23480
23977
  access: "create"
23481
23978
  },
23979
+ "recording.deleteFootprint": {
23980
+ capName: "recording",
23981
+ capScope: "system",
23982
+ addonId: null,
23983
+ access: "delete"
23984
+ },
23482
23985
  "recording.getAvailability": {
23483
23986
  capName: "recording",
23484
23987
  capScope: "system",
@@ -23509,6 +24012,12 @@ Object.freeze({
23509
24012
  addonId: null,
23510
24013
  access: "view"
23511
24014
  },
24015
+ "recording.listOpsLog": {
24016
+ capName: "recording",
24017
+ capScope: "system",
24018
+ addonId: null,
24019
+ access: "view"
24020
+ },
23512
24021
  "recording.locateSegment": {
23513
24022
  capName: "recording",
23514
24023
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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",