@camstack/addon-ai 0.4.4 → 0.4.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.
package/dist/addon.js CHANGED
@@ -11021,6 +11021,8 @@ var QueryFilterSchema = object({
11021
11021
  where: record(string(), unknown()).optional(),
11022
11022
  whereIn: record(string(), array(unknown())).optional(),
11023
11023
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
11024
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
11025
+ whereNot: record(string(), unknown()).optional(),
11024
11026
  orderBy: object({
11025
11027
  field: string(),
11026
11028
  direction: _enum(["asc", "desc"])
@@ -11040,7 +11042,8 @@ var QueryFilterSchema = object({
11040
11042
  var MutationFilterSchema = object({
11041
11043
  where: record(string(), unknown()).optional(),
11042
11044
  whereIn: record(string(), array(unknown())).optional(),
11043
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional()
11045
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
11046
+ whereNot: record(string(), unknown()).optional()
11044
11047
  });
11045
11048
  /** A single stored record: `{ id, data }`. */
11046
11049
  var SettingsRecordSchema = object({
@@ -16997,7 +17000,10 @@ var RecentTracksQueryInput = object({
16997
17000
  * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
16998
17001
  cursor: string().optional(),
16999
17002
  /** See {@link TrackProjectionSchema}. Default `full`. */
17000
- projection: TrackProjectionSchema.optional()
17003
+ projection: TrackProjectionSchema.optional(),
17004
+ /** Include stationary-promoted rows (parked objects). Default false: the
17005
+ * feed lists passages; parking records live on the stationary registry. */
17006
+ includeStationary: boolean().optional()
17001
17007
  });
17002
17008
  var RecentTracksPageSchema = object({
17003
17009
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -17215,7 +17221,11 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
17215
17221
  zone: TrackZoneFilterSchema.optional(),
17216
17222
  /** See {@link TrackProjectionSchema}. Default `full` (backward
17217
17223
  * compatible — omitting the field keeps today's exact behaviour). */
17218
- projection: TrackProjectionSchema.optional()
17224
+ projection: TrackProjectionSchema.optional(),
17225
+ /** Include stationary-promoted rows (parked objects handed to the
17226
+ * stationary registry). Default false: the timeline lists passages,
17227
+ * not parking records (operator decision, 2026-08-15). */
17228
+ includeStationary: boolean().optional()
17219
17229
  }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number$1() }), _void(), {
17220
17230
  kind: "mutation",
17221
17231
  auth: "admin"
@@ -17379,11 +17389,16 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
17379
17389
  auth: "admin"
17380
17390
  }), method(object({
17381
17391
  eventId: string(),
17382
- kind: MediaFileKindEnum.optional()
17392
+ kind: MediaFileKindEnum.optional(),
17393
+ deviceId: number$1()
17394
+ }), array(MediaFileSchema).readonly()), method(object({
17395
+ trackId: string(),
17396
+ kinds: array(MediaFileKindEnum).optional(),
17397
+ deviceId: number$1()
17383
17398
  }), array(MediaFileSchema).readonly()), method(object({
17384
17399
  trackId: string(),
17385
- kinds: array(MediaFileKindEnum).optional()
17386
- }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
17400
+ deviceId: number$1()
17401
+ }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
17387
17402
  kind: "mutation",
17388
17403
  auth: "admin"
17389
17404
  }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
@@ -19362,7 +19377,16 @@ targets: array(object({
19362
19377
  /** A sleeping battery camera: the frame is deliberately stale and will
19363
19378
  * NOT refresh in the background. A surface should say so rather than
19364
19379
  * present it as current. */
19365
- sleeping: boolean()
19380
+ sleeping: boolean(),
19381
+ /** Current device state rendered over the cached frame. State images
19382
+ * remain authoritative even when their photographic background is
19383
+ * old; null means the link must carry a current camera frame. */
19384
+ stateReason: _enum([
19385
+ "disabled",
19386
+ "sleeping",
19387
+ "unreachable",
19388
+ "waking"
19389
+ ]).nullable()
19366
19390
  })));
19367
19391
  /**
19368
19392
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -20884,6 +20908,25 @@ var BatteryStatusSchema = object({
20884
20908
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
20885
20909
  lastUpdated: number$1(),
20886
20910
  /**
20911
+ * Ms epoch of the last time the device PROVED it was reachable — a
20912
+ * completed firmware round-trip, an observed wake, or an inbound push
20913
+ * (firmware event, email). `0`/absent = never since this slice was born.
20914
+ *
20915
+ * This is the ONLY input that separates "asleep" from "gone", and it is
20916
+ * fed exclusively by PASSIVE signals: nothing may write it by reaching
20917
+ * for the radio, because a poll that confirms reachability is the same
20918
+ * poll that drains the battery. See {@link deriveBatteryPresence} — the
20919
+ * single derivation every consumer must use; no surface computes its own.
20920
+ *
20921
+ * It is deliberately NOT a clock in the
20922
+ * `scripts/check-runtime-state-durability.ts` sense: it is the
20923
+ * observation itself, and it is the only thing a 30-hour silence is
20924
+ * visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
20925
+ * Reolink provider) so a value that means "recently" cannot cost a
20926
+ * SQLite commit per round-trip.
20927
+ */
20928
+ lastContactAt: number$1().optional(),
20929
+ /**
20887
20930
  * True when the source is a BINARY low-battery indicator (HA
20888
20931
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
20889
20932
  * charge level — `percentage` is then a coarse stand-in (100 = normal,
@@ -25161,6 +25204,16 @@ var CamStreamDescriptorSchema = object({
25161
25204
  /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
25162
25205
  metadata: record(string(), unknown()).optional()
25163
25206
  });
25207
+ object({
25208
+ /** The descriptors as last built from a real camera response. Never a guess:
25209
+ * a failed or refused build writes NOTHING, so a restored catalog is always
25210
+ * one the camera itself once produced. */
25211
+ descriptors: array(CamStreamDescriptorSchema),
25212
+ /** Ms epoch of the build that produced {@link descriptors}. Lets the wake
25213
+ * path decide whether the camera's own awake window is worth spending on a
25214
+ * re-read. */
25215
+ lastFetchedAt: number$1()
25216
+ });
25164
25217
  DeviceType.Camera, method(object({ deviceId: number$1().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly());
25165
25218
  /** One of the camera's stream profiles. */
25166
25219
  var StreamProfileSchema = _enum([
@@ -32591,6 +32644,11 @@ Object.freeze({
32591
32644
  form: "single",
32592
32645
  optional: false
32593
32646
  }],
32647
+ "pipelineAnalytics.getEventMedia": [{
32648
+ name: "deviceId",
32649
+ form: "single",
32650
+ optional: false
32651
+ }],
32594
32652
  "pipelineAnalytics.getKeyEvents": [{
32595
32653
  name: "deviceId",
32596
32654
  form: "single",
@@ -32621,6 +32679,11 @@ Object.freeze({
32621
32679
  form: "single",
32622
32680
  optional: false
32623
32681
  }],
32682
+ "pipelineAnalytics.getTrackMedia": [{
32683
+ name: "deviceId",
32684
+ form: "single",
32685
+ optional: false
32686
+ }],
32624
32687
  "pipelineAnalytics.getTrainingExportSummary": [{
32625
32688
  name: "deviceIds",
32626
32689
  form: "array",
@@ -32656,6 +32719,11 @@ Object.freeze({
32656
32719
  form: "array",
32657
32720
  optional: true
32658
32721
  }],
32722
+ "pipelineAnalytics.listTrackMedia": [{
32723
+ name: "deviceId",
32724
+ form: "single",
32725
+ optional: false
32726
+ }],
32659
32727
  "pipelineAnalytics.listTracks": [{
32660
32728
  name: "deviceId",
32661
32729
  form: "single",
@@ -33096,6 +33164,12 @@ Object.freeze({
33096
33164
  form: "single",
33097
33165
  optional: false
33098
33166
  }],
33167
+ "snapshot.getSnapshotLinks": [{
33168
+ name: "targets",
33169
+ form: "object-array",
33170
+ optional: false,
33171
+ itemField: "deviceId"
33172
+ }],
33099
33173
  "snapshot.getSnapshotOverview": [{
33100
33174
  name: "deviceIds",
33101
33175
  form: "array",
@@ -72400,7 +72474,9 @@ var TestChatImageRefSchema = discriminatedUnion("kind", [object({
72400
72474
  deviceId: number$1().int().positive()
72401
72475
  }), object({
72402
72476
  kind: literal("track"),
72403
- trackId: string().min(1)
72477
+ trackId: string().min(1),
72478
+ /** Owning device — `getTrackMedia` requires it as the read's scope. */
72479
+ deviceId: number$1().int().positive()
72404
72480
  })]);
72405
72481
  /**
72406
72482
  * `.strict()`, and it is load-bearing.
@@ -72626,7 +72702,7 @@ async function resolveImage(deps, ref) {
72626
72702
  }
72627
72703
  let rows;
72628
72704
  try {
72629
- rows = await deps.getTrackMedia(ref.trackId, TRACK_MEDIA_PREFERENCE);
72705
+ rows = await deps.getTrackMedia(ref.trackId, TRACK_MEDIA_PREFERENCE, ref.deviceId);
72630
72706
  } catch (cause) {
72631
72707
  const message = cause instanceof Error ? cause.message : String(cause);
72632
72708
  deps.logger.warn("ai test chat: track media read failed — turn dropped", { meta: {
@@ -73221,9 +73297,10 @@ var AiAddon = class extends BaseAddon {
73221
73297
  });
73222
73298
  },
73223
73299
  getSnapshot: (deviceId) => api.snapshot.getSnapshot.query({ deviceId }),
73224
- getTrackMedia: (trackId, kinds) => api.pipelineAnalytics.getTrackMedia.query({
73300
+ getTrackMedia: (trackId, kinds, deviceId) => api.pipelineAnalytics.getTrackMedia.query({
73225
73301
  trackId,
73226
- kinds: [...kinds]
73302
+ kinds: [...kinds],
73303
+ deviceId
73227
73304
  }),
73228
73305
  recordUsage: (row) => usage.record(row),
73229
73306
  logger,
package/dist/addon.mjs CHANGED
@@ -11047,6 +11047,8 @@ var QueryFilterSchema = object({
11047
11047
  where: record(string(), unknown()).optional(),
11048
11048
  whereIn: record(string(), array(unknown())).optional(),
11049
11049
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
11050
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
11051
+ whereNot: record(string(), unknown()).optional(),
11050
11052
  orderBy: object({
11051
11053
  field: string(),
11052
11054
  direction: _enum(["asc", "desc"])
@@ -11066,7 +11068,8 @@ var QueryFilterSchema = object({
11066
11068
  var MutationFilterSchema = object({
11067
11069
  where: record(string(), unknown()).optional(),
11068
11070
  whereIn: record(string(), array(unknown())).optional(),
11069
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional()
11071
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
11072
+ whereNot: record(string(), unknown()).optional()
11070
11073
  });
11071
11074
  /** A single stored record: `{ id, data }`. */
11072
11075
  var SettingsRecordSchema = object({
@@ -17023,7 +17026,10 @@ var RecentTracksQueryInput = object({
17023
17026
  * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
17024
17027
  cursor: string().optional(),
17025
17028
  /** See {@link TrackProjectionSchema}. Default `full`. */
17026
- projection: TrackProjectionSchema.optional()
17029
+ projection: TrackProjectionSchema.optional(),
17030
+ /** Include stationary-promoted rows (parked objects). Default false: the
17031
+ * feed lists passages; parking records live on the stationary registry. */
17032
+ includeStationary: boolean().optional()
17027
17033
  });
17028
17034
  var RecentTracksPageSchema = object({
17029
17035
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -17241,7 +17247,11 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
17241
17247
  zone: TrackZoneFilterSchema.optional(),
17242
17248
  /** See {@link TrackProjectionSchema}. Default `full` (backward
17243
17249
  * compatible — omitting the field keeps today's exact behaviour). */
17244
- projection: TrackProjectionSchema.optional()
17250
+ projection: TrackProjectionSchema.optional(),
17251
+ /** Include stationary-promoted rows (parked objects handed to the
17252
+ * stationary registry). Default false: the timeline lists passages,
17253
+ * not parking records (operator decision, 2026-08-15). */
17254
+ includeStationary: boolean().optional()
17245
17255
  }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number$1() }), _void(), {
17246
17256
  kind: "mutation",
17247
17257
  auth: "admin"
@@ -17405,11 +17415,16 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
17405
17415
  auth: "admin"
17406
17416
  }), method(object({
17407
17417
  eventId: string(),
17408
- kind: MediaFileKindEnum.optional()
17418
+ kind: MediaFileKindEnum.optional(),
17419
+ deviceId: number$1()
17420
+ }), array(MediaFileSchema).readonly()), method(object({
17421
+ trackId: string(),
17422
+ kinds: array(MediaFileKindEnum).optional(),
17423
+ deviceId: number$1()
17409
17424
  }), array(MediaFileSchema).readonly()), method(object({
17410
17425
  trackId: string(),
17411
- kinds: array(MediaFileKindEnum).optional()
17412
- }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
17426
+ deviceId: number$1()
17427
+ }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
17413
17428
  kind: "mutation",
17414
17429
  auth: "admin"
17415
17430
  }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
@@ -19388,7 +19403,16 @@ targets: array(object({
19388
19403
  /** A sleeping battery camera: the frame is deliberately stale and will
19389
19404
  * NOT refresh in the background. A surface should say so rather than
19390
19405
  * present it as current. */
19391
- sleeping: boolean()
19406
+ sleeping: boolean(),
19407
+ /** Current device state rendered over the cached frame. State images
19408
+ * remain authoritative even when their photographic background is
19409
+ * old; null means the link must carry a current camera frame. */
19410
+ stateReason: _enum([
19411
+ "disabled",
19412
+ "sleeping",
19413
+ "unreachable",
19414
+ "waking"
19415
+ ]).nullable()
19392
19416
  })));
19393
19417
  /**
19394
19418
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -20910,6 +20934,25 @@ var BatteryStatusSchema = object({
20910
20934
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
20911
20935
  lastUpdated: number$1(),
20912
20936
  /**
20937
+ * Ms epoch of the last time the device PROVED it was reachable — a
20938
+ * completed firmware round-trip, an observed wake, or an inbound push
20939
+ * (firmware event, email). `0`/absent = never since this slice was born.
20940
+ *
20941
+ * This is the ONLY input that separates "asleep" from "gone", and it is
20942
+ * fed exclusively by PASSIVE signals: nothing may write it by reaching
20943
+ * for the radio, because a poll that confirms reachability is the same
20944
+ * poll that drains the battery. See {@link deriveBatteryPresence} — the
20945
+ * single derivation every consumer must use; no surface computes its own.
20946
+ *
20947
+ * It is deliberately NOT a clock in the
20948
+ * `scripts/check-runtime-state-durability.ts` sense: it is the
20949
+ * observation itself, and it is the only thing a 30-hour silence is
20950
+ * visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
20951
+ * Reolink provider) so a value that means "recently" cannot cost a
20952
+ * SQLite commit per round-trip.
20953
+ */
20954
+ lastContactAt: number$1().optional(),
20955
+ /**
20913
20956
  * True when the source is a BINARY low-battery indicator (HA
20914
20957
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
20915
20958
  * charge level — `percentage` is then a coarse stand-in (100 = normal,
@@ -25187,6 +25230,16 @@ var CamStreamDescriptorSchema = object({
25187
25230
  /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
25188
25231
  metadata: record(string(), unknown()).optional()
25189
25232
  });
25233
+ object({
25234
+ /** The descriptors as last built from a real camera response. Never a guess:
25235
+ * a failed or refused build writes NOTHING, so a restored catalog is always
25236
+ * one the camera itself once produced. */
25237
+ descriptors: array(CamStreamDescriptorSchema),
25238
+ /** Ms epoch of the build that produced {@link descriptors}. Lets the wake
25239
+ * path decide whether the camera's own awake window is worth spending on a
25240
+ * re-read. */
25241
+ lastFetchedAt: number$1()
25242
+ });
25190
25243
  DeviceType.Camera, method(object({ deviceId: number$1().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly());
25191
25244
  /** One of the camera's stream profiles. */
25192
25245
  var StreamProfileSchema = _enum([
@@ -32617,6 +32670,11 @@ Object.freeze({
32617
32670
  form: "single",
32618
32671
  optional: false
32619
32672
  }],
32673
+ "pipelineAnalytics.getEventMedia": [{
32674
+ name: "deviceId",
32675
+ form: "single",
32676
+ optional: false
32677
+ }],
32620
32678
  "pipelineAnalytics.getKeyEvents": [{
32621
32679
  name: "deviceId",
32622
32680
  form: "single",
@@ -32647,6 +32705,11 @@ Object.freeze({
32647
32705
  form: "single",
32648
32706
  optional: false
32649
32707
  }],
32708
+ "pipelineAnalytics.getTrackMedia": [{
32709
+ name: "deviceId",
32710
+ form: "single",
32711
+ optional: false
32712
+ }],
32650
32713
  "pipelineAnalytics.getTrainingExportSummary": [{
32651
32714
  name: "deviceIds",
32652
32715
  form: "array",
@@ -32682,6 +32745,11 @@ Object.freeze({
32682
32745
  form: "array",
32683
32746
  optional: true
32684
32747
  }],
32748
+ "pipelineAnalytics.listTrackMedia": [{
32749
+ name: "deviceId",
32750
+ form: "single",
32751
+ optional: false
32752
+ }],
32685
32753
  "pipelineAnalytics.listTracks": [{
32686
32754
  name: "deviceId",
32687
32755
  form: "single",
@@ -33122,6 +33190,12 @@ Object.freeze({
33122
33190
  form: "single",
33123
33191
  optional: false
33124
33192
  }],
33193
+ "snapshot.getSnapshotLinks": [{
33194
+ name: "targets",
33195
+ form: "object-array",
33196
+ optional: false,
33197
+ itemField: "deviceId"
33198
+ }],
33125
33199
  "snapshot.getSnapshotOverview": [{
33126
33200
  name: "deviceIds",
33127
33201
  form: "array",
@@ -72426,7 +72500,9 @@ var TestChatImageRefSchema = discriminatedUnion("kind", [object({
72426
72500
  deviceId: number$1().int().positive()
72427
72501
  }), object({
72428
72502
  kind: literal("track"),
72429
- trackId: string().min(1)
72503
+ trackId: string().min(1),
72504
+ /** Owning device — `getTrackMedia` requires it as the read's scope. */
72505
+ deviceId: number$1().int().positive()
72430
72506
  })]);
72431
72507
  /**
72432
72508
  * `.strict()`, and it is load-bearing.
@@ -72652,7 +72728,7 @@ async function resolveImage(deps, ref) {
72652
72728
  }
72653
72729
  let rows;
72654
72730
  try {
72655
- rows = await deps.getTrackMedia(ref.trackId, TRACK_MEDIA_PREFERENCE);
72731
+ rows = await deps.getTrackMedia(ref.trackId, TRACK_MEDIA_PREFERENCE, ref.deviceId);
72656
72732
  } catch (cause) {
72657
72733
  const message = cause instanceof Error ? cause.message : String(cause);
72658
72734
  deps.logger.warn("ai test chat: track media read failed — turn dropped", { meta: {
@@ -73247,9 +73323,10 @@ var AiAddon = class extends BaseAddon {
73247
73323
  });
73248
73324
  },
73249
73325
  getSnapshot: (deviceId) => api.snapshot.getSnapshot.query({ deviceId }),
73250
- getTrackMedia: (trackId, kinds) => api.pipelineAnalytics.getTrackMedia.query({
73326
+ getTrackMedia: (trackId, kinds, deviceId) => api.pipelineAnalytics.getTrackMedia.query({
73251
73327
  trackId,
73252
- kinds: [...kinds]
73328
+ kinds: [...kinds],
73329
+ deviceId
73253
73330
  }),
73254
73331
  recordUsage: (row) => usage.record(row),
73255
73332
  logger,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.4.4",
3
+ "version": "0.4.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",