@camstack/addon-ai 0.1.3 → 0.1.5

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 +203 -14
  2. package/dist/addon.mjs +203 -14
  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";
@@ -7262,6 +7265,24 @@ var RecordingRetentionSchema = object({
7262
7265
  maxSizeGb: number().min(0).optional()
7263
7266
  });
7264
7267
  /**
7268
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7269
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7270
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7271
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7272
+ *
7273
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7274
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7275
+ * preset changed over time renders every historical window at the dims it was
7276
+ * written with.
7277
+ */
7278
+ var ScrubThumbnailPresetSchema = _enum([
7279
+ "minimal",
7280
+ "low",
7281
+ "standard",
7282
+ "high",
7283
+ "max"
7284
+ ]);
7285
+ /**
7265
7286
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7266
7287
  *
7267
7288
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7298,7 +7319,14 @@ var RecordingConfigSchema = object({
7298
7319
  * derived into bands once via `migrateConfigToBands`.
7299
7320
  */
7300
7321
  bands: array(RecordingBandSchema).optional(),
7301
- retention: RecordingRetentionSchema.optional()
7322
+ retention: RecordingRetentionSchema.optional(),
7323
+ /**
7324
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7325
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7326
+ * windows only — existing sheets are immutable, and each window's index
7327
+ * carries its own tile dims so mixed-preset history renders correctly.
7328
+ */
7329
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7302
7330
  });
7303
7331
  /**
7304
7332
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -9887,7 +9915,7 @@ object({
9887
9915
  deviceId: number(),
9888
9916
  timestamp: number()
9889
9917
  });
9890
- DeviceType.Button;
9918
+ DeviceType.Button, DeviceType.Camera;
9891
9919
  /**
9892
9920
  * Enum-state sensor — a string value picked from a finite option set.
9893
9921
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -14257,6 +14285,14 @@ var ConfigEntrySchema = object({
14257
14285
  value: unknown(),
14258
14286
  description: string().optional()
14259
14287
  });
14288
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
14289
+ /** One resolved linked device — the compact projection consumers need. */
14290
+ var LinkedDeviceSchema = object({
14291
+ deviceId: number(),
14292
+ name: string(),
14293
+ location: string().nullable(),
14294
+ features: array(string())
14295
+ });
14260
14296
  var SavedDeviceRowSchema = object({
14261
14297
  /** Numeric id reserved at allocateDeviceId time. */
14262
14298
  id: number(),
@@ -14478,7 +14514,10 @@ method(object({
14478
14514
  }), _void(), {
14479
14515
  kind: "mutation",
14480
14516
  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({
14517
+ }), 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({
14518
+ mode: DeviceLinkModeSchema,
14519
+ devices: array(LinkedDeviceSchema)
14520
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
14482
14521
  deviceId: number(),
14483
14522
  values: record(string(), unknown())
14484
14523
  }), object({ success: literal(true) }), {
@@ -15792,6 +15831,61 @@ var EventKindSchema = _enum([
15792
15831
  "object",
15793
15832
  "audio"
15794
15833
  ]);
15834
+ /**
15835
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
15836
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
15837
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
15838
+ */
15839
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
15840
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
15841
+ var EventKindIconSchema = _enum([
15842
+ "motion",
15843
+ "audio",
15844
+ "person",
15845
+ "vehicle",
15846
+ "animal",
15847
+ "door",
15848
+ "pir",
15849
+ "smoke",
15850
+ "water",
15851
+ "button",
15852
+ "generic"
15853
+ ]);
15854
+ var EventKindCategorySchema = _enum([
15855
+ "motion",
15856
+ "audio",
15857
+ "detection",
15858
+ "sensor",
15859
+ "custom"
15860
+ ]);
15861
+ var EventKindDescriptorSchema = object({
15862
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
15863
+ kind: string(),
15864
+ label: string(),
15865
+ /** Hex color for timeline/legend rendering. */
15866
+ color: string(),
15867
+ icon: EventKindIconSchema,
15868
+ category: EventKindCategorySchema,
15869
+ /** Which cap + device contributes this kind. For built-ins the camera
15870
+ * itself; for sensor kinds the LINKED source device. */
15871
+ source: object({
15872
+ capName: string(),
15873
+ deviceId: number()
15874
+ })
15875
+ });
15876
+ var SensorEventSchema = object({
15877
+ id: string(),
15878
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
15879
+ * yields N rows, one per camera). */
15880
+ deviceId: number(),
15881
+ /** The linked sensor device whose state changed. */
15882
+ sourceDeviceId: number(),
15883
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
15884
+ kind: string(),
15885
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
15886
+ value: record(string(), unknown()).nullable(),
15887
+ timestamp: number()
15888
+ });
15795
15889
  var TrackPositionSchema = object({
15796
15890
  x: number(),
15797
15891
  y: number(),
@@ -15805,6 +15899,30 @@ var TrackSnapshotSchema = object({
15805
15899
  mediaKey: string()
15806
15900
  });
15807
15901
  /**
15902
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
15903
+ * divided by the track's detection-frame dims), computed at persist time.
15904
+ * Absent when the frame dims were unknown when the track was persisted
15905
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
15906
+ */
15907
+ var TrackEnvelopeSchema = object({
15908
+ minX: number(),
15909
+ minY: number(),
15910
+ maxX: number(),
15911
+ maxY: number()
15912
+ });
15913
+ /**
15914
+ * Row projection for track list queries. `full` (default) returns the
15915
+ * complete Track including the frame-rate `positions[]` history and the
15916
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
15917
+ * keeps every scalar the list surfaces actually render (ids, class(es),
15918
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
15919
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
15920
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
15921
+ * `getTrack`. Mirrors the event-store `projection` convention
15922
+ * (`getObjectEvents` et al.).
15923
+ */
15924
+ var TrackProjectionSchema = _enum(["full", "slim"]);
15925
+ /**
15808
15926
  * One audio-classification label heard on the track's camera while the
15809
15927
  * track was alive, aggregated per label. An "episode" is one persisted
15810
15928
  * audio event (the confident-classification path: score ≥ the device's
@@ -15855,7 +15973,11 @@ var TrackSchema = object({
15855
15973
  /** Audio-classification labels heard on the camera during the track's
15856
15974
  * life (score ≥ device `classificationMinScore`), aggregated per label.
15857
15975
  * Absent on legacy rows / tracks with no confident audio. */
15858
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
15976
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
15977
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
15978
+ * Populated from the persisted envelope columns on historical reads;
15979
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
15980
+ envelope: TrackEnvelopeSchema.optional()
15859
15981
  });
15860
15982
  var BaseEventFields = {
15861
15983
  id: string(),
@@ -15965,11 +16087,13 @@ var MediaFileSchema = object({
15965
16087
  sizeBytes: number(),
15966
16088
  timestamp: number()
15967
16089
  });
16090
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
16091
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
15968
16092
  var DeviceEventQueryInput = object({
15969
16093
  deviceId: number(),
15970
16094
  since: number().optional(),
15971
16095
  until: number().optional(),
15972
- limit: number().int().min(1).max(5e3).default(1e3),
16096
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
15973
16097
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
15974
16098
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
15975
16099
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -15977,6 +16101,27 @@ var DeviceEventQueryInput = object({
15977
16101
  projection: _enum(["full", "slim"]).optional()
15978
16102
  });
15979
16103
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
16104
+ var RecentTracksQueryInput = object({
16105
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
16106
+ deviceIds: array(number()),
16107
+ /** Window lower bound on `lastSeen` (inclusive). */
16108
+ since: number().optional(),
16109
+ /** Window upper bound on `lastSeen` (inclusive). */
16110
+ until: number().optional(),
16111
+ /** Page size. Default 200, max 1000. */
16112
+ limit: number().int().min(1).max(1e3).default(200),
16113
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
16114
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
16115
+ cursor: string().optional(),
16116
+ /** See {@link TrackProjectionSchema}. Default `full`. */
16117
+ projection: TrackProjectionSchema.optional()
16118
+ });
16119
+ var RecentTracksPageSchema = object({
16120
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
16121
+ tracks: array(TrackSchema).readonly(),
16122
+ /** Cursor for the next page, or null when this page is the last. */
16123
+ nextCursor: string().nullable()
16124
+ });
15980
16125
  var KeyEventQueryInput = object({
15981
16126
  deviceId: number(),
15982
16127
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16059,11 +16204,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16059
16204
  deviceId: number(),
16060
16205
  since: number().optional(),
16061
16206
  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({
16207
+ limit: number().optional(),
16208
+ /** Spatial filter only tracks whose trajectory intersects the zone
16209
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
16210
+ * envelope columns, then precisely tested per position. Tracks with
16211
+ * an unknown envelope (no frame dims at persist time) always match. */
16212
+ zone: TrackZoneFilterSchema.optional(),
16213
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
16214
+ * compatible — omitting the field keeps today's exact behaviour). */
16215
+ projection: TrackProjectionSchema.optional()
16216
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
16217
+ kind: "mutation",
16218
+ auth: "admin"
16219
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
16220
+ deviceId: number(),
16221
+ since: number().optional(),
16222
+ until: number().optional(),
16223
+ kinds: array(string()).optional(),
16224
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
16225
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16067
16226
  deviceId: number(),
16068
16227
  since: number(),
16069
16228
  until: number(),
@@ -19264,7 +19423,12 @@ var RecordingStorageUsageSchema = object({
19264
19423
  /**
19265
19424
  * Result of locating footage at a wall-clock instant for one device/profile.
19266
19425
  * `segment` carries the covering segment's window; `gap` reports the forward
19267
- * nearest covered edge (`null` past the end of footage / when none exists).
19426
+ * nearest covered edge (`null` past the end of footage / when none exists)
19427
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
19428
+ * footage behind the epoch; `null` when none — optional so older providers
19429
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
19430
+ * small inter-segment cracks (durMs under-covers the span to the next
19431
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
19268
19432
  */
19269
19433
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19270
19434
  kind: literal("segment"),
@@ -19273,7 +19437,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19273
19437
  bytes: number()
19274
19438
  }), object({
19275
19439
  kind: literal("gap"),
19276
- nearestEdgeMs: number().nullable()
19440
+ nearestEdgeMs: number().nullable(),
19441
+ prevEndMs: number().nullable().optional()
19277
19442
  })]);
19278
19443
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
19279
19444
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -20842,6 +21007,12 @@ Object.freeze({
20842
21007
  addonId: null,
20843
21008
  access: "view"
20844
21009
  },
21010
+ "deviceManager.getLinkedDevices": {
21011
+ capName: "device-manager",
21012
+ capScope: "system",
21013
+ addonId: null,
21014
+ access: "view"
21015
+ },
20845
21016
  "deviceManager.getRoleDisplayDefaults": {
20846
21017
  capName: "device-manager",
20847
21018
  capScope: "system",
@@ -22396,6 +22567,12 @@ Object.freeze({
22396
22567
  addonId: null,
22397
22568
  access: "view"
22398
22569
  },
22570
+ "pipelineAnalytics.getSensorEvents": {
22571
+ capName: "pipeline-analytics",
22572
+ capScope: "device",
22573
+ addonId: null,
22574
+ access: "view"
22575
+ },
22399
22576
  "pipelineAnalytics.getTrack": {
22400
22577
  capName: "pipeline-analytics",
22401
22578
  capScope: "device",
@@ -22408,6 +22585,18 @@ Object.freeze({
22408
22585
  addonId: null,
22409
22586
  access: "view"
22410
22587
  },
22588
+ "pipelineAnalytics.listEventKinds": {
22589
+ capName: "pipeline-analytics",
22590
+ capScope: "device",
22591
+ addonId: null,
22592
+ access: "view"
22593
+ },
22594
+ "pipelineAnalytics.listRecentTracks": {
22595
+ capName: "pipeline-analytics",
22596
+ capScope: "device",
22597
+ addonId: null,
22598
+ access: "view"
22599
+ },
22411
22600
  "pipelineAnalytics.listTracks": {
22412
22601
  capName: "pipeline-analytics",
22413
22602
  capScope: "device",
@@ -25608,7 +25797,7 @@ function entryForRef(ref) {
25608
25797
  };
25609
25798
  }
25610
25799
  //#endregion
25611
- //#region ../system/dist/model-download-service-C-IHWnXx.mjs
25800
+ //#region ../system/dist/model-download-service-Cp9f4dk6.mjs
25612
25801
  function isNonEmptyFile(filePath) {
25613
25802
  return node_fs.existsSync(filePath) && node_fs.statSync(filePath).size > 0;
25614
25803
  }
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";
@@ -7224,6 +7227,24 @@ var RecordingRetentionSchema = object({
7224
7227
  maxSizeGb: number().min(0).optional()
7225
7228
  });
7226
7229
  /**
7230
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7231
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7232
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7233
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7234
+ *
7235
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7236
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7237
+ * preset changed over time renders every historical window at the dims it was
7238
+ * written with.
7239
+ */
7240
+ var ScrubThumbnailPresetSchema = _enum([
7241
+ "minimal",
7242
+ "low",
7243
+ "standard",
7244
+ "high",
7245
+ "max"
7246
+ ]);
7247
+ /**
7227
7248
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7228
7249
  *
7229
7250
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7260,7 +7281,14 @@ var RecordingConfigSchema = object({
7260
7281
  * derived into bands once via `migrateConfigToBands`.
7261
7282
  */
7262
7283
  bands: array(RecordingBandSchema).optional(),
7263
- retention: RecordingRetentionSchema.optional()
7284
+ retention: RecordingRetentionSchema.optional(),
7285
+ /**
7286
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7287
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7288
+ * windows only — existing sheets are immutable, and each window's index
7289
+ * carries its own tile dims so mixed-preset history renders correctly.
7290
+ */
7291
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7264
7292
  });
7265
7293
  /**
7266
7294
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -9849,7 +9877,7 @@ object({
9849
9877
  deviceId: number(),
9850
9878
  timestamp: number()
9851
9879
  });
9852
- DeviceType.Button;
9880
+ DeviceType.Button, DeviceType.Camera;
9853
9881
  /**
9854
9882
  * Enum-state sensor — a string value picked from a finite option set.
9855
9883
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -14219,6 +14247,14 @@ var ConfigEntrySchema = object({
14219
14247
  value: unknown(),
14220
14248
  description: string().optional()
14221
14249
  });
14250
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
14251
+ /** One resolved linked device — the compact projection consumers need. */
14252
+ var LinkedDeviceSchema = object({
14253
+ deviceId: number(),
14254
+ name: string(),
14255
+ location: string().nullable(),
14256
+ features: array(string())
14257
+ });
14222
14258
  var SavedDeviceRowSchema = object({
14223
14259
  /** Numeric id reserved at allocateDeviceId time. */
14224
14260
  id: number(),
@@ -14440,7 +14476,10 @@ method(object({
14440
14476
  }), _void(), {
14441
14477
  kind: "mutation",
14442
14478
  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({
14479
+ }), 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({
14480
+ mode: DeviceLinkModeSchema,
14481
+ devices: array(LinkedDeviceSchema)
14482
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
14444
14483
  deviceId: number(),
14445
14484
  values: record(string(), unknown())
14446
14485
  }), object({ success: literal(true) }), {
@@ -15754,6 +15793,61 @@ var EventKindSchema = _enum([
15754
15793
  "object",
15755
15794
  "audio"
15756
15795
  ]);
15796
+ /**
15797
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
15798
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
15799
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
15800
+ */
15801
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
15802
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
15803
+ var EventKindIconSchema = _enum([
15804
+ "motion",
15805
+ "audio",
15806
+ "person",
15807
+ "vehicle",
15808
+ "animal",
15809
+ "door",
15810
+ "pir",
15811
+ "smoke",
15812
+ "water",
15813
+ "button",
15814
+ "generic"
15815
+ ]);
15816
+ var EventKindCategorySchema = _enum([
15817
+ "motion",
15818
+ "audio",
15819
+ "detection",
15820
+ "sensor",
15821
+ "custom"
15822
+ ]);
15823
+ var EventKindDescriptorSchema = object({
15824
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
15825
+ kind: string(),
15826
+ label: string(),
15827
+ /** Hex color for timeline/legend rendering. */
15828
+ color: string(),
15829
+ icon: EventKindIconSchema,
15830
+ category: EventKindCategorySchema,
15831
+ /** Which cap + device contributes this kind. For built-ins the camera
15832
+ * itself; for sensor kinds the LINKED source device. */
15833
+ source: object({
15834
+ capName: string(),
15835
+ deviceId: number()
15836
+ })
15837
+ });
15838
+ var SensorEventSchema = object({
15839
+ id: string(),
15840
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
15841
+ * yields N rows, one per camera). */
15842
+ deviceId: number(),
15843
+ /** The linked sensor device whose state changed. */
15844
+ sourceDeviceId: number(),
15845
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
15846
+ kind: string(),
15847
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
15848
+ value: record(string(), unknown()).nullable(),
15849
+ timestamp: number()
15850
+ });
15757
15851
  var TrackPositionSchema = object({
15758
15852
  x: number(),
15759
15853
  y: number(),
@@ -15767,6 +15861,30 @@ var TrackSnapshotSchema = object({
15767
15861
  mediaKey: string()
15768
15862
  });
15769
15863
  /**
15864
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
15865
+ * divided by the track's detection-frame dims), computed at persist time.
15866
+ * Absent when the frame dims were unknown when the track was persisted
15867
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
15868
+ */
15869
+ var TrackEnvelopeSchema = object({
15870
+ minX: number(),
15871
+ minY: number(),
15872
+ maxX: number(),
15873
+ maxY: number()
15874
+ });
15875
+ /**
15876
+ * Row projection for track list queries. `full` (default) returns the
15877
+ * complete Track including the frame-rate `positions[]` history and the
15878
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
15879
+ * keeps every scalar the list surfaces actually render (ids, class(es),
15880
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
15881
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
15882
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
15883
+ * `getTrack`. Mirrors the event-store `projection` convention
15884
+ * (`getObjectEvents` et al.).
15885
+ */
15886
+ var TrackProjectionSchema = _enum(["full", "slim"]);
15887
+ /**
15770
15888
  * One audio-classification label heard on the track's camera while the
15771
15889
  * track was alive, aggregated per label. An "episode" is one persisted
15772
15890
  * audio event (the confident-classification path: score ≥ the device's
@@ -15817,7 +15935,11 @@ var TrackSchema = object({
15817
15935
  /** Audio-classification labels heard on the camera during the track's
15818
15936
  * life (score ≥ device `classificationMinScore`), aggregated per label.
15819
15937
  * Absent on legacy rows / tracks with no confident audio. */
15820
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
15938
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
15939
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
15940
+ * Populated from the persisted envelope columns on historical reads;
15941
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
15942
+ envelope: TrackEnvelopeSchema.optional()
15821
15943
  });
15822
15944
  var BaseEventFields = {
15823
15945
  id: string(),
@@ -15927,11 +16049,13 @@ var MediaFileSchema = object({
15927
16049
  sizeBytes: number(),
15928
16050
  timestamp: number()
15929
16051
  });
16052
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
16053
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
15930
16054
  var DeviceEventQueryInput = object({
15931
16055
  deviceId: number(),
15932
16056
  since: number().optional(),
15933
16057
  until: number().optional(),
15934
- limit: number().int().min(1).max(5e3).default(1e3),
16058
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
15935
16059
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
15936
16060
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
15937
16061
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -15939,6 +16063,27 @@ var DeviceEventQueryInput = object({
15939
16063
  projection: _enum(["full", "slim"]).optional()
15940
16064
  });
15941
16065
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
16066
+ var RecentTracksQueryInput = object({
16067
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
16068
+ deviceIds: array(number()),
16069
+ /** Window lower bound on `lastSeen` (inclusive). */
16070
+ since: number().optional(),
16071
+ /** Window upper bound on `lastSeen` (inclusive). */
16072
+ until: number().optional(),
16073
+ /** Page size. Default 200, max 1000. */
16074
+ limit: number().int().min(1).max(1e3).default(200),
16075
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
16076
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
16077
+ cursor: string().optional(),
16078
+ /** See {@link TrackProjectionSchema}. Default `full`. */
16079
+ projection: TrackProjectionSchema.optional()
16080
+ });
16081
+ var RecentTracksPageSchema = object({
16082
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
16083
+ tracks: array(TrackSchema).readonly(),
16084
+ /** Cursor for the next page, or null when this page is the last. */
16085
+ nextCursor: string().nullable()
16086
+ });
15942
16087
  var KeyEventQueryInput = object({
15943
16088
  deviceId: number(),
15944
16089
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16021,11 +16166,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16021
16166
  deviceId: number(),
16022
16167
  since: number().optional(),
16023
16168
  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({
16169
+ limit: number().optional(),
16170
+ /** Spatial filter only tracks whose trajectory intersects the zone
16171
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
16172
+ * envelope columns, then precisely tested per position. Tracks with
16173
+ * an unknown envelope (no frame dims at persist time) always match. */
16174
+ zone: TrackZoneFilterSchema.optional(),
16175
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
16176
+ * compatible — omitting the field keeps today's exact behaviour). */
16177
+ projection: TrackProjectionSchema.optional()
16178
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
16179
+ kind: "mutation",
16180
+ auth: "admin"
16181
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
16182
+ deviceId: number(),
16183
+ since: number().optional(),
16184
+ until: number().optional(),
16185
+ kinds: array(string()).optional(),
16186
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
16187
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
16029
16188
  deviceId: number(),
16030
16189
  since: number(),
16031
16190
  until: number(),
@@ -19226,7 +19385,12 @@ var RecordingStorageUsageSchema = object({
19226
19385
  /**
19227
19386
  * Result of locating footage at a wall-clock instant for one device/profile.
19228
19387
  * `segment` carries the covering segment's window; `gap` reports the forward
19229
- * nearest covered edge (`null` past the end of footage / when none exists).
19388
+ * nearest covered edge (`null` past the end of footage / when none exists)
19389
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
19390
+ * footage behind the epoch; `null` when none — optional so older providers
19391
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
19392
+ * small inter-segment cracks (durMs under-covers the span to the next
19393
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
19230
19394
  */
19231
19395
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19232
19396
  kind: literal("segment"),
@@ -19235,7 +19399,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
19235
19399
  bytes: number()
19236
19400
  }), object({
19237
19401
  kind: literal("gap"),
19238
- nearestEdgeMs: number().nullable()
19402
+ nearestEdgeMs: number().nullable(),
19403
+ prevEndMs: number().nullable().optional()
19239
19404
  })]);
19240
19405
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
19241
19406
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -20804,6 +20969,12 @@ Object.freeze({
20804
20969
  addonId: null,
20805
20970
  access: "view"
20806
20971
  },
20972
+ "deviceManager.getLinkedDevices": {
20973
+ capName: "device-manager",
20974
+ capScope: "system",
20975
+ addonId: null,
20976
+ access: "view"
20977
+ },
20807
20978
  "deviceManager.getRoleDisplayDefaults": {
20808
20979
  capName: "device-manager",
20809
20980
  capScope: "system",
@@ -22358,6 +22529,12 @@ Object.freeze({
22358
22529
  addonId: null,
22359
22530
  access: "view"
22360
22531
  },
22532
+ "pipelineAnalytics.getSensorEvents": {
22533
+ capName: "pipeline-analytics",
22534
+ capScope: "device",
22535
+ addonId: null,
22536
+ access: "view"
22537
+ },
22361
22538
  "pipelineAnalytics.getTrack": {
22362
22539
  capName: "pipeline-analytics",
22363
22540
  capScope: "device",
@@ -22370,6 +22547,18 @@ Object.freeze({
22370
22547
  addonId: null,
22371
22548
  access: "view"
22372
22549
  },
22550
+ "pipelineAnalytics.listEventKinds": {
22551
+ capName: "pipeline-analytics",
22552
+ capScope: "device",
22553
+ addonId: null,
22554
+ access: "view"
22555
+ },
22556
+ "pipelineAnalytics.listRecentTracks": {
22557
+ capName: "pipeline-analytics",
22558
+ capScope: "device",
22559
+ addonId: null,
22560
+ access: "view"
22561
+ },
22373
22562
  "pipelineAnalytics.listTracks": {
22374
22563
  capName: "pipeline-analytics",
22375
22564
  capScope: "device",
@@ -25570,7 +25759,7 @@ function entryForRef(ref) {
25570
25759
  };
25571
25760
  }
25572
25761
  //#endregion
25573
- //#region ../system/dist/model-download-service-C-IHWnXx.mjs
25762
+ //#region ../system/dist/model-download-service-Cp9f4dk6.mjs
25574
25763
  function isNonEmptyFile(filePath) {
25575
25764
  return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
25576
25765
  }
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.5",
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",