@camstack/types 1.2.66 → 1.2.68

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/index.d.ts CHANGED
@@ -119,7 +119,7 @@ export { AutomationActionSchema, AutomationConditionOperatorSchema, AutomationCo
119
119
  export * from './cap-call-context.js';
120
120
  export * from './capabilities/index.js';
121
121
  export { APPLE_SA_TO_MACRO, AUDIO_MACRO_LABELS, getAudioMacroClassIds, mapAudioLabelToMacro, YAMNET_TO_MACRO, } from './catalogs/audio-classmap.js';
122
- export { COCO_80_LABELS, COCO_TO_MACRO, MACRO_LABELS, } from './catalogs/coco-classmap.js';
122
+ export { COCO_80_LABELS, COCO_TO_MACRO, DETECTION_MACRO_CLASSES, isDetectionMacroClass, MACRO_LABELS, } from './catalogs/coco-classmap.js';
123
123
  export { DEVICE_STATE_READERS, readDeviceStateFrom, stateVocabularyFor, } from './catalogs/device-state-vocabulary.js';
124
124
  export { colorForKind, DEFAULT_EVENT_COLOR, EVENT_TAXONOMY, type EventTaxonomyCategory, type EventTaxonomyEntry, type EventTaxonomyLevel, getTaxonomyEntry, subKindsOf, TAXONOMY_COLORS, } from './catalogs/event-taxonomy.js';
125
125
  export { buildNcTaxonomy, NC_TAXONOMY, type NcTaxonomy, type NcTaxonomyEntry, NcTaxonomyEntrySchema, NcTaxonomySchema, } from './catalogs/nc-taxonomy.js';
package/dist/index.js CHANGED
@@ -10467,11 +10467,33 @@ var NotificationFormatSchema = zod.z.enum([
10467
10467
  * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
10468
10468
  * renderer's icon set; "acknowledge" survives an adapter that draws it
10469
10469
  * differently.
10470
+ *
10471
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
10472
+ *
10473
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
10474
+ * the whole set onto its own renderer's vocabulary through a
10475
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
10476
+ * fallback, so adding a member here fails every adapter's build until someone
10477
+ * decides its glyph, which is the only place that decision can be made
10478
+ * honestly.
10479
+ *
10480
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
10481
+ * `disarm` straight through; iOS feeds that string to
10482
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
10483
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
10484
+ * mapping, and "the field is documented" is not "the value renders".
10485
+ *
10486
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
10487
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
10488
+ * so an addon that emits a token the running hub does not know does not lose an
10489
+ * icon — its whole `send` fails Zod validation and the notification never
10490
+ * arrives. Never emit a new token from an addon before the train carrying it.
10470
10491
  */
10471
10492
  var NotificationActionIconSchema = zod.z.enum([
10472
10493
  "acknowledge",
10473
10494
  "dismiss",
10474
10495
  "silence",
10496
+ "snooze",
10475
10497
  "view",
10476
10498
  "play",
10477
10499
  "open",
@@ -10479,9 +10501,13 @@ var NotificationActionIconSchema = zod.z.enum([
10479
10501
  "lock",
10480
10502
  "unlock",
10481
10503
  "arm",
10504
+ "arm-home",
10505
+ "arm-away",
10506
+ "arm-night",
10482
10507
  "disarm",
10483
10508
  "light",
10484
- "alert"
10509
+ "alert",
10510
+ "camera"
10485
10511
  ]);
10486
10512
  /** A single tap-through action button. */
10487
10513
  var NotificationActionSchema = zod.z.object({
@@ -11039,6 +11065,29 @@ var MACRO_LABELS = [
11039
11065
  name: "Package"
11040
11066
  }
11041
11067
  ];
11068
+ /**
11069
+ * The class names a per-class COUNT can ever be keyed on — the macro ids, and
11070
+ * only those.
11071
+ *
11072
+ * `preserveOriginal: false` below is the whole reason this set exists: the
11073
+ * executor maps each raw COCO label onto its macro and DROPS everything else,
11074
+ * so a tracked detection's `className` is always one of {@link MACRO_LABELS}.
11075
+ * Any surface that indexes counts by class name — the ZoneAnalytics occupancy
11076
+ * snapshot's `byClass`, and therefore every occupancy rule — can match nothing
11077
+ * else. Derived from the catalog, never hand-listed, so a new macro joins it by
11078
+ * being declared once.
11079
+ *
11080
+ * Cost of not having it, 2026-08-12: the notification rule editor offered the
11081
+ * full taxonomy for an occupancy class (occupancy is the one condition that
11082
+ * does not go through the engine's macro-expanding `expandClassSelector`), two
11083
+ * live rules were authored on `car`, and both counters read 0 forever.
11084
+ */
11085
+ var DETECTION_MACRO_CLASSES = new Set(MACRO_LABELS.map((l) => l.id));
11086
+ /** True when `className` is a macro the detector can actually emit (and hence a
11087
+ * class a count can be keyed on) — see {@link DETECTION_MACRO_CLASSES}. */
11088
+ function isDetectionMacroClass(className) {
11089
+ return DETECTION_MACRO_CLASSES.has(className.trim().toLowerCase());
11090
+ }
11042
11091
  var COCO_TO_MACRO = {
11043
11092
  mapping: {
11044
11093
  person: "person",
@@ -11914,6 +11963,8 @@ var NcSystemEventKindSchema = zod.z.enum([
11914
11963
  "alarm-triggered",
11915
11964
  "alarm-armed",
11916
11965
  "alarm-disarmed",
11966
+ "alarm-arming",
11967
+ "alarm-arm-refused",
11917
11968
  "camera-online",
11918
11969
  "camera-offline",
11919
11970
  "camera-disabled",
@@ -11933,16 +11984,22 @@ var NC_LEGACY_SYSTEM_EVENT_KINDS = new Set([
11933
11984
  */
11934
11985
  var NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NcSystemEventKindSchema.options.filter((kind) => !NC_LEGACY_SYSTEM_EVENT_KINDS.has(kind));
11935
11986
  /**
11936
- * The panel's three transitions, as ONE list.
11987
+ * The panel's transitions, as ONE list.
11937
11988
  *
11938
11989
  * Named here rather than spelled out at each of the four sites that need them
11939
11990
  * (the emitter, the intake, the editor group, the combined-notification gate),
11940
- * because a fourth transition added to the enum and forgotten at one of them is
11941
- * an alarm state nobody can be notified about.
11991
+ * because a transition added to the enum and forgotten at one of them is an
11992
+ * alarm state nobody can be notified about. That is exactly what this list
11993
+ * failed to prevent while it held three: `alarm-arming` was the fourth.
11994
+ *
11995
+ * In LIFECYCLE order, which is the order an editor lists them in and the order
11996
+ * a person experiences them — not the order they were added.
11942
11997
  */
11943
11998
  var NC_ALARM_SYSTEM_EVENT_KINDS = [
11944
- "alarm-triggered",
11999
+ "alarm-arm-refused",
12000
+ "alarm-arming",
11945
12001
  "alarm-armed",
12002
+ "alarm-triggered",
11946
12003
  "alarm-disarmed"
11947
12004
  ];
11948
12005
  /**
@@ -12873,13 +12930,17 @@ var NC_CONDITION_CATALOG = [
12873
12930
  label: "Server update available"
12874
12931
  },
12875
12932
  {
12876
- value: "alarm-triggered",
12877
- label: "Alarm triggered"
12933
+ value: "alarm-arming",
12934
+ label: "Alarm arming (exit delay)"
12878
12935
  },
12879
12936
  {
12880
12937
  value: "alarm-armed",
12881
12938
  label: "Alarm armed"
12882
12939
  },
12940
+ {
12941
+ value: "alarm-triggered",
12942
+ label: "Alarm triggered"
12943
+ },
12883
12944
  {
12884
12945
  value: "alarm-disarmed",
12885
12946
  label: "Alarm disarmed"
@@ -16350,10 +16411,26 @@ var detectionFpsField = {
16350
16411
  default: 10,
16351
16412
  step: 1
16352
16413
  };
16414
+ /**
16415
+ * The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
16416
+ *
16417
+ * The recheck is now on by default (a parked car is invisible to occupancy
16418
+ * rules until the stationary registry has been rebuilt by motion, which after a
16419
+ * restart may be never on a quiet camera). Each cycle re-subscribes a detection
16420
+ * session — an RTSP re-dial — so the switch is only affordable at a WIDE
16421
+ * interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
16422
+ * 30 s. A parked car is therefore counted within 5 minutes of a restart.
16423
+ *
16424
+ * Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
16425
+ * the host validates `attachCamera` against ITS copy of this schema, so a
16426
+ * runner asked for 600 would be rejected by the hub until a `@camstack/server`
16427
+ * carrying the wider bound is installed everywhere. 300 is the widest value
16428
+ * that ships with an addon deploy.
16429
+ */
16353
16430
  var occupancyRecheckSecField = {
16354
16431
  min: 0,
16355
16432
  max: 300,
16356
- default: 30,
16433
+ default: 300,
16357
16434
  step: 5
16358
16435
  };
16359
16436
  var occupancyRecheckFramesField = {
@@ -16551,15 +16628,21 @@ var RunnerCameraConfigSchema = zod.z.object({
16551
16628
  */
16552
16629
  onboardMotionDrivesAnalyzer: zod.z.boolean().default(true),
16553
16630
  /**
16554
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
16555
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
16556
- * this is off by default because the recheck re-subscribes a detection session
16557
- * every N seconds while `watching`, a major source of pull-decoder re-dial
16558
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
16631
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
16632
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
16559
16633
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
16560
16634
  * (and only render) when this is enabled.
16561
- */
16562
- occupancyRecheckEnabled: zod.z.boolean().default(false),
16635
+ *
16636
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
16637
+ * recheck re-subscribes a detection session every N seconds while `watching`
16638
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
16639
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
16640
+ * object is counted only while the stationary registry holds it, and the
16641
+ * registry rebuilds from motion, so after a restart a parked car was invisible
16642
+ * to every occupancy rule until something moved in front of it. The churn is
16643
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
16644
+ */
16645
+ occupancyRecheckEnabled: zod.z.boolean().default(true),
16563
16646
  occupancyRecheckSec: zod.z.number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
16564
16647
  occupancyRecheckFrames: zod.z.number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
16565
16648
  /**
@@ -16692,8 +16775,8 @@ var RunnerCameraDeviceUIFields = [
16692
16775
  type: "boolean",
16693
16776
  style: "checkbox",
16694
16777
  label: "Occupancy re-check",
16695
- description: "Periodically re-sample a few frames during the watching phase to confirm the scene is truly empty (catches stationary objects motion-gating would miss). Off by default it adds decoder re-dial churn.",
16696
- default: false
16778
+ description: "Periodically re-sample a few frames during the watching phase, so objects that are simply parked are still counted (motion gating alone loses them after a restart). On by default at a wide interval; turn it off on cameras where the extra decoder re-dial is expensive.",
16779
+ default: true
16697
16780
  },
16698
16781
  {
16699
16782
  key: "occupancyRecheckSec",
@@ -31657,11 +31740,40 @@ var SystemMirror = class {
31657
31740
  cb(deviceId, lastInfo);
31658
31741
  } catch {}
31659
31742
  }
31743
+ /**
31744
+ * ONE device's bindings, not the fleet's.
31745
+ *
31746
+ * This runs on every `capability.binding-changed` AND on every
31747
+ * `device.registered`, per mirror. It used to answer that with
31748
+ * `getAllBindings` and `.find()` the one row — measured on the live
31749
+ * hub (524 devices): 903 ms and 373 KB per call, against 21 ms and
31750
+ * 613 bytes for `getBindings({deviceId})`.
31751
+ *
31752
+ * Adopting 20 Home Assistant devices registers 166 devices (parents +
31753
+ * accessories), so an operator with the admin UI open bought 166
31754
+ * full-fleet binding sweeps: ~150 s of hub main-thread time inside a
31755
+ * 219 s adoption, with every other RPC — including the adoption's own —
31756
+ * queued behind them. Same defect, same fix, as `refreshDeviceMetadata`
31757
+ * below (`listAll` → `getDevice`).
31758
+ *
31759
+ * Removal is the destructive direction (every proxy handle a consumer
31760
+ * holds goes inert), and an EMPTY binding list is not a claim that the
31761
+ * device is gone — a device can legitimately bind nothing. So it needs
31762
+ * a second read to agree (D49); `device.unregistered` remains the
31763
+ * primary removal path.
31764
+ */
31660
31765
  async refreshBinding(deviceId) {
31661
31766
  try {
31662
- const fresh = (await this.api.deviceManager.getAllBindings.query({})).find((b) => b.deviceId === deviceId);
31663
- if (fresh) this.bindings.set(deviceId, fresh);
31664
- else this.applyDeviceRemoval(deviceId);
31767
+ const fresh = await this.api.deviceManager.getBindings.query({ deviceId });
31768
+ if (fresh.entries.length > 0) {
31769
+ this.bindings.set(deviceId, fresh);
31770
+ return;
31771
+ }
31772
+ if (await this.api.deviceManager.getDevice.query({ deviceId }) === null) {
31773
+ this.applyDeviceRemoval(deviceId);
31774
+ return;
31775
+ }
31776
+ this.bindings.set(deviceId, fresh);
31665
31777
  } catch {}
31666
31778
  }
31667
31779
  async refreshDeviceMetadata(deviceId, kind) {
@@ -39833,6 +39945,21 @@ function toggleAudioLabel(labels, id) {
39833
39945
  var CLASS_CONDITION_IDS = new Set(["classes", "classesExclude"]);
39834
39946
  /** Condition ids that render the sensor/control label picker. */
39835
39947
  var LABEL_CONDITION_IDS = new Set(["sensorKinds"]);
39948
+ /**
39949
+ * Condition ids whose class scope is a COUNT, not a match — they get the
39950
+ * MACRO-ONLY vocabulary.
39951
+ *
39952
+ * Every other class condition goes through the engine's `expandClassSelector`,
39953
+ * which expands a macro to its subs and matches a sub against the detection's
39954
+ * own class. Occupancy does not: it reads `byClass[className]` out of the
39955
+ * ZoneAnalytics snapshot, and that record is keyed on MACROS only (the executor
39956
+ * maps COCO onto the macro and drops the rest). A sub-type therefore names a key
39957
+ * that does not exist — the counter is 0 forever and the rule cannot fire.
39958
+ *
39959
+ * Live cost, 2026-08-12: two rules on device 617 were authored with `car`
39960
+ * because this picker offered the whole taxonomy. They were silent for a day.
39961
+ */
39962
+ var COUNT_CLASS_CONDITION_IDS = new Set(["occupancy"]);
39836
39963
  function toOption(entry) {
39837
39964
  return {
39838
39965
  value: entry.kind,
@@ -39910,6 +40037,14 @@ function pickerForCondition(conditionId, taxonomy) {
39910
40037
  });
39911
40038
  return groups.length > 0 ? { groups } : null;
39912
40039
  }
40040
+ if (COUNT_CLASS_CONDITION_IDS.has(conditionId)) {
40041
+ const options = taxonomy.videoClasses.filter((e) => e.parentKind === null).map(toOption);
40042
+ return options.length > 0 ? { groups: [{
40043
+ key: "macro",
40044
+ fallbackLabel: "Classes",
40045
+ options
40046
+ }] } : null;
40047
+ }
39913
40048
  if (LABEL_CONDITION_IDS.has(conditionId)) {
39914
40049
  const groups = groupLabels(taxonomy.labels);
39915
40050
  return groups.length > 0 ? { groups } : null;
@@ -42512,6 +42647,7 @@ exports.DETAIL_CROP_PADDING_FIELD = DETAIL_CROP_PADDING_FIELD;
42512
42647
  exports.DETAIL_CROP_PADDING_KEY = DETAIL_CROP_PADDING_KEY;
42513
42648
  exports.DETAIL_CROP_SECTION_ID = DETAIL_CROP_SECTION_ID;
42514
42649
  exports.DETAIL_CROP_SQUARE_KEY = DETAIL_CROP_SQUARE_KEY;
42650
+ exports.DETECTION_MACRO_CLASSES = DETECTION_MACRO_CLASSES;
42515
42651
  exports.DETECTION_PIPELINE_CAP_NAME = DETECTION_PIPELINE_CAP_NAME;
42516
42652
  exports.DEVICE_BACKEND_TO_FORMAT = DEVICE_BACKEND_TO_FORMAT;
42517
42653
  exports.DEVICE_CAP_NAMES = DEVICE_CAP_NAMES;
@@ -43337,6 +43473,7 @@ exports.isAudioLabelSelected = isAudioLabelSelected;
43337
43473
  exports.isBaseConditionKey = isBaseConditionKey;
43338
43474
  exports.isCollectionArrayMethod = isCollectionArrayMethod;
43339
43475
  exports.isDeployableToAgent = isDeployableToAgent;
43476
+ exports.isDetectionMacroClass = isDetectionMacroClass;
43340
43477
  exports.isDeviceConfigCap = require_sleep.isDeviceConfigCap;
43341
43478
  exports.isDeviceScopedCap = require_sleep.isDeviceScopedCap;
43342
43479
  exports.isEvent = require_sleep.isEvent;