@camstack/addon-ai 0.4.9 → 0.4.11

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 +136 -22
  2. package/dist/addon.mjs +136 -22
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -15,7 +15,7 @@ let node_zlib = require("node:zlib");
15
15
  let node_fs_promises = require("node:fs/promises");
16
16
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
17
17
  let node_child_process = require("node:child_process");
18
- //#region ../types/dist/event-category-C0lyLd5U.mjs
18
+ //#region ../types/dist/event-category-XfKNtfCc.mjs
19
19
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
20
20
  EventCategory["SystemBoot"] = "system.boot";
21
21
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -54,6 +54,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
54
54
  EventCategory["AddonInstalled"] = "addon.installed";
55
55
  EventCategory["AddonUninstalled"] = "addon.uninstalled";
56
56
  EventCategory["AddonCrashed"] = "addon.crashed";
57
+ /**
58
+ * A RUNNER the D6 crash circuit-breaker gave up on — terminal.
59
+ *
60
+ * `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
61
+ * respawned"); this is the one that never resolves itself. Emitted exactly
62
+ * once per trip, by `process-service.ts`, carrying the node, the runner, the
63
+ * addons it hosted and the crash count that tripped the breaker.
64
+ *
65
+ * It exists because a runner marked terminally `failed` used to be SILENT:
66
+ * on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
67
+ * errors, the breaker stopped respawning it (correctly), `/health` kept
68
+ * returning 200, and fleet-wide recording was gone for 3h15 before the
69
+ * operator's phone told him. The Notification Center's system-event intake
70
+ * consumes this category and maps it to the `addon-crash-loop` kind.
71
+ */
72
+ EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
57
73
  EventCategory["AddonError"] = "addon.error";
58
74
  EventCategory["AddonPageReady"] = "addon.page-ready";
59
75
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -7806,6 +7822,12 @@ var RecordingConfigSchema = object({
7806
7822
  /** DERIVED summary of `bands`, stamped by the recorder on every save.
7807
7823
  * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7808
7824
  mode: RecordingStorageModeSchema.optional(),
7825
+ /**
7826
+ * Which assigned broker slots to record. Absent / empty = {@link
7827
+ * DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
7828
+ * camera's currently assigned slots — never `mid` unless the operator
7829
+ * picks it, and never a slot the broker has not assigned.
7830
+ */
7809
7831
  profiles: array(CamProfileSchema).optional(),
7810
7832
  segmentSeconds: number$1().int().positive().optional(),
7811
7833
  /**
@@ -7886,7 +7908,11 @@ var RelocateFootageInputSchema = object({
7886
7908
  profiles: array(string()).optional(),
7887
7909
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7888
7910
  * never allowed to starve live writers. */
7889
- throttleMbps: number$1().min(1).max(1e3).optional()
7911
+ throttleMbps: number$1().min(1).max(1e3).optional(),
7912
+ /** Move only segments whose startMs is >= this. Absent = the whole source
7913
+ * pile. Used when a full drain is too expensive and the operator only
7914
+ * wants the recent window on the new disk. */
7915
+ sinceMs: number$1().int().optional()
7890
7916
  });
7891
7917
  /** Internal, lease-scoped participant operation. It is intentionally separate
7892
7918
  * from persistent recording settings: a migration never changes
@@ -14530,6 +14556,7 @@ var NcSystemEventKindSchema = _enum([
14530
14556
  "node-offline",
14531
14557
  "node-inference-unavailable",
14532
14558
  "detection-blind",
14559
+ "addon-crash-loop",
14533
14560
  "addon-update-available",
14534
14561
  "server-update-available",
14535
14562
  "alarm-triggered",
@@ -17103,6 +17130,12 @@ var TrackCascadeCountsSchema = object({
17103
17130
  /** Per-track CLIP search vectors removed (best-effort). */
17104
17131
  embeddings: number$1().int()
17105
17132
  });
17133
+ /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17134
+ var DiskReconcileCountsSchema = object({
17135
+ mediaDropped: number$1().int(),
17136
+ tracks: number$1().int(),
17137
+ events: number$1().int()
17138
+ });
17106
17139
  /** Event-store footprint for one camera. */
17107
17140
  var EventStoreDeviceFootprintSchema = object({
17108
17141
  deviceId: number$1(),
@@ -17279,6 +17312,9 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
17279
17312
  }), method(object({ deviceId: number$1() }), TrackCascadeCountsSchema, {
17280
17313
  kind: "mutation",
17281
17314
  auth: "admin"
17315
+ }), method(object({ deviceId: number$1() }), DiskReconcileCountsSchema, {
17316
+ kind: "mutation",
17317
+ auth: "admin"
17282
17318
  }), method(object({
17283
17319
  deviceId: number$1(),
17284
17320
  trackIds: array(string()).min(1)
@@ -18789,6 +18825,24 @@ var CameraStatusDegradationSchema = object({
18789
18825
  *
18790
18826
  * See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
18791
18827
  */
18828
+ var DiskReconcileJobSchema = object({
18829
+ state: _enum([
18830
+ "idle",
18831
+ "running",
18832
+ "done",
18833
+ "error"
18834
+ ]),
18835
+ total: number$1().int().nonnegative(),
18836
+ completed: number$1().int().nonnegative(),
18837
+ currentDeviceId: number$1().int().nullable(),
18838
+ failed: array(number$1().int()).readonly(),
18839
+ mediaDropped: number$1().int().nonnegative(),
18840
+ tracks: number$1().int().nonnegative(),
18841
+ events: number$1().int().nonnegative(),
18842
+ startedAtMs: number$1().int().nullable(),
18843
+ finishedAtMs: number$1().int().nullable(),
18844
+ error: string().nullable()
18845
+ });
18792
18846
  var CameraStatusSchema = object({
18793
18847
  deviceId: number$1(),
18794
18848
  assignment: CameraAssignmentStatusSchema,
@@ -19020,7 +19074,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
19020
19074
  }), CameraSwitchGroupSchema, {
19021
19075
  kind: "mutation",
19022
19076
  auth: "admin"
19023
- }), method(object({ deviceId: number$1() }), CameraStatusSchema), method(object({ deviceIds: array(number$1()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
19077
+ }), method(object({ deviceId: number$1() }), CameraStatusSchema), method(object({ deviceIds: array(number$1()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
19078
+ kind: "mutation",
19079
+ auth: "admin"
19080
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
19024
19081
  name: string(),
19025
19082
  description: string().optional(),
19026
19083
  config: CameraPipelineConfigSchema
@@ -20093,20 +20150,6 @@ var VectorStatsResultSchema = object({
20093
20150
  exact: boolean()
20094
20151
  });
20095
20152
  method(VectorDeclareIndexInputSchema, _void(), { kind: "mutation" }), method(VectorUpsertInputSchema, VectorUpsertResultSchema, { kind: "mutation" }), method(VectorQueryInputSchema, VectorQueryResultSchema), method(VectorGetInputSchema, VectorGetResultSchema), method(VectorDeleteInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorStatsInputSchema, VectorStatsResultSchema);
20096
- /**
20097
- * `videoclips` — the unified, navigable-clip surface for a camera.
20098
- *
20099
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
20100
- * provider per device, substitutable. The DEFAULT provider (registered by
20101
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
20102
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
20103
- * is a time-WINDOW over existing footage, never a separate file. A camera that
20104
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
20105
- * wrapper provider for its device and serve its own clip catalog + URLs.
20106
- *
20107
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
20108
- * temporal overlap, so the API never decides `continuous` vs `events`.
20109
- */
20110
20153
  var ClipSchema = object({
20111
20154
  /** Opaque, provider-namespaced id. The default provider encodes the time
20112
20155
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -20124,6 +20167,14 @@ var ClipSchema = object({
20124
20167
  endMs: number$1()
20125
20168
  }),
20126
20169
  /**
20170
+ * Distinct object classes attached to this visit (`person`, `car`, …),
20171
+ * dominant first, capped at {@link MAX_CLIP_LABELS}. The ribbon renders these
20172
+ * instead of the bare kind — "Object" tells the operator nothing a colour bar
20173
+ * did not. Absent (never empty) when the visit attached no classified object
20174
+ * event, so a motion/audio-only visit keeps its kind label.
20175
+ */
20176
+ labels: array(string()).max(3).optional(),
20177
+ /**
20127
20178
  * Lazy thumbnail URL, never inlined.
20128
20179
  *
20129
20180
  * Recording-derived clips (events-mode keep-window, and the prepared
@@ -20132,12 +20183,41 @@ var ClipSchema = object({
20132
20183
  * of the event that owns `kind` (object > motion > audio). Do not extract
20133
20184
  * a keyframe from the recorded segments. Other providers (onboard, HKSV)
20134
20185
  * mint their own stills.
20186
+ *
20187
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
20188
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
20189
+ * Absent is meaningful — "this visit has no event still" — never "we did not
20190
+ * look". Stamping it from a URL template made 35% of one camera's clips point
20191
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
20192
+ * A read that FAILS drops the claim; it never invents it.
20135
20193
  */
20136
20194
  thumbnail: string().optional(),
20137
- /** Analytics event ids that overlap this visit. Empty on footage-only clips.
20138
- * The default provider's visit grain puts many motion heartbeats on one clip
20139
- * instead of minting one clip per marker. */
20195
+ /**
20196
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
20197
+ * can be decoded. Present whenever the visit came from recorded availability;
20198
+ * absent on a per-event padded window (there is no footage to promise).
20199
+ *
20200
+ * This is not a thumbnail and not a second byte path: it is the argument to
20201
+ * the recorder's existing still route. The surface — never the provider —
20202
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
20203
+ * contiguous range, not of the visit: a visit spans its holes by
20204
+ * construction, so a naive midpoint lands in dead air.
20205
+ */
20206
+ stillAtMs: number$1().optional(),
20207
+ /**
20208
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
20209
+ * first within kind (object → motion → audio), capped at
20210
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
20211
+ *
20212
+ * Bounded because it is not a payload the surface pages through: one visit on
20213
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
20214
+ * camera-day. Read {@link eventCount} for the true total.
20215
+ */
20140
20216
  eventIds: array(string()).optional(),
20217
+ /** How many analytics events actually overlap this visit. Differs from
20218
+ * `eventIds.length` exactly when the sample was capped — so a truncated
20219
+ * list is never mistaken for a quiet visit. */
20220
+ eventCount: number$1().int().nonnegative().optional(),
20141
20221
  /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
20142
20222
  * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
20143
20223
  * bar keeps showing them via `recording.getAvailability`. */
@@ -20608,7 +20688,14 @@ var SearchResultSchema = object({
20608
20688
  });
20609
20689
  var AutoUpdateSettingsSchema = object({
20610
20690
  channel: ChannelSchema,
20611
- intervalSeconds: number$1()
20691
+ intervalSeconds: number$1(),
20692
+ /**
20693
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
20694
+ * `channel`: the poller runs while auto-apply is `off`, because being told
20695
+ * about a publish and installing it are different decisions. Clamped
20696
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
20697
+ */
20698
+ updateCheckIntervalSeconds: number$1()
20612
20699
  });
20613
20700
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
20614
20701
  var RestartAddonResultSchema = unknown();
@@ -20749,7 +20836,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
20749
20836
  auth: "admin"
20750
20837
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
20751
20838
  channel: ChannelSchema,
20752
- intervalSeconds: number$1().min(300).max(86400).optional()
20839
+ intervalSeconds: number$1().min(300).max(86400).optional(),
20840
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
20841
+ updateCheckIntervalSeconds: number$1().min(900).max(604800).optional()
20753
20842
  }), unknown(), {
20754
20843
  kind: "mutation",
20755
20844
  auth: "admin"
@@ -29414,6 +29503,12 @@ Object.freeze({
29414
29503
  addonId: null,
29415
29504
  access: "create"
29416
29505
  },
29506
+ "pipelineAnalytics.reconcileFromDisk": {
29507
+ capName: "pipeline-analytics",
29508
+ capScope: "device",
29509
+ addonId: null,
29510
+ access: "create"
29511
+ },
29417
29512
  "pipelineAnalytics.refreshStorageLocationsForMigration": {
29418
29513
  capName: "pipeline-analytics",
29419
29514
  capScope: "device",
@@ -29816,6 +29911,12 @@ Object.freeze({
29816
29911
  addonId: null,
29817
29912
  access: "view"
29818
29913
  },
29914
+ "pipelineOrchestrator.getReconcileFromDiskStatus": {
29915
+ capName: "pipeline-orchestrator",
29916
+ capScope: "system",
29917
+ addonId: null,
29918
+ access: "view"
29919
+ },
29819
29920
  "pipelineOrchestrator.listAgentSettings": {
29820
29921
  capName: "pipeline-orchestrator",
29821
29922
  capScope: "system",
@@ -29840,6 +29941,12 @@ Object.freeze({
29840
29941
  addonId: null,
29841
29942
  access: "create"
29842
29943
  },
29944
+ "pipelineOrchestrator.reconcileFromDisk": {
29945
+ capName: "pipeline-orchestrator",
29946
+ capScope: "system",
29947
+ addonId: null,
29948
+ access: "create"
29949
+ },
29843
29950
  "pipelineOrchestrator.removeAgentSettings": {
29844
29951
  capName: "pipeline-orchestrator",
29845
29952
  capScope: "system",
@@ -32831,6 +32938,11 @@ Object.freeze({
32831
32938
  form: "single",
32832
32939
  optional: true
32833
32940
  }],
32941
+ "pipelineAnalytics.reconcileFromDisk": [{
32942
+ name: "deviceId",
32943
+ form: "single",
32944
+ optional: false
32945
+ }],
32834
32946
  "pipelineAnalytics.restageRetrainTrack": [{
32835
32947
  name: "deviceId",
32836
32948
  form: "single",
@@ -33896,6 +34008,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
33896
34008
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
33897
34009
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
33898
34010
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
34011
+ var MB = 1024 * 1024;
34012
+ 1024 * MB, 3072 * MB;
33899
34013
  //#endregion
33900
34014
  //#region ../../node_modules/@ai-sdk/provider/dist/index.js
33901
34015
  var marker$2 = "vercel.ai.error";
package/dist/addon.mjs CHANGED
@@ -41,7 +41,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
41
41
  }) : target, mod));
42
42
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
43
43
  //#endregion
44
- //#region ../types/dist/event-category-C0lyLd5U.mjs
44
+ //#region ../types/dist/event-category-XfKNtfCc.mjs
45
45
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
46
46
  EventCategory["SystemBoot"] = "system.boot";
47
47
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -80,6 +80,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
80
80
  EventCategory["AddonInstalled"] = "addon.installed";
81
81
  EventCategory["AddonUninstalled"] = "addon.uninstalled";
82
82
  EventCategory["AddonCrashed"] = "addon.crashed";
83
+ /**
84
+ * A RUNNER the D6 crash circuit-breaker gave up on — terminal.
85
+ *
86
+ * `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
87
+ * respawned"); this is the one that never resolves itself. Emitted exactly
88
+ * once per trip, by `process-service.ts`, carrying the node, the runner, the
89
+ * addons it hosted and the crash count that tripped the breaker.
90
+ *
91
+ * It exists because a runner marked terminally `failed` used to be SILENT:
92
+ * on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
93
+ * errors, the breaker stopped respawning it (correctly), `/health` kept
94
+ * returning 200, and fleet-wide recording was gone for 3h15 before the
95
+ * operator's phone told him. The Notification Center's system-event intake
96
+ * consumes this category and maps it to the `addon-crash-loop` kind.
97
+ */
98
+ EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
83
99
  EventCategory["AddonError"] = "addon.error";
84
100
  EventCategory["AddonPageReady"] = "addon.page-ready";
85
101
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -7832,6 +7848,12 @@ var RecordingConfigSchema = object({
7832
7848
  /** DERIVED summary of `bands`, stamped by the recorder on every save.
7833
7849
  * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7834
7850
  mode: RecordingStorageModeSchema.optional(),
7851
+ /**
7852
+ * Which assigned broker slots to record. Absent / empty = {@link
7853
+ * DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
7854
+ * camera's currently assigned slots — never `mid` unless the operator
7855
+ * picks it, and never a slot the broker has not assigned.
7856
+ */
7835
7857
  profiles: array(CamProfileSchema).optional(),
7836
7858
  segmentSeconds: number$1().int().positive().optional(),
7837
7859
  /**
@@ -7912,7 +7934,11 @@ var RelocateFootageInputSchema = object({
7912
7934
  profiles: array(string()).optional(),
7913
7935
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7914
7936
  * never allowed to starve live writers. */
7915
- throttleMbps: number$1().min(1).max(1e3).optional()
7937
+ throttleMbps: number$1().min(1).max(1e3).optional(),
7938
+ /** Move only segments whose startMs is >= this. Absent = the whole source
7939
+ * pile. Used when a full drain is too expensive and the operator only
7940
+ * wants the recent window on the new disk. */
7941
+ sinceMs: number$1().int().optional()
7916
7942
  });
7917
7943
  /** Internal, lease-scoped participant operation. It is intentionally separate
7918
7944
  * from persistent recording settings: a migration never changes
@@ -14556,6 +14582,7 @@ var NcSystemEventKindSchema = _enum([
14556
14582
  "node-offline",
14557
14583
  "node-inference-unavailable",
14558
14584
  "detection-blind",
14585
+ "addon-crash-loop",
14559
14586
  "addon-update-available",
14560
14587
  "server-update-available",
14561
14588
  "alarm-triggered",
@@ -17129,6 +17156,12 @@ var TrackCascadeCountsSchema = object({
17129
17156
  /** Per-track CLIP search vectors removed (best-effort). */
17130
17157
  embeddings: number$1().int()
17131
17158
  });
17159
+ /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17160
+ var DiskReconcileCountsSchema = object({
17161
+ mediaDropped: number$1().int(),
17162
+ tracks: number$1().int(),
17163
+ events: number$1().int()
17164
+ });
17132
17165
  /** Event-store footprint for one camera. */
17133
17166
  var EventStoreDeviceFootprintSchema = object({
17134
17167
  deviceId: number$1(),
@@ -17305,6 +17338,9 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
17305
17338
  }), method(object({ deviceId: number$1() }), TrackCascadeCountsSchema, {
17306
17339
  kind: "mutation",
17307
17340
  auth: "admin"
17341
+ }), method(object({ deviceId: number$1() }), DiskReconcileCountsSchema, {
17342
+ kind: "mutation",
17343
+ auth: "admin"
17308
17344
  }), method(object({
17309
17345
  deviceId: number$1(),
17310
17346
  trackIds: array(string()).min(1)
@@ -18815,6 +18851,24 @@ var CameraStatusDegradationSchema = object({
18815
18851
  *
18816
18852
  * See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
18817
18853
  */
18854
+ var DiskReconcileJobSchema = object({
18855
+ state: _enum([
18856
+ "idle",
18857
+ "running",
18858
+ "done",
18859
+ "error"
18860
+ ]),
18861
+ total: number$1().int().nonnegative(),
18862
+ completed: number$1().int().nonnegative(),
18863
+ currentDeviceId: number$1().int().nullable(),
18864
+ failed: array(number$1().int()).readonly(),
18865
+ mediaDropped: number$1().int().nonnegative(),
18866
+ tracks: number$1().int().nonnegative(),
18867
+ events: number$1().int().nonnegative(),
18868
+ startedAtMs: number$1().int().nullable(),
18869
+ finishedAtMs: number$1().int().nullable(),
18870
+ error: string().nullable()
18871
+ });
18818
18872
  var CameraStatusSchema = object({
18819
18873
  deviceId: number$1(),
18820
18874
  assignment: CameraAssignmentStatusSchema,
@@ -19046,7 +19100,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
19046
19100
  }), CameraSwitchGroupSchema, {
19047
19101
  kind: "mutation",
19048
19102
  auth: "admin"
19049
- }), method(object({ deviceId: number$1() }), CameraStatusSchema), method(object({ deviceIds: array(number$1()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
19103
+ }), method(object({ deviceId: number$1() }), CameraStatusSchema), method(object({ deviceIds: array(number$1()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
19104
+ kind: "mutation",
19105
+ auth: "admin"
19106
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
19050
19107
  name: string(),
19051
19108
  description: string().optional(),
19052
19109
  config: CameraPipelineConfigSchema
@@ -20119,20 +20176,6 @@ var VectorStatsResultSchema = object({
20119
20176
  exact: boolean()
20120
20177
  });
20121
20178
  method(VectorDeclareIndexInputSchema, _void(), { kind: "mutation" }), method(VectorUpsertInputSchema, VectorUpsertResultSchema, { kind: "mutation" }), method(VectorQueryInputSchema, VectorQueryResultSchema), method(VectorGetInputSchema, VectorGetResultSchema), method(VectorDeleteInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorStatsInputSchema, VectorStatsResultSchema);
20122
- /**
20123
- * `videoclips` — the unified, navigable-clip surface for a camera.
20124
- *
20125
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
20126
- * provider per device, substitutable. The DEFAULT provider (registered by
20127
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
20128
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
20129
- * is a time-WINDOW over existing footage, never a separate file. A camera that
20130
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
20131
- * wrapper provider for its device and serve its own clip catalog + URLs.
20132
- *
20133
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
20134
- * temporal overlap, so the API never decides `continuous` vs `events`.
20135
- */
20136
20179
  var ClipSchema = object({
20137
20180
  /** Opaque, provider-namespaced id. The default provider encodes the time
20138
20181
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -20150,6 +20193,14 @@ var ClipSchema = object({
20150
20193
  endMs: number$1()
20151
20194
  }),
20152
20195
  /**
20196
+ * Distinct object classes attached to this visit (`person`, `car`, …),
20197
+ * dominant first, capped at {@link MAX_CLIP_LABELS}. The ribbon renders these
20198
+ * instead of the bare kind — "Object" tells the operator nothing a colour bar
20199
+ * did not. Absent (never empty) when the visit attached no classified object
20200
+ * event, so a motion/audio-only visit keeps its kind label.
20201
+ */
20202
+ labels: array(string()).max(3).optional(),
20203
+ /**
20153
20204
  * Lazy thumbnail URL, never inlined.
20154
20205
  *
20155
20206
  * Recording-derived clips (events-mode keep-window, and the prepared
@@ -20158,12 +20209,41 @@ var ClipSchema = object({
20158
20209
  * of the event that owns `kind` (object > motion > audio). Do not extract
20159
20210
  * a keyframe from the recorded segments. Other providers (onboard, HKSV)
20160
20211
  * mint their own stills.
20212
+ *
20213
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
20214
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
20215
+ * Absent is meaningful — "this visit has no event still" — never "we did not
20216
+ * look". Stamping it from a URL template made 35% of one camera's clips point
20217
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
20218
+ * A read that FAILS drops the claim; it never invents it.
20161
20219
  */
20162
20220
  thumbnail: string().optional(),
20163
- /** Analytics event ids that overlap this visit. Empty on footage-only clips.
20164
- * The default provider's visit grain puts many motion heartbeats on one clip
20165
- * instead of minting one clip per marker. */
20221
+ /**
20222
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
20223
+ * can be decoded. Present whenever the visit came from recorded availability;
20224
+ * absent on a per-event padded window (there is no footage to promise).
20225
+ *
20226
+ * This is not a thumbnail and not a second byte path: it is the argument to
20227
+ * the recorder's existing still route. The surface — never the provider —
20228
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
20229
+ * contiguous range, not of the visit: a visit spans its holes by
20230
+ * construction, so a naive midpoint lands in dead air.
20231
+ */
20232
+ stillAtMs: number$1().optional(),
20233
+ /**
20234
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
20235
+ * first within kind (object → motion → audio), capped at
20236
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
20237
+ *
20238
+ * Bounded because it is not a payload the surface pages through: one visit on
20239
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
20240
+ * camera-day. Read {@link eventCount} for the true total.
20241
+ */
20166
20242
  eventIds: array(string()).optional(),
20243
+ /** How many analytics events actually overlap this visit. Differs from
20244
+ * `eventIds.length` exactly when the sample was capped — so a truncated
20245
+ * list is never mistaken for a quiet visit. */
20246
+ eventCount: number$1().int().nonnegative().optional(),
20167
20247
  /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
20168
20248
  * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
20169
20249
  * bar keeps showing them via `recording.getAvailability`. */
@@ -20634,7 +20714,14 @@ var SearchResultSchema = object({
20634
20714
  });
20635
20715
  var AutoUpdateSettingsSchema = object({
20636
20716
  channel: ChannelSchema,
20637
- intervalSeconds: number$1()
20717
+ intervalSeconds: number$1(),
20718
+ /**
20719
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
20720
+ * `channel`: the poller runs while auto-apply is `off`, because being told
20721
+ * about a publish and installing it are different decisions. Clamped
20722
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
20723
+ */
20724
+ updateCheckIntervalSeconds: number$1()
20638
20725
  });
20639
20726
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
20640
20727
  var RestartAddonResultSchema = unknown();
@@ -20775,7 +20862,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
20775
20862
  auth: "admin"
20776
20863
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
20777
20864
  channel: ChannelSchema,
20778
- intervalSeconds: number$1().min(300).max(86400).optional()
20865
+ intervalSeconds: number$1().min(300).max(86400).optional(),
20866
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
20867
+ updateCheckIntervalSeconds: number$1().min(900).max(604800).optional()
20779
20868
  }), unknown(), {
20780
20869
  kind: "mutation",
20781
20870
  auth: "admin"
@@ -29440,6 +29529,12 @@ Object.freeze({
29440
29529
  addonId: null,
29441
29530
  access: "create"
29442
29531
  },
29532
+ "pipelineAnalytics.reconcileFromDisk": {
29533
+ capName: "pipeline-analytics",
29534
+ capScope: "device",
29535
+ addonId: null,
29536
+ access: "create"
29537
+ },
29443
29538
  "pipelineAnalytics.refreshStorageLocationsForMigration": {
29444
29539
  capName: "pipeline-analytics",
29445
29540
  capScope: "device",
@@ -29842,6 +29937,12 @@ Object.freeze({
29842
29937
  addonId: null,
29843
29938
  access: "view"
29844
29939
  },
29940
+ "pipelineOrchestrator.getReconcileFromDiskStatus": {
29941
+ capName: "pipeline-orchestrator",
29942
+ capScope: "system",
29943
+ addonId: null,
29944
+ access: "view"
29945
+ },
29845
29946
  "pipelineOrchestrator.listAgentSettings": {
29846
29947
  capName: "pipeline-orchestrator",
29847
29948
  capScope: "system",
@@ -29866,6 +29967,12 @@ Object.freeze({
29866
29967
  addonId: null,
29867
29968
  access: "create"
29868
29969
  },
29970
+ "pipelineOrchestrator.reconcileFromDisk": {
29971
+ capName: "pipeline-orchestrator",
29972
+ capScope: "system",
29973
+ addonId: null,
29974
+ access: "create"
29975
+ },
29869
29976
  "pipelineOrchestrator.removeAgentSettings": {
29870
29977
  capName: "pipeline-orchestrator",
29871
29978
  capScope: "system",
@@ -32857,6 +32964,11 @@ Object.freeze({
32857
32964
  form: "single",
32858
32965
  optional: true
32859
32966
  }],
32967
+ "pipelineAnalytics.reconcileFromDisk": [{
32968
+ name: "deviceId",
32969
+ form: "single",
32970
+ optional: false
32971
+ }],
32860
32972
  "pipelineAnalytics.restageRetrainTrack": [{
32861
32973
  name: "deviceId",
32862
32974
  form: "single",
@@ -33922,6 +34034,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
33922
34034
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
33923
34035
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
33924
34036
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
34037
+ var MB = 1024 * 1024;
34038
+ 1024 * MB, 3072 * MB;
33925
34039
  //#endregion
33926
34040
  //#region ../../node_modules/@ai-sdk/provider/dist/index.js
33927
34041
  var marker$2 = "vercel.ai.error";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
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",