@camstack/addon-provider-velux 0.2.154 → 0.2.155

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
@@ -33,8 +33,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
33
33
  //#endregion
34
34
  let node_tls = require("node:tls");
35
35
  let timers_promises = require("timers/promises");
36
- let node_net = require("node:net");
37
- //#region ../types/dist/event-category-CaURIwQA.mjs
36
+ //#region ../types/dist/event-category-BmPm98n0.mjs
38
37
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
39
38
  EventCategory["SystemBoot"] = "system.boot";
40
39
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -518,6 +517,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
518
517
  EventCategory["RunningStarted"] = "running.started";
519
518
  /** Mirrors `runningStateCapability.events.onFinished`. */
520
519
  EventCategory["RunningFinished"] = "running.finished";
520
+ /** Mirrors `deviceRunsCapability.events.onRunOpened` - a durable run record opened. */
521
+ EventCategory["DeviceRunOpened"] = "device-run.opened";
522
+ /** Mirrors `deviceRunsCapability.events.onRunClosed` - a durable run record closed. */
523
+ EventCategory["DeviceRunClosed"] = "device-run.closed";
521
524
  /**
522
525
  * Cap event fired by every device that registers the `event-emitter`
523
526
  * capability. Mirrors `eventEmitterCapability.events.onEvent`. Carries
@@ -5866,7 +5869,7 @@ var ZodIssueCode = {
5866
5869
  var ZodFirstPartyTypeKind;
5867
5870
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5868
5871
  //#endregion
5869
- //#region ../types/dist/sleep-CSYuxssh.mjs
5872
+ //#region ../types/dist/sleep-BKzhQTSm.mjs
5870
5873
  /**
5871
5874
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5872
5875
  * window to float samples (D455).
@@ -9379,6 +9382,7 @@ var CAP_NAMES = Object.freeze([
9379
9382
  "device-ops",
9380
9383
  "device-provider",
9381
9384
  "device-removal-hook",
9385
+ "device-runs",
9382
9386
  "device-state",
9383
9387
  "device-status",
9384
9388
  "dishwasher",
@@ -11323,7 +11327,7 @@ object({
11323
11327
  params: record(string(), union([string(), number()])).optional()
11324
11328
  });
11325
11329
  //#endregion
11326
- //#region ../types/dist/composition-BBzVf2xd.mjs
11330
+ //#region ../types/dist/composition-BiKWlUCc.mjs
11327
11331
  /**
11328
11332
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11329
11333
  *
@@ -12256,7 +12260,15 @@ var HelperSpecSchema = discriminatedUnion("kind", [
12256
12260
  thresholdWatts: number().positive(),
12257
12261
  startDwellMs: number().int().nonnegative(),
12258
12262
  stopDwellMs: number().int().nonnegative(),
12259
- member: HelperMemberSchema
12263
+ member: HelperMemberSchema,
12264
+ /**
12265
+ * The plug that powers the appliance (`switch.on`). Optional: when set, the
12266
+ * composition carries a `power-source` feature, so the hub can cut and restore
12267
+ * that plug (power-source link of a NEW-target composition).
12268
+ */
12269
+ powerSwitch: HelperMemberSchema.optional(),
12270
+ /** How long a forced restart keeps the plug off; the cap's default when unset. */
12271
+ offSeconds: number().optional()
12260
12272
  }),
12261
12273
  object({
12262
12274
  kind: literal("statistic"),
@@ -13654,7 +13666,13 @@ var MigrationLedgerEntrySchema = object({
13654
13666
  /** Why it is `blocked` or `held` (`dependency-failed`, `snapshot-refused`, …). */
13655
13667
  reason: string().nullable(),
13656
13668
  nodeId: string().nullable(),
13657
- bootId: string().nullable()
13669
+ bootId: string().nullable(),
13670
+ /**
13671
+ * The catalogued meta of this id (description, body kind, store, `dependsOn`…),
13672
+ * joined by the engine from the catalogues it knows this boot. Absent when no
13673
+ * catalogue lists the id (a newer database).
13674
+ */
13675
+ meta: MigrationMetaSchema.optional()
13658
13676
  });
13659
13677
  /** A catalogued migration that is not applied, with where it stands. */
13660
13678
  var MigrationPendingEntrySchema = object({
@@ -19461,6 +19479,14 @@ var APPLIANCE_CAP_VOCABULARY = {
19461
19479
  }
19462
19480
  } }
19463
19481
  },
19482
+ "device-runs": {
19483
+ scope: "device",
19484
+ noun: t("Runs", "Esecuzioni"),
19485
+ events: {
19486
+ onRunOpened: { notOperatorFacing: "the durable record of a run that the running-state start flip already announces" },
19487
+ onRunClosed: { notOperatorFacing: "the durable record of a run that the running-state finish flip already announces" }
19488
+ }
19489
+ },
19464
19490
  update: {
19465
19491
  scope: "device",
19466
19492
  noun: t("Firmware update", "Aggiornamento firmware"),
@@ -46043,6 +46069,117 @@ function actuator(input) {
46043
46069
  };
46044
46070
  }
46045
46071
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
46072
+ var PowerSourceRefusalReasonSchema = _enum([
46073
+ "no-power-source",
46074
+ "switch-missing",
46075
+ "switch-offline",
46076
+ "switch-unknown",
46077
+ "restart-in-progress",
46078
+ "switch-call-failed"
46079
+ ]);
46080
+ var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
46081
+ accepted: literal(false),
46082
+ reason: PowerSourceRefusalReasonSchema,
46083
+ /** One line for the operator, naming the switch. */
46084
+ message: string()
46085
+ })]);
46086
+ var PowerSourceInfoSchema = object({
46087
+ /** False when the device has no power source linked; the rest is then empty. */
46088
+ linked: boolean(),
46089
+ switchDeviceId: number().nullable(),
46090
+ switchName: string().nullable(),
46091
+ /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
46092
+ switchOn: boolean().nullable(),
46093
+ switchOnline: boolean().nullable(),
46094
+ restartInProgress: boolean(),
46095
+ offSeconds: number().int()
46096
+ });
46097
+ /** The composed slice: the switch state the link mirrors, plus the stored off time. */
46098
+ var PowerSourceStateSchema = object({
46099
+ /** Null while the switch has not reported: unknown is never `false` (D393). */
46100
+ switchOn: boolean().nullable(),
46101
+ offSeconds: number().int(),
46102
+ lastFetchedAt: number()
46103
+ });
46104
+ var POWER_SOURCE_DEVICE_TYPES = [
46105
+ DeviceType.Gateway,
46106
+ DeviceType.Hub,
46107
+ DeviceType.Camera,
46108
+ DeviceType.Sensor,
46109
+ DeviceType.MediaPlayer,
46110
+ DeviceType.Vacuum,
46111
+ DeviceType.Generic,
46112
+ DeviceType.Washer,
46113
+ DeviceType.Dryer,
46114
+ DeviceType.Dishwasher,
46115
+ DeviceType.PetFountain,
46116
+ DeviceType.Fan,
46117
+ DeviceType.Humidifier,
46118
+ DeviceType.Climate,
46119
+ DeviceType.WaterHeater
46120
+ ];
46121
+ var powerSourceCustomize = {
46122
+ version: 1,
46123
+ targets: [{
46124
+ id: "switch",
46125
+ cardinality: "scalar",
46126
+ vocabulary: {
46127
+ cap: "power-source",
46128
+ stateField: "switchOn"
46129
+ },
46130
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
46131
+ apply: "add",
46132
+ path: "switchOn",
46133
+ sources: [{
46134
+ kind: "switch",
46135
+ transforms: [{ kind: "identity" }]
46136
+ }],
46137
+ companions: [{
46138
+ kind: "constant",
46139
+ path: "offSeconds",
46140
+ value: 10,
46141
+ when: "add"
46142
+ }, {
46143
+ kind: "clock",
46144
+ path: "lastFetchedAt",
46145
+ when: "always"
46146
+ }],
46147
+ sourceReport: { status: "assignment-status" }
46148
+ }]
46149
+ };
46150
+ var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
46151
+ var powerSourceCapability = {
46152
+ name: "power-source",
46153
+ scope: "device",
46154
+ mode: "singleton",
46155
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
46156
+ methods: {
46157
+ getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }),
46158
+ powerOn: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
46159
+ kind: "mutation",
46160
+ auth: "admin"
46161
+ }),
46162
+ powerOff: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
46163
+ kind: "mutation",
46164
+ auth: "admin"
46165
+ }),
46166
+ forceRestart: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
46167
+ kind: "mutation",
46168
+ auth: "admin"
46169
+ })
46170
+ },
46171
+ runtimeState: PowerSourceStateSchema,
46172
+ customize: powerSourceCustomize,
46173
+ /**
46174
+ * Runtime-state durability: **session** — mirrored from the linked switch on
46175
+ * its next report; a restored value would outlive the reading it names.
46176
+ *
46177
+ * See `RuntimeStateDurability`. Enforced by
46178
+ * `scripts/check-runtime-state-durability.ts`.
46179
+ */
46180
+ durability: "session",
46181
+ history: "none"
46182
+ };
46046
46183
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
46047
46184
  DeviceType.Camera;
46048
46185
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
@@ -47468,6 +47605,101 @@ method(object({ deviceId: number().int() }), array(SeriesInfoSchema).readonly())
47468
47605
  kind: "mutation",
47469
47606
  auth: "admin"
47470
47607
  });
47608
+ /**
47609
+ * device-runs - the durable record of ONE run of a device: a washing cycle, a
47610
+ * dishwasher cycle, later a mow or a clean (D992).
47611
+ *
47612
+ * A run is a DOCUMENT (opened at one instant, closed at another, carrying what
47613
+ * was measured over it), never a sample - device-history keeps the numbers over
47614
+ * time and its `device-runs` provider keeps these documents beside them, because
47615
+ * the energy of a run is a question about device-history's own series. The
47616
+ * record is written from the `running-state` flip on `DeviceStateChanged`; it
47617
+ * survives a restart (the open row is on disk) and a missed flip (boot compares
47618
+ * the open rows against the live snapshots).
47619
+ *
47620
+ * Every measurement is `number | null` and `null` means "could not be
47621
+ * measured", never `0` (D393). A run closed by the boot reconcile because the
47622
+ * device stopped while nobody was watching has `outcome: 'unknown'`.
47623
+ */
47624
+ var DeviceRunKindSchema = _enum([
47625
+ "appliance",
47626
+ "mower",
47627
+ "vacuum"
47628
+ ]);
47629
+ var DeviceRunStateSchema = _enum(["open", "closed"]);
47630
+ var DeviceRunOutcomeSchema = _enum([
47631
+ "completed",
47632
+ "aborted",
47633
+ "error",
47634
+ "unknown"
47635
+ ]);
47636
+ /** What opened or closed the run. `reconcile` is the boot comparison against live state. */
47637
+ var DeviceRunSourceSchema = _enum([
47638
+ "running-state",
47639
+ "activity",
47640
+ "reconcile"
47641
+ ]);
47642
+ /** How the energy of a run was obtained; `null` when it could not be. */
47643
+ var DeviceRunEnergyMethodSchema = _enum(["counter-delta", "history-integral"]);
47644
+ var DeviceRunPhaseSchema = object({
47645
+ phase: string(),
47646
+ at: number()
47647
+ });
47648
+ /** The kind-specific block; a later phase adds a branch, nothing else changes. */
47649
+ var DeviceRunDetailSchema = discriminatedUnion("kind", [object({
47650
+ kind: literal("appliance"),
47651
+ /** kWh used over the run. */
47652
+ energyKwh: number().nullable().meta(readingMeta("energy")),
47653
+ energyMethod: DeviceRunEnergyMethodSchema.nullable(),
47654
+ /** Highest power seen while the run was open. */
47655
+ peakW: number().nullable().meta(readingMeta("power")),
47656
+ /** Time-weighted mean power while the run was open. */
47657
+ avgW: number().nullable().meta(readingMeta("power")),
47658
+ /** The program the appliance said it ran when the run opened. */
47659
+ program: string().nullable(),
47660
+ remainingAtStartSec: number().nullable().meta(readingMeta("duration")),
47661
+ /** The last phase the device reported before the run closed. */
47662
+ finalPhase: string().nullable(),
47663
+ /**
47664
+ * The meter's energy counter when the run opened; `null` when the meter had
47665
+ * not said (or the run was opened late, by the boot reconcile). It lives in
47666
+ * the row so a run that outlives a restart still ends with `end - start`.
47667
+ */
47668
+ energyStartKwh: number().nullable().meta(readingMeta("energy")),
47669
+ /** Milliseconds of power readings behind `avgW` - the weight a restart resumes the mean with. */
47670
+ powerObservedMs: number()
47671
+ })]);
47672
+ var DeviceRunSchema = object({
47673
+ /** `${deviceId}:${startedAt}`. */
47674
+ runId: string(),
47675
+ deviceId: number().int(),
47676
+ kind: DeviceRunKindSchema,
47677
+ state: DeviceRunStateSchema,
47678
+ startedAt: number(),
47679
+ endedAt: number().nullable(),
47680
+ durationMs: number().nullable(),
47681
+ outcome: DeviceRunOutcomeSchema.nullable(),
47682
+ startSource: DeviceRunSourceSchema,
47683
+ endSource: DeviceRunSourceSchema.nullable(),
47684
+ /** Where the device's running bit came from (`running-state.source`); `null` when it did not say. */
47685
+ runningSource: _enum(["native", "assigned"]).nullable(),
47686
+ phases: array(DeviceRunPhaseSchema),
47687
+ detail: DeviceRunDetailSchema
47688
+ });
47689
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, method(object({
47690
+ deviceId: number().int(),
47691
+ outcome: DeviceRunOutcomeSchema.optional(),
47692
+ atFrom: number().optional(),
47693
+ atTo: number().optional(),
47694
+ limit: number().int().positive().max(200).default(50),
47695
+ cursor: string().optional()
47696
+ }), object({
47697
+ runs: array(DeviceRunSchema).readonly(),
47698
+ nextCursor: string().nullable()
47699
+ })), method(object({
47700
+ deviceId: number().int(),
47701
+ runId: string()
47702
+ }), DeviceRunSchema.nullable()), method(object({ deviceId: number().int() }), DeviceRunSchema.nullable());
47471
47703
  var DeviceCaStatusSchema = discriminatedUnion("state", [
47472
47704
  object({ state: literal("absent") }),
47473
47705
  object({
@@ -47603,8 +47835,8 @@ method(object({}), object({
47603
47835
  });
47604
47836
  var KeychainKeySourceSchema = object({
47605
47837
  kind: _enum([
47606
- "config-file",
47607
- "previous-config-file",
47838
+ "mounted-file",
47839
+ "previous-mounted-file",
47608
47840
  "platform",
47609
47841
  "default-file"
47610
47842
  ]),
@@ -52100,7 +52332,8 @@ var PALETTE_ENTRY_IDS = [
52100
52332
  "switch",
52101
52333
  "siren",
52102
52334
  "button",
52103
- "doorbell"
52335
+ "doorbell",
52336
+ "appliance-cycle"
52104
52337
  ];
52105
52338
  var AUDIO_ENTRY_ID = "audio";
52106
52339
  /** Six rungs, quiet → loud. The opaque end is the entry's hex. */
@@ -52171,7 +52404,8 @@ var CAMSTACK_HEX = {
52171
52404
  switch: "#7a812a",
52172
52405
  siren: "#ac5d7f",
52173
52406
  button: "#b276cd",
52174
- doorbell: "#0087ab"
52407
+ doorbell: "#0087ab",
52408
+ "appliance-cycle": "#2a8c8c"
52175
52409
  };
52176
52410
  var CAMSTACK_FALLBACK = [
52177
52411
  "#b34dc7",
@@ -52236,7 +52470,8 @@ var HIGH_CONTRAST_HEX = {
52236
52470
  switch: "#d05259",
52237
52471
  siren: "#cd67b3",
52238
52472
  button: "#c9746a",
52239
- doorbell: "#338eeb"
52473
+ doorbell: "#338eeb",
52474
+ "appliance-cycle": "#00848a"
52240
52475
  };
52241
52476
  var HIGH_CONTRAST_FALLBACK = [
52242
52477
  "#817f3b",
@@ -52301,7 +52536,8 @@ var OKABE_ITO_HEX = {
52301
52536
  switch: "#ad2c4e",
52302
52537
  siren: "#348e08",
52303
52538
  button: "#d27303",
52304
- doorbell: "#55770f"
52539
+ doorbell: "#55770f",
52540
+ "appliance-cycle": "#00737d"
52305
52541
  };
52306
52542
  var OKABE_ITO_FALLBACK = [
52307
52543
  "#9a3784",
@@ -52510,6 +52746,7 @@ sub("switch", "control", "control", "switch", "Switch");
52510
52746
  sub("siren", "control", "control", "siren", "Siren");
52511
52747
  sub("button", "control", "control", "button", "Button");
52512
52748
  sub("doorbell", "control", "control", "doorbell", "Doorbell");
52749
+ sub("appliance-cycle", "control", "control", "switch", "Appliance cycle");
52513
52750
  for (const l of AUDIO_MACRO_LABELS) {
52514
52751
  const kind = `audio-${l.id}`;
52515
52752
  if (entries.has(kind)) continue;
@@ -61141,7 +61378,8 @@ var CAP_TO_KIND = {
61141
61378
  cover: "cover",
61142
61379
  switch: "switch",
61143
61380
  button: "button",
61144
- doorbell: "doorbell"
61381
+ doorbell: "doorbell",
61382
+ "running-state": "appliance-cycle"
61145
61383
  };
61146
61384
  function buildDescriptor(capName, kind) {
61147
61385
  const t = EVENT_TAXONOMY[kind];
@@ -64271,10 +64509,7 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
64271
64509
  }), InstallPackageResultSchema, {
64272
64510
  kind: "mutation",
64273
64511
  auth: "admin"
64274
- }), method(object({ packageName: string() }), InstallPackageResultSchema, {
64275
- kind: "mutation",
64276
- auth: "admin"
64277
- }), method(_void(), boolean()), method(_void(), array(unknown()).readonly()), method(object({
64512
+ }), method(object({
64278
64513
  packageName: string().min(1),
64279
64514
  nodeId: string().optional(),
64280
64515
  everywhere: boolean().optional()
@@ -65535,19 +65770,19 @@ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status:
65535
65770
  reason: _enum(["unknown-template", "not-a-media-player"]),
65536
65771
  detail: string()
65537
65772
  })]);
65538
- var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
65773
+ var deviceInput = object({ deviceId: number().int().nonnegative() });
65539
65774
  DeviceType.MediaPlayer;
65540
- method(deviceInput$1, RemoteConfigSchema, { auth: "admin" }), method(deviceInput$1.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
65775
+ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
65541
65776
  kind: "mutation",
65542
65777
  auth: "admin"
65543
- }), method(deviceInput$1, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput$1.extend({
65778
+ }), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
65544
65779
  role: RemoteRoleSchema,
65545
65780
  /** Sends in a row (volume steps). */
65546
65781
  repeat: number().int().min(1).max(50).default(1)
65547
65782
  }), RemoteSendResultSchema, {
65548
65783
  kind: "mutation",
65549
65784
  auth: "protected"
65550
- }), method(deviceInput$1.extend({
65785
+ }), method(deviceInput.extend({
65551
65786
  command: RemoteMediaCommandSchema,
65552
65787
  muted: boolean().optional(),
65553
65788
  source: string().optional(),
@@ -69921,117 +70156,6 @@ var lockControlCapability = {
69921
70156
  * whether persisting is worth a SQLite commit. */
69922
70157
  volatileStateFields: ["lastChangedAt"]
69923
70158
  };
69924
- var PowerSourceRefusalReasonSchema = _enum([
69925
- "no-power-source",
69926
- "switch-missing",
69927
- "switch-offline",
69928
- "switch-unknown",
69929
- "restart-in-progress",
69930
- "switch-call-failed"
69931
- ]);
69932
- var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
69933
- accepted: literal(false),
69934
- reason: PowerSourceRefusalReasonSchema,
69935
- /** One line for the operator, naming the switch. */
69936
- message: string()
69937
- })]);
69938
- var PowerSourceInfoSchema = object({
69939
- /** False when the device has no power source linked; the rest is then empty. */
69940
- linked: boolean(),
69941
- switchDeviceId: number().nullable(),
69942
- switchName: string().nullable(),
69943
- /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
69944
- switchOn: boolean().nullable(),
69945
- switchOnline: boolean().nullable(),
69946
- restartInProgress: boolean(),
69947
- offSeconds: number().int()
69948
- });
69949
- /** The composed slice: the switch state the link mirrors, plus the stored off time. */
69950
- var PowerSourceStateSchema = object({
69951
- /** Null while the switch has not reported: unknown is never `false` (D393). */
69952
- switchOn: boolean().nullable(),
69953
- offSeconds: number().int(),
69954
- lastFetchedAt: number()
69955
- });
69956
- var POWER_SOURCE_DEVICE_TYPES = [
69957
- DeviceType.Gateway,
69958
- DeviceType.Hub,
69959
- DeviceType.Camera,
69960
- DeviceType.Sensor,
69961
- DeviceType.MediaPlayer,
69962
- DeviceType.Vacuum,
69963
- DeviceType.Generic,
69964
- DeviceType.Washer,
69965
- DeviceType.Dryer,
69966
- DeviceType.Dishwasher,
69967
- DeviceType.PetFountain,
69968
- DeviceType.Fan,
69969
- DeviceType.Humidifier,
69970
- DeviceType.Climate,
69971
- DeviceType.WaterHeater
69972
- ];
69973
- var powerSourceCustomize = {
69974
- version: 1,
69975
- targets: [{
69976
- id: "switch",
69977
- cardinality: "scalar",
69978
- vocabulary: {
69979
- cap: "power-source",
69980
- stateField: "switchOn"
69981
- },
69982
- deviceTypes: POWER_SOURCE_DEVICE_TYPES,
69983
- apply: "add",
69984
- path: "switchOn",
69985
- sources: [{
69986
- kind: "switch",
69987
- transforms: [{ kind: "identity" }]
69988
- }],
69989
- companions: [{
69990
- kind: "constant",
69991
- path: "offSeconds",
69992
- value: 10,
69993
- when: "add"
69994
- }, {
69995
- kind: "clock",
69996
- path: "lastFetchedAt",
69997
- when: "always"
69998
- }],
69999
- sourceReport: { status: "assignment-status" }
70000
- }]
70001
- };
70002
- var deviceInput = object({ deviceId: number().int().nonnegative() });
70003
- var powerSourceCapability = {
70004
- name: "power-source",
70005
- scope: "device",
70006
- mode: "singleton",
70007
- deviceTypes: POWER_SOURCE_DEVICE_TYPES,
70008
- methods: {
70009
- getStatus: systemMethod(deviceInput, PowerSourceInfoSchema, { auth: "admin" }),
70010
- powerOn: systemMethod(deviceInput, PowerSourceActionResultSchema, {
70011
- kind: "mutation",
70012
- auth: "admin"
70013
- }),
70014
- powerOff: systemMethod(deviceInput, PowerSourceActionResultSchema, {
70015
- kind: "mutation",
70016
- auth: "admin"
70017
- }),
70018
- forceRestart: systemMethod(deviceInput, PowerSourceActionResultSchema, {
70019
- kind: "mutation",
70020
- auth: "admin"
70021
- })
70022
- },
70023
- runtimeState: PowerSourceStateSchema,
70024
- customize: powerSourceCustomize,
70025
- /**
70026
- * Runtime-state durability: **session** — mirrored from the linked switch on
70027
- * its next report; a restored value would outlive the reading it names.
70028
- *
70029
- * See `RuntimeStateDurability`. Enforced by
70030
- * `scripts/check-runtime-state-durability.ts`.
70031
- */
70032
- durability: "session",
70033
- history: "none"
70034
- };
70035
70159
  /**
70036
70160
  * A monotonic total between resets (D944). Energy uses `power-meter.energy`
70037
70161
  * so dashboards keep one series; every other instantaneous reading lands here.
@@ -73573,6 +73697,10 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
73573
73697
  /** Generic Customize target for the running boolean (D936). */
73574
73698
  var runningCustomize = {
73575
73699
  version: 1,
73700
+ overridable: [{
73701
+ path: "running",
73702
+ by: "running"
73703
+ }],
73576
73704
  targets: [{
73577
73705
  id: "running",
73578
73706
  cardinality: "scalar",
@@ -73630,7 +73758,7 @@ var runningCustomize = {
73630
73758
  kind: "constant",
73631
73759
  path: "phase",
73632
73760
  value: null,
73633
- when: "always"
73761
+ when: "add"
73634
73762
  },
73635
73763
  {
73636
73764
  kind: "clock",
@@ -76940,7 +77068,7 @@ var ExternalIdentityKeySchema = object({
76940
77068
  provider: string().min(1),
76941
77069
  subject: string().min(1)
76942
77070
  });
76943
- method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
77071
+ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(_void(), object({ defaultPasswordInUse: boolean() }), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
76944
77072
  kind: "mutation",
76945
77073
  auth: "admin",
76946
77074
  access: "create"
@@ -80279,13 +80407,6 @@ Object.freeze({
80279
80407
  access: "view",
80280
80408
  role: "admin"
80281
80409
  },
80282
- "addons.installFromWorkspace": {
80283
- capName: "addons",
80284
- capScope: "system",
80285
- addonId: null,
80286
- access: "create",
80287
- role: "admin"
80288
- },
80289
80410
  "addons.installPackage": {
80290
80411
  capName: "addons",
80291
80412
  capScope: "system",
@@ -80293,13 +80414,6 @@ Object.freeze({
80293
80414
  access: "create",
80294
80415
  role: "admin"
80295
80416
  },
80296
- "addons.isWorkspaceAvailable": {
80297
- capName: "addons",
80298
- capScope: "system",
80299
- addonId: null,
80300
- access: "view",
80301
- role: "admin"
80302
- },
80303
80417
  "addons.list": {
80304
80418
  capName: "addons",
80305
80419
  capScope: "system",
@@ -80349,13 +80463,6 @@ Object.freeze({
80349
80463
  access: "view",
80350
80464
  role: "admin"
80351
80465
  },
80352
- "addons.listWorkspacePackages": {
80353
- capName: "addons",
80354
- capScope: "system",
80355
- addonId: null,
80356
- access: "view",
80357
- role: "admin"
80358
- },
80359
80466
  "addons.onAddonLogs": {
80360
80467
  capName: "addons",
80361
80468
  capScope: "system",
@@ -83023,6 +83130,27 @@ Object.freeze({
83023
83130
  access: "create",
83024
83131
  role: "admin"
83025
83132
  },
83133
+ "deviceRuns.getCurrentRun": {
83134
+ capName: "device-runs",
83135
+ capScope: "device",
83136
+ addonId: null,
83137
+ access: "view",
83138
+ role: "viewer"
83139
+ },
83140
+ "deviceRuns.getRun": {
83141
+ capName: "device-runs",
83142
+ capScope: "device",
83143
+ addonId: null,
83144
+ access: "view",
83145
+ role: "viewer"
83146
+ },
83147
+ "deviceRuns.listRuns": {
83148
+ capName: "device-runs",
83149
+ capScope: "device",
83150
+ addonId: null,
83151
+ access: "view",
83152
+ role: "viewer"
83153
+ },
83026
83154
  "deviceState.claimFields": {
83027
83155
  capName: "device-state",
83028
83156
  capScope: "system",
@@ -89736,6 +89864,13 @@ Object.freeze({
89736
89864
  access: "view",
89737
89865
  role: "admin"
89738
89866
  },
89867
+ "userManagement.getAdminBootstrapStatus": {
89868
+ capName: "user-management",
89869
+ capScope: "system",
89870
+ addonId: null,
89871
+ access: "view",
89872
+ role: "admin"
89873
+ },
89739
89874
  "userManagement.getTotpStatus": {
89740
89875
  capName: "user-management",
89741
89876
  capScope: "system",
@@ -91221,6 +91356,21 @@ Object.freeze({
91221
91356
  form: "single",
91222
91357
  optional: false
91223
91358
  }],
91359
+ "deviceRuns.getCurrentRun": [{
91360
+ name: "deviceId",
91361
+ form: "single",
91362
+ optional: false
91363
+ }],
91364
+ "deviceRuns.getRun": [{
91365
+ name: "deviceId",
91366
+ form: "single",
91367
+ optional: false
91368
+ }],
91369
+ "deviceRuns.listRuns": [{
91370
+ name: "deviceId",
91371
+ form: "single",
91372
+ optional: false
91373
+ }],
91224
91374
  "deviceState.claimFields": [{
91225
91375
  name: "deviceId",
91226
91376
  form: "single",
@@ -93703,13 +93853,114 @@ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
93703
93853
  *
93704
93854
  * Pure, RN-safe: the viewer imports the support rule from here.
93705
93855
  */
93856
+ /**
93857
+ * Control caps that still have a raise and a clear an operator names: an
93858
+ * appliance cycle raises when it STARTS and clears when it FINISHES. Kept out
93859
+ * of `SOURCE_CAP_ACTIVE_FIELD` on purpose — that table also makes a device a
93860
+ * camera-sensor / recorder-trigger source, and a dishwasher is neither.
93861
+ */
93862
+ var CYCLE_RAISED_FIELD_BY_CAP = { "running-state": "running" };
93706
93863
  /** Event kind → the boolean field of its cap slice that means "raised". */
93707
93864
  var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
93865
+ const cycleField = CYCLE_RAISED_FIELD_BY_CAP[capName];
93866
+ if (cycleField !== void 0) return [[descriptor.kind, cycleField]];
93708
93867
  const field = SOURCE_CAP_ACTIVE_FIELD[capName];
93709
93868
  if (field === void 0 || descriptor.category !== "sensor") return [];
93710
93869
  return [[descriptor.kind, field]];
93711
93870
  })));
93712
93871
  Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
93872
+ Object.freeze({
93873
+ contact: {
93874
+ starts: {
93875
+ en: "opened",
93876
+ it: "si apre"
93877
+ },
93878
+ finishes: {
93879
+ en: "closed",
93880
+ it: "si chiude"
93881
+ }
93882
+ },
93883
+ "motion-sensor": {
93884
+ starts: {
93885
+ en: "motion detected",
93886
+ it: "movimento rilevato"
93887
+ },
93888
+ finishes: {
93889
+ en: "motion stopped",
93890
+ it: "movimento terminato"
93891
+ }
93892
+ },
93893
+ smoke: {
93894
+ starts: {
93895
+ en: "smoke detected",
93896
+ it: "fumo rilevato"
93897
+ },
93898
+ finishes: {
93899
+ en: "smoke gone",
93900
+ it: "fumo scomparso"
93901
+ }
93902
+ },
93903
+ flood: {
93904
+ starts: {
93905
+ en: "water detected",
93906
+ it: "acqua rilevata"
93907
+ },
93908
+ finishes: {
93909
+ en: "dry again",
93910
+ it: "di nuovo asciutto"
93911
+ }
93912
+ },
93913
+ gas: {
93914
+ starts: {
93915
+ en: "gas detected",
93916
+ it: "gas rilevato"
93917
+ },
93918
+ finishes: {
93919
+ en: "gas gone",
93920
+ it: "gas scomparso"
93921
+ }
93922
+ },
93923
+ "carbon-monoxide": {
93924
+ starts: {
93925
+ en: "carbon monoxide detected",
93926
+ it: "monossido rilevato"
93927
+ },
93928
+ finishes: {
93929
+ en: "carbon monoxide gone",
93930
+ it: "monossido scomparso"
93931
+ }
93932
+ },
93933
+ vibration: {
93934
+ starts: {
93935
+ en: "vibration detected",
93936
+ it: "vibrazione rilevata"
93937
+ },
93938
+ finishes: {
93939
+ en: "vibration stopped",
93940
+ it: "vibrazione terminata"
93941
+ }
93942
+ },
93943
+ tamper: {
93944
+ starts: {
93945
+ en: "tampered with",
93946
+ it: "manomesso"
93947
+ },
93948
+ finishes: {
93949
+ en: "tamper cleared",
93950
+ it: "manomissione rientrata"
93951
+ }
93952
+ },
93953
+ "appliance-cycle": {
93954
+ starts: {
93955
+ en: "the cycle started",
93956
+ it: "il ciclo parte"
93957
+ },
93958
+ finishes: {
93959
+ en: "the cycle finished",
93960
+ it: "il ciclo è terminato"
93961
+ }
93962
+ }
93963
+ });
93713
93964
  object({
93714
93965
  phase: _enum(["raised", "resolved"]),
93715
93966
  episodeId: string().min(1),
@@ -93916,6 +94167,10 @@ var STATE_SUMMARY_VARS = [
93916
94167
  [...RULE_TEXT],
93917
94168
  [...RULE_TEXT],
93918
94169
  [...RULE_TEXT],
94170
+ [...RULE_TEXT],
94171
+ [...RULE_TEXT],
94172
+ [...RULE_TEXT],
94173
+ [...RULE_TEXT],
93919
94174
  [...RULE_BODY_ONLY],
93920
94175
  [...RULE_TEXT],
93921
94176
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
@@ -93933,6 +94188,7 @@ var STATE_SUMMARY_VARS = [
93933
94188
  [...RULE_TEXT],
93934
94189
  [...RULE_TEXT],
93935
94190
  [...RULE_TEXT],
94191
+ [...RULE_TEXT],
93936
94192
  ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
93937
94193
  [...RULE_TEXT],
93938
94194
  [...RULE_TEXT],
@@ -94177,6 +94433,7 @@ new Set([
94177
94433
  "siren",
94178
94434
  "button",
94179
94435
  "doorbell",
94436
+ "appliance-cycle",
94180
94437
  "audio-speech",
94181
94438
  "audio-scream",
94182
94439
  "audio-crying",
@@ -103486,22 +103743,26 @@ var VeluxClientRegistry = class {
103486
103743
  };
103487
103744
  //#endregion
103488
103745
  //#region src/klf-probe.ts
103489
- /** The gateway's TLS API port. */
103490
- var KLF_PORT = 51200;
103491
103746
  /**
103492
- * Defaults add up, worst case, to 3 + 2 x 5 + 2 + 2 = 17 s, under the
103493
- * `CONNECTION_TEST_TIMEOUT_MS` ceiling of the wizard, so a stuck gateway is named
103494
- * here instead of the wizard giving up unnamed.
103747
+ * Defaults add up, worst case, to 12 + 3 + 1.5 + 1.5 = 18 s, which is the
103748
+ * wizard's `CONNECTION_TEST_TIMEOUT_MS` ceiling (20 s) minus the 2 s margin of
103749
+ * `DEFAULT_TEST_DEADLINE_MS`, so a stuck gateway is named here instead of the
103750
+ * wizard giving up unnamed. The handshake gets 12 s because a real KLF-200 needs
103751
+ * about 4.2 s for ONE handshake (TLSv1.2, DHE-RSA-AES256-SHA256 on a weak CPU).
103752
+ *
103753
+ * A probe opens exactly ONE connection: the gateway frees a closed slot very
103754
+ * slowly (measured 2026-11-01: a bare TCP connect and close before the TLS connect
103755
+ * left the real session without a handshake for 20 s), so no pre-check connects first.
103495
103756
  */
103496
103757
  var DEFAULT_KLF_PROBE_TIMEOUTS_MS = {
103497
- tcp: 3e3,
103498
- login: 5e3,
103499
- version: 2e3,
103500
- logout: 2e3
103758
+ tls: 12e3,
103759
+ password: 3e3,
103760
+ version: 1500,
103761
+ logout: 1500
103501
103762
  };
103502
103763
  var PHASE_TEXT = {
103503
- tcp: "did not accept a TCP connection on port 51200",
103504
- login: "did not complete the secure login (TLS handshake or password answer)",
103764
+ tls: "did not accept a connection or complete the TLS handshake on port 51200",
103765
+ password: "did not answer the password",
103505
103766
  version: "did not answer the version request",
103506
103767
  logout: "did not close the session"
103507
103768
  };
@@ -103528,26 +103789,43 @@ async function withDeadline(work, ms, onTimeout) {
103528
103789
  clearTimeout(timer);
103529
103790
  }
103530
103791
  }
103531
- /** A bare TCP connect to the gateway port: tells "nothing listens" from "TLS is stuck". */
103532
- function tcpReachable(host, ms) {
103533
- return new Promise((resolve, reject) => {
103534
- const socket = (0, node_net.connect)({
103535
- host,
103536
- port: KLF_PORT
103537
- });
103538
- socket.setTimeout(ms, () => {
103539
- socket.destroy();
103540
- reject(new KlfProbeTimeoutError("tcp", host, ms));
103541
- });
103542
- socket.once("connect", () => {
103543
- socket.destroy();
103544
- resolve();
103545
- });
103546
- socket.once("error", (err) => {
103547
- socket.destroy();
103548
- reject(err);
103549
- });
103792
+ /** How often the login watcher checks whether the handshake has finished. */
103793
+ var PHASE_POLL_MS = 20;
103794
+ /**
103795
+ * The library's `loginAsync` runs the TLS handshake and the password answer as one
103796
+ * call. `KLF200SocketProtocol` appears when the handshake is done and the password
103797
+ * is on its way, so watching it splits the call into two bounded, named steps.
103798
+ */
103799
+ async function loginInPhases(connection, host, password, limits) {
103800
+ let handshakeDone = false;
103801
+ let passwordTimer;
103802
+ let tlsTimer;
103803
+ let poll;
103804
+ const tlsError = () => new KlfProbeTimeoutError("tls", host, limits.tls);
103805
+ const passwordError = () => new KlfProbeTimeoutError("password", host, limits.password);
103806
+ const bounds = new Promise((_resolve, reject) => {
103807
+ tlsTimer = setTimeout(() => reject(tlsError()), limits.tls);
103808
+ poll = setInterval(() => {
103809
+ if (handshakeDone || connection.KLF200SocketProtocol === void 0) return;
103810
+ handshakeDone = true;
103811
+ clearTimeout(tlsTimer);
103812
+ clearInterval(poll);
103813
+ passwordTimer = setTimeout(() => reject(passwordError()), limits.password);
103814
+ }, PHASE_POLL_MS);
103550
103815
  });
103816
+ const libraryS = (limits.tls + limits.password) / 1e3;
103817
+ try {
103818
+ await Promise.race([connection.loginAsync(password, libraryS), bounds]);
103819
+ } catch (err) {
103820
+ if (err instanceof KlfProbeTimeoutError) throw err;
103821
+ if (/^timeout$/i.test(errMsg(err)) || err instanceof Error && err.name === "TimeoutError") throw handshakeDone ? passwordError() : tlsError();
103822
+ throw err;
103823
+ } finally {
103824
+ clearTimeout(tlsTimer);
103825
+ clearTimeout(passwordTimer);
103826
+ clearInterval(poll);
103827
+ bounds.catch(() => void 0);
103828
+ }
103551
103829
  }
103552
103830
  /**
103553
103831
  * Opens ONE session, authenticates, reads the firmware version and closes the
@@ -103560,15 +103838,13 @@ async function probeKlfGateway(options) {
103560
103838
  ...DEFAULT_KLF_PROBE_TIMEOUTS_MS,
103561
103839
  ...options.timeoutsMs
103562
103840
  };
103563
- await tcpReachable(options.host, limits.tcp);
103564
103841
  const connection = options.tls === void 0 ? new Connection(options.host) : new Connection(options.host, options.tls);
103565
- const loginError = () => new KlfProbeTimeoutError("login", options.host, limits.login);
103566
103842
  try {
103567
103843
  try {
103568
- await withDeadline(connection.loginAsync(options.password, limits.login / 1e3), limits.login * 2 + 500, loginError);
103844
+ await loginInPhases(connection, options.host, options.password, limits);
103569
103845
  } catch (err) {
103570
103846
  if (err instanceof KlfProbeTimeoutError) throw err;
103571
- if (/^timeout$/i.test(errMsg(err)) || err instanceof Error && err.name === "TimeoutError") throw loginError();
103847
+ if (err instanceof Error && typeof Reflect.get(err, "code") === "string") throw err;
103572
103848
  throw loginFailure(err);
103573
103849
  }
103574
103850
  try {