@camstack/addon-google-services 0.2.35 → 0.2.36

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.
@@ -789,7 +789,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
789
789
  return EventCategory;
790
790
  }({});
791
791
  //#endregion
792
- //#region ../types/dist/device-type-BOWLtoOt.mjs
792
+ //#region ../types/dist/device-type-D1fH5kaC.mjs
793
793
  /**
794
794
  import { errMsg } from '@camstack/types'
795
795
  * Extract a human-readable message from an unknown error value.
@@ -978,6 +978,9 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
978
978
  DeviceFeature["CoverPositionable"] = "cover-positionable";
979
979
  /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
980
980
  DeviceFeature["CoverTilt"] = "cover-tilt";
981
+ /** Cover has a real stop: `stop` halts the travel in place. Gates the stop
982
+ * control in exporters; a provider whose `stop` is a no-op never declares it. */
983
+ DeviceFeature["CoverStoppable"] = "cover-stoppable";
981
984
  /** Valve exposes intermediate position control (0..100). Gates the
982
985
  * position slider / drag surface UI. */
983
986
  DeviceFeature["ValvePositionable"] = "valve-positionable";
@@ -1017,6 +1020,12 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
1017
1020
  DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
1018
1021
  /** Media player exposes stop distinct from pause. */
1019
1022
  DeviceFeature["MediaPlayerStop"] = "media-player-stop";
1023
+ /** Media player exposes volume up / down in steps (an infrared TV has no level). */
1024
+ DeviceFeature["MediaPlayerVolumeStep"] = "media-player-volume-step";
1025
+ /** Media player exposes channel up / down in steps. */
1026
+ DeviceFeature["MediaPlayerChannelStep"] = "media-player-channel-step";
1027
+ /** Media player exposes power on / off. */
1028
+ DeviceFeature["MediaPlayerPower"] = "media-player-power";
1020
1029
  /** Alarm panel requires a PIN code on arm/disarm. */
1021
1030
  DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
1022
1031
  /** Presence device carries GPS coordinates (lat/lng/accuracy) in
@@ -5976,7 +5985,7 @@ var ZodIssueCode = {
5976
5985
  var ZodFirstPartyTypeKind;
5977
5986
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5978
5987
  //#endregion
5979
- //#region ../types/dist/sleep-CdJHPm-k.mjs
5988
+ //#region ../types/dist/sleep-BKClVk0K.mjs
5980
5989
  /**
5981
5990
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5982
5991
  * window to float samples (D455).
@@ -6055,6 +6064,22 @@ function buildAlawTable() {
6055
6064
  }
6056
6065
  buildUlawTable();
6057
6066
  buildAlawTable();
6067
+ /**
6068
+ * An addon whose own migrations did not apply refuses to initialise (D987). The error is typed
6069
+ * so a runner hosting several addons can tell "this addon is blocked" from a crash and keep its
6070
+ * co-located siblings running. Recognised by NAME and shape, never `instanceof`: a forked addon
6071
+ * bundles its own copy of this module, so the class the runner sees is not the one that threw.
6072
+ */
6073
+ var MIGRATIONS_BLOCKED_ERROR_NAME = "AddonMigrationsBlockedError";
6074
+ var AddonMigrationsBlockedError = class extends Error {
6075
+ /** The ledger ids that did not apply and block the owner. */
6076
+ migrationIds;
6077
+ constructor(migrationIds) {
6078
+ super(`migration(s) ${migrationIds.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
6079
+ this.name = MIGRATIONS_BLOCKED_ERROR_NAME;
6080
+ this.migrationIds = migrationIds;
6081
+ }
6082
+ };
6058
6083
  /** Is this field's stored value a credential? */
6059
6084
  function isSecretConfigField(field) {
6060
6085
  if (field.type === "password") return true;
@@ -6492,6 +6517,53 @@ function emitReadiness(bus, params) {
6492
6517
  ts
6493
6518
  }));
6494
6519
  }
6520
+ /** Why a catalogue entry is not a runnable body of its declared kind. */
6521
+ var AddonMigrationDefinitionError = class extends Error {
6522
+ id;
6523
+ constructor(id, message) {
6524
+ super(`${id}: ${message}`);
6525
+ this.id = id;
6526
+ this.name = "AddonMigrationDefinitionError";
6527
+ }
6528
+ };
6529
+ /**
6530
+ * Bind a catalogue to the addon's ports. Throws {@link AddonMigrationDefinitionError} when
6531
+ * an entry's `bodyKind` does not match the body it carries, or is `sql` (a hub-only
6532
+ * body). A throwing body never escapes `execute`: it is reported as a failed result.
6533
+ */
6534
+ function defineAddonMigrations(catalogue, ports) {
6535
+ return catalogue.map((entry) => {
6536
+ const { meta } = entry;
6537
+ if ("plan" in entry) {
6538
+ if (meta.bodyKind !== "plan") throw new AddonMigrationDefinitionError(meta.id, `carries a plan body but declares "${meta.bodyKind}"`);
6539
+ return {
6540
+ meta,
6541
+ execute: () => guarded(async () => ({
6542
+ kind: "plan",
6543
+ plan: await entry.plan(ports)
6544
+ }))
6545
+ };
6546
+ }
6547
+ if (meta.bodyKind !== "procedure") throw new AddonMigrationDefinitionError(meta.id, `carries a procedure body but declares "${meta.bodyKind}"`);
6548
+ return {
6549
+ meta,
6550
+ execute: () => guarded(async () => ({
6551
+ kind: "procedure",
6552
+ result: await entry.procedure(ports)
6553
+ }))
6554
+ };
6555
+ });
6556
+ }
6557
+ async function guarded(work) {
6558
+ try {
6559
+ return await work();
6560
+ } catch (err) {
6561
+ return {
6562
+ kind: "failed",
6563
+ error: errMsg(err)
6564
+ };
6565
+ }
6566
+ }
6495
6567
  var NOT_RUN = {
6496
6568
  declared: false,
6497
6569
  snapshot: null,
@@ -7661,7 +7733,7 @@ var BaseAddon = class {
7661
7733
  startMigration: (input) => api.startMigration.mutate(input),
7662
7734
  finishMigration: (input) => api.finishMigration.mutate(input)
7663
7735
  }, migrations, this.ctx.logger);
7664
- if (report.blockOwner.length > 0) throw new Error(`migration(s) ${report.blockOwner.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
7736
+ if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7665
7737
  return report;
7666
7738
  }
7667
7739
  /**
@@ -9652,6 +9724,7 @@ var CAP_NAMES = Object.freeze([
9652
9724
  "backup",
9653
9725
  "battery",
9654
9726
  "binary",
9727
+ "board-key-source",
9655
9728
  "brightness",
9656
9729
  "broker",
9657
9730
  "button",
@@ -9773,6 +9846,7 @@ var CAP_NAMES = Object.freeze([
9773
9846
  "privacy-mask",
9774
9847
  "ptz",
9775
9848
  "ptz-autotrack",
9849
+ "pump-cycle",
9776
9850
  "reboot",
9777
9851
  "record-entries",
9778
9852
  "recording",
@@ -11065,18 +11139,6 @@ Object.freeze({
11065
11139
  "path": "pumpRunning",
11066
11140
  "valueType": "boolean"
11067
11141
  },
11068
- {
11069
- "path": "pumpIntervalEnabled",
11070
- "valueType": "boolean"
11071
- },
11072
- {
11073
- "path": "pumpOnTime",
11074
- "valueType": "number"
11075
- },
11076
- {
11077
- "path": "pumpOffTime",
11078
- "valueType": "number"
11079
- },
11080
11142
  {
11081
11143
  "path": "lastDrink",
11082
11144
  "valueType": "number"
@@ -11200,6 +11262,20 @@ Object.freeze({
11200
11262
  "valueType": "number"
11201
11263
  }
11202
11264
  ],
11265
+ "pump-cycle": [
11266
+ {
11267
+ "path": "enabled",
11268
+ "valueType": "boolean"
11269
+ },
11270
+ {
11271
+ "path": "onTime",
11272
+ "valueType": "number"
11273
+ },
11274
+ {
11275
+ "path": "offTime",
11276
+ "valueType": "number"
11277
+ }
11278
+ ],
11203
11279
  "recording-archive": [
11204
11280
  {
11205
11281
  "path": "deviceId",
@@ -11663,7 +11739,7 @@ function userError(code, params, englishFallback) {
11663
11739
  return new CamstackError(code, params, englishFallback);
11664
11740
  }
11665
11741
  //#endregion
11666
- //#region ../types/dist/composition-BDUoQsw9.mjs
11742
+ //#region ../types/dist/composition-BodkvOV0.mjs
11667
11743
  /**
11668
11744
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11669
11745
  *
@@ -12800,7 +12876,14 @@ var CompositionForwardCommandSchema = object({
12800
12876
  method: string().min(1),
12801
12877
  args: record(string().min(1), CompositionForwardArgSchema).optional()
12802
12878
  });
12803
- var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
12879
+ var CompositionCommandTargetSchema = discriminatedUnion("kind", [
12880
+ CompositionForwardCommandSchema,
12881
+ CompositionCodeSourceSchema,
12882
+ object({
12883
+ kind: literal("extension"),
12884
+ target: literal("remote")
12885
+ })
12886
+ ]);
12804
12887
  /** An item key inside an item-array cap (`consumables.items[<key>]`). */
12805
12888
  var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12806
12889
  /**
@@ -12877,6 +12960,73 @@ var NodeColorIdSchema = _enum([
12877
12960
  "node-g",
12878
12961
  "node-h"
12879
12962
  ]);
12963
+ //#endregion
12964
+ //#region ../types/dist/index.mjs
12965
+ var SMTP_INBOUND_DEFAULT_PORT = 2525;
12966
+ var SMTP_INBOUND_DEFAULT_DOMAIN = "camstack.local";
12967
+ var SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES = 25 * 1024 * 1024;
12968
+ /** The text excerpt a message carries on the bus. */
12969
+ var SMTP_INBOUND_TEXT_MAX_CHARS = 4096;
12970
+ var PortSchema = number().int().min(1).max(65535);
12971
+ /** A recipient local-part: what stands before the `@`. */
12972
+ var SmtpRecipientSchema = string().min(1).max(64).regex(/^[A-Za-z0-9._+-]+$/);
12973
+ object({
12974
+ port: PortSchema.default(SMTP_INBOUND_DEFAULT_PORT),
12975
+ bindHost: string().min(1).default("0.0.0.0"),
12976
+ domain: string().min(1).default(SMTP_INBOUND_DEFAULT_DOMAIN),
12977
+ requireAuth: boolean().default(true),
12978
+ username: string().default(""),
12979
+ password: string().default("").meta({ secret: true }),
12980
+ starttls: boolean().default(false),
12981
+ maxMessageBytes: number().int().positive().default(SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES)
12982
+ });
12983
+ object({
12984
+ host: string().min(1).nullable(),
12985
+ hostReason: string().nullable(),
12986
+ port: PortSchema,
12987
+ domain: string().min(1),
12988
+ requireAuth: boolean(),
12989
+ username: string(),
12990
+ password: string(),
12991
+ starttls: boolean()
12992
+ });
12993
+ object({
12994
+ recipients: array(SmtpRecipientSchema).min(1),
12995
+ /**
12996
+ * Who is subscribing. A second `subscribe` with the same id REPLACES that subscriber's
12997
+ * recipients (the way a consumer that restarted, or lost a device, declares its set again);
12998
+ * a recipient held by another subscriber is refused with `smtp-inbound/recipient-claimed`.
12999
+ * Absent, the subscriber is anonymous and unique.
13000
+ */
13001
+ subscriberId: string().min(1).max(64).optional()
13002
+ });
13003
+ var SmtpInboundAttachmentSchema = object({
13004
+ filename: string(),
13005
+ contentType: string(),
13006
+ bytes: number().int().nonnegative()
13007
+ });
13008
+ /** The payload of one `broker.message` (the key is the recipient local-part). */
13009
+ var SmtpInboundMessageSchema = object({
13010
+ /** Monotonic per mailbox; a gap tells a consumer it missed an event. */
13011
+ seq: number().int().positive(),
13012
+ messageId: string(),
13013
+ receivedAt: number().int().nonnegative(),
13014
+ from: string(),
13015
+ to: string(),
13016
+ subject: string(),
13017
+ text: string().max(SMTP_INBOUND_TEXT_MAX_CHARS),
13018
+ attachments: array(SmtpInboundAttachmentSchema)
13019
+ });
13020
+ object({
13021
+ /** The newest seq issued so far (0 before any mail). */
13022
+ lastSeq: number().int().nonnegative(),
13023
+ /** The oldest seq still retained; messages below it are gone. */
13024
+ oldestSeq: number().int().nonnegative(),
13025
+ messages: array(object({
13026
+ key: string(),
13027
+ message: SmtpInboundMessageSchema
13028
+ }))
13029
+ });
12880
13030
  new Set([
12881
13031
  "track",
12882
13032
  "face",
@@ -14088,6 +14238,16 @@ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
14088
14238
  precondition: PreconditionSchema
14089
14239
  })
14090
14240
  ])) });
14241
+ /** `JSON.stringify` with object keys sorted, so equal values spell the same. */
14242
+ function canonicalJson(value) {
14243
+ if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
14244
+ if (typeof value === "object" && value !== null) return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
14245
+ return JSON.stringify(value) ?? "null";
14246
+ }
14247
+ /** The precondition string for a value a plan read (`null` when it read nothing). */
14248
+ function preconditionOf(value) {
14249
+ return canonicalJson(value ?? null);
14250
+ }
14091
14251
  /**
14092
14252
  * What a `procedure` body reports. `already-done` means the rerun observed the
14093
14253
  * work finished (a crash between the work and the ledger row, or a manual fix)
@@ -19266,24 +19426,52 @@ var APPLIANCE_CAP_VOCABULARY = {
19266
19426
  args: { on: false }
19267
19427
  },
19268
19428
  {
19269
- id: "petFountain.intervalOn",
19270
- method: "setPumpInterval",
19429
+ id: "petFountain.resetDailyConsumption",
19430
+ method: "resetDailyConsumption",
19431
+ label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
19432
+ phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
19433
+ icon: "rotate-ccw"
19434
+ }
19435
+ ],
19436
+ state: [{
19437
+ kind: "boolean",
19438
+ field: "empty",
19439
+ label: t("Tank empty", "Serbatoio vuoto"),
19440
+ whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
19441
+ whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
19442
+ icon: "droplet"
19443
+ }, {
19444
+ kind: "numeric",
19445
+ field: "waterLevel",
19446
+ quantity: t("the water level", "il livello dell’acqua"),
19447
+ label: t("Water level", "Livello dell’acqua"),
19448
+ unit: "%",
19449
+ icon: "droplets"
19450
+ }]
19451
+ },
19452
+ "pump-cycle": {
19453
+ scope: "device",
19454
+ noun: t("Pump cycle", "Ciclo della pompa"),
19455
+ actions: [
19456
+ {
19457
+ id: "pumpCycle.on",
19458
+ method: "setEnabled",
19271
19459
  label: t("Pump cycle on", "Ciclo pompa attivo"),
19272
19460
  phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
19273
19461
  icon: "timer",
19274
19462
  args: { on: true }
19275
19463
  },
19276
19464
  {
19277
- id: "petFountain.intervalOff",
19278
- method: "setPumpInterval",
19465
+ id: "pumpCycle.off",
19466
+ method: "setEnabled",
19279
19467
  label: t("Pump cycle off", "Ciclo pompa spento"),
19280
19468
  phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
19281
19469
  icon: "timer-off",
19282
19470
  args: { on: false }
19283
19471
  },
19284
19472
  {
19285
- id: "petFountain.pumpOnMinutes",
19286
- method: "setPumpOnMinutes",
19473
+ id: "pumpCycle.onMinutes",
19474
+ method: "setOnMinutes",
19287
19475
  label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
19288
19476
  phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
19289
19477
  icon: "timer",
@@ -19299,8 +19487,8 @@ var APPLIANCE_CAP_VOCABULARY = {
19299
19487
  }]
19300
19488
  },
19301
19489
  {
19302
- id: "petFountain.pumpOffMinutes",
19303
- method: "setPumpOffMinutes",
19490
+ id: "pumpCycle.offMinutes",
19491
+ method: "setOffMinutes",
19304
19492
  label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
19305
19493
  phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
19306
19494
  icon: "timer-off",
@@ -19314,35 +19502,63 @@ var APPLIANCE_CAP_VOCABULARY = {
19314
19502
  unit: "min"
19315
19503
  }
19316
19504
  }]
19317
- },
19318
- {
19319
- id: "petFountain.resetDailyConsumption",
19320
- method: "resetDailyConsumption",
19321
- label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
19322
- phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
19323
- icon: "rotate-ccw"
19324
19505
  }
19325
19506
  ],
19326
19507
  state: [{
19327
19508
  kind: "boolean",
19328
- field: "empty",
19329
- label: t("Tank empty", "Serbatoio vuoto"),
19330
- whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
19331
- whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
19332
- icon: "droplet"
19333
- }, {
19334
- kind: "numeric",
19335
- field: "waterLevel",
19336
- quantity: t("the water level", "il livello dell’acqua"),
19337
- label: t("Water level", "Livello dell’acqua"),
19338
- unit: "%",
19339
- icon: "droplets"
19509
+ field: "enabled",
19510
+ label: t("Pump cycle", "Ciclo pompa"),
19511
+ whenTrue: t("When {device} starts running its pump on a cycle", "Quando {device} inizia a far lavorare la pompa a ciclo"),
19512
+ whenFalse: t("When {device} runs its pump continuously", "Quando {device} fa lavorare la pompa in continuo"),
19513
+ icon: "timer"
19340
19514
  }]
19341
19515
  },
19342
19516
  "media-player": {
19343
19517
  scope: "device",
19344
19518
  noun: t("Media player", "Lettore multimediale"),
19345
19519
  actions: [
19520
+ {
19521
+ id: "media.turnOn",
19522
+ method: "turnOn",
19523
+ label: t("Turn on", "Accendi"),
19524
+ phrase: t("Turn on {device}", "Accendi {device}"),
19525
+ icon: "power"
19526
+ },
19527
+ {
19528
+ id: "media.turnOff",
19529
+ method: "turnOff",
19530
+ label: t("Turn off", "Spegni"),
19531
+ phrase: t("Turn off {device}", "Spegni {device}"),
19532
+ icon: "power"
19533
+ },
19534
+ {
19535
+ id: "media.volumeUp",
19536
+ method: "volumeUp",
19537
+ label: t("Volume up", "Alza il volume"),
19538
+ phrase: t("Raise the volume of {device}", "Alza il volume di {device}"),
19539
+ icon: "volume-2"
19540
+ },
19541
+ {
19542
+ id: "media.volumeDown",
19543
+ method: "volumeDown",
19544
+ label: t("Volume down", "Abbassa il volume"),
19545
+ phrase: t("Lower the volume of {device}", "Abbassa il volume di {device}"),
19546
+ icon: "volume-2"
19547
+ },
19548
+ {
19549
+ id: "media.channelUp",
19550
+ method: "channelUp",
19551
+ label: t("Channel up", "Canale successivo"),
19552
+ phrase: t("Go to the next channel on {device}", "Passa al canale successivo su {device}"),
19553
+ icon: "skip-forward"
19554
+ },
19555
+ {
19556
+ id: "media.channelDown",
19557
+ method: "channelDown",
19558
+ label: t("Channel down", "Canale precedente"),
19559
+ phrase: t("Go to the previous channel on {device}", "Torna al canale precedente su {device}"),
19560
+ icon: "skip-back"
19561
+ },
19346
19562
  {
19347
19563
  id: "media.play",
19348
19564
  method: "play",
@@ -21229,6 +21445,7 @@ var SYSTEM_CAP_VOCABULARY = {
21229
21445
  "audio-analyzer": system(PIPELINE),
21230
21446
  "audio-codec": system(INTERNAL),
21231
21447
  "auth-provider": system(IDENTITY),
21448
+ "board-key-source": system(INTERNAL),
21232
21449
  backup: system("system backups — run and restored from the Backups page"),
21233
21450
  broker: system("message-broker connections — configured, not actuated"),
21234
21451
  "calendar-source": system(INTERNAL),
@@ -45691,6 +45908,49 @@ object({
45691
45908
  lastChangedAt: number()
45692
45909
  });
45693
45910
  DeviceType.Sensor;
45911
+ /**
45912
+ * Why a board key could not be read. Typed so a caller reports the cause and
45913
+ * never treats "could not read" as "has no key" (D49).
45914
+ */
45915
+ var BoardKeyFailureReasonSchema = _enum([
45916
+ "unknown-source",
45917
+ "not-admin",
45918
+ "no-builder",
45919
+ "no-ingress-url",
45920
+ "unreachable",
45921
+ "unknown-board",
45922
+ "no-encryption-key",
45923
+ "unreadable-config"
45924
+ ]);
45925
+ var BoardKeySourceSchema = object({
45926
+ sourceId: string(),
45927
+ name: string()
45928
+ });
45929
+ var BoardKeyBoardSchema = object({
45930
+ /** The builder's file name, e.g. `kitchen.yaml`: the handle `getBoardKey` takes. */
45931
+ configuration: string(),
45932
+ /** The node name the builder gives the board. */
45933
+ name: string(),
45934
+ /** The address the builder knows the board by, when it has one. */
45935
+ address: string().nullable()
45936
+ });
45937
+ var BoardKeyFailureSchema = object({
45938
+ ok: literal(false),
45939
+ reason: BoardKeyFailureReasonSchema,
45940
+ detail: string().optional()
45941
+ });
45942
+ var ListBoardsResultSchema = discriminatedUnion("ok", [object({
45943
+ ok: literal(true),
45944
+ boards: array(BoardKeyBoardSchema).readonly()
45945
+ }), BoardKeyFailureSchema]);
45946
+ var GetBoardKeyResultSchema = discriminatedUnion("ok", [object({
45947
+ ok: literal(true),
45948
+ key: string()
45949
+ }), BoardKeyFailureSchema]);
45950
+ method(_void(), array(BoardKeySourceSchema).readonly(), { auth: "admin" }), method(object({ sourceId: string().min(1) }), ListBoardsResultSchema, { auth: "admin" }), method(object({
45951
+ sourceId: string().min(1),
45952
+ configuration: string().min(1)
45953
+ }), GetBoardKeyResultSchema, { auth: "admin" });
45694
45954
  object({
45695
45955
  /** Current level as 0..100 inclusive. Firmware-reported. */
45696
45956
  level: number().min(0).max(100).meta(readingMeta("percentage")),
@@ -47827,6 +48087,188 @@ DeviceType.Control, method(object({
47827
48087
  kind: "mutation",
47828
48088
  auth: "protected"
47829
48089
  });
48090
+ /**
48091
+ * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
48092
+ * Apple TV, Spotify, Roku, generic OTT receivers, …) on
48093
+ * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
48094
+ * surface — those use `DeviceType.Camera` + the broker pipeline.
48095
+ *
48096
+ * The cap declares a wide method surface; not every backend supports
48097
+ * every method. Drivers signal sub-capability via the
48098
+ * `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
48099
+ * corresponding method) so the UI gates buttons without a slice
48100
+ * fetch — same pattern as `PanTiltZoom` for cameras.
48101
+ */
48102
+ var MediaPlayerStateSchema = _enum([
48103
+ "off",
48104
+ "on",
48105
+ "idle",
48106
+ "playing",
48107
+ "paused",
48108
+ "buffering",
48109
+ "standby"
48110
+ ]);
48111
+ var MediaPlayerRepeatSchema = _enum([
48112
+ "off",
48113
+ "all",
48114
+ "one"
48115
+ ]);
48116
+ var MediaInfoSchema = object({
48117
+ /** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
48118
+ * `podcast`, …). */
48119
+ type: string(),
48120
+ /** Human-readable title. */
48121
+ title: string(),
48122
+ /** Optional artist / channel / station label. */
48123
+ artist: string().optional(),
48124
+ /** Optional album / season / show name. */
48125
+ album: string().optional(),
48126
+ /** Optional cover-art / thumbnail URL. */
48127
+ imageUrl: string().optional()
48128
+ });
48129
+ /**
48130
+ * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
48131
+ * the device said so. `assumed`: the driver has no feedback (infrared) and
48132
+ * tracks what it last SENT; it moves only after a send was accepted, and a
48133
+ * human at the real remote makes it wrong. A UI shows an assumed value as a
48134
+ * guess, never as a reading.
48135
+ */
48136
+ var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
48137
+ /** The fields of a media-player status a driver can only guess. */
48138
+ var MediaPlayerAssumedFieldSchema = _enum([
48139
+ "state",
48140
+ "volumeLevel",
48141
+ "isMuted",
48142
+ "source"
48143
+ ]);
48144
+ var MediaPlayerStatusSchema = object({
48145
+ /** Playback lifecycle state. */
48146
+ state: MediaPlayerStateSchema,
48147
+ /**
48148
+ * Whether the state fields are readings or the driver's own bookkeeping.
48149
+ * A slice written before this field existed parses as `reported`.
48150
+ */
48151
+ stateSource: MediaPlayerStateSourceSchema.default("reported"),
48152
+ /**
48153
+ * Which fields are the driver's own guess. `stateSource` is `assumed` when
48154
+ * this is not empty; a field not listed here is a reading even then (power
48155
+ * from a running-state source, mute guessed from the last send).
48156
+ */
48157
+ assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
48158
+ /** Volume as 0..100 inclusive. Null when the device has no volume
48159
+ * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
48160
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
48161
+ /** Mute toggle distinct from volume=0. Null when no mute surface.
48162
+ * Pair with `DeviceFeature.MediaPlayerMute`. */
48163
+ isMuted: boolean().nullable(),
48164
+ /** Active source / input. Empty when no source surface. */
48165
+ source: string(),
48166
+ /** Selectable sources. Empty when no source surface.
48167
+ * Pair with `DeviceFeature.MediaPlayerSelectSource`. */
48168
+ availableSources: array(string()),
48169
+ /** Currently-playing media info. Null when nothing is playing. */
48170
+ currentMedia: MediaInfoSchema.nullable(),
48171
+ /** Current playback position in ms. Null when not seekable or
48172
+ * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
48173
+ position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
48174
+ /** Total duration of the current media in ms. Null when unknown
48175
+ * (live stream) or nothing is playing. */
48176
+ duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
48177
+ /** Shuffle toggle. Null when no shuffle surface.
48178
+ * Pair with `DeviceFeature.MediaPlayerShuffle`. */
48179
+ shuffle: boolean().nullable(),
48180
+ /** Repeat mode. Null when no repeat surface.
48181
+ * Pair with `DeviceFeature.MediaPlayerRepeat`. */
48182
+ repeat: MediaPlayerRepeatSchema.nullable(),
48183
+ /** Ms epoch when the slice was last updated. */
48184
+ lastChangedAt: number()
48185
+ });
48186
+ var stepInput = object({
48187
+ deviceId: number().int().nonnegative(),
48188
+ /** How many presses: the caller's count (Alexa `volumeSteps`, `channelCount`). */
48189
+ steps: number().int().min(1).max(50).default(1)
48190
+ });
48191
+ var deviceOnly = object({ deviceId: number().int().nonnegative() });
48192
+ DeviceType.MediaPlayer, method(deviceOnly, _void(), {
48193
+ kind: "mutation",
48194
+ auth: "protected"
48195
+ }), method(deviceOnly, _void(), {
48196
+ kind: "mutation",
48197
+ auth: "protected"
48198
+ }), method(stepInput, _void(), {
48199
+ kind: "mutation",
48200
+ auth: "protected"
48201
+ }), method(stepInput, _void(), {
48202
+ kind: "mutation",
48203
+ auth: "protected"
48204
+ }), method(stepInput, _void(), {
48205
+ kind: "mutation",
48206
+ auth: "protected"
48207
+ }), method(stepInput, _void(), {
48208
+ kind: "mutation",
48209
+ auth: "protected"
48210
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48211
+ kind: "mutation",
48212
+ auth: "protected"
48213
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48214
+ kind: "mutation",
48215
+ auth: "protected"
48216
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48217
+ kind: "mutation",
48218
+ auth: "protected"
48219
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48220
+ kind: "mutation",
48221
+ auth: "protected"
48222
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48223
+ kind: "mutation",
48224
+ auth: "protected"
48225
+ }), method(object({
48226
+ deviceId: number().int().nonnegative(),
48227
+ position: number().int().nonnegative()
48228
+ }), _void(), {
48229
+ kind: "mutation",
48230
+ auth: "protected"
48231
+ }), method(object({
48232
+ deviceId: number().int().nonnegative(),
48233
+ volumeLevel: number().min(0).max(100)
48234
+ }), _void(), {
48235
+ kind: "mutation",
48236
+ auth: "protected"
48237
+ }), method(object({
48238
+ deviceId: number().int().nonnegative(),
48239
+ muted: boolean()
48240
+ }), _void(), {
48241
+ kind: "mutation",
48242
+ auth: "protected"
48243
+ }), method(object({
48244
+ deviceId: number().int().nonnegative(),
48245
+ shuffle: boolean()
48246
+ }), _void(), {
48247
+ kind: "mutation",
48248
+ auth: "protected"
48249
+ }), method(object({
48250
+ deviceId: number().int().nonnegative(),
48251
+ repeat: MediaPlayerRepeatSchema
48252
+ }), _void(), {
48253
+ kind: "mutation",
48254
+ auth: "protected"
48255
+ }), method(object({
48256
+ deviceId: number().int().nonnegative(),
48257
+ source: string().min(1)
48258
+ }), _void(), {
48259
+ kind: "mutation",
48260
+ auth: "protected"
48261
+ }), method(object({
48262
+ deviceId: number().int().nonnegative(),
48263
+ /** Media identifier / URL. */
48264
+ mediaId: string().min(1),
48265
+ /** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
48266
+ * passes it through to the upstream service. */
48267
+ mediaType: string().min(1)
48268
+ }), _void(), {
48269
+ kind: "mutation",
48270
+ auth: "protected"
48271
+ });
47830
48272
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, 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.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.Heater, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Fan, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder, DeviceType.Heater;
47831
48273
  var NUMBER = {
47832
48274
  kind: "fixed",
@@ -51412,7 +51854,7 @@ object({
51412
51854
  /** Ms epoch of the last value received from the appliance; 0 = never. */
51413
51855
  lastFetchedAt: number()
51414
51856
  });
51415
- var DeviceIdSchema$1 = number().int().nonnegative();
51857
+ var DeviceIdSchema$2 = number().int().nonnegative();
51416
51858
  /** A value a program option or setting can take. */
51417
51859
  var DishwasherValueSchema = union([
51418
51860
  boolean(),
@@ -51476,77 +51918,77 @@ var DishwasherOptionsSchema = object({
51476
51918
  options: array(DishwasherOptionDescriptorSchema)
51477
51919
  });
51478
51920
  DeviceType.Dishwasher, method(object({
51479
- deviceId: DeviceIdSchema$1,
51921
+ deviceId: DeviceIdSchema$2,
51480
51922
  program: string().min(1).optional(),
51481
51923
  options: record(string(), DishwasherValueSchema).optional()
51482
51924
  }), _void(), {
51483
51925
  kind: "mutation",
51484
51926
  auth: "protected"
51485
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
51927
+ }), method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
51486
51928
  kind: "mutation",
51487
51929
  auth: "protected"
51488
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
51930
+ }), method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
51489
51931
  kind: "mutation",
51490
51932
  auth: "protected"
51491
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
51933
+ }), method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
51492
51934
  kind: "mutation",
51493
51935
  auth: "protected"
51494
51936
  }), method(object({
51495
- deviceId: DeviceIdSchema$1,
51937
+ deviceId: DeviceIdSchema$2,
51496
51938
  program: string().min(1)
51497
51939
  }), _void(), {
51498
51940
  kind: "mutation",
51499
51941
  auth: "protected"
51500
51942
  }), method(object({
51501
- deviceId: DeviceIdSchema$1,
51943
+ deviceId: DeviceIdSchema$2,
51502
51944
  on: boolean()
51503
51945
  }), _void(), {
51504
51946
  kind: "mutation",
51505
51947
  auth: "protected"
51506
51948
  }), method(object({
51507
- deviceId: DeviceIdSchema$1,
51949
+ deviceId: DeviceIdSchema$2,
51508
51950
  on: boolean()
51509
51951
  }), _void(), {
51510
51952
  kind: "mutation",
51511
51953
  auth: "protected"
51512
51954
  }), method(object({
51513
- deviceId: DeviceIdSchema$1,
51955
+ deviceId: DeviceIdSchema$2,
51514
51956
  on: boolean()
51515
51957
  }), _void(), {
51516
51958
  kind: "mutation",
51517
51959
  auth: "protected"
51518
51960
  }), method(object({
51519
- deviceId: DeviceIdSchema$1,
51961
+ deviceId: DeviceIdSchema$2,
51520
51962
  on: boolean()
51521
51963
  }), _void(), {
51522
51964
  kind: "mutation",
51523
51965
  auth: "protected"
51524
51966
  }), method(object({
51525
- deviceId: DeviceIdSchema$1,
51967
+ deviceId: DeviceIdSchema$2,
51526
51968
  on: boolean()
51527
51969
  }), _void(), {
51528
51970
  kind: "mutation",
51529
51971
  auth: "protected"
51530
51972
  }), method(object({
51531
- deviceId: DeviceIdSchema$1,
51973
+ deviceId: DeviceIdSchema$2,
51532
51974
  on: boolean()
51533
51975
  }), _void(), {
51534
51976
  kind: "mutation",
51535
51977
  auth: "protected"
51536
51978
  }), method(object({
51537
- deviceId: DeviceIdSchema$1,
51979
+ deviceId: DeviceIdSchema$2,
51538
51980
  on: boolean()
51539
51981
  }), _void(), {
51540
51982
  kind: "mutation",
51541
51983
  auth: "protected"
51542
51984
  }), method(object({
51543
- deviceId: DeviceIdSchema$1,
51985
+ deviceId: DeviceIdSchema$2,
51544
51986
  key: string().min(1),
51545
51987
  value: DishwasherValueSchema
51546
51988
  }), _void(), {
51547
51989
  kind: "mutation",
51548
51990
  auth: "protected"
51549
- }), method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" });
51991
+ }), method(object({ deviceId: DeviceIdSchema$2 }), DishwasherOptionsSchema, { auth: "protected" });
51550
51992
  /**
51551
51993
  * Palette entry ids (D939). Audio is the one gradient; `audio-*` leaves are
51552
51994
  * not entries. `heatmapScrub` is a mark, not an event kind.
@@ -53287,6 +53729,11 @@ var IntegrationInstanceSchema = object({
53287
53729
  name: string(),
53288
53730
  /** The addon's own rule (an URL is present, a client id is present...). */
53289
53731
  configured: boolean(),
53732
+ /**
53733
+ * The instance's own on/off switch (D996): always present, derived from the record body
53734
+ * (`enabled !== false`, so a row written before the field existed is on).
53735
+ */
53736
+ enabled: boolean(),
53290
53737
  /** Values for the instance form; a secret is a `SecretFieldView`, never text. */
53291
53738
  formValues: record(string(), unknown()),
53292
53739
  status: IntegrationInstanceStatusSchema.optional()
@@ -53306,6 +53753,12 @@ method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }), method
53306
53753
  }), method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
53307
53754
  kind: "mutation",
53308
53755
  auth: "admin"
53756
+ }), method(object({
53757
+ id: IntegrationInstanceIdSchema,
53758
+ enabled: boolean()
53759
+ }), object({ ok: literal(true) }), {
53760
+ kind: "mutation",
53761
+ auth: "admin"
53309
53762
  }), method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
53310
53763
  kind: "mutation",
53311
53764
  auth: "admin"
@@ -55298,164 +55751,6 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
55298
55751
  ]);
55299
55752
  method(_void(), array(LoginMethodContributionSchema).readonly());
55300
55753
  /**
55301
- * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
55302
- * Apple TV, Spotify, Roku, generic OTT receivers, …) on
55303
- * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
55304
- * surface — those use `DeviceType.Camera` + the broker pipeline.
55305
- *
55306
- * The cap declares a wide method surface; not every backend supports
55307
- * every method. Drivers signal sub-capability via the
55308
- * `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
55309
- * corresponding method) so the UI gates buttons without a slice
55310
- * fetch — same pattern as `PanTiltZoom` for cameras.
55311
- */
55312
- var MediaPlayerStateSchema = _enum([
55313
- "off",
55314
- "on",
55315
- "idle",
55316
- "playing",
55317
- "paused",
55318
- "buffering",
55319
- "standby"
55320
- ]);
55321
- var MediaPlayerRepeatSchema = _enum([
55322
- "off",
55323
- "all",
55324
- "one"
55325
- ]);
55326
- var MediaInfoSchema = object({
55327
- /** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
55328
- * `podcast`, …). */
55329
- type: string(),
55330
- /** Human-readable title. */
55331
- title: string(),
55332
- /** Optional artist / channel / station label. */
55333
- artist: string().optional(),
55334
- /** Optional album / season / show name. */
55335
- album: string().optional(),
55336
- /** Optional cover-art / thumbnail URL. */
55337
- imageUrl: string().optional()
55338
- });
55339
- /**
55340
- * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
55341
- * the device said so. `assumed`: the driver has no feedback (infrared) and
55342
- * tracks what it last SENT; it moves only after a send was accepted, and a
55343
- * human at the real remote makes it wrong. A UI shows an assumed value as a
55344
- * guess, never as a reading.
55345
- */
55346
- var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
55347
- /** The fields of a media-player status a driver can only guess. */
55348
- var MediaPlayerAssumedFieldSchema = _enum([
55349
- "state",
55350
- "volumeLevel",
55351
- "isMuted",
55352
- "source"
55353
- ]);
55354
- object({
55355
- /** Playback lifecycle state. */
55356
- state: MediaPlayerStateSchema,
55357
- /**
55358
- * Whether the state fields are readings or the driver's own bookkeeping.
55359
- * A slice written before this field existed parses as `reported`.
55360
- */
55361
- stateSource: MediaPlayerStateSourceSchema.default("reported"),
55362
- /**
55363
- * Which fields are the driver's own guess. `stateSource` is `assumed` when
55364
- * this is not empty; a field not listed here is a reading even then (power
55365
- * from a running-state source, mute guessed from the last send).
55366
- */
55367
- assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
55368
- /** Volume as 0..100 inclusive. Null when the device has no volume
55369
- * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
55370
- volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
55371
- /** Mute toggle distinct from volume=0. Null when no mute surface.
55372
- * Pair with `DeviceFeature.MediaPlayerMute`. */
55373
- isMuted: boolean().nullable(),
55374
- /** Active source / input. Empty when no source surface. */
55375
- source: string(),
55376
- /** Selectable sources. Empty when no source surface.
55377
- * Pair with `DeviceFeature.MediaPlayerSelectSource`. */
55378
- availableSources: array(string()),
55379
- /** Currently-playing media info. Null when nothing is playing. */
55380
- currentMedia: MediaInfoSchema.nullable(),
55381
- /** Current playback position in ms. Null when not seekable or
55382
- * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
55383
- position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
55384
- /** Total duration of the current media in ms. Null when unknown
55385
- * (live stream) or nothing is playing. */
55386
- duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
55387
- /** Shuffle toggle. Null when no shuffle surface.
55388
- * Pair with `DeviceFeature.MediaPlayerShuffle`. */
55389
- shuffle: boolean().nullable(),
55390
- /** Repeat mode. Null when no repeat surface.
55391
- * Pair with `DeviceFeature.MediaPlayerRepeat`. */
55392
- repeat: MediaPlayerRepeatSchema.nullable(),
55393
- /** Ms epoch when the slice was last updated. */
55394
- lastChangedAt: number()
55395
- });
55396
- DeviceType.MediaPlayer, method(object({ deviceId: number().int().nonnegative() }), _void(), {
55397
- kind: "mutation",
55398
- auth: "protected"
55399
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55400
- kind: "mutation",
55401
- auth: "protected"
55402
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55403
- kind: "mutation",
55404
- auth: "protected"
55405
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55406
- kind: "mutation",
55407
- auth: "protected"
55408
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55409
- kind: "mutation",
55410
- auth: "protected"
55411
- }), method(object({
55412
- deviceId: number().int().nonnegative(),
55413
- position: number().int().nonnegative()
55414
- }), _void(), {
55415
- kind: "mutation",
55416
- auth: "protected"
55417
- }), method(object({
55418
- deviceId: number().int().nonnegative(),
55419
- volumeLevel: number().min(0).max(100)
55420
- }), _void(), {
55421
- kind: "mutation",
55422
- auth: "protected"
55423
- }), method(object({
55424
- deviceId: number().int().nonnegative(),
55425
- muted: boolean()
55426
- }), _void(), {
55427
- kind: "mutation",
55428
- auth: "protected"
55429
- }), method(object({
55430
- deviceId: number().int().nonnegative(),
55431
- shuffle: boolean()
55432
- }), _void(), {
55433
- kind: "mutation",
55434
- auth: "protected"
55435
- }), method(object({
55436
- deviceId: number().int().nonnegative(),
55437
- repeat: MediaPlayerRepeatSchema
55438
- }), _void(), {
55439
- kind: "mutation",
55440
- auth: "protected"
55441
- }), method(object({
55442
- deviceId: number().int().nonnegative(),
55443
- source: string().min(1)
55444
- }), _void(), {
55445
- kind: "mutation",
55446
- auth: "protected"
55447
- }), method(object({
55448
- deviceId: number().int().nonnegative(),
55449
- /** Media identifier / URL. */
55450
- mediaId: string().min(1),
55451
- /** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
55452
- * passes it through to the upstream service. */
55453
- mediaType: string().min(1)
55454
- }), _void(), {
55455
- kind: "mutation",
55456
- auth: "protected"
55457
- });
55458
- /**
55459
55754
  * mesh-network — collection cap for mesh-VPN providers.
55460
55755
  *
55461
55756
  * Different shape from `network-access` (public tunnels):
@@ -63349,7 +63644,14 @@ var NcMediaPolicySchema = object({
63349
63644
  */
63350
63645
  profile: CamProfileSchema.optional(),
63351
63646
  /** See {@link NcLinkedCameraModeSchema}. */
63352
- linkedCameras: NcLinkedCameraModeSchema.optional()
63647
+ linkedCameras: NcLinkedCameraModeSchema.optional(),
63648
+ /**
63649
+ * Sensor rules only: may this rule wake a SLEEPING battery camera to take
63650
+ * the linked still it attaches (D998)? Absent means yes, an explicit `false`
63651
+ * opts out. Deliberately not a Zod default: a rule that never chose keeps
63652
+ * following the hub's. One bounded wake per event; nothing is attached later.
63653
+ */
63654
+ wakeSleepingCamera: boolean().optional()
63353
63655
  });
63354
63656
  /**
63355
63657
  * Cooldown GRANULARITY over the subject's class — how much a fired
@@ -63741,6 +64043,21 @@ var NcTemplateVarKindSchema = _enum([
63741
64043
  "sound",
63742
64044
  "system"
63743
64045
  ]);
64046
+ /**
64047
+ * The default title/body a rule of one SITUATION is delivered with when the
64048
+ * operator wrote no template — served so an editor can pre-fill it (and the
64049
+ * operator can rebuild it from placeholders) instead of starting from a blank
64050
+ * box that, once saved, silently replaces it. `id` names the situation inside
64051
+ * a rule `kind`: `appliance-cycle` is a `sensor` rule whose device is an
64052
+ * appliance finishing a cycle. Texts are raw templates (`{{var}}` intact), in
64053
+ * the hub's current language and with its overrides applied.
64054
+ */
64055
+ var NcTemplateDefaultSchema = object({
64056
+ id: _enum(["appliance-cycle"]),
64057
+ kind: NcRuleKindSchema,
64058
+ title: string(),
64059
+ body: string()
64060
+ });
63744
64061
  /** Which producer renders a template: an ordinary rule, a timelapse, a digest. */
63745
64062
  var NcTemplateFamilySchema = _enum([
63746
64063
  "rule",
@@ -64596,7 +64913,11 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
64596
64913
  }), method(object({}), object({
64597
64914
  catalog: array(NcConditionDescriptorSchema),
64598
64915
  taxonomy: NcTaxonomySchema.optional()
64599
- })), method(object({}), object({ vars: array(NcTemplateVarDescriptorSchema) })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
64916
+ })), method(object({}), object({
64917
+ vars: array(NcTemplateVarDescriptorSchema),
64918
+ /** Per-situation default texts; absent on a hub that predates them. */
64919
+ defaults: array(NcTemplateDefaultSchema)
64920
+ })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
64600
64921
  kind: "mutation",
64601
64922
  access: "view"
64602
64923
  }), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), NcRuleLastRunsSchema, { auth: "admin" }), method(object({ artifactId: string().min(1) }), object({ url: string().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
@@ -65344,52 +65665,27 @@ object({
65344
65665
  empty: boolean().nullable(),
65345
65666
  /** True while the pump is running. Null while unknown. */
65346
65667
  pumpRunning: boolean().nullable(),
65347
- /** True when the pump works on an on/off duty cycle rather than continuously. */
65348
- pumpIntervalEnabled: boolean().nullable(),
65349
- /** How long the pump runs per cycle, in seconds. */
65350
- pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
65351
- /** How long the pump rests per cycle, in seconds. */
65352
- pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
65353
65668
  /** Water drunk in the last drink, in litres. */
65354
65669
  lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
65355
65670
  /** Water drunk in the current hour, in litres. */
65356
65671
  currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
65357
65672
  /** Water drunk over the last 24 hours (rolling), in litres. */
65358
65673
  last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
65359
- /** Water drunk since local midnight, in litres. */
65674
+ /** Water drunk since local midnight, in litres. Null when the fountain has no such sensor. */
65360
65675
  todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
65361
65676
  /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
65362
65677
  error: string().nullable(),
65363
65678
  /** Ms epoch when the slice was last refreshed. */
65364
65679
  lastFetchedAt: number()
65365
65680
  });
65366
- var DeviceIdSchema = number().int().nonnegative();
65367
- var MinutesSchema = number().int().min(0);
65681
+ var DeviceIdSchema$1 = number().int().nonnegative();
65368
65682
  DeviceType.PetFountain, method(object({
65369
- deviceId: DeviceIdSchema,
65370
- on: boolean()
65371
- }), _void(), {
65372
- kind: "mutation",
65373
- auth: "protected"
65374
- }), method(object({
65375
- deviceId: DeviceIdSchema,
65683
+ deviceId: DeviceIdSchema$1,
65376
65684
  on: boolean()
65377
65685
  }), _void(), {
65378
65686
  kind: "mutation",
65379
65687
  auth: "protected"
65380
- }), method(object({
65381
- deviceId: DeviceIdSchema,
65382
- minutes: MinutesSchema
65383
- }), _void(), {
65384
- kind: "mutation",
65385
- auth: "protected"
65386
- }), method(object({
65387
- deviceId: DeviceIdSchema,
65388
- minutes: MinutesSchema
65389
- }), _void(), {
65390
- kind: "mutation",
65391
- auth: "protected"
65392
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
65688
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
65393
65689
  kind: "mutation",
65394
65690
  auth: "protected"
65395
65691
  });
@@ -67349,6 +67645,37 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
67349
67645
  deviceId: number(),
67350
67646
  status: PtzAutotrackStatusSchema
67351
67647
  });
67648
+ object({
67649
+ /** True when the pump works on the on/off cycle, false when it runs continuously. Null while unknown. */
67650
+ enabled: boolean().nullable(),
67651
+ /** How long the pump runs per cycle, in seconds. Null while unknown. */
67652
+ onTime: number().min(0).nullable().meta(readingMeta("duration")),
67653
+ /** How long the pump rests per cycle, in seconds. Null while unknown. */
67654
+ offTime: number().min(0).nullable().meta(readingMeta("duration")),
67655
+ /** Ms epoch when the slice was last refreshed. */
67656
+ lastFetchedAt: number()
67657
+ });
67658
+ var DeviceIdSchema = number().int().nonnegative();
67659
+ var MinutesSchema = number().int().min(0);
67660
+ DeviceType.PetFountain, method(object({
67661
+ deviceId: DeviceIdSchema,
67662
+ on: boolean()
67663
+ }), _void(), {
67664
+ kind: "mutation",
67665
+ auth: "protected"
67666
+ }), method(object({
67667
+ deviceId: DeviceIdSchema,
67668
+ minutes: MinutesSchema
67669
+ }), _void(), {
67670
+ kind: "mutation",
67671
+ auth: "protected"
67672
+ }), method(object({
67673
+ deviceId: DeviceIdSchema,
67674
+ minutes: MinutesSchema
67675
+ }), _void(), {
67676
+ kind: "mutation",
67677
+ auth: "protected"
67678
+ });
67352
67679
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
67353
67680
  kind: "mutation",
67354
67681
  auth: "admin"
@@ -70062,7 +70389,13 @@ var RemoteMediaCommandSchema = _enum([
70062
70389
  "next",
70063
70390
  "previous",
70064
70391
  "setMute",
70065
- "selectSource"
70392
+ "selectSource",
70393
+ "turnOn",
70394
+ "turnOff",
70395
+ "volumeUp",
70396
+ "volumeDown",
70397
+ "channelUp",
70398
+ "channelDown"
70066
70399
  ]);
70067
70400
  /** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
70068
70401
  var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
@@ -70083,7 +70416,7 @@ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.e
70083
70416
  }), RemoteSendResultSchema, {
70084
70417
  kind: "mutation",
70085
70418
  auth: "protected"
70086
- }), method(deviceInput.extend({
70419
+ }), method(deviceInput, MediaPlayerStatusSchema.nullable(), { auth: "protected" }), method(deviceInput.extend({
70087
70420
  command: RemoteMediaCommandSchema,
70088
70421
  muted: boolean().optional(),
70089
70422
  source: string().optional(),
@@ -71111,10 +71444,12 @@ DeviceType.Camera, method(object({
71111
71444
  * even when the cache is well within the device's
71112
71445
  * `snapshotMaxAgeS` window.
71113
71446
  *
71114
- * **`force` is an OPERATOR signal, not a freshness preference.** On a
71447
+ * **`force` is a demand with a named principal, not a freshness
71448
+ * preference** — an operator button, the scheduled refresh (D847), or a
71449
+ * notification rule that left the camera's wake allowed (D998). On a
71115
71450
  * battery camera it is the one thing that walks past the wrapper's
71116
- * sleep gate and wakes the camera, so a background caller — a poller,
71117
- * an event handler, a thumbnail — must NEVER set it. Every such caller
71451
+ * sleep gate and wakes the camera, so a poller, a thumbnail or a
71452
+ * prestream must NEVER set it. Every such caller
71118
71453
  * gets the cached frame, which on a sleeping battery camera is the
71119
71454
  * correct answer: stale but honest beats woken.
71120
71455
  */
@@ -71922,7 +72257,11 @@ object({
71922
72257
  });
71923
72258
  Object.values(DeviceType), method(_void(), array(StreamSignalSchema).readonly());
71924
72259
  object({
71925
- on: boolean().meta({ label: "On" }),
72260
+ /**
72261
+ * `null` = the device has never reported its state (a measurement that
72262
+ * has not happened is unknown, never `false`).
72263
+ */
72264
+ on: boolean().nullable().meta({ label: "On" }),
71926
72265
  /** Ms epoch of the last state change. Useful for UI "X minutes ago". */
71927
72266
  lastChangedAt: number()
71928
72267
  });
@@ -71934,7 +72273,7 @@ DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceTyp
71934
72273
  auth: "protected"
71935
72274
  }), object({
71936
72275
  deviceId: number(),
71937
- on: boolean(),
72276
+ on: boolean().nullable(),
71938
72277
  lastChangedAt: number()
71939
72278
  });
71940
72279
  /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
@@ -74946,6 +75285,7 @@ function buildDescriptor(capName, kind) {
74946
75285
  * contributes one entry per bound cap present in this map.
74947
75286
  */
74948
75287
  var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
75288
+ DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
74949
75289
  var btn = (idSuffix, row, col, init) => ({
74950
75290
  idSuffix,
74951
75291
  row,
@@ -75072,7 +75412,7 @@ discriminatedUnion("kind", [object({
75072
75412
  kind: literal("pronto"),
75073
75413
  code: string()
75074
75414
  })]);
75075
- DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
75415
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
75076
75416
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
75077
75417
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
75078
75418
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
@@ -76053,6 +76393,27 @@ Object.freeze({
76053
76393
  access: "view",
76054
76394
  role: "viewer"
76055
76395
  },
76396
+ "boardKeySource.getBoardKey": {
76397
+ capName: "board-key-source",
76398
+ capScope: "system",
76399
+ addonId: null,
76400
+ access: "view",
76401
+ role: "admin"
76402
+ },
76403
+ "boardKeySource.listBoards": {
76404
+ capName: "board-key-source",
76405
+ capScope: "system",
76406
+ addonId: null,
76407
+ access: "view",
76408
+ role: "admin"
76409
+ },
76410
+ "boardKeySource.listSources": {
76411
+ capName: "board-key-source",
76412
+ capScope: "system",
76413
+ addonId: null,
76414
+ access: "view",
76415
+ role: "admin"
76416
+ },
76056
76417
  "brightness.getStatus": {
76057
76418
  capName: "brightness",
76058
76419
  capScope: "device",
@@ -78734,6 +79095,13 @@ Object.freeze({
78734
79095
  access: "view",
78735
79096
  role: "admin"
78736
79097
  },
79098
+ "integrationInstances.setInstanceEnabled": {
79099
+ capName: "integration-instances",
79100
+ capScope: "system",
79101
+ addonId: null,
79102
+ access: "create",
79103
+ role: "admin"
79104
+ },
78737
79105
  "integrationInstances.testInstance": {
78738
79106
  capName: "integration-instances",
78739
79107
  capScope: "system",
@@ -79532,6 +79900,20 @@ Object.freeze({
79532
79900
  access: "view",
79533
79901
  role: "admin"
79534
79902
  },
79903
+ "mediaPlayer.channelDown": {
79904
+ capName: "media-player",
79905
+ capScope: "device",
79906
+ addonId: null,
79907
+ access: "create",
79908
+ role: "viewer"
79909
+ },
79910
+ "mediaPlayer.channelUp": {
79911
+ capName: "media-player",
79912
+ capScope: "device",
79913
+ addonId: null,
79914
+ access: "create",
79915
+ role: "viewer"
79916
+ },
79535
79917
  "mediaPlayer.getStatus": {
79536
79918
  capName: "media-player",
79537
79919
  capScope: "device",
@@ -79623,6 +80005,34 @@ Object.freeze({
79623
80005
  access: "create",
79624
80006
  role: "viewer"
79625
80007
  },
80008
+ "mediaPlayer.turnOff": {
80009
+ capName: "media-player",
80010
+ capScope: "device",
80011
+ addonId: null,
80012
+ access: "create",
80013
+ role: "viewer"
80014
+ },
80015
+ "mediaPlayer.turnOn": {
80016
+ capName: "media-player",
80017
+ capScope: "device",
80018
+ addonId: null,
80019
+ access: "create",
80020
+ role: "viewer"
80021
+ },
80022
+ "mediaPlayer.volumeDown": {
80023
+ capName: "media-player",
80024
+ capScope: "device",
80025
+ addonId: null,
80026
+ access: "create",
80027
+ role: "viewer"
80028
+ },
80029
+ "mediaPlayer.volumeUp": {
80030
+ capName: "media-player",
80031
+ capScope: "device",
80032
+ addonId: null,
80033
+ access: "create",
80034
+ role: "viewer"
80035
+ },
79626
80036
  "meshNetwork.getStatus": {
79627
80037
  capName: "mesh-network",
79628
80038
  capScope: "system",
@@ -80869,27 +81279,6 @@ Object.freeze({
80869
81279
  access: "create",
80870
81280
  role: "viewer"
80871
81281
  },
80872
- "petFountain.setPumpInterval": {
80873
- capName: "pet-fountain",
80874
- capScope: "device",
80875
- addonId: null,
80876
- access: "create",
80877
- role: "viewer"
80878
- },
80879
- "petFountain.setPumpOffMinutes": {
80880
- capName: "pet-fountain",
80881
- capScope: "device",
80882
- addonId: null,
80883
- access: "create",
80884
- role: "viewer"
80885
- },
80886
- "petFountain.setPumpOnMinutes": {
80887
- capName: "pet-fountain",
80888
- capScope: "device",
80889
- addonId: null,
80890
- access: "create",
80891
- role: "viewer"
80892
- },
80893
81282
  "pipelineAnalytics.addRetrainFrameFromRecording": {
80894
81283
  capName: "pipeline-analytics",
80895
81284
  capScope: "device",
@@ -82668,6 +83057,34 @@ Object.freeze({
82668
83057
  access: "create",
82669
83058
  role: "admin"
82670
83059
  },
83060
+ "pumpCycle.getStatus": {
83061
+ capName: "pump-cycle",
83062
+ capScope: "device",
83063
+ addonId: null,
83064
+ access: "view",
83065
+ role: "viewer"
83066
+ },
83067
+ "pumpCycle.setEnabled": {
83068
+ capName: "pump-cycle",
83069
+ capScope: "device",
83070
+ addonId: null,
83071
+ access: "create",
83072
+ role: "viewer"
83073
+ },
83074
+ "pumpCycle.setOffMinutes": {
83075
+ capName: "pump-cycle",
83076
+ capScope: "device",
83077
+ addonId: null,
83078
+ access: "create",
83079
+ role: "viewer"
83080
+ },
83081
+ "pumpCycle.setOnMinutes": {
83082
+ capName: "pump-cycle",
83083
+ capScope: "device",
83084
+ addonId: null,
83085
+ access: "create",
83086
+ role: "viewer"
83087
+ },
82671
83088
  "reboot.reboot": {
82672
83089
  capName: "reboot",
82673
83090
  capScope: "device",
@@ -83172,6 +83589,13 @@ Object.freeze({
83172
83589
  access: "create",
83173
83590
  role: "admin"
83174
83591
  },
83592
+ "remote.getAssumedStatus": {
83593
+ capName: "remote",
83594
+ capScope: "system",
83595
+ addonId: null,
83596
+ access: "view",
83597
+ role: "admin"
83598
+ },
83175
83599
  "remote.getConfig": {
83176
83600
  capName: "remote",
83177
83601
  capScope: "system",
@@ -86838,6 +87262,16 @@ Object.freeze({
86838
87262
  form: "single",
86839
87263
  optional: true
86840
87264
  }],
87265
+ "mediaPlayer.channelDown": [{
87266
+ name: "deviceId",
87267
+ form: "single",
87268
+ optional: false
87269
+ }],
87270
+ "mediaPlayer.channelUp": [{
87271
+ name: "deviceId",
87272
+ form: "single",
87273
+ optional: false
87274
+ }],
86841
87275
  "mediaPlayer.getStatus": [{
86842
87276
  name: "deviceId",
86843
87277
  form: "single",
@@ -86903,6 +87337,26 @@ Object.freeze({
86903
87337
  form: "single",
86904
87338
  optional: false
86905
87339
  }],
87340
+ "mediaPlayer.turnOff": [{
87341
+ name: "deviceId",
87342
+ form: "single",
87343
+ optional: false
87344
+ }],
87345
+ "mediaPlayer.turnOn": [{
87346
+ name: "deviceId",
87347
+ form: "single",
87348
+ optional: false
87349
+ }],
87350
+ "mediaPlayer.volumeDown": [{
87351
+ name: "deviceId",
87352
+ form: "single",
87353
+ optional: false
87354
+ }],
87355
+ "mediaPlayer.volumeUp": [{
87356
+ name: "deviceId",
87357
+ form: "single",
87358
+ optional: false
87359
+ }],
86906
87360
  "meter.getStatus": [{
86907
87361
  name: "deviceId",
86908
87362
  form: "single",
@@ -87212,21 +87666,6 @@ Object.freeze({
87212
87666
  form: "single",
87213
87667
  optional: false
87214
87668
  }],
87215
- "petFountain.setPumpInterval": [{
87216
- name: "deviceId",
87217
- form: "single",
87218
- optional: false
87219
- }],
87220
- "petFountain.setPumpOffMinutes": [{
87221
- name: "deviceId",
87222
- form: "single",
87223
- optional: false
87224
- }],
87225
- "petFountain.setPumpOnMinutes": [{
87226
- name: "deviceId",
87227
- form: "single",
87228
- optional: false
87229
- }],
87230
87669
  "pipelineAnalytics.addRetrainFrameFromRecording": [{
87231
87670
  name: "deviceId",
87232
87671
  form: "single",
@@ -87862,6 +88301,26 @@ Object.freeze({
87862
88301
  form: "single",
87863
88302
  optional: false
87864
88303
  }],
88304
+ "pumpCycle.getStatus": [{
88305
+ name: "deviceId",
88306
+ form: "single",
88307
+ optional: false
88308
+ }],
88309
+ "pumpCycle.setEnabled": [{
88310
+ name: "deviceId",
88311
+ form: "single",
88312
+ optional: false
88313
+ }],
88314
+ "pumpCycle.setOffMinutes": [{
88315
+ name: "deviceId",
88316
+ form: "single",
88317
+ optional: false
88318
+ }],
88319
+ "pumpCycle.setOnMinutes": [{
88320
+ name: "deviceId",
88321
+ form: "single",
88322
+ optional: false
88323
+ }],
87865
88324
  "reboot.reboot": [{
87866
88325
  name: "deviceId",
87867
88326
  form: "single",
@@ -88063,6 +88522,11 @@ Object.freeze({
88063
88522
  form: "single",
88064
88523
  optional: true
88065
88524
  }],
88525
+ "remote.getAssumedStatus": [{
88526
+ name: "deviceId",
88527
+ form: "single",
88528
+ optional: false
88529
+ }],
88066
88530
  "remote.getConfig": [{
88067
88531
  name: "deviceId",
88068
88532
  form: "single",
@@ -89318,6 +89782,7 @@ var STATE_SUMMARY_VARS = [
89318
89782
  [...RULE_TEXT],
89319
89783
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
89320
89784
  ([...RULE_TEXT], [...COMBINED_ALARM]),
89785
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
89321
89786
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
89322
89787
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
89323
89788
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -90079,13 +90544,20 @@ var GoogleBrokerEntrySchema = object({
90079
90544
  calendarFilter: string().default(""),
90080
90545
  /** Default From for Gmail send — usually the connected account email. */
90081
90546
  fromAddress: string().default(""),
90082
- fromName: string().default("")
90547
+ fromName: string().default(""),
90548
+ /**
90549
+ * The stamped calendar ids (`<brokerId>:<googleCalendarId>`) this broker imports. `undefined`
90550
+ * means "not moved here yet": the reader falls back to the shared `calendarIds` setting of
90551
+ * the addon until the migration copied it. Empty = import none.
90552
+ */
90553
+ calendarIds: array(string()).optional()
90083
90554
  });
90084
90555
  var GoogleServicesConfigSchema = object({
90085
90556
  brokers: array(GoogleBrokerEntrySchema).default([]),
90086
90557
  /**
90087
- * Stamped calendar ids (`brokerId:googleCalendarId`) to import.
90088
- * Empty = import none. Edited under Connectivity → Calendars.
90558
+ * @deprecated The shared set (`brokerId:googleCalendarId`). Each broker owns its own
90559
+ * `calendarIds` now; this key is read only while a broker has none, and the migration
90560
+ * `google-services/2026-11-03-drop-shared-calendar-ids` deletes it.
90089
90561
  */
90090
90562
  calendarIds: array(string()).default([]),
90091
90563
  /** @deprecated Pre-broker singleton — cleared after migrateLegacyToBrokers. */
@@ -90163,14 +90635,15 @@ function settingsToBrokerFields(settings) {
90163
90635
  clientSecret: parsed.clientSecret,
90164
90636
  calendarFilter: calendarIdsToFilter(parsed),
90165
90637
  fromAddress: parsed.fromAddress ?? "",
90166
- fromName: parsed.fromName ?? ""
90638
+ fromName: parsed.fromName ?? "",
90639
+ ...parsed.calendarIds !== void 0 ? { calendarIds: [...parsed.calendarIds] } : {}
90167
90640
  };
90168
90641
  }
90169
90642
  function brokerEntryToSettings(entry) {
90170
90643
  return {
90171
90644
  clientId: entry.clientId,
90172
90645
  clientSecret: entry.clientSecret,
90173
- calendarIds: [...parseCalendarFilter(entry.calendarFilter)],
90646
+ calendarIds: [...entry.calendarIds ?? parseCalendarFilter(entry.calendarFilter)],
90174
90647
  fromAddress: entry.fromAddress,
90175
90648
  fromName: entry.fromName
90176
90649
  };
@@ -90221,6 +90694,13 @@ function migrateLegacyToBrokers(rawConfig, legacyOauth) {
90221
90694
  didMigrate: true
90222
90695
  };
90223
90696
  }
90697
+ /**
90698
+ * The stamped calendar ids one broker imports: its own set, or — while the migration has not
90699
+ * reached it — the shared setting older versions wrote, so the move never changes what is imported.
90700
+ */
90701
+ function calendarIdsOfEntry(entry, shared) {
90702
+ return new Set(entry?.calendarIds ?? shared);
90703
+ }
90224
90704
  function parseCalendarFilter(raw) {
90225
90705
  const trimmed = raw.trim();
90226
90706
  if (!trimmed) return /* @__PURE__ */ new Set();
@@ -90307,6 +90787,7 @@ var GoogleBrokerRegistry = class {
90307
90787
  const entry = GoogleBrokerEntrySchema.parse({
90308
90788
  id,
90309
90789
  name,
90790
+ calendarIds: [],
90310
90791
  ...fields
90311
90792
  });
90312
90793
  this.entries = [...this.entries, entry];
@@ -90320,6 +90801,7 @@ var GoogleBrokerRegistry = class {
90320
90801
  const next = GoogleBrokerEntrySchema.parse({
90321
90802
  id: prev.id,
90322
90803
  name: prev.name,
90804
+ ...prev.calendarIds !== void 0 ? { calendarIds: prev.calendarIds } : {},
90323
90805
  ...fields
90324
90806
  });
90325
90807
  this.entries = this.entries.map((e, i) => i === idx ? next : e);
@@ -90525,6 +91007,24 @@ function buildGoogleBrokerSettingsSchema(publicOrigin) {
90525
91007
  showToggle: true
90526
91008
  }]
90527
91009
  },
91010
+ {
91011
+ id: "google-calendars-import",
91012
+ title: "Calendars to import",
91013
+ titleKey: "google-services:googleBrokerSettings.s_google-calendars-import.title",
91014
+ description: "Nothing selected = no calendars imported from this account. Options appear once the account is signed in.",
91015
+ descriptionKey: "google-services:googleBrokerSettings.s_google-calendars-import.description",
91016
+ columns: 1,
91017
+ fields: [{
91018
+ type: "multiselect",
91019
+ key: "calendarIds",
91020
+ label: "Calendars",
91021
+ labelKey: "google-services:googleBrokerSettings.s_google-calendars-import.calendarIds.label",
91022
+ options: [],
91023
+ optionsFrom: "_calendarCatalog",
91024
+ description: "The calendars of this Google account that CamStack imports.",
91025
+ descriptionKey: "google-services:googleBrokerSettings.s_google-calendars-import.calendarIds.description"
91026
+ }]
91027
+ },
90528
91028
  {
90529
91029
  id: "gmail-send",
90530
91030
  title: "Gmail send",
@@ -90548,7 +91048,7 @@ function buildGoogleBrokerSettingsSchema(publicOrigin) {
90548
91048
  }
90549
91049
  ] };
90550
91050
  }
90551
- /** Connectivity Calendars / Email pages — calendar import picker lives here (not on broker Add). */
91051
+ /** Connectivity Calendars / Email pages — the calendar import picker is on each broker. */
90552
91052
  function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
90553
91053
  const redirect = escapeHtml(redirectUriDisplay(publicOrigin));
90554
91054
  if (cap === "calendar-source") return { sections: [{
@@ -90564,27 +91064,10 @@ function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
90564
91064
  format: "html",
90565
91065
  content: [
90566
91066
  "<p>Link a Google account under Connectivity → <strong>Brokers</strong> ",
90567
- "(Client ID / secret + Sign in). Then pick calendars below. ",
91067
+ "(Client ID / secret + Sign in). Pick the calendars to import on each broker. ",
90568
91068
  `OAuth redirect URI: <code>${redirect}</code>.</p>`
90569
91069
  ].join("")
90570
91070
  }]
90571
- }, {
90572
- id: "google-calendars-import",
90573
- title: "Calendars to import",
90574
- titleKey: "google-services:googleFeatureSettings.s_google-calendars-import.title",
90575
- description: "Nothing selected = no calendars imported. Options appear after at least one Google broker is signed in.",
90576
- descriptionKey: "google-services:googleFeatureSettings.s_google-calendars-import.description",
90577
- columns: 1,
90578
- fields: [{
90579
- type: "multiselect",
90580
- key: "calendarIds",
90581
- label: "Calendars",
90582
- labelKey: "google-services:googleFeatureSettings.s_google-calendars-import.calendarIds.label",
90583
- options: [],
90584
- optionsFrom: "_calendarCatalog",
90585
- description: "Ids are stamped as brokerId:googleCalendarId across every Google broker.",
90586
- descriptionKey: "google-services:googleFeatureSettings.s_google-calendars-import.calendarIds.description"
90587
- }]
90588
91071
  }] };
90589
91072
  return { sections: [{
90590
91073
  id: "setup-intro",
@@ -90674,11 +91157,11 @@ function createGoogleCalendarApi(getAccessToken, http = { fetch }) {
90674
91157
  function createGoogleCalendarSource(deps) {
90675
91158
  return {
90676
91159
  listCalendars: async () => {
90677
- const selected = deps.selectedCalendarIds();
90678
- if (selected.size === 0) return [];
90679
91160
  const out = [];
90680
91161
  for (const account of deps.accounts()) {
90681
91162
  if (!account.connected) continue;
91163
+ const selected = deps.selectedCalendarIds(account.brokerId);
91164
+ if (selected.size === 0) continue;
90682
91165
  try {
90683
91166
  const list = await account.api.listCalendars();
90684
91167
  for (const c of list) {
@@ -90701,20 +91184,18 @@ function createGoogleCalendarSource(deps) {
90701
91184
  return out;
90702
91185
  },
90703
91186
  listEvents: async (input) => {
90704
- const selected = deps.selectedCalendarIds();
90705
- if (selected.size === 0) return [];
90706
91187
  const timeMin = new Date(input.startMs).toISOString();
90707
91188
  const timeMax = new Date(input.endMs).toISOString();
90708
91189
  const accounts = deps.accounts().filter((a) => a.connected);
90709
91190
  const byBroker = new Map(accounts.map((a) => [a.brokerId, a]));
90710
- const requestIds = input.calendarIds !== void 0 && input.calendarIds.length > 0 ? input.calendarIds : [...selected];
91191
+ const requestIds = input.calendarIds !== void 0 && input.calendarIds.length > 0 ? input.calendarIds : [...new Set(deps.accounts().flatMap((account) => [...deps.selectedCalendarIds(account.brokerId)].filter((stamped) => parseStampedCalendarId(stamped)?.brokerId === account.brokerId)))];
90711
91192
  /** brokerId → google calendar ids to fetch */
90712
91193
  const wanted = /* @__PURE__ */ new Map();
90713
91194
  for (const stamped of requestIds) {
90714
- if (!selected.has(stamped)) continue;
90715
91195
  const parsed = parseStampedCalendarId(stamped);
90716
91196
  if (parsed === null) continue;
90717
91197
  if (!byBroker.has(parsed.brokerId)) continue;
91198
+ if (!deps.selectedCalendarIds(parsed.brokerId).has(stamped)) continue;
90718
91199
  const list = wanted.get(parsed.brokerId) ?? [];
90719
91200
  list.push(parsed.googleCalendarId);
90720
91201
  wanted.set(parsed.brokerId, list);
@@ -91063,11 +91544,152 @@ var googleBrokersEntryKind = defineEntryKind("brokers", {
91063
91544
  clientSecret: GoogleBrokerEntrySchema.shape.clientSecret,
91064
91545
  calendarFilter: GoogleBrokerEntrySchema.shape.calendarFilter,
91065
91546
  fromAddress: GoogleBrokerEntrySchema.shape.fromAddress,
91066
- fromName: GoogleBrokerEntrySchema.shape.fromName
91547
+ fromName: GoogleBrokerEntrySchema.shape.fromName,
91548
+ calendarIds: GoogleBrokerEntrySchema.shape.calendarIds
91067
91549
  }),
91068
91550
  writeTrigger: "an operator adds or edits a Google OAuth broker"
91069
91551
  });
91070
91552
  //#endregion
91553
+ //#region src/migrations/calendar-ids-to-brokers.ts
91554
+ /**
91555
+ * The calendars a Google account imports are a fact of THAT account (D985): each broker record
91556
+ * carries its own `calendarIds`, and the addon-level `calendarIds` setting (one shared set of
91557
+ * `<brokerId>:<googleCalendarId>` ids) goes away.
91558
+ *
91559
+ * Two migrations, in this order:
91560
+ *
91561
+ * 1. `calendar-ids-to-brokers` (procedure) copies the shared set to EVERY broker record that has
91562
+ * no `calendarIds` yet. A record that already has one is never overwritten; a broker deleted
91563
+ * meanwhile is skipped, never recreated; a stored value that is not a list of strings is an
91564
+ * ambiguity and throws (nothing is guessed).
91565
+ * 2. `drop-shared-calendar-ids` (plan) deletes the shared setting, but only after a read-back:
91566
+ * every live broker must show its own `calendarIds`. When one of them holds a set that differs
91567
+ * from the shared one (an operator choice made before the copy), the shared value is the
91568
+ * loser and stays PARKED where it is, never deleted.
91569
+ *
91570
+ * Both are idempotent: with the copy landed the first reports `already-done`, and with the row
91571
+ * gone the second plans nothing.
91572
+ */
91573
+ var GOOGLE_ADDON_ID = "google-services";
91574
+ /** The canonical settings table: one row per key, id `<addonId>.<key>`. */
91575
+ var ADDON_SETTINGS_TABLE = "addon-settings";
91576
+ /** The legacy shared set. */
91577
+ var SHARED_CALENDAR_IDS_KEY = "calendarIds";
91578
+ async function readSharedSet(ports) {
91579
+ const answer = await ports.readStored(SHARED_CALENDAR_IDS_KEY);
91580
+ if (answer.kind === "unreadable") throw new Error(`google-services: the shared calendarIds cannot be read (${answer.reason})`);
91581
+ if (answer.kind === "absent") return null;
91582
+ const value = answer.value;
91583
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new Error("google-services: the shared calendarIds is not a list of ids — nothing copied");
91584
+ return value;
91585
+ }
91586
+ async function copyCalendarIdsToBrokers(ports) {
91587
+ const shared = await readSharedSet(ports);
91588
+ if (shared === null) return {
91589
+ outcome: "already-done",
91590
+ summary: { reason: "no-shared-set" }
91591
+ };
91592
+ let copied = 0;
91593
+ let kept = 0;
91594
+ let gone = 0;
91595
+ for (const broker of await ports.listBrokers()) {
91596
+ if (broker.calendarIds !== void 0) {
91597
+ kept += 1;
91598
+ continue;
91599
+ }
91600
+ if (await ports.setBrokerCalendarIds(broker.id, shared)) copied += 1;
91601
+ else gone += 1;
91602
+ }
91603
+ return {
91604
+ outcome: copied === 0 ? "already-done" : "done",
91605
+ summary: {
91606
+ copied,
91607
+ kept,
91608
+ gone
91609
+ }
91610
+ };
91611
+ }
91612
+ var sameSet = (a, b) => {
91613
+ const left = new Set(a);
91614
+ const right = new Set(b);
91615
+ return left.size === right.size && [...left].every((id) => right.has(id));
91616
+ };
91617
+ async function planDropSharedCalendarIds(ports) {
91618
+ const shared = await readSharedSet(ports);
91619
+ if (shared === null) return { edits: [] };
91620
+ const brokers = await ports.listBrokers();
91621
+ if (brokers.length === 0) return { edits: [] };
91622
+ const missing = brokers.filter((broker) => broker.calendarIds === void 0);
91623
+ if (missing.length > 0) throw new Error(`google-services: the shared calendarIds is not dropped, ${missing.map((b) => b.id).join(", ")} has no set of its own`);
91624
+ if (!brokers.every((broker) => sameSet(broker.calendarIds ?? [], shared))) return { edits: [] };
91625
+ const rowId = `${GOOGLE_ADDON_ID}.${SHARED_CALENDAR_IDS_KEY}`;
91626
+ return { edits: [{
91627
+ op: "deleteKeys",
91628
+ scope: ADDON_SETTINGS_TABLE,
91629
+ keys: [rowId],
91630
+ precondition: preconditionOf({ [rowId]: {
91631
+ addonId: GOOGLE_ADDON_ID,
91632
+ key: SHARED_CALENDAR_IDS_KEY,
91633
+ value: shared
91634
+ } })
91635
+ }] };
91636
+ }
91637
+ //#endregion
91638
+ //#region src/migrations/index.ts
91639
+ /** The `google-services` catalogue (D987): offered to the engine when the addon initialises. */
91640
+ var ADDON_MIGRATIONS = [{
91641
+ meta: {
91642
+ id: "google-services/2026-11-03-calendar-ids-to-brokers",
91643
+ owner: "google-services",
91644
+ level: "addon-state",
91645
+ seq: 1,
91646
+ dependsOn: [],
91647
+ store: "settings",
91648
+ scope: "cluster",
91649
+ onFailure: "tolerate",
91650
+ bodyKind: "procedure",
91651
+ description: "Copy the shared calendar set to every Google broker record that has none of its own",
91652
+ fixtureMark: "the client secret marker of a broker record the migration does not know"
91653
+ },
91654
+ procedure: copyCalendarIdsToBrokers
91655
+ }, {
91656
+ meta: {
91657
+ id: "google-services/2026-11-03-drop-shared-calendar-ids",
91658
+ owner: "google-services",
91659
+ level: "addon-state",
91660
+ seq: 2,
91661
+ dependsOn: ["google-services/2026-11-03-calendar-ids-to-brokers"],
91662
+ store: "settings",
91663
+ scope: "cluster",
91664
+ onFailure: "tolerate",
91665
+ bodyKind: "plan",
91666
+ description: "Delete the shared calendar set once every broker shows its own",
91667
+ fixtureMark: "the shared set parked when a broker holds a different one"
91668
+ },
91669
+ plan: planDropSharedCalendarIds
91670
+ }];
91671
+ //#endregion
91672
+ //#region src/migrations/ports.ts
91673
+ /** The migration's ports over the `brokers` record kind and the addon's raw settings read. */
91674
+ function calendarIdsPortsOver(kind, readStored) {
91675
+ return {
91676
+ readStored,
91677
+ listBrokers: async () => {
91678
+ return (await kind.query({ limit: 1e3 })).records.map((record) => ({
91679
+ id: record.id,
91680
+ calendarIds: record.data.calendarIds
91681
+ }));
91682
+ },
91683
+ setBrokerCalendarIds: async (id, ids) => {
91684
+ const row = await kind.get(id);
91685
+ if (row === null) return false;
91686
+ if (row.data.calendarIds !== void 0) return true;
91687
+ await kind.patch(id, { calendarIds: [...ids] });
91688
+ return true;
91689
+ }
91690
+ };
91691
+ }
91692
+ //#endregion
91071
91693
  //#region src/google-broker-store.ts
91072
91694
  function rowToEntry(id, data) {
91073
91695
  const parsed = GoogleBrokerEntrySchema.safeParse({
@@ -91105,14 +91727,15 @@ var GoogleBrokerStore = class GoogleBrokerStore {
91105
91727
  const nextIds = new Set(entries.map((e) => e.id));
91106
91728
  for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
91107
91729
  for (const entry of entries) {
91108
- const { id, name, clientId, clientSecret, calendarFilter, fromAddress, fromName } = entry;
91730
+ const { id, name, clientId, clientSecret, calendarFilter, fromAddress, fromName, calendarIds } = entry;
91109
91731
  const body = {
91110
91732
  name,
91111
91733
  clientId,
91112
91734
  clientSecret,
91113
91735
  calendarFilter,
91114
91736
  fromAddress,
91115
- fromName
91737
+ fromName,
91738
+ ...calendarIds !== void 0 ? { calendarIds } : {}
91116
91739
  };
91117
91740
  const parsed = googleBrokersEntryKind.schema.safeParse(body);
91118
91741
  if (!parsed.success) throw new Error(`invalid Google broker '${id}': ${parsed.error.message}`);
@@ -91209,6 +91832,7 @@ var GoogleServicesAddon = class extends BaseAddon {
91209
91832
  await this.brokerStore.replaceAll(this.config.brokers);
91210
91833
  this.activeBrokers = await this.loadPersistedBrokers();
91211
91834
  }
91835
+ await this.migrateCalendarIds(records);
91212
91836
  this.registry.seedFrom(this.activeBrokers, oauthAccounts.accounts);
91213
91837
  await this.refreshPublicOrigin();
91214
91838
  const calendarProvider = createGoogleCalendarSource({
@@ -91218,7 +91842,7 @@ var GoogleServicesAddon = class extends BaseAddon {
91218
91842
  connected: this.registry.isConnected(entry.id),
91219
91843
  api: createGoogleCalendarApi(() => this.ensureAccessToken(entry.id))
91220
91844
  })),
91221
- selectedCalendarIds: () => new Set(this.config.calendarIds ?? [])
91845
+ selectedCalendarIds: (brokerId) => this.calendarIdsOf(brokerId)
91222
91846
  });
91223
91847
  const smtpProvider = createGmailSmtpProvider({ accounts: () => this.registry.listEntries().map((entry) => {
91224
91848
  const oauth = this.registry.getOauth(entry.id);
@@ -91389,7 +92013,10 @@ var GoogleServicesAddon = class extends BaseAddon {
91389
92013
  getSettings: async ({ id }) => {
91390
92014
  const entry = this.registry.getEntry(id);
91391
92015
  if (entry === null) return null;
91392
- return brokerEntryToSettings(entry);
92016
+ return {
92017
+ ...brokerEntryToSettings(entry),
92018
+ _calendarCatalog: await this.loadStampedCalendarCatalog(id)
92019
+ };
91393
92020
  },
91394
92021
  setSettings: async ({ id, settings }) => {
91395
92022
  if (!owns(id)) return;
@@ -91402,6 +92029,7 @@ var GoogleServicesAddon = class extends BaseAddon {
91402
92029
  const oauth = this.registry.getOauth(id);
91403
92030
  return {
91404
92031
  ...brokerEntryToSettings(entry),
92032
+ _calendarCatalog: await this.loadStampedCalendarCatalog(id),
91405
92033
  accountEmail: oauth?.accountEmail ?? "",
91406
92034
  connected: this.registry.isConnected(id)
91407
92035
  };
@@ -91607,6 +92235,13 @@ var GoogleServicesAddon = class extends BaseAddon {
91607
92235
  const cutoff = Date.now() - 10 * 6e4;
91608
92236
  for (const [k, v] of this.pendingStates) if (v.issuedAt < cutoff) this.pendingStates.delete(k);
91609
92237
  }
92238
+ /**
92239
+ * The shared calendar set becomes each broker's own (D985, D987). Brokers are reloaded once the
92240
+ * copy landed, so the registry is seeded with the sets the migration wrote.
92241
+ */
92242
+ async migrateCalendarIds(records) {
92243
+ if ((await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, calendarIdsPortsOver(records.kind(googleBrokersEntryKind), (key) => this.readStoredGlobalKey(key))))).applied.length > 0) this.activeBrokers = await this.loadPersistedBrokers();
92244
+ }
91610
92245
  async loadPersistedBrokers() {
91611
92246
  const store = this.brokerStore;
91612
92247
  if (store !== null) {
@@ -91630,9 +92265,10 @@ var GoogleServicesAddon = class extends BaseAddon {
91630
92265
  await this.oauthAccountsHandle.set({ accounts: [...this.registry.listOauthAccounts()] });
91631
92266
  }
91632
92267
  /** Stamped catalog for Connectivity → Calendars (`optionsFrom`). */
91633
- async loadStampedCalendarCatalog() {
92268
+ async loadStampedCalendarCatalog(onlyBrokerId) {
91634
92269
  const out = [];
91635
92270
  for (const entry of this.registry.listEntries()) {
92271
+ if (onlyBrokerId !== void 0 && entry.id !== onlyBrokerId) continue;
91636
92272
  if (!this.registry.isConnected(entry.id)) continue;
91637
92273
  try {
91638
92274
  const list = await createGoogleCalendarApi(() => this.ensureAccessToken(entry.id)).listCalendars();
@@ -91772,13 +92408,15 @@ var GoogleServicesAddon = class extends BaseAddon {
91772
92408
  }
91773
92409
  async getGlobalSettings(overlay, cap, nodeId) {
91774
92410
  await this.refreshPublicOrigin();
91775
- if (cap !== "calendar-source") return super.getGlobalSettings(overlay, cap, nodeId);
91776
- const catalog = await this.loadStampedCalendarCatalog();
91777
- return super.getGlobalSettings({
91778
- ...overlay,
91779
- calendarIds: this.config.calendarIds ?? [],
91780
- ...catalog.length > 0 ? { _calendarCatalog: catalog } : {}
91781
- }, cap, nodeId);
92411
+ return super.getGlobalSettings(overlay, cap, nodeId);
92412
+ }
92413
+ /**
92414
+ * The stamped calendar ids ONE broker imports. A broker the migration has not reached yet
92415
+ * (no `calendarIds` of its own) reads the shared setting older versions wrote, so the move
92416
+ * never changes what is imported mid-way.
92417
+ */
92418
+ calendarIdsOf(brokerId) {
92419
+ return calendarIdsOfEntry(this.registry.getEntry(brokerId), this.config.calendarIds ?? []);
91782
92420
  }
91783
92421
  };
91784
92422
  function asSettingsCap(cap) {