@camstack/addon-agent-ui 1.2.19 → 1.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/addon.js +187 -29
  2. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- //#region ../types/dist/event-category-Bxo5yJjt.mjs
3
+ //#region ../types/dist/event-category-XfKNtfCc.mjs
4
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
5
5
  EventCategory["SystemBoot"] = "system.boot";
6
6
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -17,9 +17,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
17
17
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
18
18
  /**
19
19
  * A newer addon or server-root package version was found by the
20
- * authoritative registry check. Emitted once per
21
- * `(target, packageName, currentVersion, latestVersion)` transition; repeated
22
- * polling of the same result is deduplicated by the checker.
20
+ * authoritative registry check. Emitted once when any observed
21
+ * `latestVersion` changes (or a package/node first appears behind);
22
+ * the payload carries the full currently-available list. Repeated
23
+ * polling of the same latests is silent.
23
24
  */
24
25
  EventCategory["UpdateAvailable"] = "update.available";
25
26
  /**
@@ -38,6 +39,22 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
38
39
  EventCategory["AddonInstalled"] = "addon.installed";
39
40
  EventCategory["AddonUninstalled"] = "addon.uninstalled";
40
41
  EventCategory["AddonCrashed"] = "addon.crashed";
42
+ /**
43
+ * A RUNNER the D6 crash circuit-breaker gave up on — terminal.
44
+ *
45
+ * `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
46
+ * respawned"); this is the one that never resolves itself. Emitted exactly
47
+ * once per trip, by `process-service.ts`, carrying the node, the runner, the
48
+ * addons it hosted and the crash count that tripped the breaker.
49
+ *
50
+ * It exists because a runner marked terminally `failed` used to be SILENT:
51
+ * on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
52
+ * errors, the breaker stopped respawning it (correctly), `/health` kept
53
+ * returning 200, and fleet-wide recording was gone for 3h15 before the
54
+ * operator's phone told him. The Notification Center's system-event intake
55
+ * consumes this category and maps it to the `addon-crash-loop` kind.
56
+ */
57
+ EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
41
58
  EventCategory["AddonError"] = "addon.error";
42
59
  EventCategory["AddonPageReady"] = "addon.page-ready";
43
60
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -7542,6 +7559,10 @@ var RecordingBandSchema = object({
7542
7559
  preBufferSec: number().min(0).optional(),
7543
7560
  postBufferSec: number().min(0).optional()
7544
7561
  });
7562
+ ({
7563
+ preBufferSec: 10,
7564
+ postBufferSec: 30
7565
+ }).postBufferSec * 1e3;
7545
7566
  /**
7546
7567
  * Per-device retention overrides. Every field is optional; an unset or `0`
7547
7568
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -7589,6 +7610,12 @@ var RecordingConfigSchema = object({
7589
7610
  /** DERIVED summary of `bands`, stamped by the recorder on every save.
7590
7611
  * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7591
7612
  mode: RecordingStorageModeSchema.optional(),
7613
+ /**
7614
+ * Which assigned broker slots to record. Absent / empty = {@link
7615
+ * DEFAULT_RECORDING_PROFILES} (`high`+`low`) intersected with the
7616
+ * camera's currently assigned slots — never `mid` unless the operator
7617
+ * picks it, and never a slot the broker has not assigned.
7618
+ */
7592
7619
  profiles: array(CamProfileSchema).optional(),
7593
7620
  segmentSeconds: number().int().positive().optional(),
7594
7621
  /**
@@ -7669,7 +7696,11 @@ var RelocateFootageInputSchema = object({
7669
7696
  profiles: array(string()).optional(),
7670
7697
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7671
7698
  * never allowed to starve live writers. */
7672
- throttleMbps: number().min(1).max(1e3).optional()
7699
+ throttleMbps: number().min(1).max(1e3).optional(),
7700
+ /** Move only segments whose startMs is >= this. Absent = the whole source
7701
+ * pile. Used when a full drain is too expensive and the operator only
7702
+ * wants the recent window on the new disk. */
7703
+ sinceMs: number().int().optional()
7673
7704
  });
7674
7705
  /** Internal, lease-scoped participant operation. It is intentionally separate
7675
7706
  * from persistent recording settings: a migration never changes
@@ -14232,6 +14263,7 @@ var NcSystemEventKindSchema = _enum([
14232
14263
  "node-offline",
14233
14264
  "node-inference-unavailable",
14234
14265
  "detection-blind",
14266
+ "addon-crash-loop",
14235
14267
  "addon-update-available",
14236
14268
  "server-update-available",
14237
14269
  "alarm-triggered",
@@ -14239,6 +14271,9 @@ var NcSystemEventKindSchema = _enum([
14239
14271
  "alarm-disarmed",
14240
14272
  "alarm-arming",
14241
14273
  "alarm-arm-refused",
14274
+ "addon-updated",
14275
+ "server-updated",
14276
+ "export-completed",
14242
14277
  "camera-online",
14243
14278
  "camera-offline",
14244
14279
  "camera-disabled",
@@ -16132,13 +16167,19 @@ var RetrainStatusSchema = _enum([
16132
16167
  * "never marked" from "already trained" must read `retrainStatus`.
16133
16168
  *
16134
16169
  * `debug` does NOT pin; it is attention, not durability.
16170
+ *
16171
+ * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
16172
+ * A favourited track is skipped by retention the same way `staging` is, but
16173
+ * it does not enter `none|staging|trained` and has no staging budget.
16135
16174
  */
16136
16175
  var TrackFlagFields = {
16137
16176
  /** Operator marked this track as training material — i.e. `retrainStatus` is
16138
16177
  * `'staging'`. */
16139
16178
  markForTrain: boolean().optional(),
16140
16179
  /** Operator marked this track for diagnostic attention. */
16141
- debug: boolean().optional()
16180
+ debug: boolean().optional(),
16181
+ /** Operator favourited this track. Pins it against pruning. */
16182
+ favourited: boolean().optional()
16142
16183
  };
16143
16184
  /**
16144
16185
  * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
@@ -16163,6 +16204,7 @@ var TrackFlagsSchema = object({
16163
16204
  trackId: string(),
16164
16205
  markForTrain: boolean(),
16165
16206
  debug: boolean(),
16207
+ favourited: boolean(),
16166
16208
  /** The lifecycle state the boolean was derived from. Required here (unlike on
16167
16209
  * a track row) because this shape is only ever produced by the write body,
16168
16210
  * which always knows it — and a surface that has just written needs to render
@@ -16795,6 +16837,12 @@ var TrackCascadeCountsSchema = object({
16795
16837
  /** Per-track CLIP search vectors removed (best-effort). */
16796
16838
  embeddings: number().int()
16797
16839
  });
16840
+ /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
16841
+ var DiskReconcileCountsSchema = object({
16842
+ mediaDropped: number().int(),
16843
+ tracks: number().int(),
16844
+ events: number().int()
16845
+ });
16798
16846
  /** Event-store footprint for one camera. */
16799
16847
  var EventStoreDeviceFootprintSchema = object({
16800
16848
  deviceId: number(),
@@ -16971,6 +17019,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16971
17019
  }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
16972
17020
  kind: "mutation",
16973
17021
  auth: "admin"
17022
+ }), method(object({ deviceId: number() }), DiskReconcileCountsSchema, {
17023
+ kind: "mutation",
17024
+ auth: "admin"
16974
17025
  }), method(object({
16975
17026
  deviceId: number(),
16976
17027
  trackIds: array(string()).min(1)
@@ -18481,6 +18532,24 @@ var CameraStatusDegradationSchema = object({
18481
18532
  *
18482
18533
  * See spec: `docs/superpowers/specs/2026-06-24-camera-status-aggregator-cap.md`
18483
18534
  */
18535
+ var DiskReconcileJobSchema = object({
18536
+ state: _enum([
18537
+ "idle",
18538
+ "running",
18539
+ "done",
18540
+ "error"
18541
+ ]),
18542
+ total: number().int().nonnegative(),
18543
+ completed: number().int().nonnegative(),
18544
+ currentDeviceId: number().int().nullable(),
18545
+ failed: array(number().int()).readonly(),
18546
+ mediaDropped: number().int().nonnegative(),
18547
+ tracks: number().int().nonnegative(),
18548
+ events: number().int().nonnegative(),
18549
+ startedAtMs: number().int().nullable(),
18550
+ finishedAtMs: number().int().nullable(),
18551
+ error: string().nullable()
18552
+ });
18484
18553
  var CameraStatusSchema = object({
18485
18554
  deviceId: number(),
18486
18555
  assignment: CameraAssignmentStatusSchema,
@@ -18712,7 +18781,10 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
18712
18781
  }), CameraSwitchGroupSchema, {
18713
18782
  kind: "mutation",
18714
18783
  auth: "admin"
18715
- }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18784
+ }), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), DiskReconcileJobSchema, {
18785
+ kind: "mutation",
18786
+ auth: "admin"
18787
+ }), method(_void(), DiskReconcileJobSchema, { auth: "admin" }), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
18716
18788
  name: string(),
18717
18789
  description: string().optional(),
18718
18790
  config: CameraPipelineConfigSchema
@@ -19117,7 +19189,14 @@ targets: array(object({
19117
19189
  "sleeping",
19118
19190
  "unreachable",
19119
19191
  "waking"
19120
- ]).nullable()
19192
+ ]).nullable(),
19193
+ /** A battery camera (whatever its current state). An AWAKE battery
19194
+ * camera is deliberately NOT recaptured on the poll cadence — every
19195
+ * capture is a camera hit that would keep it out of sleep — so its
19196
+ * cached frame legitimately ages past the currency ceiling while
19197
+ * nothing is streaming. A surface must keep painting it (a fresh
19198
+ * frame is captured at each wake), not blank to "unavailable". */
19199
+ battery: boolean()
19121
19200
  })));
19122
19201
  /**
19123
19202
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -19778,20 +19857,6 @@ var VectorStatsResultSchema = object({
19778
19857
  exact: boolean()
19779
19858
  });
19780
19859
  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);
19781
- /**
19782
- * `videoclips` — the unified, navigable-clip surface for a camera.
19783
- *
19784
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
19785
- * provider per device, substitutable. The DEFAULT provider (registered by
19786
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
19787
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
19788
- * is a time-WINDOW over existing footage, never a separate file. A camera that
19789
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
19790
- * wrapper provider for its device and serve its own clip catalog + URLs.
19791
- *
19792
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
19793
- * temporal overlap, so the API never decides `continuous` vs `events`.
19794
- */
19795
19860
  var ClipSchema = object({
19796
19861
  /** Opaque, provider-namespaced id. The default provider encodes the time
19797
19862
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -19808,8 +19873,57 @@ var ClipSchema = object({
19808
19873
  startMs: number(),
19809
19874
  endMs: number()
19810
19875
  }),
19811
- /** Thumbnail URL (lazy; e.g. analytics `getEventMedia`). Never inlined. */
19812
- thumbnail: string().optional()
19876
+ /**
19877
+ * Lazy thumbnail URL, never inlined.
19878
+ *
19879
+ * Recording-derived clips (events-mode keep-window, and the prepared
19880
+ * continuous event+fragment visit) MUST use the snapshot of the **main
19881
+ * event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
19882
+ * of the event that owns `kind` (object > motion > audio). Do not extract
19883
+ * a keyframe from the recorded segments. Other providers (onboard, HKSV)
19884
+ * mint their own stills.
19885
+ *
19886
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
19887
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
19888
+ * Absent is meaningful — "this visit has no event still" — never "we did not
19889
+ * look". Stamping it from a URL template made 35% of one camera's clips point
19890
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
19891
+ * A read that FAILS drops the claim; it never invents it.
19892
+ */
19893
+ thumbnail: string().optional(),
19894
+ /**
19895
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
19896
+ * can be decoded. Present whenever the visit came from recorded availability;
19897
+ * absent on a per-event padded window (there is no footage to promise).
19898
+ *
19899
+ * This is not a thumbnail and not a second byte path: it is the argument to
19900
+ * the recorder's existing still route. The surface — never the provider —
19901
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
19902
+ * contiguous range, not of the visit: a visit spans its holes by
19903
+ * construction, so a naive midpoint lands in dead air.
19904
+ */
19905
+ stillAtMs: number().optional(),
19906
+ /**
19907
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
19908
+ * first within kind (object → motion → audio), capped at
19909
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
19910
+ *
19911
+ * Bounded because it is not a payload the surface pages through: one visit on
19912
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
19913
+ * camera-day. Read {@link eventCount} for the true total.
19914
+ */
19915
+ eventIds: array(string()).optional(),
19916
+ /** How many analytics events actually overlap this visit. Differs from
19917
+ * `eventIds.length` exactly when the sample was capped — so a truncated
19918
+ * list is never mistaken for a quiet visit. */
19919
+ eventCount: number().int().nonnegative().optional(),
19920
+ /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
19921
+ * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
19922
+ * bar keeps showing them via `recording.getAvailability`. */
19923
+ holes: array(object({
19924
+ startMs: number(),
19925
+ endMs: number()
19926
+ })).optional()
19813
19927
  });
19814
19928
  var ClipPlaybackSchema = object({
19815
19929
  /** HLS master URL through the hub data-plane (Range + token in path). */
@@ -20273,7 +20387,14 @@ var SearchResultSchema = object({
20273
20387
  });
20274
20388
  var AutoUpdateSettingsSchema = object({
20275
20389
  channel: ChannelSchema,
20276
- intervalSeconds: number()
20390
+ intervalSeconds: number(),
20391
+ /**
20392
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
20393
+ * `channel`: the poller runs while auto-apply is `off`, because being told
20394
+ * about a publish and installing it are different decisions. Clamped
20395
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
20396
+ */
20397
+ updateCheckIntervalSeconds: number()
20277
20398
  });
20278
20399
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
20279
20400
  var RestartAddonResultSchema = unknown();
@@ -20414,7 +20535,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
20414
20535
  auth: "admin"
20415
20536
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
20416
20537
  channel: ChannelSchema,
20417
- intervalSeconds: number().min(300).max(86400).optional()
20538
+ intervalSeconds: number().min(300).max(86400).optional(),
20539
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
20540
+ updateCheckIntervalSeconds: number().min(900).max(604800).optional()
20418
20541
  }), unknown(), {
20419
20542
  kind: "mutation",
20420
20543
  auth: "admin"
@@ -24407,7 +24530,9 @@ var ExportOptionsSchema = object({
24407
24530
  includeAudio: boolean(),
24408
24531
  maxLifeMs: number().int().positive(),
24409
24532
  deleteAfterDownload: boolean(),
24410
- title: string().max(200).optional()
24533
+ title: string().max(200).optional(),
24534
+ /** Notification-output target ids to ping when this export becomes ready. */
24535
+ notifyTargetIds: array(string().min(1)).max(20).optional()
24411
24536
  }).superRefine((v, ctx) => {
24412
24537
  if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
24413
24538
  code: ZodIssueCode.custom,
@@ -24466,10 +24591,18 @@ var ExportBytesSchema = object({
24466
24591
  });
24467
24592
  method(object({
24468
24593
  deviceId: number(),
24469
- profile: string(),
24594
+ /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
24595
+ profile: string().optional(),
24596
+ profiles: array(string()).min(1).optional(),
24470
24597
  fromMs: number(),
24471
24598
  toMs: number(),
24472
24599
  options: ExportOptionsSchema
24600
+ }).superRefine((v, ctx) => {
24601
+ if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
24602
+ code: ZodIssueCode.custom,
24603
+ message: "pass profiles[] (min 1) or legacy profile",
24604
+ path: ["profiles"]
24605
+ });
24473
24606
  }), ExportRecordSchema, {
24474
24607
  kind: "mutation",
24475
24608
  auth: "protected"
@@ -29069,6 +29202,12 @@ Object.freeze({
29069
29202
  addonId: null,
29070
29203
  access: "create"
29071
29204
  },
29205
+ "pipelineAnalytics.reconcileFromDisk": {
29206
+ capName: "pipeline-analytics",
29207
+ capScope: "device",
29208
+ addonId: null,
29209
+ access: "create"
29210
+ },
29072
29211
  "pipelineAnalytics.refreshStorageLocationsForMigration": {
29073
29212
  capName: "pipeline-analytics",
29074
29213
  capScope: "device",
@@ -29471,6 +29610,12 @@ Object.freeze({
29471
29610
  addonId: null,
29472
29611
  access: "view"
29473
29612
  },
29613
+ "pipelineOrchestrator.getReconcileFromDiskStatus": {
29614
+ capName: "pipeline-orchestrator",
29615
+ capScope: "system",
29616
+ addonId: null,
29617
+ access: "view"
29618
+ },
29474
29619
  "pipelineOrchestrator.listAgentSettings": {
29475
29620
  capName: "pipeline-orchestrator",
29476
29621
  capScope: "system",
@@ -29495,6 +29640,12 @@ Object.freeze({
29495
29640
  addonId: null,
29496
29641
  access: "create"
29497
29642
  },
29643
+ "pipelineOrchestrator.reconcileFromDisk": {
29644
+ capName: "pipeline-orchestrator",
29645
+ capScope: "system",
29646
+ addonId: null,
29647
+ access: "create"
29648
+ },
29498
29649
  "pipelineOrchestrator.removeAgentSettings": {
29499
29650
  capName: "pipeline-orchestrator",
29500
29651
  capScope: "system",
@@ -32486,6 +32637,11 @@ Object.freeze({
32486
32637
  form: "single",
32487
32638
  optional: true
32488
32639
  }],
32640
+ "pipelineAnalytics.reconcileFromDisk": [{
32641
+ name: "deviceId",
32642
+ form: "single",
32643
+ optional: false
32644
+ }],
32489
32645
  "pipelineAnalytics.restageRetrainTrack": [{
32490
32646
  name: "deviceId",
32491
32647
  form: "single",
@@ -33551,6 +33707,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
33551
33707
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
33552
33708
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
33553
33709
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
33710
+ var MB = 1024 * 1024;
33711
+ 1024 * MB, 3072 * MB;
33554
33712
  //#endregion
33555
33713
  //#region src/addon.ts
33556
33714
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -33572,7 +33730,7 @@ var AgentUIAddon = class extends BaseAddon {
33572
33730
  capability: adminUiCapability,
33573
33731
  provider: {
33574
33732
  getStaticDir: async () => ({ staticDir: path.resolve(__dirname) }),
33575
- getVersion: async () => ({ version: "1.2.19" })
33733
+ getVersion: async () => ({ version: "1.2.21" })
33576
33734
  }
33577
33735
  }];
33578
33736
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-agent-ui",
3
- "version": "1.2.19",
3
+ "version": "1.2.21",
4
4
  "description": "Agent UI — lightweight status dashboard served by every agent node",
5
5
  "keywords": [
6
6
  "camstack",