@camstack/addon-provider-velux 0.2.72 → 0.2.74

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.
package/dist/addon.js CHANGED
@@ -6445,6 +6445,21 @@ var CameraStreamSchema = object({
6445
6445
  */
6446
6446
  autoEligible: boolean().optional(),
6447
6447
  /**
6448
+ * The profile slot the PUBLISHER says this stream is: `low` for a stream
6449
+ * whose only sensible role is the low-bandwidth one, `high` for a main
6450
+ * stream. Consulted by the broker's automatic ranking
6451
+ * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
6452
+ * an operator's manual assignment still wins, and an absent hint is the
6453
+ * pixel-ranked default every camera had before this field existed.
6454
+ *
6455
+ * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
6456
+ * storage class is written for on its own (`recordings` is high|mid,
6457
+ * `recordingsLow` is low) and the one profile scrub does not read. The robot
6458
+ * camera's single 720p relay is a `low` in every respect except the slot the
6459
+ * heuristic put it in.
6460
+ */
6461
+ profileHint: CamProfileSchema.optional(),
6462
+ /**
6448
6463
  * Transport-specific opaque metadata. The broker passes it through to
6449
6464
  * the source reader without inspecting it. Currently used by
6450
6465
  * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
@@ -7894,7 +7909,22 @@ var RecordingTriggersSchema = object({
7894
7909
  * il livello: un contatto trovato già aperto al riavvio del runner non fa
7895
7910
  * registrare.
7896
7911
  */
7897
- sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
7912
+ sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional(),
7913
+ /**
7914
+ * La camera registra per tutta la durata del SEGNALE che lei stessa
7915
+ * pubblica — la cap `recording-signal` (`active: true` mentre il device è in
7916
+ * funzione: un robot che pulisce). È un LIVELLO, non un fronte: la finestra
7917
+ * resta aperta finché il segnale è alto e si chiude `postBufferSec` dopo la
7918
+ * caduta, così una pulizia di quaranta minuti è UNA clip.
7919
+ *
7920
+ * Non nomina un device: il segnale è della camera stessa, tenuto fresco dal
7921
+ * suo provider (D224); il recorder lo ascolta su `DeviceStateChanged` e lo
7922
+ * riconcilia leggendo la slice via RPC, con un tetto oltre il quale un
7923
+ * segnale mai abbassato (un evento perso) non può tenere aperta una
7924
+ * registrazione (D11). Vedi `recorder/addon/band-decision.ts`
7925
+ * (`holdTrigger` / `releaseTrigger`).
7926
+ */
7927
+ deviceSignal: boolean().optional()
7898
7928
  });
7899
7929
  /**
7900
7930
  * Mode of a single recording band — the recorder per-band vocabulary.
@@ -11358,6 +11388,8 @@ method(object({
11358
11388
  label: string().optional(),
11359
11389
  deviceFeatures: array(string()).optional(),
11360
11390
  autoEligible: boolean().optional(),
11391
+ /** The slot the publisher says this stream IS — see `CameraStreamSchema.profileHint`. */
11392
+ profileHint: CamProfileSchema.optional(),
11361
11393
  metadata: record(string(), unknown()).optional(),
11362
11394
  /** Required when kind === 'derived' — the source camStreamId the
11363
11395
  * derived broker reads its encoded plane from.
@@ -29883,6 +29915,77 @@ method(object({
29883
29915
  auth: "protected"
29884
29916
  });
29885
29917
  /**
29918
+ * A camera's own "record me NOW" LEVEL — a signal the device raises while
29919
+ * something it knows about is happening (a robot vacuum cleaning, a machine
29920
+ * running, a gate open) and lowers when it stops.
29921
+ *
29922
+ * It is a level, not an edge. The sensor triggers the recorder already listens
29923
+ * to (`SOURCE_CAP_ACTIVE_FIELD`) fire on a rising edge and record for a fixed
29924
+ * post-buffer; a cleaning lasts forty minutes, and forty minutes of "this is
29925
+ * still happening" cannot be expressed as an edge plus a post-buffer without
29926
+ * either re-triggering on a timer (polling — D224 forbids the consumer keeping
29927
+ * the fact fresh) or picking a post-buffer long enough to cover every job,
29928
+ * which records the dock for that long after every short one. So the recorder
29929
+ * reads the LEVEL: it records for as long as `active` is true, plus the band's
29930
+ * own post-buffer once it falls.
29931
+ *
29932
+ * Who owns the fact: the PROVIDER of the device, which already reads the state
29933
+ * the signal is derived from (the vacuum's lifecycle state, for Dreame). It
29934
+ * writes this slice on every change; consumers subscribe to the kernel's
29935
+ * `DeviceStateChanged` for it and read it back through
29936
+ * `deviceState.getCapSlice` to reconcile — never a second copy, never a poll
29937
+ * against the device (D224, D8/D11).
29938
+ *
29939
+ * Deliberately NOT a `binary` sensor on the camera: a binary sensor is exported
29940
+ * to Home Assistant, HomeKit and the sensor picker as a thing an operator can
29941
+ * bind, and "the camera wants to record" is not a room sensor. It is a fact
29942
+ * about the camera, on the camera.
29943
+ */
29944
+ var RecordingSignalStatusSchema = object({
29945
+ /** True while the device asks to be recorded. */
29946
+ active: boolean(),
29947
+ /**
29948
+ * Why, in the provider's own vocabulary (a vacuum's `cleaning` / `docked`,
29949
+ * …). Free text for the log line and the UI badge; the recorder never
29950
+ * branches on it.
29951
+ */
29952
+ reason: string(),
29953
+ /** Ms epoch of the last `active` transition. 0 if never observed. */
29954
+ lastChangedAt: number()
29955
+ });
29956
+ /** The runtime-state slice: the status plus the clock every slice carries. */
29957
+ var RecordingSignalRuntimeStateSchema = RecordingSignalStatusSchema.extend({ lastFetchedAt: number() });
29958
+ var recordingSignalCapability = {
29959
+ name: "recording-signal",
29960
+ scope: "device",
29961
+ deviceNative: true,
29962
+ mode: "singleton",
29963
+ deviceTypes: [DeviceType.Camera],
29964
+ methods: {
29965
+ /** The current level, straight from the slice the provider keeps fresh. */
29966
+ getStatus: method(object({ deviceId: number() }), RecordingSignalStatusSchema) },
29967
+ status: {
29968
+ schema: RecordingSignalStatusSchema,
29969
+ kind: "push",
29970
+ empty: {
29971
+ active: false,
29972
+ reason: "unknown",
29973
+ lastChangedAt: 0
29974
+ }
29975
+ },
29976
+ runtimeState: RecordingSignalRuntimeStateSchema,
29977
+ /**
29978
+ * Runtime-state durability: **session** — a restored `active: true` from
29979
+ * before a restart is exactly the stale level the recorder's reconcile bound
29980
+ * exists to end, and the provider re-derives the true level on activation
29981
+ * anyway. Nothing is lost by forgetting it; a lie is avoided.
29982
+ *
29983
+ * See `RuntimeStateDurability`. Enforced by
29984
+ * `scripts/check-runtime-state-durability.ts`.
29985
+ */
29986
+ durability: "session"
29987
+ };
29988
+ /**
29886
29989
  * scene-monitor — device-scoped reference-region state cap. An operator marks
29887
29990
  * a rect ROI on a camera frame and names one or more states; the engine
29888
29991
  * (pipeline-analytics, designated post-processing node) reports which state is
@@ -30380,6 +30483,8 @@ var CamStreamDescriptorSchema = object({
30380
30483
  deviceFeatures: array(string()).optional(),
30381
30484
  /** Eligible for automatic profile assignment. Absent = `true`. */
30382
30485
  autoEligible: boolean().optional(),
30486
+ /** The slot the publisher says this stream IS — see `CameraStreamSchema.profileHint`. */
30487
+ profileHint: CamProfileSchema.optional(),
30383
30488
  /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
30384
30489
  metadata: record(string(), unknown()).optional()
30385
30490
  });
@@ -32519,6 +32624,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32519
32624
  pressureSensor: pressureSensorCapability,
32520
32625
  privacyMask: privacyMaskCapability,
32521
32626
  ptzAutotrack: ptzAutotrackCapability,
32627
+ recordingSignal: recordingSignalCapability,
32522
32628
  sceneMonitor: sceneMonitorCapability,
32523
32629
  scriptRunner: scriptRunnerCapability,
32524
32630
  smoke: smokeCapability,
@@ -33362,6 +33468,53 @@ var BaseDeviceProvider = class extends BaseAddon {
33362
33468
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
33363
33469
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
33364
33470
  }
33471
+ /**
33472
+ * A top-level persisted row with NO configuration is a leftover, not a
33473
+ * device — quarantine it instead of dialling it.
33474
+ *
33475
+ * Device 614 was deleted, resurrected as a config-less shell by a write
33476
+ * already in flight (D376), and then dialled at every single boot: eight
33477
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
33478
+ * each one the same Zod refusal on `host` and `password`. The bound was
33479
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
33480
+ * about the row ever stopped costing attempts and log lines.
33481
+ *
33482
+ * No retry can add a configuration that is not there. The row is reported
33483
+ * once, with its `deviceId`, and set aside.
33484
+ *
33485
+ * Top-level only: an accessory child legitimately carries an empty blob (it
33486
+ * lives off its parent), and a hub-adopted child with an emptied blob is
33487
+ * repaired by `healSavedConfig` from its parent instead. Providers that
33488
+ * genuinely create configuration-free top-level devices turn this off.
33489
+ */
33490
+ quarantineConfiglessTopLevelRestores = true;
33491
+ isUnrestorableShell(saved) {
33492
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
33493
+ if (saved.parentDeviceId !== null) return false;
33494
+ const config = saved.config;
33495
+ if (config === void 0) return false;
33496
+ return Object.keys(config).length === 0;
33497
+ }
33498
+ quarantineShell(saved) {
33499
+ this._permanentRestoreFailures.set(saved.id, {
33500
+ deviceId: saved.id,
33501
+ stableId: saved.stableId,
33502
+ type: saved.type,
33503
+ attempts: 0,
33504
+ lastError: "persisted row has no configuration",
33505
+ failedAt: Date.now()
33506
+ });
33507
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
33508
+ tags: {
33509
+ deviceId: saved.id,
33510
+ stableId: saved.stableId
33511
+ },
33512
+ meta: {
33513
+ type: saved.type,
33514
+ provider: this.providerName
33515
+ }
33516
+ });
33517
+ }
33365
33518
  cancelRestoreRetries() {
33366
33519
  this._restoreRetryScheduler?.cancel();
33367
33520
  this._restoreRetryScheduler = null;
@@ -33546,6 +33699,10 @@ var BaseDeviceProvider = class extends BaseAddon {
33546
33699
  });
33547
33700
  return;
33548
33701
  }
33702
+ if (this.isUnrestorableShell(saved)) {
33703
+ this.quarantineShell(saved);
33704
+ return;
33705
+ }
33549
33706
  try {
33550
33707
  await attemptRestore(saved);
33551
33708
  } catch (err) {
@@ -38266,6 +38423,12 @@ Object.freeze({
38266
38423
  addonId: null,
38267
38424
  access: "view"
38268
38425
  },
38426
+ "recordingSignal.getStatus": {
38427
+ capName: "recording-signal",
38428
+ capScope: "device",
38429
+ addonId: null,
38430
+ access: "view"
38431
+ },
38269
38432
  "sceneMonitor.captureReference": {
38270
38433
  capName: "scene-monitor",
38271
38434
  capScope: "device",
@@ -41258,6 +41421,11 @@ Object.freeze({
41258
41421
  form: "single",
41259
41422
  optional: true
41260
41423
  }],
41424
+ "recordingSignal.getStatus": [{
41425
+ name: "deviceId",
41426
+ form: "single",
41427
+ optional: false
41428
+ }],
41261
41429
  "sceneMonitor.captureReference": [{
41262
41430
  name: "deviceId",
41263
41431
  form: "single",
package/dist/addon.mjs CHANGED
@@ -6444,6 +6444,21 @@ var CameraStreamSchema = object({
6444
6444
  */
6445
6445
  autoEligible: boolean().optional(),
6446
6446
  /**
6447
+ * The profile slot the PUBLISHER says this stream is: `low` for a stream
6448
+ * whose only sensible role is the low-bandwidth one, `high` for a main
6449
+ * stream. Consulted by the broker's automatic ranking
6450
+ * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
6451
+ * an operator's manual assignment still wins, and an absent hint is the
6452
+ * pixel-ranked default every camera had before this field existed.
6453
+ *
6454
+ * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
6455
+ * storage class is written for on its own (`recordings` is high|mid,
6456
+ * `recordingsLow` is low) and the one profile scrub does not read. The robot
6457
+ * camera's single 720p relay is a `low` in every respect except the slot the
6458
+ * heuristic put it in.
6459
+ */
6460
+ profileHint: CamProfileSchema.optional(),
6461
+ /**
6447
6462
  * Transport-specific opaque metadata. The broker passes it through to
6448
6463
  * the source reader without inspecting it. Currently used by
6449
6464
  * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
@@ -7893,7 +7908,22 @@ var RecordingTriggersSchema = object({
7893
7908
  * il livello: un contatto trovato già aperto al riavvio del runner non fa
7894
7909
  * registrare.
7895
7910
  */
7896
- sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
7911
+ sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional(),
7912
+ /**
7913
+ * La camera registra per tutta la durata del SEGNALE che lei stessa
7914
+ * pubblica — la cap `recording-signal` (`active: true` mentre il device è in
7915
+ * funzione: un robot che pulisce). È un LIVELLO, non un fronte: la finestra
7916
+ * resta aperta finché il segnale è alto e si chiude `postBufferSec` dopo la
7917
+ * caduta, così una pulizia di quaranta minuti è UNA clip.
7918
+ *
7919
+ * Non nomina un device: il segnale è della camera stessa, tenuto fresco dal
7920
+ * suo provider (D224); il recorder lo ascolta su `DeviceStateChanged` e lo
7921
+ * riconcilia leggendo la slice via RPC, con un tetto oltre il quale un
7922
+ * segnale mai abbassato (un evento perso) non può tenere aperta una
7923
+ * registrazione (D11). Vedi `recorder/addon/band-decision.ts`
7924
+ * (`holdTrigger` / `releaseTrigger`).
7925
+ */
7926
+ deviceSignal: boolean().optional()
7897
7927
  });
7898
7928
  /**
7899
7929
  * Mode of a single recording band — the recorder per-band vocabulary.
@@ -11357,6 +11387,8 @@ method(object({
11357
11387
  label: string().optional(),
11358
11388
  deviceFeatures: array(string()).optional(),
11359
11389
  autoEligible: boolean().optional(),
11390
+ /** The slot the publisher says this stream IS — see `CameraStreamSchema.profileHint`. */
11391
+ profileHint: CamProfileSchema.optional(),
11360
11392
  metadata: record(string(), unknown()).optional(),
11361
11393
  /** Required when kind === 'derived' — the source camStreamId the
11362
11394
  * derived broker reads its encoded plane from.
@@ -29882,6 +29914,77 @@ method(object({
29882
29914
  auth: "protected"
29883
29915
  });
29884
29916
  /**
29917
+ * A camera's own "record me NOW" LEVEL — a signal the device raises while
29918
+ * something it knows about is happening (a robot vacuum cleaning, a machine
29919
+ * running, a gate open) and lowers when it stops.
29920
+ *
29921
+ * It is a level, not an edge. The sensor triggers the recorder already listens
29922
+ * to (`SOURCE_CAP_ACTIVE_FIELD`) fire on a rising edge and record for a fixed
29923
+ * post-buffer; a cleaning lasts forty minutes, and forty minutes of "this is
29924
+ * still happening" cannot be expressed as an edge plus a post-buffer without
29925
+ * either re-triggering on a timer (polling — D224 forbids the consumer keeping
29926
+ * the fact fresh) or picking a post-buffer long enough to cover every job,
29927
+ * which records the dock for that long after every short one. So the recorder
29928
+ * reads the LEVEL: it records for as long as `active` is true, plus the band's
29929
+ * own post-buffer once it falls.
29930
+ *
29931
+ * Who owns the fact: the PROVIDER of the device, which already reads the state
29932
+ * the signal is derived from (the vacuum's lifecycle state, for Dreame). It
29933
+ * writes this slice on every change; consumers subscribe to the kernel's
29934
+ * `DeviceStateChanged` for it and read it back through
29935
+ * `deviceState.getCapSlice` to reconcile — never a second copy, never a poll
29936
+ * against the device (D224, D8/D11).
29937
+ *
29938
+ * Deliberately NOT a `binary` sensor on the camera: a binary sensor is exported
29939
+ * to Home Assistant, HomeKit and the sensor picker as a thing an operator can
29940
+ * bind, and "the camera wants to record" is not a room sensor. It is a fact
29941
+ * about the camera, on the camera.
29942
+ */
29943
+ var RecordingSignalStatusSchema = object({
29944
+ /** True while the device asks to be recorded. */
29945
+ active: boolean(),
29946
+ /**
29947
+ * Why, in the provider's own vocabulary (a vacuum's `cleaning` / `docked`,
29948
+ * …). Free text for the log line and the UI badge; the recorder never
29949
+ * branches on it.
29950
+ */
29951
+ reason: string(),
29952
+ /** Ms epoch of the last `active` transition. 0 if never observed. */
29953
+ lastChangedAt: number()
29954
+ });
29955
+ /** The runtime-state slice: the status plus the clock every slice carries. */
29956
+ var RecordingSignalRuntimeStateSchema = RecordingSignalStatusSchema.extend({ lastFetchedAt: number() });
29957
+ var recordingSignalCapability = {
29958
+ name: "recording-signal",
29959
+ scope: "device",
29960
+ deviceNative: true,
29961
+ mode: "singleton",
29962
+ deviceTypes: [DeviceType.Camera],
29963
+ methods: {
29964
+ /** The current level, straight from the slice the provider keeps fresh. */
29965
+ getStatus: method(object({ deviceId: number() }), RecordingSignalStatusSchema) },
29966
+ status: {
29967
+ schema: RecordingSignalStatusSchema,
29968
+ kind: "push",
29969
+ empty: {
29970
+ active: false,
29971
+ reason: "unknown",
29972
+ lastChangedAt: 0
29973
+ }
29974
+ },
29975
+ runtimeState: RecordingSignalRuntimeStateSchema,
29976
+ /**
29977
+ * Runtime-state durability: **session** — a restored `active: true` from
29978
+ * before a restart is exactly the stale level the recorder's reconcile bound
29979
+ * exists to end, and the provider re-derives the true level on activation
29980
+ * anyway. Nothing is lost by forgetting it; a lie is avoided.
29981
+ *
29982
+ * See `RuntimeStateDurability`. Enforced by
29983
+ * `scripts/check-runtime-state-durability.ts`.
29984
+ */
29985
+ durability: "session"
29986
+ };
29987
+ /**
29885
29988
  * scene-monitor — device-scoped reference-region state cap. An operator marks
29886
29989
  * a rect ROI on a camera frame and names one or more states; the engine
29887
29990
  * (pipeline-analytics, designated post-processing node) reports which state is
@@ -30379,6 +30482,8 @@ var CamStreamDescriptorSchema = object({
30379
30482
  deviceFeatures: array(string()).optional(),
30380
30483
  /** Eligible for automatic profile assignment. Absent = `true`. */
30381
30484
  autoEligible: boolean().optional(),
30485
+ /** The slot the publisher says this stream IS — see `CameraStreamSchema.profileHint`. */
30486
+ profileHint: CamProfileSchema.optional(),
30382
30487
  /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
30383
30488
  metadata: record(string(), unknown()).optional()
30384
30489
  });
@@ -32518,6 +32623,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32518
32623
  pressureSensor: pressureSensorCapability,
32519
32624
  privacyMask: privacyMaskCapability,
32520
32625
  ptzAutotrack: ptzAutotrackCapability,
32626
+ recordingSignal: recordingSignalCapability,
32521
32627
  sceneMonitor: sceneMonitorCapability,
32522
32628
  scriptRunner: scriptRunnerCapability,
32523
32629
  smoke: smokeCapability,
@@ -33361,6 +33467,53 @@ var BaseDeviceProvider = class extends BaseAddon {
33361
33467
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
33362
33468
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
33363
33469
  }
33470
+ /**
33471
+ * A top-level persisted row with NO configuration is a leftover, not a
33472
+ * device — quarantine it instead of dialling it.
33473
+ *
33474
+ * Device 614 was deleted, resurrected as a config-less shell by a write
33475
+ * already in flight (D376), and then dialled at every single boot: eight
33476
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
33477
+ * each one the same Zod refusal on `host` and `password`. The bound was
33478
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
33479
+ * about the row ever stopped costing attempts and log lines.
33480
+ *
33481
+ * No retry can add a configuration that is not there. The row is reported
33482
+ * once, with its `deviceId`, and set aside.
33483
+ *
33484
+ * Top-level only: an accessory child legitimately carries an empty blob (it
33485
+ * lives off its parent), and a hub-adopted child with an emptied blob is
33486
+ * repaired by `healSavedConfig` from its parent instead. Providers that
33487
+ * genuinely create configuration-free top-level devices turn this off.
33488
+ */
33489
+ quarantineConfiglessTopLevelRestores = true;
33490
+ isUnrestorableShell(saved) {
33491
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
33492
+ if (saved.parentDeviceId !== null) return false;
33493
+ const config = saved.config;
33494
+ if (config === void 0) return false;
33495
+ return Object.keys(config).length === 0;
33496
+ }
33497
+ quarantineShell(saved) {
33498
+ this._permanentRestoreFailures.set(saved.id, {
33499
+ deviceId: saved.id,
33500
+ stableId: saved.stableId,
33501
+ type: saved.type,
33502
+ attempts: 0,
33503
+ lastError: "persisted row has no configuration",
33504
+ failedAt: Date.now()
33505
+ });
33506
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
33507
+ tags: {
33508
+ deviceId: saved.id,
33509
+ stableId: saved.stableId
33510
+ },
33511
+ meta: {
33512
+ type: saved.type,
33513
+ provider: this.providerName
33514
+ }
33515
+ });
33516
+ }
33364
33517
  cancelRestoreRetries() {
33365
33518
  this._restoreRetryScheduler?.cancel();
33366
33519
  this._restoreRetryScheduler = null;
@@ -33545,6 +33698,10 @@ var BaseDeviceProvider = class extends BaseAddon {
33545
33698
  });
33546
33699
  return;
33547
33700
  }
33701
+ if (this.isUnrestorableShell(saved)) {
33702
+ this.quarantineShell(saved);
33703
+ return;
33704
+ }
33548
33705
  try {
33549
33706
  await attemptRestore(saved);
33550
33707
  } catch (err) {
@@ -38265,6 +38422,12 @@ Object.freeze({
38265
38422
  addonId: null,
38266
38423
  access: "view"
38267
38424
  },
38425
+ "recordingSignal.getStatus": {
38426
+ capName: "recording-signal",
38427
+ capScope: "device",
38428
+ addonId: null,
38429
+ access: "view"
38430
+ },
38268
38431
  "sceneMonitor.captureReference": {
38269
38432
  capName: "scene-monitor",
38270
38433
  capScope: "device",
@@ -41257,6 +41420,11 @@ Object.freeze({
41257
41420
  form: "single",
41258
41421
  optional: true
41259
41422
  }],
41423
+ "recordingSignal.getStatus": [{
41424
+ name: "deviceId",
41425
+ form: "single",
41426
+ optional: false
41427
+ }],
41260
41428
  "sceneMonitor.captureReference": [{
41261
41429
  name: "deviceId",
41262
41430
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-velux",
3
- "version": "0.2.72",
3
+ "version": "0.2.74",
4
4
  "description": "Velux KLF-200 device-provider addon for CamStack — local-gateway window + shutter covers (positional, no tilt)",
5
5
  "keywords": [
6
6
  "camstack",