@camstack/addon-decoder-nodeav 1.2.14 → 1.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +148 -17
  2. package/dist/index.mjs +148 -17
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13067,11 +13067,33 @@ var NotificationFormatSchema = _enum([
13067
13067
  * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
13068
13068
  * renderer's icon set; "acknowledge" survives an adapter that draws it
13069
13069
  * differently.
13070
+ *
13071
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
13072
+ *
13073
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
13074
+ * the whole set onto its own renderer's vocabulary through a
13075
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
13076
+ * fallback, so adding a member here fails every adapter's build until someone
13077
+ * decides its glyph, which is the only place that decision can be made
13078
+ * honestly.
13079
+ *
13080
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
13081
+ * `disarm` straight through; iOS feeds that string to
13082
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
13083
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
13084
+ * mapping, and "the field is documented" is not "the value renders".
13085
+ *
13086
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
13087
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
13088
+ * so an addon that emits a token the running hub does not know does not lose an
13089
+ * icon — its whole `send` fails Zod validation and the notification never
13090
+ * arrives. Never emit a new token from an addon before the train carrying it.
13070
13091
  */
13071
13092
  var NotificationActionIconSchema = _enum([
13072
13093
  "acknowledge",
13073
13094
  "dismiss",
13074
13095
  "silence",
13096
+ "snooze",
13075
13097
  "view",
13076
13098
  "play",
13077
13099
  "open",
@@ -13079,9 +13101,13 @@ var NotificationActionIconSchema = _enum([
13079
13101
  "lock",
13080
13102
  "unlock",
13081
13103
  "arm",
13104
+ "arm-home",
13105
+ "arm-away",
13106
+ "arm-night",
13082
13107
  "disarm",
13083
13108
  "light",
13084
- "alert"
13109
+ "alert",
13110
+ "camera"
13085
13111
  ]);
13086
13112
  /** A single tap-through action button. */
13087
13113
  var NotificationActionSchema = object({
@@ -13097,7 +13123,23 @@ var NotificationActionSchema = object({
13097
13123
  * else — see `notification-center/action-token.ts` for what that does and
13098
13124
  * does not buy.
13099
13125
  */
13100
- destructive: boolean().optional()
13126
+ destructive: boolean().optional(),
13127
+ /**
13128
+ * How the tap should REACH the url.
13129
+ *
13130
+ * `navigate` (absent, and every button authored before this field) opens it:
13131
+ * the phone leaves the notification and shows whatever the callback returns.
13132
+ * That is right for a button whose answer the operator wants to read.
13133
+ *
13134
+ * `background` fires it as a POST and stays put. It exists for the buttons
13135
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
13136
+ * an answer to the notification, and being thrown into a browser tab to
13137
+ * confirm it costs more attention than the notification did. A backend that
13138
+ * cannot do a background call renders it as an ordinary link (the adapters
13139
+ * fall back rather than dropping the button), so this is a preference, never
13140
+ * a requirement.
13141
+ */
13142
+ mode: _enum(["navigate", "background"]).optional()
13101
13143
  });
13102
13144
  /**
13103
13145
  * The canonical notification. `body` is the only hard field (Apprise model).
@@ -13265,6 +13307,24 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
13265
13307
  targetId: string(),
13266
13308
  enabled: boolean()
13267
13309
  }), _void(), { kind: "mutation" });
13310
+ new Set([
13311
+ {
13312
+ id: "person",
13313
+ name: "Person"
13314
+ },
13315
+ {
13316
+ id: "vehicle",
13317
+ name: "Vehicle"
13318
+ },
13319
+ {
13320
+ id: "animal",
13321
+ name: "Animal"
13322
+ },
13323
+ {
13324
+ id: "package",
13325
+ name: "Package"
13326
+ }
13327
+ ].map((l) => l.id));
13268
13328
  var COCO_TO_MACRO = {
13269
13329
  mapping: {
13270
13330
  person: "person",
@@ -14062,6 +14122,8 @@ var NcSystemEventKindSchema = _enum([
14062
14122
  "alarm-triggered",
14063
14123
  "alarm-armed",
14064
14124
  "alarm-disarmed",
14125
+ "alarm-arming",
14126
+ "alarm-arm-refused",
14065
14127
  "camera-online",
14066
14128
  "camera-offline",
14067
14129
  "camera-disabled",
@@ -14098,6 +14160,9 @@ var NcSystemEventConditionSchema = object({
14098
14160
  nodeIds: array(string().min(1)).min(1).optional(),
14099
14161
  packageNames: array(string().min(1)).min(1).optional()
14100
14162
  });
14163
+ /** Hard ceiling on a window (24h). A snooze that could not expire would be an
14164
+ * outage the operator asked for once and forgot. */
14165
+ var NC_SNOOZE_MAX_MINUTES = 1440;
14101
14166
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
14102
14167
  var NcScheduleSchema = object({
14103
14168
  windows: array(object({
@@ -14474,15 +14539,15 @@ var NcConditionsSchema = object({
14474
14539
  * (an `immediate` rule naming an `audio-*` class, one notification per
14475
14540
  * classified sample) stays exactly as it was for rules that already use it.
14476
14541
  *
14477
- * NOT in {@link NC_CONDITION_CATALOG} yet, and that is the sequencing rule
14478
- * rather than an oversight: the viewer mirrors the descriptor enums BY HAND
14479
- * (`camstack/src/data/notification-center.ts`, guarded by
14480
- * `scripts/check-viewer-condition-mirror.ts`) and its rule editor STRIPS the
14542
+ * In {@link NC_CONDITION_CATALOG} since P2, and the ORDER it got there is the
14543
+ * rule rather than an accident: the viewer mirrors the descriptor enums BY
14544
+ * HAND (`camstack/src/data/notification-center.ts`, guarded by
14545
+ * `scripts/check-viewer-condition-mirror.ts`) and its rule editor strips the
14481
14546
  * condition fields it does not know when a rule is saved from the phone.
14482
14547
  * Publishing an editor for a condition the app cannot round-trip is how an
14483
- * operator loses a rule's conditions by opening it — so the descriptor, the
14484
- * admin widget and the viewer mirror land together (P2 + P3), and only then
14485
- * does an audio rule become authorable.
14548
+ * operator loses a rule's conditions by opening it — so the viewer mirror
14549
+ * (P3, shipped) went FIRST, and the descriptor an editor renders from
14550
+ * follows here.
14486
14551
  */
14487
14552
  audio: NcAudioConditionSchema.optional()
14488
14553
  });
@@ -14718,6 +14783,30 @@ var NcRuleInputSchema = object({
14718
14783
  */
14719
14784
  snoozeAllowGlobal: boolean().optional(),
14720
14785
  /**
14786
+ * The snooze durations THIS rule's notification offers as buttons, in
14787
+ * minutes.
14788
+ *
14789
+ * Three states, and all three are distinct — which is exactly why this is
14790
+ * `.optional()` and never `.default()`. A Zod default does not run on the
14791
+ * addon cap path (three production failures in one day), so a schema default
14792
+ * would collapse the first two:
14793
+ *
14794
+ * | value | meaning |
14795
+ * | --- | --- |
14796
+ * | absent | the operator never said ⇒ {@link NC_DEFAULT_SNOOZE_MINUTES} |
14797
+ * | `[]` | **no snooze buttons on this rule** — the explicit override |
14798
+ * | a list | these choices, de-duplicated and sorted, at most four |
14799
+ *
14800
+ * `.max(4)` because the notifier's own action budget is small (ntfy allows
14801
+ * three buttons in total) and a rule that spent it all on snooze choices
14802
+ * would push its own tap-through actions off the notification.
14803
+ *
14804
+ * An empty list is NOT an alarm exemption: a rule the alarm is about, or
14805
+ * that arms the panel, is exempt automatically and cannot be silenced by a
14806
+ * window from anywhere (D133).
14807
+ */
14808
+ snoozeOptions: array(number().int().min(1).max(NC_SNOOZE_MAX_MINUTES)).max(4).optional(),
14809
+ /**
14721
14810
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
14722
14811
  *
14723
14812
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -14817,6 +14906,7 @@ var NcConditionDescriptorSchema = object({
14817
14906
  "device",
14818
14907
  "package",
14819
14908
  "occupancy",
14909
+ "audio",
14820
14910
  "system"
14821
14911
  ]),
14822
14912
  label: string(),
@@ -14835,6 +14925,7 @@ var NcConditionDescriptorSchema = object({
14835
14925
  "crossingSelect",
14836
14926
  "polygonDraw",
14837
14927
  "occupancy",
14928
+ "audio",
14838
14929
  "deviceState",
14839
14930
  "systemEvent"
14840
14931
  ]),
@@ -14986,7 +15077,20 @@ var NcSnoozeInputSchema = object({
14986
15077
  ruleId: string().optional(),
14987
15078
  /** Required when `scope: 'device'`. */
14988
15079
  deviceId: number().int().optional(),
14989
- durationMinutes: number().int().min(1).max(1440),
15080
+ /**
15081
+ * Narrow the window to these subject classes — "the cat, not the person".
15082
+ *
15083
+ * ORTHOGONAL to `scope`, deliberately, and absent means EVERY class: that is
15084
+ * what every window authored before this field meant, so no persisted row
15085
+ * changes meaning and no client has to learn anything to keep working.
15086
+ *
15087
+ * It is what makes the window's real key `(deviceId, classes[])` and lets it
15088
+ * cross rules (D133): the operator points at a camera and a kind of thing,
15089
+ * not at whichever of their four rules happened to produce the notification
15090
+ * they are dismissing.
15091
+ */
15092
+ classes: array(string().min(1)).min(1).optional(),
15093
+ durationMinutes: number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
14990
15094
  /**
14991
15095
  * Silence this for EVERY recipient, not just the caller. Permission is
14992
15096
  * checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
@@ -15011,6 +15115,10 @@ var NcSnoozeSchema = object({
15011
15115
  scope: NcSnoozeScopeSchema,
15012
15116
  ruleId: string().optional(),
15013
15117
  deviceId: number().int().optional(),
15118
+ /** Subject classes this window covers. ABSENT = every class — see
15119
+ * {@link NcSnoozeInputSchema.shape.classes}. Lives in the JSON blob and has
15120
+ * no SQLite column: nothing queries a window by class. */
15121
+ classes: array(string().min(1)).min(1).optional(),
15014
15122
  startedAt: number(),
15015
15123
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
15016
15124
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -17248,6 +17356,22 @@ var detectionFpsField = {
17248
17356
  default: 10,
17249
17357
  step: 1
17250
17358
  };
17359
+ /**
17360
+ * The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
17361
+ *
17362
+ * The recheck is now on by default (a parked car is invisible to occupancy
17363
+ * rules until the stationary registry has been rebuilt by motion, which after a
17364
+ * restart may be never on a quiet camera). Each cycle re-subscribes a detection
17365
+ * session — an RTSP re-dial — so the switch is only affordable at a WIDE
17366
+ * interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
17367
+ * 30 s. A parked car is therefore counted within 5 minutes of a restart.
17368
+ *
17369
+ * Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
17370
+ * the host validates `attachCamera` against ITS copy of this schema, so a
17371
+ * runner asked for 600 would be rejected by the hub until a `@camstack/server`
17372
+ * carrying the wider bound is installed everywhere. 300 is the widest value
17373
+ * that ships with an addon deploy.
17374
+ */
17251
17375
  var occupancyRecheckSecField = {
17252
17376
  min: 0,
17253
17377
  max: 300,
@@ -17449,15 +17573,21 @@ var RunnerCameraConfigSchema = object({
17449
17573
  */
17450
17574
  onboardMotionDrivesAnalyzer: boolean().default(true),
17451
17575
  /**
17452
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
17453
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
17454
- * this is off by default because the recheck re-subscribes a detection session
17455
- * every N seconds while `watching`, a major source of pull-decoder re-dial
17456
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
17576
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
17577
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
17457
17578
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
17458
17579
  * (and only render) when this is enabled.
17459
- */
17460
- occupancyRecheckEnabled: boolean().default(false),
17580
+ *
17581
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
17582
+ * recheck re-subscribes a detection session every N seconds while `watching`
17583
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
17584
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
17585
+ * object is counted only while the stationary registry holds it, and the
17586
+ * registry rebuilds from motion, so after a restart a parked car was invisible
17587
+ * to every occupancy rule until something moved in front of it. The churn is
17588
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
17589
+ */
17590
+ occupancyRecheckEnabled: boolean().default(true),
17461
17591
  occupancyRecheckSec: number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
17462
17592
  occupancyRecheckFrames: number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
17463
17593
  /**
@@ -30583,6 +30713,7 @@ Object.freeze({
30583
30713
  "network-access": "ingress",
30584
30714
  "smtp-provider": "email"
30585
30715
  });
30716
+ new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
30586
30717
  new Set(["devices", "classes"]);
30587
30718
  /**
30588
30719
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
package/dist/index.mjs CHANGED
@@ -13063,11 +13063,33 @@ var NotificationFormatSchema = _enum([
13063
13063
  * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
13064
13064
  * renderer's icon set; "acknowledge" survives an adapter that draws it
13065
13065
  * differently.
13066
+ *
13067
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
13068
+ *
13069
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
13070
+ * the whole set onto its own renderer's vocabulary through a
13071
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
13072
+ * fallback, so adding a member here fails every adapter's build until someone
13073
+ * decides its glyph, which is the only place that decision can be made
13074
+ * honestly.
13075
+ *
13076
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
13077
+ * `disarm` straight through; iOS feeds that string to
13078
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
13079
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
13080
+ * mapping, and "the field is documented" is not "the value renders".
13081
+ *
13082
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
13083
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
13084
+ * so an addon that emits a token the running hub does not know does not lose an
13085
+ * icon — its whole `send` fails Zod validation and the notification never
13086
+ * arrives. Never emit a new token from an addon before the train carrying it.
13066
13087
  */
13067
13088
  var NotificationActionIconSchema = _enum([
13068
13089
  "acknowledge",
13069
13090
  "dismiss",
13070
13091
  "silence",
13092
+ "snooze",
13071
13093
  "view",
13072
13094
  "play",
13073
13095
  "open",
@@ -13075,9 +13097,13 @@ var NotificationActionIconSchema = _enum([
13075
13097
  "lock",
13076
13098
  "unlock",
13077
13099
  "arm",
13100
+ "arm-home",
13101
+ "arm-away",
13102
+ "arm-night",
13078
13103
  "disarm",
13079
13104
  "light",
13080
- "alert"
13105
+ "alert",
13106
+ "camera"
13081
13107
  ]);
13082
13108
  /** A single tap-through action button. */
13083
13109
  var NotificationActionSchema = object({
@@ -13093,7 +13119,23 @@ var NotificationActionSchema = object({
13093
13119
  * else — see `notification-center/action-token.ts` for what that does and
13094
13120
  * does not buy.
13095
13121
  */
13096
- destructive: boolean().optional()
13122
+ destructive: boolean().optional(),
13123
+ /**
13124
+ * How the tap should REACH the url.
13125
+ *
13126
+ * `navigate` (absent, and every button authored before this field) opens it:
13127
+ * the phone leaves the notification and shows whatever the callback returns.
13128
+ * That is right for a button whose answer the operator wants to read.
13129
+ *
13130
+ * `background` fires it as a POST and stays put. It exists for the buttons
13131
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
13132
+ * an answer to the notification, and being thrown into a browser tab to
13133
+ * confirm it costs more attention than the notification did. A backend that
13134
+ * cannot do a background call renders it as an ordinary link (the adapters
13135
+ * fall back rather than dropping the button), so this is a preference, never
13136
+ * a requirement.
13137
+ */
13138
+ mode: _enum(["navigate", "background"]).optional()
13097
13139
  });
13098
13140
  /**
13099
13141
  * The canonical notification. `body` is the only hard field (Apprise model).
@@ -13261,6 +13303,24 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
13261
13303
  targetId: string(),
13262
13304
  enabled: boolean()
13263
13305
  }), _void(), { kind: "mutation" });
13306
+ new Set([
13307
+ {
13308
+ id: "person",
13309
+ name: "Person"
13310
+ },
13311
+ {
13312
+ id: "vehicle",
13313
+ name: "Vehicle"
13314
+ },
13315
+ {
13316
+ id: "animal",
13317
+ name: "Animal"
13318
+ },
13319
+ {
13320
+ id: "package",
13321
+ name: "Package"
13322
+ }
13323
+ ].map((l) => l.id));
13264
13324
  var COCO_TO_MACRO = {
13265
13325
  mapping: {
13266
13326
  person: "person",
@@ -14058,6 +14118,8 @@ var NcSystemEventKindSchema = _enum([
14058
14118
  "alarm-triggered",
14059
14119
  "alarm-armed",
14060
14120
  "alarm-disarmed",
14121
+ "alarm-arming",
14122
+ "alarm-arm-refused",
14061
14123
  "camera-online",
14062
14124
  "camera-offline",
14063
14125
  "camera-disabled",
@@ -14094,6 +14156,9 @@ var NcSystemEventConditionSchema = object({
14094
14156
  nodeIds: array(string().min(1)).min(1).optional(),
14095
14157
  packageNames: array(string().min(1)).min(1).optional()
14096
14158
  });
14159
+ /** Hard ceiling on a window (24h). A snooze that could not expire would be an
14160
+ * outage the operator asked for once and forgot. */
14161
+ var NC_SNOOZE_MAX_MINUTES = 1440;
14097
14162
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
14098
14163
  var NcScheduleSchema = object({
14099
14164
  windows: array(object({
@@ -14470,15 +14535,15 @@ var NcConditionsSchema = object({
14470
14535
  * (an `immediate` rule naming an `audio-*` class, one notification per
14471
14536
  * classified sample) stays exactly as it was for rules that already use it.
14472
14537
  *
14473
- * NOT in {@link NC_CONDITION_CATALOG} yet, and that is the sequencing rule
14474
- * rather than an oversight: the viewer mirrors the descriptor enums BY HAND
14475
- * (`camstack/src/data/notification-center.ts`, guarded by
14476
- * `scripts/check-viewer-condition-mirror.ts`) and its rule editor STRIPS the
14538
+ * In {@link NC_CONDITION_CATALOG} since P2, and the ORDER it got there is the
14539
+ * rule rather than an accident: the viewer mirrors the descriptor enums BY
14540
+ * HAND (`camstack/src/data/notification-center.ts`, guarded by
14541
+ * `scripts/check-viewer-condition-mirror.ts`) and its rule editor strips the
14477
14542
  * condition fields it does not know when a rule is saved from the phone.
14478
14543
  * Publishing an editor for a condition the app cannot round-trip is how an
14479
- * operator loses a rule's conditions by opening it — so the descriptor, the
14480
- * admin widget and the viewer mirror land together (P2 + P3), and only then
14481
- * does an audio rule become authorable.
14544
+ * operator loses a rule's conditions by opening it — so the viewer mirror
14545
+ * (P3, shipped) went FIRST, and the descriptor an editor renders from
14546
+ * follows here.
14482
14547
  */
14483
14548
  audio: NcAudioConditionSchema.optional()
14484
14549
  });
@@ -14714,6 +14779,30 @@ var NcRuleInputSchema = object({
14714
14779
  */
14715
14780
  snoozeAllowGlobal: boolean().optional(),
14716
14781
  /**
14782
+ * The snooze durations THIS rule's notification offers as buttons, in
14783
+ * minutes.
14784
+ *
14785
+ * Three states, and all three are distinct — which is exactly why this is
14786
+ * `.optional()` and never `.default()`. A Zod default does not run on the
14787
+ * addon cap path (three production failures in one day), so a schema default
14788
+ * would collapse the first two:
14789
+ *
14790
+ * | value | meaning |
14791
+ * | --- | --- |
14792
+ * | absent | the operator never said ⇒ {@link NC_DEFAULT_SNOOZE_MINUTES} |
14793
+ * | `[]` | **no snooze buttons on this rule** — the explicit override |
14794
+ * | a list | these choices, de-duplicated and sorted, at most four |
14795
+ *
14796
+ * `.max(4)` because the notifier's own action budget is small (ntfy allows
14797
+ * three buttons in total) and a rule that spent it all on snooze choices
14798
+ * would push its own tap-through actions off the notification.
14799
+ *
14800
+ * An empty list is NOT an alarm exemption: a rule the alarm is about, or
14801
+ * that arms the panel, is exempt automatically and cannot be silenced by a
14802
+ * window from anywhere (D133).
14803
+ */
14804
+ snoozeOptions: array(number().int().min(1).max(NC_SNOOZE_MAX_MINUTES)).max(4).optional(),
14805
+ /**
14717
14806
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
14718
14807
  *
14719
14808
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -14813,6 +14902,7 @@ var NcConditionDescriptorSchema = object({
14813
14902
  "device",
14814
14903
  "package",
14815
14904
  "occupancy",
14905
+ "audio",
14816
14906
  "system"
14817
14907
  ]),
14818
14908
  label: string(),
@@ -14831,6 +14921,7 @@ var NcConditionDescriptorSchema = object({
14831
14921
  "crossingSelect",
14832
14922
  "polygonDraw",
14833
14923
  "occupancy",
14924
+ "audio",
14834
14925
  "deviceState",
14835
14926
  "systemEvent"
14836
14927
  ]),
@@ -14982,7 +15073,20 @@ var NcSnoozeInputSchema = object({
14982
15073
  ruleId: string().optional(),
14983
15074
  /** Required when `scope: 'device'`. */
14984
15075
  deviceId: number().int().optional(),
14985
- durationMinutes: number().int().min(1).max(1440),
15076
+ /**
15077
+ * Narrow the window to these subject classes — "the cat, not the person".
15078
+ *
15079
+ * ORTHOGONAL to `scope`, deliberately, and absent means EVERY class: that is
15080
+ * what every window authored before this field meant, so no persisted row
15081
+ * changes meaning and no client has to learn anything to keep working.
15082
+ *
15083
+ * It is what makes the window's real key `(deviceId, classes[])` and lets it
15084
+ * cross rules (D133): the operator points at a camera and a kind of thing,
15085
+ * not at whichever of their four rules happened to produce the notification
15086
+ * they are dismissing.
15087
+ */
15088
+ classes: array(string().min(1)).min(1).optional(),
15089
+ durationMinutes: number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
14986
15090
  /**
14987
15091
  * Silence this for EVERY recipient, not just the caller. Permission is
14988
15092
  * checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
@@ -15007,6 +15111,10 @@ var NcSnoozeSchema = object({
15007
15111
  scope: NcSnoozeScopeSchema,
15008
15112
  ruleId: string().optional(),
15009
15113
  deviceId: number().int().optional(),
15114
+ /** Subject classes this window covers. ABSENT = every class — see
15115
+ * {@link NcSnoozeInputSchema.shape.classes}. Lives in the JSON blob and has
15116
+ * no SQLite column: nothing queries a window by class. */
15117
+ classes: array(string().min(1)).min(1).optional(),
15010
15118
  startedAt: number(),
15011
15119
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
15012
15120
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -17244,6 +17352,22 @@ var detectionFpsField = {
17244
17352
  default: 10,
17245
17353
  step: 1
17246
17354
  };
17355
+ /**
17356
+ * The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
17357
+ *
17358
+ * The recheck is now on by default (a parked car is invisible to occupancy
17359
+ * rules until the stationary registry has been rebuilt by motion, which after a
17360
+ * restart may be never on a quiet camera). Each cycle re-subscribes a detection
17361
+ * session — an RTSP re-dial — so the switch is only affordable at a WIDE
17362
+ * interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
17363
+ * 30 s. A parked car is therefore counted within 5 minutes of a restart.
17364
+ *
17365
+ * Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
17366
+ * the host validates `attachCamera` against ITS copy of this schema, so a
17367
+ * runner asked for 600 would be rejected by the hub until a `@camstack/server`
17368
+ * carrying the wider bound is installed everywhere. 300 is the widest value
17369
+ * that ships with an addon deploy.
17370
+ */
17247
17371
  var occupancyRecheckSecField = {
17248
17372
  min: 0,
17249
17373
  max: 300,
@@ -17445,15 +17569,21 @@ var RunnerCameraConfigSchema = object({
17445
17569
  */
17446
17570
  onboardMotionDrivesAnalyzer: boolean().default(true),
17447
17571
  /**
17448
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
17449
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
17450
- * this is off by default because the recheck re-subscribes a detection session
17451
- * every N seconds while `watching`, a major source of pull-decoder re-dial
17452
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
17572
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
17573
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
17453
17574
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
17454
17575
  * (and only render) when this is enabled.
17455
- */
17456
- occupancyRecheckEnabled: boolean().default(false),
17576
+ *
17577
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
17578
+ * recheck re-subscribes a detection session every N seconds while `watching`
17579
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
17580
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
17581
+ * object is counted only while the stationary registry holds it, and the
17582
+ * registry rebuilds from motion, so after a restart a parked car was invisible
17583
+ * to every occupancy rule until something moved in front of it. The churn is
17584
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
17585
+ */
17586
+ occupancyRecheckEnabled: boolean().default(true),
17457
17587
  occupancyRecheckSec: number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
17458
17588
  occupancyRecheckFrames: number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
17459
17589
  /**
@@ -30579,6 +30709,7 @@ Object.freeze({
30579
30709
  "network-access": "ingress",
30580
30710
  "smtp-provider": "email"
30581
30711
  });
30712
+ new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
30582
30713
  new Set(["devices", "classes"]);
30583
30714
  /**
30584
30715
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-decoder-nodeav",
3
- "version": "1.2.14",
3
+ "version": "1.2.16",
4
4
  "description": "Standalone in-process node-av decoder addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",