@camstack/addon-notifiers 1.1.25 → 1.1.27

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.
Files changed (3) hide show
  1. package/dist/addon.js +202 -13
  2. package/dist/addon.mjs +202 -13
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  let node_zlib = require("node:zlib");
6
- //#region ../types/dist/event-category-CFZs3jI4.mjs
6
+ //#region ../types/dist/event-category-H4AVePnn.mjs
7
7
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
8
8
  EventCategory["SystemBoot"] = "system.boot";
9
9
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -153,6 +153,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
153
153
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
154
154
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
155
155
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
156
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
157
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
158
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
156
159
  EventCategory["DetectionEvent"] = "detection.event";
157
160
  EventCategory["SessionTrackNew"] = "session.track.new";
158
161
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7339,6 +7342,24 @@ var RecordingRetentionSchema = object({
7339
7342
  maxSizeGb: number().min(0).optional()
7340
7343
  });
7341
7344
  /**
7345
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7346
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7347
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7348
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7349
+ *
7350
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7351
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7352
+ * preset changed over time renders every historical window at the dims it was
7353
+ * written with.
7354
+ */
7355
+ var ScrubThumbnailPresetSchema = _enum([
7356
+ "minimal",
7357
+ "low",
7358
+ "standard",
7359
+ "high",
7360
+ "max"
7361
+ ]);
7362
+ /**
7342
7363
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7343
7364
  *
7344
7365
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7375,7 +7396,14 @@ var RecordingConfigSchema = object({
7375
7396
  * derived into bands once via `migrateConfigToBands`.
7376
7397
  */
7377
7398
  bands: array(RecordingBandSchema).optional(),
7378
- retention: RecordingRetentionSchema.optional()
7399
+ retention: RecordingRetentionSchema.optional(),
7400
+ /**
7401
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7402
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7403
+ * windows only — existing sheets are immutable, and each window's index
7404
+ * carries its own tile dims so mixed-preset history renders correctly.
7405
+ */
7406
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7379
7407
  });
7380
7408
  /**
7381
7409
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -10127,7 +10155,7 @@ object({
10127
10155
  deviceId: number(),
10128
10156
  timestamp: number()
10129
10157
  });
10130
- DeviceType.Button;
10158
+ DeviceType.Button, DeviceType.Camera;
10131
10159
  /**
10132
10160
  * Enum-state sensor — a string value picked from a finite option set.
10133
10161
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -14516,6 +14544,14 @@ var ConfigEntrySchema = object({
14516
14544
  value: unknown(),
14517
14545
  description: string().optional()
14518
14546
  });
14547
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
14548
+ /** One resolved linked device — the compact projection consumers need. */
14549
+ var LinkedDeviceSchema = object({
14550
+ deviceId: number(),
14551
+ name: string(),
14552
+ location: string().nullable(),
14553
+ features: array(string())
14554
+ });
14519
14555
  var SavedDeviceRowSchema = object({
14520
14556
  /** Numeric id reserved at allocateDeviceId time. */
14521
14557
  id: number(),
@@ -14737,7 +14773,10 @@ method(object({
14737
14773
  }), _void(), {
14738
14774
  kind: "mutation",
14739
14775
  auth: "admin"
14740
- }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
14776
+ }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
14777
+ mode: DeviceLinkModeSchema,
14778
+ devices: array(LinkedDeviceSchema)
14779
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
14741
14780
  deviceId: number(),
14742
14781
  values: record(string(), unknown())
14743
14782
  }), object({ success: literal(true) }), {
@@ -16020,6 +16059,61 @@ var EventKindSchema = _enum([
16020
16059
  "object",
16021
16060
  "audio"
16022
16061
  ]);
16062
+ /**
16063
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
16064
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
16065
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
16066
+ */
16067
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
16068
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
16069
+ var EventKindIconSchema = _enum([
16070
+ "motion",
16071
+ "audio",
16072
+ "person",
16073
+ "vehicle",
16074
+ "animal",
16075
+ "door",
16076
+ "pir",
16077
+ "smoke",
16078
+ "water",
16079
+ "button",
16080
+ "generic"
16081
+ ]);
16082
+ var EventKindCategorySchema = _enum([
16083
+ "motion",
16084
+ "audio",
16085
+ "detection",
16086
+ "sensor",
16087
+ "custom"
16088
+ ]);
16089
+ var EventKindDescriptorSchema = object({
16090
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16091
+ kind: string(),
16092
+ label: string(),
16093
+ /** Hex color for timeline/legend rendering. */
16094
+ color: string(),
16095
+ icon: EventKindIconSchema,
16096
+ category: EventKindCategorySchema,
16097
+ /** Which cap + device contributes this kind. For built-ins the camera
16098
+ * itself; for sensor kinds the LINKED source device. */
16099
+ source: object({
16100
+ capName: string(),
16101
+ deviceId: number()
16102
+ })
16103
+ });
16104
+ var SensorEventSchema = object({
16105
+ id: string(),
16106
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
16107
+ * yields N rows, one per camera). */
16108
+ deviceId: number(),
16109
+ /** The linked sensor device whose state changed. */
16110
+ sourceDeviceId: number(),
16111
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
16112
+ kind: string(),
16113
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
16114
+ value: record(string(), unknown()).nullable(),
16115
+ timestamp: number()
16116
+ });
16023
16117
  var TrackPositionSchema = object({
16024
16118
  x: number(),
16025
16119
  y: number(),
@@ -16033,6 +16127,30 @@ var TrackSnapshotSchema = object({
16033
16127
  mediaKey: string()
16034
16128
  });
16035
16129
  /**
16130
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
16131
+ * divided by the track's detection-frame dims), computed at persist time.
16132
+ * Absent when the frame dims were unknown when the track was persisted
16133
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
16134
+ */
16135
+ var TrackEnvelopeSchema = object({
16136
+ minX: number(),
16137
+ minY: number(),
16138
+ maxX: number(),
16139
+ maxY: number()
16140
+ });
16141
+ /**
16142
+ * Row projection for track list queries. `full` (default) returns the
16143
+ * complete Track including the frame-rate `positions[]` history and the
16144
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
16145
+ * keeps every scalar the list surfaces actually render (ids, class(es),
16146
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16147
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
16148
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16149
+ * `getTrack`. Mirrors the event-store `projection` convention
16150
+ * (`getObjectEvents` et al.).
16151
+ */
16152
+ var TrackProjectionSchema = _enum(["full", "slim"]);
16153
+ /**
16036
16154
  * One audio-classification label heard on the track's camera while the
16037
16155
  * track was alive, aggregated per label. An "episode" is one persisted
16038
16156
  * audio event (the confident-classification path: score ≥ the device's
@@ -16083,7 +16201,11 @@ var TrackSchema = object({
16083
16201
  /** Audio-classification labels heard on the camera during the track's
16084
16202
  * life (score ≥ device `classificationMinScore`), aggregated per label.
16085
16203
  * Absent on legacy rows / tracks with no confident audio. */
16086
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
16204
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
16205
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
16206
+ * Populated from the persisted envelope columns on historical reads;
16207
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
16208
+ envelope: TrackEnvelopeSchema.optional()
16087
16209
  });
16088
16210
  var BaseEventFields = {
16089
16211
  id: string(),
@@ -16193,11 +16315,13 @@ var MediaFileSchema = object({
16193
16315
  sizeBytes: number(),
16194
16316
  timestamp: number()
16195
16317
  });
16318
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
16319
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
16196
16320
  var DeviceEventQueryInput = object({
16197
16321
  deviceId: number(),
16198
16322
  since: number().optional(),
16199
16323
  until: number().optional(),
16200
- limit: number().int().min(1).max(5e3).default(1e3),
16324
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
16201
16325
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
16202
16326
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
16203
16327
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -16205,6 +16329,27 @@ var DeviceEventQueryInput = object({
16205
16329
  projection: _enum(["full", "slim"]).optional()
16206
16330
  });
16207
16331
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
16332
+ var RecentTracksQueryInput = object({
16333
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
16334
+ deviceIds: array(number()),
16335
+ /** Window lower bound on `lastSeen` (inclusive). */
16336
+ since: number().optional(),
16337
+ /** Window upper bound on `lastSeen` (inclusive). */
16338
+ until: number().optional(),
16339
+ /** Page size. Default 200, max 1000. */
16340
+ limit: number().int().min(1).max(1e3).default(200),
16341
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
16342
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
16343
+ cursor: string().optional(),
16344
+ /** See {@link TrackProjectionSchema}. Default `full`. */
16345
+ projection: TrackProjectionSchema.optional()
16346
+ });
16347
+ var RecentTracksPageSchema = object({
16348
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
16349
+ tracks: array(TrackSchema).readonly(),
16350
+ /** Cursor for the next page, or null when this page is the last. */
16351
+ nextCursor: string().nullable()
16352
+ });
16208
16353
  var KeyEventQueryInput = object({
16209
16354
  deviceId: number(),
16210
16355
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16287,11 +16432,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16287
16432
  deviceId: number(),
16288
16433
  since: number().optional(),
16289
16434
  until: number().optional(),
16290
- limit: number().optional()
16291
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
16292
- kind: "mutation",
16293
- auth: "admin"
16294
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16435
+ limit: number().optional(),
16436
+ /** Spatial filter only tracks whose trajectory intersects the zone
16437
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
16438
+ * envelope columns, then precisely tested per position. Tracks with
16439
+ * an unknown envelope (no frame dims at persist time) always match. */
16440
+ zone: TrackZoneFilterSchema.optional(),
16441
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
16442
+ * compatible — omitting the field keeps today's exact behaviour). */
16443
+ projection: TrackProjectionSchema.optional()
16444
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
16445
+ kind: "mutation",
16446
+ auth: "admin"
16447
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
16448
+ deviceId: number(),
16449
+ since: number().optional(),
16450
+ until: number().optional(),
16451
+ kinds: array(string()).optional(),
16452
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
16453
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16295
16454
  deviceId: number(),
16296
16455
  since: number(),
16297
16456
  until: number(),
@@ -19492,7 +19651,12 @@ var RecordingStorageUsageSchema = object({
19492
19651
  /**
19493
19652
  * Result of locating footage at a wall-clock instant for one device/profile.
19494
19653
  * `segment` carries the covering segment's window; `gap` reports the forward
19495
- * nearest covered edge (`null` past the end of footage / when none exists).
19654
+ * nearest covered edge (`null` past the end of footage / when none exists)
19655
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
19656
+ * footage behind the epoch; `null` when none — optional so older providers
19657
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
19658
+ * small inter-segment cracks (durMs under-covers the span to the next
19659
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
19496
19660
  */
19497
19661
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19498
19662
  kind: literal("segment"),
@@ -19501,7 +19665,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19501
19665
  bytes: number()
19502
19666
  }), object({
19503
19667
  kind: literal("gap"),
19504
- nearestEdgeMs: number().nullable()
19668
+ nearestEdgeMs: number().nullable(),
19669
+ prevEndMs: number().nullable().optional()
19505
19670
  })]);
19506
19671
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
19507
19672
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -21070,6 +21235,12 @@ Object.freeze({
21070
21235
  addonId: null,
21071
21236
  access: "view"
21072
21237
  },
21238
+ "deviceManager.getLinkedDevices": {
21239
+ capName: "device-manager",
21240
+ capScope: "system",
21241
+ addonId: null,
21242
+ access: "view"
21243
+ },
21073
21244
  "deviceManager.getRoleDisplayDefaults": {
21074
21245
  capName: "device-manager",
21075
21246
  capScope: "system",
@@ -22624,6 +22795,12 @@ Object.freeze({
22624
22795
  addonId: null,
22625
22796
  access: "view"
22626
22797
  },
22798
+ "pipelineAnalytics.getSensorEvents": {
22799
+ capName: "pipeline-analytics",
22800
+ capScope: "device",
22801
+ addonId: null,
22802
+ access: "view"
22803
+ },
22627
22804
  "pipelineAnalytics.getTrack": {
22628
22805
  capName: "pipeline-analytics",
22629
22806
  capScope: "device",
@@ -22636,6 +22813,18 @@ Object.freeze({
22636
22813
  addonId: null,
22637
22814
  access: "view"
22638
22815
  },
22816
+ "pipelineAnalytics.listEventKinds": {
22817
+ capName: "pipeline-analytics",
22818
+ capScope: "device",
22819
+ addonId: null,
22820
+ access: "view"
22821
+ },
22822
+ "pipelineAnalytics.listRecentTracks": {
22823
+ capName: "pipeline-analytics",
22824
+ capScope: "device",
22825
+ addonId: null,
22826
+ access: "view"
22827
+ },
22639
22828
  "pipelineAnalytics.listTracks": {
22640
22829
  capName: "pipeline-analytics",
22641
22830
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
2
- //#region ../types/dist/event-category-CFZs3jI4.mjs
2
+ //#region ../types/dist/event-category-H4AVePnn.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -149,6 +149,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
149
149
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
150
150
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
151
151
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
152
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
153
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
154
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
152
155
  EventCategory["DetectionEvent"] = "detection.event";
153
156
  EventCategory["SessionTrackNew"] = "session.track.new";
154
157
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7335,6 +7338,24 @@ var RecordingRetentionSchema = object({
7335
7338
  maxSizeGb: number().min(0).optional()
7336
7339
  });
7337
7340
  /**
7341
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7342
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7343
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7344
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7345
+ *
7346
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7347
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7348
+ * preset changed over time renders every historical window at the dims it was
7349
+ * written with.
7350
+ */
7351
+ var ScrubThumbnailPresetSchema = _enum([
7352
+ "minimal",
7353
+ "low",
7354
+ "standard",
7355
+ "high",
7356
+ "max"
7357
+ ]);
7358
+ /**
7338
7359
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7339
7360
  *
7340
7361
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7371,7 +7392,14 @@ var RecordingConfigSchema = object({
7371
7392
  * derived into bands once via `migrateConfigToBands`.
7372
7393
  */
7373
7394
  bands: array(RecordingBandSchema).optional(),
7374
- retention: RecordingRetentionSchema.optional()
7395
+ retention: RecordingRetentionSchema.optional(),
7396
+ /**
7397
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7398
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7399
+ * windows only — existing sheets are immutable, and each window's index
7400
+ * carries its own tile dims so mixed-preset history renders correctly.
7401
+ */
7402
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7375
7403
  });
7376
7404
  /**
7377
7405
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -10123,7 +10151,7 @@ object({
10123
10151
  deviceId: number(),
10124
10152
  timestamp: number()
10125
10153
  });
10126
- DeviceType.Button;
10154
+ DeviceType.Button, DeviceType.Camera;
10127
10155
  /**
10128
10156
  * Enum-state sensor — a string value picked from a finite option set.
10129
10157
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -14512,6 +14540,14 @@ var ConfigEntrySchema = object({
14512
14540
  value: unknown(),
14513
14541
  description: string().optional()
14514
14542
  });
14543
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
14544
+ /** One resolved linked device — the compact projection consumers need. */
14545
+ var LinkedDeviceSchema = object({
14546
+ deviceId: number(),
14547
+ name: string(),
14548
+ location: string().nullable(),
14549
+ features: array(string())
14550
+ });
14515
14551
  var SavedDeviceRowSchema = object({
14516
14552
  /** Numeric id reserved at allocateDeviceId time. */
14517
14553
  id: number(),
@@ -14733,7 +14769,10 @@ method(object({
14733
14769
  }), _void(), {
14734
14770
  kind: "mutation",
14735
14771
  auth: "admin"
14736
- }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
14772
+ }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
14773
+ mode: DeviceLinkModeSchema,
14774
+ devices: array(LinkedDeviceSchema)
14775
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
14737
14776
  deviceId: number(),
14738
14777
  values: record(string(), unknown())
14739
14778
  }), object({ success: literal(true) }), {
@@ -16016,6 +16055,61 @@ var EventKindSchema = _enum([
16016
16055
  "object",
16017
16056
  "audio"
16018
16057
  ]);
16058
+ /**
16059
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
16060
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
16061
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
16062
+ */
16063
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
16064
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
16065
+ var EventKindIconSchema = _enum([
16066
+ "motion",
16067
+ "audio",
16068
+ "person",
16069
+ "vehicle",
16070
+ "animal",
16071
+ "door",
16072
+ "pir",
16073
+ "smoke",
16074
+ "water",
16075
+ "button",
16076
+ "generic"
16077
+ ]);
16078
+ var EventKindCategorySchema = _enum([
16079
+ "motion",
16080
+ "audio",
16081
+ "detection",
16082
+ "sensor",
16083
+ "custom"
16084
+ ]);
16085
+ var EventKindDescriptorSchema = object({
16086
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16087
+ kind: string(),
16088
+ label: string(),
16089
+ /** Hex color for timeline/legend rendering. */
16090
+ color: string(),
16091
+ icon: EventKindIconSchema,
16092
+ category: EventKindCategorySchema,
16093
+ /** Which cap + device contributes this kind. For built-ins the camera
16094
+ * itself; for sensor kinds the LINKED source device. */
16095
+ source: object({
16096
+ capName: string(),
16097
+ deviceId: number()
16098
+ })
16099
+ });
16100
+ var SensorEventSchema = object({
16101
+ id: string(),
16102
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
16103
+ * yields N rows, one per camera). */
16104
+ deviceId: number(),
16105
+ /** The linked sensor device whose state changed. */
16106
+ sourceDeviceId: number(),
16107
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
16108
+ kind: string(),
16109
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
16110
+ value: record(string(), unknown()).nullable(),
16111
+ timestamp: number()
16112
+ });
16019
16113
  var TrackPositionSchema = object({
16020
16114
  x: number(),
16021
16115
  y: number(),
@@ -16029,6 +16123,30 @@ var TrackSnapshotSchema = object({
16029
16123
  mediaKey: string()
16030
16124
  });
16031
16125
  /**
16126
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
16127
+ * divided by the track's detection-frame dims), computed at persist time.
16128
+ * Absent when the frame dims were unknown when the track was persisted
16129
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
16130
+ */
16131
+ var TrackEnvelopeSchema = object({
16132
+ minX: number(),
16133
+ minY: number(),
16134
+ maxX: number(),
16135
+ maxY: number()
16136
+ });
16137
+ /**
16138
+ * Row projection for track list queries. `full` (default) returns the
16139
+ * complete Track including the frame-rate `positions[]` history and the
16140
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
16141
+ * keeps every scalar the list surfaces actually render (ids, class(es),
16142
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16143
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
16144
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16145
+ * `getTrack`. Mirrors the event-store `projection` convention
16146
+ * (`getObjectEvents` et al.).
16147
+ */
16148
+ var TrackProjectionSchema = _enum(["full", "slim"]);
16149
+ /**
16032
16150
  * One audio-classification label heard on the track's camera while the
16033
16151
  * track was alive, aggregated per label. An "episode" is one persisted
16034
16152
  * audio event (the confident-classification path: score ≥ the device's
@@ -16079,7 +16197,11 @@ var TrackSchema = object({
16079
16197
  /** Audio-classification labels heard on the camera during the track's
16080
16198
  * life (score ≥ device `classificationMinScore`), aggregated per label.
16081
16199
  * Absent on legacy rows / tracks with no confident audio. */
16082
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
16200
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
16201
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
16202
+ * Populated from the persisted envelope columns on historical reads;
16203
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
16204
+ envelope: TrackEnvelopeSchema.optional()
16083
16205
  });
16084
16206
  var BaseEventFields = {
16085
16207
  id: string(),
@@ -16189,11 +16311,13 @@ var MediaFileSchema = object({
16189
16311
  sizeBytes: number(),
16190
16312
  timestamp: number()
16191
16313
  });
16314
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
16315
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
16192
16316
  var DeviceEventQueryInput = object({
16193
16317
  deviceId: number(),
16194
16318
  since: number().optional(),
16195
16319
  until: number().optional(),
16196
- limit: number().int().min(1).max(5e3).default(1e3),
16320
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
16197
16321
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
16198
16322
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
16199
16323
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -16201,6 +16325,27 @@ var DeviceEventQueryInput = object({
16201
16325
  projection: _enum(["full", "slim"]).optional()
16202
16326
  });
16203
16327
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
16328
+ var RecentTracksQueryInput = object({
16329
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
16330
+ deviceIds: array(number()),
16331
+ /** Window lower bound on `lastSeen` (inclusive). */
16332
+ since: number().optional(),
16333
+ /** Window upper bound on `lastSeen` (inclusive). */
16334
+ until: number().optional(),
16335
+ /** Page size. Default 200, max 1000. */
16336
+ limit: number().int().min(1).max(1e3).default(200),
16337
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
16338
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
16339
+ cursor: string().optional(),
16340
+ /** See {@link TrackProjectionSchema}. Default `full`. */
16341
+ projection: TrackProjectionSchema.optional()
16342
+ });
16343
+ var RecentTracksPageSchema = object({
16344
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
16345
+ tracks: array(TrackSchema).readonly(),
16346
+ /** Cursor for the next page, or null when this page is the last. */
16347
+ nextCursor: string().nullable()
16348
+ });
16204
16349
  var KeyEventQueryInput = object({
16205
16350
  deviceId: number(),
16206
16351
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16283,11 +16428,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16283
16428
  deviceId: number(),
16284
16429
  since: number().optional(),
16285
16430
  until: number().optional(),
16286
- limit: number().optional()
16287
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
16288
- kind: "mutation",
16289
- auth: "admin"
16290
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16431
+ limit: number().optional(),
16432
+ /** Spatial filter only tracks whose trajectory intersects the zone
16433
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
16434
+ * envelope columns, then precisely tested per position. Tracks with
16435
+ * an unknown envelope (no frame dims at persist time) always match. */
16436
+ zone: TrackZoneFilterSchema.optional(),
16437
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
16438
+ * compatible — omitting the field keeps today's exact behaviour). */
16439
+ projection: TrackProjectionSchema.optional()
16440
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
16441
+ kind: "mutation",
16442
+ auth: "admin"
16443
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
16444
+ deviceId: number(),
16445
+ since: number().optional(),
16446
+ until: number().optional(),
16447
+ kinds: array(string()).optional(),
16448
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
16449
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16291
16450
  deviceId: number(),
16292
16451
  since: number(),
16293
16452
  until: number(),
@@ -19488,7 +19647,12 @@ var RecordingStorageUsageSchema = object({
19488
19647
  /**
19489
19648
  * Result of locating footage at a wall-clock instant for one device/profile.
19490
19649
  * `segment` carries the covering segment's window; `gap` reports the forward
19491
- * nearest covered edge (`null` past the end of footage / when none exists).
19650
+ * nearest covered edge (`null` past the end of footage / when none exists)
19651
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
19652
+ * footage behind the epoch; `null` when none — optional so older providers
19653
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
19654
+ * small inter-segment cracks (durMs under-covers the span to the next
19655
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
19492
19656
  */
19493
19657
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19494
19658
  kind: literal("segment"),
@@ -19497,7 +19661,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19497
19661
  bytes: number()
19498
19662
  }), object({
19499
19663
  kind: literal("gap"),
19500
- nearestEdgeMs: number().nullable()
19664
+ nearestEdgeMs: number().nullable(),
19665
+ prevEndMs: number().nullable().optional()
19501
19666
  })]);
19502
19667
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
19503
19668
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -21066,6 +21231,12 @@ Object.freeze({
21066
21231
  addonId: null,
21067
21232
  access: "view"
21068
21233
  },
21234
+ "deviceManager.getLinkedDevices": {
21235
+ capName: "device-manager",
21236
+ capScope: "system",
21237
+ addonId: null,
21238
+ access: "view"
21239
+ },
21069
21240
  "deviceManager.getRoleDisplayDefaults": {
21070
21241
  capName: "device-manager",
21071
21242
  capScope: "system",
@@ -22620,6 +22791,12 @@ Object.freeze({
22620
22791
  addonId: null,
22621
22792
  access: "view"
22622
22793
  },
22794
+ "pipelineAnalytics.getSensorEvents": {
22795
+ capName: "pipeline-analytics",
22796
+ capScope: "device",
22797
+ addonId: null,
22798
+ access: "view"
22799
+ },
22623
22800
  "pipelineAnalytics.getTrack": {
22624
22801
  capName: "pipeline-analytics",
22625
22802
  capScope: "device",
@@ -22632,6 +22809,18 @@ Object.freeze({
22632
22809
  addonId: null,
22633
22810
  access: "view"
22634
22811
  },
22812
+ "pipelineAnalytics.listEventKinds": {
22813
+ capName: "pipeline-analytics",
22814
+ capScope: "device",
22815
+ addonId: null,
22816
+ access: "view"
22817
+ },
22818
+ "pipelineAnalytics.listRecentTracks": {
22819
+ capName: "pipeline-analytics",
22820
+ capScope: "device",
22821
+ addonId: null,
22822
+ access: "view"
22823
+ },
22635
22824
  "pipelineAnalytics.listTracks": {
22636
22825
  capName: "pipeline-analytics",
22637
22826
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-notifiers",
3
- "version": "1.1.25",
3
+ "version": "1.1.27",
4
4
  "description": "System notifiers addon for CamStack — a `notification-output` collection provider hosting per-kind notifier adapters (ntfy, pushover, gotify, telegram, discord, webhook, zentik).",
5
5
  "keywords": [
6
6
  "camstack",