@camstack/addon-provider-enpal 0.1.17 → 0.1.19

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
@@ -3,7 +3,7 @@ let node_child_process = require("node:child_process");
3
3
  let node_fs = require("node:fs");
4
4
  let node_path = require("node:path");
5
5
  let node_url = require("node:url");
6
- //#region ../types/dist/event-category-78tcBemo.mjs
6
+ //#region ../types/dist/event-category-Dv-Gcv1f.mjs
7
7
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
8
8
  EventCategory["SystemBoot"] = "system.boot";
9
9
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -444,6 +444,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
444
444
  */
445
445
  EventCategory["DeviceStateChanged"] = "device.state-changed";
446
446
  /**
447
+ * A device COMMAND (a vocabulary action, D920/D983) settled in the hub's
448
+ * transition audit (D1022): confirmed, already in place, unconfirmed or
449
+ * failed. Never a device's own change. Payload: `DeviceCommandSettledEvent`
450
+ * (`{deviceId, action, outcome, actor, at}`, no arguments). It feeds
451
+ * the Notification Center `device-command` kind. Telemetry (D8): losing one
452
+ * costs a notification, never state; the audit row is the record.
453
+ */
454
+ EventCategory["DeviceCommandSettled"] = "device.command-settled";
455
+ /**
447
456
  * The NATIVE shadow of a cap a composition has claimed fields of moved
448
457
  * (D663). Payload: `{ deviceId, capName, native }` — what the provider
449
458
  * wrote, BEFORE the owned fields are overlaid. Owner-only (D224): it is for
@@ -6048,7 +6057,7 @@ var ZodIssueCode = {
6048
6057
  var ZodFirstPartyTypeKind;
6049
6058
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6050
6059
  //#endregion
6051
- //#region ../types/dist/sleep-D0roI_CZ.mjs
6060
+ //#region ../types/dist/sleep-DyIxjZZt.mjs
6052
6061
  /**
6053
6062
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6054
6063
  * window to float samples (D455).
@@ -6731,6 +6740,13 @@ async function runAddonMigrations(host, migrations, logger, options = {}) {
6731
6740
  else skipped.push(id);
6732
6741
  };
6733
6742
  const selected = options.only === void 0 ? null : new Set(options.only);
6743
+ const deferred = /* @__PURE__ */ new Set();
6744
+ const deferredBy = (reason) => {
6745
+ if (selected === null || reason === null) return null;
6746
+ const dep = /^dependency-(?:pending|blocked):(.+)$/.exec(reason)?.[1];
6747
+ if (dep === void 0 || !byId.has(dep)) return null;
6748
+ return !selected.has(dep) || deferred.has(dep) ? dep : null;
6749
+ };
6734
6750
  for (const step of declaration.steps) {
6735
6751
  if (selected !== null && !selected.has(step.id)) continue;
6736
6752
  const bound = byId.get(step.id);
@@ -6742,6 +6758,19 @@ async function runAddonMigrations(host, migrations, logger, options = {}) {
6742
6758
  try {
6743
6759
  const gate = await host.startMigration({ id: step.id });
6744
6760
  if (!gate.proceed) {
6761
+ const waitsFor = deferredBy(gate.reason);
6762
+ if (waitsFor !== null) {
6763
+ log.info("migration deferred: its predecessor runs in another phase of this owner", {
6764
+ tags,
6765
+ meta: {
6766
+ waitsFor,
6767
+ reason: gate.reason
6768
+ }
6769
+ });
6770
+ deferred.add(step.id);
6771
+ skipped.push(step.id);
6772
+ continue;
6773
+ }
6745
6774
  log.warn("migration not run", {
6746
6775
  tags,
6747
6776
  meta: {
@@ -10279,6 +10308,13 @@ var DeviceTransitionSchema = object({
10279
10308
  actor: TransitionActorSchema,
10280
10309
  command: TransitionCommandSchema.optional()
10281
10310
  });
10311
+ object({
10312
+ deviceId: number$1().int(),
10313
+ action: string(),
10314
+ outcome: TransitionOutcomeSchema,
10315
+ actor: TransitionActorSchema,
10316
+ at: number$1()
10317
+ });
10282
10318
  var READING_QUANTITIES = [
10283
10319
  "temperature",
10284
10320
  "pressure",
@@ -12302,7 +12338,10 @@ var ACTUATOR_CAP_VOCABULARY = {
12302
12338
  "ir-transmitter": {
12303
12339
  scope: "device",
12304
12340
  noun: t("IR / RF transmitter", "Trasmettitore IR / RF"),
12305
- notOperatorFacing: { send: "a raw pulse train is not something an operator composes in an automation; the remote that owns the command table sends it" }
12341
+ notOperatorFacing: {
12342
+ send: "a raw pulse train is not something an operator composes in an automation; the remote that owns the command table sends it",
12343
+ learn: "an interactive capture the operator starts from the source dialog and answers by pressing a physical remote; no automation can supply the signal"
12344
+ }
12306
12345
  },
12307
12346
  control: {
12308
12347
  scope: "device",
@@ -15223,6 +15262,32 @@ var CAP_VOCABULARY_BY_NAME = {
15223
15262
  ...SYSTEM_CAP_VOCABULARY
15224
15263
  };
15225
15264
  /**
15265
+ * Vocabulary actions by id (D983). An action step names a vocabulary action
15266
+ * (`cover.open`), never a raw cap method; this is the one lookup that turns the
15267
+ * id into its cap, method and effect — and a persisted legacy `cap` + `method`
15268
+ * pair back into the action id when exactly one action matches.
15269
+ */
15270
+ function buildIndex() {
15271
+ const byId = /* @__PURE__ */ new Map();
15272
+ for (const [cap, entry] of Object.entries(CAP_VOCABULARY_BY_NAME)) {
15273
+ if (entry.scope !== "device") continue;
15274
+ for (const action of entry.actions ?? []) byId.set(action.id, {
15275
+ cap,
15276
+ action
15277
+ });
15278
+ }
15279
+ return byId;
15280
+ }
15281
+ var index;
15282
+ function actionIndex() {
15283
+ index ??= buildIndex();
15284
+ return index;
15285
+ }
15286
+ /** The vocabulary action with this id, or `undefined`. */
15287
+ function findVocabAction(actionId) {
15288
+ return actionIndex().get(actionId);
15289
+ }
15290
+ /**
15226
15291
  * `addon-pages-source` — collection cap exposing per-provider raw page
15227
15292
  * declarations. Every addon that contributes a UI page registers a
15228
15293
  * provider here. The hub-side singleton aggregator (`addon-pages` cap,
@@ -44908,21 +44973,23 @@ function convertLegacyWhen(half) {
44908
44973
  * (instants) and state leaves (durations). It replaces the flat trigger list
44909
44974
  * plus the separate condition tree; the firing rule is in that file and in
44910
44975
  * ADR "one tree".
44911
- * - **Actions** — the `wait` and `cap` steps, plus the `{kind:'code'}`
44976
+ * - **Actions** — the `wait` and device `command` steps, plus the `{kind:'code'}`
44912
44977
  * fragment of §3.2.2. See {@link AutomationActionSchema}.
44913
44978
  *
44914
44979
  * A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
44915
44980
  * {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
44916
44981
  */
44917
44982
  /**
44918
- * One action step: `wait`, or a `cap` call.
44983
+ * One action step: `wait`, or a device `command`.
44919
44984
  *
44920
- * A cap step's target is who it acts on (D983: `devices`, a `domain`, or a
44921
- * `{ $var }` resolved from the run's variables); the legacy `deviceId` is still
44922
- * READ, never written. The step runner (`runSteps`) owns the device-scope
44923
- * check, the stop-at-first-failure and the variable substitution, once, for
44924
- * every caller — automations, Actions, and the notification centre, which runs
44925
- * only Actions (planning#473).
44985
+ * A command step names a VOCABULARY action (`cover.open`) and who it acts on
44986
+ * (D983: `devices`, a `domain`, or a `{ $var }` resolved from the run's
44987
+ * variables). It never names a cap method: the step runner turns the action
44988
+ * id into its cap + method through the vocabulary index when the step runs
44989
+ * (`vocabCall`), the one place that mapping happens. The step runner
44990
+ * (`runSteps`) owns the device-scope check, the stop-at-first-failure and the
44991
+ * variable substitution, once, for every caller — automations, Actions, and
44992
+ * the notification centre, which runs only Actions (planning#473).
44926
44993
  */
44927
44994
  /** The longest one `wait` step lasts. It is cut by a restart of the run and does not survive a hub restart. */
44928
44995
  var WAIT_MAX_SECONDS = 3600;
@@ -44931,39 +44998,22 @@ var AutomationWaitStepSchema = object({
44931
44998
  kind: literal("wait"),
44932
44999
  seconds: number$1().min(0).max(WAIT_MAX_SECONDS, WAIT_TOO_LONG)
44933
45000
  });
44934
- /** The legacy `deviceId` of a cap step: still READ, never written (D983). */
44935
- var LegacyStepDeviceSchema = union([
44936
- number$1().int(),
44937
- array(number$1().int()).min(1),
44938
- object({ $var: string().min(1) })
44939
- ]);
44940
45001
  /**
44941
- * The fields of a cap step, before the target rules are applied. Exported for
44942
- * a shape that is "a cap call, narrowed" (a remote-control binding is one
44943
- * device); a step is {@link AutomationCapStepSchema}.
45002
+ * The fields of a command step, before the vocabulary check. A step is
45003
+ * {@link AutomationCommandStepSchema}.
44944
45004
  */
44945
- var AutomationCapStepBaseSchema = object({
44946
- kind: literal("cap"),
44947
- /**
44948
- * The vocabulary action this step performs (`cover.open`). Required when
44949
- * `target` is a `domain` (the domain is "every device offering this
44950
- * action"); filled by {@link normalizeCapStep} for a legacy step whose
44951
- * cap + method match exactly one vocabulary action.
44952
- */
44953
- actionId: string().min(1).optional(),
44954
- /** WHO the step acts on. The only shape that is written. */
44955
- target: ActionTargetSchema.optional(),
45005
+ var AutomationCommandStepBaseSchema = object({
45006
+ kind: literal("command"),
45007
+ /** WHO the step acts on. */
45008
+ target: ActionTargetSchema,
45009
+ /** The vocabulary action this step performs (`cover.open`). */
45010
+ actionId: string().min(1),
44956
45011
  /**
44957
- * @deprecated The pre-D983 target: one device, several, or `{ $var }`.
44958
- * Still READ — {@link normalizeCapStep} maps it to `target` — and never
44959
- * written.
45012
+ * The values of the action's OPEN args (`position`, `presetId`); the args
45013
+ * the vocabulary bakes into the action are added when the step runs. Values
45014
+ * may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
45015
+ * SUBSTITUTE and do not evaluate (§3.2.3).
44960
45016
  */
44961
- deviceId: LegacyStepDeviceSchema.optional(),
44962
- cap: string().min(1),
44963
- method: string().min(1),
44964
- /** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
44965
- * SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
44966
- * the expression leaf. */
44967
45017
  args: record(string(), unknown()).optional(),
44968
45018
  /**
44969
45019
  * The unit the person picked per arg (display only). `args` always hold the
@@ -44972,18 +45022,20 @@ var AutomationCapStepBaseSchema = object({
44972
45022
  */
44973
45023
  argUnits: record(string(), string().min(1)).optional()
44974
45024
  });
45025
+ /** The vocabulary check every command step passes: the action exists. */
45026
+ function refineKnownAction(step, ctx) {
45027
+ if (findVocabAction(step.actionId) === void 0) ctx.addIssue({
45028
+ code: "custom",
45029
+ message: `"${step.actionId}" is not a vocabulary action id`,
45030
+ path: ["actionId"]
45031
+ });
45032
+ }
44975
45033
  /**
44976
- * The steps an internal Action may contain: cap and wait, and nothing that can
45034
+ * The steps an internal Action may contain: command and wait, and nothing that can
44977
45035
  * call back into an Action — so an Action can never reach itself (no cycles).
44978
45036
  * This is the LEAF shape: no condition of its own.
44979
45037
  */
44980
- var LeafActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCapStepBaseSchema.refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
44981
- message: "a cap step needs a target",
44982
- path: ["target"]
44983
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
44984
- message: "a domain target needs the vocabulary action it applies to (actionId)",
44985
- path: ["actionId"]
44986
- })]);
45038
+ var LeafActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCommandStepBaseSchema.superRefine(refineKnownAction)]);
44987
45039
  /**
44988
45040
  * The optional guard of a step (#472): `if` is the ONE `when` tree (D980), read
44989
45041
  * on the state mirror when the step is reached; `else` runs when it is false.
@@ -45004,13 +45056,7 @@ var GuardedWaitStepSchema = AutomationWaitStepSchema.extend(stepGuardShape).refi
45004
45056
  * same two step kinds an automation runs, minus a guard that reads the
45005
45057
  * automation's trigger, which such a list does not have.
45006
45058
  */
45007
- var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, AutomationCapStepBaseSchema.extend(stepGuardShape).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
45008
- message: "a cap step needs a target",
45009
- path: ["target"]
45010
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
45011
- message: "a domain target needs the vocabulary action it applies to (actionId)",
45012
- path: ["actionId"]
45013
- }).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf)]).superRefine((step, ctx) => {
45059
+ var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, AutomationCommandStepBaseSchema.extend(stepGuardShape).superRefine(refineKnownAction).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf)]).superRefine((step, ctx) => {
45014
45060
  if (step.if !== void 0 && hasTriggeredByLeaves(step.if)) ctx.addIssue({
45015
45061
  code: "custom",
45016
45062
  message: TRIGGERED_BY_NEEDS_AUTOMATION,
@@ -45025,17 +45071,11 @@ var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, Automa
45025
45071
  var continueOnErrorShape = {
45026
45072
  /** A failure of this step is traced `failed-continued` and the next step runs. */
45027
45073
  continueOnError: literal(true).optional() };
45028
- /** An automation's cap step: the shared guarded cap step, plus `continueOnError`. */
45029
- var AutomationGuardedCapStepSchema = AutomationCapStepBaseSchema.extend({
45074
+ /** An automation's command step: the shared guarded command step, plus `continueOnError`. */
45075
+ var AutomationGuardedCommandStepSchema = AutomationCommandStepBaseSchema.extend({
45030
45076
  ...stepGuardShape,
45031
45077
  ...continueOnErrorShape
45032
- }).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
45033
- message: "a cap step needs a target",
45034
- path: ["target"]
45035
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
45036
- message: "a domain target needs the vocabulary action it applies to (actionId)",
45037
- path: ["actionId"]
45038
- }).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
45078
+ }).superRefine(refineKnownAction).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
45039
45079
  /** End the run here, on purpose. `reason` may carry `{{vars.x}}` slots. */
45040
45080
  var AutomationStopStepSchema = object({
45041
45081
  kind: literal("stop"),
@@ -45115,13 +45155,7 @@ function nestTooDeep(issue) {
45115
45155
  }
45116
45156
  var plainStepSchemas = [
45117
45157
  AutomationWaitStepSchema.strict(),
45118
- AutomationCapStepBaseSchema.extend(continueOnErrorShape).strict().refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
45119
- message: "a cap step needs a target",
45120
- path: ["target"]
45121
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
45122
- message: "a domain target needs the vocabulary action it applies to (actionId)",
45123
- path: ["actionId"]
45124
- }),
45158
+ AutomationCommandStepBaseSchema.extend(continueOnErrorShape).strict().superRefine(refineKnownAction),
45125
45159
  AutomationStopStepSchema.omit({ if: true }).strict(),
45126
45160
  AutomationSetStepSchema.omit({ if: true }).strict(),
45127
45161
  AutomationRunActionStepSchema.strict()
@@ -45132,7 +45166,7 @@ var plainStepSchemas = [
45132
45166
  */
45133
45167
  var AutomationBranchStepSchema = discriminatedUnion("kind", plainStepSchemas, { error: nestTooDeep });
45134
45168
  /** A step that can run beside others: it writes no variable and does not end the run. */
45135
- var AutomationParallelMemberSchema = AutomationBranchStepSchema.refine((step) => step.kind === "cap" || step.kind === "action" || step.kind === "wait", "a parallel holds device commands, actions and waits only: a set or a stop beside other steps has no order");
45169
+ var AutomationParallelMemberSchema = AutomationBranchStepSchema.refine((step) => step.kind === "command" || step.kind === "action" || step.kind === "wait", "a parallel holds device commands, actions and waits only: a set or a stop beside other steps has no order");
45136
45170
  /**
45137
45171
  * Several steps at once (#500): all are started together and all are awaited.
45138
45172
  * A failure fails the run once the others finished, as in a sequence; a member
@@ -45212,7 +45246,7 @@ var AutomationBodyStepSchema = discriminatedUnion("kind", [
45212
45246
  var AutomationRepeatStepSchema = repeatSchemaOf(AutomationBodyStepSchema);
45213
45247
  var AutomationActionSchema = discriminatedUnion("kind", [
45214
45248
  GuardedWaitStepSchema,
45215
- AutomationGuardedCapStepSchema,
45249
+ AutomationGuardedCommandStepSchema,
45216
45250
  AutomationStopStepSchema,
45217
45251
  AutomationSetStepSchema,
45218
45252
  chooseSchemaOf(AutomationBodyStepSchema),
@@ -45283,110 +45317,6 @@ function convertLegacyRecipe(legacy) {
45283
45317
  };
45284
45318
  }
45285
45319
  var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
45286
- var DETECTION_DESCRIPTIONS = {
45287
- deviceId: "the camera that saw it",
45288
- className: "what it saw",
45289
- trackId: "the track",
45290
- score: "how sure the detector was",
45291
- label: "the label it matched (a name, a plate), empty when none"
45292
- };
45293
- var STATIONARY_DESCRIPTIONS = {
45294
- deviceId: "the camera that saw it",
45295
- className: "what it saw",
45296
- zones: "the zones it is in",
45297
- phase: "appeared or departed",
45298
- entryId: "the stopped object"
45299
- };
45300
- var BECOMES_DESCRIPTIONS = {
45301
- deviceId: "the device that changed",
45302
- from: "the value before the change, empty when it is not known",
45303
- to: "the value it changed to"
45304
- };
45305
- /**
45306
- * A fire's payload is offered too, as `data.<path>` (`{{vars.data.channel}}`):
45307
- * those names depend on the device, so they are not in this list.
45308
- */
45309
- var FIRED_DESCRIPTIONS = {
45310
- deviceId: "the device that fired",
45311
- eventType: "the event, as the device names it",
45312
- sourceId: "the part of the device that fired (a button), empty when it names none",
45313
- eventKind: "the kind of press (press, long-press …), empty when the device declares none"
45314
- };
45315
- function describe$1(descriptions) {
45316
- return Object.entries(descriptions).map(([name, description]) => ({
45317
- name,
45318
- description
45319
- }));
45320
- }
45321
- describe$1(DETECTION_DESCRIPTIONS);
45322
- describe$1(STATIONARY_DESCRIPTIONS);
45323
- describe$1(FIRED_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
45324
- ...v,
45325
- hintKey: "firedDeviceId"
45326
- } : v);
45327
- describe$1(BECOMES_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
45328
- ...v,
45329
- hintKey: "becomesDeviceId"
45330
- } : v);
45331
- new Set(Object.values(COCO_TO_MACRO.mapping));
45332
- /**
45333
- * Generic stateless event emitter. Installed on a `DeviceType.EventEmitter`
45334
- * device. Carries the device's EXACT declared event vocabulary verbatim
45335
- * (NO normalization). Two shapes:
45336
- * - structured source (HA `event.*` entity) → `vocabulary` is the fixed
45337
- * declared set (e.g. press_short, press_long, …).
45338
- * - generic/legacy source (HA bus events, e.g. zha_event) → `vocabulary`
45339
- * is [] (open) and `lastEvent.data` carries the complex payload.
45340
- * `vocabulary` (D1044) names each event for a person and types it for a
45341
- * rule; `eventTypes` is its list of native tokens, kept for older readers.
45342
- * A fire names the part of the device it came from in `sourceId` when the
45343
- * vocabulary declares sources (a remote's buttons).
45344
- * `seq` is monotonic so two identical events still produce distinct slice writes.
45345
- */
45346
- var EventFireSchema = object({
45347
- deviceId: number$1(),
45348
- eventType: string(),
45349
- sourceId: string().optional(),
45350
- data: record(string(), unknown()).nullable(),
45351
- timestamp: number$1().meta(readingMeta("none")),
45352
- seq: number$1().meta(readingMeta("count"))
45353
- });
45354
- var EventEmitterStatusSchema = object({
45355
- eventTypes: array(string()),
45356
- /**
45357
- * Required of a provider by the type. A status written before the field
45358
- * existed still parses, as an empty vocabulary: read it through
45359
- * `readEventVocabulary`, which then answers with the plain tokens.
45360
- */
45361
- vocabulary: array(EventVocabularyEntrySchema).default([]),
45362
- lastEvent: EventFireSchema.nullable(),
45363
- eventCountSinceStart: number$1().meta(readingMeta("count"))
45364
- });
45365
- var eventEmitterCapability = {
45366
- name: "event-emitter",
45367
- scope: "device",
45368
- deviceNative: true,
45369
- mode: "singleton",
45370
- deviceTypes: [DeviceType.EventEmitter],
45371
- methods: {},
45372
- events: { onEvent: { data: EventFireSchema } },
45373
- status: {
45374
- schema: EventEmitterStatusSchema,
45375
- kind: "push"
45376
- },
45377
- runtimeState: EventEmitterStatusSchema,
45378
- /**
45379
- * Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
45380
- *
45381
- * See `RuntimeStateDurability`. Enforced by
45382
- * `scripts/check-runtime-state-durability.ts`.
45383
- */
45384
- durability: "session",
45385
- /**
45386
- * History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
45387
- */
45388
- history: "none"
45389
- };
45390
45320
  /**
45391
45321
  * Schedulers (D983) — an entity that performs actions over the day.
45392
45322
  *
@@ -45785,6 +45715,110 @@ var AutomationStateDocSchema = object({
45785
45715
  lastRunStartedAt: number$1().nullable(),
45786
45716
  traces: array(AutomationRunTraceSchema)
45787
45717
  });
45718
+ var DETECTION_DESCRIPTIONS = {
45719
+ deviceId: "the camera that saw it",
45720
+ className: "what it saw",
45721
+ trackId: "the track",
45722
+ score: "how sure the detector was",
45723
+ label: "the label it matched (a name, a plate), empty when none"
45724
+ };
45725
+ var STATIONARY_DESCRIPTIONS = {
45726
+ deviceId: "the camera that saw it",
45727
+ className: "what it saw",
45728
+ zones: "the zones it is in",
45729
+ phase: "appeared or departed",
45730
+ entryId: "the stopped object"
45731
+ };
45732
+ var BECOMES_DESCRIPTIONS = {
45733
+ deviceId: "the device that changed",
45734
+ from: "the value before the change, empty when it is not known",
45735
+ to: "the value it changed to"
45736
+ };
45737
+ /**
45738
+ * A fire's payload is offered too, as `data.<path>` (`{{vars.data.channel}}`):
45739
+ * those names depend on the device, so they are not in this list.
45740
+ */
45741
+ var FIRED_DESCRIPTIONS = {
45742
+ deviceId: "the device that fired",
45743
+ eventType: "the event, as the device names it",
45744
+ sourceId: "the part of the device that fired (a button), empty when it names none",
45745
+ eventKind: "the kind of press (press, long-press …), empty when the device declares none"
45746
+ };
45747
+ function describe$1(descriptions) {
45748
+ return Object.entries(descriptions).map(([name, description]) => ({
45749
+ name,
45750
+ description
45751
+ }));
45752
+ }
45753
+ describe$1(DETECTION_DESCRIPTIONS);
45754
+ describe$1(STATIONARY_DESCRIPTIONS);
45755
+ describe$1(FIRED_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
45756
+ ...v,
45757
+ hintKey: "firedDeviceId"
45758
+ } : v);
45759
+ describe$1(BECOMES_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
45760
+ ...v,
45761
+ hintKey: "becomesDeviceId"
45762
+ } : v);
45763
+ new Set(Object.values(COCO_TO_MACRO.mapping));
45764
+ /**
45765
+ * Generic stateless event emitter. Installed on a `DeviceType.EventEmitter`
45766
+ * device. Carries the device's EXACT declared event vocabulary verbatim
45767
+ * (NO normalization). Two shapes:
45768
+ * - structured source (HA `event.*` entity) → `vocabulary` is the fixed
45769
+ * declared set (e.g. press_short, press_long, …).
45770
+ * - generic/legacy source (HA bus events, e.g. zha_event) → `vocabulary`
45771
+ * is [] (open) and `lastEvent.data` carries the complex payload.
45772
+ * `vocabulary` (D1044) names each event for a person and types it for a
45773
+ * rule; `eventTypes` is its list of native tokens, kept for older readers.
45774
+ * A fire names the part of the device it came from in `sourceId` when the
45775
+ * vocabulary declares sources (a remote's buttons).
45776
+ * `seq` is monotonic so two identical events still produce distinct slice writes.
45777
+ */
45778
+ var EventFireSchema = object({
45779
+ deviceId: number$1(),
45780
+ eventType: string(),
45781
+ sourceId: string().optional(),
45782
+ data: record(string(), unknown()).nullable(),
45783
+ timestamp: number$1().meta(readingMeta("none")),
45784
+ seq: number$1().meta(readingMeta("count"))
45785
+ });
45786
+ var EventEmitterStatusSchema = object({
45787
+ eventTypes: array(string()),
45788
+ /**
45789
+ * Required of a provider by the type. A status written before the field
45790
+ * existed still parses, as an empty vocabulary: read it through
45791
+ * `readEventVocabulary`, which then answers with the plain tokens.
45792
+ */
45793
+ vocabulary: array(EventVocabularyEntrySchema).default([]),
45794
+ lastEvent: EventFireSchema.nullable(),
45795
+ eventCountSinceStart: number$1().meta(readingMeta("count"))
45796
+ });
45797
+ var eventEmitterCapability = {
45798
+ name: "event-emitter",
45799
+ scope: "device",
45800
+ deviceNative: true,
45801
+ mode: "singleton",
45802
+ deviceTypes: [DeviceType.EventEmitter],
45803
+ methods: {},
45804
+ events: { onEvent: { data: EventFireSchema } },
45805
+ status: {
45806
+ schema: EventEmitterStatusSchema,
45807
+ kind: "push"
45808
+ },
45809
+ runtimeState: EventEmitterStatusSchema,
45810
+ /**
45811
+ * Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
45812
+ *
45813
+ * See `RuntimeStateDurability`. Enforced by
45814
+ * `scripts/check-runtime-state-durability.ts`.
45815
+ */
45816
+ durability: "session",
45817
+ /**
45818
+ * History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
45819
+ */
45820
+ history: "none"
45821
+ };
45788
45822
  /**
45789
45823
  * Actions — named, runnable things an operator (or an automation) can start,
45790
45824
  * declared as LIST ENTRIES, never as devices.
@@ -45964,6 +45998,177 @@ var UnitWarningSchema = object({
45964
45998
  assumedUnit: string(),
45965
45999
  message: string()
45966
46000
  });
46001
+ /**
46002
+ * `device-export` — collection cap for addons that export camstack
46003
+ * devices to external ecosystems (HomeAssistant via MQTT discovery,
46004
+ * HomeKit/HAP, Alexa Smart Home, …).
46005
+ *
46006
+ * No `ecosystem` enum — the addon id identifies the export target.
46007
+ * Each addon owns its mapping logic in its own settings UI. The cap
46008
+ * exposes only the COMMON surface: link state, supported device
46009
+ * kinds (so the UI can filter the device picker), and per-device
46010
+ * expose/unexpose.
46011
+ *
46012
+ * INSTANCES. One export addon can serve several independent targets (a
46013
+ * HomeKit bridge, a Home Assistant broker). `listInstances` names them.
46014
+ * `instanceId` on a READ is optional: absent = the whole addon (linked if any
46015
+ * enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
46016
+ * default instance's); present = that instance; an id the provider does not
46017
+ * serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
46018
+ * never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
46019
+ * change always names the target it changes. A single-instance provider
46020
+ * accepts an absent id on a read or its own id, and refuses any other.
46021
+ */
46022
+ /** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
46023
+ var InstanceIdSchema$1 = string().min(1);
46024
+ var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
46025
+ /** One independent export target of an addon. */
46026
+ var ExportInstanceSchema = object({
46027
+ instanceId: InstanceIdSchema$1,
46028
+ name: string(),
46029
+ enabled: boolean(),
46030
+ kind: string().optional(),
46031
+ /**
46032
+ * What this instance's platform accepts as a device name, in one operator-facing sentence
46033
+ * (D1049). The derived "Name on <instance>" field shows it; absent = the field says nothing
46034
+ * about the rule.
46035
+ */
46036
+ nameRule: string().optional()
46037
+ });
46038
+ var LinkStateSchema = _enum([
46039
+ "unlinked",
46040
+ "linked",
46041
+ "error"
46042
+ ]);
46043
+ /**
46044
+ * A single label/value row surfaced in the export panel's "Setup"
46045
+ * section. Rendered with a copy-to-clipboard button. `secret: true`
46046
+ * rows are masked by default with a reveal toggle (client ids,
46047
+ * secrets, …).
46048
+ */
46049
+ var ExportSetupFieldSchema = object({
46050
+ label: string(),
46051
+ value: string(),
46052
+ /** Mask the value by default + render a reveal toggle (client id, secrets). */
46053
+ secret: boolean().optional()
46054
+ });
46055
+ /**
46056
+ * Generic, addon-agnostic pairing/account block. Any export addon can
46057
+ * surface a scannable QR, a set of copyable label/value rows, and a
46058
+ * free-form operator note — the `DeviceExportPanel` renders whatever
46059
+ * the provider supplies and skips the section entirely when `setup`
46060
+ * is unset.
46061
+ */
46062
+ var ExportSetupSchema = object({
46063
+ /** A string to render as a scannable QR — HAP `X-HM://…` URI, a pairing URL, etc. Omitted when there's nothing to scan. */
46064
+ qr: string().optional(),
46065
+ /** Label/value rows shown with a copy button (HAP setup code, OAuth URLs, client id, linked-account count, …). */
46066
+ fields: array(ExportSetupFieldSchema).readonly().optional(),
46067
+ /** Free-form operator instructions rendered above the fields. */
46068
+ note: string().optional()
46069
+ });
46070
+ var DeviceExportStatusSchema = object({
46071
+ linkState: LinkStateSchema,
46072
+ exposedDeviceCount: number$1(),
46073
+ error: string().optional(),
46074
+ /**
46075
+ * Optional pairing/account info the panel renders in a generic
46076
+ * "Setup" section. Addon-agnostic — the addon id identifies the
46077
+ * export target, never an `ecosystem` key here.
46078
+ */
46079
+ setup: ExportSetupSchema.optional()
46080
+ });
46081
+ var DeviceKindSchema = string();
46082
+ /**
46083
+ * EXPOSED NAME. What one instance calls a device on its platform, when it should not be the
46084
+ * device's own name. The exporter owns it: it is stored per instance in the exporter's per-device
46085
+ * store (D865), validated by the exporter against its platform's naming rules, and never reaches
46086
+ * the exported id — a rename updates the SAME device on the platform, never a second one.
46087
+ * Blank or `null` = the device's own name. A name the platform would refuse is refused with
46088
+ * `ExportNameRefusedError` (`export-name-refused`), whose message says why.
46089
+ */
46090
+ var ExposedDeviceSchema = object({
46091
+ deviceId: string(),
46092
+ exposedAs: string().optional(),
46093
+ /** The operator's name for this device on this instance; absent = the device's own name. */
46094
+ exposedName: string().optional(),
46095
+ capabilities: array(string()).optional()
46096
+ });
46097
+ var ExposeInputSchema = object({
46098
+ instanceId: InstanceIdSchema$1,
46099
+ deviceId: string(),
46100
+ capabilities: array(string()).optional(),
46101
+ /** Set the exposed name in the same call; absent = leave it as it is, blank/null = clear it. */
46102
+ exposedName: string().nullable().optional()
46103
+ });
46104
+ var SetExposedNameInputSchema = object({
46105
+ instanceId: InstanceIdSchema$1,
46106
+ deviceId: string(),
46107
+ /** Blank or null = the device's own name. */
46108
+ name: string().nullable(),
46109
+ /**
46110
+ * Validate only: refuse exactly as a real call would, write nothing. A caller saving several
46111
+ * names at once checks every one first, so one refusal writes none (D1049).
46112
+ */
46113
+ dryRun: boolean().optional()
46114
+ });
46115
+ var UnexposeInputSchema = object({
46116
+ instanceId: InstanceIdSchema$1,
46117
+ deviceId: string()
46118
+ });
46119
+ /**
46120
+ * What an exporter may expose besides devices (D968). An action and a
46121
+ * notification target are LIST ENTRIES, never devices (D954): they enter an
46122
+ * exporter's own selection, not the device registry.
46123
+ */
46124
+ var ExportEntityKindSchema = _enum([
46125
+ "device",
46126
+ "action",
46127
+ "notifier"
46128
+ ]);
46129
+ var ExportEntityRefSchema = discriminatedUnion("kind", [
46130
+ object({
46131
+ kind: literal("device"),
46132
+ deviceId: string()
46133
+ }),
46134
+ object({
46135
+ kind: literal("action"),
46136
+ actionId: string().min(1)
46137
+ }),
46138
+ object({
46139
+ kind: literal("notifier"),
46140
+ targetId: string().min(1)
46141
+ })
46142
+ ]);
46143
+ var ExposedEntitySchema = object({
46144
+ entity: ExportEntityRefSchema,
46145
+ exposedAs: string().optional()
46146
+ });
46147
+ var ExposeEntityInputSchema = object({
46148
+ instanceId: InstanceIdSchema$1,
46149
+ entity: ExportEntityRefSchema
46150
+ });
46151
+ var UnexposeEntityInputSchema = object({
46152
+ instanceId: InstanceIdSchema$1,
46153
+ entity: ExportEntityRefSchema
46154
+ });
46155
+ method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
46156
+ kind: "mutation",
46157
+ auth: "admin"
46158
+ }), method(UnexposeInputSchema, _void(), {
46159
+ kind: "mutation",
46160
+ auth: "admin"
46161
+ }), method(SetExposedNameInputSchema, _void(), {
46162
+ kind: "mutation",
46163
+ auth: "admin"
46164
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
46165
+ kind: "mutation",
46166
+ auth: "admin"
46167
+ }), optionalMethod(UnexposeEntityInputSchema, _void(), {
46168
+ kind: "mutation",
46169
+ auth: "admin"
46170
+ });
46171
+ record(string(), string());
45967
46172
  _enum([
45968
46173
  "automation",
45969
46174
  "action",
@@ -51167,11 +51372,13 @@ var IrCodecError = class extends Error {
51167
51372
  this.code = code;
51168
51373
  }
51169
51374
  };
51375
+ /** Maximum number of mark/space entries one pulse train may carry. */
51376
+ var IR_TIMINGS_MAX = 1024;
51170
51377
  /**
51171
51378
  * A pulse train in signed microseconds: mark (+) first, then space (-), strictly
51172
51379
  * alternating, no zeros, 2..1024 entries.
51173
51380
  */
51174
- var IrTimingsSchema = array(number$1().int()).min(2).max(1024).superRefine((timings, ctx) => {
51381
+ var IrTimingsSchema = array(number$1().int()).min(2).max(IR_TIMINGS_MAX).superRefine((timings, ctx) => {
51175
51382
  timings.forEach((value, index) => {
51176
51383
  const expectMark = index % 2 === 0;
51177
51384
  if (value === 0 || value > 0 !== expectMark) ctx.addIssue({
@@ -51218,15 +51425,186 @@ function decodePronto(code) {
51218
51425
  carrierHz: Math.round(1e6 / unitUs)
51219
51426
  };
51220
51427
  }
51221
- /** What a remote command stores: a raw pulse train, or a learned Pronto code. */
51222
- var IrPayloadSchema = discriminatedUnion("kind", [object({
51223
- kind: literal("raw"),
51224
- timings: array(number$1().int()),
51225
- settings: TransmitSettingsSchema.optional()
51226
- }), object({
51227
- kind: literal("pronto"),
51228
- code: string()
51229
- })]);
51428
+ /**
51429
+ * FastLZ level 1: a byte-oriented LZ77 stream. A control byte below 32 starts a
51430
+ * literal run of `ctrl + 1` bytes; otherwise it is a back-reference whose length
51431
+ * is `ctrl >> 5` (extended by one byte when 7) and whose distance is split
51432
+ * between the low 5 bits and the next byte.
51433
+ */
51434
+ var MAX_DISTANCE = 8192;
51435
+ var MAX_LITERAL_RUN = 32;
51436
+ var MIN_MATCH = 3;
51437
+ var MAX_MATCH = 264;
51438
+ /**
51439
+ * Most bytes a decoded IR code may hold: two bytes per timing. A level-1 stream
51440
+ * can expand ~130x, so the decoder checks this bound before every write.
51441
+ */
51442
+ var IR_DECOMPRESSED_MAX = IR_TIMINGS_MAX * 2;
51443
+ function fastlzDecompress(input) {
51444
+ const out = [];
51445
+ let ip = 0;
51446
+ const room = (count) => {
51447
+ if (out.length + count > IR_DECOMPRESSED_MAX) throw new IrCodecError("tuya-malformed", "decompressed code is larger than any IR train");
51448
+ };
51449
+ const next = () => {
51450
+ const byte = input[ip++];
51451
+ if (byte === void 0) throw new IrCodecError("tuya-malformed", "compressed stream is truncated");
51452
+ return byte;
51453
+ };
51454
+ while (ip < input.length) {
51455
+ const ctrl = next();
51456
+ if (ctrl < 32) {
51457
+ room(ctrl + 1);
51458
+ for (let i = 0; i <= ctrl; i++) out.push(next());
51459
+ continue;
51460
+ }
51461
+ let len = (ctrl >> 5) - 1;
51462
+ if (len === 6) len += next();
51463
+ const distance = ((ctrl & 31) << 8) + next() + 1;
51464
+ const start = out.length - distance;
51465
+ if (start < 0) throw new IrCodecError("tuya-malformed", "back-reference points before the stream");
51466
+ room(len + 3);
51467
+ for (let i = 0; i < len + 3; i++) {
51468
+ const byte = out[start + i];
51469
+ if (byte === void 0) throw new IrCodecError("tuya-malformed", "back-reference is invalid");
51470
+ out.push(byte);
51471
+ }
51472
+ }
51473
+ return Uint8Array.from(out);
51474
+ }
51475
+ function fastlzCompress(input) {
51476
+ const out = [];
51477
+ const lastSeen = /* @__PURE__ */ new Map();
51478
+ let literalStart = 0;
51479
+ const flushLiterals = (end) => {
51480
+ for (let from = literalStart; from < end; from += MAX_LITERAL_RUN) {
51481
+ const to = Math.min(from + MAX_LITERAL_RUN, end);
51482
+ out.push(to - from - 1);
51483
+ for (let i = from; i < to; i++) out.push(input[i] ?? 0);
51484
+ }
51485
+ };
51486
+ let pos = 0;
51487
+ while (pos < input.length) {
51488
+ let matchLen = 0;
51489
+ let matchDistance = 0;
51490
+ if (pos + MIN_MATCH <= input.length) {
51491
+ const key = (input[pos] ?? 0) << 16 | (input[pos + 1] ?? 0) << 8 | (input[pos + 2] ?? 0);
51492
+ const candidate = lastSeen.get(key);
51493
+ lastSeen.set(key, pos);
51494
+ if (candidate !== void 0 && pos - candidate <= MAX_DISTANCE) {
51495
+ let len = 0;
51496
+ while (pos + len < input.length && len < MAX_MATCH && input[candidate + len] === input[pos + len]) len++;
51497
+ if (len >= MIN_MATCH) {
51498
+ matchLen = len;
51499
+ matchDistance = pos - candidate - 1;
51500
+ }
51501
+ }
51502
+ }
51503
+ if (matchLen === 0) {
51504
+ pos++;
51505
+ continue;
51506
+ }
51507
+ flushLiterals(pos);
51508
+ const enc = matchLen - 2;
51509
+ if (enc < 7) out.push(enc << 5 | matchDistance >> 8);
51510
+ else out.push(224 | matchDistance >> 8, enc - 7);
51511
+ out.push(matchDistance & 255);
51512
+ pos += matchLen;
51513
+ literalStart = pos;
51514
+ }
51515
+ flushLiterals(input.length);
51516
+ return Uint8Array.from(out);
51517
+ }
51518
+ var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
51519
+ var MAX_DURATION_US = 65535;
51520
+ function toBase64(bytes) {
51521
+ let out = "";
51522
+ for (let i = 0; i < bytes.length; i += 3) {
51523
+ const a = bytes[i] ?? 0;
51524
+ const b = bytes[i + 1] ?? 0;
51525
+ const c = bytes[i + 2] ?? 0;
51526
+ const word = a << 16 | b << 8 | c;
51527
+ out += ALPHABET[word >> 18 & 63];
51528
+ out += ALPHABET[word >> 12 & 63];
51529
+ out += i + 1 < bytes.length ? ALPHABET[word >> 6 & 63] : "=";
51530
+ out += i + 2 < bytes.length ? ALPHABET[word & 63] : "=";
51531
+ }
51532
+ return out;
51533
+ }
51534
+ function fromBase64(text) {
51535
+ const clean = text.trim().replace(/=+$/, "");
51536
+ if (clean.length === 0 || clean.length % 4 === 1) throw new IrCodecError("tuya-malformed", "not a base64 string");
51537
+ const out = [];
51538
+ let bits = 0;
51539
+ let acc = 0;
51540
+ for (const ch of clean) {
51541
+ const value = ALPHABET.indexOf(ch);
51542
+ if (value < 0) throw new IrCodecError("tuya-malformed", "not a base64 string");
51543
+ acc = acc << 6 | value;
51544
+ bits += 6;
51545
+ if (bits >= 8) {
51546
+ bits -= 8;
51547
+ out.push(acc >> bits & 255);
51548
+ }
51549
+ }
51550
+ return Uint8Array.from(out);
51551
+ }
51552
+ /** Signed pulse train to a Tuya IR code: base64(FastLZ(uint16 LE durations)). */
51553
+ function encodeTuya(timings) {
51554
+ const bytes = new Uint8Array(timings.length * 2);
51555
+ timings.forEach((value, index) => {
51556
+ const duration = Math.abs(value);
51557
+ if (duration === 0 || duration > MAX_DURATION_US) throw new IrCodecError("invalid-timings", `timing ${index} does not fit 16 bits`);
51558
+ bytes[index * 2] = duration & 255;
51559
+ bytes[index * 2 + 1] = duration >> 8;
51560
+ });
51561
+ return toBase64(fastlzCompress(bytes));
51562
+ }
51563
+ /** A Tuya IR code to a signed pulse train (mark first). Throws `IrCodecError`. */
51564
+ function decodeTuya(code) {
51565
+ const bytes = fastlzDecompress(fromBase64(code));
51566
+ if (bytes.length === 0 || bytes.length % 2 !== 0) throw new IrCodecError("tuya-malformed", "payload is not a list of 16-bit durations");
51567
+ const timings = [];
51568
+ for (let i = 0; i < bytes.length; i += 2) {
51569
+ const duration = (bytes[i] ?? 0) | (bytes[i + 1] ?? 0) << 8;
51570
+ timings.push(i / 2 % 2 === 0 ? duration : -duration);
51571
+ }
51572
+ const checked = IrTimingsSchema.safeParse(timings);
51573
+ if (!checked.success) throw new IrCodecError("tuya-malformed", checked.error.message);
51574
+ return checked.data;
51575
+ }
51576
+ /** Device-native IR code formats a transmitter can replay verbatim. */
51577
+ var IrNativeFormatSchema = _enum(["tuya"]);
51578
+ /**
51579
+ * Longest native code accepted: base64 of the worst-case compressed stream
51580
+ * (every decoded byte as a literal, plus one control byte per 32), with margin.
51581
+ */
51582
+ var IR_NATIVE_CODE_MAX_CHARS = Math.ceil((IR_DECOMPRESSED_MAX + Math.ceil(IR_DECOMPRESSED_MAX / 32)) / 3) * 4 + 256;
51583
+ var IrNativeCodeSchema = object({
51584
+ format: IrNativeFormatSchema,
51585
+ code: string().min(1).max(IR_NATIVE_CODE_MAX_CHARS)
51586
+ });
51587
+ var IR_NATIVE_CODECS = { tuya: {
51588
+ encode: encodeTuya,
51589
+ decode: decodeTuya
51590
+ } };
51591
+ /** What a remote command stores: a raw pulse train, a learned Pronto code, or a device-native code. */
51592
+ var IrPayloadSchema = discriminatedUnion("kind", [
51593
+ object({
51594
+ kind: literal("raw"),
51595
+ timings: array(number$1().int()),
51596
+ settings: TransmitSettingsSchema.optional()
51597
+ }),
51598
+ object({
51599
+ kind: literal("pronto"),
51600
+ code: string()
51601
+ }),
51602
+ object({
51603
+ kind: literal("native"),
51604
+ format: IrNativeFormatSchema,
51605
+ code: string()
51606
+ })
51607
+ ]);
51230
51608
  /** Resolve a payload to what `ir-transmitter.send` takes. Throws `IrCodecError`. */
51231
51609
  function toTimings(payload) {
51232
51610
  if (payload.kind === "pronto") {
@@ -51236,6 +51614,10 @@ function toTimings(payload) {
51236
51614
  settings: { carrierHz: decoded.carrierHz }
51237
51615
  };
51238
51616
  }
51617
+ if (payload.kind === "native") return {
51618
+ timings: IR_NATIVE_CODECS[payload.format].decode(payload.code),
51619
+ settings: {}
51620
+ };
51239
51621
  const checked = IrTimingsSchema.safeParse(payload.timings);
51240
51622
  if (!checked.success) throw new IrCodecError("invalid-timings", checked.error.message);
51241
51623
  return {
@@ -53568,144 +53950,6 @@ method(object({
53568
53950
  kind: "mutation",
53569
53951
  auth: "admin"
53570
53952
  });
53571
- /**
53572
- * `device-export` — collection cap for addons that export camstack
53573
- * devices to external ecosystems (HomeAssistant via MQTT discovery,
53574
- * HomeKit/HAP, Alexa Smart Home, …).
53575
- *
53576
- * No `ecosystem` enum — the addon id identifies the export target.
53577
- * Each addon owns its mapping logic in its own settings UI. The cap
53578
- * exposes only the COMMON surface: link state, supported device
53579
- * kinds (so the UI can filter the device picker), and per-device
53580
- * expose/unexpose.
53581
- *
53582
- * INSTANCES. One export addon can serve several independent targets (a
53583
- * HomeKit bridge, a Home Assistant broker). `listInstances` names them.
53584
- * `instanceId` on a READ is optional: absent = the whole addon (linked if any
53585
- * enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
53586
- * default instance's); present = that instance; an id the provider does not
53587
- * serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
53588
- * never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
53589
- * change always names the target it changes. A single-instance provider
53590
- * accepts an absent id on a read or its own id, and refuses any other.
53591
- */
53592
- /** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
53593
- var InstanceIdSchema$1 = string().min(1);
53594
- var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
53595
- /** One independent export target of an addon. */
53596
- var ExportInstanceSchema = object({
53597
- instanceId: InstanceIdSchema$1,
53598
- name: string(),
53599
- enabled: boolean(),
53600
- kind: string().optional()
53601
- });
53602
- var LinkStateSchema = _enum([
53603
- "unlinked",
53604
- "linked",
53605
- "error"
53606
- ]);
53607
- /**
53608
- * A single label/value row surfaced in the export panel's "Setup"
53609
- * section. Rendered with a copy-to-clipboard button. `secret: true`
53610
- * rows are masked by default with a reveal toggle (client ids,
53611
- * secrets, …).
53612
- */
53613
- var ExportSetupFieldSchema = object({
53614
- label: string(),
53615
- value: string(),
53616
- /** Mask the value by default + render a reveal toggle (client id, secrets). */
53617
- secret: boolean().optional()
53618
- });
53619
- /**
53620
- * Generic, addon-agnostic pairing/account block. Any export addon can
53621
- * surface a scannable QR, a set of copyable label/value rows, and a
53622
- * free-form operator note — the `DeviceExportPanel` renders whatever
53623
- * the provider supplies and skips the section entirely when `setup`
53624
- * is unset.
53625
- */
53626
- var ExportSetupSchema = object({
53627
- /** A string to render as a scannable QR — HAP `X-HM://…` URI, a pairing URL, etc. Omitted when there's nothing to scan. */
53628
- qr: string().optional(),
53629
- /** Label/value rows shown with a copy button (HAP setup code, OAuth URLs, client id, linked-account count, …). */
53630
- fields: array(ExportSetupFieldSchema).readonly().optional(),
53631
- /** Free-form operator instructions rendered above the fields. */
53632
- note: string().optional()
53633
- });
53634
- var DeviceExportStatusSchema = object({
53635
- linkState: LinkStateSchema,
53636
- exposedDeviceCount: number$1(),
53637
- error: string().optional(),
53638
- /**
53639
- * Optional pairing/account info the panel renders in a generic
53640
- * "Setup" section. Addon-agnostic — the addon id identifies the
53641
- * export target, never an `ecosystem` key here.
53642
- */
53643
- setup: ExportSetupSchema.optional()
53644
- });
53645
- var DeviceKindSchema = string();
53646
- var ExposedDeviceSchema = object({
53647
- deviceId: string(),
53648
- exposedAs: string().optional(),
53649
- capabilities: array(string()).optional()
53650
- });
53651
- var ExposeInputSchema = object({
53652
- instanceId: InstanceIdSchema$1,
53653
- deviceId: string(),
53654
- capabilities: array(string()).optional()
53655
- });
53656
- var UnexposeInputSchema = object({
53657
- instanceId: InstanceIdSchema$1,
53658
- deviceId: string()
53659
- });
53660
- /**
53661
- * What an exporter may expose besides devices (D968). An action and a
53662
- * notification target are LIST ENTRIES, never devices (D954): they enter an
53663
- * exporter's own selection, not the device registry.
53664
- */
53665
- var ExportEntityKindSchema = _enum([
53666
- "device",
53667
- "action",
53668
- "notifier"
53669
- ]);
53670
- var ExportEntityRefSchema = discriminatedUnion("kind", [
53671
- object({
53672
- kind: literal("device"),
53673
- deviceId: string()
53674
- }),
53675
- object({
53676
- kind: literal("action"),
53677
- actionId: string().min(1)
53678
- }),
53679
- object({
53680
- kind: literal("notifier"),
53681
- targetId: string().min(1)
53682
- })
53683
- ]);
53684
- var ExposedEntitySchema = object({
53685
- entity: ExportEntityRefSchema,
53686
- exposedAs: string().optional()
53687
- });
53688
- var ExposeEntityInputSchema = object({
53689
- instanceId: InstanceIdSchema$1,
53690
- entity: ExportEntityRefSchema
53691
- });
53692
- var UnexposeEntityInputSchema = object({
53693
- instanceId: InstanceIdSchema$1,
53694
- entity: ExportEntityRefSchema
53695
- });
53696
- method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
53697
- kind: "mutation",
53698
- auth: "admin"
53699
- }), method(UnexposeInputSchema, _void(), {
53700
- kind: "mutation",
53701
- auth: "admin"
53702
- }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
53703
- kind: "mutation",
53704
- auth: "admin"
53705
- }), optionalMethod(UnexposeEntityInputSchema, _void(), {
53706
- kind: "mutation",
53707
- auth: "admin"
53708
- });
53709
53953
  var DeviceAliasSchema = string().max(64).regex(/^[a-z][a-z0-9_]*$/);
53710
53954
  var DeviceTemplateSchema = string().min(1).max(4096);
53711
53955
  /** One value a caller hands the renderer for `thisDevice`. */
@@ -58560,6 +58804,128 @@ var StateLookSchema = object({
58560
58804
  background: _enum(HERO_BACKGROUNDS),
58561
58805
  animation: HeroAnimationSchema
58562
58806
  });
58807
+ /** A string in both languages, as the cap vocabulary writes it (D920): no `t()` in the model. */
58808
+ var HeroTextSchema = object({
58809
+ en: string(),
58810
+ it: string()
58811
+ });
58812
+ /**
58813
+ * The ONE measurement a simple sensor shows (card wave, phase 1).
58814
+ *
58815
+ * A sensor that measures a single quantity (a temperature, a humidity, a power,
58816
+ * a contact, a text state) is drawn as that reading, once, big, inside a scene
58817
+ * whose motion says what the number means: heat rising when hot, flakes when
58818
+ * cold, a wire flowing at the speed of the power, a ring while a window is
58819
+ * open. Everything the card draws is decided here; a client only paints it.
58820
+ */
58821
+ var HERO_MEASUREMENT_QUANTITIES = [
58822
+ "temperature",
58823
+ "humidity",
58824
+ "power",
58825
+ "contact",
58826
+ "text"
58827
+ ];
58828
+ /** How a temperature reads to a person; drives the heat or the cold scene. */
58829
+ var HERO_MEASUREMENT_BANDS = [
58830
+ "hot",
58831
+ "mild",
58832
+ "cold"
58833
+ ];
58834
+ /** `updated` = the value was last read then ("2 min ago"); `changed` = it has held since then ("for 12 min"). */
58835
+ var HERO_MEASUREMENT_SINCE_KINDS = ["updated", "changed"];
58836
+ var HeroMeasurementReadingSchema = object({
58837
+ value: number$1(),
58838
+ quantity: ReadingQuantitySchema
58839
+ });
58840
+ var HeroMeasurementSchema = object({
58841
+ quantity: _enum(HERO_MEASUREMENT_QUANTITIES),
58842
+ reading: HeroMeasurementReadingSchema.nullable(),
58843
+ icon: IconKeySchema.nullable(),
58844
+ value: HeroTextSchema,
58845
+ unit: string().nullable(),
58846
+ band: _enum(HERO_MEASUREMENT_BANDS).nullable(),
58847
+ level: number$1().min(0).max(1).nullable(),
58848
+ word: HeroTextSchema,
58849
+ tone: _enum(HERO_TONES),
58850
+ since: number$1().nullable(),
58851
+ sinceKind: _enum(HERO_MEASUREMENT_SINCE_KINDS),
58852
+ historyKey: string().nullable()
58853
+ });
58854
+ var same$2 = (text) => t(text, text);
58855
+ var clamp01$1 = (n) => Math.min(1, Math.max(0, n));
58856
+ var trimmed = (n, digits) => String(Number(n.toFixed(digits)));
58857
+ function temperatureBand(celsius) {
58858
+ if (celsius < 12) return "cold";
58859
+ if (celsius > 28) return "hot";
58860
+ return "mild";
58861
+ }
58862
+ var BAND_WORD = {
58863
+ hot: t("Hot", "Caldo"),
58864
+ mild: t("Normal", "Normale"),
58865
+ cold: t("Cold", "Freddo")
58866
+ };
58867
+ var BAND_TONE = {
58868
+ hot: "warning",
58869
+ mild: "muted",
58870
+ cold: "info"
58871
+ };
58872
+ function temperatureMeasurement(celsius, since) {
58873
+ const band = temperatureBand(celsius);
58874
+ return {
58875
+ quantity: "temperature",
58876
+ reading: {
58877
+ value: celsius,
58878
+ quantity: "temperature"
58879
+ },
58880
+ icon: "thermometer",
58881
+ value: same$2(trimmed(celsius, 1)),
58882
+ unit: "°C",
58883
+ band,
58884
+ level: null,
58885
+ word: BAND_WORD[band],
58886
+ tone: BAND_TONE[band],
58887
+ since,
58888
+ sinceKind: "updated",
58889
+ historyKey: "cap:temperature-sensor.temperature"
58890
+ };
58891
+ }
58892
+ function humidityMeasurement(percent, since) {
58893
+ const dry = percent < 30;
58894
+ const humid = percent > 70;
58895
+ return {
58896
+ quantity: "humidity",
58897
+ reading: {
58898
+ value: percent,
58899
+ quantity: "humidity"
58900
+ },
58901
+ icon: "droplets",
58902
+ value: same$2(String(Math.round(percent))),
58903
+ unit: "%",
58904
+ band: null,
58905
+ level: clamp01$1(percent / 100),
58906
+ word: dry ? t("Dry", "Secco") : humid ? t("Humid", "Umido") : t("Normal", "Normale"),
58907
+ tone: dry || humid ? "warning" : "info",
58908
+ since,
58909
+ sinceKind: "updated",
58910
+ historyKey: "cap:humidity-sensor.humidity"
58911
+ };
58912
+ }
58913
+ function textMeasurement(text, since) {
58914
+ return {
58915
+ quantity: "text",
58916
+ reading: null,
58917
+ icon: null,
58918
+ value: same$2(text),
58919
+ unit: null,
58920
+ band: null,
58921
+ level: null,
58922
+ word: t("Changed", "Cambiato"),
58923
+ tone: "muted",
58924
+ since,
58925
+ sinceKind: "updated",
58926
+ historyKey: null
58927
+ };
58928
+ }
58563
58929
  /** The scene as the model carries it. `null` = the device did not say (never a guess). */
58564
58930
  var HeroSceneSchema = object({
58565
58931
  id: _enum([
@@ -58611,11 +58977,6 @@ var HeroSceneSchema = object({
58611
58977
  });
58612
58978
  DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
58613
58979
  DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
58614
- /** A string in both languages, as the cap vocabulary writes it (D920): no `t()` in the model. */
58615
- var HeroTextSchema = object({
58616
- en: string(),
58617
- it: string()
58618
- });
58619
58980
  var HeroRowIdSchema = _enum([
58620
58981
  "progress",
58621
58982
  "run-for",
@@ -58721,6 +59082,25 @@ var HeroControlSchema = discriminatedUnion("kind", [
58721
59082
  optionLabels: record(string(), HeroTextSchema).optional(),
58722
59083
  call: HeroCallSchema,
58723
59084
  valueArg: string()
59085
+ }),
59086
+ object({
59087
+ kind: literal("number"),
59088
+ id: string(),
59089
+ label: HeroTextSchema,
59090
+ value: number$1().nullable(),
59091
+ /** `null` = any number; the device rounds what it does not accept. */
59092
+ step: number$1().nullable(),
59093
+ unit: string(),
59094
+ call: HeroCallSchema,
59095
+ valueArg: string()
59096
+ }),
59097
+ object({
59098
+ kind: literal("text"),
59099
+ id: string(),
59100
+ label: HeroTextSchema,
59101
+ value: string().nullable(),
59102
+ call: HeroCallSchema,
59103
+ valueArg: string()
58724
59104
  })
58725
59105
  ]);
58726
59106
  /** Declared by its interface: a `z.infer` of one more icon union pushes the cap's inferred type past what the compiler can print. */
@@ -58922,6 +59302,11 @@ var HeroModelSchema = object({
58922
59302
  timedRun: HeroTimedRunSlotSchema.nullable(),
58923
59303
  powerSupply: HeroPowerSupplySlotSchema.nullable(),
58924
59304
  rows: array(HeroRowSchema),
59305
+ /**
59306
+ * The ONE measurement of a simple sensor, drawn once inside the scene in place of
59307
+ * the caption, tiles and value chips. Absent = the card draws as before.
59308
+ */
59309
+ measurement: HeroMeasurementSchema.optional(),
58925
59310
  /** Ids of parts that actuate the device, hidden from a session that may not act on it. */
58926
59311
  gated: array(string()),
58927
59312
  provenance: array(HeroProvenanceSchema)
@@ -58993,8 +59378,23 @@ var HeroRowModelSchema = object({
58993
59378
  * the call. Absent or empty = `primary` alone.
58994
59379
  */
58995
59380
  extras: array(HeroControlSchema).max(2).optional(),
58996
- trailing: HeroFactSchema.nullable()
59381
+ trailing: HeroFactSchema.nullable(),
59382
+ /**
59383
+ * The simple sensor's one reading (the hero's `measurement`): the row draws its value
59384
+ * at the right, the unit small, the quantity's icon in the mini-scene. Absent = an
59385
+ * older server or a device that is not a simple sensor: draw `trailing`.
59386
+ */
59387
+ measurement: HeroMeasurementSchema.optional()
58997
59388
  });
59389
+ new Set([
59390
+ "temperature",
59391
+ "humidity",
59392
+ "pressure",
59393
+ "illuminance",
59394
+ "reading",
59395
+ "air-quality",
59396
+ "valve"
59397
+ ]);
58998
59398
  DeviceType.Climate;
58999
59399
  DeviceType.Climate, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer;
59000
59400
  /**
@@ -60083,13 +60483,32 @@ var IrSendResultSchema = object({
60083
60483
  var IrTransmitterInfoSchema = object({
60084
60484
  media: array(IrMediumSchema),
60085
60485
  settable: array(TransmitSettingKeySchema),
60086
- maxTimings: number$1().int().positive()
60486
+ maxTimings: number$1().int().positive(),
60487
+ /** The transmitter can learn a code (`learn`). */
60488
+ learn: boolean().default(false),
60489
+ /** Native code formats the transmitter replays verbatim (`send.native`). */
60490
+ formats: array(IrNativeFormatSchema).default([])
60491
+ });
60492
+ var IR_LEARN_TIMEOUT_DEFAULT_MS = 2e4;
60493
+ var IR_LEARN_TIMEOUT_MAX_MS = 6e4;
60494
+ var IrLearnResultSchema = object({
60495
+ /** The code in the device's own format, when it reports one. */
60496
+ native: IrNativeCodeSchema.nullable(),
60497
+ timings: IrTimingsSchema,
60498
+ carrierHz: number$1().int().positive().nullable()
60087
60499
  });
60088
60500
  DeviceType.Generic, method(object({
60089
60501
  deviceId: number$1().int().nonnegative(),
60090
- timings: IrTimingsSchema,
60502
+ timings: IrTimingsSchema.optional(),
60503
+ native: IrNativeCodeSchema.optional(),
60091
60504
  settings: TransmitSettingsSchema.optional()
60092
- }), IrSendResultSchema, {
60505
+ }).refine((input) => input.timings === void 0 !== (input.native === void 0), { message: "send takes exactly one of timings or native" }), IrSendResultSchema, {
60506
+ kind: "mutation",
60507
+ auth: "protected"
60508
+ }), method(object({
60509
+ deviceId: number$1().int().nonnegative(),
60510
+ timeoutMs: number$1().int().positive().max(IR_LEARN_TIMEOUT_MAX_MS).default(IR_LEARN_TIMEOUT_DEFAULT_MS)
60511
+ }), IrLearnResultSchema, {
60093
60512
  kind: "mutation",
60094
60513
  auth: "protected"
60095
60514
  }), method(object({ deviceId: number$1().int().nonnegative() }), IrTransmitterInfoSchema, {
@@ -69875,6 +70294,7 @@ var NcSystemEventKindSchema = _enum([
69875
70294
  "recording-degraded",
69876
70295
  "device-attention-required",
69877
70296
  "device-attention-resolved",
70297
+ "device-command",
69878
70298
  "storage-location-low",
69879
70299
  "storage-location-critical",
69880
70300
  "storage-location-ok",
@@ -69937,11 +70357,6 @@ var NC_ALARM_SYSTEM_EVENT_KINDS = [
69937
70357
  "alarm-sensor-bypass",
69938
70358
  "alarm-disarmed"
69939
70359
  ];
69940
- /**
69941
- * One coherent system-event condition. `kinds` is the required opt-in safety
69942
- * gate; the remaining lists are optional narrowing filters relevant to the
69943
- * selected kinds.
69944
- */
69945
70360
  var NcSystemEventConditionSchema = object({
69946
70361
  kinds: array(NcSystemEventKindSchema).min(1),
69947
70362
  deviceIds: array(number$1().int()).min(1).optional(),
@@ -69973,6 +70388,28 @@ var NcSystemEventConditionSchema = object({
69973
70388
  nodeIds: array(string().min(1)).min(1).optional(),
69974
70389
  packageNames: array(string().min(1)).min(1).optional(),
69975
70390
  /**
70391
+ * `device-command` only: the vocabulary ACTION IDS (D983, `powerSource.forceRestart`)
70392
+ * this rule speaks about - the same dictionary the automation action blocks
70393
+ * pick from. The hub refuses an id the action index does not know.
70394
+ */
70395
+ commandActions: array(string().min(1)).min(1).optional(),
70396
+ /** `device-command` only: how the command settled. A device's own change is not a command. */
70397
+ commandOutcomes: array(_enum([
70398
+ "confirmed",
70399
+ "already",
70400
+ "unconfirmed",
70401
+ "failed"
70402
+ ])).min(1).optional(),
70403
+ /** `device-command` only: who asked (`user`, `addon`, `action`, `scheduler`, `composition`, `node`). */
70404
+ commandActorKinds: array(_enum([
70405
+ "user",
70406
+ "addon",
70407
+ "action",
70408
+ "scheduler",
70409
+ "composition",
70410
+ "node"
70411
+ ])).min(1).optional(),
70412
+ /**
69976
70413
  * At or below this remaining-life %, a consumable is LOW for THIS rule
69977
70414
  * (D642). Absent = {@link NC_CONSUMABLE_LOW_PERCENT_DEFAULT}; at most
69978
70415
  * {@link NC_CONSUMABLE_LOW_PERCENT_MAX}, so every episode can recover. Scoped to the
@@ -70953,6 +71390,13 @@ var NcRuleInputSchema = object({
70953
71390
  */
70954
71391
  groupingSec: number$1().int().min(5).max(60).optional(),
70955
71392
  /**
71393
+ * Hold deliveries of this rule to one target and, when two or more arrive
71394
+ * before the window ends, send them as one summary. Absent = the kind's
71395
+ * default (`groupWithinSecOf`): 5 s for a system-event rule, 0 for every
71396
+ * other kind. `0` sends each delivery as it does today.
71397
+ */
71398
+ groupWithinSec: number$1().int().min(0).max(300).optional(),
71399
+ /**
70956
71400
  * ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
70957
71401
  * carries "Correct" / "Wrong" buttons (and, where the channel has room, a
70958
71402
  * "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
@@ -71389,7 +71833,12 @@ var NcHistoryEntrySchema = object({
71389
71833
  * Assistant export) must render as "no image right now", never as a
71390
71834
  * broken link.
71391
71835
  */
71392
- artifactIds: array(string().min(1)).optional()
71836
+ artifactIds: array(string().min(1)).optional(),
71837
+ /**
71838
+ * When this row was folded into a group-window summary, the outbox id of
71839
+ * that one notification. Absent on a row that was sent as itself.
71840
+ */
71841
+ groupedInto: string().min(1).optional()
71393
71842
  });
71394
71843
  /**
71395
71844
  * Query filter for `getHistory` (spec §4.2). Every field is a narrowing
@@ -77462,10 +77911,15 @@ getStatus: method(object({ deviceId: number$1() }), RecordingSignalStatusSchema)
77462
77911
  */
77463
77912
  history: "none"
77464
77913
  };
77465
- var RemoteBindingSchema = object({ action: AutomationCapStepBaseSchema.omit({
77466
- target: true,
77467
- actionId: true
77468
- }).extend({ deviceId: number$1().int().nonnegative() }) });
77914
+ var RemoteBindingSchema = object({ action: object({
77915
+ kind: literal("cap"),
77916
+ deviceId: number$1().int().nonnegative(),
77917
+ cap: string().min(1),
77918
+ method: string().min(1),
77919
+ args: record(string(), unknown()).optional(),
77920
+ /** The unit the person picked per arg (display only); `args` always hold the cap's own unit. */
77921
+ argUnits: record(string(), string().min(1)).optional()
77922
+ }) });
77469
77923
  var RemoteCustomButtonSchema = object({
77470
77924
  /** The slug after `custom:` in the role. */
77471
77925
  key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
@@ -78455,7 +78909,7 @@ var sceneMonitorCapability = {
78455
78909
  * instead of failing silently weeks later.
78456
78910
  *
78457
78911
  * Reachable from three surfaces on this one mutation: the scene card, a
78458
- * notification button (an `onTrigger` sequence with a `kind:'cap'` step —
78912
+ * notification button (an `onTrigger` sequence with a `kind:'command'` step —
78459
78913
  * no new Notification-Center code at all), and tRPC for scripts.
78460
78914
  */
78461
78915
  resetScene: method(object({
@@ -85448,6 +85902,13 @@ function contribution(cap, schema, build) {
85448
85902
  }
85449
85903
  };
85450
85904
  }
85905
+ /** The fragment, carrying the cap's single measurement when it has one. */
85906
+ function measured(fragment, measurement) {
85907
+ return measurement === void 0 ? fragment : {
85908
+ ...fragment,
85909
+ measurement
85910
+ };
85911
+ }
85451
85912
  function action(id, label, icon, cap, method, args = {}, emphasis = "normal", confirm = null) {
85452
85913
  return {
85453
85914
  id,
@@ -86722,10 +87183,26 @@ var controlContribution = contribution("control", ControlStatusSchema, (slice, c
86722
87183
  };
86723
87184
  });
86724
87185
  var CONTROL_LABEL = t("Value", "Valore");
86725
- /** A number with known bounds is a slider, a choice a picker; text and dates stay read-only. */
87186
+ /**
87187
+ * A number with known bounds is a slider, one without a number field, a choice a picker,
87188
+ * free text a text field; a date stays read-only.
87189
+ */
86726
87190
  function controlRows(slice) {
86727
87191
  if (slice.kind === "numeric" && typeof slice.value === "number") {
86728
- if (slice.min === void 0 || slice.max === void 0) return [];
87192
+ if (slice.min === void 0 || slice.max === void 0) return [row$1("control", "sliders-horizontal", null, [{
87193
+ kind: "number",
87194
+ id: "control.value",
87195
+ label: CONTROL_LABEL,
87196
+ value: slice.value,
87197
+ step: slice.step ?? null,
87198
+ unit: "",
87199
+ call: {
87200
+ cap: "control",
87201
+ method: "setValue",
87202
+ args: { control: { kind: "numeric" } }
87203
+ },
87204
+ valueArg: "control.value"
87205
+ }])];
86729
87206
  return [row$1("control", "sliders-horizontal", null, [{
86730
87207
  kind: "slider",
86731
87208
  id: "control.value",
@@ -86756,6 +87233,18 @@ function controlRows(slice) {
86756
87233
  },
86757
87234
  valueArg: "control.value"
86758
87235
  }])];
87236
+ if (slice.kind === "text") return [row$1("control", "sliders-horizontal", null, [{
87237
+ kind: "text",
87238
+ id: "control.value",
87239
+ label: CONTROL_LABEL,
87240
+ value: typeof slice.value === "string" ? slice.value : null,
87241
+ call: {
87242
+ cap: "control",
87243
+ method: "setValue",
87244
+ args: { control: { kind: "text" } }
87245
+ },
87246
+ valueArg: "control.value"
87247
+ }])];
86759
87248
  return [];
86760
87249
  }
86761
87250
  var SENSORS_FAMILY_CONTRIBUTIONS = {
@@ -87111,15 +87600,15 @@ function sensorFragment(value, id, icon, en, it, text) {
87111
87600
  tiles: { right: [tile$1(en, it, text)] }
87112
87601
  };
87113
87602
  }
87114
- var temperatureContribution = contribution("temperature-sensor", TemperatureSensorStatusSchema, (slice) => sensorFragment(slice?.temperature ?? null, "temperature", "thermometer", "Temperature", "Temperatura", formatDegrees(slice?.temperature ?? 0)));
87115
- var humidityContribution = contribution("humidity-sensor", HumiditySensorStatusSchema, (slice) => sensorFragment(slice?.humidity ?? null, "humidity", "droplets", "Humidity", "Umidità", `${Math.round(slice?.humidity ?? 0)}%`));
87603
+ var temperatureContribution = contribution("temperature-sensor", TemperatureSensorStatusSchema, (slice) => measured(sensorFragment(slice?.temperature ?? null, "temperature", "thermometer", "Temperature", "Temperatura", formatDegrees(slice?.temperature ?? 0)), slice?.temperature == null ? void 0 : temperatureMeasurement(slice.temperature, slice.lastFetchedAt)));
87604
+ var humidityContribution = contribution("humidity-sensor", HumiditySensorStatusSchema, (slice) => measured(sensorFragment(slice?.humidity ?? null, "humidity", "droplets", "Humidity", "Umidità", `${Math.round(slice?.humidity ?? 0)}%`), slice?.humidity == null ? void 0 : humidityMeasurement(slice.humidity, slice.lastFetchedAt)));
87116
87605
  var pressureContribution = contribution("pressure-sensor", PressureSensorStatusSchema, (slice) => sensorFragment(slice?.pressure ?? null, "pressure", "gauge", "Pressure", "Pressione", `${Math.round(slice?.pressure ?? 0)} hPa`));
87117
87606
  var illuminanceContribution = contribution("ambient-light-sensor", AmbientLightSensorStatusSchema, (slice) => sensorFragment(slice?.illuminance ?? null, "illuminance", "sun", "Light", "Luce", `${Math.round(slice?.illuminance ?? 0)} lx`));
87118
87607
  var numericContribution = contribution("numeric-sensor", NumericSensorStatusSchema, (slice) => sensorFragment(slice?.value ?? null, "reading", "gauge", "Value", "Valore", String(Number((slice?.value ?? 0).toFixed(2)))));
87119
87608
  var enumContribution = contribution("enum-sensor", EnumSensorStatusSchema, (slice) => {
87120
87609
  if (slice === void 0 || slice.value === "") return { reading: reading("sensor", "unknown") };
87121
87610
  if (slice.timestamp === true) return { reading: reading("sensor", "on") };
87122
- return sensorFragment(slice.value, "reading", "gauge", "State", "Stato", slice.value);
87611
+ return measured(sensorFragment(slice.value, "reading", "gauge", "State", "Stato", slice.value), textMeasurement(slice.value, slice.lastFetchedAt));
87123
87612
  });
87124
87613
  /** Index bands: the headline of an air-quality sensor is a word, not a number. */
87125
87614
  function airQualityWord(aqi) {
@@ -90015,6 +90504,13 @@ Object.freeze({
90015
90504
  access: "view",
90016
90505
  role: "admin"
90017
90506
  },
90507
+ "deviceExport.setExposedName": {
90508
+ capName: "device-export",
90509
+ capScope: "system",
90510
+ addonId: null,
90511
+ access: "create",
90512
+ role: "admin"
90513
+ },
90018
90514
  "deviceExport.unexposeDevice": {
90019
90515
  capName: "device-export",
90020
90516
  capScope: "system",
@@ -91954,6 +92450,13 @@ Object.freeze({
91954
92450
  access: "view",
91955
92451
  role: "viewer"
91956
92452
  },
92453
+ "irTransmitter.learn": {
92454
+ capName: "ir-transmitter",
92455
+ capScope: "device",
92456
+ addonId: null,
92457
+ access: "create",
92458
+ role: "viewer"
92459
+ },
91957
92460
  "irTransmitter.send": {
91958
92461
  capName: "ir-transmitter",
91959
92462
  capScope: "device",
@@ -98472,48 +98975,6 @@ Object.freeze({
98472
98975
  role: "admin"
98473
98976
  }
98474
98977
  });
98475
- /**
98476
- * "Do" actions an operator can pick for a device — presentation-free.
98477
- *
98478
- * The wire shape stays `{ cap, method, args }`. Since D920 this module no
98479
- * longer HOLDS a catalogue: {@link DEVICE_DO_ACTIONS} is a projection of the
98480
- * cap vocabulary (`cap-vocabulary/`), the one cap-keyed map of every action
98481
- * and trigger — so a picker built on this module and one built on
98482
- * `actionsForDevice` (the vocabulary resolver) can never disagree about what
98483
- * a device can do. Ids, baked args and the matching of saved steps are the
98484
- * vocabulary's; labels here are its English labels.
98485
- *
98486
- * Shared by the admin NC Sequences / rule Do editor and Automations; the
98487
- * viewer can reuse the same catalogue once its step editor catches up.
98488
- */
98489
- function openArgKind(offer) {
98490
- if (offer.kind === "number") return "number";
98491
- if (offer.kind === "dynamic" && offer.valueKind === "number") return "number";
98492
- if (offer.kind === "enum" && offer.values.every((v) => typeof v.value === "number")) return "number";
98493
- return "string";
98494
- }
98495
- function projectOpenArg(arg) {
98496
- return {
98497
- key: arg.key,
98498
- label: arg.label.en,
98499
- kind: openArgKind(arg.offer),
98500
- offer: arg.offer,
98501
- ...arg.optional === true ? { optional: true } : {}
98502
- };
98503
- }
98504
- function projectAction(cap, action) {
98505
- const open = action.openArgs ?? [];
98506
- return {
98507
- id: action.id,
98508
- label: action.label.en,
98509
- cap,
98510
- method: action.method,
98511
- args: action.args ?? {},
98512
- ...open.length > 0 ? { openArgs: open.map(projectOpenArg) } : {}
98513
- };
98514
- }
98515
- Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
98516
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
98517
98978
  DeviceFeature.BatteryOperated;
98518
98979
  function bound(cap, methods = [], authority = "reported") {
98519
98980
  return {
@@ -99805,6 +100266,11 @@ Object.freeze({
99805
100266
  form: "single",
99806
100267
  optional: false
99807
100268
  }],
100269
+ "irTransmitter.learn": [{
100270
+ name: "deviceId",
100271
+ form: "single",
100272
+ optional: false
100273
+ }],
99808
100274
  "irTransmitter.send": [{
99809
100275
  name: "deviceId",
99810
100276
  form: "single",
@@ -101744,7 +102210,10 @@ var NC_SYSTEM_EVENT_FILTER_KEYS = [
101744
102210
  "deviceTypes",
101745
102211
  "nodeIds",
101746
102212
  "packageNames",
101747
- "consumableLowPercent"
102213
+ "consumableLowPercent",
102214
+ "commandActions",
102215
+ "commandOutcomes",
102216
+ "commandActorKinds"
101748
102217
  ];
101749
102218
  new Map(Object.entries({
101750
102219
  "device-online": "device-events",
@@ -101767,6 +102236,7 @@ new Map(Object.entries({
101767
102236
  "recording-degraded": "device-events",
101768
102237
  "device-attention-required": "device-events",
101769
102238
  "device-attention-resolved": "device-events",
102239
+ "device-command": "device-events",
101770
102240
  "node-online": "system",
101771
102241
  "node-offline": "system",
101772
102242
  "node-restarting": "system",
@@ -101867,6 +102337,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
101867
102337
  label: "Needs attention",
101868
102338
  kinds: ["device-attention-required", "device-attention-resolved"]
101869
102339
  },
102340
+ {
102341
+ id: "device-command",
102342
+ family: "device-events",
102343
+ label: "Commands given to devices",
102344
+ kinds: ["device-command"]
102345
+ },
101870
102346
  {
101871
102347
  id: "nodes",
101872
102348
  family: "system",
@@ -101944,6 +102420,15 @@ function systemEventKindsOfFamily(family) {
101944
102420
  return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
101945
102421
  }
101946
102422
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
102423
+ new Set([
102424
+ "alarm.trigger",
102425
+ "alarm.disarm",
102426
+ "alarm.arm.home",
102427
+ "alarm.arm.away",
102428
+ "alarm.arm.night",
102429
+ "alarm.arm.vacation",
102430
+ "alarm.arm.custom_bypass"
102431
+ ]);
101947
102432
  /**
101948
102433
  * WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
101949
102434
  * persists (D640 "a sensor rule fires on the raise, the clear, or both").
@@ -102139,6 +102624,7 @@ var DEVICE_EVENTS = [
102139
102624
  "recording-healthy",
102140
102625
  "recording-degraded",
102141
102626
  ...DEVICE_ATTENTION,
102627
+ "device-command",
102142
102628
  ...ALARM
102143
102629
  ];
102144
102630
  /** Every kind a summary may speak about — the two summarisable families. */
@@ -102292,6 +102778,9 @@ var STATE_SUMMARY_VARS = [
102292
102778
  [...RULE_BODY_ONLY],
102293
102779
  [...RULE_TEXT],
102294
102780
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
102781
+ [...RULE_TEXT],
102782
+ [...RULE_TEXT],
102783
+ ([...RULE_TEXT], [...DEVICE_EVENTS]),
102295
102784
  ([...RULE_TEXT], [...COMBINED_ALARM]),
102296
102785
  ([...RULE_TEXT], [...COMBINED_ALARM]),
102297
102786
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
@@ -103625,7 +104114,7 @@ function findSpec(id) {
103625
104114
  function capsOfSpec(spec) {
103626
104115
  switch (spec.kind) {
103627
104116
  case "power-meter": return ["power-meter"];
103628
- case "battery": return ["battery", "power-meter"];
104117
+ case "battery": return ["numeric-sensor", "power-meter"];
103629
104118
  case "meter": return ["meter"];
103630
104119
  case "numeric": return ["numeric-sensor"];
103631
104120
  case "text": return ["enum-sensor"];
@@ -103728,11 +104217,9 @@ function computeSlices(spec, snapshot, options, now) {
103728
104217
  const v = numberOf(voltage);
103729
104218
  const a = numberOf(current);
103730
104219
  return {
103731
- battery: {
103732
- level: nullableOf(soc),
103733
- charging: "unknown",
103734
- sleeping: false,
103735
- lastUpdated: now
104220
+ numeric: {
104221
+ value: nullableOf(soc),
104222
+ lastFetchedAt: now
103736
104223
  },
103737
104224
  ...w !== void 0 || v !== void 0 || a !== void 0 ? { powerMeter: {
103738
104225
  ...w !== void 0 ? { power: w * sign || 0 } : {},
@@ -103878,12 +104365,6 @@ var METER_CAP = meterCapability.name;
103878
104365
  var NUMERIC_CAP = numericSensorCapability.name;
103879
104366
  var ENUM_CAP = enumSensorCapability.name;
103880
104367
  var POWER_COLD = { lastFetchedAt: 0 };
103881
- var BATTERY_COLD = {
103882
- level: null,
103883
- charging: "unknown",
103884
- sleeping: false,
103885
- lastUpdated: 0
103886
- };
103887
104368
  var VALUE_COLD = {
103888
104369
  value: null,
103889
104370
  lastFetchedAt: 0
@@ -103931,6 +104412,7 @@ var EnpalSensorDevice = class extends BaseDevice {
103931
104412
  id: `${this.integrationId}:${spec.id}`,
103932
104413
  system: ENPAL_SYSTEM,
103933
104414
  ...spec.kind === "meter" || spec.kind === "numeric" ? { quantity: spec.quantity } : {},
104415
+ ...spec.kind === "battery" ? { quantity: spec.level.quantity } : {},
103934
104416
  ...spec.kind === "text" && spec.timestamp === true ? { dateTimeShape: "date" } : {}
103935
104417
  });
103936
104418
  }
@@ -103980,7 +104462,6 @@ var EnpalSensorDevice = class extends BaseDevice {
103980
104462
  energy: this.energyGuard.accept(kwh)
103981
104463
  });
103982
104464
  }
103983
- if (slices.battery !== void 0) this.runtimeState.setCapState(BATTERY_CAP, slices.battery);
103984
104465
  if (slices.meter !== void 0) {
103985
104466
  const raw = slices.meter.value;
103986
104467
  this.runtimeState.setCapState(METER_CAP, {
@@ -103997,7 +104478,8 @@ var EnpalSensorDevice = class extends BaseDevice {
103997
104478
  this.registerPowerMeter();
103998
104479
  return;
103999
104480
  case "battery":
104000
- this.registerBattery();
104481
+ this.retractBatteryCap();
104482
+ this.registerNumeric();
104001
104483
  this.registerPowerMeter();
104002
104484
  return;
104003
104485
  case "meter":
@@ -104009,8 +104491,7 @@ var EnpalSensorDevice = class extends BaseDevice {
104009
104491
  this.runtimeState.setCapState(ENUM_CAP, TEXT_COLD);
104010
104492
  return;
104011
104493
  case "numeric":
104012
- this.ctx.registerNativeCap(numericSensorCapability, { getStatus: async () => this.runtimeState.getCapState(NUMERIC_CAP) ?? VALUE_COLD });
104013
- this.runtimeState.setCapState(NUMERIC_CAP, VALUE_COLD);
104494
+ this.registerNumeric();
104014
104495
  return;
104015
104496
  }
104016
104497
  }
@@ -104018,15 +104499,18 @@ var EnpalSensorDevice = class extends BaseDevice {
104018
104499
  this.ctx.registerNativeCap(powerMeterCapability, { getStatus: async () => this.runtimeState.getCapState(POWER_CAP) ?? POWER_COLD });
104019
104500
  this.runtimeState.setCapState(POWER_CAP, POWER_COLD);
104020
104501
  }
104021
- registerBattery() {
104022
- this.ctx.registerNativeCap(batteryCapability, {
104023
- getStatus: async () => this.runtimeState.getCapState(BATTERY_CAP) ?? BATTERY_COLD,
104024
- wakeForStream: async () => ({
104025
- awoke: false,
104026
- durationMs: 0
104027
- })
104028
- });
104029
- this.runtimeState.setCapState(BATTERY_CAP, BATTERY_COLD);
104502
+ registerNumeric() {
104503
+ this.ctx.registerNativeCap(numericSensorCapability, { getStatus: async () => this.runtimeState.getCapState(NUMERIC_CAP) ?? VALUE_COLD });
104504
+ this.runtimeState.setCapState(NUMERIC_CAP, VALUE_COLD);
104505
+ }
104506
+ /**
104507
+ * The storage battery once registered the `battery` cap (#522). This process never
104508
+ * registers it, so the call retracts the binding a previous incarnation left on the hub
104509
+ * (`unregisterNativeCap`'s stale-retract path): a restored device stops answering
104510
+ * `battery` and no `battery` slice is written again.
104511
+ */
104512
+ retractBatteryCap() {
104513
+ this.ctx.unregisterNativeCap?.(BATTERY_CAP);
104030
104514
  }
104031
104515
  };
104032
104516
  //#endregion
@@ -105374,7 +105858,39 @@ var enpalConnectionsEntryKind = defineEntryKind("connections", {
105374
105858
  });
105375
105859
  /** Every kind this addon declares, keyed by its name. */
105376
105860
  var enpalRecordKinds = { connections: enpalConnectionsEntryKind };
105861
+ var REJECTED_KINDS = new Set([
105862
+ "unauthorized",
105863
+ "token-missing",
105864
+ "empty"
105865
+ ]);
105866
+ function connectionForTest(values) {
105867
+ const host = values["host"];
105868
+ if (typeof host !== "string" || host.trim().length === 0) return null;
105869
+ const parsed = enpalConnectionSchema.safeParse({ ...values });
105870
+ return parsed.success ? parsed.data : null;
105871
+ }
105872
+ function testFailure(error) {
105873
+ if (error instanceof EnpalError) return {
105874
+ success: false,
105875
+ status: REJECTED_KINDS.has(error.kind) ? "rejected" : "inconclusive",
105876
+ error: error.message
105877
+ };
105878
+ return {
105879
+ success: false,
105880
+ status: "inconclusive",
105881
+ error: "The Enpal box did not answer"
105882
+ };
105883
+ }
105884
+ function liveProbe(blazor) {
105885
+ return (connection) => readEnpalSnapshot({
105886
+ connection,
105887
+ fetch: globalThis.fetch,
105888
+ now: () => Date.now(),
105889
+ blazor: blazor?.()
105890
+ });
105891
+ }
105377
105892
  function buildEnpalInstances(records, deps) {
105893
+ const probe = deps.probe ?? liveProbe(deps.blazor);
105378
105894
  return new RecordIntegrationInstances(records, enpalConnectionsEntryKind, {
105379
105895
  description: {
105380
105896
  role: "devices",
@@ -105384,18 +105900,36 @@ function buildEnpalInstances(records, deps) {
105384
105900
  },
105385
105901
  instanceMode: "multiple",
105386
105902
  kind: "server",
105387
- canTest: false,
105903
+ canTest: true,
105388
105904
  kinds: [{
105389
105905
  id: "connection",
105390
105906
  label: "Enpal",
105391
105907
  functionCap: "device-provider",
105392
- role: "devices"
105908
+ role: "devices",
105909
+ canTest: true
105393
105910
  }]
105394
105911
  },
105395
105912
  formSchema: () => buildConnectionFormSchema(),
105396
105913
  initialBody: (name) => ({ name }),
105397
105914
  configured: (body) => (body.host ?? "").length > 0,
105398
- onChanged: () => deps.onChanged()
105915
+ onChanged: () => deps.onChanged(),
105916
+ test: async ({ values }) => {
105917
+ const connection = connectionForTest(values);
105918
+ if (connection === null) return {
105919
+ success: false,
105920
+ status: "rejected",
105921
+ error: "Host is required"
105922
+ };
105923
+ try {
105924
+ await probe(connection);
105925
+ return {
105926
+ success: true,
105927
+ status: "validated"
105928
+ };
105929
+ } catch (error) {
105930
+ return testFailure(error);
105931
+ }
105932
+ }
105399
105933
  });
105400
105934
  }
105401
105935
  /**
@@ -105688,7 +106222,10 @@ var EnpalProviderAddon = class extends BaseDeviceProvider {
105688
106222
  ...rowsDepsOver(api),
105689
106223
  kind: enpalConnectionsEntryKind
105690
106224
  }));
105691
- this.instances = buildEnpalInstances(records, { onChanged: () => this.reconcileIntegrations() });
106225
+ this.instances = buildEnpalInstances(records, {
106226
+ onChanged: () => this.reconcileIntegrations(),
106227
+ blazor: () => this.blazorReader ?? void 0
106228
+ });
105692
106229
  this.blazorReader = await this.createBlazorBridge();
105693
106230
  this.clients = new EnpalClientRegistry(this.ctx.logger, {
105694
106231
  blazor: () => this.blazorReader ?? void 0,