@camstack/addon-provider-homeassistant 1.2.151 → 1.2.152

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
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("./dist-Cn_fSOpM.js");
2
+ const require_dist = require("./dist-nbfaHTXG.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_zlib = require("node:zlib");
5
5
  //#region src/ha-admin-link.ts
package/dist/addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as parseAdminBase, A as contactCapability, At as union, B as gasCapability, C as buildAddonRouteProvider, Ct as array, D as climateControlCapability, Dt as object, E as carbonMonoxideCapability, Et as number, G as lockControlCapability, H as humiditySensorCapability, I as enumSensorCapability, J as normalizeUnit, K as mediaPlayerCapability, L as eventEmitterCapability, M as coverCapability, Mt as url, N as deviceAdminLinkCapability, Nt as EventCategory, O as colorCapability, Ot as record, P as deviceAdoptionCapability, R as fanControlCapability, S as brokerCapability, St as _enum, T as buttonCapability, Tt as literal, U as imageCapability, V as humidifierCapability, W as lawnMowerControlCapability, X as notifierCapability, Y as notificationOutputCapability, Z as numericSensorCapability, _ as automationControlCapability, _t as DeviceType, at as smokeCapability, b as binaryCapability, bt as createEvent, ct as temperatureSensorCapability, d as TargetSchema, dt as valveCapability, et as powerMeterCapability, f as accessoriesCapability, ft as vibrationCapability, g as ambientLightSensorCapability, gt as DeviceRole, h as alarmPanelCapability, ht as DeviceFeature, it as scriptRunnerCapability, j as controlCapability, jt as unknown, k as connectivityCapability, kt as string, lt as updateCapability, m as airQualitySensorCapability, mt as weatherCapability, n as BaseDevice, nt as presenceCapability, ot as switchCapability, p as addonRoutesCapability, pt as waterHeaterCapability, q as motionCapability, r as BaseDeviceProvider, rt as pressureSensorCapability, s as EnumSensorDateTimeFormatSchema, st as tamperCapability, tt as prepareNotification, ut as vacuumControlCapability, v as batteryCapability, vt as errMsg, w as buildDeviceAdminUrl, wt as discriminatedUnion, x as brightnessCapability, y as bestLocationMatch, z as floodCapability } from "./dist-DFsf2z4Q.mjs";
1
+ import { $ as parseAdminBase, A as contactCapability, At as union, B as gasCapability, C as buildAddonRouteProvider, Ct as array, D as climateControlCapability, Dt as object, E as carbonMonoxideCapability, Et as number, G as lockControlCapability, H as humiditySensorCapability, I as enumSensorCapability, J as normalizeUnit, K as mediaPlayerCapability, L as eventEmitterCapability, M as coverCapability, Mt as url, N as deviceAdminLinkCapability, Nt as EventCategory, O as colorCapability, Ot as record, P as deviceAdoptionCapability, R as fanControlCapability, S as brokerCapability, St as _enum, T as buttonCapability, Tt as literal, U as imageCapability, V as humidifierCapability, W as lawnMowerControlCapability, X as notifierCapability, Y as notificationOutputCapability, Z as numericSensorCapability, _ as automationControlCapability, _t as DeviceType, at as smokeCapability, b as binaryCapability, bt as createEvent, ct as temperatureSensorCapability, d as TargetSchema, dt as valveCapability, et as powerMeterCapability, f as accessoriesCapability, ft as vibrationCapability, g as ambientLightSensorCapability, gt as DeviceRole, h as alarmPanelCapability, ht as DeviceFeature, it as scriptRunnerCapability, j as controlCapability, jt as unknown, k as connectivityCapability, kt as string, lt as updateCapability, m as airQualitySensorCapability, mt as weatherCapability, n as BaseDevice, nt as presenceCapability, ot as switchCapability, p as addonRoutesCapability, pt as waterHeaterCapability, q as motionCapability, r as BaseDeviceProvider, rt as pressureSensorCapability, s as EnumSensorDateTimeFormatSchema, st as tamperCapability, tt as prepareNotification, ut as vacuumControlCapability, v as batteryCapability, vt as errMsg, w as buildDeviceAdminUrl, wt as discriminatedUnion, x as brightnessCapability, y as bestLocationMatch, z as floodCapability } from "./dist-BITMr6s5.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
4
4
  //#region src/ha-admin-link.ts
@@ -7480,7 +7480,7 @@ function errMsg(err) {
7480
7480
  return String(err);
7481
7481
  }
7482
7482
  //#endregion
7483
- //#region ../types/dist/composition-43aqsZQj.mjs
7483
+ //#region ../types/dist/composition-D7ULvLdD.mjs
7484
7484
  /**
7485
7485
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7486
7486
  *
@@ -7534,20 +7534,20 @@ function portable(target) {
7534
7534
  }
7535
7535
  portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7536
7536
  /**
7537
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7538
- * valutatore puro del suo FRONTE.
7537
+ * THE table "which boolean of this device type counts as HIGH", and the
7538
+ * pure evaluator of its EDGE.
7539
7539
  *
7540
- * UNA tabella per tutti quelli che chiedono "questo device è ALTO?": il
7541
- * trigger `RecordingTriggers.sensorDeviceIds` del recorder, i marker dei
7542
- * sensori della post-analysis, il bridge camera-sensors dell'orchestratore e
7543
- * le sorgenti di un campanello composto (`doorbellFeatureSources`, D671).
7544
- * Una seconda copia divergerebbe alla prima cap aggiunta, e il sintomo — "il
7545
- * sensore fa suonare il campanello ma non registra" — è esattamente D62.
7540
+ * ONE table for everyone who asks "is this device HIGH?": the
7541
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7542
+ * sensor markers, the orchestrator's camera-sensors bridge and
7543
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7544
+ * A second copy would diverge at the first cap added, and the symptom — "the
7545
+ * sensor rings the doorbell but does not record" — is exactly D62.
7546
7546
  *
7547
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7548
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7549
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7550
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7547
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7548
+ * two must not be unified: that one answers "what is the state WORD for a
7549
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7550
+ * boolean's EDGE counts". Deliberately different vocabularies.
7551
7551
  */
7552
7552
  /**
7553
7553
  * Known binary / switch source caps → the boolean slice field whose
@@ -9298,22 +9298,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9298
9298
  "package"
9299
9299
  ];
9300
9300
  /**
9301
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9302
- * un operatore può selezionare.
9301
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9302
+ * an operator can select.
9303
9303
  *
9304
- * Sono le tre offerte dallo step `object-detection`
9304
+ * They are the three offered by the `object-detection` step
9305
9305
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9306
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9307
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9308
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9309
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9306
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9307
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9308
+ * `package-detection`, whose enablement is driven by zones, and offering the
9309
+ * same word twice has already made an operator turn on the COCO proxy
9310
9310
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9311
9311
  *
9312
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9313
- * dello step e una seconda volta come union `FirstLevelMacro`
9314
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9315
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9316
- * successiva.
9312
+ * ONE list. Before today the same three were hand-written in the step's offer
9313
+ * and a second time as the union `FirstLevelMacro`
9314
+ * (`types/detection.ts`); a third copy for the recording trigger
9315
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9316
+ * invisible.
9317
9317
  */
9318
9318
  var FIRST_LEVEL_MACRO_CLASSES = [
9319
9319
  "person",
@@ -9378,17 +9378,17 @@ var RecordingStorageModeSchema = _enum([
9378
9378
  "on-device-decision"
9379
9379
  ]);
9380
9380
  /**
9381
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9382
- * tre offerte dallo step `object-detection`, da UNA lista
9381
+ * The macro classes that can open a recording window — the same
9382
+ * three offered by the `object-detection` step, from ONE list
9383
9383
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9384
9384
  */
9385
9385
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9386
9386
  /**
9387
- * True quando `values` non ripete un elemento.
9387
+ * True when `values` does not repeat an element.
9388
9388
  *
9389
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9390
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9391
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9389
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9390
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9391
+ * would count its windows twice in `segmentMissedByMs`.
9392
9392
  */
9393
9393
  var noDuplicates = (values) => new Set(values).size === values.length;
9394
9394
  /** Which detectors trigger an `events`-mode band. */
@@ -9396,34 +9396,34 @@ var RecordingTriggersSchema = object({
9396
9396
  motion: boolean().optional(),
9397
9397
  audioThresholdDbfs: number().optional(),
9398
9398
  /**
9399
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9400
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9401
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9402
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9399
+ * The macro classes whose detection opens a window. ABSENT = the source
9400
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9401
+ * trigger on, no class" is the same shape "enabled and records
9402
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9403
9403
  *
9404
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9405
- * quelle che hanno attraversato `enabledMacroClasses`, i
9406
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9407
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9408
- * finestre — vedi `recorder/object-trigger.ts`.
9404
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9405
+ * those that went through `enabledMacroClasses`, the
9406
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9407
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9408
+ * windows — see `recorder/object-trigger.ts`.
9409
9409
  */
9410
9410
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9411
9411
  /**
9412
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9413
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9412
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9413
+ * is not listened to; an empty array is rejected for the same reason as
9414
9414
  * `objectClasses`.
9415
9415
  *
9416
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9417
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9418
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9419
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9420
- * device (D12) — mai un elenco globale di cap.
9416
+ * These are SOURCE device ids, not cameras: the band names them, so the
9417
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9418
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9419
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9420
+ * device (D12) — never a global list of caps.
9421
9421
  *
9422
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9423
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9424
- * la stessa del campanello composto (D671). Ed è il FRONTE, non
9425
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9426
- * registrare.
9422
+ * What "high" means depends on the device TYPE and is not decided here:
9423
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9424
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9425
+ * the level: a contact found already open at runner restart does not
9426
+ * record.
9427
9427
  */
9428
9428
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9429
9429
  });
@@ -10500,7 +10500,7 @@ var DecoderSessionConfigSchema = object({
10500
10500
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10501
10501
  * thing: the logging settings document on the `system` cap. Two authorities
10502
10502
  * over the values is the exact defect
10503
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10503
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10504
10504
  * remove; re-introducing it from the cure side would be grotesque.
10505
10505
  *
10506
10506
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -14123,7 +14123,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
14123
14123
  *
14124
14124
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
14125
14125
  * was wrong — corrected 2026-08-26 after the hop map
14126
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
14126
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
14127
14127
  * There is **no Zod parse at all** between the door and the engine: the
14128
14128
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
14129
14129
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -15220,7 +15220,17 @@ var SiteZoneSchema = object({
15220
15220
  var ProviderStatusSchema = object({
15221
15221
  connected: boolean(),
15222
15222
  deviceCount: number(),
15223
- error: string().optional()
15223
+ error: string().optional(),
15224
+ /**
15225
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
15226
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
15227
+ * which every provider bundles, so only a provider BUILT from D696-amended
15228
+ * code says it. ABSENT means an older provider that may still write it (raw
15229
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
15230
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
15231
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
15232
+ */
15233
+ motionSliceWriter: literal("orchestrator").optional()
15224
15234
  });
15225
15235
  object({
15226
15236
  externalId: string(),
@@ -16437,7 +16447,7 @@ method(object({
16437
16447
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
16438
16448
  * ends in a log line, and a log line is the thing the operator asked to stop
16439
16449
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
16440
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
16450
+ * ricontrolliamo tutti questi punti"* [can we instrument these errors in the meantime? That way next round we recheck all these points]. Reading them meant grepping Loki and
16441
16451
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
16442
16452
  * media blackout were both diagnosed. The counters stay; this is where they can
16443
16453
  * be READ.
@@ -20696,6 +20706,29 @@ object({
20696
20706
  firstAt: number(),
20697
20707
  lastAt: number()
20698
20708
  });
20709
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
20710
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
20711
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
20712
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
20713
+ var NcAlarmModeDelaysListSchema = array(object({
20714
+ mode: AlarmArmModeSchema,
20715
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
20716
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
20717
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
20718
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20719
+ var NcAlarmModeDelaysEditListSchema = array(object({
20720
+ mode: AlarmArmModeSchema,
20721
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20722
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20723
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
20724
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20725
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20726
+ var NcAlarmNonBlockingEditSchema = object({
20727
+ mode: AlarmArmModeSchema,
20728
+ deviceId: number().int().positive(),
20729
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
20730
+ nonBlocking: boolean()
20731
+ });
20699
20732
  /**
20700
20733
  * The per-mode lists, at most one per mode. Two lists for one mode have no
20701
20734
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -20704,10 +20737,10 @@ object({
20704
20737
  var NcAlarmNonBlockingListSchema = array(object({
20705
20738
  mode: AlarmArmModeSchema,
20706
20739
  deviceIds: array(number().int()).max(200)
20707
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
20740
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20708
20741
  /**
20709
- * The three durations the panel's state machine runs on, who hears about its
20710
- * transitions, and which openings each mode tolerates.
20742
+ * The three durations — global, and per mode since D697 — the panel's state
20743
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
20711
20744
  *
20712
20745
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
20713
20746
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -20717,15 +20750,15 @@ var NcAlarmNonBlockingListSchema = array(object({
20717
20750
  */
20718
20751
  var NcAlarmSettingsSchema = object({
20719
20752
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
20720
- exitDelaySec: number().int().min(0).max(600),
20753
+ exitDelaySec: NcAlarmDelaySecSchema,
20721
20754
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
20722
- entryDelaySec: number().int().min(0).max(600),
20755
+ entryDelaySec: NcAlarmDelaySecSchema,
20723
20756
  /**
20724
20757
  * How long `triggered` lasts before the panel re-arms itself.
20725
20758
  * **0 = until an operator disarms it** — the behaviour before this field
20726
20759
  * existed, and therefore what an untouched install keeps doing.
20727
20760
  */
20728
- triggeredDurationSec: number().int().min(0).max(3600),
20761
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
20729
20762
  /**
20730
20763
  * Deprecated by D630 — every transition is announced. Parsed, never read;
20731
20764
  * remove after one release. Optional so an older admin's patch (which still
@@ -20748,17 +20781,30 @@ var NcAlarmSettingsSchema = object({
20748
20781
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20749
20782
  * parses as "every opening blocks" — the behaviour it was written under.
20750
20783
  */
20751
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
20784
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20785
+ /**
20786
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20787
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
20788
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
20789
+ */
20790
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
20752
20791
  });
20753
20792
  /**
20754
20793
  * Every field optional — a tab edits one control at a time.
20755
20794
  *
20756
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
20757
- * remove an inner `.default()`, so a patch that never named the field would
20758
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
20759
- * (the same trap {@link NcRulePatchSchema} documents).
20795
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20796
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20797
+ * named the field would parse to `[]` and wipe every mode's list on an
20798
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20799
+ * `modeDelays` means EDITS, not the stored overrides.
20760
20800
  */
20761
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20801
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20802
+ nonBlocking: NcAlarmNonBlockingListSchema,
20803
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20804
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20805
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20806
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20807
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20762
20808
  /**
20763
20809
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20764
20810
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20858,6 +20904,28 @@ var NcAlarmConfigSchema = object({
20858
20904
  */
20859
20905
  live: NcAlarmLiveSchema.optional()
20860
20906
  });
20907
+ var NcAlarmSensorReadingSchema = _enum([
20908
+ "open",
20909
+ "closed",
20910
+ "unknown",
20911
+ "not-opening"
20912
+ ]);
20913
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20914
+ var NcAlarmSensorStateSchema = object({
20915
+ deviceId: number().int().positive(),
20916
+ reading: NcAlarmSensorReadingSchema,
20917
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20918
+ state: string().nullable(),
20919
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20920
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20921
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20922
+ online: boolean().nullable()
20923
+ });
20924
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20925
+ var NcAlarmSensorsSchema = object({
20926
+ sensors: array(NcAlarmSensorStateSchema),
20927
+ readAt: number().int().nonnegative()
20928
+ });
20861
20929
  /**
20862
20930
  * ONE rule's demand on ONE camera's clip ring.
20863
20931
  *
@@ -20948,7 +21016,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20948
21016
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20949
21017
  kind: "mutation",
20950
21018
  auth: "admin"
20951
- });
21019
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20952
21020
  /**
20953
21021
  * Zod schemas for persisted record types.
20954
21022
  *
@@ -22116,14 +22184,14 @@ var TrackSchema = object({
22116
22184
  * `derive-returned-null tile=standin`,
22117
22185
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
22118
22186
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
22119
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
22187
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
22120
22188
  * tile is a face/plate stand-in, a raster crop, or an icon.
22121
22189
  *
22122
22190
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
22123
22191
  * the TILE ranked below native, so a track whose thumbnail landed natively
22124
22192
  * while its key frame missed carried no reason at all — three of the four
22125
22193
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
22126
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
22194
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
22127
22195
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
22128
22196
  * lastFrame copy source`), so its absence is now on this line whatever the
22129
22197
  * tile ended up being.
@@ -22210,7 +22278,7 @@ var MotionEventSchema = object({
22210
22278
  /**
22211
22279
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
22212
22280
  * first entry is always `0`) of every genuine off→on transition the
22213
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
22281
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
22214
22282
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
22215
22283
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
22216
22284
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -27515,7 +27583,7 @@ var ClipPlaybackSchema = object({
27515
27583
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
27516
27584
  * time the camera is awake for its own reasons — is deliberately not a member:
27517
27585
  * it was proposed, it is free on the battery, and the operator declined it on
27518
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
27586
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
27519
27587
  * D558 "Considered and not taken").
27520
27588
  */
27521
27589
  var ClipWakeSchema = _enum(["authorised"]);
@@ -27776,7 +27844,7 @@ DeviceType.Camera, method(object({
27776
27844
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
27777
27845
  * assumed the answer was a fan-out over everything a camera has. The
27778
27846
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
27779
- * solo"* — so the list is asked of one provider at a time and there is
27847
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
27780
27848
  * nothing to filter out of it.
27781
27849
  */
27782
27850
  provider: string().min(1)
@@ -35855,7 +35923,7 @@ var ReadGopBytesResultSchema = object({
35855
35923
  /**
35856
35924
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
35857
35925
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
35858
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
35926
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
35859
35927
  * a replay needs several seconds of native pixels, not one frame.
35860
35928
  *
35861
35929
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -40278,13 +40346,14 @@ var BaseDeviceProvider = class extends BaseAddon {
40278
40346
  async getStatus() {
40279
40347
  const all = await this.ctx.kernel.devices?.getAll() ?? [];
40280
40348
  const summary = this.restoreFailureSummary();
40281
- if (summary === null) return {
40349
+ const base = {
40282
40350
  connected: true,
40283
- deviceCount: all.length
40351
+ deviceCount: all.length,
40352
+ motionSliceWriter: "orchestrator"
40284
40353
  };
40354
+ if (summary === null) return base;
40285
40355
  return {
40286
- connected: true,
40287
- deviceCount: all.length,
40356
+ ...base,
40288
40357
  error: summary
40289
40358
  };
40290
40359
  }
@@ -45720,6 +45789,12 @@ Object.freeze({
45720
45789
  addonId: null,
45721
45790
  access: "view"
45722
45791
  },
45792
+ "notificationRules.getAlarmSensors": {
45793
+ capName: "notification-rules",
45794
+ capScope: "system",
45795
+ addonId: null,
45796
+ access: "view"
45797
+ },
45723
45798
  "notificationRules.getClipRetentionAsks": {
45724
45799
  capName: "notification-rules",
45725
45800
  capScope: "system",
@@ -50203,6 +50278,11 @@ Object.freeze({
50203
50278
  form: "single",
50204
50279
  optional: false
50205
50280
  }],
50281
+ "notificationRules.getAlarmSensors": [{
50282
+ name: "deviceIds",
50283
+ form: "array",
50284
+ optional: false
50285
+ }],
50206
50286
  "notificationRules.setDeviceMuted": [{
50207
50287
  name: "deviceId",
50208
50288
  form: "single",
@@ -52207,7 +52287,7 @@ var PreviewTextField = string().max(200);
52207
52287
  * Whether the notification's preview is a STILL or a short animation.
52208
52288
  *
52209
52289
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
52210
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
52290
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
52211
52291
  * night reads better as three seconds of motion than as one frame of it. Both
52212
52292
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
52213
52293
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -7481,7 +7481,7 @@ function errMsg(err) {
7481
7481
  return String(err);
7482
7482
  }
7483
7483
  //#endregion
7484
- //#region ../types/dist/composition-43aqsZQj.mjs
7484
+ //#region ../types/dist/composition-D7ULvLdD.mjs
7485
7485
  /**
7486
7486
  * The ffmpeg build every CamStack node downloads, per platform and arch.
7487
7487
  *
@@ -7535,20 +7535,20 @@ function portable(target) {
7535
7535
  }
7536
7536
  portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
7537
7537
  /**
7538
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7539
- * valutatore puro del suo FRONTE.
7538
+ * THE table "which boolean of this device type counts as HIGH", and the
7539
+ * pure evaluator of its EDGE.
7540
7540
  *
7541
- * UNA tabella per tutti quelli che chiedono "questo device è ALTO?": il
7542
- * trigger `RecordingTriggers.sensorDeviceIds` del recorder, i marker dei
7543
- * sensori della post-analysis, il bridge camera-sensors dell'orchestratore e
7544
- * le sorgenti di un campanello composto (`doorbellFeatureSources`, D671).
7545
- * Una seconda copia divergerebbe alla prima cap aggiunta, e il sintomo — "il
7546
- * sensore fa suonare il campanello ma non registra" — è esattamente D62.
7541
+ * ONE table for everyone who asks "is this device HIGH?": the
7542
+ * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
7543
+ * sensor markers, the orchestrator's camera-sensors bridge and
7544
+ * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
7545
+ * A second copy would diverge at the first cap added, and the symptom — "the
7546
+ * sensor rings the doorbell but does not record" — is exactly D62.
7547
7547
  *
7548
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7549
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7550
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7551
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7548
+ * ⚠ NOT `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), and the
7549
+ * two must not be unified: that one answers "what is the state WORD for a
7550
+ * rule" (and includes `presence`, `cover`, `alarm-panel`), this one "which
7551
+ * boolean's EDGE counts". Deliberately different vocabularies.
7552
7552
  */
7553
7553
  /**
7554
7554
  * Known binary / switch source caps → the boolean slice field whose
@@ -9299,22 +9299,22 @@ var CLASS_MAP_MACRO_TARGETS = [
9299
9299
  "package"
9300
9300
  ];
9301
9301
  /**
9302
- * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
9303
- * un operatore può selezionare.
9302
+ * The FIRST-LEVEL macro classes: those an object detector emits and that
9303
+ * an operator can select.
9304
9304
  *
9305
- * Sono le tre offerte dallo step `object-detection`
9305
+ * They are the three offered by the `object-detection` step
9306
9306
  * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
9307
- * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
9308
- * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
9309
- * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
9310
- * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
9307
+ * `enabledMacroClasses`). `package` is in {@link CLASS_MAP_MACRO_TARGETS} and in
9308
+ * `MACRO_LABELS` — it is a true macro — but NOT here: it belongs to the step
9309
+ * `package-detection`, whose enablement is driven by zones, and offering the
9310
+ * same word twice has already made an operator turn on the COCO proxy
9311
9311
  * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
9312
9312
  *
9313
- * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
9314
- * dello step e una seconda volta come union `FirstLevelMacro`
9315
- * (`types/detection.ts`); una terza copia per il trigger di registrazione
9316
- * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
9317
- * successiva.
9313
+ * ONE list. Before today the same three were hand-written in the step's offer
9314
+ * and a second time as the union `FirstLevelMacro`
9315
+ * (`types/detection.ts`); a third copy for the recording trigger
9316
+ * (`RecordingTriggers.objectClasses`) would have made the later divergence
9317
+ * invisible.
9318
9318
  */
9319
9319
  var FIRST_LEVEL_MACRO_CLASSES = [
9320
9320
  "person",
@@ -9379,17 +9379,17 @@ var RecordingStorageModeSchema = _enum([
9379
9379
  "on-device-decision"
9380
9380
  ]);
9381
9381
  /**
9382
- * Le macro classi che possono aprire una finestra di registrazione — le stesse
9383
- * tre offerte dallo step `object-detection`, da UNA lista
9382
+ * The macro classes that can open a recording window — the same
9383
+ * three offered by the `object-detection` step, from ONE list
9384
9384
  * ({@link FIRST_LEVEL_MACRO_CLASSES}).
9385
9385
  */
9386
9386
  var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
9387
9387
  /**
9388
- * True quando `values` non ripete un elemento.
9388
+ * True when `values` does not repeat an element.
9389
9389
  *
9390
- * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
9391
- * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
9392
- * conterebbe due volte le sue finestre in `segmentMissedByMs`.
9390
+ * A duplicate is not harmless: every entry of `objectClasses` / `sensorDeviceIds`
9391
+ * becomes a SOURCE in `bandTriggerSources`, and the same source twice
9392
+ * would count its windows twice in `segmentMissedByMs`.
9393
9393
  */
9394
9394
  var noDuplicates = (values) => new Set(values).size === values.length;
9395
9395
  /** Which detectors trigger an `events`-mode band. */
@@ -9397,34 +9397,34 @@ var RecordingTriggersSchema = object({
9397
9397
  motion: boolean().optional(),
9398
9398
  audioThresholdDbfs: number().optional(),
9399
9399
  /**
9400
- * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
9401
- * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
9402
- * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
9403
- * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
9400
+ * The macro classes whose detection opens a window. ABSENT = the source
9401
+ * is not listened to; an EMPTY array is rejected, because "events band, object
9402
+ * trigger on, no class" is the same shape "enabled and records
9403
+ * nothing, forever" that `eventsBandCanEverDemand` is written against.
9404
9404
  *
9405
- * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
9406
- * quelle che hanno attraversato `enabledMacroClasses`, i
9407
- * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
9408
- * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
9409
- * finestre — vedi `recorder/object-trigger.ts`.
9405
+ * The signal read is ALREADY FILTERED: only `source: 'pipeline'` detections, i.e.
9406
+ * those that went through `enabledMacroClasses`, the
9407
+ * `minConfidence<Macro>` and the full-frame guard. The camera's onboard AI
9408
+ * (`source: 'onboard'`) goes through none of those gates and does NOT open
9409
+ * windows — see `recorder/object-trigger.ts`.
9410
9410
  */
9411
9411
  objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
9412
9412
  /**
9413
- * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
9414
- * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
9413
+ * The LINKED devices whose RISING EDGE opens a window. Absent = the source
9414
+ * is not listened to; an empty array is rejected for the same reason as
9415
9415
  * `objectClasses`.
9416
9416
  *
9417
- * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
9418
- * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
9419
- * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
9420
- * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9421
- * device (D12) — mai un elenco globale di cap.
9417
+ * These are SOURCE device ids, not cameras: the band names them, so the
9418
+ * hot path (`DeviceStateChanged`, at bus rate across the whole fleet)
9419
+ * does no RPC. The OFFER from which the operator picks them is another question, and
9420
+ * is resolved with `deviceManager.getLinkedDevices` + `getBindingsBatch` per
9421
+ * device (D12) — never a global list of caps.
9422
9422
  *
9423
- * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
9424
- * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
9425
- * la stessa del campanello composto (D671). Ed è il FRONTE, non
9426
- * il livello: un contatto trovato già aperto al riavvio del runner non fa
9427
- * registrare.
9423
+ * What "high" means depends on the device TYPE and is not decided here:
9424
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) is THE table,
9425
+ * the same one as the composite doorbell (D671). And it is the EDGE, not
9426
+ * the level: a contact found already open at runner restart does not
9427
+ * record.
9428
9428
  */
9429
9429
  sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
9430
9430
  });
@@ -10501,7 +10501,7 @@ var DecoderSessionConfigSchema = object({
10501
10501
  * - **VALUE** — at which level, for which scope, until when. That stays ONE
10502
10502
  * thing: the logging settings document on the `system` cap. Two authorities
10503
10503
  * over the values is the exact defect
10504
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
10504
+ * `docs/design/plans/2026-08-26-per-component-logging.md` was written to
10505
10505
  * remove; re-introducing it from the cure side would be grotesque.
10506
10506
  *
10507
10507
  * Nothing in this file reads a clock, an env var or a store. The registry is
@@ -14124,7 +14124,7 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
14124
14124
  *
14125
14125
  * ⚠ An earlier version of this comment blamed Zod stripping, and that
14126
14126
  * was wrong — corrected 2026-08-26 after the hop map
14127
- * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
14127
+ * (`docs/design/2026-08-26-topic-hop-map.md`) traced the path.
14128
14128
  * There is **no Zod parse at all** between the door and the engine: the
14129
14129
  * dispatcher forwards the payload verbatim and UDS carries it whole. A
14130
14130
  * field declared here reaches `SqliteSettingsBackend` either way.
@@ -15221,7 +15221,17 @@ var SiteZoneSchema = object({
15221
15221
  var ProviderStatusSchema = object({
15222
15222
  connected: boolean(),
15223
15223
  deviceCount: number(),
15224
- error: string().optional()
15224
+ error: string().optional(),
15225
+ /**
15226
+ * `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
15227
+ * — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
15228
+ * which every provider bundles, so only a provider BUILT from D696-amended
15229
+ * code says it. ABSENT means an older provider that may still write it (raw
15230
+ * VMD writes, a boot seed), and the orchestrator's provider gate keeps that
15231
+ * provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
15232
+ * — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
15233
+ */
15234
+ motionSliceWriter: literal("orchestrator").optional()
15225
15235
  });
15226
15236
  object({
15227
15237
  externalId: string(),
@@ -16438,7 +16448,7 @@ method(object({
16438
16448
  * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
16439
16449
  * ends in a log line, and a log line is the thing the operator asked to stop
16440
16450
  * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
16441
- * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
16451
+ * ricontrolliamo tutti questi punti"* [can we instrument these errors in the meantime? That way next round we recheck all these points]. Reading them meant grepping Loki and
16442
16452
  * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
16443
16453
  * media blackout were both diagnosed. The counters stay; this is where they can
16444
16454
  * be READ.
@@ -20697,6 +20707,29 @@ object({
20697
20707
  firstAt: number(),
20698
20708
  lastAt: number()
20699
20709
  });
20710
+ var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
20711
+ var NcAlarmDelaySecSchema = number().int().min(0).max(600);
20712
+ var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
20713
+ var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
20714
+ var NcAlarmModeDelaysListSchema = array(object({
20715
+ mode: AlarmArmModeSchema,
20716
+ exitDelaySec: NcAlarmDelaySecSchema.optional(),
20717
+ entryDelaySec: NcAlarmDelaySecSchema.optional(),
20718
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
20719
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20720
+ var NcAlarmModeDelaysEditListSchema = array(object({
20721
+ mode: AlarmArmModeSchema,
20722
+ exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20723
+ entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
20724
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
20725
+ })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20726
+ /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20727
+ var NcAlarmNonBlockingEditSchema = object({
20728
+ mode: AlarmArmModeSchema,
20729
+ deviceId: number().int().positive(),
20730
+ /** true = on the list (may be open, does not block); false = off the list (blocks). */
20731
+ nonBlocking: boolean()
20732
+ });
20700
20733
  /**
20701
20734
  * The per-mode lists, at most one per mode. Two lists for one mode have no
20702
20735
  * meaning a reader could agree on (union? the last one?), so they are refused
@@ -20705,10 +20738,10 @@ object({
20705
20738
  var NcAlarmNonBlockingListSchema = array(object({
20706
20739
  mode: AlarmArmModeSchema,
20707
20740
  deviceIds: array(number().int()).max(200)
20708
- })).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
20741
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20709
20742
  /**
20710
- * The three durations the panel's state machine runs on, who hears about its
20711
- * transitions, and which openings each mode tolerates.
20743
+ * The three durations — global, and per mode since D697 — the panel's state
20744
+ * machine runs on, who hears about its transitions, and which openings each mode tolerates.
20712
20745
  *
20713
20746
  * They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
20714
20747
  * `alarm-panel` is `deviceNative` and its other provider mirrors somebody
@@ -20718,15 +20751,15 @@ var NcAlarmNonBlockingListSchema = array(object({
20718
20751
  */
20719
20752
  var NcAlarmSettingsSchema = object({
20720
20753
  /** Grace period between arming and the mode taking effect. 0 = immediate. */
20721
- exitDelaySec: number().int().min(0).max(600),
20754
+ exitDelaySec: NcAlarmDelaySecSchema,
20722
20755
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
20723
- entryDelaySec: number().int().min(0).max(600),
20756
+ entryDelaySec: NcAlarmDelaySecSchema,
20724
20757
  /**
20725
20758
  * How long `triggered` lasts before the panel re-arms itself.
20726
20759
  * **0 = until an operator disarms it** — the behaviour before this field
20727
20760
  * existed, and therefore what an untouched install keeps doing.
20728
20761
  */
20729
- triggeredDurationSec: number().int().min(0).max(3600),
20762
+ triggeredDurationSec: NcAlarmTriggeredSecSchema,
20730
20763
  /**
20731
20764
  * Deprecated by D630 — every transition is announced. Parsed, never read;
20732
20765
  * remove after one release. Optional so an older admin's patch (which still
@@ -20749,17 +20782,30 @@ var NcAlarmSettingsSchema = object({
20749
20782
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20750
20783
  * parses as "every opening blocks" — the behaviour it was written under.
20751
20784
  */
20752
- nonBlocking: NcAlarmNonBlockingListSchema.default([])
20785
+ nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20786
+ /**
20787
+ * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20788
+ * without a field, uses the global value above — so `[]`, the default, is exactly the
20789
+ * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
20790
+ */
20791
+ modeDelays: NcAlarmModeDelaysListSchema.default([])
20753
20792
  });
20754
20793
  /**
20755
20794
  * Every field optional — a tab edits one control at a time.
20756
20795
  *
20757
- * `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
20758
- * remove an inner `.default()`, so a patch that never named the field would
20759
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
20760
- * (the same trap {@link NcRulePatchSchema} documents).
20796
+ * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20797
+ * `.partial()` does not remove an inner `.default()`, so a patch that never
20798
+ * named the field would parse to `[]` and wipe every mode's list on an
20799
+ * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
20800
+ * `modeDelays` means EDITS, not the stored overrides.
20761
20801
  */
20762
- var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
20802
+ var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20803
+ nonBlocking: NcAlarmNonBlockingListSchema,
20804
+ /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20805
+ modeDelays: NcAlarmModeDelaysEditListSchema,
20806
+ /** ONE membership change; the panel applies it to the list it holds at its turn. */
20807
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema
20808
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20763
20809
  /**
20764
20810
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20765
20811
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20859,6 +20905,28 @@ var NcAlarmConfigSchema = object({
20859
20905
  */
20860
20906
  live: NcAlarmLiveSchema.optional()
20861
20907
  });
20908
+ var NcAlarmSensorReadingSchema = _enum([
20909
+ "open",
20910
+ "closed",
20911
+ "unknown",
20912
+ "not-opening"
20913
+ ]);
20914
+ /** One device's state as the alarm reads it — the classifier the arm check uses. */
20915
+ var NcAlarmSensorStateSchema = object({
20916
+ deviceId: number().int().positive(),
20917
+ reading: NcAlarmSensorReadingSchema,
20918
+ /** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
20919
+ state: string().nullable(),
20920
+ /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20921
+ unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20922
+ /** The device manager's liveness as the centre mirrors it; null = not known. */
20923
+ online: boolean().nullable()
20924
+ });
20925
+ var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20926
+ var NcAlarmSensorsSchema = object({
20927
+ sensors: array(NcAlarmSensorStateSchema),
20928
+ readAt: number().int().nonnegative()
20929
+ });
20862
20930
  /**
20863
20931
  * ONE rule's demand on ONE camera's clip ring.
20864
20932
  *
@@ -20949,7 +21017,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
20949
21017
  }), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
20950
21018
  kind: "mutation",
20951
21019
  auth: "admin"
20952
- });
21020
+ }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
20953
21021
  /**
20954
21022
  * Zod schemas for persisted record types.
20955
21023
  *
@@ -22117,14 +22185,14 @@ var TrackSchema = object({
22117
22185
  * `derive-returned-null tile=standin`,
22118
22186
  * `native-tile tile=native no-key-frame-at-close`, …) written at close and
22119
22187
  * CLEARED by the late-keyFrame upgrade when a native tile lands after all.
22120
- * The operator-facing answer to "perché manca l'immagine?" on a track whose
22188
+ * The operator-facing answer to "perché manca l'immagine?" [why is the image missing?] on a track whose
22121
22189
  * tile is a face/plate stand-in, a raster crop, or an icon.
22122
22190
  *
22123
22191
  * **`no-key-frame-at-close` (D400).** The line used to be written ONLY when
22124
22192
  * the TILE ranked below native, so a track whose thumbnail landed natively
22125
22193
  * while its key frame missed carried no reason at all — three of the four
22126
22194
  * `MISSING_KEYFRAME` tracks in the 2026-09-07 operator audit were exactly
22127
- * that shape, and their "niente ultimo" had no cause anywhere on the row.
22195
+ * that shape, and their "niente ultimo" [no last frame] had no cause anywhere on the row.
22128
22196
  * The key frame is the head of a cascade (`keyFrame ⇒ keyFrameSmall ⇒ the
22129
22197
  * lastFrame copy source`), so its absence is now on this line whatever the
22130
22198
  * tile ended up being.
@@ -22211,7 +22279,7 @@ var MotionEventSchema = object({
22211
22279
  /**
22212
22280
  * Ms offsets from `timestamp` (the episode's own first rising edge, so the
22213
22281
  * first entry is always `0`) of every genuine off→on transition the
22214
- * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
22282
+ * episode saw — "ogni evento on si deve salvare" [every ON event must be saved] (D475). NOT one entry per
22215
22283
  * push: a firmware source that keepalives at ~1 Hz for the whole burst
22216
22284
  * (Reolink, Hikvision) produces exactly one edge; a source that reports an
22217
22285
  * explicit `false` mid-episode and then resumes before the quiet window
@@ -27516,7 +27584,7 @@ var ClipPlaybackSchema = object({
27516
27584
  * **One authorised yes is one wake.** `next-natural` — take the clip the next
27517
27585
  * time the camera is awake for its own reasons — is deliberately not a member:
27518
27586
  * it was proposed, it is free on the battery, and the operator declined it on
27519
- * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
27587
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."* [When an export is requested the camera wakes up],
27520
27588
  * D558 "Considered and not taken").
27521
27589
  */
27522
27590
  var ClipWakeSchema = _enum(["authorised"]);
@@ -27777,7 +27845,7 @@ DeviceType.Camera, method(object({
27777
27845
  * It replaced `sources?: string[]`, a VIEW filter over source ids that
27778
27846
  * assumed the answer was a fan-out over everything a camera has. The
27779
27847
  * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
27780
- * solo"* — so the list is asked of one provider at a time and there is
27848
+ * solo"* [there is only one user] — so the list is asked of one provider at a time and there is
27781
27849
  * nothing to filter out of it.
27782
27850
  */
27783
27851
  provider: string().min(1)
@@ -35856,7 +35924,7 @@ var ReadGopBytesResultSchema = object({
35856
35924
  /**
35857
35925
  * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
35858
35926
  * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
35859
- * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
35927
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-clip-replay-on-pipeline.md`):
35860
35928
  * a replay needs several seconds of native pixels, not one frame.
35861
35929
  *
35862
35930
  * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
@@ -40279,13 +40347,14 @@ var BaseDeviceProvider = class extends BaseAddon {
40279
40347
  async getStatus() {
40280
40348
  const all = await this.ctx.kernel.devices?.getAll() ?? [];
40281
40349
  const summary = this.restoreFailureSummary();
40282
- if (summary === null) return {
40350
+ const base = {
40283
40351
  connected: true,
40284
- deviceCount: all.length
40352
+ deviceCount: all.length,
40353
+ motionSliceWriter: "orchestrator"
40285
40354
  };
40355
+ if (summary === null) return base;
40286
40356
  return {
40287
- connected: true,
40288
- deviceCount: all.length,
40357
+ ...base,
40289
40358
  error: summary
40290
40359
  };
40291
40360
  }
@@ -45721,6 +45790,12 @@ Object.freeze({
45721
45790
  addonId: null,
45722
45791
  access: "view"
45723
45792
  },
45793
+ "notificationRules.getAlarmSensors": {
45794
+ capName: "notification-rules",
45795
+ capScope: "system",
45796
+ addonId: null,
45797
+ access: "view"
45798
+ },
45724
45799
  "notificationRules.getClipRetentionAsks": {
45725
45800
  capName: "notification-rules",
45726
45801
  capScope: "system",
@@ -50204,6 +50279,11 @@ Object.freeze({
50204
50279
  form: "single",
50205
50280
  optional: false
50206
50281
  }],
50282
+ "notificationRules.getAlarmSensors": [{
50283
+ name: "deviceIds",
50284
+ form: "array",
50285
+ optional: false
50286
+ }],
50207
50287
  "notificationRules.setDeviceMuted": [{
50208
50288
  name: "deviceId",
50209
50289
  form: "single",
@@ -52208,7 +52288,7 @@ var PreviewTextField = string().max(200);
52208
52288
  * Whether the notification's preview is a STILL or a short animation.
52209
52289
  *
52210
52290
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
52211
- * rule)"* — the other rules already offer an animated preview, and a ten-hour
52291
+ * rule)"* [sent as a gif or video (as for the other rules)] — the other rules already offer an animated preview, and a ten-hour
52212
52292
  * night reads better as three seconds of motion than as one frame of it. Both
52213
52293
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
52214
52294
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  //#endregion
6
- const require_dist = require("../dist-Cn_fSOpM.js");
6
+ const require_dist = require("../dist-nbfaHTXG.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  //#region src/ha-export/topics.ts
9
9
  /**
@@ -4022,12 +4022,12 @@ async function call(slice, capName, make) {
4022
4022
  //#endregion
4023
4023
  //#region src/ha-export/camera-function-writes.ts
4024
4024
  /**
4025
- * Esegue la SCRITTURA di una funzione camera sull'autorità che la possiede.
4025
+ * Performs the WRITE of a camera function on the authority that owns it.
4026
4026
  *
4027
4027
  * `deriveCameraFunctions`/`resolveCameraFunctionWrite` (`camera-functions.ts`)
4028
- * decidono; questo modulo agisce. È il file che
4029
- * `scripts/check-switch-successor-surfaces.ts` punta come successore della
4030
- * coppia deprecata `getCameraSwitches` / `setCameraSwitch` (D113 fase 4).
4028
+ * decide; this module acts. It is the file that
4029
+ * `scripts/check-switch-successor-surfaces.ts` points to as the successor of the
4030
+ * deprecated pair `getCameraSwitches` / `setCameraSwitch` (D113 phase 4).
4031
4031
  */
4032
4032
  async function applyCameraFunctionWrite(api, deviceId, write, sourceNodeId) {
4033
4033
  switch (write.kind) {
@@ -4087,9 +4087,9 @@ async function applyCameraFunctionWrite(api, deviceId, write, sourceNodeId) {
4087
4087
  //#region src/ha-export/camera-functions.ts
4088
4088
  var DETECTION_CAP = "detection-pipeline";
4089
4089
  var AUDIO_ANALYSIS_CAP = "audio-analysis";
4090
- /** Il wrapper che SCRIVE la storia: tracce, eventi e i loro media. */
4090
+ /** The wrapper that WRITES the history: tracks, events and their media. */
4091
4091
  var PIPELINE_ANALYTICS_CAP = "pipeline-analytics";
4092
- /** Ordine di resa: raggio d'azione più ampio prima, sorgente prima del consumatore. */
4092
+ /** Render order: widest reach first, source before consumer. */
4093
4093
  var ORDER = [
4094
4094
  "stream-broker",
4095
4095
  "object-detection",
@@ -4113,9 +4113,9 @@ var LABELS = {
4113
4113
  notifications: "Notifications"
4114
4114
  };
4115
4115
  /**
4116
- * Quale cap sta dietro ogni switch wrapper-backed. Una mappa invece di un
4117
- * ternario: con tre voci il ternario diventava una catena, e la prossima voce
4118
- * sarebbe finita silenziosamente sul ramo sbagliato.
4116
+ * Which cap stands behind each wrapper-backed switch. A map instead of a
4117
+ * ternary: with three entries the ternary became a chain, and the next entry
4118
+ * would have silently landed on the wrong branch.
4119
4119
  */
4120
4120
  var WRAPPER_CAP_BY_ID = {
4121
4121
  "object-detection": DETECTION_CAP,
@@ -4173,7 +4173,7 @@ function deriveCameraFunctions(reads) {
4173
4173
  ...resolveOne(id, reads)
4174
4174
  }));
4175
4175
  }
4176
- /** `null` = non disponibile: la scrittura va RIFIUTATA, non approssimata. */
4176
+ /** `null` = not available: the write must be REFUSED, not approximated. */
4177
4177
  function resolveCameraFunctionWrite(id, on, reads) {
4178
4178
  if (!resolveOne(id, reads).available) return null;
4179
4179
  switch (id) {
@@ -5333,7 +5333,7 @@ function notificationCenterSpecs(input) {
5333
5333
  (
5334
5334
  /**
5335
5335
  * The last trigger, six entities: five facts plus its picture — "conoscere
5336
- * gli ultimi trigger, le ultime immagini... e da quale label" (operator,
5336
+ * gli ultimi trigger, le ultime immagini... e da quale label" [know the latest triggers, the latest images... and from which label] (operator,
5337
5337
  * 2026-08-25). ENABLED by default like every other entity on this device
5338
5338
  * (see {@link buildComponents}'s docblock): a rule the operator wrote is a
5339
5339
  * rule they want to watch fire.
@@ -6355,7 +6355,7 @@ var HaExportAddon = class extends require_dist.BaseAddon {
6355
6355
  * `className` and no `confidence`; reading those invented names mapped
6356
6356
  * every window to `[]`, so `audio_last_sound` sat at `unknown` for ever
6357
6357
  * while the analyzer was classifying happily (found 2026-08-09, symptom:
6358
- * "le audio labels sembrano non arrivare").
6358
+ * "le audio labels sembrano non arrivare" [the audio labels seem not to arrive]).
6359
6359
  *
6360
6360
  * The best LABEL wins over the macro: `audio` as a value tells an operator
6361
6361
  * nothing, `dog` or `crying` is the whole point. Falls back to the macro
@@ -6724,8 +6724,8 @@ var HaExportAddon = class extends require_dist.BaseAddon {
6724
6724
  /**
6725
6725
  * The most recent notification-rule trigger, GLOBAL and PER RULE —
6726
6726
  * "conoscere gli ultimi trigger, le ultime immagini... e da quale label"
6727
- * (operator, 2026-08-25), extended to "l'ultimo trigger PER REGOLA, con
6728
- * immagine e metadati" (operator, 2026-08-27).
6727
+ * [know the latest triggers, the latest images... and from which label] (operator, 2026-08-25), extended to "l'ultimo trigger PER REGOLA, con
6728
+ * immagine e metadati" [the latest trigger PER RULE, with image and metadata] (operator, 2026-08-27).
6729
6729
  *
6730
6730
  * ONE `getHistory` read serves BOTH: `reduceLastTriggers`
6731
6731
  * (`rule-triggers.ts`) reduces the same newest-first page into "the very
@@ -1,4 +1,4 @@
1
- import { C as buildAddonRouteProvider, F as deviceExportCapability, Nt as EventCategory, Q as oauthIntegrationCapability, a as COCO_TO_MACRO, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, kt as string, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema, xt as nodePin, yt as BaseAddon } from "../dist-DFsf2z4Q.mjs";
1
+ import { C as buildAddonRouteProvider, F as deviceExportCapability, Nt as EventCategory, Q as oauthIntegrationCapability, a as COCO_TO_MACRO, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, kt as string, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema, xt as nodePin, yt as BaseAddon } from "../dist-BITMr6s5.mjs";
2
2
  import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  //#region src/ha-export/topics.ts
4
4
  /**
@@ -4017,12 +4017,12 @@ async function call(slice, capName, make) {
4017
4017
  //#endregion
4018
4018
  //#region src/ha-export/camera-function-writes.ts
4019
4019
  /**
4020
- * Esegue la SCRITTURA di una funzione camera sull'autorità che la possiede.
4020
+ * Performs the WRITE of a camera function on the authority that owns it.
4021
4021
  *
4022
4022
  * `deriveCameraFunctions`/`resolveCameraFunctionWrite` (`camera-functions.ts`)
4023
- * decidono; questo modulo agisce. È il file che
4024
- * `scripts/check-switch-successor-surfaces.ts` punta come successore della
4025
- * coppia deprecata `getCameraSwitches` / `setCameraSwitch` (D113 fase 4).
4023
+ * decide; this module acts. It is the file that
4024
+ * `scripts/check-switch-successor-surfaces.ts` points to as the successor of the
4025
+ * deprecated pair `getCameraSwitches` / `setCameraSwitch` (D113 phase 4).
4026
4026
  */
4027
4027
  async function applyCameraFunctionWrite(api, deviceId, write, sourceNodeId) {
4028
4028
  switch (write.kind) {
@@ -4082,9 +4082,9 @@ async function applyCameraFunctionWrite(api, deviceId, write, sourceNodeId) {
4082
4082
  //#region src/ha-export/camera-functions.ts
4083
4083
  var DETECTION_CAP = "detection-pipeline";
4084
4084
  var AUDIO_ANALYSIS_CAP = "audio-analysis";
4085
- /** Il wrapper che SCRIVE la storia: tracce, eventi e i loro media. */
4085
+ /** The wrapper that WRITES the history: tracks, events and their media. */
4086
4086
  var PIPELINE_ANALYTICS_CAP = "pipeline-analytics";
4087
- /** Ordine di resa: raggio d'azione più ampio prima, sorgente prima del consumatore. */
4087
+ /** Render order: widest reach first, source before consumer. */
4088
4088
  var ORDER = [
4089
4089
  "stream-broker",
4090
4090
  "object-detection",
@@ -4108,9 +4108,9 @@ var LABELS = {
4108
4108
  notifications: "Notifications"
4109
4109
  };
4110
4110
  /**
4111
- * Quale cap sta dietro ogni switch wrapper-backed. Una mappa invece di un
4112
- * ternario: con tre voci il ternario diventava una catena, e la prossima voce
4113
- * sarebbe finita silenziosamente sul ramo sbagliato.
4111
+ * Which cap stands behind each wrapper-backed switch. A map instead of a
4112
+ * ternary: with three entries the ternary became a chain, and the next entry
4113
+ * would have silently landed on the wrong branch.
4114
4114
  */
4115
4115
  var WRAPPER_CAP_BY_ID = {
4116
4116
  "object-detection": DETECTION_CAP,
@@ -4168,7 +4168,7 @@ function deriveCameraFunctions(reads) {
4168
4168
  ...resolveOne(id, reads)
4169
4169
  }));
4170
4170
  }
4171
- /** `null` = non disponibile: la scrittura va RIFIUTATA, non approssimata. */
4171
+ /** `null` = not available: the write must be REFUSED, not approximated. */
4172
4172
  function resolveCameraFunctionWrite(id, on, reads) {
4173
4173
  if (!resolveOne(id, reads).available) return null;
4174
4174
  switch (id) {
@@ -5328,7 +5328,7 @@ function notificationCenterSpecs(input) {
5328
5328
  (
5329
5329
  /**
5330
5330
  * The last trigger, six entities: five facts plus its picture — "conoscere
5331
- * gli ultimi trigger, le ultime immagini... e da quale label" (operator,
5331
+ * gli ultimi trigger, le ultime immagini... e da quale label" [know the latest triggers, the latest images... and from which label] (operator,
5332
5332
  * 2026-08-25). ENABLED by default like every other entity on this device
5333
5333
  * (see {@link buildComponents}'s docblock): a rule the operator wrote is a
5334
5334
  * rule they want to watch fire.
@@ -6350,7 +6350,7 @@ var HaExportAddon = class extends BaseAddon {
6350
6350
  * `className` and no `confidence`; reading those invented names mapped
6351
6351
  * every window to `[]`, so `audio_last_sound` sat at `unknown` for ever
6352
6352
  * while the analyzer was classifying happily (found 2026-08-09, symptom:
6353
- * "le audio labels sembrano non arrivare").
6353
+ * "le audio labels sembrano non arrivare" [the audio labels seem not to arrive]).
6354
6354
  *
6355
6355
  * The best LABEL wins over the macro: `audio` as a value tells an operator
6356
6356
  * nothing, `dog` or `crying` is the whole point. Falls back to the macro
@@ -6719,8 +6719,8 @@ var HaExportAddon = class extends BaseAddon {
6719
6719
  /**
6720
6720
  * The most recent notification-rule trigger, GLOBAL and PER RULE —
6721
6721
  * "conoscere gli ultimi trigger, le ultime immagini... e da quale label"
6722
- * (operator, 2026-08-25), extended to "l'ultimo trigger PER REGOLA, con
6723
- * immagine e metadati" (operator, 2026-08-27).
6722
+ * [know the latest triggers, the latest images... and from which label] (operator, 2026-08-25), extended to "l'ultimo trigger PER REGOLA, con
6723
+ * immagine e metadati" [the latest trigger PER RULE, with image and metadata] (operator, 2026-08-27).
6724
6724
  *
6725
6725
  * ONE `getHistory` read serves BOTH: `reduceLastTriggers`
6726
6726
  * (`rule-triggers.ts`) reduces the same newest-first page into "the very
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.2.151",
3
+ "version": "1.2.152",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",