@camstack/system 1.2.102 → 1.2.104

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 (62) hide show
  1. package/dist/addon-runner.js +15 -1
  2. package/dist/addon-runner.mjs +15 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  19. package/dist/builtins/hub-forwarder/index.js +1 -1
  20. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  21. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  22. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  24. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  25. package/dist/builtins/local-network/local-network.addon.js +1 -1
  26. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  27. package/dist/builtins/loki-logging/index.js +1 -1
  28. package/dist/builtins/loki-logging/index.mjs +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  31. package/dist/builtins/platform-probe/index.js +1 -1
  32. package/dist/builtins/platform-probe/index.mjs +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  35. package/dist/builtins/snapshot/index.js +150 -22
  36. package/dist/builtins/snapshot/index.mjs +150 -22
  37. package/dist/builtins/snapshot/snapshot-cache.d.ts +63 -0
  38. package/dist/builtins/snapshot/snapshot-resize.d.ts +33 -1
  39. package/dist/builtins/snapshot/snapshot.addon.d.ts +6 -0
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  41. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-3wh5LCad.mjs → dist-CAzHQ03i.mjs} +144 -21
  51. package/dist/{dist-9sC0wZw6.js → dist-DR1VmGz6.js} +144 -21
  52. package/dist/index.d.ts +2 -2
  53. package/dist/index.js +243 -328
  54. package/dist/index.mjs +233 -319
  55. package/dist/kernel/config-schema.d.ts +14 -10
  56. package/dist/kernel/heap-watch.d.ts +106 -7
  57. package/dist/kernel/moleculer/process-service.d.ts +34 -0
  58. package/dist/logging/log-manager.d.ts +7 -5
  59. package/dist/logging/partitioned-log-buffer.d.ts +95 -16
  60. package/dist/{manifest-python-deps-Bt_J04b0.mjs → manifest-python-deps-5ei2xVUh.mjs} +355 -1
  61. package/dist/{manifest-python-deps-BtVjvfvj.js → manifest-python-deps-GejnH--L.js} +422 -0
  62. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { Ct as DeviceFeature, Gt as EventCategory, M as deriveBatteryPresence, Rt as nodePin, T as batteryCapability, Tt as DeviceType, i as BatteryStatusSchema, mt as streamQualityLabel, ut as snapshotCapability, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-3wh5LCad.mjs";
1
+ import { Ct as DeviceFeature, Gt as EventCategory, M as deriveBatteryPresence, Rt as nodePin, T as batteryCapability, Tt as DeviceType, i as BatteryStatusSchema, mt as streamQualityLabel, ut as snapshotCapability, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-CAzHQ03i.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { signExpiringUrl, verifyExpiringUrl } from "@camstack/types/node";
@@ -16,25 +16,94 @@ var SnapshotCache = class {
16
16
  byKey = /* @__PURE__ */ new Map();
17
17
  /** deviceId → its live keys, so invalidation is O(streams) not O(cache). */
18
18
  keysByDevice = /* @__PURE__ */ new Map();
19
+ maxBytes;
20
+ idleTtlMs;
21
+ now;
22
+ onEvict;
23
+ totalBytes = 0;
24
+ constructor(options = {}) {
25
+ this.maxBytes = options.maxBytes ?? 67108864;
26
+ this.idleTtlMs = options.idleTtlMs ?? 36e5;
27
+ this.now = options.now ?? (() => Date.now());
28
+ this.onEvict = options.onEvict;
29
+ }
19
30
  get(deviceId, streamId) {
20
- return this.byKey.get(keyOf(deviceId, streamId));
31
+ return this.read(keyOf(deviceId, streamId));
21
32
  }
22
33
  set(deviceId, streamId, entry) {
23
34
  const key = keyOf(deviceId, streamId);
24
- this.byKey.set(key, entry);
35
+ this.forget(key);
36
+ this.byKey.set(key, {
37
+ entry,
38
+ deviceId,
39
+ bytes: entry.data.base64.length,
40
+ lastAccessAt: this.now()
41
+ });
42
+ this.totalBytes += entry.data.base64.length;
25
43
  const keys = this.keysByDevice.get(deviceId) ?? /* @__PURE__ */ new Set();
26
44
  keys.add(key);
27
45
  this.keysByDevice.set(deviceId, keys);
46
+ this.enforceBudget(key);
28
47
  }
29
48
  /** The newest entry for a device, whichever stream produced it. */
30
49
  latest(deviceId) {
31
50
  let newest;
32
- for (const key of this.keysByDevice.get(deviceId) ?? []) {
33
- const entry = this.byKey.get(key);
51
+ for (const key of [...this.keysByDevice.get(deviceId) ?? []]) {
52
+ const entry = this.read(key);
34
53
  if (entry !== void 0 && (newest === void 0 || entry.ts > newest.ts)) newest = entry;
35
54
  }
36
55
  return newest;
37
56
  }
57
+ /** Base64 bytes currently retained. The number the budget is enforced on. */
58
+ sizeBytes() {
59
+ return this.totalBytes;
60
+ }
61
+ /** Entries currently retained. */
62
+ entryCount() {
63
+ return this.byKey.size;
64
+ }
65
+ /** Read through the idle timeout: an entry nobody has touched inside
66
+ * `idleTtlMs` is dropped here rather than on a sweep, so the cost is paid by
67
+ * the caller that proves it was not wanted. */
68
+ read(key) {
69
+ const slot = this.byKey.get(key);
70
+ if (slot === void 0) return void 0;
71
+ const at = this.now();
72
+ if (at - slot.lastAccessAt > this.idleTtlMs) {
73
+ this.evict(key, "idle");
74
+ return;
75
+ }
76
+ slot.lastAccessAt = at;
77
+ return slot.entry;
78
+ }
79
+ /** Evict least-recently-ACCESSED first until the budget holds. `protectedKey`
80
+ * is the entry just written — evicting it would make `set` a no-op. */
81
+ enforceBudget(protectedKey) {
82
+ if (this.totalBytes <= this.maxBytes) return;
83
+ const coldestFirst = [...this.byKey.entries()].filter(([key]) => key !== protectedKey).toSorted((a, b) => a[1].lastAccessAt - b[1].lastAccessAt);
84
+ for (const [key] of coldestFirst) {
85
+ if (this.totalBytes <= this.maxBytes) return;
86
+ this.evict(key, "budget");
87
+ }
88
+ }
89
+ evict(key, reason) {
90
+ const slot = this.byKey.get(key);
91
+ if (slot === void 0) return;
92
+ this.forget(key);
93
+ this.onEvict?.(slot.deviceId, slot.entry.streamId, reason);
94
+ }
95
+ /** Remove a key and everything that indexes or accounts for it. */
96
+ forget(key) {
97
+ const slot = this.byKey.get(key);
98
+ if (slot === void 0) return;
99
+ this.totalBytes -= slot.bytes;
100
+ this.byKey.delete(key);
101
+ const keys = this.keysByDevice.get(slot.deviceId);
102
+ if (keys !== void 0) {
103
+ keys.delete(key);
104
+ if (keys.size === 0) this.keysByDevice.delete(slot.deviceId);
105
+ }
106
+ }
38
107
  /**
39
108
  * Drop every stream of one device.
40
109
  *
@@ -43,12 +112,13 @@ var SnapshotCache = class {
43
112
  * serving the pre-change frame from the other key.
44
113
  */
45
114
  deleteDevice(deviceId) {
46
- for (const key of this.keysByDevice.get(deviceId) ?? []) this.byKey.delete(key);
115
+ for (const key of [...this.keysByDevice.get(deviceId) ?? []]) this.forget(key);
47
116
  this.keysByDevice.delete(deviceId);
48
117
  }
49
118
  clear() {
50
119
  this.byKey.clear();
51
120
  this.keysByDevice.clear();
121
+ this.totalBytes = 0;
52
122
  }
53
123
  };
54
124
  //#endregion
@@ -753,45 +823,89 @@ function resizeJpeg(bytes, width, timeoutMs = RESIZE_TIMEOUT_MS) {
753
823
  if (timer !== void 0) clearTimeout(timer);
754
824
  });
755
825
  }
756
- /**
757
- * Resized frames, keyed by (device, stream, width, state treatment) AND
758
- * validated against the source frame's timestamp.
759
- *
760
- * The timestamp is the whole correctness argument: a variant outlives nothing.
761
- * When the underlying frame is recaptured its `capturedAt` moves, every variant
762
- * derived from the old one stops matching, and the next request re-derives.
763
- * Without that check a card would keep showing a thumbnail of a frame the
764
- * full-size view had already replaced.
765
- */
766
826
  var SnapshotVariantCache = class SnapshotVariantCache {
767
827
  byKey = /* @__PURE__ */ new Map();
768
828
  keysByDevice = /* @__PURE__ */ new Map();
829
+ maxBytes;
830
+ now;
831
+ totalBytes = 0;
832
+ constructor(options = {}) {
833
+ this.maxBytes = options.maxBytes ?? 16777216;
834
+ this.now = options.now ?? (() => Date.now());
835
+ }
769
836
  static key(deviceId, streamId, width, variantKey) {
770
837
  return `${deviceId}:${streamId ?? "auto"}:${width}:${variantKey ?? "raw"}`;
771
838
  }
772
839
  /** The variant for this exact frame, or undefined when it is missing or was
773
- * derived from an older capture. */
840
+ * derived from an older capture.
841
+ *
842
+ * A superseded variant is DELETED here, not merely reported missing. It used
843
+ * to be left in the map: unreachable by every future read (its `sourceTs` can
844
+ * never match again) and removed by nothing, so every recapture added a dead
845
+ * key. Returning `undefined` while retaining the bytes is the leak. */
774
846
  get(deviceId, streamId, width, sourceTs, variantKey) {
775
- const entry = this.byKey.get(SnapshotVariantCache.key(deviceId, streamId, width, variantKey));
776
- return entry !== void 0 && entry.sourceTs === sourceTs ? entry.bytes : void 0;
847
+ const key = SnapshotVariantCache.key(deviceId, streamId, width, variantKey);
848
+ const slot = this.byKey.get(key);
849
+ if (slot === void 0) return void 0;
850
+ if (slot.sourceTs !== sourceTs) {
851
+ this.forget(key);
852
+ return;
853
+ }
854
+ slot.lastAccessAt = this.now();
855
+ return slot.bytes;
777
856
  }
778
857
  set(deviceId, streamId, width, sourceTs, bytes, variantKey) {
779
858
  const key = SnapshotVariantCache.key(deviceId, streamId, width, variantKey);
859
+ this.forget(key);
780
860
  this.byKey.set(key, {
781
861
  bytes,
782
- sourceTs
862
+ sourceTs,
863
+ deviceId,
864
+ lastAccessAt: this.now()
783
865
  });
866
+ this.totalBytes += bytes.byteLength;
784
867
  const keys = this.keysByDevice.get(deviceId) ?? /* @__PURE__ */ new Set();
785
868
  keys.add(key);
786
869
  this.keysByDevice.set(deviceId, keys);
870
+ this.enforceBudget(key);
871
+ }
872
+ /** Variant bytes currently retained. */
873
+ sizeBytes() {
874
+ return this.totalBytes;
875
+ }
876
+ /** Variants currently retained — including any that are no longer readable.
877
+ * The number that used to only ever go up. */
878
+ entryCount() {
879
+ return this.byKey.size;
880
+ }
881
+ /** Evict least-recently-accessed first until the budget holds. */
882
+ enforceBudget(protectedKey) {
883
+ if (this.totalBytes <= this.maxBytes) return;
884
+ const coldestFirst = [...this.byKey.entries()].filter(([key]) => key !== protectedKey).toSorted((a, b) => a[1].lastAccessAt - b[1].lastAccessAt);
885
+ for (const [key] of coldestFirst) {
886
+ if (this.totalBytes <= this.maxBytes) return;
887
+ this.forget(key);
888
+ }
889
+ }
890
+ forget(key) {
891
+ const slot = this.byKey.get(key);
892
+ if (slot === void 0) return;
893
+ this.totalBytes -= slot.bytes.byteLength;
894
+ this.byKey.delete(key);
895
+ const keys = this.keysByDevice.get(slot.deviceId);
896
+ if (keys !== void 0) {
897
+ keys.delete(key);
898
+ if (keys.size === 0) this.keysByDevice.delete(slot.deviceId);
899
+ }
787
900
  }
788
901
  deleteDevice(deviceId) {
789
- for (const key of this.keysByDevice.get(deviceId) ?? []) this.byKey.delete(key);
902
+ for (const key of [...this.keysByDevice.get(deviceId) ?? []]) this.forget(key);
790
903
  this.keysByDevice.delete(deviceId);
791
904
  }
792
905
  clear() {
793
906
  this.byKey.clear();
794
907
  this.keysByDevice.clear();
908
+ this.totalBytes = 0;
795
909
  }
796
910
  };
797
911
  //#endregion
@@ -902,7 +1016,21 @@ function effectiveMaxAgeS(prefs, isBattery) {
902
1016
  * publishes stream endpoints.
903
1017
  */
904
1018
  var SnapshotAddon = class SnapshotAddon extends BaseAddon {
905
- cache = new SnapshotCache();
1019
+ /**
1020
+ * The frame cache. Bounded by a byte budget + an idle timeout — it used to be
1021
+ * unbounded in both. Every eviction is logged with the device tag: a frame
1022
+ * that silently left the cache is the reason a later capture had to happen,
1023
+ * and per-camera is the only way that question is ever asked.
1024
+ */
1025
+ cache = new SnapshotCache({ onEvict: (deviceId, streamId, reason) => {
1026
+ this.ctx.logger.debug("snapshot cache evicted", {
1027
+ tags: { deviceId },
1028
+ meta: {
1029
+ stream: streamId ?? "auto",
1030
+ reason
1031
+ }
1032
+ });
1033
+ } });
906
1034
  /** Card-sized derivatives of the frames above, keyed by width and validated
907
1035
  * against the source frame's timestamp. */
908
1036
  variants = new SnapshotVariantCache();
@@ -25,14 +25,77 @@ export interface SnapshotCacheEntry {
25
25
  /** Stream id resolved at capture time — 'high' | 'mid' | 'low' | custom | null. */
26
26
  readonly streamId: string | null;
27
27
  }
28
+ /**
29
+ * Byte budget across every cached frame.
30
+ *
31
+ * The cache shipped with NO bound of any kind: no TTL, no LRU, no size cap. It
32
+ * holds base64 JPEG payloads — a 4K main-stream frame is ~1.6 MB as base64 —
33
+ * keyed by (device, stream), so the ceiling was "every camera × every stream it
34
+ * was ever asked for", which is not a ceiling.
35
+ *
36
+ * 64 MiB is roughly 40 main-stream frames, comfortably above the live 27-camera
37
+ * roster's working set (the whole `snapshot` runner measures 185 MB RSS, of
38
+ * which the node baseline is ~130 MB), so nothing an operator does today starts
39
+ * evicting. It exists so a roster that grows, or a device that gets probed on
40
+ * many stream ids, cannot walk this structure into the OOM.
41
+ */
42
+ export declare const SNAPSHOT_CACHE_MAX_BYTES: number;
43
+ /**
44
+ * Drop an entry nobody has READ for this long.
45
+ *
46
+ * Deliberately an IDLE timeout and not a capture-age one. Capture age is
47
+ * already governed per device by `snapshotMaxAgeS` (default 15s, but one HOUR
48
+ * for battery cameras), so a TTL on age would either fight those rules or have
49
+ * to sit above the largest of them and bound nothing. "Nobody has looked at
50
+ * this in an hour" is orthogonal to freshness and is the actual condition under
51
+ * which the bytes are dead weight — a removed camera, an unreachable one, a
52
+ * stream id probed once by a debug view.
53
+ */
54
+ export declare const SNAPSHOT_CACHE_IDLE_TTL_MS = 3600000;
55
+ /** Why an entry was dropped. `budget` = the byte ceiling; `idle` = unread for
56
+ * {@link SNAPSHOT_CACHE_IDLE_TTL_MS}. */
57
+ export type SnapshotCacheEvictReason = 'budget' | 'idle';
58
+ /** Notified for every eviction so the owner can put it on the record with the
59
+ * device tag. A cached frame that vanishes is the explanation for a later
60
+ * capture that did not have to happen; silence reads as "never happened". */
61
+ export type SnapshotCacheEvictListener = (deviceId: number, streamId: string | null, reason: SnapshotCacheEvictReason) => void;
62
+ export interface SnapshotCacheOptions {
63
+ /** Total base64 bytes held across all entries. Default {@link SNAPSHOT_CACHE_MAX_BYTES}. */
64
+ readonly maxBytes?: number;
65
+ /** Idle timeout. Default {@link SNAPSHOT_CACHE_IDLE_TTL_MS}. */
66
+ readonly idleTtlMs?: number;
67
+ /** Injected clock — the bounds must be testable without waiting an hour. */
68
+ readonly now?: () => number;
69
+ readonly onEvict?: SnapshotCacheEvictListener;
70
+ }
28
71
  export declare class SnapshotCache {
29
72
  private readonly byKey;
30
73
  /** deviceId → its live keys, so invalidation is O(streams) not O(cache). */
31
74
  private readonly keysByDevice;
75
+ private readonly maxBytes;
76
+ private readonly idleTtlMs;
77
+ private readonly now;
78
+ private readonly onEvict;
79
+ private totalBytes;
80
+ constructor(options?: SnapshotCacheOptions);
32
81
  get(deviceId: number, streamId: string | undefined): SnapshotCacheEntry | undefined;
33
82
  set(deviceId: number, streamId: string | undefined, entry: SnapshotCacheEntry): void;
34
83
  /** The newest entry for a device, whichever stream produced it. */
35
84
  latest(deviceId: number): SnapshotCacheEntry | undefined;
85
+ /** Base64 bytes currently retained. The number the budget is enforced on. */
86
+ sizeBytes(): number;
87
+ /** Entries currently retained. */
88
+ entryCount(): number;
89
+ /** Read through the idle timeout: an entry nobody has touched inside
90
+ * `idleTtlMs` is dropped here rather than on a sweep, so the cost is paid by
91
+ * the caller that proves it was not wanted. */
92
+ private read;
93
+ /** Evict least-recently-ACCESSED first until the budget holds. `protectedKey`
94
+ * is the entry just written — evicting it would make `set` a no-op. */
95
+ private enforceBudget;
96
+ private evict;
97
+ /** Remove a key and everything that indexes or accounts for it. */
98
+ private forget;
36
99
  /**
37
100
  * Drop every stream of one device.
38
101
  *
@@ -69,14 +69,46 @@ export declare function resizeJpeg(bytes: Buffer, width: number, timeoutMs?: num
69
69
  * Without that check a card would keep showing a thumbnail of a frame the
70
70
  * full-size view had already replaced.
71
71
  */
72
+ /**
73
+ * Byte budget across every retained variant.
74
+ *
75
+ * Variants are small individually (a card-sized JPEG is tens of KB) but the key
76
+ * space is a product — device × stream × width × state treatment — and the map
77
+ * had no cap at all. 16 MiB is hundreds of thumbnails, far beyond any real
78
+ * roster's working set, and turns an unbounded product into a ceiling.
79
+ */
80
+ export declare const SNAPSHOT_VARIANT_CACHE_MAX_BYTES: number;
81
+ export interface SnapshotVariantCacheOptions {
82
+ /** Total variant bytes retained. Default {@link SNAPSHOT_VARIANT_CACHE_MAX_BYTES}. */
83
+ readonly maxBytes?: number;
84
+ /** Injected clock, so LRU order is testable without real time passing. */
85
+ readonly now?: () => number;
86
+ }
72
87
  export declare class SnapshotVariantCache {
73
88
  private readonly byKey;
74
89
  private readonly keysByDevice;
90
+ private readonly maxBytes;
91
+ private readonly now;
92
+ private totalBytes;
93
+ constructor(options?: SnapshotVariantCacheOptions);
75
94
  private static key;
76
95
  /** The variant for this exact frame, or undefined when it is missing or was
77
- * derived from an older capture. */
96
+ * derived from an older capture.
97
+ *
98
+ * A superseded variant is DELETED here, not merely reported missing. It used
99
+ * to be left in the map: unreachable by every future read (its `sourceTs` can
100
+ * never match again) and removed by nothing, so every recapture added a dead
101
+ * key. Returning `undefined` while retaining the bytes is the leak. */
78
102
  get(deviceId: number, streamId: string | undefined, width: number, sourceTs: number, variantKey?: string): Buffer | undefined;
79
103
  set(deviceId: number, streamId: string | undefined, width: number, sourceTs: number, bytes: Buffer, variantKey?: string): void;
104
+ /** Variant bytes currently retained. */
105
+ sizeBytes(): number;
106
+ /** Variants currently retained — including any that are no longer readable.
107
+ * The number that used to only ever go up. */
108
+ entryCount(): number;
109
+ /** Evict least-recently-accessed first until the budget holds. */
110
+ private enforceBudget;
111
+ private forget;
80
112
  deleteDevice(deviceId: number): void;
81
113
  clear(): void;
82
114
  }
@@ -30,6 +30,12 @@ interface SnapshotAddonConfig {
30
30
  * publishes stream endpoints.
31
31
  */
32
32
  export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
33
+ /**
34
+ * The frame cache. Bounded by a byte budget + an idle timeout — it used to be
35
+ * unbounded in both. Every eviction is logged with the device tag: a frame
36
+ * that silently left the cache is the reason a later capture had to happen,
37
+ * and per-camera is the only way that question is ever asked.
38
+ */
33
39
  private readonly cache;
34
40
  /** Card-sized derivatives of the frames above, keyed by width and validated
35
41
  * against the source frame's timestamp. */
@@ -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-9sC0wZw6.js");
6
+ const require_dist = require("../../dist-DR1VmGz6.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { V as filesystemBrowseCapability, pt as storageProviderCapability, yt as BaseAddon } from "../../dist-3wh5LCad.mjs";
1
+ import { V as filesystemBrowseCapability, pt as storageProviderCapability, yt as BaseAddon } from "../../dist-CAzHQ03i.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -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-9sC0wZw6.js");
6
+ const require_dist = require("../../dist-DR1VmGz6.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs = require("node:fs");
9
9
  let node_module = require("node:module");
@@ -1,4 +1,4 @@
1
- import { A as dataStoreProviderCapability, Bt as parseJsonUnknown, _t as vectorStoreCapability, f as RUNTIME_DEFAULTS, gt as vectorDimFromBase64, j as decodeVectorBase64, kt as asJsonObject, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-3wh5LCad.mjs";
1
+ import { A as dataStoreProviderCapability, Bt as parseJsonUnknown, _t as vectorStoreCapability, f as RUNTIME_DEFAULTS, gt as vectorDimFromBase64, j as decodeVectorBase64, kt as asJsonObject, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-CAzHQ03i.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { statSync } from "node:fs";
@@ -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-9sC0wZw6.js");
6
+ const require_dist = require("../../dist-DR1VmGz6.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -1,4 +1,4 @@
1
- import { dt as storageCapability, ft as storageMigrationCapability, g as StorageMigrationJobSchema, h as StorageLocationTypeSchema, lt as settingsStoreCapability, yt as BaseAddon, zt as parseJsonObject } from "../../dist-3wh5LCad.mjs";
1
+ import { dt as storageCapability, ft as storageMigrationCapability, g as StorageMigrationJobSchema, h as StorageLocationTypeSchema, lt as settingsStoreCapability, yt as BaseAddon, zt as parseJsonObject } from "../../dist-CAzHQ03i.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -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-9sC0wZw6.js");
6
+ const require_dist = require("../../dist-DR1VmGz6.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.
@@ -1,4 +1,4 @@
1
- import { It as hydrateSchema, vt as errMsg, yt as BaseAddon } from "../../dist-3wh5LCad.mjs";
1
+ import { It as hydrateSchema, vt as errMsg, yt as BaseAddon } from "../../dist-CAzHQ03i.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.
@@ -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-9sC0wZw6.js");
6
+ const require_dist = require("../../dist-DR1VmGz6.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 { X as logDestinationCapability, yt as BaseAddon } from "../../dist-3wh5LCad.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-CAzHQ03i.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";