@camstack/addon-pipeline 1.2.370 → 1.2.371

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 (37) hide show
  1. package/dist/audio-analyzer/index.js +3 -3
  2. package/dist/audio-analyzer/index.mjs +3 -3
  3. package/dist/{custom-model-catalog-CVbK0xKc.mjs → custom-model-catalog-C6C2cSwd.mjs} +2 -2
  4. package/dist/{custom-model-catalog-BFosahFu.js → custom-model-catalog-n9v8MUL6.js} +2 -2
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-CbFPOFAD.mjs → dist-8AEiKOFO.mjs} +291 -21
  8. package/dist/{dist-jkwxbcbK.js → dist-DmuX7IFJ.js} +308 -20
  9. package/dist/{model-catalogs-DJmwfLfC.mjs → model-catalogs-BSWAHZJh.mjs} +1 -1
  10. package/dist/{model-catalogs-Ds2hC2Br.js → model-catalogs-DO4aA1qo.js} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-C-2fB9kH.mjs → node-Bd4va7c-.mjs} +1 -1
  14. package/dist/{node-C_1U1uzn.js → node-DqA6nD1z.js} +1 -1
  15. package/dist/pipeline-runner/index.js +3 -3
  16. package/dist/pipeline-runner/index.mjs +3 -3
  17. package/dist/{process-memory-Cfh1BQYg.js → process-memory-2OQ2oxrz.js} +1 -1
  18. package/dist/{process-memory-B3I-NvnX.mjs → process-memory-BvS2Z88w.mjs} +1 -1
  19. package/dist/recorder/index.js +184 -54
  20. package/dist/recorder/index.mjs +184 -54
  21. package/dist/{segment-demux-js-uxBcJy15.js → segment-demux-js-BkGK1rhu.js} +1 -1
  22. package/dist/{segment-demux-js-D3JBJeN6.mjs → segment-demux-js-By9MLn_g.mjs} +1 -1
  23. package/dist/stream-broker/_stub.js +1188 -861
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DwwDKaZJ.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DjoAY42S.mjs} +3 -3
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C3ssSNKh.mjs +26 -0
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-nua-KuDg.mjs +26 -0
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-DVwYY7oo.mjs → hostInit-DPiGNc5u.mjs} +3 -3
  30. package/dist/stream-broker/index.js +3 -3
  31. package/dist/stream-broker/index.mjs +3 -3
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/i18n/en.json +8 -0
  34. package/i18n/it.json +8 -0
  35. package/package.json +6 -1
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Bna0L8uC.mjs +0 -26
  37. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BAvXcDKy.mjs +0 -26
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-jkwxbcbK.js");
7
- const require_node = require("../node-C_1U1uzn.js");
6
+ const require_dist = require("../dist-DmuX7IFJ.js");
7
+ const require_node = require("../node-DqA6nD1z.js");
8
8
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
9
9
  const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
10
10
  const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
@@ -4996,9 +4996,10 @@ function archiveUsageComplete(ledger, locationIds) {
4996
4996
  /**
4997
4997
  * Per-device recording-config persistence for the recorder addon.
4998
4998
  *
4999
- * ONE ROW PER CAMERA in `recorder:recording-configs`, through
5000
- * {@link RowMapStore}. Asking what camera 617 records is a primary-key lookup;
5001
- * saving its config writes that row and touches no other.
4999
+ * A camera's config is its PER-DEVICE SETTINGS (D865), key `recordingConfig`,
5000
+ * through `ctx.settings.*DeviceStore`. Asking what camera 617 records is one
5001
+ * scope read; saving its config writes that key and touches no other camera.
5002
+ * Deleting the camera clears the scope centrally.
5002
5003
  *
5003
5004
  * ## What this replaced, and why
5004
5005
  *
@@ -5019,54 +5020,79 @@ function archiveUsageComplete(ledger, locationIds) {
5019
5020
  * empty fallback (= the whole fleet silently unconfigured).
5020
5021
  */
5021
5022
  /**
5022
- * @durable class=config owner=recorder
5023
- * write="one row per camera, written by `saveDeviceConfig` (the
5024
- * `recording-archive` cap's setDeviceConfig / band authoring) — the only
5025
- * writer"
5026
- * retention="none by age. A row goes only when the operator deletes the camera's
5027
- * configuration; an unconfigured camera has no row and reads as DEFAULT_DEVICE_CONFIG"
5023
+ * READ is forgiving where the wire contract is strict — see the header.
5024
+ * `.strip()` keeps a row written before a key was retired readable.
5025
+ *
5026
+ * The preprocess is the D371→D380 migration, applied on EVERY read so a row
5027
+ * behaves correctly from the first read of the boot, before
5028
+ * {@link migrateLegacyDeviceSignalRows} has persisted anything. Pure and
5029
+ * idempotent — a row with no legacy key passes through by reference.
5028
5030
  */
5029
- var RECORDING_CONFIGS_COLLECTION = "recorder:recording-configs";
5030
- var RECORDING_CONFIGS_SPEC = {
5031
- collection: RECORDING_CONFIGS_COLLECTION,
5032
- schema: require_dist.preprocess((raw) => require_dist.migrateLegacyDeviceSignalConfig(raw).value, require_dist.RecordingConfigSchema.strip()).transform((config) => require_dist.stripRetiredBandPreBufferSec(config).config),
5033
- columns: [
5034
- {
5035
- name: "deviceId",
5036
- type: "INTEGER",
5037
- notNull: true
5038
- },
5039
- {
5040
- name: "enabled",
5041
- type: "BOOLEAN",
5042
- notNull: true
5043
- },
5044
- {
5045
- name: "mode",
5046
- type: "TEXT"
5031
+ var StoredRecordingConfigSchema = require_dist.preprocess((raw) => require_dist.migrateLegacyDeviceSignalConfig(raw).value, require_dist.RecordingConfigSchema.strip()).transform((config) => require_dist.stripRetiredBandPreBufferSec(config).config);
5032
+ /** The per-device settings key a camera's recording config lives under. */
5033
+ var RECORDING_CONFIG_KEY = "recordingConfig";
5034
+ var DeviceSettingsConfigsState = class {
5035
+ deps;
5036
+ constructor(deps) {
5037
+ this.deps = deps;
5038
+ }
5039
+ async declare() {}
5040
+ parse(deviceId, raw) {
5041
+ const parsed = StoredRecordingConfigSchema.safeParse(raw);
5042
+ if (parsed.success) return parsed.data;
5043
+ this.deps.logger.warn("recording config row skipped — it does not parse", {
5044
+ tags: { deviceId },
5045
+ meta: { error: parsed.error.message }
5046
+ });
5047
+ return null;
5048
+ }
5049
+ async read(key) {
5050
+ const deviceId = Number(key);
5051
+ if (!Number.isInteger(deviceId)) return null;
5052
+ const held = (await this.deps.settings.readDeviceStore(deviceId))[RECORDING_CONFIG_KEY];
5053
+ return held === void 0 ? null : this.parse(deviceId, held);
5054
+ }
5055
+ async readAllRaw() {
5056
+ const out = /* @__PURE__ */ new Map();
5057
+ const list = this.deps.settings.listDeviceStoreIds;
5058
+ if (list === void 0) throw new Error("the per-device settings cannot list their devices on this view");
5059
+ const ids = await list.call(this.deps.settings);
5060
+ const batch = this.deps.settings.readDeviceStoreBatch;
5061
+ const blobs = batch === void 0 ? new Map(await Promise.all(ids.map(async (id) => [id, await this.deps.settings.readDeviceStore(id)]))) : await batch.call(this.deps.settings, ids);
5062
+ for (const [id, blob] of blobs) {
5063
+ const held = blob[RECORDING_CONFIG_KEY];
5064
+ if (held !== void 0) out.set(String(id), held);
5047
5065
  }
5048
- ],
5049
- project: (key, value) => ({
5050
- deviceId: Number(key),
5051
- enabled: value.enabled,
5052
- mode: value.mode ?? null
5053
- }),
5054
- deviceIdColumn: "deviceId",
5055
- loadLimit: 2e4
5066
+ return out;
5067
+ }
5068
+ async readAll() {
5069
+ const out = /* @__PURE__ */ new Map();
5070
+ for (const [key, raw] of await this.readAllRaw()) {
5071
+ const parsed = this.parse(Number(key), raw);
5072
+ if (parsed !== null) out.set(key, parsed);
5073
+ }
5074
+ return out;
5075
+ }
5076
+ async write(key, value) {
5077
+ const deviceId = Number(key);
5078
+ if (!Number.isInteger(deviceId)) throw new Error(`recording config: bad device id "${key}"`);
5079
+ await this.deps.settings.writeDeviceStore(deviceId, { [RECORDING_CONFIG_KEY]: value });
5080
+ }
5081
+ async remove(key) {
5082
+ const deviceId = Number(key);
5083
+ if (!Number.isInteger(deviceId)) return;
5084
+ await this.deps.settings.writeDeviceStore(deviceId, { [RECORDING_CONFIG_KEY]: void 0 });
5085
+ }
5056
5086
  };
5087
+ /** Build the handle over the standard per-device settings. */
5088
+ function recordingConfigsState(deps) {
5089
+ return new DeviceSettingsConfigsState(deps);
5090
+ }
5057
5091
  /** Cold-start default for a camera that has never been configured: disabled, no bands. */
5058
5092
  var DEFAULT_DEVICE_CONFIG = {
5059
5093
  enabled: false,
5060
5094
  bands: []
5061
5095
  };
5062
- /** Build the handle. `declare()` MUST run once at boot before any read/write. */
5063
- function recordingConfigsState(store, logger) {
5064
- return new require_retire_root_keys.RowMapStore({
5065
- spec: RECORDING_CONFIGS_SPEC,
5066
- store,
5067
- logger
5068
- });
5069
- }
5070
5096
  /** Read every persisted device config, keyed by numeric deviceId. */
5071
5097
  async function readDeviceConfigs(state) {
5072
5098
  const rows = await state.readAll();
@@ -6194,6 +6220,7 @@ Object.freeze({
6194
6220
  "device-export": ["listExposedDevices", "listSupportedDeviceKinds"],
6195
6221
  "device-extension": ["listSlots", "listSources"],
6196
6222
  "device-provider": ["discoverDevices", "getDevices"],
6223
+ "device-removal-hook": ["listDeviceData"],
6197
6224
  "failure-contribution": ["list"],
6198
6225
  "llm": [
6199
6226
  "getDefaults",
@@ -11106,7 +11133,7 @@ function buildRecordingProvider(deps) {
11106
11133
  limit
11107
11134
  }) ?? [],
11108
11135
  renderClip: async (input) => {
11109
- if (!deps.clipRender) throw new Error("clip rendering unavailable on this node");
11136
+ if (!deps.clipRender) throw require_dist.userError("recording.clip-render-unavailable", void 0, "clip rendering unavailable on this node");
11110
11137
  return deps.clipRender(input);
11111
11138
  },
11112
11139
  renderGif: async (input) => {
@@ -20079,7 +20106,7 @@ async function hasFootage(index, deviceId, profile, fromMs, toMs) {
20079
20106
  /** Load a record or throw a clear NOT_FOUND-style error. */
20080
20107
  async function requireRecord(state, exportId) {
20081
20108
  const rec = await getExportRecord(state, exportId);
20082
- if (!rec) throw new Error(`recording-export: unknown export "${exportId}"`);
20109
+ if (!rec) throw require_dist.userError("recording.export-unknown", { id: exportId }, `recording-export: unknown export "${exportId}"`);
20083
20110
  return rec;
20084
20111
  }
20085
20112
  /** Build the `recordingExport` provider over the injected deps. */
@@ -20192,11 +20219,14 @@ function buildRecordingExportProvider(deps) {
20192
20219
  if (subject.kind === "clip") return await createClipExport(subject, input.options);
20193
20220
  const { deviceId, profiles, fromMs, toMs } = subject;
20194
20221
  const { options } = input;
20195
- if (!(fromMs < toMs)) throw new Error(`recording-export: invalid range ${fromMs}..${toMs}`);
20222
+ if (!(fromMs < toMs)) throw require_dist.userError("recording.export-invalid-range", {
20223
+ fromMs,
20224
+ toMs
20225
+ }, `recording-export: invalid range ${fromMs}..${toMs}`);
20196
20226
  for (const profile of profiles) if (!await hasFootage(deps.index, deviceId, profile, fromMs, toMs)) throw new Error(`recording-export: no footage for ${deviceId}/${profile} in ${fromMs}..${toMs}`);
20197
20227
  const now = deps.now();
20198
20228
  const [head, ...rest] = profiles;
20199
- if (head === void 0) throw new Error("recording-export: profiles must contain at least one profile");
20229
+ if (head === void 0) throw require_dist.userError("recording.export-no-profiles", void 0, "recording-export: profiles must contain at least one profile");
20200
20230
  const first = await queueOne({
20201
20231
  deviceId,
20202
20232
  profile: head,
@@ -20247,7 +20277,10 @@ function buildRecordingExportProvider(deps) {
20247
20277
  state: rec.state
20248
20278
  }
20249
20279
  });
20250
- throw new Error(`recording-export: export "${exportId}" is not ready (${rec.state})`);
20280
+ throw require_dist.userError("recording.export-not-ready", {
20281
+ id: exportId,
20282
+ state: rec.state
20283
+ }, `recording-export: export "${exportId}" is not ready (${rec.state})`);
20251
20284
  }
20252
20285
  if (deps.now() >= rec.expiresAt) {
20253
20286
  deps.logger.warn("export byte read refused: export has expired", {
@@ -20257,7 +20290,7 @@ function buildRecordingExportProvider(deps) {
20257
20290
  expiresAt: rec.expiresAt
20258
20291
  }
20259
20292
  });
20260
- throw new Error(`recording-export: export "${exportId}" has expired`);
20293
+ throw require_dist.userError("recording.export-expired", { id: exportId }, `recording-export: export "${exportId}" has expired`);
20261
20294
  }
20262
20295
  const file = deps.outPath(rec);
20263
20296
  let size;
@@ -20271,7 +20304,7 @@ function buildRecordingExportProvider(deps) {
20271
20304
  error: String(err)
20272
20305
  }
20273
20306
  });
20274
- throw new Error(`recording-export: export "${exportId}" has no file on disk`, { cause: err });
20307
+ throw Object.assign(require_dist.userError("recording.export-no-file", { id: exportId }, `recording-export: export "${exportId}" has no file on disk`), { cause: err });
20275
20308
  }
20276
20309
  if (size > 52428800) {
20277
20310
  const shrink = deps.shrinkToBound;
@@ -20352,6 +20385,75 @@ function buildRecordingExportProvider(deps) {
20352
20385
  * the host extracted from `CAMSTACK_HUB_URL`).
20353
20386
  */
20354
20387
  var resolveRecordingHubHostname = require_hub_hostname.resolveHubHostname;
20388
+ async function nextVictims(deps, deviceId) {
20389
+ const archive = deps.archive?.() ?? null;
20390
+ if (archive === null) return null;
20391
+ const victims = [];
20392
+ for await (const page of archive.streamSegments({ deviceId })) {
20393
+ victims.push(...page);
20394
+ if (victims.length >= 2e4) break;
20395
+ }
20396
+ return victims;
20397
+ }
20398
+ /** Delete every segment the archive holds for `deviceId`, across every location of this node. */
20399
+ async function purgeDeviceFootage(deps, deviceId) {
20400
+ let deletedBuckets = 0;
20401
+ let reclaimedBytes = 0;
20402
+ for (let pass = 0; pass < 200; pass++) {
20403
+ const victims = await nextVictims(deps, deviceId);
20404
+ if (victims === null) throw new Error("no durable hour ledger on this node — footage cannot be enumerated");
20405
+ if (victims.length === 0) break;
20406
+ const freed = await evictVictims(deps, deviceId, victims);
20407
+ deletedBuckets += freed.deletedBuckets;
20408
+ reclaimedBytes += freed.reclaimedBytes;
20409
+ if (freed.deletedBuckets === 0) break;
20410
+ }
20411
+ const census = await deps.archive?.()?.census({ deviceId });
20412
+ if (census === null || census === void 0 || census.segments > 0) throw new Error(`footage of device ${deviceId} is not fully removed (${census?.segments ?? "unknown"} segments left)`);
20413
+ if (deletedBuckets > 0) await deps.opsLog?.append({
20414
+ op: "prune",
20415
+ reason: "orphaned-device",
20416
+ deviceId,
20417
+ itemsAffected: deletedBuckets,
20418
+ bytesReclaimed: reclaimedBytes,
20419
+ detail: "the device was deleted — its footage was removed with it"
20420
+ });
20421
+ return {
20422
+ deletedBuckets,
20423
+ reclaimedBytes
20424
+ };
20425
+ }
20426
+ function createRecorderRemovalHook(deps) {
20427
+ const removeDevice = async (input) => {
20428
+ const { deviceId } = input;
20429
+ const footage = await purgeDeviceFootage(deps, deviceId);
20430
+ const counts = { footage: footage.deletedBuckets };
20431
+ if (footage.deletedBuckets > 0) deps.logger.info("recorder: removed the data of a deleted device", {
20432
+ tags: { deviceId },
20433
+ meta: {
20434
+ counts,
20435
+ reclaimedBytes: footage.reclaimedBytes,
20436
+ deletedAt: input.deletedAt
20437
+ }
20438
+ });
20439
+ return { counts };
20440
+ };
20441
+ return {
20442
+ onDeviceRemoved: removeDevice,
20443
+ listDeviceData: async () => {
20444
+ const out = [];
20445
+ const locationIds = new Set(deps.locations().map((l) => l.id));
20446
+ const usage = deps.archiveDeviceUsage?.(locationIds) ?? null;
20447
+ if (usage === null) throw new Error("no durable hour ledger on this node — footage cannot be enumerated");
20448
+ for (const [deviceId, u] of usage) if (u.segments > 0) out.push({
20449
+ store: "footage",
20450
+ deviceId,
20451
+ count: u.segments
20452
+ });
20453
+ return out;
20454
+ }
20455
+ };
20456
+ }
20355
20457
  //#endregion
20356
20458
  //#region src/recorder/addon/recording-source-provider.ts
20357
20459
  /** What the picker calls us. */
@@ -20858,7 +20960,7 @@ var RECORDER_RETIRED_ROOT_KEYS = [
20858
20960
  },
20859
20961
  {
20860
20962
  key: "recordingConfigs",
20861
- successor: RECORDING_CONFIGS_COLLECTION,
20963
+ successor: "the recorder per-device settings (key recordingConfig)",
20862
20964
  reason: "one row per camera since the flatten; as a blob, asking what camera 617 records parsed every camera in the installation"
20863
20965
  }
20864
20966
  ];
@@ -23213,6 +23315,8 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
23213
23315
  * Already ALIAS-GUARDED: a writable same-type duplicate of another
23214
23316
  * location's physical root arrives here demoted to read-only. */
23215
23317
  resolvedLocations = [];
23318
+ /** The `device-removal-hook` provider (D865); its reconcile is armed in `onHubReachable`. */
23319
+ removalHook = null;
23216
23320
  /** Attach-boundary placement (assignment + mount probe + audit). Null on
23217
23321
  * non-recording nodes / before `onInitialize`. */
23218
23322
  placement = null;
@@ -23916,6 +24020,8 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
23916
24020
  migrateLegacyDeviceSignalRows(this.configStore(), this.ctx.logger).catch((err) => {
23917
24021
  this.ctx.logger.warn("recorder: device-signal config migration failed", { meta: { error: require_dist.errMsg(err) } });
23918
24022
  }).then(() => warnLostBands(this.configStore(), this.ctx.logger));
24023
+ const removalHook = createRecorderRemovalHook(recordingDeps);
24024
+ this.removalHook = removalHook;
23919
24025
  const providers = [
23920
24026
  {
23921
24027
  capability: require_dist.recordingArchiveCapability,
@@ -23947,6 +24053,10 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
23947
24053
  {
23948
24054
  capability: require_dist.loadContributionCapability,
23949
24055
  provider: { list: () => this.costRegistry.contributions() }
24056
+ },
24057
+ {
24058
+ capability: require_dist.deviceRemovalHookCapability,
24059
+ provider: removalHook
23950
24060
  }
23951
24061
  ];
23952
24062
  this.footageDiscard = createFootageDiscardJob({
@@ -24078,6 +24188,12 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
24078
24188
  async onHubReachable() {
24079
24189
  if (!this.isRecordingNode) return;
24080
24190
  await this.refreshLocations();
24191
+ const removalHook = this.removalHook;
24192
+ if (removalHook !== null) this.ctx.addDisposer(require_dist.armRemovalReconcile({
24193
+ api: this.ctx.api,
24194
+ logger: this.ctx.logger,
24195
+ remove: (input) => removalHook.onDeviceRemoved(input)
24196
+ }));
24081
24197
  try {
24082
24198
  this.exportsRoot = await this.ctx.api.storage.resolve.query({
24083
24199
  location: ARTIFACTS_MEDIA_LOCATION,
@@ -25059,19 +25175,25 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
25059
25175
  return this.schema({ sections: [{
25060
25176
  id: "cluster-recording",
25061
25177
  title: "Cluster recording",
25178
+ titleKey: "pipeline:globalSettings.s_cluster-recording.title",
25062
25179
  description: "Which node runs recording (continuous + events write path). A SINGLE node — no multi-node balancing; every other node stays fully inert. Default: hub.",
25180
+ descriptionKey: "pipeline:globalSettings.s_cluster-recording.description",
25063
25181
  columns: 1,
25064
25182
  fields: [{
25065
25183
  type: "text",
25066
25184
  key: "recordingNodeId",
25067
25185
  label: "Recording node",
25186
+ labelKey: "pipeline:globalSettings.s_cluster-recording.recordingNodeId.label",
25068
25187
  description: "Node id that records + serves playback (e.g. \"hub\"). Leave \"hub\" unless you intentionally move recording to an agent. Takes effect on addon restart.",
25188
+ descriptionKey: "pipeline:globalSettings.s_cluster-recording.recordingNodeId.description",
25069
25189
  default: RECORDING_NODE_DEFAULT
25070
25190
  }, {
25071
25191
  type: "text",
25072
25192
  key: "recordingHubHostname",
25073
25193
  label: "Hub host (cross-node)",
25194
+ labelKey: "pipeline:globalSettings.s_cluster-recording.recordingHubHostname.label",
25074
25195
  description: "Advanced: only used when the recording node is an AGENT. The hub's host the agent dials to pull broker streams (the RTSP restream port must be reachable there). Leave empty to auto-derive from the agent's hub connection.",
25196
+ descriptionKey: "pipeline:globalSettings.s_cluster-recording.recordingHubHostname.description",
25075
25197
  default: ""
25076
25198
  }]
25077
25199
  }] });
@@ -25145,8 +25267,16 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
25145
25267
  this.censusedSettingsStoreClient ??= censusStoreClient(this.ctx.api.settingsStore, this.readCensus);
25146
25268
  return this.censusedSettingsStoreClient;
25147
25269
  }
25270
+ configsState = null;
25271
+ /** A camera's recording config — its per-device settings (D865), migrated on read. */
25148
25272
  configStore() {
25149
- return recordingConfigsState(this.censusedSettingsStore(), this.ctx.logger);
25273
+ const settings = this.ctx.settings;
25274
+ if (settings === void 0) throw new Error("recorder: device settings are not available");
25275
+ this.configsState ??= recordingConfigsState({
25276
+ settings,
25277
+ logger: this.ctx.logger
25278
+ });
25279
+ return this.configsState;
25150
25280
  }
25151
25281
  /** Durable handle over the exportId→ExportRecord blob (same `BaseAddon.state()`
25152
25282
  * wrapper as the config store). */