@camstack/types 1.2.302 → 1.2.303

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.js CHANGED
@@ -6008,7 +6008,16 @@ var AutomationActionSchema = zod.z.discriminatedUnion("kind", [
6008
6008
  }),
6009
6009
  zod.z.object({
6010
6010
  kind: zod.z.literal("cap"),
6011
- deviceId: zod.z.union([zod.z.number().int(), zod.z.object({ $var: zod.z.string().min(1) })]),
6011
+ /**
6012
+ * One device, several same-family devices (locks / climate family / …),
6013
+ * or `{ $var }` for the device that triggered the run. An array is
6014
+ * fan-out at codegen — each id gets the same cap.method call.
6015
+ */
6016
+ deviceId: zod.z.union([
6017
+ zod.z.number().int(),
6018
+ zod.z.array(zod.z.number().int()).min(1),
6019
+ zod.z.object({ $var: zod.z.string().min(1) })
6020
+ ]),
6012
6021
  cap: zod.z.string().min(1),
6013
6022
  method: zod.z.string().min(1),
6014
6023
  /** Values may carry `{{vars.x}}` slots, which SUBSTITUTE and do not
@@ -6171,7 +6180,14 @@ function emitTrigger(trigger, index) {
6171
6180
  function emitAction(action, index) {
6172
6181
  switch (action.kind) {
6173
6182
  case "wait": return ` case ${index}: await sleep(${j(action.seconds * 1e3)}); break`;
6174
- case "cap": return ` case ${index}: {
6183
+ case "cap":
6184
+ if (Array.isArray(action.deviceId)) return ` case ${index}: {
6185
+ for (const deviceId of ${j(action.deviceId)}) {
6186
+ await actuate(deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)
6187
+ }
6188
+ break
6189
+ }`;
6190
+ return ` case ${index}: {
6175
6191
  const deviceId = ${typeof action.deviceId === "number" ? j(action.deviceId) : `resolveTarget(vars, ${j(action.deviceId.$var)})`}
6176
6192
  await actuate(deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)
6177
6193
  break
@@ -6405,11 +6421,13 @@ export default function block(ctx: AddonContext) {
6405
6421
  ): Promise<void> {
6406
6422
  const resolved: Record<string, unknown> = {}
6407
6423
  for (const [k, v] of Object.entries(args)) resolved[k] = substitute(v, vars)
6424
+ // Forked-addon ctx.api namespaces are tRPC Proxies over a noop function
6425
+ // (typeof === 'function'), not plain objects — reject only null/undefined.
6408
6426
  const client: unknown = Reflect.get(ctx.api, capToRouterKey(cap))
6409
6427
  const proc: unknown =
6410
- typeof client === 'object' && client !== null ? Reflect.get(client, method) : undefined
6428
+ client !== null && client !== undefined ? Reflect.get(client as object, method) : undefined
6411
6429
  const mutate: unknown =
6412
- typeof proc === 'object' && proc !== null ? Reflect.get(proc, 'mutate') : undefined
6430
+ proc !== null && proc !== undefined ? Reflect.get(proc as object, 'mutate') : undefined
6413
6431
  if (typeof mutate !== 'function') {
6414
6432
  throw new Error('no such device method: ' + cap + '.' + method)
6415
6433
  }
@@ -12441,7 +12459,7 @@ var detectionPipelineCapability = {
12441
12459
  * This matters here more than anywhere else in the repo, because every provider
12442
12460
  * that knows a device's host knows its PASSWORD too: `{ host, port, username,
12443
12461
  * password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
12444
- * and `http://admin:hunter2@192.168.50.139/` is a URL a browser accepts. The
12462
+ * and `http://admin:hunter2@192.168.x.x/` is a URL a browser accepts. The
12445
12463
  * camera's own page will ask for its own login. That is correct, and pre-
12446
12464
  * filling it is the operator's business, not ours.
12447
12465
  *
@@ -21504,12 +21522,33 @@ var NC_ALARM_NONBLOCKING_MAX = 200;
21504
21522
  var NcAlarmDelaySecSchema = zod.z.number().int().min(0).max(600);
21505
21523
  var NcAlarmTriggeredSecSchema = zod.z.number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
21506
21524
  var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
21525
+ /**
21526
+ * What the panel does when a bounded triggered window ends.
21527
+ * - `rearmAfter` — return to the armed mode
21528
+ * - `disableAfter` — auto-disarm
21529
+ * - `doNothing` — stay triggered until a human disarms
21530
+ */
21531
+ var NcAlarmPostTriggeredActionSchema = zod.z.enum([
21532
+ "rearmAfter",
21533
+ "disableAfter",
21534
+ "doNothing"
21535
+ ]);
21507
21536
  /** One mode's OVERRIDES. An absent field inherits the global value (D697). */
21508
21537
  var NcAlarmModeDelaysSchema = zod.z.object({
21509
21538
  mode: AlarmArmModeSchema,
21510
21539
  exitDelaySec: NcAlarmDelaySecSchema.optional(),
21511
21540
  entryDelaySec: NcAlarmDelaySecSchema.optional(),
21512
- triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
21541
+ /**
21542
+ * Duration of `triggered` before {@link postTriggeredAction}. Kept as the
21543
+ * historical field name so older clients still read/write a number.
21544
+ */
21545
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.optional(),
21546
+ postTriggeredAction: NcAlarmPostTriggeredActionSchema.optional(),
21547
+ /**
21548
+ * When this mode may be armed. Absent / null = always allowed (same as a
21549
+ * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
21550
+ */
21551
+ schedule: NcScheduleSchema.optional()
21513
21552
  });
21514
21553
  var NcAlarmModeDelaysListSchema = zod.z.array(NcAlarmModeDelaysSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
21515
21554
  /**
@@ -21521,7 +21560,10 @@ var NcAlarmModeDelaysEditSchema = zod.z.object({
21521
21560
  mode: AlarmArmModeSchema,
21522
21561
  exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
21523
21562
  entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
21524
- triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
21563
+ triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
21564
+ postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
21565
+ /** Set a schedule, or `null` to clear (mode always allowed again). */
21566
+ schedule: NcScheduleSchema.nullable().optional()
21525
21567
  });
21526
21568
  var NcAlarmModeDelaysEditListSchema = zod.z.array(NcAlarmModeDelaysEditSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
21527
21569
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
@@ -21594,12 +21636,18 @@ var NcAlarmSettingsSchema = zod.z.object({
21594
21636
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
21595
21637
  entryDelaySec: NcAlarmDelaySecSchema,
21596
21638
  /**
21597
- * How long `triggered` lasts before the panel re-arms itself.
21598
- * **0 = until an operator disarms it** — the behaviour before this field
21599
- * existed, and therefore what an untouched install keeps doing.
21639
+ * How long `triggered` lasts before {@link postTriggeredAction} runs.
21640
+ * Ignored when the action is `doNothing`. Historical name kept so older
21641
+ * clients still round-trip a number.
21600
21642
  */
21601
21643
  triggeredDurationSec: NcAlarmTriggeredSecSchema,
21602
21644
  /**
21645
+ * What happens when the triggered window ends. Absent on a blob older than
21646
+ * this field → derived from {@link triggeredDurationSec} (`>0` ⇒ rearmAfter,
21647
+ * else doNothing).
21648
+ */
21649
+ postTriggeredAction: NcAlarmPostTriggeredActionSchema.optional(),
21650
+ /**
21603
21651
  * Deprecated by D630 — every transition is announced. Parsed, never read;
21604
21652
  * remove after one release. Optional so an older admin's patch (which still
21605
21653
  * sends it) parses rather than failing the whole save.
@@ -37050,7 +37098,7 @@ var LocalInterfaceSchema = zod.z.object({
37050
37098
  name: zod.z.string(),
37051
37099
  family: zod.z.enum(["IPv4", "IPv6"]),
37052
37100
  address: zod.z.string(),
37053
- /** CIDR shorthand (e.g. `192.168.1.10/24`). Empty when OS doesn't expose. */
37101
+ /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
37054
37102
  cidr: zod.z.string(),
37055
37103
  /** OS netmask. Stable across calls so the SDK can hash on it. */
37056
37104
  netmask: zod.z.string(),
@@ -37249,7 +37297,7 @@ var localNetworkCapability = {
37249
37297
  * every client a second authority on a socket only the hub binds. A viewer
37250
37298
  * configured with a port-less public URL (`https://camstack.example.top`)
37251
37299
  * infers 443 from the scheme, asks with 443, and was handed
37252
- * `https://192.168.1.9:443` for a hub listening on 4443. That candidate
37300
+ * `https://192.168.x.x:443` for a hub listening on 4443. That candidate
37253
37301
  * cannot ever answer, and losing its race reads in the log exactly like
37254
37302
  * "the LAN was tried and it was slower" — so an operator sitting on his own
37255
37303
  * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
@@ -37708,7 +37756,7 @@ var MeshPeerSchema = zod.z.object({
37708
37756
  tags: zod.z.array(zod.z.string()).readonly(),
37709
37757
  /** True when the peer advertises itself as an exit-node candidate. */
37710
37758
  exitNodeOption: zod.z.boolean(),
37711
- /** Subnet routes the peer advertises (e.g. `["192.168.1.0/24"]`). */
37759
+ /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
37712
37760
  advertisedRoutes: zod.z.array(zod.z.string()).readonly(),
37713
37761
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
37714
37762
  userLogin: zod.z.string().nullable(),
@@ -57700,6 +57748,62 @@ var DEVICE_DO_ACTIONS = [
57700
57748
  method: "stop",
57701
57749
  args: {}
57702
57750
  },
57751
+ {
57752
+ id: "valve.open",
57753
+ label: "Open valve",
57754
+ cap: "valve",
57755
+ method: "open",
57756
+ args: {}
57757
+ },
57758
+ {
57759
+ id: "valve.close",
57760
+ label: "Close valve",
57761
+ cap: "valve",
57762
+ method: "close",
57763
+ args: {}
57764
+ },
57765
+ {
57766
+ id: "valve.stop",
57767
+ label: "Stop valve",
57768
+ cap: "valve",
57769
+ method: "stop",
57770
+ args: {}
57771
+ },
57772
+ {
57773
+ id: "humidifier.on",
57774
+ label: "Humidifier on",
57775
+ cap: "humidifier",
57776
+ method: "setOn",
57777
+ args: { on: true }
57778
+ },
57779
+ {
57780
+ id: "humidifier.off",
57781
+ label: "Humidifier off",
57782
+ cap: "humidifier",
57783
+ method: "setOn",
57784
+ args: { on: false }
57785
+ },
57786
+ {
57787
+ id: "waterHeater.off",
57788
+ label: "Water heater off",
57789
+ cap: "water-heater",
57790
+ method: "setOperationMode",
57791
+ args: { mode: "off" }
57792
+ },
57793
+ {
57794
+ id: "waterHeater.awayOn",
57795
+ label: "Water heater away on",
57796
+ cap: "water-heater",
57797
+ method: "setAway",
57798
+ args: { on: true }
57799
+ },
57800
+ {
57801
+ id: "waterHeater.awayOff",
57802
+ label: "Water heater away off",
57803
+ cap: "water-heater",
57804
+ method: "setAway",
57805
+ args: { on: false }
57806
+ },
57703
57807
  {
57704
57808
  id: "climate.off",
57705
57809
  label: "Climate off",
@@ -57770,6 +57874,115 @@ var DEVICE_DO_ACTIONS = [
57770
57874
  method: "returnToBase",
57771
57875
  args: {}
57772
57876
  },
57877
+ {
57878
+ id: "lawnMower.start",
57879
+ label: "Start mowing",
57880
+ cap: "lawn-mower-control",
57881
+ method: "startMowing",
57882
+ args: {}
57883
+ },
57884
+ {
57885
+ id: "lawnMower.pause",
57886
+ label: "Pause mower",
57887
+ cap: "lawn-mower-control",
57888
+ method: "pause",
57889
+ args: {}
57890
+ },
57891
+ {
57892
+ id: "lawnMower.dock",
57893
+ label: "Dock mower",
57894
+ cap: "lawn-mower-control",
57895
+ method: "dock",
57896
+ args: {}
57897
+ },
57898
+ {
57899
+ id: "media.play",
57900
+ label: "Play",
57901
+ cap: "media-player",
57902
+ method: "play",
57903
+ args: {}
57904
+ },
57905
+ {
57906
+ id: "media.pause",
57907
+ label: "Pause media",
57908
+ cap: "media-player",
57909
+ method: "pause",
57910
+ args: {}
57911
+ },
57912
+ {
57913
+ id: "media.stop",
57914
+ label: "Stop media",
57915
+ cap: "media-player",
57916
+ method: "stop",
57917
+ args: {}
57918
+ },
57919
+ {
57920
+ id: "media.next",
57921
+ label: "Next track",
57922
+ cap: "media-player",
57923
+ method: "next",
57924
+ args: {}
57925
+ },
57926
+ {
57927
+ id: "media.previous",
57928
+ label: "Previous track",
57929
+ cap: "media-player",
57930
+ method: "previous",
57931
+ args: {}
57932
+ },
57933
+ {
57934
+ id: "script.run",
57935
+ label: "Run script",
57936
+ cap: "script-runner",
57937
+ method: "run",
57938
+ args: {}
57939
+ },
57940
+ {
57941
+ id: "script.stop",
57942
+ label: "Stop script",
57943
+ cap: "script-runner",
57944
+ method: "stop",
57945
+ args: {}
57946
+ },
57947
+ {
57948
+ id: "haAutomation.enable",
57949
+ label: "Enable automation",
57950
+ cap: "automation-control",
57951
+ method: "enable",
57952
+ args: {}
57953
+ },
57954
+ {
57955
+ id: "haAutomation.disable",
57956
+ label: "Disable automation",
57957
+ cap: "automation-control",
57958
+ method: "disable",
57959
+ args: {}
57960
+ },
57961
+ {
57962
+ id: "haAutomation.trigger",
57963
+ label: "Trigger automation",
57964
+ cap: "automation-control",
57965
+ method: "trigger",
57966
+ args: {}
57967
+ },
57968
+ {
57969
+ id: "petFeeder.feed",
57970
+ label: "Feed",
57971
+ cap: "pet-feeder",
57972
+ method: "feed",
57973
+ openArgs: [{
57974
+ key: "grams",
57975
+ label: "Grams",
57976
+ kind: "number"
57977
+ }]
57978
+ },
57979
+ {
57980
+ id: "petFeeder.callPet",
57981
+ label: "Call pet",
57982
+ cap: "pet-feeder",
57983
+ method: "callPet",
57984
+ args: {}
57985
+ },
57773
57986
  {
57774
57987
  id: "ptz.goHome",
57775
57988
  label: "PTZ go home",
@@ -57997,6 +58210,94 @@ function bakedArgsEqual(action, args) {
57997
58210
  return argsEqual(action.args, args);
57998
58211
  }
57999
58212
  /**
58213
+ * Catalogue caps a `DeviceType` is expected to offer in the Do picker.
58214
+ *
58215
+ * Exhaustive over {@link DeviceType}: adding a new enum member without an
58216
+ * entry fails the TypeScript build here (same pattern as
58217
+ * {@link DEVICE_TYPE_CONTROL_KIND}). An empty list is a deliberate choice
58218
+ * for read-only / non-actuatable types — never "forgotten".
58219
+ *
58220
+ * Camera accessories (ptz, privacy-mask, …) are listed on Camera even though
58221
+ * a given camera may bind only a subset; {@link actionsForBoundCaps} still
58222
+ * filters to what the device actually binds.
58223
+ */
58224
+ var DEVICE_TYPE_DO_CAPS = {
58225
+ [require_err_msg.DeviceType.Camera]: [
58226
+ "ptz",
58227
+ "ptz-autotrack",
58228
+ "privacy-mask",
58229
+ "day-night",
58230
+ "reboot",
58231
+ "motion-trigger"
58232
+ ],
58233
+ [require_err_msg.DeviceType.Hub]: [],
58234
+ [require_err_msg.DeviceType.Light]: ["switch", "brightness"],
58235
+ [require_err_msg.DeviceType.Siren]: ["switch", "motion-trigger"],
58236
+ [require_err_msg.DeviceType.Switch]: ["switch", "motion-trigger"],
58237
+ [require_err_msg.DeviceType.Sensor]: [],
58238
+ [require_err_msg.DeviceType.Thermostat]: ["climate-control"],
58239
+ [require_err_msg.DeviceType.Climate]: ["climate-control"],
58240
+ [require_err_msg.DeviceType.Button]: ["button"],
58241
+ [require_err_msg.DeviceType.EventEmitter]: [],
58242
+ [require_err_msg.DeviceType.Update]: [],
58243
+ [require_err_msg.DeviceType.Generic]: [],
58244
+ [require_err_msg.DeviceType.Notifier]: [],
58245
+ [require_err_msg.DeviceType.Script]: ["script-runner"],
58246
+ [require_err_msg.DeviceType.Automation]: ["automation-control"],
58247
+ [require_err_msg.DeviceType.Lock]: ["lock-control"],
58248
+ [require_err_msg.DeviceType.Cover]: ["cover"],
58249
+ [require_err_msg.DeviceType.Valve]: ["valve"],
58250
+ [require_err_msg.DeviceType.Humidifier]: ["humidifier"],
58251
+ [require_err_msg.DeviceType.WaterHeater]: ["water-heater"],
58252
+ [require_err_msg.DeviceType.Fan]: ["fan-control", "switch"],
58253
+ [require_err_msg.DeviceType.MediaPlayer]: ["media-player"],
58254
+ [require_err_msg.DeviceType.AlarmPanel]: ["alarm-panel"],
58255
+ [require_err_msg.DeviceType.Control]: [],
58256
+ [require_err_msg.DeviceType.Presence]: [],
58257
+ [require_err_msg.DeviceType.Weather]: [],
58258
+ [require_err_msg.DeviceType.Vacuum]: ["vacuum-control"],
58259
+ [require_err_msg.DeviceType.LawnMower]: ["lawn-mower-control"],
58260
+ [require_err_msg.DeviceType.Container]: [],
58261
+ [require_err_msg.DeviceType.Image]: [],
58262
+ [require_err_msg.DeviceType.PetFeeder]: ["pet-feeder"]
58263
+ };
58264
+ /**
58265
+ * Whether a device type belongs in a Do / automation action device picker.
58266
+ *
58267
+ * Containers stay in {@link DEVICE_TYPE_DO_CAPS} (empty, for exhaustiveness)
58268
+ * but must never appear as selectable targets — actuate the children instead.
58269
+ */
58270
+ function isDoPickerDeviceType(type) {
58271
+ return type !== require_err_msg.DeviceType.Container;
58272
+ }
58273
+ /**
58274
+ * Do-picker family id — devices that share one may be multi-selected for
58275
+ * the same action (several locks, the climate family, cover+valve, …).
58276
+ *
58277
+ * Distinct from {@link DeviceControlKind}: humidifier / water-heater keep
58278
+ * their own control widgets but author with climate siblings.
58279
+ */
58280
+ function deviceDoFamily(type) {
58281
+ switch (type) {
58282
+ case require_err_msg.DeviceType.Climate:
58283
+ case require_err_msg.DeviceType.Thermostat:
58284
+ case require_err_msg.DeviceType.Humidifier:
58285
+ case require_err_msg.DeviceType.WaterHeater: return "climate-family";
58286
+ case require_err_msg.DeviceType.Cover:
58287
+ case require_err_msg.DeviceType.Valve: return "cover-family";
58288
+ case require_err_msg.DeviceType.Switch:
58289
+ case require_err_msg.DeviceType.Siren:
58290
+ case require_err_msg.DeviceType.Light: return "switch-family";
58291
+ case require_err_msg.DeviceType.Vacuum:
58292
+ case require_err_msg.DeviceType.LawnMower: return "robot-family";
58293
+ default: return type;
58294
+ }
58295
+ }
58296
+ /** True when two device types may share one multi-target Do step. */
58297
+ function sameDoFamily(leftType, rightType) {
58298
+ return deviceDoFamily(leftType) === deviceDoFamily(rightType);
58299
+ }
58300
+ /**
58000
58301
  * Actions offered for a device that binds the given caps.
58001
58302
  *
58002
58303
  * Only catalogue entries whose cap is bound AND still actuatable in
@@ -58007,6 +58308,50 @@ function actionsForBoundCaps(boundCaps) {
58007
58308
  const set = new Set(boundCaps);
58008
58309
  return DEVICE_DO_ACTIONS.filter((action) => set.has(action.cap) && isDoActionActuatable(action));
58009
58310
  }
58311
+ /**
58312
+ * Catalogue actions for a wire `DeviceType` string — the fallback when
58313
+ * bindings have not answered yet (or answered empty for a typed actuator).
58314
+ * Unknown type strings → empty.
58315
+ */
58316
+ function actionsForDeviceType(type) {
58317
+ if (!Object.values(require_err_msg.DeviceType).includes(type)) return [];
58318
+ return actionsForBoundCaps(DEVICE_TYPE_DO_CAPS[type]);
58319
+ }
58320
+ /**
58321
+ * Union of bound-cap actions with the type's expected catalogue caps.
58322
+ *
58323
+ * Bindings win for filtering (a lock without `lock-control` bound still
58324
+ * offers nothing from bindings alone); the type map fills gaps when the
58325
+ * binding read is empty so a Lock / Valve / Climate never shows
58326
+ * "No actions for this device" solely because getBindings lagged.
58327
+ */
58328
+ function actionsForDevice(input) {
58329
+ const fromBound = input.boundCaps !== void 0 && input.boundCaps.length > 0 ? actionsForBoundCaps(input.boundCaps) : [];
58330
+ if (fromBound.length > 0) return fromBound;
58331
+ if (input.deviceType !== void 0 && input.deviceType.length > 0) return actionsForDeviceType(input.deviceType);
58332
+ return [];
58333
+ }
58334
+ /**
58335
+ * Intersection of {@link actionsForDevice} across several targets — what a
58336
+ * multi-select same-family Do step may offer.
58337
+ */
58338
+ function actionsForDevices(targets) {
58339
+ if (targets.length === 0) return [];
58340
+ let common = null;
58341
+ for (const target of targets) {
58342
+ const offered = actionsForDevice({
58343
+ boundCaps: target.boundCaps,
58344
+ deviceType: target.deviceType
58345
+ });
58346
+ if (common === null) {
58347
+ common = offered;
58348
+ continue;
58349
+ }
58350
+ const ids = new Set(offered.map((a) => a.id));
58351
+ common = common.filter((a) => ids.has(a.id));
58352
+ }
58353
+ return common ?? [];
58354
+ }
58010
58355
  /** The whole catalogue (still actuatable) — for a `{$var}` automation target. */
58011
58356
  function allDoActions() {
58012
58357
  return DEVICE_DO_ACTIONS.filter(isDoActionActuatable);
@@ -67333,6 +67678,10 @@ function bindingPullsAlarmTrigger(binding, sequenceActionsOf) {
67333
67678
  if (steps === void 0) return false;
67334
67679
  return steps.some((step) => step.kind === "cap" && step.cap === ALARM_PANEL_CAP && step.method === ALARM_PANEL_TRIGGER_METHOD);
67335
67680
  }
67681
+ /** True when the rule's deviceState gate names any `armed_*` word. */
67682
+ function ruleGatedOnArmedState(rule) {
67683
+ return (rule.conditions.deviceState?.states ?? []).some((s) => s.trim().toLowerCase().startsWith(ARMED_WORD_PREFIX));
67684
+ }
67336
67685
  /**
67337
67686
  * A rule belongs to the alarm when its `deviceState` gate names ANY device's
67338
67687
  * `armed_*` word, or an on-match binding pulls the alarm panel's trigger
@@ -67340,10 +67689,19 @@ function bindingPullsAlarmTrigger(binding, sequenceActionsOf) {
67340
67689
  * escalation.
67341
67690
  */
67342
67691
  function ruleBelongsToAlarm(rule) {
67343
- if ((rule.conditions.deviceState?.states ?? []).some((s) => s.trim().toLowerCase().startsWith(ARMED_WORD_PREFIX))) return true;
67692
+ if (ruleGatedOnArmedState(rule)) return true;
67344
67693
  for (const binding of rule.actions?.onTrigger ?? []) if (bindingPullsAlarmTrigger(binding, rule.sequenceActionsOf)) return true;
67345
67694
  return false;
67346
67695
  }
67696
+ /**
67697
+ * A rule gated on `armed_*` auto-triggers the CamStack `alarm-panel` on match
67698
+ * — no `onTrigger` sequence required. Sirens are never part of this path.
67699
+ * An explicit `alarm-panel.trigger` binding without an armed gate stays
67700
+ * explicit actuation only ({@link ruleBelongsToAlarm}).
67701
+ */
67702
+ function ruleAutoTriggersAlarm(rule) {
67703
+ return ruleGatedOnArmedState(rule);
67704
+ }
67347
67705
  function isOpeningKind(kind) {
67348
67706
  const k = kind.trim().toLowerCase();
67349
67707
  return NC_ALARM_OPENING_EVENT_KINDS.some((opening) => opening === k);
@@ -67409,6 +67767,14 @@ var NC_ALARM_DELAY_FIELDS = [
67409
67767
  "entryDelaySec",
67410
67768
  "triggeredDurationSec"
67411
67769
  ];
67770
+ /**
67771
+ * Derive the post-triggered action from a stored blob that may predate the field:
67772
+ * a positive duration meant re-arm; zero/absent meant stay triggered.
67773
+ */
67774
+ function derivePostTriggeredAction(action, triggeredDurationSec) {
67775
+ if (action !== void 0) return action;
67776
+ return triggeredDurationSec > 0 ? "rearmAfter" : "doNothing";
67777
+ }
67412
67778
  /** The mode's value when it overrides the field, else the global one. */
67413
67779
  function pick(override, global) {
67414
67780
  return override === void 0 ? {
@@ -67425,14 +67791,19 @@ function resolveModeDelays(settings, mode) {
67425
67791
  const exit = pick(entry?.exitDelaySec, settings.exitDelaySec);
67426
67792
  const entryDelay = pick(entry?.entryDelaySec, settings.entryDelaySec);
67427
67793
  const triggered = pick(entry?.triggeredDurationSec, settings.triggeredDurationSec);
67794
+ const actionOverride = entry?.postTriggeredAction;
67795
+ const actionSource = actionOverride !== void 0 ? "mode" : "default";
67796
+ const postTriggeredAction = derivePostTriggeredAction(actionOverride ?? settings.postTriggeredAction, triggered.value);
67428
67797
  return {
67429
67798
  exitDelaySec: exit.value,
67430
67799
  entryDelaySec: entryDelay.value,
67431
67800
  triggeredDurationSec: triggered.value,
67801
+ postTriggeredAction,
67432
67802
  source: {
67433
67803
  exitDelaySec: exit.source,
67434
67804
  entryDelaySec: entryDelay.source,
67435
- triggeredDurationSec: triggered.source
67805
+ triggeredDurationSec: triggered.source,
67806
+ postTriggeredAction: actionSource
67436
67807
  }
67437
67808
  };
67438
67809
  }
@@ -67441,6 +67812,29 @@ function edited(current, edit) {
67441
67812
  if (edit === void 0) return current;
67442
67813
  return edit === null ? void 0 : edit;
67443
67814
  }
67815
+ function editedAction(current, edit) {
67816
+ if (edit === void 0) return current;
67817
+ return edit === null ? void 0 : edit;
67818
+ }
67819
+ function editedSchedule(current, edit) {
67820
+ if (edit === void 0) return current;
67821
+ return edit === null ? void 0 : edit;
67822
+ }
67823
+ function sameSchedule(a, b) {
67824
+ if (a === void 0 && b === void 0) return true;
67825
+ if (a === void 0 || b === void 0) return false;
67826
+ if (a.invert !== b.invert || a.timezone !== b.timezone) return false;
67827
+ if (a.windows.length !== b.windows.length) return false;
67828
+ for (let i = 0; i < a.windows.length; i++) {
67829
+ const wa = a.windows[i];
67830
+ const wb = b.windows[i];
67831
+ if (wa === void 0 || wb === void 0) return false;
67832
+ if (wa.startMinute !== wb.startMinute || wa.endMinute !== wb.endMinute) return false;
67833
+ if (wa.days.length !== wb.days.length) return false;
67834
+ for (let d = 0; d < wa.days.length; d++) if (wa.days[d] !== wb.days[d]) return false;
67835
+ }
67836
+ return true;
67837
+ }
67444
67838
  /**
67445
67839
  * Apply per-field edits to the stored overrides. Returns new arrays, never mutates the input;
67446
67840
  * an entry with no field left is dropped (the mode inherits everything again).
@@ -67454,13 +67848,17 @@ function applyModeDelaysEdits(current, edits) {
67454
67848
  const exit = edited(base.exitDelaySec, edit.exitDelaySec);
67455
67849
  const entry = edited(base.entryDelaySec, edit.entryDelaySec);
67456
67850
  const triggered = edited(base.triggeredDurationSec, edit.triggeredDurationSec);
67851
+ const action = editedAction(base.postTriggeredAction, edit.postTriggeredAction);
67852
+ const schedule = editedSchedule(base.schedule, edit.schedule);
67457
67853
  if (exit !== void 0) next.exitDelaySec = exit;
67458
67854
  if (entry !== void 0) next.entryDelaySec = entry;
67459
67855
  if (triggered !== void 0) next.triggeredDurationSec = triggered;
67856
+ if (action !== void 0) next.postTriggeredAction = action;
67857
+ if (schedule !== void 0) next.schedule = schedule;
67460
67858
  if (at >= 0) out[at] = next;
67461
67859
  else out.push(next);
67462
67860
  }
67463
- return out.filter((e) => e.exitDelaySec !== void 0 || e.entryDelaySec !== void 0 || e.triggeredDurationSec !== void 0);
67861
+ return out.filter((e) => e.exitDelaySec !== void 0 || e.entryDelaySec !== void 0 || e.triggeredDurationSec !== void 0 || e.postTriggeredAction !== void 0 || e.schedule !== void 0);
67464
67862
  }
67465
67863
  /**
67466
67864
  * Put ONE device on, or take it off, ONE mode's device-id list. A no-op answers
@@ -67588,6 +67986,7 @@ function alarmPatchLanded(patch, returned) {
67588
67986
  if (patch.exitDelaySec !== void 0 && patch.exitDelaySec !== returned.exitDelaySec) return false;
67589
67987
  if (patch.entryDelaySec !== void 0 && patch.entryDelaySec !== returned.entryDelaySec) return false;
67590
67988
  if (patch.triggeredDurationSec !== void 0 && patch.triggeredDurationSec !== returned.triggeredDurationSec) return false;
67989
+ if (patch.postTriggeredAction !== void 0 && patch.postTriggeredAction !== derivePostTriggeredAction(returned.postTriggeredAction, returned.triggeredDurationSec)) return false;
67591
67990
  if (patch.announceTargets !== void 0 && !sameSet(patch.announceTargets, returned.announceTargets)) return false;
67592
67991
  if (patch.nonBlocking !== void 0 && !sameLists(patch.nonBlocking, returned.nonBlocking ?? [])) return false;
67593
67992
  if (patch.blocking !== void 0 && !sameLists(patch.blocking, returned.blocking ?? [])) return false;
@@ -67600,6 +67999,14 @@ function alarmPatchLanded(patch, returned) {
67600
67999
  const got = fieldOf(entry, field);
67601
68000
  if (want === null ? got !== void 0 : got !== want) return false;
67602
68001
  }
68002
+ if (edit.postTriggeredAction !== void 0) {
68003
+ const got = entry?.postTriggeredAction;
68004
+ if (edit.postTriggeredAction === null ? got !== void 0 : got !== edit.postTriggeredAction) return false;
68005
+ }
68006
+ if (edit.schedule !== void 0) {
68007
+ const got = entry?.schedule;
68008
+ if (edit.schedule === null ? got !== void 0 : !sameSchedule(got, edit.schedule)) return false;
68009
+ }
67603
68010
  }
67604
68011
  if (patch.nonBlockingEdit !== void 0) {
67605
68012
  const e = patch.nonBlockingEdit;
@@ -67620,11 +68027,12 @@ function alarmPatchLanded(patch, returned) {
67620
68027
  /** Stable id of the always-present Default profile (migrated from flat settings). */
67621
68028
  var NC_ALARM_DEFAULT_PROFILE_ID = "default";
67622
68029
  var NC_ALARM_DEFAULT_PROFILE_NAME = "Default";
67623
- /** Stock delays — same numbers the panel shipped with before any editor existed. */
68030
+ /** Stock delays — re-arm 10 s after trigger (inherited by every mode). */
67624
68031
  var NC_ALARM_SYSTEM_DEFAULT_DELAYS = {
67625
68032
  exitDelaySec: 30,
67626
68033
  entryDelaySec: 20,
67627
- triggeredDurationSec: 0
68034
+ triggeredDurationSec: 10,
68035
+ postTriggeredAction: "rearmAfter"
67628
68036
  };
67629
68037
  /** Empty memberships + stock delays + no announce targets. */
67630
68038
  function ncAlarmSystemDefaultSettings() {
@@ -67646,6 +68054,7 @@ function cloneAlarmSettings(settings) {
67646
68054
  exitDelaySec: settings.exitDelaySec,
67647
68055
  entryDelaySec: settings.entryDelaySec,
67648
68056
  triggeredDurationSec: settings.triggeredDurationSec,
68057
+ ...settings.postTriggeredAction !== void 0 ? { postTriggeredAction: settings.postTriggeredAction } : {},
67649
68058
  ...settings.announceArm !== void 0 ? { announceArm: settings.announceArm } : {},
67650
68059
  announceTargets: [...settings.announceTargets],
67651
68060
  nonBlocking: settings.nonBlocking.map((l) => ({
@@ -67660,7 +68069,22 @@ function cloneAlarmSettings(settings) {
67660
68069
  mode: l.mode,
67661
68070
  deviceIds: [...l.deviceIds]
67662
68071
  })),
67663
- modeDelays: settings.modeDelays.map((e) => ({ ...e }))
68072
+ modeDelays: settings.modeDelays.map((e) => ({
68073
+ mode: e.mode,
68074
+ ...e.exitDelaySec !== void 0 ? { exitDelaySec: e.exitDelaySec } : {},
68075
+ ...e.entryDelaySec !== void 0 ? { entryDelaySec: e.entryDelaySec } : {},
68076
+ ...e.triggeredDurationSec !== void 0 ? { triggeredDurationSec: e.triggeredDurationSec } : {},
68077
+ ...e.postTriggeredAction !== void 0 ? { postTriggeredAction: e.postTriggeredAction } : {},
68078
+ ...e.schedule !== void 0 ? { schedule: {
68079
+ windows: e.schedule.windows.map((w) => ({
68080
+ days: [...w.days],
68081
+ startMinute: w.startMinute,
68082
+ endMinute: w.endMinute
68083
+ })),
68084
+ ...e.schedule.timezone !== void 0 ? { timezone: e.schedule.timezone } : {},
68085
+ ...e.schedule.invert === true ? { invert: true } : {}
68086
+ } } : {}
68087
+ }))
67664
68088
  };
67665
68089
  }
67666
68090
  /**
@@ -72485,6 +72909,7 @@ exports.DEVICE_SETTINGS_CONTRIBUTION_METHODS = require_sleep.DEVICE_SETTINGS_CON
72485
72909
  exports.DEVICE_STATE_READERS = DEVICE_STATE_READERS;
72486
72910
  exports.DEVICE_STATUS_METHOD = require_sleep.DEVICE_STATUS_METHOD;
72487
72911
  exports.DEVICE_TYPE_CONTROL_KIND = DEVICE_TYPE_CONTROL_KIND;
72912
+ exports.DEVICE_TYPE_DO_CAPS = DEVICE_TYPE_DO_CAPS;
72488
72913
  exports.DEVICE_TYPE_INFO = DEVICE_TYPE_INFO;
72489
72914
  exports.DISPLAY_ICON_GROUPS = DISPLAY_ICON_GROUPS;
72490
72915
  exports.DISPLAY_ICON_KEYS = DISPLAY_ICON_KEYS;
@@ -72989,6 +73414,7 @@ exports.NcAlarmModeDelaysSchema = NcAlarmModeDelaysSchema;
72989
73414
  exports.NcAlarmModeDeviceListSchema = NcAlarmModeDeviceListSchema;
72990
73415
  exports.NcAlarmNonBlockingEditSchema = NcAlarmNonBlockingEditSchema;
72991
73416
  exports.NcAlarmNonBlockingSchema = NcAlarmNonBlockingSchema;
73417
+ exports.NcAlarmPostTriggeredActionSchema = NcAlarmPostTriggeredActionSchema;
72992
73418
  exports.NcAlarmProfileIdInputSchema = NcAlarmProfileIdInputSchema;
72993
73419
  exports.NcAlarmProfileMetaSchema = NcAlarmProfileMetaSchema;
72994
73420
  exports.NcAlarmProfilesViewSchema = NcAlarmProfilesViewSchema;
@@ -73573,6 +73999,9 @@ exports.__resetLogChannelRegistryForTests = __resetLogChannelRegistryForTests;
73573
73999
  exports.accessoriesCapability = accessoriesCapability;
73574
74000
  exports.accessoryStableId = accessoryStableId;
73575
74001
  exports.actionsForBoundCaps = actionsForBoundCaps;
74002
+ exports.actionsForDevice = actionsForDevice;
74003
+ exports.actionsForDeviceType = actionsForDeviceType;
74004
+ exports.actionsForDevices = actionsForDevices;
73576
74005
  exports.activateAlarmProfile = activateAlarmProfile;
73577
74006
  exports.addonPagesCapability = addonPagesCapability;
73578
74007
  exports.addonPagesSourceCapability = addonPagesSourceCapability;
@@ -73738,6 +74167,7 @@ exports.deleteAlarmProfile = deleteAlarmProfile;
73738
74167
  exports.deriveBatteryPresence = deriveBatteryPresence;
73739
74168
  exports.deriveCameraSwitches = deriveCameraSwitches;
73740
74169
  exports.deriveDetailCropRect = deriveDetailCropRect;
74170
+ exports.derivePostTriggeredAction = derivePostTriggeredAction;
73741
74171
  exports.deriveRecordingMode = deriveRecordingMode;
73742
74172
  exports.describeComposableCaps = describeComposableCaps;
73743
74173
  exports.describeCustomActions = require_sleep.describeCustomActions;
@@ -73755,6 +74185,7 @@ exports.deviceAdoptionCapability = deviceAdoptionCapability;
73755
74185
  exports.deviceBackendToFormat = deviceBackendToFormat;
73756
74186
  exports.deviceCustomAction = require_sleep.deviceCustomAction;
73757
74187
  exports.deviceDiscoveryCapability = deviceDiscoveryCapability;
74188
+ exports.deviceDoFamily = deviceDoFamily;
73758
74189
  exports.deviceExportCapability = deviceExportCapability;
73759
74190
  exports.deviceExtensionCapability = deviceExtensionCapability;
73760
74191
  exports.deviceManagerCapability = deviceManagerCapability;
@@ -73881,6 +74312,7 @@ exports.isDeviceConfigCap = require_sleep.isDeviceConfigCap;
73881
74312
  exports.isDeviceScopedCap = require_sleep.isDeviceScopedCap;
73882
74313
  exports.isDisplayIconKey = isDisplayIconKey;
73883
74314
  exports.isDoActionActuatable = isDoActionActuatable;
74315
+ exports.isDoPickerDeviceType = isDoPickerDeviceType;
73884
74316
  exports.isEmbeddingModelFailed = isEmbeddingModelFailed;
73885
74317
  exports.isEvent = require_sleep.isEvent;
73886
74318
  exports.isEventKindLabelKey = isEventKindLabelKey;
@@ -74098,10 +74530,12 @@ exports.resolveVariantModelId = resolveVariantModelId;
74098
74530
  exports.resolveViewableDeviceIds = resolveViewableDeviceIds;
74099
74531
  exports.roleSpec = roleSpec;
74100
74532
  exports.roomCompositionExample = roomCompositionExample;
74533
+ exports.ruleAutoTriggersAlarm = ruleAutoTriggersAlarm;
74101
74534
  exports.ruleBelongsToAlarm = ruleBelongsToAlarm;
74102
74535
  exports.ruleDeviceCompatCapNames = ruleDeviceCompatCapNames;
74103
74536
  exports.ruleDeviceCompatCapQueries = ruleDeviceCompatCapQueries;
74104
74537
  exports.ruleEditorSectionsForKind = ruleEditorSectionsForKind;
74538
+ exports.ruleGatedOnArmedState = ruleGatedOnArmedState;
74105
74539
  exports.ruleGroupingApplies = ruleGroupingApplies;
74106
74540
  exports.ruleKindOf = ruleKindOf;
74107
74541
  exports.ruleKindSpec = ruleKindSpec;
@@ -74112,6 +74546,7 @@ exports.ruleSeedForSection = ruleSeedForSection;
74112
74546
  exports.runInferenceStep = runInferenceStep;
74113
74547
  exports.runtimeDevices = runtimeDevices;
74114
74548
  exports.runtimeStatePolicyFor = runtimeStatePolicyFor;
74549
+ exports.sameDoFamily = sameDoFamily;
74115
74550
  exports.sceneMonitorCapability = sceneMonitorCapability;
74116
74551
  exports.scheduleForSendTimes = scheduleForSendTimes;
74117
74552
  exports.schemaDeclaresAnyField = schemaDeclaresAnyField;