@camstack/addon-matter-broker 0.1.20 → 0.1.21

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 +194 -14
  2. package/dist/addon.mjs +194 -14
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -13,7 +13,7 @@ let node_stream_promises = require("node:stream/promises");
13
13
  let node_net = require("node:net");
14
14
  let node_dgram = require("node:dgram");
15
15
  node_dgram = require_esm.__toESM(node_dgram, 1);
16
- //#region ../types/dist/event-category-CFZs3jI4.mjs
16
+ //#region ../types/dist/event-category-H4AVePnn.mjs
17
17
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
18
18
  EventCategory["SystemBoot"] = "system.boot";
19
19
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -163,6 +163,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
163
163
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
164
164
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
165
165
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
166
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
167
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
168
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
166
169
  EventCategory["DetectionEvent"] = "detection.event";
167
170
  EventCategory["SessionTrackNew"] = "session.track.new";
168
171
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -10810,10 +10813,23 @@ var deviceDiscoveryCapability = {
10810
10813
  }
10811
10814
  };
10812
10815
  /**
10813
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10814
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10815
- * time the firmware pushes a ring; status tracks the last press and
10816
- * a pressCount since start (diagnostic).
10816
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10817
+ * name (same pattern as `snapshot`):
10818
+ *
10819
+ * - **Native** providers: registered per-device by device-driver
10820
+ * addons via `ctx.registerNativeCap` — either on a
10821
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10822
+ * or directly on the camera (Reolink registers at camera level).
10823
+ * Emits an `onPressed` event every time the firmware pushes a
10824
+ * ring; status tracks the last press and a pressCount since start
10825
+ * (diagnostic).
10826
+ *
10827
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10828
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10829
+ * device (contact / switch / event-emitter …) into a doorbell for
10830
+ * a camera. `defaultActive: false` — the operator explicitly binds
10831
+ * it per camera in the device-bindings UI, then picks the source
10832
+ * device + trigger in the per-device settings.
10817
10833
  *
10818
10834
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10819
10835
  * — subscribers listening at the camera level receive ring events
@@ -10834,7 +10850,10 @@ var doorbellCapability = {
10834
10850
  scope: "device",
10835
10851
  deviceNative: true,
10836
10852
  mode: "singleton",
10837
- deviceTypes: [DeviceType.Button],
10853
+ kind: "wrapper",
10854
+ defaultActive: false,
10855
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10856
+ exposesDeviceSettings: true,
10838
10857
  methods: {},
10839
10858
  events: {
10840
10859
  /**
@@ -17252,6 +17271,14 @@ var ConfigEntrySchema = object({
17252
17271
  value: unknown(),
17253
17272
  description: string$2().optional()
17254
17273
  });
17274
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17275
+ /** One resolved linked device — the compact projection consumers need. */
17276
+ var LinkedDeviceSchema = object({
17277
+ deviceId: number(),
17278
+ name: string$2(),
17279
+ location: string$2().nullable(),
17280
+ features: array(string$2())
17281
+ });
17255
17282
  var SavedDeviceRowSchema = object({
17256
17283
  /** Numeric id reserved at allocateDeviceId time. */
17257
17284
  id: number(),
@@ -17473,7 +17500,10 @@ method(object({
17473
17500
  }), _void(), {
17474
17501
  kind: "mutation",
17475
17502
  auth: "admin"
17476
- }), method(object({ addonId: string$2() }), array(SavedDeviceRowSchema)), method(object({ addonId: string$2().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({
17503
+ }), method(object({ addonId: string$2() }), array(SavedDeviceRowSchema)), method(object({ addonId: string$2().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
17504
+ mode: DeviceLinkModeSchema,
17505
+ devices: array(LinkedDeviceSchema)
17506
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17477
17507
  deviceId: number(),
17478
17508
  values: record(string$2(), unknown())
17479
17509
  }), object({ success: literal(true) }), {
@@ -18742,6 +18772,61 @@ var EventKindSchema = _enum([
18742
18772
  "object",
18743
18773
  "audio"
18744
18774
  ]);
18775
+ /**
18776
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18777
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18778
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18779
+ */
18780
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18781
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18782
+ var EventKindIconSchema = _enum([
18783
+ "motion",
18784
+ "audio",
18785
+ "person",
18786
+ "vehicle",
18787
+ "animal",
18788
+ "door",
18789
+ "pir",
18790
+ "smoke",
18791
+ "water",
18792
+ "button",
18793
+ "generic"
18794
+ ]);
18795
+ var EventKindCategorySchema = _enum([
18796
+ "motion",
18797
+ "audio",
18798
+ "detection",
18799
+ "sensor",
18800
+ "custom"
18801
+ ]);
18802
+ var EventKindDescriptorSchema = object({
18803
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18804
+ kind: string$2(),
18805
+ label: string$2(),
18806
+ /** Hex color for timeline/legend rendering. */
18807
+ color: string$2(),
18808
+ icon: EventKindIconSchema,
18809
+ category: EventKindCategorySchema,
18810
+ /** Which cap + device contributes this kind. For built-ins the camera
18811
+ * itself; for sensor kinds the LINKED source device. */
18812
+ source: object({
18813
+ capName: string$2(),
18814
+ deviceId: number()
18815
+ })
18816
+ });
18817
+ var SensorEventSchema = object({
18818
+ id: string$2(),
18819
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18820
+ * yields N rows, one per camera). */
18821
+ deviceId: number(),
18822
+ /** The linked sensor device whose state changed. */
18823
+ sourceDeviceId: number(),
18824
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18825
+ kind: string$2(),
18826
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18827
+ value: record(string$2(), unknown()).nullable(),
18828
+ timestamp: number()
18829
+ });
18745
18830
  var TrackPositionSchema = object({
18746
18831
  x: number(),
18747
18832
  y: number(),
@@ -18755,6 +18840,30 @@ var TrackSnapshotSchema = object({
18755
18840
  mediaKey: string$2()
18756
18841
  });
18757
18842
  /**
18843
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18844
+ * divided by the track's detection-frame dims), computed at persist time.
18845
+ * Absent when the frame dims were unknown when the track was persisted
18846
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18847
+ */
18848
+ var TrackEnvelopeSchema = object({
18849
+ minX: number(),
18850
+ minY: number(),
18851
+ maxX: number(),
18852
+ maxY: number()
18853
+ });
18854
+ /**
18855
+ * Row projection for track list queries. `full` (default) returns the
18856
+ * complete Track including the frame-rate `positions[]` history and the
18857
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18858
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18859
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18860
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18861
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18862
+ * `getTrack`. Mirrors the event-store `projection` convention
18863
+ * (`getObjectEvents` et al.).
18864
+ */
18865
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18866
+ /**
18758
18867
  * One audio-classification label heard on the track's camera while the
18759
18868
  * track was alive, aggregated per label. An "episode" is one persisted
18760
18869
  * audio event (the confident-classification path: score ≥ the device's
@@ -18805,7 +18914,11 @@ var TrackSchema = object({
18805
18914
  /** Audio-classification labels heard on the camera during the track's
18806
18915
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18807
18916
  * Absent on legacy rows / tracks with no confident audio. */
18808
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18917
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18918
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18919
+ * Populated from the persisted envelope columns on historical reads;
18920
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18921
+ envelope: TrackEnvelopeSchema.optional()
18809
18922
  });
18810
18923
  var BaseEventFields = {
18811
18924
  id: string$2(),
@@ -18915,11 +19028,13 @@ var MediaFileSchema = object({
18915
19028
  sizeBytes: number(),
18916
19029
  timestamp: number()
18917
19030
  });
19031
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19032
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18918
19033
  var DeviceEventQueryInput = object({
18919
19034
  deviceId: number(),
18920
19035
  since: number().optional(),
18921
19036
  until: number().optional(),
18922
- limit: number().int().min(1).max(5e3).default(1e3),
19037
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18923
19038
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18924
19039
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18925
19040
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18927,6 +19042,27 @@ var DeviceEventQueryInput = object({
18927
19042
  projection: _enum(["full", "slim"]).optional()
18928
19043
  });
18929
19044
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string$2().optional() });
19045
+ var RecentTracksQueryInput = object({
19046
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19047
+ deviceIds: array(number()),
19048
+ /** Window lower bound on `lastSeen` (inclusive). */
19049
+ since: number().optional(),
19050
+ /** Window upper bound on `lastSeen` (inclusive). */
19051
+ until: number().optional(),
19052
+ /** Page size. Default 200, max 1000. */
19053
+ limit: number().int().min(1).max(1e3).default(200),
19054
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19055
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19056
+ cursor: string$2().optional(),
19057
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19058
+ projection: TrackProjectionSchema.optional()
19059
+ });
19060
+ var RecentTracksPageSchema = object({
19061
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19062
+ tracks: array(TrackSchema).readonly(),
19063
+ /** Cursor for the next page, or null when this page is the last. */
19064
+ nextCursor: string$2().nullable()
19065
+ });
18930
19066
  var KeyEventQueryInput = object({
18931
19067
  deviceId: number(),
18932
19068
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19009,11 +19145,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19009
19145
  deviceId: number(),
19010
19146
  since: number().optional(),
19011
19147
  until: number().optional(),
19012
- limit: number().optional()
19013
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19148
+ limit: number().optional(),
19149
+ /** Spatial filter only tracks whose trajectory intersects the zone
19150
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19151
+ * envelope columns, then precisely tested per position. Tracks with
19152
+ * an unknown envelope (no frame dims at persist time) always match. */
19153
+ zone: TrackZoneFilterSchema.optional(),
19154
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19155
+ * compatible — omitting the field keeps today's exact behaviour). */
19156
+ projection: TrackProjectionSchema.optional()
19157
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19014
19158
  kind: "mutation",
19015
19159
  auth: "admin"
19016
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19160
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19161
+ deviceId: number(),
19162
+ since: number().optional(),
19163
+ until: number().optional(),
19164
+ kinds: array(string$2()).optional(),
19165
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19166
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19017
19167
  deviceId: number(),
19018
19168
  since: number(),
19019
19169
  until: number(),
@@ -22231,7 +22381,12 @@ var RecordingStorageUsageSchema = object({
22231
22381
  /**
22232
22382
  * Result of locating footage at a wall-clock instant for one device/profile.
22233
22383
  * `segment` carries the covering segment's window; `gap` reports the forward
22234
- * nearest covered edge (`null` past the end of footage / when none exists).
22384
+ * nearest covered edge (`null` past the end of footage / when none exists)
22385
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22386
+ * footage behind the epoch; `null` when none — optional so older providers
22387
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22388
+ * small inter-segment cracks (durMs under-covers the span to the next
22389
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22235
22390
  */
22236
22391
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22237
22392
  kind: literal("segment"),
@@ -22240,7 +22395,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22240
22395
  bytes: number()
22241
22396
  }), object({
22242
22397
  kind: literal("gap"),
22243
- nearestEdgeMs: number().nullable()
22398
+ nearestEdgeMs: number().nullable(),
22399
+ prevEndMs: number().nullable().optional()
22244
22400
  })]);
22245
22401
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22246
22402
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23809,6 +23965,12 @@ Object.freeze({
23809
23965
  addonId: null,
23810
23966
  access: "view"
23811
23967
  },
23968
+ "deviceManager.getLinkedDevices": {
23969
+ capName: "device-manager",
23970
+ capScope: "system",
23971
+ addonId: null,
23972
+ access: "view"
23973
+ },
23812
23974
  "deviceManager.getRoleDisplayDefaults": {
23813
23975
  capName: "device-manager",
23814
23976
  capScope: "system",
@@ -25363,6 +25525,12 @@ Object.freeze({
25363
25525
  addonId: null,
25364
25526
  access: "view"
25365
25527
  },
25528
+ "pipelineAnalytics.getSensorEvents": {
25529
+ capName: "pipeline-analytics",
25530
+ capScope: "device",
25531
+ addonId: null,
25532
+ access: "view"
25533
+ },
25366
25534
  "pipelineAnalytics.getTrack": {
25367
25535
  capName: "pipeline-analytics",
25368
25536
  capScope: "device",
@@ -25375,6 +25543,18 @@ Object.freeze({
25375
25543
  addonId: null,
25376
25544
  access: "view"
25377
25545
  },
25546
+ "pipelineAnalytics.listEventKinds": {
25547
+ capName: "pipeline-analytics",
25548
+ capScope: "device",
25549
+ addonId: null,
25550
+ access: "view"
25551
+ },
25552
+ "pipelineAnalytics.listRecentTracks": {
25553
+ capName: "pipeline-analytics",
25554
+ capScope: "device",
25555
+ addonId: null,
25556
+ access: "view"
25557
+ },
25378
25558
  "pipelineAnalytics.listTracks": {
25379
25559
  capName: "pipeline-analytics",
25380
25560
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -11,7 +11,7 @@ import { networkInterfaces, tmpdir, uptime } from "node:os";
11
11
  import { finished } from "node:stream/promises";
12
12
  import { createConnection, createServer as createServer$2 } from "node:net";
13
13
  import * as dgram from "node:dgram";
14
- //#region ../types/dist/event-category-CFZs3jI4.mjs
14
+ //#region ../types/dist/event-category-H4AVePnn.mjs
15
15
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
16
16
  EventCategory["SystemBoot"] = "system.boot";
17
17
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -161,6 +161,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
161
161
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
162
162
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
163
163
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
164
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
165
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
166
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
164
167
  EventCategory["DetectionEvent"] = "detection.event";
165
168
  EventCategory["SessionTrackNew"] = "session.track.new";
166
169
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -10808,10 +10811,23 @@ var deviceDiscoveryCapability = {
10808
10811
  }
10809
10812
  };
10810
10813
  /**
10811
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10812
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10813
- * time the firmware pushes a ring; status tracks the last press and
10814
- * a pressCount since start (diagnostic).
10814
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10815
+ * name (same pattern as `snapshot`):
10816
+ *
10817
+ * - **Native** providers: registered per-device by device-driver
10818
+ * addons via `ctx.registerNativeCap` — either on a
10819
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10820
+ * or directly on the camera (Reolink registers at camera level).
10821
+ * Emits an `onPressed` event every time the firmware pushes a
10822
+ * ring; status tracks the last press and a pressCount since start
10823
+ * (diagnostic).
10824
+ *
10825
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10826
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10827
+ * device (contact / switch / event-emitter …) into a doorbell for
10828
+ * a camera. `defaultActive: false` — the operator explicitly binds
10829
+ * it per camera in the device-bindings UI, then picks the source
10830
+ * device + trigger in the per-device settings.
10815
10831
  *
10816
10832
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10817
10833
  * — subscribers listening at the camera level receive ring events
@@ -10832,7 +10848,10 @@ var doorbellCapability = {
10832
10848
  scope: "device",
10833
10849
  deviceNative: true,
10834
10850
  mode: "singleton",
10835
- deviceTypes: [DeviceType.Button],
10851
+ kind: "wrapper",
10852
+ defaultActive: false,
10853
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10854
+ exposesDeviceSettings: true,
10836
10855
  methods: {},
10837
10856
  events: {
10838
10857
  /**
@@ -17250,6 +17269,14 @@ var ConfigEntrySchema = object({
17250
17269
  value: unknown(),
17251
17270
  description: string$2().optional()
17252
17271
  });
17272
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17273
+ /** One resolved linked device — the compact projection consumers need. */
17274
+ var LinkedDeviceSchema = object({
17275
+ deviceId: number(),
17276
+ name: string$2(),
17277
+ location: string$2().nullable(),
17278
+ features: array(string$2())
17279
+ });
17253
17280
  var SavedDeviceRowSchema = object({
17254
17281
  /** Numeric id reserved at allocateDeviceId time. */
17255
17282
  id: number(),
@@ -17471,7 +17498,10 @@ method(object({
17471
17498
  }), _void(), {
17472
17499
  kind: "mutation",
17473
17500
  auth: "admin"
17474
- }), method(object({ addonId: string$2() }), array(SavedDeviceRowSchema)), method(object({ addonId: string$2().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({
17501
+ }), method(object({ addonId: string$2() }), array(SavedDeviceRowSchema)), method(object({ addonId: string$2().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
17502
+ mode: DeviceLinkModeSchema,
17503
+ devices: array(LinkedDeviceSchema)
17504
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17475
17505
  deviceId: number(),
17476
17506
  values: record(string$2(), unknown())
17477
17507
  }), object({ success: literal(true) }), {
@@ -18740,6 +18770,61 @@ var EventKindSchema = _enum([
18740
18770
  "object",
18741
18771
  "audio"
18742
18772
  ]);
18773
+ /**
18774
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18775
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18776
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18777
+ */
18778
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18779
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18780
+ var EventKindIconSchema = _enum([
18781
+ "motion",
18782
+ "audio",
18783
+ "person",
18784
+ "vehicle",
18785
+ "animal",
18786
+ "door",
18787
+ "pir",
18788
+ "smoke",
18789
+ "water",
18790
+ "button",
18791
+ "generic"
18792
+ ]);
18793
+ var EventKindCategorySchema = _enum([
18794
+ "motion",
18795
+ "audio",
18796
+ "detection",
18797
+ "sensor",
18798
+ "custom"
18799
+ ]);
18800
+ var EventKindDescriptorSchema = object({
18801
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18802
+ kind: string$2(),
18803
+ label: string$2(),
18804
+ /** Hex color for timeline/legend rendering. */
18805
+ color: string$2(),
18806
+ icon: EventKindIconSchema,
18807
+ category: EventKindCategorySchema,
18808
+ /** Which cap + device contributes this kind. For built-ins the camera
18809
+ * itself; for sensor kinds the LINKED source device. */
18810
+ source: object({
18811
+ capName: string$2(),
18812
+ deviceId: number()
18813
+ })
18814
+ });
18815
+ var SensorEventSchema = object({
18816
+ id: string$2(),
18817
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18818
+ * yields N rows, one per camera). */
18819
+ deviceId: number(),
18820
+ /** The linked sensor device whose state changed. */
18821
+ sourceDeviceId: number(),
18822
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18823
+ kind: string$2(),
18824
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18825
+ value: record(string$2(), unknown()).nullable(),
18826
+ timestamp: number()
18827
+ });
18743
18828
  var TrackPositionSchema = object({
18744
18829
  x: number(),
18745
18830
  y: number(),
@@ -18753,6 +18838,30 @@ var TrackSnapshotSchema = object({
18753
18838
  mediaKey: string$2()
18754
18839
  });
18755
18840
  /**
18841
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18842
+ * divided by the track's detection-frame dims), computed at persist time.
18843
+ * Absent when the frame dims were unknown when the track was persisted
18844
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18845
+ */
18846
+ var TrackEnvelopeSchema = object({
18847
+ minX: number(),
18848
+ minY: number(),
18849
+ maxX: number(),
18850
+ maxY: number()
18851
+ });
18852
+ /**
18853
+ * Row projection for track list queries. `full` (default) returns the
18854
+ * complete Track including the frame-rate `positions[]` history and the
18855
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18856
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18857
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18858
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18859
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18860
+ * `getTrack`. Mirrors the event-store `projection` convention
18861
+ * (`getObjectEvents` et al.).
18862
+ */
18863
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18864
+ /**
18756
18865
  * One audio-classification label heard on the track's camera while the
18757
18866
  * track was alive, aggregated per label. An "episode" is one persisted
18758
18867
  * audio event (the confident-classification path: score ≥ the device's
@@ -18803,7 +18912,11 @@ var TrackSchema = object({
18803
18912
  /** Audio-classification labels heard on the camera during the track's
18804
18913
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18805
18914
  * Absent on legacy rows / tracks with no confident audio. */
18806
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18915
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18916
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18917
+ * Populated from the persisted envelope columns on historical reads;
18918
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18919
+ envelope: TrackEnvelopeSchema.optional()
18807
18920
  });
18808
18921
  var BaseEventFields = {
18809
18922
  id: string$2(),
@@ -18913,11 +19026,13 @@ var MediaFileSchema = object({
18913
19026
  sizeBytes: number(),
18914
19027
  timestamp: number()
18915
19028
  });
19029
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19030
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18916
19031
  var DeviceEventQueryInput = object({
18917
19032
  deviceId: number(),
18918
19033
  since: number().optional(),
18919
19034
  until: number().optional(),
18920
- limit: number().int().min(1).max(5e3).default(1e3),
19035
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18921
19036
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18922
19037
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18923
19038
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18925,6 +19040,27 @@ var DeviceEventQueryInput = object({
18925
19040
  projection: _enum(["full", "slim"]).optional()
18926
19041
  });
18927
19042
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string$2().optional() });
19043
+ var RecentTracksQueryInput = object({
19044
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19045
+ deviceIds: array(number()),
19046
+ /** Window lower bound on `lastSeen` (inclusive). */
19047
+ since: number().optional(),
19048
+ /** Window upper bound on `lastSeen` (inclusive). */
19049
+ until: number().optional(),
19050
+ /** Page size. Default 200, max 1000. */
19051
+ limit: number().int().min(1).max(1e3).default(200),
19052
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19053
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19054
+ cursor: string$2().optional(),
19055
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19056
+ projection: TrackProjectionSchema.optional()
19057
+ });
19058
+ var RecentTracksPageSchema = object({
19059
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19060
+ tracks: array(TrackSchema).readonly(),
19061
+ /** Cursor for the next page, or null when this page is the last. */
19062
+ nextCursor: string$2().nullable()
19063
+ });
18928
19064
  var KeyEventQueryInput = object({
18929
19065
  deviceId: number(),
18930
19066
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19007,11 +19143,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19007
19143
  deviceId: number(),
19008
19144
  since: number().optional(),
19009
19145
  until: number().optional(),
19010
- limit: number().optional()
19011
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19146
+ limit: number().optional(),
19147
+ /** Spatial filter only tracks whose trajectory intersects the zone
19148
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19149
+ * envelope columns, then precisely tested per position. Tracks with
19150
+ * an unknown envelope (no frame dims at persist time) always match. */
19151
+ zone: TrackZoneFilterSchema.optional(),
19152
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19153
+ * compatible — omitting the field keeps today's exact behaviour). */
19154
+ projection: TrackProjectionSchema.optional()
19155
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19012
19156
  kind: "mutation",
19013
19157
  auth: "admin"
19014
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19158
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19159
+ deviceId: number(),
19160
+ since: number().optional(),
19161
+ until: number().optional(),
19162
+ kinds: array(string$2()).optional(),
19163
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19164
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19015
19165
  deviceId: number(),
19016
19166
  since: number(),
19017
19167
  until: number(),
@@ -22229,7 +22379,12 @@ var RecordingStorageUsageSchema = object({
22229
22379
  /**
22230
22380
  * Result of locating footage at a wall-clock instant for one device/profile.
22231
22381
  * `segment` carries the covering segment's window; `gap` reports the forward
22232
- * nearest covered edge (`null` past the end of footage / when none exists).
22382
+ * nearest covered edge (`null` past the end of footage / when none exists)
22383
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22384
+ * footage behind the epoch; `null` when none — optional so older providers
22385
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22386
+ * small inter-segment cracks (durMs under-covers the span to the next
22387
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22233
22388
  */
22234
22389
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22235
22390
  kind: literal("segment"),
@@ -22238,7 +22393,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22238
22393
  bytes: number()
22239
22394
  }), object({
22240
22395
  kind: literal("gap"),
22241
- nearestEdgeMs: number().nullable()
22396
+ nearestEdgeMs: number().nullable(),
22397
+ prevEndMs: number().nullable().optional()
22242
22398
  })]);
22243
22399
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22244
22400
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23807,6 +23963,12 @@ Object.freeze({
23807
23963
  addonId: null,
23808
23964
  access: "view"
23809
23965
  },
23966
+ "deviceManager.getLinkedDevices": {
23967
+ capName: "device-manager",
23968
+ capScope: "system",
23969
+ addonId: null,
23970
+ access: "view"
23971
+ },
23810
23972
  "deviceManager.getRoleDisplayDefaults": {
23811
23973
  capName: "device-manager",
23812
23974
  capScope: "system",
@@ -25361,6 +25523,12 @@ Object.freeze({
25361
25523
  addonId: null,
25362
25524
  access: "view"
25363
25525
  },
25526
+ "pipelineAnalytics.getSensorEvents": {
25527
+ capName: "pipeline-analytics",
25528
+ capScope: "device",
25529
+ addonId: null,
25530
+ access: "view"
25531
+ },
25364
25532
  "pipelineAnalytics.getTrack": {
25365
25533
  capName: "pipeline-analytics",
25366
25534
  capScope: "device",
@@ -25373,6 +25541,18 @@ Object.freeze({
25373
25541
  addonId: null,
25374
25542
  access: "view"
25375
25543
  },
25544
+ "pipelineAnalytics.listEventKinds": {
25545
+ capName: "pipeline-analytics",
25546
+ capScope: "device",
25547
+ addonId: null,
25548
+ access: "view"
25549
+ },
25550
+ "pipelineAnalytics.listRecentTracks": {
25551
+ capName: "pipeline-analytics",
25552
+ capScope: "device",
25553
+ addonId: null,
25554
+ access: "view"
25555
+ },
25376
25556
  "pipelineAnalytics.listTracks": {
25377
25557
  capName: "pipeline-analytics",
25378
25558
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",