@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.
@@ -785,7 +785,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
785
785
  return EventCategory;
786
786
  }({});
787
787
  //#endregion
788
- //#region ../types/dist/device-type-BOWLtoOt.mjs
788
+ //#region ../types/dist/device-type-D1fH5kaC.mjs
789
789
  /**
790
790
  import { errMsg } from '@camstack/types'
791
791
  * Extract a human-readable message from an unknown error value.
@@ -974,6 +974,9 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
974
974
  DeviceFeature["CoverPositionable"] = "cover-positionable";
975
975
  /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
976
976
  DeviceFeature["CoverTilt"] = "cover-tilt";
977
+ /** Cover has a real stop: `stop` halts the travel in place. Gates the stop
978
+ * control in exporters; a provider whose `stop` is a no-op never declares it. */
979
+ DeviceFeature["CoverStoppable"] = "cover-stoppable";
977
980
  /** Valve exposes intermediate position control (0..100). Gates the
978
981
  * position slider / drag surface UI. */
979
982
  DeviceFeature["ValvePositionable"] = "valve-positionable";
@@ -1013,6 +1016,12 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
1013
1016
  DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
1014
1017
  /** Media player exposes stop distinct from pause. */
1015
1018
  DeviceFeature["MediaPlayerStop"] = "media-player-stop";
1019
+ /** Media player exposes volume up / down in steps (an infrared TV has no level). */
1020
+ DeviceFeature["MediaPlayerVolumeStep"] = "media-player-volume-step";
1021
+ /** Media player exposes channel up / down in steps. */
1022
+ DeviceFeature["MediaPlayerChannelStep"] = "media-player-channel-step";
1023
+ /** Media player exposes power on / off. */
1024
+ DeviceFeature["MediaPlayerPower"] = "media-player-power";
1016
1025
  /** Alarm panel requires a PIN code on arm/disarm. */
1017
1026
  DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
1018
1027
  /** Presence device carries GPS coordinates (lat/lng/accuracy) in
@@ -5972,7 +5981,7 @@ var ZodIssueCode = {
5972
5981
  var ZodFirstPartyTypeKind;
5973
5982
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5974
5983
  //#endregion
5975
- //#region ../types/dist/sleep-CdJHPm-k.mjs
5984
+ //#region ../types/dist/sleep-BKClVk0K.mjs
5976
5985
  /**
5977
5986
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5978
5987
  * window to float samples (D455).
@@ -6051,6 +6060,22 @@ function buildAlawTable() {
6051
6060
  }
6052
6061
  buildUlawTable();
6053
6062
  buildAlawTable();
6063
+ /**
6064
+ * An addon whose own migrations did not apply refuses to initialise (D987). The error is typed
6065
+ * so a runner hosting several addons can tell "this addon is blocked" from a crash and keep its
6066
+ * co-located siblings running. Recognised by NAME and shape, never `instanceof`: a forked addon
6067
+ * bundles its own copy of this module, so the class the runner sees is not the one that threw.
6068
+ */
6069
+ var MIGRATIONS_BLOCKED_ERROR_NAME = "AddonMigrationsBlockedError";
6070
+ var AddonMigrationsBlockedError = class extends Error {
6071
+ /** The ledger ids that did not apply and block the owner. */
6072
+ migrationIds;
6073
+ constructor(migrationIds) {
6074
+ super(`migration(s) ${migrationIds.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
6075
+ this.name = MIGRATIONS_BLOCKED_ERROR_NAME;
6076
+ this.migrationIds = migrationIds;
6077
+ }
6078
+ };
6054
6079
  /** Is this field's stored value a credential? */
6055
6080
  function isSecretConfigField(field) {
6056
6081
  if (field.type === "password") return true;
@@ -6488,6 +6513,53 @@ function emitReadiness(bus, params) {
6488
6513
  ts
6489
6514
  }));
6490
6515
  }
6516
+ /** Why a catalogue entry is not a runnable body of its declared kind. */
6517
+ var AddonMigrationDefinitionError = class extends Error {
6518
+ id;
6519
+ constructor(id, message) {
6520
+ super(`${id}: ${message}`);
6521
+ this.id = id;
6522
+ this.name = "AddonMigrationDefinitionError";
6523
+ }
6524
+ };
6525
+ /**
6526
+ * Bind a catalogue to the addon's ports. Throws {@link AddonMigrationDefinitionError} when
6527
+ * an entry's `bodyKind` does not match the body it carries, or is `sql` (a hub-only
6528
+ * body). A throwing body never escapes `execute`: it is reported as a failed result.
6529
+ */
6530
+ function defineAddonMigrations(catalogue, ports) {
6531
+ return catalogue.map((entry) => {
6532
+ const { meta } = entry;
6533
+ if ("plan" in entry) {
6534
+ if (meta.bodyKind !== "plan") throw new AddonMigrationDefinitionError(meta.id, `carries a plan body but declares "${meta.bodyKind}"`);
6535
+ return {
6536
+ meta,
6537
+ execute: () => guarded(async () => ({
6538
+ kind: "plan",
6539
+ plan: await entry.plan(ports)
6540
+ }))
6541
+ };
6542
+ }
6543
+ if (meta.bodyKind !== "procedure") throw new AddonMigrationDefinitionError(meta.id, `carries a procedure body but declares "${meta.bodyKind}"`);
6544
+ return {
6545
+ meta,
6546
+ execute: () => guarded(async () => ({
6547
+ kind: "procedure",
6548
+ result: await entry.procedure(ports)
6549
+ }))
6550
+ };
6551
+ });
6552
+ }
6553
+ async function guarded(work) {
6554
+ try {
6555
+ return await work();
6556
+ } catch (err) {
6557
+ return {
6558
+ kind: "failed",
6559
+ error: errMsg(err)
6560
+ };
6561
+ }
6562
+ }
6491
6563
  var NOT_RUN = {
6492
6564
  declared: false,
6493
6565
  snapshot: null,
@@ -7657,7 +7729,7 @@ var BaseAddon = class {
7657
7729
  startMigration: (input) => api.startMigration.mutate(input),
7658
7730
  finishMigration: (input) => api.finishMigration.mutate(input)
7659
7731
  }, migrations, this.ctx.logger);
7660
- 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`);
7732
+ if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7661
7733
  return report;
7662
7734
  }
7663
7735
  /**
@@ -9648,6 +9720,7 @@ var CAP_NAMES = Object.freeze([
9648
9720
  "backup",
9649
9721
  "battery",
9650
9722
  "binary",
9723
+ "board-key-source",
9651
9724
  "brightness",
9652
9725
  "broker",
9653
9726
  "button",
@@ -9769,6 +9842,7 @@ var CAP_NAMES = Object.freeze([
9769
9842
  "privacy-mask",
9770
9843
  "ptz",
9771
9844
  "ptz-autotrack",
9845
+ "pump-cycle",
9772
9846
  "reboot",
9773
9847
  "record-entries",
9774
9848
  "recording",
@@ -11061,18 +11135,6 @@ Object.freeze({
11061
11135
  "path": "pumpRunning",
11062
11136
  "valueType": "boolean"
11063
11137
  },
11064
- {
11065
- "path": "pumpIntervalEnabled",
11066
- "valueType": "boolean"
11067
- },
11068
- {
11069
- "path": "pumpOnTime",
11070
- "valueType": "number"
11071
- },
11072
- {
11073
- "path": "pumpOffTime",
11074
- "valueType": "number"
11075
- },
11076
11138
  {
11077
11139
  "path": "lastDrink",
11078
11140
  "valueType": "number"
@@ -11196,6 +11258,20 @@ Object.freeze({
11196
11258
  "valueType": "number"
11197
11259
  }
11198
11260
  ],
11261
+ "pump-cycle": [
11262
+ {
11263
+ "path": "enabled",
11264
+ "valueType": "boolean"
11265
+ },
11266
+ {
11267
+ "path": "onTime",
11268
+ "valueType": "number"
11269
+ },
11270
+ {
11271
+ "path": "offTime",
11272
+ "valueType": "number"
11273
+ }
11274
+ ],
11199
11275
  "recording-archive": [
11200
11276
  {
11201
11277
  "path": "deviceId",
@@ -11659,7 +11735,7 @@ function userError(code, params, englishFallback) {
11659
11735
  return new CamstackError(code, params, englishFallback);
11660
11736
  }
11661
11737
  //#endregion
11662
- //#region ../types/dist/composition-BDUoQsw9.mjs
11738
+ //#region ../types/dist/composition-BodkvOV0.mjs
11663
11739
  /**
11664
11740
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11665
11741
  *
@@ -12796,7 +12872,14 @@ var CompositionForwardCommandSchema = object({
12796
12872
  method: string().min(1),
12797
12873
  args: record(string().min(1), CompositionForwardArgSchema).optional()
12798
12874
  });
12799
- var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
12875
+ var CompositionCommandTargetSchema = discriminatedUnion("kind", [
12876
+ CompositionForwardCommandSchema,
12877
+ CompositionCodeSourceSchema,
12878
+ object({
12879
+ kind: literal("extension"),
12880
+ target: literal("remote")
12881
+ })
12882
+ ]);
12800
12883
  /** An item key inside an item-array cap (`consumables.items[<key>]`). */
12801
12884
  var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12802
12885
  /**
@@ -12873,6 +12956,73 @@ var NodeColorIdSchema = _enum([
12873
12956
  "node-g",
12874
12957
  "node-h"
12875
12958
  ]);
12959
+ //#endregion
12960
+ //#region ../types/dist/index.mjs
12961
+ var SMTP_INBOUND_DEFAULT_PORT = 2525;
12962
+ var SMTP_INBOUND_DEFAULT_DOMAIN = "camstack.local";
12963
+ var SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES = 25 * 1024 * 1024;
12964
+ /** The text excerpt a message carries on the bus. */
12965
+ var SMTP_INBOUND_TEXT_MAX_CHARS = 4096;
12966
+ var PortSchema = number().int().min(1).max(65535);
12967
+ /** A recipient local-part: what stands before the `@`. */
12968
+ var SmtpRecipientSchema = string().min(1).max(64).regex(/^[A-Za-z0-9._+-]+$/);
12969
+ object({
12970
+ port: PortSchema.default(SMTP_INBOUND_DEFAULT_PORT),
12971
+ bindHost: string().min(1).default("0.0.0.0"),
12972
+ domain: string().min(1).default(SMTP_INBOUND_DEFAULT_DOMAIN),
12973
+ requireAuth: boolean().default(true),
12974
+ username: string().default(""),
12975
+ password: string().default("").meta({ secret: true }),
12976
+ starttls: boolean().default(false),
12977
+ maxMessageBytes: number().int().positive().default(SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES)
12978
+ });
12979
+ object({
12980
+ host: string().min(1).nullable(),
12981
+ hostReason: string().nullable(),
12982
+ port: PortSchema,
12983
+ domain: string().min(1),
12984
+ requireAuth: boolean(),
12985
+ username: string(),
12986
+ password: string(),
12987
+ starttls: boolean()
12988
+ });
12989
+ object({
12990
+ recipients: array(SmtpRecipientSchema).min(1),
12991
+ /**
12992
+ * Who is subscribing. A second `subscribe` with the same id REPLACES that subscriber's
12993
+ * recipients (the way a consumer that restarted, or lost a device, declares its set again);
12994
+ * a recipient held by another subscriber is refused with `smtp-inbound/recipient-claimed`.
12995
+ * Absent, the subscriber is anonymous and unique.
12996
+ */
12997
+ subscriberId: string().min(1).max(64).optional()
12998
+ });
12999
+ var SmtpInboundAttachmentSchema = object({
13000
+ filename: string(),
13001
+ contentType: string(),
13002
+ bytes: number().int().nonnegative()
13003
+ });
13004
+ /** The payload of one `broker.message` (the key is the recipient local-part). */
13005
+ var SmtpInboundMessageSchema = object({
13006
+ /** Monotonic per mailbox; a gap tells a consumer it missed an event. */
13007
+ seq: number().int().positive(),
13008
+ messageId: string(),
13009
+ receivedAt: number().int().nonnegative(),
13010
+ from: string(),
13011
+ to: string(),
13012
+ subject: string(),
13013
+ text: string().max(SMTP_INBOUND_TEXT_MAX_CHARS),
13014
+ attachments: array(SmtpInboundAttachmentSchema)
13015
+ });
13016
+ object({
13017
+ /** The newest seq issued so far (0 before any mail). */
13018
+ lastSeq: number().int().nonnegative(),
13019
+ /** The oldest seq still retained; messages below it are gone. */
13020
+ oldestSeq: number().int().nonnegative(),
13021
+ messages: array(object({
13022
+ key: string(),
13023
+ message: SmtpInboundMessageSchema
13024
+ }))
13025
+ });
12876
13026
  new Set([
12877
13027
  "track",
12878
13028
  "face",
@@ -14084,6 +14234,16 @@ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
14084
14234
  precondition: PreconditionSchema
14085
14235
  })
14086
14236
  ])) });
14237
+ /** `JSON.stringify` with object keys sorted, so equal values spell the same. */
14238
+ function canonicalJson(value) {
14239
+ if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
14240
+ 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(",")}}`;
14241
+ return JSON.stringify(value) ?? "null";
14242
+ }
14243
+ /** The precondition string for a value a plan read (`null` when it read nothing). */
14244
+ function preconditionOf(value) {
14245
+ return canonicalJson(value ?? null);
14246
+ }
14087
14247
  /**
14088
14248
  * What a `procedure` body reports. `already-done` means the rerun observed the
14089
14249
  * work finished (a crash between the work and the ledger row, or a manual fix)
@@ -19262,24 +19422,52 @@ var APPLIANCE_CAP_VOCABULARY = {
19262
19422
  args: { on: false }
19263
19423
  },
19264
19424
  {
19265
- id: "petFountain.intervalOn",
19266
- method: "setPumpInterval",
19425
+ id: "petFountain.resetDailyConsumption",
19426
+ method: "resetDailyConsumption",
19427
+ label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
19428
+ phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
19429
+ icon: "rotate-ccw"
19430
+ }
19431
+ ],
19432
+ state: [{
19433
+ kind: "boolean",
19434
+ field: "empty",
19435
+ label: t("Tank empty", "Serbatoio vuoto"),
19436
+ whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
19437
+ whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
19438
+ icon: "droplet"
19439
+ }, {
19440
+ kind: "numeric",
19441
+ field: "waterLevel",
19442
+ quantity: t("the water level", "il livello dell’acqua"),
19443
+ label: t("Water level", "Livello dell’acqua"),
19444
+ unit: "%",
19445
+ icon: "droplets"
19446
+ }]
19447
+ },
19448
+ "pump-cycle": {
19449
+ scope: "device",
19450
+ noun: t("Pump cycle", "Ciclo della pompa"),
19451
+ actions: [
19452
+ {
19453
+ id: "pumpCycle.on",
19454
+ method: "setEnabled",
19267
19455
  label: t("Pump cycle on", "Ciclo pompa attivo"),
19268
19456
  phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
19269
19457
  icon: "timer",
19270
19458
  args: { on: true }
19271
19459
  },
19272
19460
  {
19273
- id: "petFountain.intervalOff",
19274
- method: "setPumpInterval",
19461
+ id: "pumpCycle.off",
19462
+ method: "setEnabled",
19275
19463
  label: t("Pump cycle off", "Ciclo pompa spento"),
19276
19464
  phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
19277
19465
  icon: "timer-off",
19278
19466
  args: { on: false }
19279
19467
  },
19280
19468
  {
19281
- id: "petFountain.pumpOnMinutes",
19282
- method: "setPumpOnMinutes",
19469
+ id: "pumpCycle.onMinutes",
19470
+ method: "setOnMinutes",
19283
19471
  label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
19284
19472
  phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
19285
19473
  icon: "timer",
@@ -19295,8 +19483,8 @@ var APPLIANCE_CAP_VOCABULARY = {
19295
19483
  }]
19296
19484
  },
19297
19485
  {
19298
- id: "petFountain.pumpOffMinutes",
19299
- method: "setPumpOffMinutes",
19486
+ id: "pumpCycle.offMinutes",
19487
+ method: "setOffMinutes",
19300
19488
  label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
19301
19489
  phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
19302
19490
  icon: "timer-off",
@@ -19310,35 +19498,63 @@ var APPLIANCE_CAP_VOCABULARY = {
19310
19498
  unit: "min"
19311
19499
  }
19312
19500
  }]
19313
- },
19314
- {
19315
- id: "petFountain.resetDailyConsumption",
19316
- method: "resetDailyConsumption",
19317
- label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
19318
- phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
19319
- icon: "rotate-ccw"
19320
19501
  }
19321
19502
  ],
19322
19503
  state: [{
19323
19504
  kind: "boolean",
19324
- field: "empty",
19325
- label: t("Tank empty", "Serbatoio vuoto"),
19326
- whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
19327
- whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
19328
- icon: "droplet"
19329
- }, {
19330
- kind: "numeric",
19331
- field: "waterLevel",
19332
- quantity: t("the water level", "il livello dell’acqua"),
19333
- label: t("Water level", "Livello dell’acqua"),
19334
- unit: "%",
19335
- icon: "droplets"
19505
+ field: "enabled",
19506
+ label: t("Pump cycle", "Ciclo pompa"),
19507
+ whenTrue: t("When {device} starts running its pump on a cycle", "Quando {device} inizia a far lavorare la pompa a ciclo"),
19508
+ whenFalse: t("When {device} runs its pump continuously", "Quando {device} fa lavorare la pompa in continuo"),
19509
+ icon: "timer"
19336
19510
  }]
19337
19511
  },
19338
19512
  "media-player": {
19339
19513
  scope: "device",
19340
19514
  noun: t("Media player", "Lettore multimediale"),
19341
19515
  actions: [
19516
+ {
19517
+ id: "media.turnOn",
19518
+ method: "turnOn",
19519
+ label: t("Turn on", "Accendi"),
19520
+ phrase: t("Turn on {device}", "Accendi {device}"),
19521
+ icon: "power"
19522
+ },
19523
+ {
19524
+ id: "media.turnOff",
19525
+ method: "turnOff",
19526
+ label: t("Turn off", "Spegni"),
19527
+ phrase: t("Turn off {device}", "Spegni {device}"),
19528
+ icon: "power"
19529
+ },
19530
+ {
19531
+ id: "media.volumeUp",
19532
+ method: "volumeUp",
19533
+ label: t("Volume up", "Alza il volume"),
19534
+ phrase: t("Raise the volume of {device}", "Alza il volume di {device}"),
19535
+ icon: "volume-2"
19536
+ },
19537
+ {
19538
+ id: "media.volumeDown",
19539
+ method: "volumeDown",
19540
+ label: t("Volume down", "Abbassa il volume"),
19541
+ phrase: t("Lower the volume of {device}", "Abbassa il volume di {device}"),
19542
+ icon: "volume-2"
19543
+ },
19544
+ {
19545
+ id: "media.channelUp",
19546
+ method: "channelUp",
19547
+ label: t("Channel up", "Canale successivo"),
19548
+ phrase: t("Go to the next channel on {device}", "Passa al canale successivo su {device}"),
19549
+ icon: "skip-forward"
19550
+ },
19551
+ {
19552
+ id: "media.channelDown",
19553
+ method: "channelDown",
19554
+ label: t("Channel down", "Canale precedente"),
19555
+ phrase: t("Go to the previous channel on {device}", "Torna al canale precedente su {device}"),
19556
+ icon: "skip-back"
19557
+ },
19342
19558
  {
19343
19559
  id: "media.play",
19344
19560
  method: "play",
@@ -21225,6 +21441,7 @@ var SYSTEM_CAP_VOCABULARY = {
21225
21441
  "audio-analyzer": system(PIPELINE),
21226
21442
  "audio-codec": system(INTERNAL),
21227
21443
  "auth-provider": system(IDENTITY),
21444
+ "board-key-source": system(INTERNAL),
21228
21445
  backup: system("system backups — run and restored from the Backups page"),
21229
21446
  broker: system("message-broker connections — configured, not actuated"),
21230
21447
  "calendar-source": system(INTERNAL),
@@ -45687,6 +45904,49 @@ object({
45687
45904
  lastChangedAt: number()
45688
45905
  });
45689
45906
  DeviceType.Sensor;
45907
+ /**
45908
+ * Why a board key could not be read. Typed so a caller reports the cause and
45909
+ * never treats "could not read" as "has no key" (D49).
45910
+ */
45911
+ var BoardKeyFailureReasonSchema = _enum([
45912
+ "unknown-source",
45913
+ "not-admin",
45914
+ "no-builder",
45915
+ "no-ingress-url",
45916
+ "unreachable",
45917
+ "unknown-board",
45918
+ "no-encryption-key",
45919
+ "unreadable-config"
45920
+ ]);
45921
+ var BoardKeySourceSchema = object({
45922
+ sourceId: string(),
45923
+ name: string()
45924
+ });
45925
+ var BoardKeyBoardSchema = object({
45926
+ /** The builder's file name, e.g. `kitchen.yaml`: the handle `getBoardKey` takes. */
45927
+ configuration: string(),
45928
+ /** The node name the builder gives the board. */
45929
+ name: string(),
45930
+ /** The address the builder knows the board by, when it has one. */
45931
+ address: string().nullable()
45932
+ });
45933
+ var BoardKeyFailureSchema = object({
45934
+ ok: literal(false),
45935
+ reason: BoardKeyFailureReasonSchema,
45936
+ detail: string().optional()
45937
+ });
45938
+ var ListBoardsResultSchema = discriminatedUnion("ok", [object({
45939
+ ok: literal(true),
45940
+ boards: array(BoardKeyBoardSchema).readonly()
45941
+ }), BoardKeyFailureSchema]);
45942
+ var GetBoardKeyResultSchema = discriminatedUnion("ok", [object({
45943
+ ok: literal(true),
45944
+ key: string()
45945
+ }), BoardKeyFailureSchema]);
45946
+ method(_void(), array(BoardKeySourceSchema).readonly(), { auth: "admin" }), method(object({ sourceId: string().min(1) }), ListBoardsResultSchema, { auth: "admin" }), method(object({
45947
+ sourceId: string().min(1),
45948
+ configuration: string().min(1)
45949
+ }), GetBoardKeyResultSchema, { auth: "admin" });
45690
45950
  object({
45691
45951
  /** Current level as 0..100 inclusive. Firmware-reported. */
45692
45952
  level: number().min(0).max(100).meta(readingMeta("percentage")),
@@ -47823,6 +48083,188 @@ DeviceType.Control, method(object({
47823
48083
  kind: "mutation",
47824
48084
  auth: "protected"
47825
48085
  });
48086
+ /**
48087
+ * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
48088
+ * Apple TV, Spotify, Roku, generic OTT receivers, …) on
48089
+ * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
48090
+ * surface — those use `DeviceType.Camera` + the broker pipeline.
48091
+ *
48092
+ * The cap declares a wide method surface; not every backend supports
48093
+ * every method. Drivers signal sub-capability via the
48094
+ * `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
48095
+ * corresponding method) so the UI gates buttons without a slice
48096
+ * fetch — same pattern as `PanTiltZoom` for cameras.
48097
+ */
48098
+ var MediaPlayerStateSchema = _enum([
48099
+ "off",
48100
+ "on",
48101
+ "idle",
48102
+ "playing",
48103
+ "paused",
48104
+ "buffering",
48105
+ "standby"
48106
+ ]);
48107
+ var MediaPlayerRepeatSchema = _enum([
48108
+ "off",
48109
+ "all",
48110
+ "one"
48111
+ ]);
48112
+ var MediaInfoSchema = object({
48113
+ /** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
48114
+ * `podcast`, …). */
48115
+ type: string(),
48116
+ /** Human-readable title. */
48117
+ title: string(),
48118
+ /** Optional artist / channel / station label. */
48119
+ artist: string().optional(),
48120
+ /** Optional album / season / show name. */
48121
+ album: string().optional(),
48122
+ /** Optional cover-art / thumbnail URL. */
48123
+ imageUrl: string().optional()
48124
+ });
48125
+ /**
48126
+ * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
48127
+ * the device said so. `assumed`: the driver has no feedback (infrared) and
48128
+ * tracks what it last SENT; it moves only after a send was accepted, and a
48129
+ * human at the real remote makes it wrong. A UI shows an assumed value as a
48130
+ * guess, never as a reading.
48131
+ */
48132
+ var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
48133
+ /** The fields of a media-player status a driver can only guess. */
48134
+ var MediaPlayerAssumedFieldSchema = _enum([
48135
+ "state",
48136
+ "volumeLevel",
48137
+ "isMuted",
48138
+ "source"
48139
+ ]);
48140
+ var MediaPlayerStatusSchema = object({
48141
+ /** Playback lifecycle state. */
48142
+ state: MediaPlayerStateSchema,
48143
+ /**
48144
+ * Whether the state fields are readings or the driver's own bookkeeping.
48145
+ * A slice written before this field existed parses as `reported`.
48146
+ */
48147
+ stateSource: MediaPlayerStateSourceSchema.default("reported"),
48148
+ /**
48149
+ * Which fields are the driver's own guess. `stateSource` is `assumed` when
48150
+ * this is not empty; a field not listed here is a reading even then (power
48151
+ * from a running-state source, mute guessed from the last send).
48152
+ */
48153
+ assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
48154
+ /** Volume as 0..100 inclusive. Null when the device has no volume
48155
+ * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
48156
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
48157
+ /** Mute toggle distinct from volume=0. Null when no mute surface.
48158
+ * Pair with `DeviceFeature.MediaPlayerMute`. */
48159
+ isMuted: boolean().nullable(),
48160
+ /** Active source / input. Empty when no source surface. */
48161
+ source: string(),
48162
+ /** Selectable sources. Empty when no source surface.
48163
+ * Pair with `DeviceFeature.MediaPlayerSelectSource`. */
48164
+ availableSources: array(string()),
48165
+ /** Currently-playing media info. Null when nothing is playing. */
48166
+ currentMedia: MediaInfoSchema.nullable(),
48167
+ /** Current playback position in ms. Null when not seekable or
48168
+ * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
48169
+ position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
48170
+ /** Total duration of the current media in ms. Null when unknown
48171
+ * (live stream) or nothing is playing. */
48172
+ duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
48173
+ /** Shuffle toggle. Null when no shuffle surface.
48174
+ * Pair with `DeviceFeature.MediaPlayerShuffle`. */
48175
+ shuffle: boolean().nullable(),
48176
+ /** Repeat mode. Null when no repeat surface.
48177
+ * Pair with `DeviceFeature.MediaPlayerRepeat`. */
48178
+ repeat: MediaPlayerRepeatSchema.nullable(),
48179
+ /** Ms epoch when the slice was last updated. */
48180
+ lastChangedAt: number()
48181
+ });
48182
+ var stepInput = object({
48183
+ deviceId: number().int().nonnegative(),
48184
+ /** How many presses: the caller's count (Alexa `volumeSteps`, `channelCount`). */
48185
+ steps: number().int().min(1).max(50).default(1)
48186
+ });
48187
+ var deviceOnly = object({ deviceId: number().int().nonnegative() });
48188
+ DeviceType.MediaPlayer, method(deviceOnly, _void(), {
48189
+ kind: "mutation",
48190
+ auth: "protected"
48191
+ }), method(deviceOnly, _void(), {
48192
+ kind: "mutation",
48193
+ auth: "protected"
48194
+ }), method(stepInput, _void(), {
48195
+ kind: "mutation",
48196
+ auth: "protected"
48197
+ }), method(stepInput, _void(), {
48198
+ kind: "mutation",
48199
+ auth: "protected"
48200
+ }), method(stepInput, _void(), {
48201
+ kind: "mutation",
48202
+ auth: "protected"
48203
+ }), method(stepInput, _void(), {
48204
+ kind: "mutation",
48205
+ auth: "protected"
48206
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48207
+ kind: "mutation",
48208
+ auth: "protected"
48209
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48210
+ kind: "mutation",
48211
+ auth: "protected"
48212
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48213
+ kind: "mutation",
48214
+ auth: "protected"
48215
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48216
+ kind: "mutation",
48217
+ auth: "protected"
48218
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48219
+ kind: "mutation",
48220
+ auth: "protected"
48221
+ }), method(object({
48222
+ deviceId: number().int().nonnegative(),
48223
+ position: number().int().nonnegative()
48224
+ }), _void(), {
48225
+ kind: "mutation",
48226
+ auth: "protected"
48227
+ }), method(object({
48228
+ deviceId: number().int().nonnegative(),
48229
+ volumeLevel: number().min(0).max(100)
48230
+ }), _void(), {
48231
+ kind: "mutation",
48232
+ auth: "protected"
48233
+ }), method(object({
48234
+ deviceId: number().int().nonnegative(),
48235
+ muted: boolean()
48236
+ }), _void(), {
48237
+ kind: "mutation",
48238
+ auth: "protected"
48239
+ }), method(object({
48240
+ deviceId: number().int().nonnegative(),
48241
+ shuffle: boolean()
48242
+ }), _void(), {
48243
+ kind: "mutation",
48244
+ auth: "protected"
48245
+ }), method(object({
48246
+ deviceId: number().int().nonnegative(),
48247
+ repeat: MediaPlayerRepeatSchema
48248
+ }), _void(), {
48249
+ kind: "mutation",
48250
+ auth: "protected"
48251
+ }), method(object({
48252
+ deviceId: number().int().nonnegative(),
48253
+ source: string().min(1)
48254
+ }), _void(), {
48255
+ kind: "mutation",
48256
+ auth: "protected"
48257
+ }), method(object({
48258
+ deviceId: number().int().nonnegative(),
48259
+ /** Media identifier / URL. */
48260
+ mediaId: string().min(1),
48261
+ /** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
48262
+ * passes it through to the upstream service. */
48263
+ mediaType: string().min(1)
48264
+ }), _void(), {
48265
+ kind: "mutation",
48266
+ auth: "protected"
48267
+ });
47826
48268
  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;
47827
48269
  var NUMBER = {
47828
48270
  kind: "fixed",
@@ -51408,7 +51850,7 @@ object({
51408
51850
  /** Ms epoch of the last value received from the appliance; 0 = never. */
51409
51851
  lastFetchedAt: number()
51410
51852
  });
51411
- var DeviceIdSchema$1 = number().int().nonnegative();
51853
+ var DeviceIdSchema$2 = number().int().nonnegative();
51412
51854
  /** A value a program option or setting can take. */
51413
51855
  var DishwasherValueSchema = union([
51414
51856
  boolean(),
@@ -51472,77 +51914,77 @@ var DishwasherOptionsSchema = object({
51472
51914
  options: array(DishwasherOptionDescriptorSchema)
51473
51915
  });
51474
51916
  DeviceType.Dishwasher, method(object({
51475
- deviceId: DeviceIdSchema$1,
51917
+ deviceId: DeviceIdSchema$2,
51476
51918
  program: string().min(1).optional(),
51477
51919
  options: record(string(), DishwasherValueSchema).optional()
51478
51920
  }), _void(), {
51479
51921
  kind: "mutation",
51480
51922
  auth: "protected"
51481
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
51923
+ }), method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
51482
51924
  kind: "mutation",
51483
51925
  auth: "protected"
51484
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
51926
+ }), method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
51485
51927
  kind: "mutation",
51486
51928
  auth: "protected"
51487
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
51929
+ }), method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
51488
51930
  kind: "mutation",
51489
51931
  auth: "protected"
51490
51932
  }), method(object({
51491
- deviceId: DeviceIdSchema$1,
51933
+ deviceId: DeviceIdSchema$2,
51492
51934
  program: string().min(1)
51493
51935
  }), _void(), {
51494
51936
  kind: "mutation",
51495
51937
  auth: "protected"
51496
51938
  }), method(object({
51497
- deviceId: DeviceIdSchema$1,
51939
+ deviceId: DeviceIdSchema$2,
51498
51940
  on: boolean()
51499
51941
  }), _void(), {
51500
51942
  kind: "mutation",
51501
51943
  auth: "protected"
51502
51944
  }), method(object({
51503
- deviceId: DeviceIdSchema$1,
51945
+ deviceId: DeviceIdSchema$2,
51504
51946
  on: boolean()
51505
51947
  }), _void(), {
51506
51948
  kind: "mutation",
51507
51949
  auth: "protected"
51508
51950
  }), method(object({
51509
- deviceId: DeviceIdSchema$1,
51951
+ deviceId: DeviceIdSchema$2,
51510
51952
  on: boolean()
51511
51953
  }), _void(), {
51512
51954
  kind: "mutation",
51513
51955
  auth: "protected"
51514
51956
  }), method(object({
51515
- deviceId: DeviceIdSchema$1,
51957
+ deviceId: DeviceIdSchema$2,
51516
51958
  on: boolean()
51517
51959
  }), _void(), {
51518
51960
  kind: "mutation",
51519
51961
  auth: "protected"
51520
51962
  }), method(object({
51521
- deviceId: DeviceIdSchema$1,
51963
+ deviceId: DeviceIdSchema$2,
51522
51964
  on: boolean()
51523
51965
  }), _void(), {
51524
51966
  kind: "mutation",
51525
51967
  auth: "protected"
51526
51968
  }), method(object({
51527
- deviceId: DeviceIdSchema$1,
51969
+ deviceId: DeviceIdSchema$2,
51528
51970
  on: boolean()
51529
51971
  }), _void(), {
51530
51972
  kind: "mutation",
51531
51973
  auth: "protected"
51532
51974
  }), method(object({
51533
- deviceId: DeviceIdSchema$1,
51975
+ deviceId: DeviceIdSchema$2,
51534
51976
  on: boolean()
51535
51977
  }), _void(), {
51536
51978
  kind: "mutation",
51537
51979
  auth: "protected"
51538
51980
  }), method(object({
51539
- deviceId: DeviceIdSchema$1,
51981
+ deviceId: DeviceIdSchema$2,
51540
51982
  key: string().min(1),
51541
51983
  value: DishwasherValueSchema
51542
51984
  }), _void(), {
51543
51985
  kind: "mutation",
51544
51986
  auth: "protected"
51545
- }), method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" });
51987
+ }), method(object({ deviceId: DeviceIdSchema$2 }), DishwasherOptionsSchema, { auth: "protected" });
51546
51988
  /**
51547
51989
  * Palette entry ids (D939). Audio is the one gradient; `audio-*` leaves are
51548
51990
  * not entries. `heatmapScrub` is a mark, not an event kind.
@@ -53283,6 +53725,11 @@ var IntegrationInstanceSchema = object({
53283
53725
  name: string(),
53284
53726
  /** The addon's own rule (an URL is present, a client id is present...). */
53285
53727
  configured: boolean(),
53728
+ /**
53729
+ * The instance's own on/off switch (D996): always present, derived from the record body
53730
+ * (`enabled !== false`, so a row written before the field existed is on).
53731
+ */
53732
+ enabled: boolean(),
53286
53733
  /** Values for the instance form; a secret is a `SecretFieldView`, never text. */
53287
53734
  formValues: record(string(), unknown()),
53288
53735
  status: IntegrationInstanceStatusSchema.optional()
@@ -53302,6 +53749,12 @@ method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }), method
53302
53749
  }), method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
53303
53750
  kind: "mutation",
53304
53751
  auth: "admin"
53752
+ }), method(object({
53753
+ id: IntegrationInstanceIdSchema,
53754
+ enabled: boolean()
53755
+ }), object({ ok: literal(true) }), {
53756
+ kind: "mutation",
53757
+ auth: "admin"
53305
53758
  }), method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
53306
53759
  kind: "mutation",
53307
53760
  auth: "admin"
@@ -55294,164 +55747,6 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
55294
55747
  ]);
55295
55748
  method(_void(), array(LoginMethodContributionSchema).readonly());
55296
55749
  /**
55297
- * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
55298
- * Apple TV, Spotify, Roku, generic OTT receivers, …) on
55299
- * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
55300
- * surface — those use `DeviceType.Camera` + the broker pipeline.
55301
- *
55302
- * The cap declares a wide method surface; not every backend supports
55303
- * every method. Drivers signal sub-capability via the
55304
- * `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
55305
- * corresponding method) so the UI gates buttons without a slice
55306
- * fetch — same pattern as `PanTiltZoom` for cameras.
55307
- */
55308
- var MediaPlayerStateSchema = _enum([
55309
- "off",
55310
- "on",
55311
- "idle",
55312
- "playing",
55313
- "paused",
55314
- "buffering",
55315
- "standby"
55316
- ]);
55317
- var MediaPlayerRepeatSchema = _enum([
55318
- "off",
55319
- "all",
55320
- "one"
55321
- ]);
55322
- var MediaInfoSchema = object({
55323
- /** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
55324
- * `podcast`, …). */
55325
- type: string(),
55326
- /** Human-readable title. */
55327
- title: string(),
55328
- /** Optional artist / channel / station label. */
55329
- artist: string().optional(),
55330
- /** Optional album / season / show name. */
55331
- album: string().optional(),
55332
- /** Optional cover-art / thumbnail URL. */
55333
- imageUrl: string().optional()
55334
- });
55335
- /**
55336
- * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
55337
- * the device said so. `assumed`: the driver has no feedback (infrared) and
55338
- * tracks what it last SENT; it moves only after a send was accepted, and a
55339
- * human at the real remote makes it wrong. A UI shows an assumed value as a
55340
- * guess, never as a reading.
55341
- */
55342
- var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
55343
- /** The fields of a media-player status a driver can only guess. */
55344
- var MediaPlayerAssumedFieldSchema = _enum([
55345
- "state",
55346
- "volumeLevel",
55347
- "isMuted",
55348
- "source"
55349
- ]);
55350
- object({
55351
- /** Playback lifecycle state. */
55352
- state: MediaPlayerStateSchema,
55353
- /**
55354
- * Whether the state fields are readings or the driver's own bookkeeping.
55355
- * A slice written before this field existed parses as `reported`.
55356
- */
55357
- stateSource: MediaPlayerStateSourceSchema.default("reported"),
55358
- /**
55359
- * Which fields are the driver's own guess. `stateSource` is `assumed` when
55360
- * this is not empty; a field not listed here is a reading even then (power
55361
- * from a running-state source, mute guessed from the last send).
55362
- */
55363
- assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
55364
- /** Volume as 0..100 inclusive. Null when the device has no volume
55365
- * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
55366
- volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
55367
- /** Mute toggle distinct from volume=0. Null when no mute surface.
55368
- * Pair with `DeviceFeature.MediaPlayerMute`. */
55369
- isMuted: boolean().nullable(),
55370
- /** Active source / input. Empty when no source surface. */
55371
- source: string(),
55372
- /** Selectable sources. Empty when no source surface.
55373
- * Pair with `DeviceFeature.MediaPlayerSelectSource`. */
55374
- availableSources: array(string()),
55375
- /** Currently-playing media info. Null when nothing is playing. */
55376
- currentMedia: MediaInfoSchema.nullable(),
55377
- /** Current playback position in ms. Null when not seekable or
55378
- * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
55379
- position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
55380
- /** Total duration of the current media in ms. Null when unknown
55381
- * (live stream) or nothing is playing. */
55382
- duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
55383
- /** Shuffle toggle. Null when no shuffle surface.
55384
- * Pair with `DeviceFeature.MediaPlayerShuffle`. */
55385
- shuffle: boolean().nullable(),
55386
- /** Repeat mode. Null when no repeat surface.
55387
- * Pair with `DeviceFeature.MediaPlayerRepeat`. */
55388
- repeat: MediaPlayerRepeatSchema.nullable(),
55389
- /** Ms epoch when the slice was last updated. */
55390
- lastChangedAt: number()
55391
- });
55392
- DeviceType.MediaPlayer, method(object({ deviceId: number().int().nonnegative() }), _void(), {
55393
- kind: "mutation",
55394
- auth: "protected"
55395
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55396
- kind: "mutation",
55397
- auth: "protected"
55398
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55399
- kind: "mutation",
55400
- auth: "protected"
55401
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55402
- kind: "mutation",
55403
- auth: "protected"
55404
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55405
- kind: "mutation",
55406
- auth: "protected"
55407
- }), method(object({
55408
- deviceId: number().int().nonnegative(),
55409
- position: number().int().nonnegative()
55410
- }), _void(), {
55411
- kind: "mutation",
55412
- auth: "protected"
55413
- }), method(object({
55414
- deviceId: number().int().nonnegative(),
55415
- volumeLevel: number().min(0).max(100)
55416
- }), _void(), {
55417
- kind: "mutation",
55418
- auth: "protected"
55419
- }), method(object({
55420
- deviceId: number().int().nonnegative(),
55421
- muted: boolean()
55422
- }), _void(), {
55423
- kind: "mutation",
55424
- auth: "protected"
55425
- }), method(object({
55426
- deviceId: number().int().nonnegative(),
55427
- shuffle: boolean()
55428
- }), _void(), {
55429
- kind: "mutation",
55430
- auth: "protected"
55431
- }), method(object({
55432
- deviceId: number().int().nonnegative(),
55433
- repeat: MediaPlayerRepeatSchema
55434
- }), _void(), {
55435
- kind: "mutation",
55436
- auth: "protected"
55437
- }), method(object({
55438
- deviceId: number().int().nonnegative(),
55439
- source: string().min(1)
55440
- }), _void(), {
55441
- kind: "mutation",
55442
- auth: "protected"
55443
- }), method(object({
55444
- deviceId: number().int().nonnegative(),
55445
- /** Media identifier / URL. */
55446
- mediaId: string().min(1),
55447
- /** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
55448
- * passes it through to the upstream service. */
55449
- mediaType: string().min(1)
55450
- }), _void(), {
55451
- kind: "mutation",
55452
- auth: "protected"
55453
- });
55454
- /**
55455
55750
  * mesh-network — collection cap for mesh-VPN providers.
55456
55751
  *
55457
55752
  * Different shape from `network-access` (public tunnels):
@@ -63345,7 +63640,14 @@ var NcMediaPolicySchema = object({
63345
63640
  */
63346
63641
  profile: CamProfileSchema.optional(),
63347
63642
  /** See {@link NcLinkedCameraModeSchema}. */
63348
- linkedCameras: NcLinkedCameraModeSchema.optional()
63643
+ linkedCameras: NcLinkedCameraModeSchema.optional(),
63644
+ /**
63645
+ * Sensor rules only: may this rule wake a SLEEPING battery camera to take
63646
+ * the linked still it attaches (D998)? Absent means yes, an explicit `false`
63647
+ * opts out. Deliberately not a Zod default: a rule that never chose keeps
63648
+ * following the hub's. One bounded wake per event; nothing is attached later.
63649
+ */
63650
+ wakeSleepingCamera: boolean().optional()
63349
63651
  });
63350
63652
  /**
63351
63653
  * Cooldown GRANULARITY over the subject's class — how much a fired
@@ -63737,6 +64039,21 @@ var NcTemplateVarKindSchema = _enum([
63737
64039
  "sound",
63738
64040
  "system"
63739
64041
  ]);
64042
+ /**
64043
+ * The default title/body a rule of one SITUATION is delivered with when the
64044
+ * operator wrote no template — served so an editor can pre-fill it (and the
64045
+ * operator can rebuild it from placeholders) instead of starting from a blank
64046
+ * box that, once saved, silently replaces it. `id` names the situation inside
64047
+ * a rule `kind`: `appliance-cycle` is a `sensor` rule whose device is an
64048
+ * appliance finishing a cycle. Texts are raw templates (`{{var}}` intact), in
64049
+ * the hub's current language and with its overrides applied.
64050
+ */
64051
+ var NcTemplateDefaultSchema = object({
64052
+ id: _enum(["appliance-cycle"]),
64053
+ kind: NcRuleKindSchema,
64054
+ title: string(),
64055
+ body: string()
64056
+ });
63740
64057
  /** Which producer renders a template: an ordinary rule, a timelapse, a digest. */
63741
64058
  var NcTemplateFamilySchema = _enum([
63742
64059
  "rule",
@@ -64592,7 +64909,11 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
64592
64909
  }), method(object({}), object({
64593
64910
  catalog: array(NcConditionDescriptorSchema),
64594
64911
  taxonomy: NcTaxonomySchema.optional()
64595
- })), method(object({}), object({ vars: array(NcTemplateVarDescriptorSchema) })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
64912
+ })), method(object({}), object({
64913
+ vars: array(NcTemplateVarDescriptorSchema),
64914
+ /** Per-situation default texts; absent on a hub that predates them. */
64915
+ defaults: array(NcTemplateDefaultSchema)
64916
+ })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
64596
64917
  kind: "mutation",
64597
64918
  access: "view"
64598
64919
  }), 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 }), {
@@ -65340,52 +65661,27 @@ object({
65340
65661
  empty: boolean().nullable(),
65341
65662
  /** True while the pump is running. Null while unknown. */
65342
65663
  pumpRunning: boolean().nullable(),
65343
- /** True when the pump works on an on/off duty cycle rather than continuously. */
65344
- pumpIntervalEnabled: boolean().nullable(),
65345
- /** How long the pump runs per cycle, in seconds. */
65346
- pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
65347
- /** How long the pump rests per cycle, in seconds. */
65348
- pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
65349
65664
  /** Water drunk in the last drink, in litres. */
65350
65665
  lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
65351
65666
  /** Water drunk in the current hour, in litres. */
65352
65667
  currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
65353
65668
  /** Water drunk over the last 24 hours (rolling), in litres. */
65354
65669
  last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
65355
- /** Water drunk since local midnight, in litres. */
65670
+ /** Water drunk since local midnight, in litres. Null when the fountain has no such sensor. */
65356
65671
  todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
65357
65672
  /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
65358
65673
  error: string().nullable(),
65359
65674
  /** Ms epoch when the slice was last refreshed. */
65360
65675
  lastFetchedAt: number()
65361
65676
  });
65362
- var DeviceIdSchema = number().int().nonnegative();
65363
- var MinutesSchema = number().int().min(0);
65677
+ var DeviceIdSchema$1 = number().int().nonnegative();
65364
65678
  DeviceType.PetFountain, method(object({
65365
- deviceId: DeviceIdSchema,
65366
- on: boolean()
65367
- }), _void(), {
65368
- kind: "mutation",
65369
- auth: "protected"
65370
- }), method(object({
65371
- deviceId: DeviceIdSchema,
65679
+ deviceId: DeviceIdSchema$1,
65372
65680
  on: boolean()
65373
65681
  }), _void(), {
65374
65682
  kind: "mutation",
65375
65683
  auth: "protected"
65376
- }), method(object({
65377
- deviceId: DeviceIdSchema,
65378
- minutes: MinutesSchema
65379
- }), _void(), {
65380
- kind: "mutation",
65381
- auth: "protected"
65382
- }), method(object({
65383
- deviceId: DeviceIdSchema,
65384
- minutes: MinutesSchema
65385
- }), _void(), {
65386
- kind: "mutation",
65387
- auth: "protected"
65388
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
65684
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
65389
65685
  kind: "mutation",
65390
65686
  auth: "protected"
65391
65687
  });
@@ -67345,6 +67641,37 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
67345
67641
  deviceId: number(),
67346
67642
  status: PtzAutotrackStatusSchema
67347
67643
  });
67644
+ object({
67645
+ /** True when the pump works on the on/off cycle, false when it runs continuously. Null while unknown. */
67646
+ enabled: boolean().nullable(),
67647
+ /** How long the pump runs per cycle, in seconds. Null while unknown. */
67648
+ onTime: number().min(0).nullable().meta(readingMeta("duration")),
67649
+ /** How long the pump rests per cycle, in seconds. Null while unknown. */
67650
+ offTime: number().min(0).nullable().meta(readingMeta("duration")),
67651
+ /** Ms epoch when the slice was last refreshed. */
67652
+ lastFetchedAt: number()
67653
+ });
67654
+ var DeviceIdSchema = number().int().nonnegative();
67655
+ var MinutesSchema = number().int().min(0);
67656
+ DeviceType.PetFountain, method(object({
67657
+ deviceId: DeviceIdSchema,
67658
+ on: boolean()
67659
+ }), _void(), {
67660
+ kind: "mutation",
67661
+ auth: "protected"
67662
+ }), method(object({
67663
+ deviceId: DeviceIdSchema,
67664
+ minutes: MinutesSchema
67665
+ }), _void(), {
67666
+ kind: "mutation",
67667
+ auth: "protected"
67668
+ }), method(object({
67669
+ deviceId: DeviceIdSchema,
67670
+ minutes: MinutesSchema
67671
+ }), _void(), {
67672
+ kind: "mutation",
67673
+ auth: "protected"
67674
+ });
67348
67675
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
67349
67676
  kind: "mutation",
67350
67677
  auth: "admin"
@@ -70058,7 +70385,13 @@ var RemoteMediaCommandSchema = _enum([
70058
70385
  "next",
70059
70386
  "previous",
70060
70387
  "setMute",
70061
- "selectSource"
70388
+ "selectSource",
70389
+ "turnOn",
70390
+ "turnOff",
70391
+ "volumeUp",
70392
+ "volumeDown",
70393
+ "channelUp",
70394
+ "channelDown"
70062
70395
  ]);
70063
70396
  /** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
70064
70397
  var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
@@ -70079,7 +70412,7 @@ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.e
70079
70412
  }), RemoteSendResultSchema, {
70080
70413
  kind: "mutation",
70081
70414
  auth: "protected"
70082
- }), method(deviceInput.extend({
70415
+ }), method(deviceInput, MediaPlayerStatusSchema.nullable(), { auth: "protected" }), method(deviceInput.extend({
70083
70416
  command: RemoteMediaCommandSchema,
70084
70417
  muted: boolean().optional(),
70085
70418
  source: string().optional(),
@@ -71107,10 +71440,12 @@ DeviceType.Camera, method(object({
71107
71440
  * even when the cache is well within the device's
71108
71441
  * `snapshotMaxAgeS` window.
71109
71442
  *
71110
- * **`force` is an OPERATOR signal, not a freshness preference.** On a
71443
+ * **`force` is a demand with a named principal, not a freshness
71444
+ * preference** — an operator button, the scheduled refresh (D847), or a
71445
+ * notification rule that left the camera's wake allowed (D998). On a
71111
71446
  * battery camera it is the one thing that walks past the wrapper's
71112
- * sleep gate and wakes the camera, so a background caller — a poller,
71113
- * an event handler, a thumbnail — must NEVER set it. Every such caller
71447
+ * sleep gate and wakes the camera, so a poller, a thumbnail or a
71448
+ * prestream must NEVER set it. Every such caller
71114
71449
  * gets the cached frame, which on a sleeping battery camera is the
71115
71450
  * correct answer: stale but honest beats woken.
71116
71451
  */
@@ -71918,7 +72253,11 @@ object({
71918
72253
  });
71919
72254
  Object.values(DeviceType), method(_void(), array(StreamSignalSchema).readonly());
71920
72255
  object({
71921
- on: boolean().meta({ label: "On" }),
72256
+ /**
72257
+ * `null` = the device has never reported its state (a measurement that
72258
+ * has not happened is unknown, never `false`).
72259
+ */
72260
+ on: boolean().nullable().meta({ label: "On" }),
71922
72261
  /** Ms epoch of the last state change. Useful for UI "X minutes ago". */
71923
72262
  lastChangedAt: number()
71924
72263
  });
@@ -71930,7 +72269,7 @@ DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceTyp
71930
72269
  auth: "protected"
71931
72270
  }), object({
71932
72271
  deviceId: number(),
71933
- on: boolean(),
72272
+ on: boolean().nullable(),
71934
72273
  lastChangedAt: number()
71935
72274
  });
71936
72275
  /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
@@ -74942,6 +75281,7 @@ function buildDescriptor(capName, kind) {
74942
75281
  * contributes one entry per bound cap present in this map.
74943
75282
  */
74944
75283
  var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
75284
+ DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
74945
75285
  var btn = (idSuffix, row, col, init) => ({
74946
75286
  idSuffix,
74947
75287
  row,
@@ -75068,7 +75408,7 @@ discriminatedUnion("kind", [object({
75068
75408
  kind: literal("pronto"),
75069
75409
  code: string()
75070
75410
  })]);
75071
- DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
75411
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
75072
75412
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
75073
75413
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
75074
75414
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
@@ -76049,6 +76389,27 @@ Object.freeze({
76049
76389
  access: "view",
76050
76390
  role: "viewer"
76051
76391
  },
76392
+ "boardKeySource.getBoardKey": {
76393
+ capName: "board-key-source",
76394
+ capScope: "system",
76395
+ addonId: null,
76396
+ access: "view",
76397
+ role: "admin"
76398
+ },
76399
+ "boardKeySource.listBoards": {
76400
+ capName: "board-key-source",
76401
+ capScope: "system",
76402
+ addonId: null,
76403
+ access: "view",
76404
+ role: "admin"
76405
+ },
76406
+ "boardKeySource.listSources": {
76407
+ capName: "board-key-source",
76408
+ capScope: "system",
76409
+ addonId: null,
76410
+ access: "view",
76411
+ role: "admin"
76412
+ },
76052
76413
  "brightness.getStatus": {
76053
76414
  capName: "brightness",
76054
76415
  capScope: "device",
@@ -78730,6 +79091,13 @@ Object.freeze({
78730
79091
  access: "view",
78731
79092
  role: "admin"
78732
79093
  },
79094
+ "integrationInstances.setInstanceEnabled": {
79095
+ capName: "integration-instances",
79096
+ capScope: "system",
79097
+ addonId: null,
79098
+ access: "create",
79099
+ role: "admin"
79100
+ },
78733
79101
  "integrationInstances.testInstance": {
78734
79102
  capName: "integration-instances",
78735
79103
  capScope: "system",
@@ -79528,6 +79896,20 @@ Object.freeze({
79528
79896
  access: "view",
79529
79897
  role: "admin"
79530
79898
  },
79899
+ "mediaPlayer.channelDown": {
79900
+ capName: "media-player",
79901
+ capScope: "device",
79902
+ addonId: null,
79903
+ access: "create",
79904
+ role: "viewer"
79905
+ },
79906
+ "mediaPlayer.channelUp": {
79907
+ capName: "media-player",
79908
+ capScope: "device",
79909
+ addonId: null,
79910
+ access: "create",
79911
+ role: "viewer"
79912
+ },
79531
79913
  "mediaPlayer.getStatus": {
79532
79914
  capName: "media-player",
79533
79915
  capScope: "device",
@@ -79619,6 +80001,34 @@ Object.freeze({
79619
80001
  access: "create",
79620
80002
  role: "viewer"
79621
80003
  },
80004
+ "mediaPlayer.turnOff": {
80005
+ capName: "media-player",
80006
+ capScope: "device",
80007
+ addonId: null,
80008
+ access: "create",
80009
+ role: "viewer"
80010
+ },
80011
+ "mediaPlayer.turnOn": {
80012
+ capName: "media-player",
80013
+ capScope: "device",
80014
+ addonId: null,
80015
+ access: "create",
80016
+ role: "viewer"
80017
+ },
80018
+ "mediaPlayer.volumeDown": {
80019
+ capName: "media-player",
80020
+ capScope: "device",
80021
+ addonId: null,
80022
+ access: "create",
80023
+ role: "viewer"
80024
+ },
80025
+ "mediaPlayer.volumeUp": {
80026
+ capName: "media-player",
80027
+ capScope: "device",
80028
+ addonId: null,
80029
+ access: "create",
80030
+ role: "viewer"
80031
+ },
79622
80032
  "meshNetwork.getStatus": {
79623
80033
  capName: "mesh-network",
79624
80034
  capScope: "system",
@@ -80865,27 +81275,6 @@ Object.freeze({
80865
81275
  access: "create",
80866
81276
  role: "viewer"
80867
81277
  },
80868
- "petFountain.setPumpInterval": {
80869
- capName: "pet-fountain",
80870
- capScope: "device",
80871
- addonId: null,
80872
- access: "create",
80873
- role: "viewer"
80874
- },
80875
- "petFountain.setPumpOffMinutes": {
80876
- capName: "pet-fountain",
80877
- capScope: "device",
80878
- addonId: null,
80879
- access: "create",
80880
- role: "viewer"
80881
- },
80882
- "petFountain.setPumpOnMinutes": {
80883
- capName: "pet-fountain",
80884
- capScope: "device",
80885
- addonId: null,
80886
- access: "create",
80887
- role: "viewer"
80888
- },
80889
81278
  "pipelineAnalytics.addRetrainFrameFromRecording": {
80890
81279
  capName: "pipeline-analytics",
80891
81280
  capScope: "device",
@@ -82664,6 +83053,34 @@ Object.freeze({
82664
83053
  access: "create",
82665
83054
  role: "admin"
82666
83055
  },
83056
+ "pumpCycle.getStatus": {
83057
+ capName: "pump-cycle",
83058
+ capScope: "device",
83059
+ addonId: null,
83060
+ access: "view",
83061
+ role: "viewer"
83062
+ },
83063
+ "pumpCycle.setEnabled": {
83064
+ capName: "pump-cycle",
83065
+ capScope: "device",
83066
+ addonId: null,
83067
+ access: "create",
83068
+ role: "viewer"
83069
+ },
83070
+ "pumpCycle.setOffMinutes": {
83071
+ capName: "pump-cycle",
83072
+ capScope: "device",
83073
+ addonId: null,
83074
+ access: "create",
83075
+ role: "viewer"
83076
+ },
83077
+ "pumpCycle.setOnMinutes": {
83078
+ capName: "pump-cycle",
83079
+ capScope: "device",
83080
+ addonId: null,
83081
+ access: "create",
83082
+ role: "viewer"
83083
+ },
82667
83084
  "reboot.reboot": {
82668
83085
  capName: "reboot",
82669
83086
  capScope: "device",
@@ -83168,6 +83585,13 @@ Object.freeze({
83168
83585
  access: "create",
83169
83586
  role: "admin"
83170
83587
  },
83588
+ "remote.getAssumedStatus": {
83589
+ capName: "remote",
83590
+ capScope: "system",
83591
+ addonId: null,
83592
+ access: "view",
83593
+ role: "admin"
83594
+ },
83171
83595
  "remote.getConfig": {
83172
83596
  capName: "remote",
83173
83597
  capScope: "system",
@@ -86834,6 +87258,16 @@ Object.freeze({
86834
87258
  form: "single",
86835
87259
  optional: true
86836
87260
  }],
87261
+ "mediaPlayer.channelDown": [{
87262
+ name: "deviceId",
87263
+ form: "single",
87264
+ optional: false
87265
+ }],
87266
+ "mediaPlayer.channelUp": [{
87267
+ name: "deviceId",
87268
+ form: "single",
87269
+ optional: false
87270
+ }],
86837
87271
  "mediaPlayer.getStatus": [{
86838
87272
  name: "deviceId",
86839
87273
  form: "single",
@@ -86899,6 +87333,26 @@ Object.freeze({
86899
87333
  form: "single",
86900
87334
  optional: false
86901
87335
  }],
87336
+ "mediaPlayer.turnOff": [{
87337
+ name: "deviceId",
87338
+ form: "single",
87339
+ optional: false
87340
+ }],
87341
+ "mediaPlayer.turnOn": [{
87342
+ name: "deviceId",
87343
+ form: "single",
87344
+ optional: false
87345
+ }],
87346
+ "mediaPlayer.volumeDown": [{
87347
+ name: "deviceId",
87348
+ form: "single",
87349
+ optional: false
87350
+ }],
87351
+ "mediaPlayer.volumeUp": [{
87352
+ name: "deviceId",
87353
+ form: "single",
87354
+ optional: false
87355
+ }],
86902
87356
  "meter.getStatus": [{
86903
87357
  name: "deviceId",
86904
87358
  form: "single",
@@ -87208,21 +87662,6 @@ Object.freeze({
87208
87662
  form: "single",
87209
87663
  optional: false
87210
87664
  }],
87211
- "petFountain.setPumpInterval": [{
87212
- name: "deviceId",
87213
- form: "single",
87214
- optional: false
87215
- }],
87216
- "petFountain.setPumpOffMinutes": [{
87217
- name: "deviceId",
87218
- form: "single",
87219
- optional: false
87220
- }],
87221
- "petFountain.setPumpOnMinutes": [{
87222
- name: "deviceId",
87223
- form: "single",
87224
- optional: false
87225
- }],
87226
87665
  "pipelineAnalytics.addRetrainFrameFromRecording": [{
87227
87666
  name: "deviceId",
87228
87667
  form: "single",
@@ -87858,6 +88297,26 @@ Object.freeze({
87858
88297
  form: "single",
87859
88298
  optional: false
87860
88299
  }],
88300
+ "pumpCycle.getStatus": [{
88301
+ name: "deviceId",
88302
+ form: "single",
88303
+ optional: false
88304
+ }],
88305
+ "pumpCycle.setEnabled": [{
88306
+ name: "deviceId",
88307
+ form: "single",
88308
+ optional: false
88309
+ }],
88310
+ "pumpCycle.setOffMinutes": [{
88311
+ name: "deviceId",
88312
+ form: "single",
88313
+ optional: false
88314
+ }],
88315
+ "pumpCycle.setOnMinutes": [{
88316
+ name: "deviceId",
88317
+ form: "single",
88318
+ optional: false
88319
+ }],
87861
88320
  "reboot.reboot": [{
87862
88321
  name: "deviceId",
87863
88322
  form: "single",
@@ -88059,6 +88518,11 @@ Object.freeze({
88059
88518
  form: "single",
88060
88519
  optional: true
88061
88520
  }],
88521
+ "remote.getAssumedStatus": [{
88522
+ name: "deviceId",
88523
+ form: "single",
88524
+ optional: false
88525
+ }],
88062
88526
  "remote.getConfig": [{
88063
88527
  name: "deviceId",
88064
88528
  form: "single",
@@ -89314,6 +89778,7 @@ var STATE_SUMMARY_VARS = [
89314
89778
  [...RULE_TEXT],
89315
89779
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
89316
89780
  ([...RULE_TEXT], [...COMBINED_ALARM]),
89781
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
89317
89782
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
89318
89783
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
89319
89784
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -90075,13 +90540,20 @@ var GoogleBrokerEntrySchema = object({
90075
90540
  calendarFilter: string().default(""),
90076
90541
  /** Default From for Gmail send — usually the connected account email. */
90077
90542
  fromAddress: string().default(""),
90078
- fromName: string().default("")
90543
+ fromName: string().default(""),
90544
+ /**
90545
+ * The stamped calendar ids (`<brokerId>:<googleCalendarId>`) this broker imports. `undefined`
90546
+ * means "not moved here yet": the reader falls back to the shared `calendarIds` setting of
90547
+ * the addon until the migration copied it. Empty = import none.
90548
+ */
90549
+ calendarIds: array(string()).optional()
90079
90550
  });
90080
90551
  var GoogleServicesConfigSchema = object({
90081
90552
  brokers: array(GoogleBrokerEntrySchema).default([]),
90082
90553
  /**
90083
- * Stamped calendar ids (`brokerId:googleCalendarId`) to import.
90084
- * Empty = import none. Edited under Connectivity → Calendars.
90554
+ * @deprecated The shared set (`brokerId:googleCalendarId`). Each broker owns its own
90555
+ * `calendarIds` now; this key is read only while a broker has none, and the migration
90556
+ * `google-services/2026-11-03-drop-shared-calendar-ids` deletes it.
90085
90557
  */
90086
90558
  calendarIds: array(string()).default([]),
90087
90559
  /** @deprecated Pre-broker singleton — cleared after migrateLegacyToBrokers. */
@@ -90159,14 +90631,15 @@ function settingsToBrokerFields(settings) {
90159
90631
  clientSecret: parsed.clientSecret,
90160
90632
  calendarFilter: calendarIdsToFilter(parsed),
90161
90633
  fromAddress: parsed.fromAddress ?? "",
90162
- fromName: parsed.fromName ?? ""
90634
+ fromName: parsed.fromName ?? "",
90635
+ ...parsed.calendarIds !== void 0 ? { calendarIds: [...parsed.calendarIds] } : {}
90163
90636
  };
90164
90637
  }
90165
90638
  function brokerEntryToSettings(entry) {
90166
90639
  return {
90167
90640
  clientId: entry.clientId,
90168
90641
  clientSecret: entry.clientSecret,
90169
- calendarIds: [...parseCalendarFilter(entry.calendarFilter)],
90642
+ calendarIds: [...entry.calendarIds ?? parseCalendarFilter(entry.calendarFilter)],
90170
90643
  fromAddress: entry.fromAddress,
90171
90644
  fromName: entry.fromName
90172
90645
  };
@@ -90217,6 +90690,13 @@ function migrateLegacyToBrokers(rawConfig, legacyOauth) {
90217
90690
  didMigrate: true
90218
90691
  };
90219
90692
  }
90693
+ /**
90694
+ * The stamped calendar ids one broker imports: its own set, or — while the migration has not
90695
+ * reached it — the shared setting older versions wrote, so the move never changes what is imported.
90696
+ */
90697
+ function calendarIdsOfEntry(entry, shared) {
90698
+ return new Set(entry?.calendarIds ?? shared);
90699
+ }
90220
90700
  function parseCalendarFilter(raw) {
90221
90701
  const trimmed = raw.trim();
90222
90702
  if (!trimmed) return /* @__PURE__ */ new Set();
@@ -90303,6 +90783,7 @@ var GoogleBrokerRegistry = class {
90303
90783
  const entry = GoogleBrokerEntrySchema.parse({
90304
90784
  id,
90305
90785
  name,
90786
+ calendarIds: [],
90306
90787
  ...fields
90307
90788
  });
90308
90789
  this.entries = [...this.entries, entry];
@@ -90316,6 +90797,7 @@ var GoogleBrokerRegistry = class {
90316
90797
  const next = GoogleBrokerEntrySchema.parse({
90317
90798
  id: prev.id,
90318
90799
  name: prev.name,
90800
+ ...prev.calendarIds !== void 0 ? { calendarIds: prev.calendarIds } : {},
90319
90801
  ...fields
90320
90802
  });
90321
90803
  this.entries = this.entries.map((e, i) => i === idx ? next : e);
@@ -90521,6 +91003,24 @@ function buildGoogleBrokerSettingsSchema(publicOrigin) {
90521
91003
  showToggle: true
90522
91004
  }]
90523
91005
  },
91006
+ {
91007
+ id: "google-calendars-import",
91008
+ title: "Calendars to import",
91009
+ titleKey: "google-services:googleBrokerSettings.s_google-calendars-import.title",
91010
+ description: "Nothing selected = no calendars imported from this account. Options appear once the account is signed in.",
91011
+ descriptionKey: "google-services:googleBrokerSettings.s_google-calendars-import.description",
91012
+ columns: 1,
91013
+ fields: [{
91014
+ type: "multiselect",
91015
+ key: "calendarIds",
91016
+ label: "Calendars",
91017
+ labelKey: "google-services:googleBrokerSettings.s_google-calendars-import.calendarIds.label",
91018
+ options: [],
91019
+ optionsFrom: "_calendarCatalog",
91020
+ description: "The calendars of this Google account that CamStack imports.",
91021
+ descriptionKey: "google-services:googleBrokerSettings.s_google-calendars-import.calendarIds.description"
91022
+ }]
91023
+ },
90524
91024
  {
90525
91025
  id: "gmail-send",
90526
91026
  title: "Gmail send",
@@ -90544,7 +91044,7 @@ function buildGoogleBrokerSettingsSchema(publicOrigin) {
90544
91044
  }
90545
91045
  ] };
90546
91046
  }
90547
- /** Connectivity Calendars / Email pages — calendar import picker lives here (not on broker Add). */
91047
+ /** Connectivity Calendars / Email pages — the calendar import picker is on each broker. */
90548
91048
  function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
90549
91049
  const redirect = escapeHtml(redirectUriDisplay(publicOrigin));
90550
91050
  if (cap === "calendar-source") return { sections: [{
@@ -90560,27 +91060,10 @@ function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
90560
91060
  format: "html",
90561
91061
  content: [
90562
91062
  "<p>Link a Google account under Connectivity → <strong>Brokers</strong> ",
90563
- "(Client ID / secret + Sign in). Then pick calendars below. ",
91063
+ "(Client ID / secret + Sign in). Pick the calendars to import on each broker. ",
90564
91064
  `OAuth redirect URI: <code>${redirect}</code>.</p>`
90565
91065
  ].join("")
90566
91066
  }]
90567
- }, {
90568
- id: "google-calendars-import",
90569
- title: "Calendars to import",
90570
- titleKey: "google-services:googleFeatureSettings.s_google-calendars-import.title",
90571
- description: "Nothing selected = no calendars imported. Options appear after at least one Google broker is signed in.",
90572
- descriptionKey: "google-services:googleFeatureSettings.s_google-calendars-import.description",
90573
- columns: 1,
90574
- fields: [{
90575
- type: "multiselect",
90576
- key: "calendarIds",
90577
- label: "Calendars",
90578
- labelKey: "google-services:googleFeatureSettings.s_google-calendars-import.calendarIds.label",
90579
- options: [],
90580
- optionsFrom: "_calendarCatalog",
90581
- description: "Ids are stamped as brokerId:googleCalendarId across every Google broker.",
90582
- descriptionKey: "google-services:googleFeatureSettings.s_google-calendars-import.calendarIds.description"
90583
- }]
90584
91067
  }] };
90585
91068
  return { sections: [{
90586
91069
  id: "setup-intro",
@@ -90670,11 +91153,11 @@ function createGoogleCalendarApi(getAccessToken, http = { fetch }) {
90670
91153
  function createGoogleCalendarSource(deps) {
90671
91154
  return {
90672
91155
  listCalendars: async () => {
90673
- const selected = deps.selectedCalendarIds();
90674
- if (selected.size === 0) return [];
90675
91156
  const out = [];
90676
91157
  for (const account of deps.accounts()) {
90677
91158
  if (!account.connected) continue;
91159
+ const selected = deps.selectedCalendarIds(account.brokerId);
91160
+ if (selected.size === 0) continue;
90678
91161
  try {
90679
91162
  const list = await account.api.listCalendars();
90680
91163
  for (const c of list) {
@@ -90697,20 +91180,18 @@ function createGoogleCalendarSource(deps) {
90697
91180
  return out;
90698
91181
  },
90699
91182
  listEvents: async (input) => {
90700
- const selected = deps.selectedCalendarIds();
90701
- if (selected.size === 0) return [];
90702
91183
  const timeMin = new Date(input.startMs).toISOString();
90703
91184
  const timeMax = new Date(input.endMs).toISOString();
90704
91185
  const accounts = deps.accounts().filter((a) => a.connected);
90705
91186
  const byBroker = new Map(accounts.map((a) => [a.brokerId, a]));
90706
- const requestIds = input.calendarIds !== void 0 && input.calendarIds.length > 0 ? input.calendarIds : [...selected];
91187
+ 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)))];
90707
91188
  /** brokerId → google calendar ids to fetch */
90708
91189
  const wanted = /* @__PURE__ */ new Map();
90709
91190
  for (const stamped of requestIds) {
90710
- if (!selected.has(stamped)) continue;
90711
91191
  const parsed = parseStampedCalendarId(stamped);
90712
91192
  if (parsed === null) continue;
90713
91193
  if (!byBroker.has(parsed.brokerId)) continue;
91194
+ if (!deps.selectedCalendarIds(parsed.brokerId).has(stamped)) continue;
90714
91195
  const list = wanted.get(parsed.brokerId) ?? [];
90715
91196
  list.push(parsed.googleCalendarId);
90716
91197
  wanted.set(parsed.brokerId, list);
@@ -91059,11 +91540,152 @@ var googleBrokersEntryKind = defineEntryKind("brokers", {
91059
91540
  clientSecret: GoogleBrokerEntrySchema.shape.clientSecret,
91060
91541
  calendarFilter: GoogleBrokerEntrySchema.shape.calendarFilter,
91061
91542
  fromAddress: GoogleBrokerEntrySchema.shape.fromAddress,
91062
- fromName: GoogleBrokerEntrySchema.shape.fromName
91543
+ fromName: GoogleBrokerEntrySchema.shape.fromName,
91544
+ calendarIds: GoogleBrokerEntrySchema.shape.calendarIds
91063
91545
  }),
91064
91546
  writeTrigger: "an operator adds or edits a Google OAuth broker"
91065
91547
  });
91066
91548
  //#endregion
91549
+ //#region src/migrations/calendar-ids-to-brokers.ts
91550
+ /**
91551
+ * The calendars a Google account imports are a fact of THAT account (D985): each broker record
91552
+ * carries its own `calendarIds`, and the addon-level `calendarIds` setting (one shared set of
91553
+ * `<brokerId>:<googleCalendarId>` ids) goes away.
91554
+ *
91555
+ * Two migrations, in this order:
91556
+ *
91557
+ * 1. `calendar-ids-to-brokers` (procedure) copies the shared set to EVERY broker record that has
91558
+ * no `calendarIds` yet. A record that already has one is never overwritten; a broker deleted
91559
+ * meanwhile is skipped, never recreated; a stored value that is not a list of strings is an
91560
+ * ambiguity and throws (nothing is guessed).
91561
+ * 2. `drop-shared-calendar-ids` (plan) deletes the shared setting, but only after a read-back:
91562
+ * every live broker must show its own `calendarIds`. When one of them holds a set that differs
91563
+ * from the shared one (an operator choice made before the copy), the shared value is the
91564
+ * loser and stays PARKED where it is, never deleted.
91565
+ *
91566
+ * Both are idempotent: with the copy landed the first reports `already-done`, and with the row
91567
+ * gone the second plans nothing.
91568
+ */
91569
+ var GOOGLE_ADDON_ID = "google-services";
91570
+ /** The canonical settings table: one row per key, id `<addonId>.<key>`. */
91571
+ var ADDON_SETTINGS_TABLE = "addon-settings";
91572
+ /** The legacy shared set. */
91573
+ var SHARED_CALENDAR_IDS_KEY = "calendarIds";
91574
+ async function readSharedSet(ports) {
91575
+ const answer = await ports.readStored(SHARED_CALENDAR_IDS_KEY);
91576
+ if (answer.kind === "unreadable") throw new Error(`google-services: the shared calendarIds cannot be read (${answer.reason})`);
91577
+ if (answer.kind === "absent") return null;
91578
+ const value = answer.value;
91579
+ 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");
91580
+ return value;
91581
+ }
91582
+ async function copyCalendarIdsToBrokers(ports) {
91583
+ const shared = await readSharedSet(ports);
91584
+ if (shared === null) return {
91585
+ outcome: "already-done",
91586
+ summary: { reason: "no-shared-set" }
91587
+ };
91588
+ let copied = 0;
91589
+ let kept = 0;
91590
+ let gone = 0;
91591
+ for (const broker of await ports.listBrokers()) {
91592
+ if (broker.calendarIds !== void 0) {
91593
+ kept += 1;
91594
+ continue;
91595
+ }
91596
+ if (await ports.setBrokerCalendarIds(broker.id, shared)) copied += 1;
91597
+ else gone += 1;
91598
+ }
91599
+ return {
91600
+ outcome: copied === 0 ? "already-done" : "done",
91601
+ summary: {
91602
+ copied,
91603
+ kept,
91604
+ gone
91605
+ }
91606
+ };
91607
+ }
91608
+ var sameSet = (a, b) => {
91609
+ const left = new Set(a);
91610
+ const right = new Set(b);
91611
+ return left.size === right.size && [...left].every((id) => right.has(id));
91612
+ };
91613
+ async function planDropSharedCalendarIds(ports) {
91614
+ const shared = await readSharedSet(ports);
91615
+ if (shared === null) return { edits: [] };
91616
+ const brokers = await ports.listBrokers();
91617
+ if (brokers.length === 0) return { edits: [] };
91618
+ const missing = brokers.filter((broker) => broker.calendarIds === void 0);
91619
+ 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`);
91620
+ if (!brokers.every((broker) => sameSet(broker.calendarIds ?? [], shared))) return { edits: [] };
91621
+ const rowId = `${GOOGLE_ADDON_ID}.${SHARED_CALENDAR_IDS_KEY}`;
91622
+ return { edits: [{
91623
+ op: "deleteKeys",
91624
+ scope: ADDON_SETTINGS_TABLE,
91625
+ keys: [rowId],
91626
+ precondition: preconditionOf({ [rowId]: {
91627
+ addonId: GOOGLE_ADDON_ID,
91628
+ key: SHARED_CALENDAR_IDS_KEY,
91629
+ value: shared
91630
+ } })
91631
+ }] };
91632
+ }
91633
+ //#endregion
91634
+ //#region src/migrations/index.ts
91635
+ /** The `google-services` catalogue (D987): offered to the engine when the addon initialises. */
91636
+ var ADDON_MIGRATIONS = [{
91637
+ meta: {
91638
+ id: "google-services/2026-11-03-calendar-ids-to-brokers",
91639
+ owner: "google-services",
91640
+ level: "addon-state",
91641
+ seq: 1,
91642
+ dependsOn: [],
91643
+ store: "settings",
91644
+ scope: "cluster",
91645
+ onFailure: "tolerate",
91646
+ bodyKind: "procedure",
91647
+ description: "Copy the shared calendar set to every Google broker record that has none of its own",
91648
+ fixtureMark: "the client secret marker of a broker record the migration does not know"
91649
+ },
91650
+ procedure: copyCalendarIdsToBrokers
91651
+ }, {
91652
+ meta: {
91653
+ id: "google-services/2026-11-03-drop-shared-calendar-ids",
91654
+ owner: "google-services",
91655
+ level: "addon-state",
91656
+ seq: 2,
91657
+ dependsOn: ["google-services/2026-11-03-calendar-ids-to-brokers"],
91658
+ store: "settings",
91659
+ scope: "cluster",
91660
+ onFailure: "tolerate",
91661
+ bodyKind: "plan",
91662
+ description: "Delete the shared calendar set once every broker shows its own",
91663
+ fixtureMark: "the shared set parked when a broker holds a different one"
91664
+ },
91665
+ plan: planDropSharedCalendarIds
91666
+ }];
91667
+ //#endregion
91668
+ //#region src/migrations/ports.ts
91669
+ /** The migration's ports over the `brokers` record kind and the addon's raw settings read. */
91670
+ function calendarIdsPortsOver(kind, readStored) {
91671
+ return {
91672
+ readStored,
91673
+ listBrokers: async () => {
91674
+ return (await kind.query({ limit: 1e3 })).records.map((record) => ({
91675
+ id: record.id,
91676
+ calendarIds: record.data.calendarIds
91677
+ }));
91678
+ },
91679
+ setBrokerCalendarIds: async (id, ids) => {
91680
+ const row = await kind.get(id);
91681
+ if (row === null) return false;
91682
+ if (row.data.calendarIds !== void 0) return true;
91683
+ await kind.patch(id, { calendarIds: [...ids] });
91684
+ return true;
91685
+ }
91686
+ };
91687
+ }
91688
+ //#endregion
91067
91689
  //#region src/google-broker-store.ts
91068
91690
  function rowToEntry(id, data) {
91069
91691
  const parsed = GoogleBrokerEntrySchema.safeParse({
@@ -91101,14 +91723,15 @@ var GoogleBrokerStore = class GoogleBrokerStore {
91101
91723
  const nextIds = new Set(entries.map((e) => e.id));
91102
91724
  for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
91103
91725
  for (const entry of entries) {
91104
- const { id, name, clientId, clientSecret, calendarFilter, fromAddress, fromName } = entry;
91726
+ const { id, name, clientId, clientSecret, calendarFilter, fromAddress, fromName, calendarIds } = entry;
91105
91727
  const body = {
91106
91728
  name,
91107
91729
  clientId,
91108
91730
  clientSecret,
91109
91731
  calendarFilter,
91110
91732
  fromAddress,
91111
- fromName
91733
+ fromName,
91734
+ ...calendarIds !== void 0 ? { calendarIds } : {}
91112
91735
  };
91113
91736
  const parsed = googleBrokersEntryKind.schema.safeParse(body);
91114
91737
  if (!parsed.success) throw new Error(`invalid Google broker '${id}': ${parsed.error.message}`);
@@ -91205,6 +91828,7 @@ var GoogleServicesAddon = class extends BaseAddon {
91205
91828
  await this.brokerStore.replaceAll(this.config.brokers);
91206
91829
  this.activeBrokers = await this.loadPersistedBrokers();
91207
91830
  }
91831
+ await this.migrateCalendarIds(records);
91208
91832
  this.registry.seedFrom(this.activeBrokers, oauthAccounts.accounts);
91209
91833
  await this.refreshPublicOrigin();
91210
91834
  const calendarProvider = createGoogleCalendarSource({
@@ -91214,7 +91838,7 @@ var GoogleServicesAddon = class extends BaseAddon {
91214
91838
  connected: this.registry.isConnected(entry.id),
91215
91839
  api: createGoogleCalendarApi(() => this.ensureAccessToken(entry.id))
91216
91840
  })),
91217
- selectedCalendarIds: () => new Set(this.config.calendarIds ?? [])
91841
+ selectedCalendarIds: (brokerId) => this.calendarIdsOf(brokerId)
91218
91842
  });
91219
91843
  const smtpProvider = createGmailSmtpProvider({ accounts: () => this.registry.listEntries().map((entry) => {
91220
91844
  const oauth = this.registry.getOauth(entry.id);
@@ -91385,7 +92009,10 @@ var GoogleServicesAddon = class extends BaseAddon {
91385
92009
  getSettings: async ({ id }) => {
91386
92010
  const entry = this.registry.getEntry(id);
91387
92011
  if (entry === null) return null;
91388
- return brokerEntryToSettings(entry);
92012
+ return {
92013
+ ...brokerEntryToSettings(entry),
92014
+ _calendarCatalog: await this.loadStampedCalendarCatalog(id)
92015
+ };
91389
92016
  },
91390
92017
  setSettings: async ({ id, settings }) => {
91391
92018
  if (!owns(id)) return;
@@ -91398,6 +92025,7 @@ var GoogleServicesAddon = class extends BaseAddon {
91398
92025
  const oauth = this.registry.getOauth(id);
91399
92026
  return {
91400
92027
  ...brokerEntryToSettings(entry),
92028
+ _calendarCatalog: await this.loadStampedCalendarCatalog(id),
91401
92029
  accountEmail: oauth?.accountEmail ?? "",
91402
92030
  connected: this.registry.isConnected(id)
91403
92031
  };
@@ -91603,6 +92231,13 @@ var GoogleServicesAddon = class extends BaseAddon {
91603
92231
  const cutoff = Date.now() - 10 * 6e4;
91604
92232
  for (const [k, v] of this.pendingStates) if (v.issuedAt < cutoff) this.pendingStates.delete(k);
91605
92233
  }
92234
+ /**
92235
+ * The shared calendar set becomes each broker's own (D985, D987). Brokers are reloaded once the
92236
+ * copy landed, so the registry is seeded with the sets the migration wrote.
92237
+ */
92238
+ async migrateCalendarIds(records) {
92239
+ if ((await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, calendarIdsPortsOver(records.kind(googleBrokersEntryKind), (key) => this.readStoredGlobalKey(key))))).applied.length > 0) this.activeBrokers = await this.loadPersistedBrokers();
92240
+ }
91606
92241
  async loadPersistedBrokers() {
91607
92242
  const store = this.brokerStore;
91608
92243
  if (store !== null) {
@@ -91626,9 +92261,10 @@ var GoogleServicesAddon = class extends BaseAddon {
91626
92261
  await this.oauthAccountsHandle.set({ accounts: [...this.registry.listOauthAccounts()] });
91627
92262
  }
91628
92263
  /** Stamped catalog for Connectivity → Calendars (`optionsFrom`). */
91629
- async loadStampedCalendarCatalog() {
92264
+ async loadStampedCalendarCatalog(onlyBrokerId) {
91630
92265
  const out = [];
91631
92266
  for (const entry of this.registry.listEntries()) {
92267
+ if (onlyBrokerId !== void 0 && entry.id !== onlyBrokerId) continue;
91632
92268
  if (!this.registry.isConnected(entry.id)) continue;
91633
92269
  try {
91634
92270
  const list = await createGoogleCalendarApi(() => this.ensureAccessToken(entry.id)).listCalendars();
@@ -91768,13 +92404,15 @@ var GoogleServicesAddon = class extends BaseAddon {
91768
92404
  }
91769
92405
  async getGlobalSettings(overlay, cap, nodeId) {
91770
92406
  await this.refreshPublicOrigin();
91771
- if (cap !== "calendar-source") return super.getGlobalSettings(overlay, cap, nodeId);
91772
- const catalog = await this.loadStampedCalendarCatalog();
91773
- return super.getGlobalSettings({
91774
- ...overlay,
91775
- calendarIds: this.config.calendarIds ?? [],
91776
- ...catalog.length > 0 ? { _calendarCatalog: catalog } : {}
91777
- }, cap, nodeId);
92407
+ return super.getGlobalSettings(overlay, cap, nodeId);
92408
+ }
92409
+ /**
92410
+ * The stamped calendar ids ONE broker imports. A broker the migration has not reached yet
92411
+ * (no `calendarIds` of its own) reads the shared setting older versions wrote, so the move
92412
+ * never changes what is imported mid-way.
92413
+ */
92414
+ calendarIdsOf(brokerId) {
92415
+ return calendarIdsOfEntry(this.registry.getEntry(brokerId), this.config.calendarIds ?? []);
91778
92416
  }
91779
92417
  };
91780
92418
  function asSettingsCap(cap) {