@camstack/system 1.1.51 → 1.1.53

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 (61) hide show
  1. package/dist/addon-runner.js +2 -2
  2. package/dist/addon-runner.mjs +2 -2
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  9. package/dist/builtins/alerts/alerts.addon.js +1 -1
  10. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/device-manager/device-linked-devices.d.ts +47 -0
  16. package/dist/builtins/device-manager/device-manager.addon.js +2821 -2683
  17. package/dist/builtins/device-manager/device-manager.addon.mjs +2821 -2683
  18. package/dist/builtins/doorbell/index.d.ts +2 -0
  19. package/dist/builtins/doorbell/index.js +8 -0
  20. package/dist/builtins/doorbell/index.mjs +2 -0
  21. package/dist/builtins/doorbell/trigger-engine.d.ts +69 -0
  22. package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +87 -0
  23. package/dist/builtins/doorbell/virtual-doorbell.addon.js +489 -0
  24. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +483 -0
  25. package/dist/builtins/hub-forwarder/index.js +1 -1
  26. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  27. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  28. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  29. package/dist/builtins/local-network/local-network.addon.js +1 -1
  30. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  31. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  32. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  33. package/dist/builtins/platform-probe/index.js +1 -1
  34. package/dist/builtins/platform-probe/index.mjs +1 -1
  35. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  36. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  37. package/dist/builtins/snapshot/index.js +1 -1
  38. package/dist/builtins/snapshot/index.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  41. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +11 -3
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +11 -3
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  45. package/dist/builtins/system-config/system-config.addon.js +4 -4
  46. package/dist/builtins/system-config/system-config.addon.mjs +4 -4
  47. package/dist/builtins/winston-logging/index.js +1 -1
  48. package/dist/builtins/winston-logging/index.mjs +1 -1
  49. package/dist/{dist-C3uHEBtP.js → dist-BNro5CRu.js} +325 -27
  50. package/dist/{dist-BWQX9yUj.mjs → dist-D7_69kDt.mjs} +320 -28
  51. package/dist/http/file-data-plane.d.ts +8 -0
  52. package/dist/index.js +23 -6
  53. package/dist/index.mjs +23 -7
  54. package/dist/kernel/index.d.ts +1 -1
  55. package/dist/kernel/moleculer/readiness-context.d.ts +12 -8
  56. package/dist/kernel/moleculer/readiness-service.d.ts +16 -1
  57. package/dist/{manifest-python-deps-CVcJ9hDX.js → manifest-python-deps-DszE7bYn.js} +35 -9
  58. package/dist/{manifest-python-deps-BA6KA9If.mjs → manifest-python-deps-EM2H-jjS.mjs} +35 -9
  59. package/dist/{model-download-service-C-IHWnXx.mjs → model-download-service-Cp9f4dk6.mjs} +1 -1
  60. package/dist/{model-download-service-D-Umz4-L.js → model-download-service-hf0ookyy.js} +1 -1
  61. package/package.json +13 -1
@@ -1,4 +1,4 @@
1
- import { Ct as parseJsonUnknown, J as settingsStoreCapability, nt as errMsg, pt as asJsonObject, rt as BaseAddon, s as RUNTIME_DEFAULTS } from "../../dist-BWQX9yUj.mjs";
1
+ import { Y as settingsStoreCapability, it as BaseAddon, mt as asJsonObject, rt as errMsg, s as RUNTIME_DEFAULTS, wt as parseJsonUnknown } from "../../dist-D7_69kDt.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import Database from "better-sqlite3";
4
4
  //#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
@@ -504,10 +504,18 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
504
504
  if (existingCols.length > 0) {
505
505
  const existingNames = new Set(existingCols.map((c) => c.name));
506
506
  const declaredNames = new Set(schema.columns.map((c) => c.name));
507
- const missingDeclared = schema.columns.some((c) => !existingNames.has(c.name));
508
- if (existingCols.length <= 2 && existingNames.has("data") && !declaredNames.has("data") || missingDeclared) try {
507
+ const missingCols = schema.columns.filter((c) => !existingNames.has(c.name));
508
+ const isLegacyKv = existingCols.length <= 2 && existingNames.has("data") && !declaredNames.has("data");
509
+ const addableInPlace = (col) => !col.primaryKey && !col.unique && (!col.notNull || col.defaultValue !== void 0);
510
+ if (isLegacyKv || missingCols.some((c) => !addableInPlace(c))) try {
509
511
  this.getDb().exec(`DROP TABLE "${table}"`);
510
512
  } catch {}
513
+ else if (missingCols.length > 0) for (const col of missingCols) {
514
+ const parts = [`"${col.name}" ${col.type}`];
515
+ if (col.notNull) parts.push("NOT NULL");
516
+ if (col.defaultValue !== void 0) parts.push(`DEFAULT ${typeof col.defaultValue === "string" ? `'${col.defaultValue}'` : col.defaultValue === null ? "NULL" : String(col.defaultValue)}`);
517
+ this.getDb().exec(`ALTER TABLE "${table}" ADD COLUMN ${parts.join(" ")}`);
518
+ }
511
519
  }
512
520
  this.getDb().exec(`CREATE TABLE IF NOT EXISTS "${table}" (${colDefs.join(", ")})`);
513
521
  if (schema.indexes) for (const idx of schema.indexes) {
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-C3uHEBtP.js");
6
+ const require_dist = require("../../dist-BNro5CRu.js");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_chunk.__toESM(node_path);
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { St as parseJsonObject, X as storageCapability, rt as BaseAddon, u as StorageLocationTypeSchema } from "../../dist-BWQX9yUj.mjs";
1
+ import { Ct as parseJsonObject, Z as storageCapability, it as BaseAddon, u as StorageLocationTypeSchema } from "../../dist-D7_69kDt.mjs";
2
2
  import * as path$1 from "node:path";
3
3
  import * as fs from "node:fs/promises";
4
4
  import { buildStorageLocationRegistry } from "@camstack/system";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-C3uHEBtP.js");
6
+ const require_dist = require("../../dist-BNro5CRu.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -108,9 +108,9 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
108
108
  type: "text",
109
109
  key: "tokenExpiry",
110
110
  label: "Token Expiry",
111
- description: "JWT token lifetime (e.g. 24h, 7d, 1h)",
112
- placeholder: "24h",
113
- default: "24h"
111
+ description: "JWT token lifetime (e.g. 24h, 7d, 30d)",
112
+ placeholder: "30d",
113
+ default: "30d"
114
114
  }]
115
115
  }] };
116
116
  }
@@ -1,4 +1,4 @@
1
- import { bt as hydrateSchema, nt as errMsg, rt as BaseAddon } from "../../dist-BWQX9yUj.mjs";
1
+ import { it as BaseAddon, rt as errMsg, xt as hydrateSchema } from "../../dist-D7_69kDt.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -103,9 +103,9 @@ var SystemConfigAddon = class extends BaseAddon {
103
103
  type: "text",
104
104
  key: "tokenExpiry",
105
105
  label: "Token Expiry",
106
- description: "JWT token lifetime (e.g. 24h, 7d, 1h)",
107
- placeholder: "24h",
108
- default: "24h"
106
+ description: "JWT token lifetime (e.g. 24h, 7d, 30d)",
107
+ placeholder: "30d",
108
+ default: "30d"
109
109
  }]
110
110
  }] };
111
111
  }
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-C3uHEBtP.js");
6
+ const require_dist = require("../../dist-BNro5CRu.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { I as logDestinationCapability, rt as BaseAddon } from "../../dist-BWQX9yUj.mjs";
1
+ import { L as logDestinationCapability, it as BaseAddon } from "../../dist-D7_69kDt.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
@@ -1,5 +1,5 @@
1
1
  let zod = require("zod");
2
- //#region ../types/dist/event-category-CFZs3jI4.mjs
2
+ //#region ../types/dist/event-category-H4AVePnn.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -149,6 +149,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
149
149
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
150
150
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
151
151
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
152
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
153
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
154
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
152
155
  EventCategory["DetectionEvent"] = "detection.event";
153
156
  EventCategory["SessionTrackNew"] = "session.track.new";
154
157
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -1799,6 +1802,19 @@ function scopeKey(scope) {
1799
1802
  case "device": return `device:${scope.deviceId}`;
1800
1803
  }
1801
1804
  }
1805
+ /**
1806
+ * Producer transition order within ONE generation: `starting → ready →
1807
+ * down`. Used by `hydrate` to accept only forward moves for
1808
+ * same-generation snapshot records (a snapshot can be ahead of a lost
1809
+ * delta, never behind it, within one producer generation).
1810
+ */
1811
+ function stateRank(state) {
1812
+ switch (state) {
1813
+ case "starting": return 0;
1814
+ case "ready": return 1;
1815
+ case "down": return 2;
1816
+ }
1817
+ }
1802
1818
  function scopesEqual(a, b) {
1803
1819
  if (a.type !== b.type) return false;
1804
1820
  if (a.type === "global" || b.type === "global") return true;
@@ -1849,43 +1865,70 @@ var ReadinessRegistry = class {
1849
1865
  return Array.from(this.snapshot.values());
1850
1866
  }
1851
1867
  /**
1852
- * Hydrate the snapshot from an authoritative source. Entries already
1853
- * present locally are skipped live deltas (received via the event
1854
- * bus subscription) always take precedence over the snapshot. For
1855
- * each newly hydrated entry, a one-shot transition is dispatched to
1856
- * matching subscriptions so pending `awaitReady` callers unblock
1857
- * without having to wait for a fresh event.
1868
+ * Hydrate the snapshot from an authoritative source a RECONCILE, not
1869
+ * an add-only merge (D8: readiness events are telemetry and may be
1870
+ * lost; the on-reconnect/periodic snapshot re-pull is the repair path,
1871
+ * so it MUST be able to advance a stale record the 2026-07-17
1872
+ * "still awaiting platform-probe forever" wedge was this method
1873
+ * skipping every already-seen key).
1858
1874
  *
1859
- * Local `epoch` is reset to 1 per entry consumer-side epoch is
1860
- * derived from observed generation transitions, so this mirrors the
1861
- * value that would have been assigned had the consumer observed the
1862
- * first `ready` event directly.
1875
+ * - **Unseen keys** are added (epoch 1 mirrors the value the
1876
+ * consumer would have derived from the first observed `ready`).
1877
+ * - **Existing keys** are UPDATED when the authoritative record
1878
+ * carries a different generation (producer restart / synthetic-down
1879
+ * superseded — epoch bumps on a new-generation `ready`, mirroring
1880
+ * `ingest`), or a FORWARD state move within the same generation
1881
+ * (`starting → ready → down`; within one producer generation a
1882
+ * snapshot can only be ahead of a lost delta, never behind it).
1883
+ * - **Never a same-generation regress** — a snapshot pulled just
1884
+ * before a live delta landed must not un-ready a record (the next
1885
+ * reconcile round converges it anyway).
1886
+ * - **Never a record this process is authoritative for** — node-scoped
1887
+ * records for our own nodeId, and device/global records we emitted,
1888
+ * keep local truth (an authority's stale copy of OUR record must
1889
+ * not regress us).
1890
+ *
1891
+ * Every applied entry dispatches a one-shot transition to matching
1892
+ * subscriptions so pending `awaitReady` callers unblock without having
1893
+ * to wait for a fresh event.
1863
1894
  */
1864
1895
  hydrate(records) {
1865
1896
  const now = this.now();
1866
1897
  for (const record of records) {
1867
1898
  const key = readinessKey(record.capName, record.scope);
1868
- if (this.snapshot.has(key)) continue;
1899
+ const prev = this.snapshot.get(key) ?? null;
1900
+ let epoch;
1901
+ let durationInPrevState;
1902
+ if (prev !== null) {
1903
+ if (this.isLocallyAuthoritative(prev)) continue;
1904
+ const sameGeneration = prev.generation === record.generation;
1905
+ if (sameGeneration && stateRank(record.state) <= stateRank(prev.state)) continue;
1906
+ epoch = !sameGeneration && record.state === "ready" ? prev.epoch + 1 : prev.epoch;
1907
+ durationInPrevState = Math.max(0, now - prev.lastChange);
1908
+ } else {
1909
+ epoch = 1;
1910
+ durationInPrevState = 0;
1911
+ }
1869
1912
  const hydrated = {
1870
1913
  capName: record.capName,
1871
1914
  scope: record.scope,
1872
1915
  state: record.state,
1873
1916
  generation: record.generation,
1874
- epoch: 1,
1917
+ epoch,
1875
1918
  lastChange: now,
1876
1919
  sourceNodeId: record.sourceNodeId
1877
1920
  };
1878
1921
  this.snapshot.set(key, hydrated);
1879
- if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated, gen=${record.generation.slice(0, 6)})`);
1922
+ if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated${prev !== null ? " update" : ""}, gen=${record.generation.slice(0, 6)})`);
1880
1923
  const transition = {
1881
1924
  capName: record.capName,
1882
1925
  scope: record.scope,
1883
1926
  state: record.state,
1884
- epoch: 1,
1927
+ epoch,
1885
1928
  generation: record.generation,
1886
1929
  sourceNodeId: "hydrated",
1887
1930
  ts: now,
1888
- durationInPrevState: 0
1931
+ durationInPrevState
1889
1932
  };
1890
1933
  for (const sub of this.subscriptions) {
1891
1934
  if (sub.capName !== record.capName) continue;
@@ -1898,6 +1941,17 @@ var ReadinessRegistry = class {
1898
1941
  }
1899
1942
  }
1900
1943
  }
1944
+ /**
1945
+ * True when THIS process is the origin authority for `record` — a
1946
+ * snapshot pulled from elsewhere must never overwrite it:
1947
+ * - node-scoped records whose `scope.nodeId` is our own node id
1948
+ * (this process — or a child bridged onto our bus — emits them);
1949
+ * - device/global-scoped records whose latest transition WE emitted.
1950
+ */
1951
+ isLocallyAuthoritative(record) {
1952
+ if (record.scope.type === "node") return record.scope.nodeId === this.sourceNodeId;
1953
+ return record.sourceNodeId === this.sourceNodeId;
1954
+ }
1901
1955
  /** Shallow copy of the full snapshot — mainly for diagnostics/tests. */
1902
1956
  getAll() {
1903
1957
  return new Map(this.snapshot);
@@ -3201,6 +3255,24 @@ var RecordingRetentionSchema = zod.z.object({
3201
3255
  maxSizeGb: zod.z.number().min(0).optional()
3202
3256
  });
3203
3257
  /**
3258
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
3259
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
3260
+ * previews at. Five graduated steps; absent on a config = `standard` (the
3261
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
3262
+ *
3263
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
3264
+ * Each window's index sidecar carries its own tile dims, so a camera whose
3265
+ * preset changed over time renders every historical window at the dims it was
3266
+ * written with.
3267
+ */
3268
+ var ScrubThumbnailPresetSchema = zod.z.enum([
3269
+ "minimal",
3270
+ "low",
3271
+ "standard",
3272
+ "high",
3273
+ "max"
3274
+ ]);
3275
+ /**
3204
3276
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
3205
3277
  *
3206
3278
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -3237,7 +3309,14 @@ var RecordingConfigSchema = zod.z.object({
3237
3309
  * derived into bands once via `migrateConfigToBands`.
3238
3310
  */
3239
3311
  bands: zod.z.array(RecordingBandSchema).optional(),
3240
- retention: RecordingRetentionSchema.optional()
3312
+ retention: RecordingRetentionSchema.optional(),
3313
+ /**
3314
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
3315
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
3316
+ * windows only — existing sheets are immutable, and each window's index
3317
+ * carries its own tile dims so mixed-preset history renders correctly.
3318
+ */
3319
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
3241
3320
  });
3242
3321
  /**
3243
3322
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -6903,10 +6982,23 @@ var deviceDiscoveryCapability = {
6903
6982
  }
6904
6983
  };
6905
6984
  /**
6906
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
6907
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
6908
- * time the firmware pushes a ring; status tracks the last press and
6909
- * a pressCount since start (diagnostic).
6985
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
6986
+ * name (same pattern as `snapshot`):
6987
+ *
6988
+ * - **Native** providers: registered per-device by device-driver
6989
+ * addons via `ctx.registerNativeCap` — either on a
6990
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
6991
+ * or directly on the camera (Reolink registers at camera level).
6992
+ * Emits an `onPressed` event every time the firmware pushes a
6993
+ * ring; status tracks the last press and a pressCount since start
6994
+ * (diagnostic).
6995
+ *
6996
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
6997
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
6998
+ * device (contact / switch / event-emitter …) into a doorbell for
6999
+ * a camera. `defaultActive: false` — the operator explicitly binds
7000
+ * it per camera in the device-bindings UI, then picks the source
7001
+ * device + trigger in the per-device settings.
6910
7002
  *
6911
7003
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
6912
7004
  * — subscribers listening at the camera level receive ring events
@@ -6927,7 +7019,10 @@ var doorbellCapability = {
6927
7019
  scope: "device",
6928
7020
  deviceNative: true,
6929
7021
  mode: "singleton",
6930
- deviceTypes: [DeviceType.Button],
7022
+ kind: "wrapper",
7023
+ defaultActive: false,
7024
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
7025
+ exposesDeviceSettings: true,
6931
7026
  methods: {},
6932
7027
  events: {
6933
7028
  /**
@@ -13565,6 +13660,14 @@ var ConfigEntrySchema = zod.z.object({
13565
13660
  value: zod.z.unknown(),
13566
13661
  description: zod.z.string().optional()
13567
13662
  });
13663
+ var DeviceLinkModeSchema = zod.z.enum(["auto", "manual"]);
13664
+ /** One resolved linked device — the compact projection consumers need. */
13665
+ var LinkedDeviceSchema = zod.z.object({
13666
+ deviceId: zod.z.number(),
13667
+ name: zod.z.string(),
13668
+ location: zod.z.string().nullable(),
13669
+ features: zod.z.array(zod.z.string())
13670
+ });
13568
13671
  var SavedDeviceRowSchema = zod.z.object({
13569
13672
  /** Numeric id reserved at allocateDeviceId time. */
13570
13673
  id: zod.z.number(),
@@ -13672,6 +13775,7 @@ var deviceManagerCapability = {
13672
13775
  name: "device-manager",
13673
13776
  scope: "system",
13674
13777
  mode: "singleton",
13778
+ exposesDeviceSettings: true,
13675
13779
  methods: {
13676
13780
  /** Reserve (or re-resolve) a progressive numeric id for `(addonId, stableId)`.
13677
13781
  * Idempotent: returns the existing id if one is already persisted for the
@@ -13922,6 +14026,19 @@ var deviceManagerCapability = {
13922
14026
  getDevice: method(zod.z.object({ deviceId: zod.z.number() }), DeviceInfoSchema.nullable()),
13923
14027
  /** List children of a parent device (by parent numeric id). */
13924
14028
  getChildren: method(zod.z.object({ parentDeviceId: zod.z.number() }), zod.z.array(DeviceInfoSchema)),
14029
+ /**
14030
+ * Resolve the devices LINKED to a camera — the single policy authority
14031
+ * both consumers call (viewer devices panel + pipeline-analytics event
14032
+ * kinds/ingest). Device-tree children are ALWAYS included; mode 'auto'
14033
+ * (default) adds every device sharing the camera's non-null `location`,
14034
+ * mode 'manual' adds the persisted manual list instead. Excludes the
14035
+ * camera itself; deduped. Configuration is standard per-device settings
14036
+ * (see the module docblock) — there are no bespoke link mutations.
14037
+ */
14038
+ getLinkedDevices: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.object({
14039
+ mode: DeviceLinkModeSchema,
14040
+ devices: zod.z.array(LinkedDeviceSchema)
14041
+ })),
13925
14042
  /** Get stream sources for a camera device. */
13926
14043
  getStreamSources: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(StreamSourceEntrySchema$1)),
13927
14044
  /** Get config entries (key + value + description) for a device. */
@@ -15640,6 +15757,61 @@ var EventKindSchema = zod.z.enum([
15640
15757
  "object",
15641
15758
  "audio"
15642
15759
  ]);
15760
+ /**
15761
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
15762
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
15763
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
15764
+ */
15765
+ var TrackZoneFilterSchema = zod.z.discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
15766
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
15767
+ var EventKindIconSchema = zod.z.enum([
15768
+ "motion",
15769
+ "audio",
15770
+ "person",
15771
+ "vehicle",
15772
+ "animal",
15773
+ "door",
15774
+ "pir",
15775
+ "smoke",
15776
+ "water",
15777
+ "button",
15778
+ "generic"
15779
+ ]);
15780
+ var EventKindCategorySchema = zod.z.enum([
15781
+ "motion",
15782
+ "audio",
15783
+ "detection",
15784
+ "sensor",
15785
+ "custom"
15786
+ ]);
15787
+ var EventKindDescriptorSchema = zod.z.object({
15788
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
15789
+ kind: zod.z.string(),
15790
+ label: zod.z.string(),
15791
+ /** Hex color for timeline/legend rendering. */
15792
+ color: zod.z.string(),
15793
+ icon: EventKindIconSchema,
15794
+ category: EventKindCategorySchema,
15795
+ /** Which cap + device contributes this kind. For built-ins the camera
15796
+ * itself; for sensor kinds the LINKED source device. */
15797
+ source: zod.z.object({
15798
+ capName: zod.z.string(),
15799
+ deviceId: zod.z.number()
15800
+ })
15801
+ });
15802
+ var SensorEventSchema = zod.z.object({
15803
+ id: zod.z.string(),
15804
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
15805
+ * yields N rows, one per camera). */
15806
+ deviceId: zod.z.number(),
15807
+ /** The linked sensor device whose state changed. */
15808
+ sourceDeviceId: zod.z.number(),
15809
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
15810
+ kind: zod.z.string(),
15811
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
15812
+ value: zod.z.record(zod.z.string(), zod.z.unknown()).nullable(),
15813
+ timestamp: zod.z.number()
15814
+ });
15643
15815
  var TrackPositionSchema = zod.z.object({
15644
15816
  x: zod.z.number(),
15645
15817
  y: zod.z.number(),
@@ -15653,6 +15825,30 @@ var TrackSnapshotSchema = zod.z.object({
15653
15825
  mediaKey: zod.z.string()
15654
15826
  });
15655
15827
  /**
15828
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
15829
+ * divided by the track's detection-frame dims), computed at persist time.
15830
+ * Absent when the frame dims were unknown when the track was persisted
15831
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
15832
+ */
15833
+ var TrackEnvelopeSchema = zod.z.object({
15834
+ minX: zod.z.number(),
15835
+ minY: zod.z.number(),
15836
+ maxX: zod.z.number(),
15837
+ maxY: zod.z.number()
15838
+ });
15839
+ /**
15840
+ * Row projection for track list queries. `full` (default) returns the
15841
+ * complete Track including the frame-rate `positions[]` history and the
15842
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
15843
+ * keeps every scalar the list surfaces actually render (ids, class(es),
15844
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
15845
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
15846
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
15847
+ * `getTrack`. Mirrors the event-store `projection` convention
15848
+ * (`getObjectEvents` et al.).
15849
+ */
15850
+ var TrackProjectionSchema = zod.z.enum(["full", "slim"]);
15851
+ /**
15656
15852
  * One audio-classification label heard on the track's camera while the
15657
15853
  * track was alive, aggregated per label. An "episode" is one persisted
15658
15854
  * audio event (the confident-classification path: score ≥ the device's
@@ -15703,7 +15899,11 @@ var TrackSchema = zod.z.object({
15703
15899
  /** Audio-classification labels heard on the camera during the track's
15704
15900
  * life (score ≥ device `classificationMinScore`), aggregated per label.
15705
15901
  * Absent on legacy rows / tracks with no confident audio. */
15706
- audioLabels: zod.z.array(TrackAudioLabelSchema).readonly().optional()
15902
+ audioLabels: zod.z.array(TrackAudioLabelSchema).readonly().optional(),
15903
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
15904
+ * Populated from the persisted envelope columns on historical reads;
15905
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
15906
+ envelope: TrackEnvelopeSchema.optional()
15707
15907
  });
15708
15908
  var BaseEventFields = {
15709
15909
  id: zod.z.string(),
@@ -15813,11 +16013,13 @@ var MediaFileSchema = zod.z.object({
15813
16013
  sizeBytes: zod.z.number(),
15814
16014
  timestamp: zod.z.number()
15815
16015
  });
16016
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
16017
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
15816
16018
  var DeviceEventQueryInput = zod.z.object({
15817
16019
  deviceId: zod.z.number(),
15818
16020
  since: zod.z.number().optional(),
15819
16021
  until: zod.z.number().optional(),
15820
- limit: zod.z.number().int().min(1).max(5e3).default(1e3),
16022
+ limit: zod.z.number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
15821
16023
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
15822
16024
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
15823
16025
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -15825,6 +16027,27 @@ var DeviceEventQueryInput = zod.z.object({
15825
16027
  projection: zod.z.enum(["full", "slim"]).optional()
15826
16028
  });
15827
16029
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: zod.z.string().optional() });
16030
+ var RecentTracksQueryInput = zod.z.object({
16031
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
16032
+ deviceIds: zod.z.array(zod.z.number()),
16033
+ /** Window lower bound on `lastSeen` (inclusive). */
16034
+ since: zod.z.number().optional(),
16035
+ /** Window upper bound on `lastSeen` (inclusive). */
16036
+ until: zod.z.number().optional(),
16037
+ /** Page size. Default 200, max 1000. */
16038
+ limit: zod.z.number().int().min(1).max(1e3).default(200),
16039
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
16040
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
16041
+ cursor: zod.z.string().optional(),
16042
+ /** See {@link TrackProjectionSchema}. Default `full`. */
16043
+ projection: TrackProjectionSchema.optional()
16044
+ });
16045
+ var RecentTracksPageSchema = zod.z.object({
16046
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
16047
+ tracks: zod.z.array(TrackSchema).readonly(),
16048
+ /** Cursor for the next page, or null when this page is the last. */
16049
+ nextCursor: zod.z.string().nullable()
16050
+ });
15828
16051
  var KeyEventQueryInput = zod.z.object({
15829
16052
  deviceId: zod.z.number(),
15830
16053
  /** Window lower bound (track firstSeen ≥ since). */
@@ -15921,8 +16144,27 @@ var pipelineAnalyticsCapability = {
15921
16144
  deviceId: zod.z.number(),
15922
16145
  since: zod.z.number().optional(),
15923
16146
  until: zod.z.number().optional(),
15924
- limit: zod.z.number().optional()
16147
+ limit: zod.z.number().optional(),
16148
+ /** Spatial filter — only tracks whose trajectory intersects the zone
16149
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
16150
+ * envelope columns, then precisely tested per position. Tracks with
16151
+ * an unknown envelope (no frame dims at persist time) always match. */
16152
+ zone: TrackZoneFilterSchema.optional(),
16153
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
16154
+ * compatible — omitting the field keeps today's exact behaviour). */
16155
+ projection: TrackProjectionSchema.optional()
15925
16156
  }), zod.z.array(TrackSchema).readonly()),
16157
+ /**
16158
+ * Batched cluster-wide track listing — ONE call for the events page /
16159
+ * reel first paint instead of a per-camera `listTracks` fan-out. Merges
16160
+ * the persisted completed tracks of every requested device, sorted by
16161
+ * `lastSeen` DESC with a stable (lastSeen, trackId) cursor. Per-device
16162
+ * indexed pages (`idx_tracks_device_lastSeen`) are k-way merged
16163
+ * provider-side; `projection: 'slim'` drops the heavy `positions[]` /
16164
+ * `snapshots[]` JSON (returned as empty arrays). Active in-RAM tracks
16165
+ * are not included (same contract as `listTracks`).
16166
+ */
16167
+ listRecentTracks: method(RecentTracksQueryInput, RecentTracksPageSchema),
15926
16168
  clearTracks: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), {
15927
16169
  kind: "mutation",
15928
16170
  auth: "admin"
@@ -15931,6 +16173,26 @@ var pipelineAnalyticsCapability = {
15931
16173
  getObjectEvents: method(ObjectEventQueryInput, zod.z.array(ObjectEventSchema).readonly()),
15932
16174
  getAudioEvents: method(DeviceEventQueryInput, zod.z.array(AudioEventSchema).readonly()),
15933
16175
  /**
16176
+ * Every event kind the device can produce: built-ins (motion + audio),
16177
+ * detection classes actually observed for the device (from the track
16178
+ * history), and sensor kinds contributed by LINKED devices (resolved via
16179
+ * `device-manager.getLinkedDevices`, mapped through `EVENT_KIND_BY_CAP`).
16180
+ */
16181
+ listEventKinds: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(EventKindDescriptorSchema).readonly()),
16182
+ /**
16183
+ * Sensor-event history for a camera: state changes of LINKED sensor
16184
+ * devices, attributed to the camera at ingest time (one row per linked
16185
+ * camera). Mirrors `getAudioEvents` query semantics; `kinds` narrows to
16186
+ * a kind subset.
16187
+ */
16188
+ getSensorEvents: method(zod.z.object({
16189
+ deviceId: zod.z.number(),
16190
+ since: zod.z.number().optional(),
16191
+ until: zod.z.number().optional(),
16192
+ kinds: zod.z.array(zod.z.string()).optional(),
16193
+ limit: zod.z.number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
16194
+ }), zod.z.array(SensorEventSchema).readonly()),
16195
+ /**
15934
16196
  * Importance-ranked highlights for a device+window. Queries completed
15935
16197
  * tracks by (deviceId, firstSeen ∈ [since,until]), scores each (or reuses
15936
16198
  * the persisted score), filters by minImportance/classFilter, orders by
@@ -20545,7 +20807,12 @@ var RecordingStorageUsageSchema = zod.z.object({
20545
20807
  /**
20546
20808
  * Result of locating footage at a wall-clock instant for one device/profile.
20547
20809
  * `segment` carries the covering segment's window; `gap` reports the forward
20548
- * nearest covered edge (`null` past the end of footage / when none exists).
20810
+ * nearest covered edge (`null` past the end of footage / when none exists)
20811
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
20812
+ * footage behind the epoch; `null` when none — optional so older providers
20813
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
20814
+ * small inter-segment cracks (durMs under-covers the span to the next
20815
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
20549
20816
  */
20550
20817
  var LocateSegmentResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
20551
20818
  kind: zod.z.literal("segment"),
@@ -20554,7 +20821,8 @@ var LocateSegmentResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
20554
20821
  bytes: zod.z.number()
20555
20822
  }), zod.z.object({
20556
20823
  kind: zod.z.literal("gap"),
20557
- nearestEdgeMs: zod.z.number().nullable()
20824
+ nearestEdgeMs: zod.z.number().nullable(),
20825
+ prevEndMs: zod.z.number().nullable().optional()
20558
20826
  })]);
20559
20827
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
20560
20828
  var ReadSegmentBytesResultSchema = zod.z.object({ data: zod.z.instanceof(Uint8Array) });
@@ -22717,6 +22985,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
22717
22985
  addonId: null,
22718
22986
  access: "view"
22719
22987
  },
22988
+ "deviceManager.getLinkedDevices": {
22989
+ capName: "device-manager",
22990
+ capScope: "system",
22991
+ addonId: null,
22992
+ access: "view"
22993
+ },
22720
22994
  "deviceManager.getRoleDisplayDefaults": {
22721
22995
  capName: "device-manager",
22722
22996
  capScope: "system",
@@ -24271,6 +24545,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
24271
24545
  addonId: null,
24272
24546
  access: "view"
24273
24547
  },
24548
+ "pipelineAnalytics.getSensorEvents": {
24549
+ capName: "pipeline-analytics",
24550
+ capScope: "device",
24551
+ addonId: null,
24552
+ access: "view"
24553
+ },
24274
24554
  "pipelineAnalytics.getTrack": {
24275
24555
  capName: "pipeline-analytics",
24276
24556
  capScope: "device",
@@ -24283,6 +24563,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
24283
24563
  addonId: null,
24284
24564
  access: "view"
24285
24565
  },
24566
+ "pipelineAnalytics.listEventKinds": {
24567
+ capName: "pipeline-analytics",
24568
+ capScope: "device",
24569
+ addonId: null,
24570
+ access: "view"
24571
+ },
24572
+ "pipelineAnalytics.listRecentTracks": {
24573
+ capName: "pipeline-analytics",
24574
+ capScope: "device",
24575
+ addonId: null,
24576
+ access: "view"
24577
+ },
24286
24578
  "pipelineAnalytics.listTracks": {
24287
24579
  capName: "pipeline-analytics",
24288
24580
  capScope: "device",
@@ -26548,6 +26840,12 @@ Object.defineProperty(exports, "deviceStatusCapability", {
26548
26840
  return deviceStatusCapability;
26549
26841
  }
26550
26842
  });
26843
+ Object.defineProperty(exports, "doorbellCapability", {
26844
+ enumerable: true,
26845
+ get: function() {
26846
+ return doorbellCapability;
26847
+ }
26848
+ });
26551
26849
  Object.defineProperty(exports, "emitDownForOwnedCaps", {
26552
26850
  enumerable: true,
26553
26851
  get: function() {