@camstack/system 1.2.275 → 1.2.277

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 (63) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/autotrack/index.js +1 -1
  8. package/dist/builtins/autotrack/index.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/camera-grid/grid-last-frame-store.d.ts +39 -0
  12. package/dist/builtins/camera-grid/index.js +67 -12
  13. package/dist/builtins/camera-grid/index.mjs +67 -12
  14. package/dist/builtins/console-logging/index.js +1 -1
  15. package/dist/builtins/console-logging/index.mjs +1 -1
  16. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  17. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  18. package/dist/builtins/device-manager/device-aggregation.d.ts +0 -6
  19. package/dist/builtins/device-manager/device-bindings-store.d.ts +22 -2
  20. package/dist/builtins/device-manager/device-manager.addon.d.ts +4 -0
  21. package/dist/builtins/device-manager/device-manager.addon.js +122 -58
  22. package/dist/builtins/device-manager/device-manager.addon.mjs +122 -59
  23. package/dist/builtins/device-manager/device-meta-types.d.ts +24 -3
  24. package/dist/builtins/device-manager/device-source-activation.d.ts +36 -0
  25. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  26. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  27. package/dist/builtins/hub-forwarder/index.js +1 -1
  28. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  29. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  30. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  31. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  32. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  33. package/dist/builtins/local-network/local-network.addon.js +1 -1
  34. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  35. package/dist/builtins/loki-logging/index.js +1 -1
  36. package/dist/builtins/loki-logging/index.mjs +1 -1
  37. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  38. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  39. package/dist/builtins/platform-probe/index.js +1 -1
  40. package/dist/builtins/platform-probe/index.mjs +1 -1
  41. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  42. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/snapshot/index.js +1 -1
  44. package/dist/builtins/snapshot/index.mjs +1 -1
  45. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  46. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  47. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  48. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  49. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  50. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  51. package/dist/builtins/system-config/system-config.addon.js +1 -1
  52. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  53. package/dist/builtins/winston-logging/index.js +1 -1
  54. package/dist/builtins/winston-logging/index.mjs +1 -1
  55. package/dist/{dist-CYXhwfDf.js → dist-Bu8HN-KS.js} +354 -11
  56. package/dist/{dist-DWSJVcZe.mjs → dist-DOF9wnat.mjs} +349 -12
  57. package/dist/index.d.ts +7 -0
  58. package/dist/index.js +61 -48
  59. package/dist/index.mjs +62 -50
  60. package/dist/kernel/capability-registry.d.ts +28 -8
  61. package/dist/{retired-settings-keys-DEFtUH8o.js → retired-settings-keys-BwE6S2eQ.js} +1 -1
  62. package/dist/{retired-settings-keys-BHGnCLW0.mjs → retired-settings-keys-DXMfts8c.mjs} +1 -1
  63. package/package.json +2 -2
@@ -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-CYXhwfDf.js");
6
+ const require_dist = require("../../dist-Bu8HN-KS.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { P as addonPagesCapability, Pn as EventCategory, an as errMsg, sn as BaseAddon } from "../../dist-DWSJVcZe.mjs";
1
+ import { Fn as EventCategory, P as addonPagesCapability, cn as BaseAddon, on as errMsg } from "../../dist-DOF9wnat.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
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-CYXhwfDf.js");
6
+ const require_dist = require("../../dist-Bu8HN-KS.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { I as addonWidgetsCapability, Pn as EventCategory, an as errMsg, sn as BaseAddon } from "../../dist-DWSJVcZe.mjs";
1
+ import { Fn as EventCategory, I as addonWidgetsCapability, cn as BaseAddon, on as errMsg } from "../../dist-DOF9wnat.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
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-CYXhwfDf.js");
6
+ const require_dist = require("../../dist-Bu8HN-KS.js");
7
7
  //#region src/builtins/alerts/prune-policy.ts
8
8
  /**
9
9
  * Ids older than `cutoffMs`, oldest first.
@@ -1,4 +1,4 @@
1
- import { L as alertsCapability, Pn as EventCategory, an as errMsg, bn as createEvent, n as AlertSchema, sn as BaseAddon } from "../../dist-DWSJVcZe.mjs";
1
+ import { Fn as EventCategory, L as alertsCapability, cn as BaseAddon, n as AlertSchema, on as errMsg, xn as createEvent } from "../../dist-DOF9wnat.mjs";
2
2
  //#region src/builtins/alerts/prune-policy.ts
3
3
  /**
4
4
  * Ids older than `cutoffMs`, oldest first.
@@ -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-CYXhwfDf.js");
6
+ const require_dist = require("../../dist-Bu8HN-KS.js");
7
7
  //#region src/builtins/autotrack/autotrack-config.ts
8
8
  var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
9
9
  deadZoneFraction: .5,
@@ -1,4 +1,4 @@
1
- import { B as bareAddonId, En as isEvent, It as ptzAutotrackCapability, Pn as EventCategory, an as errMsg, h as FIRST_LEVEL_MACRO_CLASSES, sn as BaseAddon } from "../../dist-DWSJVcZe.mjs";
1
+ import { B as bareAddonId, Dn as isEvent, Fn as EventCategory, Lt as ptzAutotrackCapability, cn as BaseAddon, h as FIRST_LEVEL_MACRO_CLASSES, on as errMsg } from "../../dist-DOF9wnat.mjs";
2
2
  //#region src/builtins/autotrack/autotrack-config.ts
3
3
  var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
4
4
  deadZoneFraction: .5,
@@ -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-CYXhwfDf.js");
6
+ const require_dist = require("../../dist-Bu8HN-KS.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let buffer = require("buffer");
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { Dt as mayWriteToLocation, Pn as EventCategory, Zt as storageOccupancyCapability, sn as BaseAddon, z as backupCapability } from "../../dist-DWSJVcZe.mjs";
1
+ import { Dt as mayWriteToLocation, Fn as EventCategory, Qt as storageOccupancyCapability, cn as BaseAddon, z as backupCapability } from "../../dist-DOF9wnat.mjs";
2
2
  import { randomBytes, randomUUID } from "node:crypto";
3
3
  import { Buffer as Buffer$1 } from "buffer";
4
4
  import * as fsp from "node:fs/promises";
@@ -1,19 +1,58 @@
1
1
  import { GridLastFrameStore } from './grid-snapshot.js';
2
+ import { GridStreamLogger } from './grid-stream-session.js';
3
+ /**
4
+ * Exactly the filesystem this store touches, so a caller can hand it a
5
+ * different one and a spec can hand it a broken one.
6
+ *
7
+ * The four calls are `node:fs/promises`' own shapes, narrowed to the single
8
+ * overload used here — that is what makes `promises` assignable to it with no
9
+ * cast.
10
+ */
11
+ export interface GridFrameFs {
12
+ mkdir(dir: string, options: {
13
+ readonly recursive: true;
14
+ }): Promise<string | undefined>;
15
+ writeFile(path: string, data: Buffer): Promise<void>;
16
+ rename(from: string, to: string): Promise<void>;
17
+ readFile(path: string): Promise<Buffer>;
18
+ }
2
19
  export interface GridLastFrameFileStoreOptions {
3
20
  /** `ctx.dataDir` — the addon's exclusive directory. */
4
21
  readonly dataDir: string;
22
+ /** Where a dropped frame is named. A silent drop reads as "never happened". */
23
+ readonly logger: GridStreamLogger;
5
24
  readonly minIntervalMs?: number;
6
25
  /** Injectable for tests; `Date.now` otherwise. */
7
26
  readonly now?: () => number;
27
+ /** Injectable for tests; `node:fs/promises` otherwise. */
28
+ readonly fs?: GridFrameFs;
29
+ /** The bound on how long the caller waits on the disk. */
30
+ readonly deadlineMs?: number;
8
31
  }
9
32
  export declare class GridLastFrameFileStore implements GridLastFrameStore {
10
33
  private readonly dir;
11
34
  private readonly minIntervalMs;
12
35
  private readonly now;
36
+ private readonly fs;
37
+ private readonly deadlineMs;
38
+ private readonly logger;
13
39
  /** Last write per device, so the throttle survives without touching disk. */
14
40
  private readonly lastWriteMs;
15
41
  constructor(options: GridLastFrameFileStoreOptions);
16
42
  put(deviceId: number, jpeg: Buffer): Promise<void>;
17
43
  get(deviceId: number): Promise<Buffer | null>;
44
+ /**
45
+ * Run one piece of disk work, and answer `fallback` whether it REFUSES or
46
+ * never answers at all.
47
+ *
48
+ * The `try/catch` this replaces covered only the first of those. Measured on
49
+ * the CI runner (Linux, root, in a container) 2026-09-20: `fs.mkdir` with
50
+ * `recursive: true` under a parent that cannot hold a directory never
51
+ * settles — procfs answers ENOENT and Node's recursive mkdir retries the
52
+ * child forever — and `put` is AWAITED on the snapshot answer path, so the
53
+ * grid's snapshot RPC never answered either. An exception is not the only
54
+ * way the disk says no.
55
+ */
56
+ private bounded;
18
57
  private pathFor;
19
58
  }
@@ -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-CYXhwfDf.js");
6
+ const require_dist = require("../../dist-Bu8HN-KS.js");
7
7
  let zod = require("zod");
8
8
  let _camstack_types_node = require("@camstack/types/node");
9
9
  let node_path = require("node:path");
@@ -910,37 +910,89 @@ var GridFrameSampler = class {
910
910
  var DEFAULT_MIN_INTERVAL_MS = 6e4;
911
911
  /** Its own subdirectory, so the addon's other state is never mistaken for a frame. */
912
912
  var SUBDIR = "last-frame";
913
+ /**
914
+ * How long the caller may be made to wait on the disk, for ANY of it.
915
+ *
916
+ * A bonus that costs a snapshot is not a bonus. One second is far more than a
917
+ * healthy local write needs and far less than a viewer will sit through.
918
+ */
919
+ var DEFAULT_DEADLINE_MS = 1e3;
913
920
  var GridLastFrameFileStore = class {
914
921
  dir;
915
922
  minIntervalMs;
916
923
  now;
924
+ fs;
925
+ deadlineMs;
926
+ logger;
917
927
  /** Last write per device, so the throttle survives without touching disk. */
918
928
  lastWriteMs = /* @__PURE__ */ new Map();
919
929
  constructor(options) {
920
930
  this.dir = (0, node_path.join)(options.dataDir, SUBDIR);
921
931
  this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
922
932
  this.now = options.now ?? (() => Date.now());
933
+ this.fs = options.fs ?? node_fs.promises;
934
+ this.deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;
935
+ this.logger = options.logger;
923
936
  }
924
937
  async put(deviceId, jpeg) {
925
938
  if (jpeg.length === 0) return;
926
939
  const at = this.now();
927
940
  const previous = this.lastWriteMs.get(deviceId);
928
941
  if (previous !== void 0 && at - previous < this.minIntervalMs) return;
929
- try {
930
- await node_fs.promises.mkdir(this.dir, { recursive: true });
942
+ if (await this.bounded(deviceId, "keep the composed frame", false, async () => {
943
+ await this.fs.mkdir(this.dir, { recursive: true });
931
944
  const target = this.pathFor(deviceId);
932
945
  const staging = `${target}.writing`;
933
- await node_fs.promises.writeFile(staging, jpeg);
934
- await node_fs.promises.rename(staging, target);
935
- this.lastWriteMs.set(deviceId, at);
936
- } catch {}
946
+ await this.fs.writeFile(staging, jpeg);
947
+ await this.fs.rename(staging, target);
948
+ return true;
949
+ })) this.lastWriteMs.set(deviceId, at);
937
950
  }
938
951
  async get(deviceId) {
939
- try {
940
- const jpeg = await node_fs.promises.readFile(this.pathFor(deviceId));
952
+ return this.bounded(deviceId, "read the kept frame", null, async () => {
953
+ const jpeg = await this.fs.readFile(this.pathFor(deviceId));
941
954
  return jpeg.length > 0 ? jpeg : null;
942
- } catch {
943
- return null;
955
+ });
956
+ }
957
+ /**
958
+ * Run one piece of disk work, and answer `fallback` whether it REFUSES or
959
+ * never answers at all.
960
+ *
961
+ * The `try/catch` this replaces covered only the first of those. Measured on
962
+ * the CI runner (Linux, root, in a container) 2026-09-20: `fs.mkdir` with
963
+ * `recursive: true` under a parent that cannot hold a directory never
964
+ * settles — procfs answers ENOENT and Node's recursive mkdir retries the
965
+ * child forever — and `put` is AWAITED on the snapshot answer path, so the
966
+ * grid's snapshot RPC never answered either. An exception is not the only
967
+ * way the disk says no.
968
+ */
969
+ async bounded(deviceId, what, fallback, work) {
970
+ let timer = null;
971
+ const expiry = new Promise((resolve) => {
972
+ timer = setTimeout(() => resolve({ settled: false }), this.deadlineMs);
973
+ timer.unref();
974
+ });
975
+ try {
976
+ const outcome = await Promise.race([work().then((value) => ({
977
+ settled: true,
978
+ value
979
+ })), expiry]);
980
+ if (!outcome.settled) {
981
+ this.logger.warn(`camera grid: the disk did not ${what} within its deadline`, {
982
+ tags: { deviceId },
983
+ meta: { deadlineMs: this.deadlineMs }
984
+ });
985
+ return fallback;
986
+ }
987
+ return outcome.value;
988
+ } catch (error) {
989
+ this.logger.warn(`camera grid: the disk could not ${what}`, {
990
+ tags: { deviceId },
991
+ meta: { error: require_dist.errMsg(error) }
992
+ });
993
+ return fallback;
994
+ } finally {
995
+ if (timer !== null) clearTimeout(timer);
944
996
  }
945
997
  }
946
998
  pathFor(deviceId) {
@@ -2954,7 +3006,10 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
2954
3006
  logger: this.ctx.logger.child("snapshot"),
2955
3007
  sessionsFor: (deviceId) => this.sessionsForDevice(deviceId),
2956
3008
  sampleFrame: async (session) => this.sampleFrame(session),
2957
- store: new GridLastFrameFileStore({ dataDir: this.ctx.dataDir })
3009
+ store: new GridLastFrameFileStore({
3010
+ dataDir: this.ctx.dataDir,
3011
+ logger: this.ctx.logger.child("snapshot")
3012
+ })
2958
3013
  });
2959
3014
  this.activity = new GridActivityForwarder({
2960
3015
  eventBus: this.ctx.eventBus,
@@ -1,4 +1,4 @@
1
- import { $t as streamCatalogCapability, At as motionCapability, Jt as snapshotCapability, Pn as EventCategory, U as cameraGridLayoutCapability, Y as declarationOwnerNodeId, an as errMsg, b as MOTION_CLOSE_AFTER_MS, bn as createEvent, dn as DeviceFeature, f as DERIVED_CAMERA_SILENCED_CAP_NAMES, i as BaseDevice, on as buildFfmpegArgs, p as DeclaredDevices, pn as DeviceType, sn as BaseAddon, wn as hydrateSchema } from "../../dist-DWSJVcZe.mjs";
1
+ import { At as motionCapability, Fn as EventCategory, Tn as hydrateSchema, U as cameraGridLayoutCapability, Y as declarationOwnerNodeId, Yt as snapshotCapability, b as MOTION_CLOSE_AFTER_MS, cn as BaseAddon, en as streamCatalogCapability, f as DERIVED_CAMERA_SILENCED_CAP_NAMES, fn as DeviceFeature, i as BaseDevice, mn as DeviceType, on as errMsg, p as DeclaredDevices, sn as buildFfmpegArgs, xn as createEvent } from "../../dist-DOF9wnat.mjs";
2
2
  import { z } from "zod";
3
3
  import { FfmpegProcess } from "@camstack/types/node";
4
4
  import { join } from "node:path";
@@ -905,37 +905,89 @@ var GridFrameSampler = class {
905
905
  var DEFAULT_MIN_INTERVAL_MS = 6e4;
906
906
  /** Its own subdirectory, so the addon's other state is never mistaken for a frame. */
907
907
  var SUBDIR = "last-frame";
908
+ /**
909
+ * How long the caller may be made to wait on the disk, for ANY of it.
910
+ *
911
+ * A bonus that costs a snapshot is not a bonus. One second is far more than a
912
+ * healthy local write needs and far less than a viewer will sit through.
913
+ */
914
+ var DEFAULT_DEADLINE_MS = 1e3;
908
915
  var GridLastFrameFileStore = class {
909
916
  dir;
910
917
  minIntervalMs;
911
918
  now;
919
+ fs;
920
+ deadlineMs;
921
+ logger;
912
922
  /** Last write per device, so the throttle survives without touching disk. */
913
923
  lastWriteMs = /* @__PURE__ */ new Map();
914
924
  constructor(options) {
915
925
  this.dir = join(options.dataDir, SUBDIR);
916
926
  this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
917
927
  this.now = options.now ?? (() => Date.now());
928
+ this.fs = options.fs ?? promises;
929
+ this.deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;
930
+ this.logger = options.logger;
918
931
  }
919
932
  async put(deviceId, jpeg) {
920
933
  if (jpeg.length === 0) return;
921
934
  const at = this.now();
922
935
  const previous = this.lastWriteMs.get(deviceId);
923
936
  if (previous !== void 0 && at - previous < this.minIntervalMs) return;
924
- try {
925
- await promises.mkdir(this.dir, { recursive: true });
937
+ if (await this.bounded(deviceId, "keep the composed frame", false, async () => {
938
+ await this.fs.mkdir(this.dir, { recursive: true });
926
939
  const target = this.pathFor(deviceId);
927
940
  const staging = `${target}.writing`;
928
- await promises.writeFile(staging, jpeg);
929
- await promises.rename(staging, target);
930
- this.lastWriteMs.set(deviceId, at);
931
- } catch {}
941
+ await this.fs.writeFile(staging, jpeg);
942
+ await this.fs.rename(staging, target);
943
+ return true;
944
+ })) this.lastWriteMs.set(deviceId, at);
932
945
  }
933
946
  async get(deviceId) {
934
- try {
935
- const jpeg = await promises.readFile(this.pathFor(deviceId));
947
+ return this.bounded(deviceId, "read the kept frame", null, async () => {
948
+ const jpeg = await this.fs.readFile(this.pathFor(deviceId));
936
949
  return jpeg.length > 0 ? jpeg : null;
937
- } catch {
938
- return null;
950
+ });
951
+ }
952
+ /**
953
+ * Run one piece of disk work, and answer `fallback` whether it REFUSES or
954
+ * never answers at all.
955
+ *
956
+ * The `try/catch` this replaces covered only the first of those. Measured on
957
+ * the CI runner (Linux, root, in a container) 2026-09-20: `fs.mkdir` with
958
+ * `recursive: true` under a parent that cannot hold a directory never
959
+ * settles — procfs answers ENOENT and Node's recursive mkdir retries the
960
+ * child forever — and `put` is AWAITED on the snapshot answer path, so the
961
+ * grid's snapshot RPC never answered either. An exception is not the only
962
+ * way the disk says no.
963
+ */
964
+ async bounded(deviceId, what, fallback, work) {
965
+ let timer = null;
966
+ const expiry = new Promise((resolve) => {
967
+ timer = setTimeout(() => resolve({ settled: false }), this.deadlineMs);
968
+ timer.unref();
969
+ });
970
+ try {
971
+ const outcome = await Promise.race([work().then((value) => ({
972
+ settled: true,
973
+ value
974
+ })), expiry]);
975
+ if (!outcome.settled) {
976
+ this.logger.warn(`camera grid: the disk did not ${what} within its deadline`, {
977
+ tags: { deviceId },
978
+ meta: { deadlineMs: this.deadlineMs }
979
+ });
980
+ return fallback;
981
+ }
982
+ return outcome.value;
983
+ } catch (error) {
984
+ this.logger.warn(`camera grid: the disk could not ${what}`, {
985
+ tags: { deviceId },
986
+ meta: { error: errMsg(error) }
987
+ });
988
+ return fallback;
989
+ } finally {
990
+ if (timer !== null) clearTimeout(timer);
939
991
  }
940
992
  }
941
993
  pathFor(deviceId) {
@@ -2949,7 +3001,10 @@ var CameraGridAddon = class extends BaseAddon {
2949
3001
  logger: this.ctx.logger.child("snapshot"),
2950
3002
  sessionsFor: (deviceId) => this.sessionsForDevice(deviceId),
2951
3003
  sampleFrame: async (session) => this.sampleFrame(session),
2952
- store: new GridLastFrameFileStore({ dataDir: this.ctx.dataDir })
3004
+ store: new GridLastFrameFileStore({
3005
+ dataDir: this.ctx.dataDir,
3006
+ logger: this.ctx.logger.child("snapshot")
3007
+ })
2953
3008
  });
2954
3009
  this.activity = new GridActivityForwarder({
2955
3010
  eventBus: this.ctx.eventBus,
@@ -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-CYXhwfDf.js");
6
+ const require_dist = require("../../dist-Bu8HN-KS.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { Ct as logDestinationCapability, sn as BaseAddon } from "../../dist-DWSJVcZe.mjs";
1
+ import { Ct as logDestinationCapability, cn as BaseAddon } from "../../dist-DOF9wnat.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-CYXhwfDf.js");
3
+ const require_dist = require("../../dist-Bu8HN-KS.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -1,4 +1,4 @@
1
- import { G as coreBlockAddonId, K as coreBlockIdFromAddonId, an as errMsg, c as CORE_BLOCKS_ADDON_ID, l as CORE_BLOCK_ADDON_PREFIX, p as DeclaredDevices, q as coreBlocksCapability, sn as BaseAddon, u as CoreBlockSchema } from "../../dist-DWSJVcZe.mjs";
1
+ import { G as coreBlockAddonId, K as coreBlockIdFromAddonId, c as CORE_BLOCKS_ADDON_ID, cn as BaseAddon, l as CORE_BLOCK_ADDON_PREFIX, on as errMsg, p as DeclaredDevices, q as coreBlocksCapability, u as CoreBlockSchema } from "../../dist-DOF9wnat.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";
@@ -295,12 +295,6 @@ export declare function listBindableCapsForDeviceType(deps: AggregationDeps, inp
295
295
  capName: string;
296
296
  wrappers: string[];
297
297
  }>>;
298
- export declare function setWrapperActive(deps: AggregationDeps, input: {
299
- deviceId: number;
300
- capName: string;
301
- wrapperAddonId: string;
302
- active: boolean;
303
- }): Promise<void>;
304
298
  /**
305
299
  * Per-device wireable-field catalog — the domain status fields a device's
306
300
  * bound caps expose, for the operator's cross-device wiring UI. Binding-driven:
@@ -57,8 +57,18 @@ export interface BindingsDeps {
57
57
  * `native-registered`) on its next boot.
58
58
  */
59
59
  export declare function wireRemoteNativeCapSync(ctx: AddonContext, remoteNativeCaps: RemoteNativeCaps): void;
60
- export declare function readBindingsStore(deps: BindingsDeps): Promise<DeviceBindingsStore>;
61
- export declare function writeBindingsStore(deps: BindingsDeps, next: DeviceBindingsStore): Promise<void>;
60
+ /**
61
+ * All a bindings-store read or write needs: the settings door. Named narrower
62
+ * than {@link BindingsDeps} so the activation verb (`device-source-activation.ts`)
63
+ * can reach the store without the device ledger and the remote-native cache it
64
+ * never touches — `BindingsDeps` satisfies it structurally, so every existing
65
+ * call site is unchanged.
66
+ */
67
+ export interface BindingsStoreDeps {
68
+ readonly ctx: AddonContext;
69
+ }
70
+ export declare function readBindingsStore(deps: BindingsStoreDeps): Promise<DeviceBindingsStore>;
71
+ export declare function writeBindingsStore(deps: BindingsStoreDeps, next: DeviceBindingsStore): Promise<void>;
62
72
  export declare function resolveWrapperNodeId(_wrapperAddonId: string): string;
63
73
  /**
64
74
  * Reduce a provider node id to the routable form `DeviceProxy` can pin.
@@ -151,6 +161,16 @@ export type LedgerNonEmpty = () => Promise<boolean>;
151
161
  * changes nothing (D49: a read that fails must not destroy work).
152
162
  */
153
163
  export declare function capAppliesToDeviceType(def: CapabilityDefinition | undefined, deviceType: string | undefined): boolean;
164
+ /**
165
+ * An addon's identity, with the node it happens to run on stripped.
166
+ *
167
+ * A capability registered on an agent arrives under `<addonId>@<nodeId>` —
168
+ * `pipeline-analytics@little-unraid`. That is the same ADDON as the hub-local
169
+ * `pipeline-analytics`, and treating the two as different providers is what
170
+ * put `analytics` in device 592's clip picker three times (D554 amended).
171
+ * Where a provider RUNS is a routing fact; it is not part of who it is.
172
+ */
173
+ export declare function addonIdentityOf(addonId: string): string;
154
174
  export declare function getBindings(deps: BindingsDeps, input: {
155
175
  deviceId: number;
156
176
  }): Promise<{
@@ -166,8 +166,12 @@ export declare class DeviceManagerAddon extends BaseAddon {
166
166
  deviceId: number;
167
167
  capName: string;
168
168
  wrapperAddonId: string;
169
+ /** The source to toggle on a collection cap. Absent = all of the addon's. */
170
+ sourceId?: string;
169
171
  active: boolean;
170
172
  }): Promise<void>;
173
+ /** The narrow dependency bag the activation verb needs (D556). */
174
+ private get wrapperActivationDeps();
171
175
  protected onInitialize(): Promise<ProviderRegistration[] | void>;
172
176
  protected onShutdown(): Promise<void>;
173
177
  }