@camstack/addon-ai 0.1.3 → 0.1.4

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 +177 -13
  2. package/dist/addon.mjs +177 -13
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -43,7 +43,7 @@ let node_fs_promises = require("node:fs/promises");
43
43
  node_fs_promises = __toESM(node_fs_promises);
44
44
  let node_child_process = require("node:child_process");
45
45
  let node_net = require("node:net");
46
- //#region ../types/dist/event-category-CFZs3jI4.mjs
46
+ //#region ../types/dist/event-category-H4AVePnn.mjs
47
47
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
48
48
  EventCategory["SystemBoot"] = "system.boot";
49
49
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -193,6 +193,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
193
193
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
194
194
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
195
195
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
196
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
197
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
198
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
196
199
  EventCategory["DetectionEvent"] = "detection.event";
197
200
  EventCategory["SessionTrackNew"] = "session.track.new";
198
201
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -9887,7 +9890,7 @@ object({
9887
9890
  deviceId: number(),
9888
9891
  timestamp: number()
9889
9892
  });
9890
- DeviceType.Button;
9893
+ DeviceType.Button, DeviceType.Camera;
9891
9894
  /**
9892
9895
  * Enum-state sensor — a string value picked from a finite option set.
9893
9896
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -14257,6 +14260,14 @@ var ConfigEntrySchema = object({
14257
14260
  value: unknown(),
14258
14261
  description: string().optional()
14259
14262
  });
14263
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
14264
+ /** One resolved linked device — the compact projection consumers need. */
14265
+ var LinkedDeviceSchema = object({
14266
+ deviceId: number(),
14267
+ name: string(),
14268
+ location: string().nullable(),
14269
+ features: array(string())
14270
+ });
14260
14271
  var SavedDeviceRowSchema = object({
14261
14272
  /** Numeric id reserved at allocateDeviceId time. */
14262
14273
  id: number(),
@@ -14478,7 +14489,10 @@ method(object({
14478
14489
  }), _void(), {
14479
14490
  kind: "mutation",
14480
14491
  auth: "admin"
14481
- }), 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({
14492
+ }), 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({
14493
+ mode: DeviceLinkModeSchema,
14494
+ devices: array(LinkedDeviceSchema)
14495
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
14482
14496
  deviceId: number(),
14483
14497
  values: record(string(), unknown())
14484
14498
  }), object({ success: literal(true) }), {
@@ -15792,6 +15806,61 @@ var EventKindSchema = _enum([
15792
15806
  "object",
15793
15807
  "audio"
15794
15808
  ]);
15809
+ /**
15810
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
15811
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
15812
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
15813
+ */
15814
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
15815
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
15816
+ var EventKindIconSchema = _enum([
15817
+ "motion",
15818
+ "audio",
15819
+ "person",
15820
+ "vehicle",
15821
+ "animal",
15822
+ "door",
15823
+ "pir",
15824
+ "smoke",
15825
+ "water",
15826
+ "button",
15827
+ "generic"
15828
+ ]);
15829
+ var EventKindCategorySchema = _enum([
15830
+ "motion",
15831
+ "audio",
15832
+ "detection",
15833
+ "sensor",
15834
+ "custom"
15835
+ ]);
15836
+ var EventKindDescriptorSchema = object({
15837
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
15838
+ kind: string(),
15839
+ label: string(),
15840
+ /** Hex color for timeline/legend rendering. */
15841
+ color: string(),
15842
+ icon: EventKindIconSchema,
15843
+ category: EventKindCategorySchema,
15844
+ /** Which cap + device contributes this kind. For built-ins the camera
15845
+ * itself; for sensor kinds the LINKED source device. */
15846
+ source: object({
15847
+ capName: string(),
15848
+ deviceId: number()
15849
+ })
15850
+ });
15851
+ var SensorEventSchema = object({
15852
+ id: string(),
15853
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
15854
+ * yields N rows, one per camera). */
15855
+ deviceId: number(),
15856
+ /** The linked sensor device whose state changed. */
15857
+ sourceDeviceId: number(),
15858
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
15859
+ kind: string(),
15860
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
15861
+ value: record(string(), unknown()).nullable(),
15862
+ timestamp: number()
15863
+ });
15795
15864
  var TrackPositionSchema = object({
15796
15865
  x: number(),
15797
15866
  y: number(),
@@ -15805,6 +15874,30 @@ var TrackSnapshotSchema = object({
15805
15874
  mediaKey: string()
15806
15875
  });
15807
15876
  /**
15877
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
15878
+ * divided by the track's detection-frame dims), computed at persist time.
15879
+ * Absent when the frame dims were unknown when the track was persisted
15880
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
15881
+ */
15882
+ var TrackEnvelopeSchema = object({
15883
+ minX: number(),
15884
+ minY: number(),
15885
+ maxX: number(),
15886
+ maxY: number()
15887
+ });
15888
+ /**
15889
+ * Row projection for track list queries. `full` (default) returns the
15890
+ * complete Track including the frame-rate `positions[]` history and the
15891
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
15892
+ * keeps every scalar the list surfaces actually render (ids, class(es),
15893
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
15894
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
15895
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
15896
+ * `getTrack`. Mirrors the event-store `projection` convention
15897
+ * (`getObjectEvents` et al.).
15898
+ */
15899
+ var TrackProjectionSchema = _enum(["full", "slim"]);
15900
+ /**
15808
15901
  * One audio-classification label heard on the track's camera while the
15809
15902
  * track was alive, aggregated per label. An "episode" is one persisted
15810
15903
  * audio event (the confident-classification path: score ≥ the device's
@@ -15855,7 +15948,11 @@ var TrackSchema = object({
15855
15948
  /** Audio-classification labels heard on the camera during the track's
15856
15949
  * life (score ≥ device `classificationMinScore`), aggregated per label.
15857
15950
  * Absent on legacy rows / tracks with no confident audio. */
15858
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
15951
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
15952
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
15953
+ * Populated from the persisted envelope columns on historical reads;
15954
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
15955
+ envelope: TrackEnvelopeSchema.optional()
15859
15956
  });
15860
15957
  var BaseEventFields = {
15861
15958
  id: string(),
@@ -15965,11 +16062,13 @@ var MediaFileSchema = object({
15965
16062
  sizeBytes: number(),
15966
16063
  timestamp: number()
15967
16064
  });
16065
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
16066
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
15968
16067
  var DeviceEventQueryInput = object({
15969
16068
  deviceId: number(),
15970
16069
  since: number().optional(),
15971
16070
  until: number().optional(),
15972
- limit: number().int().min(1).max(5e3).default(1e3),
16071
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
15973
16072
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
15974
16073
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
15975
16074
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -15977,6 +16076,27 @@ var DeviceEventQueryInput = object({
15977
16076
  projection: _enum(["full", "slim"]).optional()
15978
16077
  });
15979
16078
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
16079
+ var RecentTracksQueryInput = object({
16080
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
16081
+ deviceIds: array(number()),
16082
+ /** Window lower bound on `lastSeen` (inclusive). */
16083
+ since: number().optional(),
16084
+ /** Window upper bound on `lastSeen` (inclusive). */
16085
+ until: number().optional(),
16086
+ /** Page size. Default 200, max 1000. */
16087
+ limit: number().int().min(1).max(1e3).default(200),
16088
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
16089
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
16090
+ cursor: string().optional(),
16091
+ /** See {@link TrackProjectionSchema}. Default `full`. */
16092
+ projection: TrackProjectionSchema.optional()
16093
+ });
16094
+ var RecentTracksPageSchema = object({
16095
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
16096
+ tracks: array(TrackSchema).readonly(),
16097
+ /** Cursor for the next page, or null when this page is the last. */
16098
+ nextCursor: string().nullable()
16099
+ });
15980
16100
  var KeyEventQueryInput = object({
15981
16101
  deviceId: number(),
15982
16102
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16059,11 +16179,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16059
16179
  deviceId: number(),
16060
16180
  since: number().optional(),
16061
16181
  until: number().optional(),
16062
- limit: number().optional()
16063
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
16064
- kind: "mutation",
16065
- auth: "admin"
16066
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16182
+ limit: number().optional(),
16183
+ /** Spatial filter only tracks whose trajectory intersects the zone
16184
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
16185
+ * envelope columns, then precisely tested per position. Tracks with
16186
+ * an unknown envelope (no frame dims at persist time) always match. */
16187
+ zone: TrackZoneFilterSchema.optional(),
16188
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
16189
+ * compatible — omitting the field keeps today's exact behaviour). */
16190
+ projection: TrackProjectionSchema.optional()
16191
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
16192
+ kind: "mutation",
16193
+ auth: "admin"
16194
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
16195
+ deviceId: number(),
16196
+ since: number().optional(),
16197
+ until: number().optional(),
16198
+ kinds: array(string()).optional(),
16199
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
16200
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16067
16201
  deviceId: number(),
16068
16202
  since: number(),
16069
16203
  until: number(),
@@ -19264,7 +19398,12 @@ var RecordingStorageUsageSchema = object({
19264
19398
  /**
19265
19399
  * Result of locating footage at a wall-clock instant for one device/profile.
19266
19400
  * `segment` carries the covering segment's window; `gap` reports the forward
19267
- * nearest covered edge (`null` past the end of footage / when none exists).
19401
+ * nearest covered edge (`null` past the end of footage / when none exists)
19402
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
19403
+ * footage behind the epoch; `null` when none — optional so older providers
19404
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
19405
+ * small inter-segment cracks (durMs under-covers the span to the next
19406
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
19268
19407
  */
19269
19408
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19270
19409
  kind: literal("segment"),
@@ -19273,7 +19412,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19273
19412
  bytes: number()
19274
19413
  }), object({
19275
19414
  kind: literal("gap"),
19276
- nearestEdgeMs: number().nullable()
19415
+ nearestEdgeMs: number().nullable(),
19416
+ prevEndMs: number().nullable().optional()
19277
19417
  })]);
19278
19418
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
19279
19419
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -20842,6 +20982,12 @@ Object.freeze({
20842
20982
  addonId: null,
20843
20983
  access: "view"
20844
20984
  },
20985
+ "deviceManager.getLinkedDevices": {
20986
+ capName: "device-manager",
20987
+ capScope: "system",
20988
+ addonId: null,
20989
+ access: "view"
20990
+ },
20845
20991
  "deviceManager.getRoleDisplayDefaults": {
20846
20992
  capName: "device-manager",
20847
20993
  capScope: "system",
@@ -22396,6 +22542,12 @@ Object.freeze({
22396
22542
  addonId: null,
22397
22543
  access: "view"
22398
22544
  },
22545
+ "pipelineAnalytics.getSensorEvents": {
22546
+ capName: "pipeline-analytics",
22547
+ capScope: "device",
22548
+ addonId: null,
22549
+ access: "view"
22550
+ },
22399
22551
  "pipelineAnalytics.getTrack": {
22400
22552
  capName: "pipeline-analytics",
22401
22553
  capScope: "device",
@@ -22408,6 +22560,18 @@ Object.freeze({
22408
22560
  addonId: null,
22409
22561
  access: "view"
22410
22562
  },
22563
+ "pipelineAnalytics.listEventKinds": {
22564
+ capName: "pipeline-analytics",
22565
+ capScope: "device",
22566
+ addonId: null,
22567
+ access: "view"
22568
+ },
22569
+ "pipelineAnalytics.listRecentTracks": {
22570
+ capName: "pipeline-analytics",
22571
+ capScope: "device",
22572
+ addonId: null,
22573
+ access: "view"
22574
+ },
22411
22575
  "pipelineAnalytics.listTracks": {
22412
22576
  capName: "pipeline-analytics",
22413
22577
  capScope: "device",
@@ -25608,7 +25772,7 @@ function entryForRef(ref) {
25608
25772
  };
25609
25773
  }
25610
25774
  //#endregion
25611
- //#region ../system/dist/model-download-service-C-IHWnXx.mjs
25775
+ //#region ../system/dist/model-download-service-Cp9f4dk6.mjs
25612
25776
  function isNonEmptyFile(filePath) {
25613
25777
  return node_fs.existsSync(filePath) && node_fs.statSync(filePath).size > 0;
25614
25778
  }
package/dist/addon.mjs CHANGED
@@ -5,7 +5,7 @@ import { createReadStream } from "node:fs";
5
5
  import * as fsp from "node:fs/promises";
6
6
  import { spawn } from "node:child_process";
7
7
  import { createServer } from "node:net";
8
- //#region ../types/dist/event-category-CFZs3jI4.mjs
8
+ //#region ../types/dist/event-category-H4AVePnn.mjs
9
9
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
10
10
  EventCategory["SystemBoot"] = "system.boot";
11
11
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -155,6 +155,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
155
155
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
156
156
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
157
157
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
158
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
159
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
160
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
158
161
  EventCategory["DetectionEvent"] = "detection.event";
159
162
  EventCategory["SessionTrackNew"] = "session.track.new";
160
163
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -9849,7 +9852,7 @@ object({
9849
9852
  deviceId: number(),
9850
9853
  timestamp: number()
9851
9854
  });
9852
- DeviceType.Button;
9855
+ DeviceType.Button, DeviceType.Camera;
9853
9856
  /**
9854
9857
  * Enum-state sensor — a string value picked from a finite option set.
9855
9858
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -14219,6 +14222,14 @@ var ConfigEntrySchema = object({
14219
14222
  value: unknown(),
14220
14223
  description: string().optional()
14221
14224
  });
14225
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
14226
+ /** One resolved linked device — the compact projection consumers need. */
14227
+ var LinkedDeviceSchema = object({
14228
+ deviceId: number(),
14229
+ name: string(),
14230
+ location: string().nullable(),
14231
+ features: array(string())
14232
+ });
14222
14233
  var SavedDeviceRowSchema = object({
14223
14234
  /** Numeric id reserved at allocateDeviceId time. */
14224
14235
  id: number(),
@@ -14440,7 +14451,10 @@ method(object({
14440
14451
  }), _void(), {
14441
14452
  kind: "mutation",
14442
14453
  auth: "admin"
14443
- }), 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({
14454
+ }), 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({
14455
+ mode: DeviceLinkModeSchema,
14456
+ devices: array(LinkedDeviceSchema)
14457
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
14444
14458
  deviceId: number(),
14445
14459
  values: record(string(), unknown())
14446
14460
  }), object({ success: literal(true) }), {
@@ -15754,6 +15768,61 @@ var EventKindSchema = _enum([
15754
15768
  "object",
15755
15769
  "audio"
15756
15770
  ]);
15771
+ /**
15772
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
15773
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
15774
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
15775
+ */
15776
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
15777
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
15778
+ var EventKindIconSchema = _enum([
15779
+ "motion",
15780
+ "audio",
15781
+ "person",
15782
+ "vehicle",
15783
+ "animal",
15784
+ "door",
15785
+ "pir",
15786
+ "smoke",
15787
+ "water",
15788
+ "button",
15789
+ "generic"
15790
+ ]);
15791
+ var EventKindCategorySchema = _enum([
15792
+ "motion",
15793
+ "audio",
15794
+ "detection",
15795
+ "sensor",
15796
+ "custom"
15797
+ ]);
15798
+ var EventKindDescriptorSchema = object({
15799
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
15800
+ kind: string(),
15801
+ label: string(),
15802
+ /** Hex color for timeline/legend rendering. */
15803
+ color: string(),
15804
+ icon: EventKindIconSchema,
15805
+ category: EventKindCategorySchema,
15806
+ /** Which cap + device contributes this kind. For built-ins the camera
15807
+ * itself; for sensor kinds the LINKED source device. */
15808
+ source: object({
15809
+ capName: string(),
15810
+ deviceId: number()
15811
+ })
15812
+ });
15813
+ var SensorEventSchema = object({
15814
+ id: string(),
15815
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
15816
+ * yields N rows, one per camera). */
15817
+ deviceId: number(),
15818
+ /** The linked sensor device whose state changed. */
15819
+ sourceDeviceId: number(),
15820
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
15821
+ kind: string(),
15822
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
15823
+ value: record(string(), unknown()).nullable(),
15824
+ timestamp: number()
15825
+ });
15757
15826
  var TrackPositionSchema = object({
15758
15827
  x: number(),
15759
15828
  y: number(),
@@ -15767,6 +15836,30 @@ var TrackSnapshotSchema = object({
15767
15836
  mediaKey: string()
15768
15837
  });
15769
15838
  /**
15839
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
15840
+ * divided by the track's detection-frame dims), computed at persist time.
15841
+ * Absent when the frame dims were unknown when the track was persisted
15842
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
15843
+ */
15844
+ var TrackEnvelopeSchema = object({
15845
+ minX: number(),
15846
+ minY: number(),
15847
+ maxX: number(),
15848
+ maxY: number()
15849
+ });
15850
+ /**
15851
+ * Row projection for track list queries. `full` (default) returns the
15852
+ * complete Track including the frame-rate `positions[]` history and the
15853
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
15854
+ * keeps every scalar the list surfaces actually render (ids, class(es),
15855
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
15856
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
15857
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
15858
+ * `getTrack`. Mirrors the event-store `projection` convention
15859
+ * (`getObjectEvents` et al.).
15860
+ */
15861
+ var TrackProjectionSchema = _enum(["full", "slim"]);
15862
+ /**
15770
15863
  * One audio-classification label heard on the track's camera while the
15771
15864
  * track was alive, aggregated per label. An "episode" is one persisted
15772
15865
  * audio event (the confident-classification path: score ≥ the device's
@@ -15817,7 +15910,11 @@ var TrackSchema = object({
15817
15910
  /** Audio-classification labels heard on the camera during the track's
15818
15911
  * life (score ≥ device `classificationMinScore`), aggregated per label.
15819
15912
  * Absent on legacy rows / tracks with no confident audio. */
15820
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
15913
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
15914
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
15915
+ * Populated from the persisted envelope columns on historical reads;
15916
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
15917
+ envelope: TrackEnvelopeSchema.optional()
15821
15918
  });
15822
15919
  var BaseEventFields = {
15823
15920
  id: string(),
@@ -15927,11 +16024,13 @@ var MediaFileSchema = object({
15927
16024
  sizeBytes: number(),
15928
16025
  timestamp: number()
15929
16026
  });
16027
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
16028
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
15930
16029
  var DeviceEventQueryInput = object({
15931
16030
  deviceId: number(),
15932
16031
  since: number().optional(),
15933
16032
  until: number().optional(),
15934
- limit: number().int().min(1).max(5e3).default(1e3),
16033
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
15935
16034
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
15936
16035
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
15937
16036
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -15939,6 +16038,27 @@ var DeviceEventQueryInput = object({
15939
16038
  projection: _enum(["full", "slim"]).optional()
15940
16039
  });
15941
16040
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
16041
+ var RecentTracksQueryInput = object({
16042
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
16043
+ deviceIds: array(number()),
16044
+ /** Window lower bound on `lastSeen` (inclusive). */
16045
+ since: number().optional(),
16046
+ /** Window upper bound on `lastSeen` (inclusive). */
16047
+ until: number().optional(),
16048
+ /** Page size. Default 200, max 1000. */
16049
+ limit: number().int().min(1).max(1e3).default(200),
16050
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
16051
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
16052
+ cursor: string().optional(),
16053
+ /** See {@link TrackProjectionSchema}. Default `full`. */
16054
+ projection: TrackProjectionSchema.optional()
16055
+ });
16056
+ var RecentTracksPageSchema = object({
16057
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
16058
+ tracks: array(TrackSchema).readonly(),
16059
+ /** Cursor for the next page, or null when this page is the last. */
16060
+ nextCursor: string().nullable()
16061
+ });
15942
16062
  var KeyEventQueryInput = object({
15943
16063
  deviceId: number(),
15944
16064
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16021,11 +16141,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16021
16141
  deviceId: number(),
16022
16142
  since: number().optional(),
16023
16143
  until: number().optional(),
16024
- limit: number().optional()
16025
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
16026
- kind: "mutation",
16027
- auth: "admin"
16028
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16144
+ limit: number().optional(),
16145
+ /** Spatial filter only tracks whose trajectory intersects the zone
16146
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
16147
+ * envelope columns, then precisely tested per position. Tracks with
16148
+ * an unknown envelope (no frame dims at persist time) always match. */
16149
+ zone: TrackZoneFilterSchema.optional(),
16150
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
16151
+ * compatible — omitting the field keeps today's exact behaviour). */
16152
+ projection: TrackProjectionSchema.optional()
16153
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
16154
+ kind: "mutation",
16155
+ auth: "admin"
16156
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
16157
+ deviceId: number(),
16158
+ since: number().optional(),
16159
+ until: number().optional(),
16160
+ kinds: array(string()).optional(),
16161
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
16162
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16029
16163
  deviceId: number(),
16030
16164
  since: number(),
16031
16165
  until: number(),
@@ -19226,7 +19360,12 @@ var RecordingStorageUsageSchema = object({
19226
19360
  /**
19227
19361
  * Result of locating footage at a wall-clock instant for one device/profile.
19228
19362
  * `segment` carries the covering segment's window; `gap` reports the forward
19229
- * nearest covered edge (`null` past the end of footage / when none exists).
19363
+ * nearest covered edge (`null` past the end of footage / when none exists)
19364
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
19365
+ * footage behind the epoch; `null` when none — optional so older providers
19366
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
19367
+ * small inter-segment cracks (durMs under-covers the span to the next
19368
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
19230
19369
  */
19231
19370
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19232
19371
  kind: literal("segment"),
@@ -19235,7 +19374,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19235
19374
  bytes: number()
19236
19375
  }), object({
19237
19376
  kind: literal("gap"),
19238
- nearestEdgeMs: number().nullable()
19377
+ nearestEdgeMs: number().nullable(),
19378
+ prevEndMs: number().nullable().optional()
19239
19379
  })]);
19240
19380
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
19241
19381
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -20804,6 +20944,12 @@ Object.freeze({
20804
20944
  addonId: null,
20805
20945
  access: "view"
20806
20946
  },
20947
+ "deviceManager.getLinkedDevices": {
20948
+ capName: "device-manager",
20949
+ capScope: "system",
20950
+ addonId: null,
20951
+ access: "view"
20952
+ },
20807
20953
  "deviceManager.getRoleDisplayDefaults": {
20808
20954
  capName: "device-manager",
20809
20955
  capScope: "system",
@@ -22358,6 +22504,12 @@ Object.freeze({
22358
22504
  addonId: null,
22359
22505
  access: "view"
22360
22506
  },
22507
+ "pipelineAnalytics.getSensorEvents": {
22508
+ capName: "pipeline-analytics",
22509
+ capScope: "device",
22510
+ addonId: null,
22511
+ access: "view"
22512
+ },
22361
22513
  "pipelineAnalytics.getTrack": {
22362
22514
  capName: "pipeline-analytics",
22363
22515
  capScope: "device",
@@ -22370,6 +22522,18 @@ Object.freeze({
22370
22522
  addonId: null,
22371
22523
  access: "view"
22372
22524
  },
22525
+ "pipelineAnalytics.listEventKinds": {
22526
+ capName: "pipeline-analytics",
22527
+ capScope: "device",
22528
+ addonId: null,
22529
+ access: "view"
22530
+ },
22531
+ "pipelineAnalytics.listRecentTracks": {
22532
+ capName: "pipeline-analytics",
22533
+ capScope: "device",
22534
+ addonId: null,
22535
+ access: "view"
22536
+ },
22373
22537
  "pipelineAnalytics.listTracks": {
22374
22538
  capName: "pipeline-analytics",
22375
22539
  capScope: "device",
@@ -25570,7 +25734,7 @@ function entryForRef(ref) {
25570
25734
  };
25571
25735
  }
25572
25736
  //#endregion
25573
- //#region ../system/dist/model-download-service-C-IHWnXx.mjs
25737
+ //#region ../system/dist/model-download-service-Cp9f4dk6.mjs
25574
25738
  function isNonEmptyFile(filePath) {
25575
25739
  return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
25576
25740
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",