@camstack/addon-provider-homeassistant 1.2.654 → 1.2.656

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.656] - 2026-11-04
11
+
12
+ ### Changed
13
+
14
+ - Home Assistant export: each instance's export switch, image-link address and exported devices now live in one record per instance; a migration moves the old settings and integration values, and the export no longer reads them
15
+
16
+ ## [1.2.655] - 2026-11-04
17
+
18
+ ### Changed
19
+
20
+ - Exposing or hiding a device in Home Assistant export changes only the Home Assistant instance it names, not every instance
21
+ - Home Assistant connections are listed on the instances page; their stored connection is flattened once at start-up (same ids and tokens).
22
+ - Connections move to the flat body without stranding keychain refs: the old token refs are released when the nested object goes
23
+
10
24
  ## [1.2.654] - 2026-11-03
11
25
 
12
26
  ### Changed
package/dist/addon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_ha_instance_settings = require("./ha-instance-settings-Bn_Trz6p.js");
2
+ const require_ha_instance_settings = require("./ha-instance-settings-Dm0tU8iv.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_zlib = require("node:zlib");
5
5
  //#region src/ha-admin-link.ts
@@ -5765,22 +5765,16 @@ var TEST_HANDSHAKE_TIMEOUT_MS = 8e3;
5765
5765
  var HaBrokerEntrySchema = require_ha_instance_settings.object({
5766
5766
  id: require_ha_instance_settings.string(),
5767
5767
  name: require_ha_instance_settings.string(),
5768
- /** Either Supervisor (token from env) or long-lived (persisted). */
5769
- auth: require_ha_instance_settings.discriminatedUnion("kind", [require_ha_instance_settings.object({
5770
- kind: require_ha_instance_settings.literal("supervisor"),
5771
- supervisorToken: require_ha_instance_settings.string().meta({ secret: true })
5772
- }), require_ha_instance_settings.object({
5773
- kind: require_ha_instance_settings.literal("long-lived"),
5774
- baseUrl: require_ha_instance_settings.string(),
5775
- accessToken: require_ha_instance_settings.string().meta({ secret: true })
5776
- })]),
5777
- /** Optional FK to the integration record that spawned this broker.
5778
- * Populated when the wizard's "+ New Integration" flow created the
5779
- * broker; null when the operator added it via the Settings panel
5780
- * directly. Used by the integration listener to keep the broker
5781
- * list in sync with the integrations table (cascade-delete on
5782
- * integration removal). */
5783
- integrationId: require_ha_instance_settings.string().optional()
5768
+ /** Supervisor (token from env) or long-lived (persisted); picks which fields below are used. */
5769
+ authKind: require_ha_instance_settings._enum(["long-lived", "supervisor"]),
5770
+ /** Origin URL of the HA instance; long-lived only. */
5771
+ baseUrl: require_ha_instance_settings.string().optional(),
5772
+ accessToken: require_ha_instance_settings.string().optional().meta({ secret: true }),
5773
+ supervisorToken: require_ha_instance_settings.string().optional().meta({ secret: true }),
5774
+ /** Optional FK to the integration record that spawned this broker. */
5775
+ integrationId: require_ha_instance_settings.string().optional(),
5776
+ /** The instance's own switch (D996); absent means on. */
5777
+ enabled: require_ha_instance_settings.boolean().optional()
5784
5778
  });
5785
5779
  /**
5786
5780
  * Validates a `broker.add` `settings` payload for the HA kind. Accepts:
@@ -5805,6 +5799,34 @@ function settingsToAuth(settings) {
5805
5799
  accessToken: parsed.accessToken
5806
5800
  };
5807
5801
  }
5802
+ /** The flat entry fields of an auth config: the one place the two shapes meet. */
5803
+ function authToEntryFields(auth) {
5804
+ return auth.kind === "supervisor" ? {
5805
+ authKind: "supervisor",
5806
+ supervisorToken: auth.supervisorToken
5807
+ } : {
5808
+ authKind: "long-lived",
5809
+ baseUrl: auth.baseUrl,
5810
+ accessToken: auth.accessToken
5811
+ };
5812
+ }
5813
+ /** The auth config an entry stands for; null while the entry is not filled in. */
5814
+ function haAuthOf(entry) {
5815
+ const parsed = HaAuthConfigSchema.safeParse(entry.authKind === "supervisor" ? {
5816
+ kind: "supervisor",
5817
+ supervisorToken: entry.supervisorToken ?? ""
5818
+ } : {
5819
+ kind: "long-lived",
5820
+ baseUrl: entry.baseUrl ?? "",
5821
+ accessToken: entry.accessToken ?? ""
5822
+ });
5823
+ return parsed.success ? parsed.data : null;
5824
+ }
5825
+ /** The flat settings payload (`settingsToAuth` shape) of an entry; null while it is not filled in. */
5826
+ function entryToSettings(entry) {
5827
+ const auth = haAuthOf(entry);
5828
+ return auth === null ? null : authToSettings(auth);
5829
+ }
5808
5830
  function authToSettings(auth) {
5809
5831
  if (auth.kind === "supervisor") return { supervisorToken: auth.supervisorToken };
5810
5832
  return {
@@ -5867,7 +5889,7 @@ var HaBrokerRegistry = class {
5867
5889
  * started concurrently; transient errors don't block bootstrap. */
5868
5890
  async restore(entries) {
5869
5891
  this.seedCounterFromEntries(entries);
5870
- await Promise.all(entries.map(async (entry) => {
5892
+ await Promise.all(entries.filter((entry) => entry.enabled !== false && haAuthOf(entry) !== null).map(async (entry) => {
5871
5893
  try {
5872
5894
  await this.startManagerForEntry(entry);
5873
5895
  } catch (err) {
@@ -5928,7 +5950,7 @@ var HaBrokerRegistry = class {
5928
5950
  const entry = {
5929
5951
  id: options.id !== void 0 ? this.adoptId(options.id) : this.allocateId(),
5930
5952
  name,
5931
- auth,
5953
+ ...authToEntryFields(auth),
5932
5954
  ...options.integrationId !== void 0 ? { integrationId: options.integrationId } : {}
5933
5955
  };
5934
5956
  await this.startManagerForEntry(entry);
@@ -5949,7 +5971,7 @@ var HaBrokerRegistry = class {
5949
5971
  return {
5950
5972
  id,
5951
5973
  name: existing.getInfo().name,
5952
- auth
5974
+ ...authToEntryFields(auth)
5953
5975
  };
5954
5976
  }
5955
5977
  /**
@@ -6132,10 +6154,12 @@ var HaBrokerRegistry = class {
6132
6154
  this.logger.debug("ha-broker already active — reusing existing connection", { meta: { brokerId: entry.id } });
6133
6155
  return;
6134
6156
  }
6157
+ const auth = haAuthOf(entry);
6158
+ if (auth === null) throw new Error(`ha-broker '${entry.id}' has no complete credentials`);
6135
6159
  const manager = new HaIntegrationManager({
6136
6160
  id: entry.id,
6137
6161
  name: entry.name,
6138
- auth: entry.auth,
6162
+ auth,
6139
6163
  logger: this.logger,
6140
6164
  eventBus: this.eventBus,
6141
6165
  onConnected: (brokerId) => this.onBrokerConnected?.(brokerId),
@@ -7196,16 +7220,24 @@ function createHaIconRouteProvider() {
7196
7220
  //#region src/broker-record-kinds.ts
7197
7221
  /**
7198
7222
  * Broker entry kind (B2): credentials live in records; integrations keep brokerId.
7223
+ *
7224
+ * The body is FLAT (planning#408 P3): the form the instances page edits is the stored shape,
7225
+ * with the two tokens at the top level where the records door keeps them in the keychain.
7199
7226
  */
7200
7227
  var HA_BROKERS_KIND = "brokers";
7201
7228
  var HaBrokerBodySchema = require_ha_instance_settings.entryBody({
7202
- auth: HaBrokerEntrySchema.shape.auth,
7203
- integrationId: require_ha_instance_settings.string().optional()
7229
+ authKind: HaBrokerEntrySchema.shape.authKind,
7230
+ baseUrl: HaBrokerEntrySchema.shape.baseUrl,
7231
+ accessToken: HaBrokerEntrySchema.shape.accessToken,
7232
+ supervisorToken: HaBrokerEntrySchema.shape.supervisorToken,
7233
+ integrationId: HaBrokerEntrySchema.shape.integrationId,
7234
+ enabled: HaBrokerEntrySchema.shape.enabled
7204
7235
  });
7205
7236
  var haBrokersEntryKind = require_ha_instance_settings.defineEntryKind(HA_BROKERS_KIND, {
7206
7237
  title: "Home Assistant brokers",
7207
7238
  schema: HaBrokerBodySchema,
7208
- writeTrigger: "an operator adds, edits or removes a Home Assistant broker connection"
7239
+ writeTrigger: "an operator adds, edits or removes a Home Assistant broker connection",
7240
+ retiredSecretPaths: ["auth.accessToken", "auth.supervisorToken"]
7209
7241
  });
7210
7242
  //#endregion
7211
7243
  //#region src/record-kinds.ts
@@ -7737,8 +7769,12 @@ function rowToEntry(id, data) {
7737
7769
  const parsed = HaBrokerEntrySchema.safeParse({
7738
7770
  id,
7739
7771
  name: data.name,
7740
- auth: data.auth,
7741
- integrationId: data.integrationId
7772
+ authKind: data.authKind,
7773
+ baseUrl: data.baseUrl,
7774
+ accessToken: data.accessToken,
7775
+ supervisorToken: data.supervisorToken,
7776
+ integrationId: data.integrationId,
7777
+ enabled: data.enabled
7742
7778
  });
7743
7779
  return parsed.success ? parsed.data : null;
7744
7780
  }
@@ -7777,10 +7813,9 @@ var HaBrokerStore = class HaBrokerStore {
7777
7813
  }
7778
7814
  }
7779
7815
  async upsert(entry) {
7780
- const { id, name, auth, integrationId } = entry;
7816
+ const { id, integrationId, ...fields } = entry;
7781
7817
  const body = {
7782
- name,
7783
- auth,
7818
+ ...Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0)),
7784
7819
  ...integrationId === void 0 ? {} : { integrationId }
7785
7820
  };
7786
7821
  const parsed = haBrokersEntryKind.schema.safeParse(body);
@@ -7801,6 +7836,98 @@ var HaBrokerStore = class HaBrokerStore {
7801
7836
  }
7802
7837
  };
7803
7838
  //#endregion
7839
+ //#region src/ha-instances.ts
7840
+ /**
7841
+ * The Home Assistant connections as `integration-instances` (planning#408 P3): one instance per
7842
+ * `brokers` record, over the SAME records and ids (`ha_NNN`) the `broker` cap serves. The body is
7843
+ * flat, so the form the instances page edits is the stored shape.
7844
+ */
7845
+ var HA_INSTANCE_KIND = "home-assistant";
7846
+ function isHaBodyConfigured(body) {
7847
+ return body.authKind === "supervisor" ? (body.supervisorToken ?? "").length > 0 : (body.baseUrl ?? "").length > 0 && (body.accessToken ?? "").length > 0;
7848
+ }
7849
+ function statusOf(info) {
7850
+ if (info === null) return {
7851
+ state: "unknown",
7852
+ label: "Not connected"
7853
+ };
7854
+ if (info.status === "connected") return {
7855
+ state: "ok",
7856
+ label: "Connected"
7857
+ };
7858
+ if (info.status === "connecting") return {
7859
+ state: "unknown",
7860
+ label: "Connecting"
7861
+ };
7862
+ return {
7863
+ state: "error",
7864
+ label: info.status,
7865
+ ...info.error !== null ? { detail: info.error } : {}
7866
+ };
7867
+ }
7868
+ function buildHaInstances(records, deps) {
7869
+ return new require_ha_instance_settings.RecordIntegrationInstances(records, haBrokersEntryKind, {
7870
+ description: {
7871
+ role: "both",
7872
+ instanceMode: "multiple",
7873
+ kind: "connection",
7874
+ canTest: true,
7875
+ kinds: [{
7876
+ id: HA_INSTANCE_KIND,
7877
+ label: "Home Assistant"
7878
+ }],
7879
+ allowRestoreId: true
7880
+ },
7881
+ formSchema: () => haBrokerSettingsSchema,
7882
+ initialBody: (name) => ({
7883
+ name,
7884
+ authKind: "long-lived"
7885
+ }),
7886
+ mintId: () => deps.mintId(),
7887
+ kindOf: () => HA_INSTANCE_KIND,
7888
+ health: (_body, id) => {
7889
+ const info = deps.infoOf(id);
7890
+ return {
7891
+ status: statusOf(info),
7892
+ ...info?.lastCheckedAt != null ? { lastCheckedAt: info.lastCheckedAt } : {}
7893
+ };
7894
+ },
7895
+ configured: isHaBodyConfigured,
7896
+ onChanged: deps.onChanged,
7897
+ test: async ({ id, values }) => {
7898
+ if (["accessToken", "supervisorToken"].some((key) => {
7899
+ const v = values[key];
7900
+ return typeof v === "string" && v.length > 0 && v !== "__camstack_redacted__";
7901
+ })) {
7902
+ const r = await deps.probe(values);
7903
+ return r.ok ? {
7904
+ success: true,
7905
+ status: "validated",
7906
+ ...r.latencyMs !== void 0 ? { latencyMs: r.latencyMs } : {}
7907
+ } : {
7908
+ success: false,
7909
+ status: "rejected",
7910
+ error: r.error ?? "Home Assistant rejected the connection"
7911
+ };
7912
+ }
7913
+ const info = id === null ? null : deps.infoOf(id);
7914
+ if (info === null) return {
7915
+ success: false,
7916
+ status: "inconclusive",
7917
+ error: "Not connected: save the connection first"
7918
+ };
7919
+ return info.status === "connected" ? {
7920
+ success: true,
7921
+ status: "validated"
7922
+ } : {
7923
+ success: false,
7924
+ status: "rejected",
7925
+ error: info.error ?? `status: ${info.status}`
7926
+ };
7927
+ }
7928
+ });
7929
+ }
7930
+ //#endregion
7804
7931
  //#region src/ha-board-key-source.ts
7805
7932
  /**
7806
7933
  * `board-key-source` for Home Assistant: reads an ESPHome board's API
@@ -7974,6 +8101,38 @@ function createHaBoardKeyProvider(source) {
7974
8101
  };
7975
8102
  }
7976
8103
  //#endregion
8104
+ //#region src/migrations/flatten-broker-records.ts
8105
+ /**
8106
+ * The Home Assistant broker records keep their connection flat (planning#408 P3): the nested
8107
+ * `auth` object (`{ kind, baseUrl, accessToken }` or `{ kind, supervisorToken }`) becomes the top
8108
+ * level `authKind`, `baseUrl`, `accessToken` and `supervisorToken`, under the SAME record id, so
8109
+ * the devices that carry `ha_NNN` keep their broker. The work is `flattenNestedRecords`: every
8110
+ * secret is read before anything is written, read back from its new keychain ref before the
8111
+ * nested object goes, and the old refs are deleted last.
8112
+ */
8113
+ var FLATTEN_BROKER_RECORDS_ID = "provider-homeassistant/2026-11-04-flatten-broker-records";
8114
+ function text(value) {
8115
+ return typeof value === "string" ? value : void 0;
8116
+ }
8117
+ async function flattenBrokerRecords(ports) {
8118
+ return require_ha_instance_settings.flattenNestedRecords(ports, {
8119
+ legacyKey: "auth",
8120
+ flatSecretKeys: ["accessToken", "supervisorToken"],
8121
+ toFlat: (legacy) => {
8122
+ const kind = legacy["kind"] === "supervisor" ? "supervisor" : "long-lived";
8123
+ const baseUrl = text(legacy["baseUrl"]);
8124
+ const accessToken = text(legacy["accessToken"]);
8125
+ const supervisorToken = text(legacy["supervisorToken"]);
8126
+ return {
8127
+ authKind: kind,
8128
+ ...baseUrl !== void 0 ? { baseUrl } : {},
8129
+ ...accessToken !== void 0 ? { accessToken } : {},
8130
+ ...supervisorToken !== void 0 ? { supervisorToken } : {}
8131
+ };
8132
+ }
8133
+ });
8134
+ }
8135
+ //#endregion
7977
8136
  //#region src/migrations/calendar-ids-to-integration.ts
7978
8137
  /**
7979
8138
  * The HA calendars an instance imports are a fact of THAT instance (D985): each Home Assistant
@@ -8063,37 +8222,56 @@ async function planDropSharedCalendarIds(ports) {
8063
8222
  //#endregion
8064
8223
  //#region src/migrations/index.ts
8065
8224
  /** The `provider-homeassistant` catalogue (D987): offered to the engine when the addon initialises. */
8066
- var ADDON_MIGRATIONS = [{
8067
- meta: {
8068
- id: "provider-homeassistant/2026-11-03-calendar-ids-to-integration",
8069
- owner: "provider-homeassistant",
8070
- level: "addon-state",
8071
- seq: 1,
8072
- dependsOn: [],
8073
- store: "settings",
8074
- scope: "cluster",
8075
- onFailure: "tolerate",
8076
- bodyKind: "procedure",
8077
- description: "Copy the shared calendar set to the settings of every Home Assistant integration that has none",
8078
- fixtureMark: "the broker link stored beside the calendars of an integration"
8225
+ var ADDON_MIGRATIONS = [
8226
+ {
8227
+ meta: {
8228
+ id: "provider-homeassistant/2026-11-03-calendar-ids-to-integration",
8229
+ owner: "provider-homeassistant",
8230
+ level: "addon-state",
8231
+ seq: 1,
8232
+ dependsOn: [],
8233
+ store: "settings",
8234
+ scope: "cluster",
8235
+ onFailure: "tolerate",
8236
+ bodyKind: "procedure",
8237
+ description: "Copy the shared calendar set to the settings of every Home Assistant integration that has none",
8238
+ fixtureMark: "the broker link stored beside the calendars of an integration"
8239
+ },
8240
+ procedure: copyCalendarIdsToIntegrations
8079
8241
  },
8080
- procedure: copyCalendarIdsToIntegrations
8081
- }, {
8082
- meta: {
8083
- id: "provider-homeassistant/2026-11-03-drop-shared-calendar-ids",
8084
- owner: "provider-homeassistant",
8085
- level: "addon-state",
8086
- seq: 2,
8087
- dependsOn: ["provider-homeassistant/2026-11-03-calendar-ids-to-integration"],
8088
- store: "settings",
8089
- scope: "cluster",
8090
- onFailure: "tolerate",
8091
- bodyKind: "plan",
8092
- description: "Delete the shared calendar set once every integration shows its own",
8093
- fixtureMark: "the shared set parked when an integration holds a different one"
8242
+ {
8243
+ meta: {
8244
+ id: "provider-homeassistant/2026-11-03-drop-shared-calendar-ids",
8245
+ owner: "provider-homeassistant",
8246
+ level: "addon-state",
8247
+ seq: 2,
8248
+ dependsOn: ["provider-homeassistant/2026-11-03-calendar-ids-to-integration"],
8249
+ store: "settings",
8250
+ scope: "cluster",
8251
+ onFailure: "tolerate",
8252
+ bodyKind: "plan",
8253
+ description: "Delete the shared calendar set once every integration shows its own",
8254
+ fixtureMark: "the shared set parked when an integration holds a different one"
8255
+ },
8256
+ plan: planDropSharedCalendarIds
8094
8257
  },
8095
- plan: planDropSharedCalendarIds
8096
- }];
8258
+ {
8259
+ meta: {
8260
+ id: FLATTEN_BROKER_RECORDS_ID,
8261
+ owner: "provider-homeassistant",
8262
+ level: "addon-state",
8263
+ seq: 3,
8264
+ dependsOn: [],
8265
+ store: "settings",
8266
+ scope: "cluster",
8267
+ onFailure: "block-owner",
8268
+ bodyKind: "procedure",
8269
+ description: "Flatten the nested auth of every Home Assistant broker record under its own id, moving each token to a top-level keychain secret",
8270
+ fixtureMark: "a non-secret field stored beside the auth of a broker record"
8271
+ },
8272
+ procedure: flattenBrokerRecords
8273
+ }
8274
+ ];
8097
8275
  //#endregion
8098
8276
  //#region src/ha-calendar-source.ts
8099
8277
  function isCalendarEntity(entityId) {
@@ -8707,10 +8885,7 @@ function planStandaloneReconcile(devices, freshById, brokerId) {
8707
8885
  var ADOPTION_FILTER_DEVICES = "devices";
8708
8886
  var ADOPTION_FILTER_ENTITIES = "entities";
8709
8887
  var HA_KIND = "home-assistant";
8710
- var DEFAULT_CONFIG = {
8711
- brokers: [],
8712
- calendarIds: []
8713
- };
8888
+ var DEFAULT_CONFIG = { calendarIds: [] };
8714
8889
  /**
8715
8890
  * Home Assistant provider addon.
8716
8891
  *
@@ -8757,6 +8932,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
8757
8932
  }
8758
8933
  registry = null;
8759
8934
  brokerStore = null;
8935
+ instances = null;
8760
8936
  /** Runtime broker rows (records when B2 is live, else settings). */
8761
8937
  activeBrokers = [];
8762
8938
  /** Periodic re-sync sweep handle (cleared on shutdown). */
@@ -8771,11 +8947,26 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
8771
8947
  constructor() {
8772
8948
  super({ ...DEFAULT_CONFIG });
8773
8949
  }
8950
+ flattenPorts(records) {
8951
+ if (this.ctx.secrets === void 0) throw new Error("provider-homeassistant: ctx.secrets is required");
8952
+ return {
8953
+ readStored: (key) => this.readStoredGlobalKey(key),
8954
+ ...require_ha_instance_settings.haInstanceSettingsPortsOver(this.ctx.api.integrations),
8955
+ ...require_ha_instance_settings.flattenPortsOver(records.kind(haBrokersEntryKind))
8956
+ };
8957
+ }
8774
8958
  async onInitialize() {
8775
8959
  const records = this.ctx.records;
8776
8960
  if (records === void 0) throw new Error("provider-homeassistant: ctx.records is required (hub-only addon)");
8777
8961
  await records.declare(haAllRecordKinds);
8778
8962
  this.brokerStore = HaBrokerStore.of(records);
8963
+ this.instances = buildHaInstances(records, {
8964
+ mintId: () => this.requireRegistry().allocateId(),
8965
+ infoOf: (id) => this.registry?.get(id) ?? null,
8966
+ onChanged: (id) => this.syncBrokerRuntime(id),
8967
+ probe: (settings) => this.requireRegistry().testSettings(settings)
8968
+ });
8969
+ await this.runMigrations(require_ha_instance_settings.defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id === FLATTEN_BROKER_RECORDS_ID), this.flattenPorts(records)));
8779
8970
  this.activeBrokers = await this.loadPersistedBrokers();
8780
8971
  this.registry = new HaBrokerRegistry(this.ctx.logger, this.ctx.eventBus);
8781
8972
  this.registry.setOnBrokerConnected((brokerId) => {
@@ -8792,10 +8983,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
8792
8983
  this.reconcileAllConnectedBrokers();
8793
8984
  }, HaProviderAddon.RESYNC_INTERVAL_MS);
8794
8985
  this.ctx.logger.info("ha provider initialised", { meta: { brokerCount: this.activeBrokers.length } });
8795
- await this.runMigrations(require_ha_instance_settings.defineAddonMigrations(ADDON_MIGRATIONS, {
8796
- readStored: (key) => this.readStoredGlobalKey(key),
8797
- ...require_ha_instance_settings.haInstanceSettingsPortsOver(this.ctx.api.integrations)
8798
- }));
8986
+ await this.runMigrations(require_ha_instance_settings.defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id !== FLATTEN_BROKER_RECORDS_ID), this.flattenPorts(records)));
8799
8987
  await this.reconcileIntegrationsToBrokers();
8800
8988
  this.subscribeIntegrationLifecycle();
8801
8989
  return [
@@ -8803,6 +8991,10 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
8803
8991
  capability: require_ha_instance_settings.brokerCapability,
8804
8992
  provider: this.buildBrokerProvider()
8805
8993
  },
8994
+ {
8995
+ capability: require_ha_instance_settings.integrationInstancesCapability,
8996
+ provider: this.requireInstances().toProvider()
8997
+ },
8806
8998
  {
8807
8999
  capability: require_ha_instance_settings.deviceAdoptionCapability,
8808
9000
  provider: this.buildAdoptionProvider()
@@ -9082,11 +9274,8 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9082
9274
  for (const entry of persisted) {
9083
9275
  if (entry.id && liveIds.has(entry.id)) {
9084
9276
  try {
9085
- const settings = entry.auth.kind === "supervisor" ? { supervisorToken: entry.auth.supervisorToken } : {
9086
- baseUrl: entry.auth.baseUrl,
9087
- accessToken: entry.auth.accessToken
9088
- };
9089
- await reg.updateEntry(entry.id, settings);
9277
+ const settings = entryToSettings(entry);
9278
+ if (settings !== null) await reg.updateEntry(entry.id, settings);
9090
9279
  } catch (err) {
9091
9280
  this.ctx.logger.warn("ha onConfigChanged: updateEntry failed", { meta: {
9092
9281
  brokerId: entry.id,
@@ -9096,10 +9285,8 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9096
9285
  continue;
9097
9286
  }
9098
9287
  try {
9099
- const settings = entry.auth.kind === "supervisor" ? { supervisorToken: entry.auth.supervisorToken } : {
9100
- baseUrl: entry.auth.baseUrl,
9101
- accessToken: entry.auth.accessToken
9102
- };
9288
+ const settings = entryToSettings(entry);
9289
+ if (settings === null) continue;
9103
9290
  const created = await reg.createEntry(entry.name, settings);
9104
9291
  if (created.id !== entry.id) {
9105
9292
  const next = persisted.map((b) => b === entry ? {
@@ -9117,21 +9304,59 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9117
9304
  }
9118
9305
  }
9119
9306
  async loadPersistedBrokers() {
9120
- const store = this.brokerStore;
9121
- if (store !== null) {
9122
- const fromRecords = await store.listResolved();
9123
- if (fromRecords.length > 0) return fromRecords;
9307
+ return this.requireBrokerStore().listResolved();
9308
+ }
9309
+ requireInstances() {
9310
+ if (this.instances === null) throw new Error("provider-homeassistant: instances not ready");
9311
+ return this.instances;
9312
+ }
9313
+ /**
9314
+ * After an instance changed (created, edited, switched, deleted) the runtime follows the record:
9315
+ * a missing record removes the manager and its adopted devices, an off or unfilled one stops the
9316
+ * manager but keeps the devices, a filled one starts or re-authenticates it.
9317
+ */
9318
+ async syncBrokerRuntime(id) {
9319
+ const reg = this.requireRegistry();
9320
+ const entry = await this.requireBrokerStore().getById(id);
9321
+ this.activeBrokers = await this.loadPersistedBrokers();
9322
+ if (entry === null) {
9323
+ await reg.removeEntry(id);
9324
+ const deviceRegistry = this.ctx.kernel.deviceRegistry;
9325
+ const devices = this.ctx.kernel.devices;
9326
+ if (deviceRegistry && devices) await cascadeRemoveDevicesForBroker({
9327
+ reg: deviceRegistry,
9328
+ devices,
9329
+ addonId: this.ctx.id,
9330
+ brokerId: id,
9331
+ logger: this.ctx.logger
9332
+ });
9333
+ return;
9124
9334
  }
9125
- return this.config.brokers;
9335
+ const settings = entry.enabled === false ? null : entryToSettings(entry);
9336
+ if (settings === null) {
9337
+ await reg.removeEntry(id);
9338
+ return;
9339
+ }
9340
+ try {
9341
+ if (reg.get(id) !== null) await reg.updateEntry(id, settings);
9342
+ else await reg.createEntry(entry.name, settings, {
9343
+ id,
9344
+ ...entry.integrationId !== void 0 ? { integrationId: entry.integrationId } : {}
9345
+ });
9346
+ } catch (err) {
9347
+ this.ctx.logger.warn("ha instance: could not start the broker", { meta: {
9348
+ brokerId: id,
9349
+ error: require_ha_instance_settings.errMsg(err)
9350
+ } });
9351
+ }
9352
+ }
9353
+ requireBrokerStore() {
9354
+ if (this.brokerStore === null) throw new Error("provider-homeassistant: broker store not ready");
9355
+ return this.brokerStore;
9126
9356
  }
9127
9357
  async persistBrokerEntries(entries) {
9128
9358
  this.activeBrokers = [...entries];
9129
- const store = this.brokerStore;
9130
- if (store !== null) {
9131
- await store.replaceAll(entries);
9132
- return;
9133
- }
9134
- await this.updateGlobalSettings({ brokers: entries });
9359
+ await this.requireBrokerStore().replaceAll(entries);
9135
9360
  }
9136
9361
  buildBrokerProvider() {
9137
9362
  const ownerAddonId = this.ctx.id;
@@ -9205,7 +9430,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9205
9430
  getSettings: async ({ id }) => {
9206
9431
  const entry = this.activeBrokers.find((b) => b.id === id);
9207
9432
  if (!entry) return null;
9208
- return authToSettings(entry.auth);
9433
+ return entryToSettings(entry);
9209
9434
  },
9210
9435
  setSettings: async ({ id, settings }) => {
9211
9436
  if (!owns(id)) return;
@@ -9216,7 +9441,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9216
9441
  getBrokerConfig: async ({ id }) => {
9217
9442
  const entry = this.activeBrokers.find((b) => b.id === id);
9218
9443
  if (!entry) return null;
9219
- return authToSettings(entry.auth);
9444
+ return entryToSettings(entry);
9220
9445
  },
9221
9446
  getSettingsSchema: async ({ kind }) => {
9222
9447
  if (kind !== HA_KIND) return null;