@camstack/system 1.2.321 → 1.2.323

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/addon.d.ts +13 -1
  12. package/dist/builtins/camera-grid/index.js +46 -12
  13. package/dist/builtins/camera-grid/index.mjs +46 -12
  14. package/dist/builtins/composer/composer.addon.js +2 -2
  15. package/dist/builtins/composer/composer.addon.mjs +2 -2
  16. package/dist/builtins/console-logging/index.js +1 -1
  17. package/dist/builtins/console-logging/index.mjs +1 -1
  18. package/dist/builtins/core-blocks/composition-api.d.ts +4 -2
  19. package/dist/builtins/core-blocks/core-blocks.addon.js +6 -4
  20. package/dist/builtins/core-blocks/core-blocks.addon.mjs +6 -4
  21. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  22. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  23. package/dist/builtins/doorbell/doorbell-composition-migration.d.ts +1 -13
  24. package/dist/builtins/doorbell/virtual-doorbell.addon.js +6 -64
  25. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +5 -63
  26. package/dist/builtins/hub-forwarder/index.js +1 -1
  27. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  28. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  29. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  30. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  31. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  32. package/dist/builtins/local-network/local-network.addon.js +1 -1
  33. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  34. package/dist/builtins/loki-logging/index.js +1 -1
  35. package/dist/builtins/loki-logging/index.mjs +1 -1
  36. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  37. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  38. package/dist/builtins/platform-probe/index.js +1 -1
  39. package/dist/builtins/platform-probe/index.mjs +1 -1
  40. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  41. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  42. package/dist/builtins/snapshot/index.js +18 -5
  43. package/dist/builtins/snapshot/index.mjs +18 -5
  44. package/dist/builtins/snapshot/snapshot.addon.d.ts +3 -0
  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/{composition-sources-iDqQVPng.mjs → composition-sources-CeAPNF5Q.mjs} +1 -1
  56. package/dist/{composition-sources-gGrw35Lt.js → composition-sources-DkVxrXLm.js} +1 -1
  57. package/dist/{dist-BVHQ6TX1.mjs → dist-Bs6UyVGd.mjs} +2020 -1479
  58. package/dist/{dist-BMDYmNV3.js → dist-Bup0sTZb.js} +2043 -1490
  59. package/dist/index.js +1 -1
  60. package/dist/index.mjs +1 -1
  61. package/dist/{retired-settings-keys-FhjpCM5N.js → retired-settings-keys-Db0P4Yvn.js} +1 -1
  62. package/dist/{retired-settings-keys-BlQLUReM.mjs → retired-settings-keys-LROp1DXx.mjs} +1 -1
  63. package/package.json +1 -1
@@ -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-BMDYmNV3.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.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 { Gn as BaseAddon, R as addonPagesCapability, Wn as errMsg, hr as EventCategory } from "../../dist-BVHQ6TX1.mjs";
1
+ import { Kn as errMsg, R as addonPagesCapability, _r as EventCategory, qn as BaseAddon } from "../../dist-Bs6UyVGd.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-BMDYmNV3.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.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 { B as addonWidgetsCapability, Gn as BaseAddon, Wn as errMsg, hr as EventCategory } from "../../dist-BVHQ6TX1.mjs";
1
+ import { B as addonWidgetsCapability, Kn as errMsg, _r as EventCategory, qn as BaseAddon } from "../../dist-Bs6UyVGd.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-BMDYmNV3.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.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 { Gn as BaseAddon, V as alertsCapability, Wn as errMsg, hr as EventCategory, n as AlertSchema, tr as createEvent } from "../../dist-BVHQ6TX1.mjs";
1
+ import { Kn as errMsg, V as alertsCapability, _r as EventCategory, n as AlertSchema, qn as BaseAddon, rr as createEvent } from "../../dist-Bs6UyVGd.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-BMDYmNV3.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.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 { $t as ptzAutotrackCapability, Gn as BaseAddon, W as bareAddonId, Wn as errMsg, _ as FIRST_LEVEL_MACRO_CLASSES, hr as EventCategory, sr as isEvent } from "../../dist-BVHQ6TX1.mjs";
1
+ import { Kn as errMsg, W as bareAddonId, _ as FIRST_LEVEL_MACRO_CLASSES, _r as EventCategory, lr as isEvent, qn as BaseAddon, rn as ptzAutotrackCapability } from "../../dist-Bs6UyVGd.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-BMDYmNV3.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.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 { Gn as BaseAddon, Ht as mayWriteToLocation, U as backupCapability, hr as EventCategory, mn as storageOccupancyCapability } from "../../dist-BVHQ6TX1.mjs";
1
+ import { Gt as mayWriteToLocation, U as backupCapability, _r as EventCategory, qn as BaseAddon, vn as storageOccupancyCapability } from "../../dist-Bs6UyVGd.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";
@@ -26,7 +26,13 @@ export declare class CameraGridAddon extends BaseAddon<CameraGridAddonConfig> {
26
26
  private activity;
27
27
  private snapshots;
28
28
  private frameSampler;
29
+ /** The node's pinned ffmpeg (D540). `null` until init resolved it — a
30
+ * composition then refuses by name; there is no PATH default. */
29
31
  private ffmpegBinaryPath;
32
+ /** The node's pinned build, remembered only on success (D540). */
33
+ private readonly pinnedFfmpeg;
34
+ /** A resolution in flight, so a reconcile pass never starts a second. */
35
+ private ffmpegResolving;
30
36
  private integrationId;
31
37
  constructor();
32
38
  protected onInitialize(): Promise<AddonInitResult>;
@@ -147,7 +153,13 @@ export declare class CameraGridAddon extends BaseAddon<CameraGridAddonConfig> {
147
153
  private instanceForDevice;
148
154
  private gridViewFor;
149
155
  private saveGridLayout;
150
- private resolveFfmpegBinaryPath;
156
+ /**
157
+ * Resolve the pinned build, single-flighted, and hand it to the frame sampler.
158
+ * Never throws: a failure is an ERROR and leaves the binary unknown, so the
159
+ * next pass that wants a composition asks again. Never PATH: the host's
160
+ * ffmpeg is not ours (`packages/types/src/deps/ffmpeg-binary-source.ts`).
161
+ */
162
+ private resolveFfmpeg;
151
163
  /**
152
164
  * The integration page: a list of grids, with a NAME.
153
165
  *
@@ -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-BMDYmNV3.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.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");
@@ -2982,7 +2982,13 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
2982
2982
  activity = null;
2983
2983
  snapshots = null;
2984
2984
  frameSampler = null;
2985
- ffmpegBinaryPath = "ffmpeg";
2985
+ /** The node's pinned ffmpeg (D540). `null` until init resolved it — a
2986
+ * composition then refuses by name; there is no PATH default. */
2987
+ ffmpegBinaryPath = null;
2988
+ /** The node's pinned build, remembered only on success (D540). */
2989
+ pinnedFfmpeg = require_dist.memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
2990
+ /** A resolution in flight, so a reconcile pass never starts a second. */
2991
+ ffmpegResolving = null;
2986
2992
  integrationId;
2987
2993
  constructor() {
2988
2994
  super({ ...DEFAULTS });
@@ -2991,17 +2997,13 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
2991
2997
  this.replaceTombstones(this.config.cameraGridTombstones);
2992
2998
  await this.normalizeRows();
2993
2999
  if (require_dist.declarationOwnerNodeId(this.ctx.kernel.localNodeId) === "hub") {
2994
- this.ffmpegBinaryPath = await this.resolveFfmpegBinaryPath();
3000
+ await this.resolveFfmpeg();
2995
3001
  const relay = new GridStreamRelay({
2996
3002
  logger: this.ctx.logger.child("relay"),
2997
3003
  sessionFor: (request) => this.sessionFor(request)
2998
3004
  });
2999
3005
  await relay.start();
3000
3006
  this.relay = relay;
3001
- this.frameSampler = new GridFrameSampler({
3002
- binaryPath: this.ffmpegBinaryPath,
3003
- logger: this.ctx.logger.child("snapshot")
3004
- });
3005
3007
  this.snapshots = new GridSnapshotSource({
3006
3008
  logger: this.ctx.logger.child("snapshot"),
3007
3009
  sessionsFor: (deviceId) => this.sessionsForDevice(deviceId),
@@ -3341,7 +3343,19 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
3341
3343
  continue;
3342
3344
  }
3343
3345
  if (this.sessions.has(key)) continue;
3344
- this.sessions.set(key, new GridSessionEntry(instanceId, profile, this.makeSession(instanceId, deviceId, profile)));
3346
+ const binaryPath = this.ffmpegBinaryPath;
3347
+ if (binaryPath === null) {
3348
+ this.ctx.logger.error("camera-grid: composition refused — no pinned ffmpeg on this node (D540)", {
3349
+ tags: { deviceId },
3350
+ meta: {
3351
+ instanceId,
3352
+ profile
3353
+ }
3354
+ });
3355
+ this.resolveFfmpeg();
3356
+ continue;
3357
+ }
3358
+ this.sessions.set(key, new GridSessionEntry(instanceId, profile, this.makeSession(instanceId, deviceId, profile, binaryPath)));
3345
3359
  }
3346
3360
  }
3347
3361
  /**
@@ -3371,9 +3385,9 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
3371
3385
  }
3372
3386
  return names;
3373
3387
  }
3374
- makeSession(instanceId, deviceId, profile) {
3388
+ makeSession(instanceId, deviceId, profile, binaryPath) {
3375
3389
  const childContext = {
3376
- binaryPath: this.ffmpegBinaryPath,
3390
+ binaryPath,
3377
3391
  logger: this.ctx.logger,
3378
3392
  deviceId,
3379
3393
  resolveSourceNames: (deviceIds) => this.sourceNames(deviceIds)
@@ -3590,8 +3604,28 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
3590
3604
  if (!view) throw new Error("camera-grid: the grid vanished while it was being saved");
3591
3605
  return view;
3592
3606
  }
3593
- async resolveFfmpegBinaryPath() {
3594
- return await this.ctx.deps.ensureFfmpeg();
3607
+ /**
3608
+ * Resolve the pinned build, single-flighted, and hand it to the frame sampler.
3609
+ * Never throws: a failure is an ERROR and leaves the binary unknown, so the
3610
+ * next pass that wants a composition asks again. Never PATH: the host's
3611
+ * ffmpeg is not ours (`packages/types/src/deps/ffmpeg-binary-source.ts`).
3612
+ */
3613
+ resolveFfmpeg() {
3614
+ if (this.ffmpegBinaryPath !== null) return Promise.resolve();
3615
+ if (this.ffmpegResolving !== null) return this.ffmpegResolving;
3616
+ const attempt = this.pinnedFfmpeg().then((binaryPath) => {
3617
+ this.ffmpegBinaryPath = binaryPath;
3618
+ this.frameSampler = new GridFrameSampler({
3619
+ binaryPath,
3620
+ logger: this.ctx.logger.child("snapshot")
3621
+ });
3622
+ }).catch((err) => {
3623
+ this.ctx.logger.error("camera-grid: the pinned ffmpeg could not be resolved — compositions refuse until it is (D540)", { meta: { error: require_dist.errMsg(err) } });
3624
+ }).finally(() => {
3625
+ this.ffmpegResolving = null;
3626
+ });
3627
+ this.ffmpegResolving = attempt;
3628
+ return attempt;
3595
3629
  }
3596
3630
  /**
3597
3631
  * The integration page: a list of grids, with a NAME.
@@ -1,4 +1,4 @@
1
- import { Gn as BaseAddon, Gt as motionCapability, J as cameraGridLayoutCapability, T as MOTION_CLOSE_AFTER_MS, Un as DeviceType, Vn as DeviceFeature, Wn as errMsg, ar as hydrateSchema, dn as snapshotCapability, gn as streamCatalogCapability, h as DeclaredDevices, hr as EventCategory, i as BaseDevice, kn as buildFfmpegArgs, m as DERIVED_CAMERA_SILENCED_CAP_NAMES, ot as declarationOwnerNodeId, tr as createEvent } from "../../dist-BVHQ6TX1.mjs";
1
+ import { Gn as DeviceType, Kn as errMsg, Kt as memoizePinnedFfmpeg, T as MOTION_CLOSE_AFTER_MS, Un as DeviceFeature, Y as cameraGridLayoutCapability, Yt as motionCapability, _r as EventCategory, bn as streamCatalogCapability, h as DeclaredDevices, hn as snapshotCapability, i as BaseDevice, jn as buildFfmpegArgs, m as DERIVED_CAMERA_SILENCED_CAP_NAMES, qn as BaseAddon, rr as createEvent, sr as hydrateSchema, st as declarationOwnerNodeId } from "../../dist-Bs6UyVGd.mjs";
2
2
  import { z } from "zod";
3
3
  import { FfmpegProcess } from "@camstack/types/node";
4
4
  import { join } from "node:path";
@@ -2977,7 +2977,13 @@ var CameraGridAddon = class extends BaseAddon {
2977
2977
  activity = null;
2978
2978
  snapshots = null;
2979
2979
  frameSampler = null;
2980
- ffmpegBinaryPath = "ffmpeg";
2980
+ /** The node's pinned ffmpeg (D540). `null` until init resolved it — a
2981
+ * composition then refuses by name; there is no PATH default. */
2982
+ ffmpegBinaryPath = null;
2983
+ /** The node's pinned build, remembered only on success (D540). */
2984
+ pinnedFfmpeg = memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
2985
+ /** A resolution in flight, so a reconcile pass never starts a second. */
2986
+ ffmpegResolving = null;
2981
2987
  integrationId;
2982
2988
  constructor() {
2983
2989
  super({ ...DEFAULTS });
@@ -2986,17 +2992,13 @@ var CameraGridAddon = class extends BaseAddon {
2986
2992
  this.replaceTombstones(this.config.cameraGridTombstones);
2987
2993
  await this.normalizeRows();
2988
2994
  if (declarationOwnerNodeId(this.ctx.kernel.localNodeId) === "hub") {
2989
- this.ffmpegBinaryPath = await this.resolveFfmpegBinaryPath();
2995
+ await this.resolveFfmpeg();
2990
2996
  const relay = new GridStreamRelay({
2991
2997
  logger: this.ctx.logger.child("relay"),
2992
2998
  sessionFor: (request) => this.sessionFor(request)
2993
2999
  });
2994
3000
  await relay.start();
2995
3001
  this.relay = relay;
2996
- this.frameSampler = new GridFrameSampler({
2997
- binaryPath: this.ffmpegBinaryPath,
2998
- logger: this.ctx.logger.child("snapshot")
2999
- });
3000
3002
  this.snapshots = new GridSnapshotSource({
3001
3003
  logger: this.ctx.logger.child("snapshot"),
3002
3004
  sessionsFor: (deviceId) => this.sessionsForDevice(deviceId),
@@ -3336,7 +3338,19 @@ var CameraGridAddon = class extends BaseAddon {
3336
3338
  continue;
3337
3339
  }
3338
3340
  if (this.sessions.has(key)) continue;
3339
- this.sessions.set(key, new GridSessionEntry(instanceId, profile, this.makeSession(instanceId, deviceId, profile)));
3341
+ const binaryPath = this.ffmpegBinaryPath;
3342
+ if (binaryPath === null) {
3343
+ this.ctx.logger.error("camera-grid: composition refused — no pinned ffmpeg on this node (D540)", {
3344
+ tags: { deviceId },
3345
+ meta: {
3346
+ instanceId,
3347
+ profile
3348
+ }
3349
+ });
3350
+ this.resolveFfmpeg();
3351
+ continue;
3352
+ }
3353
+ this.sessions.set(key, new GridSessionEntry(instanceId, profile, this.makeSession(instanceId, deviceId, profile, binaryPath)));
3340
3354
  }
3341
3355
  }
3342
3356
  /**
@@ -3366,9 +3380,9 @@ var CameraGridAddon = class extends BaseAddon {
3366
3380
  }
3367
3381
  return names;
3368
3382
  }
3369
- makeSession(instanceId, deviceId, profile) {
3383
+ makeSession(instanceId, deviceId, profile, binaryPath) {
3370
3384
  const childContext = {
3371
- binaryPath: this.ffmpegBinaryPath,
3385
+ binaryPath,
3372
3386
  logger: this.ctx.logger,
3373
3387
  deviceId,
3374
3388
  resolveSourceNames: (deviceIds) => this.sourceNames(deviceIds)
@@ -3585,8 +3599,28 @@ var CameraGridAddon = class extends BaseAddon {
3585
3599
  if (!view) throw new Error("camera-grid: the grid vanished while it was being saved");
3586
3600
  return view;
3587
3601
  }
3588
- async resolveFfmpegBinaryPath() {
3589
- return await this.ctx.deps.ensureFfmpeg();
3602
+ /**
3603
+ * Resolve the pinned build, single-flighted, and hand it to the frame sampler.
3604
+ * Never throws: a failure is an ERROR and leaves the binary unknown, so the
3605
+ * next pass that wants a composition asks again. Never PATH: the host's
3606
+ * ffmpeg is not ours (`packages/types/src/deps/ffmpeg-binary-source.ts`).
3607
+ */
3608
+ resolveFfmpeg() {
3609
+ if (this.ffmpegBinaryPath !== null) return Promise.resolve();
3610
+ if (this.ffmpegResolving !== null) return this.ffmpegResolving;
3611
+ const attempt = this.pinnedFfmpeg().then((binaryPath) => {
3612
+ this.ffmpegBinaryPath = binaryPath;
3613
+ this.frameSampler = new GridFrameSampler({
3614
+ binaryPath,
3615
+ logger: this.ctx.logger.child("snapshot")
3616
+ });
3617
+ }).catch((err) => {
3618
+ this.ctx.logger.error("camera-grid: the pinned ffmpeg could not be resolved — compositions refuse until it is (D540)", { meta: { error: errMsg(err) } });
3619
+ }).finally(() => {
3620
+ this.ffmpegResolving = null;
3621
+ });
3622
+ this.ffmpegResolving = attempt;
3623
+ return attempt;
3590
3624
  }
3591
3625
  /**
3592
3626
  * The integration page: a list of grids, with a NAME.
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BMDYmNV3.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.js");
7
7
  const require_device_state_changed = require("../../device-state-changed-Bk7cvTnz.js");
8
- const require_composition_sources = require("../../composition-sources-gGrw35Lt.js");
8
+ const require_composition_sources = require("../../composition-sources-DkVxrXLm.js");
9
9
  //#region src/builtins/composer/composed-device.ts
10
10
  /**
11
11
  * A composed device: an ordinary `BaseDevice` whose caps are served by the
@@ -1,6 +1,6 @@
1
- import { An as cloneStatefulExpressionMemory, Bn as toExpressionValue, Fn as compositionSliceKey, Gn as BaseAddon, In as compositionSourceKey, K as buildCapabilityLookup, Ln as createStatefulExpressionMemory, Mn as composedDeviceStableId, Nn as compositionFeatureEnabled, Pn as compositionItemFieldPath, Un as DeviceType, Wn as errMsg, X as composerCapability, Xt as planComposition, Z as compositionFieldKey, c as COMPOSED_ITEM_FIXED_FIELDS, h as DeclaredDevices, hr as EventCategory, i as BaseDevice, jn as composedDeviceRef, t as ALL_CAPABILITY_DEFINITIONS, tr as createEvent, vt as evaluateCompositionField, wn as COMPOSER_ADDON_ID } from "../../dist-BVHQ6TX1.mjs";
1
+ import { Fn as compositionFeatureEnabled, Gn as DeviceType, Hn as toExpressionValue, In as compositionItemFieldPath, K as buildCapabilityLookup, Kn as errMsg, Ln as compositionSliceKey, Mn as cloneStatefulExpressionMemory, Nn as composedDeviceRef, On as COMPOSER_ADDON_ID, Pn as composedDeviceStableId, Q as compositionFieldKey, Rn as compositionSourceKey, Z as composerCapability, _r as EventCategory, c as COMPOSED_ITEM_FIXED_FIELDS, en as planComposition, h as DeclaredDevices, i as BaseDevice, qn as BaseAddon, rr as createEvent, t as ALL_CAPABILITY_DEFINITIONS, xt as evaluateCompositionField, zn as createStatefulExpressionMemory } from "../../dist-Bs6UyVGd.mjs";
2
2
  import { n as parseDeviceStateChanged, t as isRecord$2 } from "../../device-state-changed-DoBbkyEI.mjs";
3
- import { n as normalizedNativeOwner } from "../../composition-sources-iDqQVPng.mjs";
3
+ import { n as normalizedNativeOwner } from "../../composition-sources-CeAPNF5Q.mjs";
4
4
  import { z } from "zod";
5
5
  //#region src/builtins/composer/composed-device.ts
6
6
  /**
@@ -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-BMDYmNV3.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.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 { Gn as BaseAddon, Rt as logDestinationCapability } from "../../dist-BVHQ6TX1.mjs";
1
+ import { Vt as logDestinationCapability, qn as BaseAddon } from "../../dist-Bs6UyVGd.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,4 +1,4 @@
1
- import { CapabilityLookup, CompositionBlockPatch, CompositionBlockState, CompositionSourceRef, CoreBlock, ICoreBlocksProvider, IScopedLogger } from '@camstack/types';
1
+ import { CapabilityDefinition, CapabilityLookup, CompositionBlockPatch, CompositionBlockState, CompositionSourceRef, CoreBlock, ICoreBlocksProvider, IScopedLogger } from '@camstack/types';
2
2
  import { NamedCompositionInput } from './core-block-store.js';
3
3
  import { CompositionDescriptions } from './composition-sources.js';
4
4
  export interface CompositionStoreView {
@@ -17,8 +17,10 @@ export interface CompositionApiDeps {
17
17
  readonly composerStates: (blockIds: readonly string[]) => Promise<readonly CompositionBlockState[]>;
18
18
  readonly convergeSoon: () => void;
19
19
  readonly logger: IScopedLogger;
20
+ /** Every capability definition — the listing is derived from them, never from the registry's live state. */
21
+ readonly capabilityDefinitions: readonly CapabilityDefinition[];
20
22
  }
21
- export type CompositionApi = Pick<ICoreBlocksProvider, 'createComposition' | 'updateComposition' | 'getComposition' | 'listCompositions' | 'validateComposition' | 'getCustomization'>;
23
+ export type CompositionApi = Pick<ICoreBlocksProvider, 'createComposition' | 'updateComposition' | 'getComposition' | 'listCompositions' | 'validateComposition' | 'getCustomization' | 'listComposableCaps'>;
22
24
  /**
23
25
  * Every write runs validate-then-persist on ONE chain. Validation awaits a
24
26
  * source read before the save lands; two unserialized saves (A reads B, B
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-BMDYmNV3.js");
4
- const require_composition_sources = require("../../composition-sources-gGrw35Lt.js");
3
+ const require_dist = require("../../dist-Bup0sTZb.js");
4
+ const require_composition_sources = require("../../composition-sources-DkVxrXLm.js");
5
5
  let node_crypto = require("node:crypto");
6
6
  let _camstack_types_node = require("@camstack/types/node");
7
7
  let node_fs_promises = require("node:fs/promises");
@@ -1407,7 +1407,8 @@ function buildCompositionApi(deps) {
1407
1407
  }
1408
1408
  };
1409
1409
  },
1410
- validateComposition: async ({ composition, blockId }) => validateAgainstStore(deps, composition, blockId ?? null)
1410
+ validateComposition: async ({ composition, blockId }) => validateAgainstStore(deps, composition, blockId ?? null),
1411
+ listComposableCaps: async ({ deviceType }) => ({ caps: [...require_dist.describeComposableCaps(deps.capabilityDefinitions, deviceType)] })
1411
1412
  };
1412
1413
  }
1413
1414
  //#endregion
@@ -1705,7 +1706,8 @@ var CoreBlocksAddon = class extends require_dist.BaseAddon {
1705
1706
  lookupCap: require_dist.buildCapabilityLookup(require_dist.ALL_CAPABILITY_DEFINITIONS),
1706
1707
  composerStates: async (blockIds) => (await requireApi().composer.getStates.query({ blockIds: [...blockIds] })).states,
1707
1708
  convergeSoon: () => this.convergeSoon(),
1708
- logger: this.ctx.logger.child("compositions")
1709
+ logger: this.ctx.logger.child("compositions"),
1710
+ capabilityDefinitions: require_dist.ALL_CAPABILITY_DEFINITIONS
1709
1711
  });
1710
1712
  }
1711
1713
  };
@@ -1,5 +1,5 @@
1
- import { $ as coreBlockAddonId, Gn as BaseAddon, In as compositionSourceKey, K as buildCapabilityLookup, Q as compositionSourceRefs, Wn as errMsg, Xt as planComposition, d as CORE_BLOCK_ADDON_PREFIX, et as coreBlockComposition, f as CoreBlockSchema, h as DeclaredDevices, jn as composedDeviceRef, l as COMPOSER_RUNNER_ID, nt as coreBlockSourceKind, rt as coreBlocksCapability, t as ALL_CAPABILITY_DEFINITIONS, tn as replacedFieldKeys, tt as coreBlockIdFromAddonId, u as CORE_BLOCKS_ADDON_ID, yn as validateComposition } from "../../dist-BVHQ6TX1.mjs";
2
- import { t as describeCompositionSources } from "../../composition-sources-iDqQVPng.mjs";
1
+ import { $ as compositionSourceRefs, Cn as validateComposition, K as buildCapabilityLookup, Kn as errMsg, Nn as composedDeviceRef, Rn as compositionSourceKey, an as replacedFieldKeys, d as CORE_BLOCK_ADDON_PREFIX, dt as describeComposableCaps, en as planComposition, et as coreBlockAddonId, f as CoreBlockSchema, h as DeclaredDevices, it as coreBlocksCapability, l as COMPOSER_RUNNER_ID, nt as coreBlockIdFromAddonId, qn as BaseAddon, rt as coreBlockSourceKind, t as ALL_CAPABILITY_DEFINITIONS, tt as coreBlockComposition, u as CORE_BLOCKS_ADDON_ID } from "../../dist-Bs6UyVGd.mjs";
2
+ import { t as describeCompositionSources } from "../../composition-sources-CeAPNF5Q.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { customizationBlockName } from "@camstack/types/node";
5
5
  import * as fs from "node:fs/promises";
@@ -1403,7 +1403,8 @@ function buildCompositionApi(deps) {
1403
1403
  }
1404
1404
  };
1405
1405
  },
1406
- validateComposition: async ({ composition, blockId }) => validateAgainstStore(deps, composition, blockId ?? null)
1406
+ validateComposition: async ({ composition, blockId }) => validateAgainstStore(deps, composition, blockId ?? null),
1407
+ listComposableCaps: async ({ deviceType }) => ({ caps: [...describeComposableCaps(deps.capabilityDefinitions, deviceType)] })
1407
1408
  };
1408
1409
  }
1409
1410
  //#endregion
@@ -1701,7 +1702,8 @@ var CoreBlocksAddon = class extends BaseAddon {
1701
1702
  lookupCap: buildCapabilityLookup(ALL_CAPABILITY_DEFINITIONS),
1702
1703
  composerStates: async (blockIds) => (await requireApi().composer.getStates.query({ blockIds: [...blockIds] })).states,
1703
1704
  convergeSoon: () => this.convergeSoon(),
1704
- logger: this.ctx.logger.child("compositions")
1705
+ logger: this.ctx.logger.child("compositions"),
1706
+ capabilityDefinitions: ALL_CAPABILITY_DEFINITIONS
1705
1707
  });
1706
1708
  }
1707
1709
  };
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BMDYmNV3.js");
7
- const require_retired_settings_keys = require("../../retired-settings-keys-FhjpCM5N.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.js");
7
+ const require_retired_settings_keys = require("../../retired-settings-keys-Db0P4Yvn.js");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
10
10
  let _camstack_types_node = require("@camstack/types/node");
@@ -1,5 +1,5 @@
1
- import { Gn as BaseAddon, Hn as DeviceRole, Kt as normalizeUnit, Lt as locationIconKey, Nt as legacyAutoLinkIds, S as LocationIconIdSchema, Un as DeviceType, Vn as DeviceFeature, Wn as errMsg, Yt as parseStreamParamsFormPatch, Zn as WELL_KNOWN_TAB_MAP, _t as enumerateSchemaFields, an as runtimeStatePolicyFor, bt as expandContainer, ct as defineExtensionSlotProvider, dt as deviceManagerCapability, ft as deviceStateCapability, g as DeviceStatusSchema, gt as enumerateItemArrayFields, hr as EventCategory, it as createDeviceExtensionProvider, j as STREAM_PROFILE_META, mr as sleep$1, o as CAMERA_SWITCH_ORDER, or as isDeviceConfigCap, p as DAY_NAMES, pt as deviceStatusCapability, q as buildStreamParamsConfigSchema, s as CAP_NAMES_WITH_STATUS, t as ALL_CAPABILITY_DEFINITIONS, un as siteZoneKey, ut as deviceExtensionCapability, v as FieldClaimModeSchema, w as MINUTES_PER_DAY, wt as formatMinutes, y as FieldClaimSchema } from "../../dist-BVHQ6TX1.mjs";
2
- import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-BlQLUReM.mjs";
1
+ import { $n as WELL_KNOWN_TAB_MAP, $t as parseStreamParamsFormPatch, Bt as locationIconKey, Ct as expandContainer, Dt as formatMinutes, Gn as DeviceType, It as legacyAutoLinkIds, J as buildStreamParamsConfigSchema, Kn as errMsg, S as LocationIconIdSchema, Un as DeviceFeature, Wn as DeviceRole, Xt as normalizeUnit, _r as EventCategory, at as createDeviceExtensionProvider, bt as enumerateSchemaFields, cr as isDeviceConfigCap, ft as deviceExtensionCapability, g as DeviceStatusSchema, gr as sleep$1, ht as deviceStatusCapability, j as STREAM_PROFILE_META, ln as runtimeStatePolicyFor, lt as defineExtensionSlotProvider, mn as siteZoneKey, mt as deviceStateCapability, o as CAMERA_SWITCH_ORDER, p as DAY_NAMES, pt as deviceManagerCapability, qn as BaseAddon, s as CAP_NAMES_WITH_STATUS, t as ALL_CAPABILITY_DEFINITIONS, v as FieldClaimModeSchema, w as MINUTES_PER_DAY, y as FieldClaimSchema, yt as enumerateItemArrayFields } from "../../dist-Bs6UyVGd.mjs";
2
+ import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-LROp1DXx.mjs";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
@@ -1,4 +1,4 @@
1
- import { CompositionFieldsFeature, CompositionSourceRef, DeviceType, ICoreBlocksProvider, IScopedLogger } from '@camstack/types';
1
+ import { DeviceType, ICoreBlocksProvider, IScopedLogger } from '@camstack/types';
2
2
  import { BindingEntryView } from './binding-mirror.js';
3
3
  import { DoorbellSource } from './doorbell-settings.js';
4
4
  export declare const DOORBELL_COMPOSITION_MIGRATION_STATE_KEY = "doorbell-source-composition-migration-v1";
@@ -32,17 +32,6 @@ export interface DoorbellCompositionMigrationDeps {
32
32
  readonly isComplete: () => Promise<boolean>;
33
33
  readonly markComplete: () => Promise<void>;
34
34
  }
35
- interface DoorbellFeatureSource {
36
- readonly source: CompositionSourceRef;
37
- readonly cap: string;
38
- readonly activeField: string;
39
- readonly changedAtField: string;
40
- }
41
- export declare function buildDoorbellCompositionFeature(input: {
42
- readonly camera: CompositionSourceRef;
43
- readonly sources: readonly DoorbellFeatureSource[];
44
- readonly enabled: boolean;
45
- }): CompositionFieldsFeature;
46
35
  export declare class DoorbellCompositionMigration {
47
36
  private readonly deps;
48
37
  private rerunRequested;
@@ -56,4 +45,3 @@ export declare class DoorbellCompositionMigration {
56
45
  private runAttempt;
57
46
  private warnCamera;
58
47
  }
59
- export {};
@@ -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-BMDYmNV3.js");
6
+ const require_dist = require("../../dist-Bup0sTZb.js");
7
7
  const require_device_state_changed = require("../../device-state-changed-Bk7cvTnz.js");
8
8
  let zod = require("zod");
9
9
  //#region src/builtins/doorbell/trigger-engine.ts
@@ -612,50 +612,6 @@ var DOORBELL_COMPOSITION_MIGRATION_CALLER = {
612
612
  userId: "system:doorbell-source-migration",
613
613
  isAdmin: true
614
614
  };
615
- function buildDoorbellCompositionFeature(input) {
616
- const sources = [...input.sources].toSorted((a, b) => `${a.source.addonId}/${a.source.stableId}#${a.cap}`.localeCompare(`${b.source.addonId}/${b.source.stableId}#${b.cap}`));
617
- if (sources.length === 0) throw new Error("doorbell composition needs at least one source");
618
- const edgeBindings = {};
619
- sources.forEach((entry, index) => {
620
- edgeBindings[`active${index}`] = {
621
- kind: "from",
622
- source: entry.source,
623
- cap: entry.cap,
624
- fieldPath: entry.activeField
625
- };
626
- edgeBindings[`changed${index}`] = {
627
- kind: "from",
628
- source: entry.source,
629
- cap: entry.cap,
630
- fieldPath: entry.changedAtField
631
- };
632
- });
633
- return {
634
- kind: "fields",
635
- cap: "doorbell",
636
- enabled: input.enabled,
637
- fields: {
638
- lastPressedAt: {
639
- kind: "expression",
640
- expr: `latest(${sources.map((_, index) => `rose(active${index}, changed${index}, 2000)`).join(", ")})`,
641
- bindings: edgeBindings
642
- },
643
- pressCountSinceStart: {
644
- kind: "expression",
645
- expr: "count(lastPressedAt)",
646
- bindings: { lastPressedAt: {
647
- kind: "from",
648
- source: {
649
- addonId: input.camera.addonId,
650
- stableId: input.camera.stableId
651
- },
652
- cap: "doorbell",
653
- fieldPath: "lastPressedAt"
654
- } }
655
- }
656
- }
657
- };
658
- }
659
615
  function isRecord(value) {
660
616
  return typeof value === "object" && value !== null && !Array.isArray(value);
661
617
  }
@@ -671,32 +627,18 @@ function withoutEnabled(feature) {
671
627
  const { enabled: _enabled, ...rest } = feature;
672
628
  return rest;
673
629
  }
674
- function bindingHasCap(entries, cap) {
675
- return entries.some((entry) => entry.capName === cap);
676
- }
677
630
  function migrationSources(camera, configured, devicesById, bindingsById) {
678
631
  const sources = [];
679
632
  for (const configuredSource of configured) {
680
633
  const device = devicesById.get(configuredSource.deviceId);
681
634
  const entries = bindingsById.get(configuredSource.deviceId);
682
635
  if (device === void 0 || entries === void 0) throw new Error(`source device ${configuredSource.deviceId} could not be resolved and read`);
683
- const ref = {
636
+ const found = require_dist.doorbellFeatureSources({
684
637
  addonId: device.addonId,
685
638
  stableId: device.stableId
686
- };
687
- const supportedCaps = Object.keys(require_dist.SOURCE_CAP_ACTIVE_FIELD).filter((cap) => Object.hasOwn(require_dist.SOURCE_CAP_CHANGED_AT_FIELD, cap) && bindingHasCap(entries, cap)).toSorted();
688
- if (supportedCaps.length === 0) throw new Error(`source device ${configuredSource.deviceId} has no supported source capability`);
689
- for (const cap of supportedCaps) {
690
- const activeField = require_dist.SOURCE_CAP_ACTIVE_FIELD[cap];
691
- const changedAtField = require_dist.SOURCE_CAP_CHANGED_AT_FIELD[cap];
692
- if (activeField === void 0 || changedAtField === void 0) throw new Error(`source capability ${cap} has no transition field mapping`);
693
- sources.push({
694
- source: ref,
695
- cap,
696
- activeField,
697
- changedAtField
698
- });
699
- }
639
+ }, entries.map((entry) => entry.capName));
640
+ if (found.length === 0) throw new Error(`source device ${configuredSource.deviceId} has no supported source capability`);
641
+ sources.push(...found);
700
642
  }
701
643
  if (sources.length === 0) throw new Error(`camera ${camera.id} has no resolvable doorbell sources`);
702
644
  return sources;
@@ -803,7 +745,7 @@ var DoorbellCompositionMigration = class {
803
745
  const composerBound = composerIsBound(entries);
804
746
  const crashRecovery = !wrapperActive && composerBound && existingDoorbell?.kind === "fields" && existingDoorbell.enabled === true;
805
747
  const enabled = wrapperActive || crashRecovery;
806
- const canonical = buildDoorbellCompositionFeature({
748
+ const canonical = require_dist.buildDoorbellCompositionFeature({
807
749
  camera: cameraRef,
808
750
  sources,
809
751
  enabled