@camstack/addon-provider-homeassistant 1.2.654 → 1.2.655

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,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.655] - 2026-11-04
11
+
12
+ ### Changed
13
+
14
+ - Exposing or hiding a device in Home Assistant export changes only the Home Assistant instance it names, not every instance
15
+ - Home Assistant connections are listed on the instances page; their stored connection is flattened once at start-up (same ids and tokens).
16
+ - Connections move to the flat body without stranding keychain refs: the old token refs are released when the nested object goes
17
+
10
18
  ## [1.2.654] - 2026-11-03
11
19
 
12
20
  ### 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-5Ms7yRju.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,97 @@ 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
+ instanceMode: "multiple",
7872
+ kind: "connection",
7873
+ canTest: true,
7874
+ kinds: [{
7875
+ id: HA_INSTANCE_KIND,
7876
+ label: "Home Assistant"
7877
+ }],
7878
+ allowRestoreId: true
7879
+ },
7880
+ formSchema: () => haBrokerSettingsSchema,
7881
+ initialBody: (name) => ({
7882
+ name,
7883
+ authKind: "long-lived"
7884
+ }),
7885
+ mintId: () => deps.mintId(),
7886
+ kindOf: () => HA_INSTANCE_KIND,
7887
+ health: (_body, id) => {
7888
+ const info = deps.infoOf(id);
7889
+ return {
7890
+ status: statusOf(info),
7891
+ ...info?.lastCheckedAt != null ? { lastCheckedAt: info.lastCheckedAt } : {}
7892
+ };
7893
+ },
7894
+ configured: isHaBodyConfigured,
7895
+ onChanged: deps.onChanged,
7896
+ test: async ({ id, values }) => {
7897
+ if (["accessToken", "supervisorToken"].some((key) => {
7898
+ const v = values[key];
7899
+ return typeof v === "string" && v.length > 0 && v !== "__camstack_redacted__";
7900
+ })) {
7901
+ const r = await deps.probe(values);
7902
+ return r.ok ? {
7903
+ success: true,
7904
+ status: "validated",
7905
+ ...r.latencyMs !== void 0 ? { latencyMs: r.latencyMs } : {}
7906
+ } : {
7907
+ success: false,
7908
+ status: "rejected",
7909
+ error: r.error ?? "Home Assistant rejected the connection"
7910
+ };
7911
+ }
7912
+ const info = id === null ? null : deps.infoOf(id);
7913
+ if (info === null) return {
7914
+ success: false,
7915
+ status: "inconclusive",
7916
+ error: "Not connected: save the connection first"
7917
+ };
7918
+ return info.status === "connected" ? {
7919
+ success: true,
7920
+ status: "validated"
7921
+ } : {
7922
+ success: false,
7923
+ status: "rejected",
7924
+ error: info.error ?? `status: ${info.status}`
7925
+ };
7926
+ }
7927
+ });
7928
+ }
7929
+ //#endregion
7804
7930
  //#region src/ha-board-key-source.ts
7805
7931
  /**
7806
7932
  * `board-key-source` for Home Assistant: reads an ESPHome board's API
@@ -7974,6 +8100,38 @@ function createHaBoardKeyProvider(source) {
7974
8100
  };
7975
8101
  }
7976
8102
  //#endregion
8103
+ //#region src/migrations/flatten-broker-records.ts
8104
+ /**
8105
+ * The Home Assistant broker records keep their connection flat (planning#408 P3): the nested
8106
+ * `auth` object (`{ kind, baseUrl, accessToken }` or `{ kind, supervisorToken }`) becomes the top
8107
+ * level `authKind`, `baseUrl`, `accessToken` and `supervisorToken`, under the SAME record id, so
8108
+ * the devices that carry `ha_NNN` keep their broker. The work is `flattenNestedRecords`: every
8109
+ * secret is read before anything is written, read back from its new keychain ref before the
8110
+ * nested object goes, and the old refs are deleted last.
8111
+ */
8112
+ var FLATTEN_BROKER_RECORDS_ID = "provider-homeassistant/2026-11-04-flatten-broker-records";
8113
+ function text(value) {
8114
+ return typeof value === "string" ? value : void 0;
8115
+ }
8116
+ async function flattenBrokerRecords(ports) {
8117
+ return require_ha_instance_settings.flattenNestedRecords(ports, {
8118
+ legacyKey: "auth",
8119
+ flatSecretKeys: ["accessToken", "supervisorToken"],
8120
+ toFlat: (legacy) => {
8121
+ const kind = legacy["kind"] === "supervisor" ? "supervisor" : "long-lived";
8122
+ const baseUrl = text(legacy["baseUrl"]);
8123
+ const accessToken = text(legacy["accessToken"]);
8124
+ const supervisorToken = text(legacy["supervisorToken"]);
8125
+ return {
8126
+ authKind: kind,
8127
+ ...baseUrl !== void 0 ? { baseUrl } : {},
8128
+ ...accessToken !== void 0 ? { accessToken } : {},
8129
+ ...supervisorToken !== void 0 ? { supervisorToken } : {}
8130
+ };
8131
+ }
8132
+ });
8133
+ }
8134
+ //#endregion
7977
8135
  //#region src/migrations/calendar-ids-to-integration.ts
7978
8136
  /**
7979
8137
  * The HA calendars an instance imports are a fact of THAT instance (D985): each Home Assistant
@@ -8063,37 +8221,56 @@ async function planDropSharedCalendarIds(ports) {
8063
8221
  //#endregion
8064
8222
  //#region src/migrations/index.ts
8065
8223
  /** 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"
8224
+ var ADDON_MIGRATIONS = [
8225
+ {
8226
+ meta: {
8227
+ id: "provider-homeassistant/2026-11-03-calendar-ids-to-integration",
8228
+ owner: "provider-homeassistant",
8229
+ level: "addon-state",
8230
+ seq: 1,
8231
+ dependsOn: [],
8232
+ store: "settings",
8233
+ scope: "cluster",
8234
+ onFailure: "tolerate",
8235
+ bodyKind: "procedure",
8236
+ description: "Copy the shared calendar set to the settings of every Home Assistant integration that has none",
8237
+ fixtureMark: "the broker link stored beside the calendars of an integration"
8238
+ },
8239
+ procedure: copyCalendarIdsToIntegrations
8079
8240
  },
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"
8241
+ {
8242
+ meta: {
8243
+ id: "provider-homeassistant/2026-11-03-drop-shared-calendar-ids",
8244
+ owner: "provider-homeassistant",
8245
+ level: "addon-state",
8246
+ seq: 2,
8247
+ dependsOn: ["provider-homeassistant/2026-11-03-calendar-ids-to-integration"],
8248
+ store: "settings",
8249
+ scope: "cluster",
8250
+ onFailure: "tolerate",
8251
+ bodyKind: "plan",
8252
+ description: "Delete the shared calendar set once every integration shows its own",
8253
+ fixtureMark: "the shared set parked when an integration holds a different one"
8254
+ },
8255
+ plan: planDropSharedCalendarIds
8094
8256
  },
8095
- plan: planDropSharedCalendarIds
8096
- }];
8257
+ {
8258
+ meta: {
8259
+ id: FLATTEN_BROKER_RECORDS_ID,
8260
+ owner: "provider-homeassistant",
8261
+ level: "addon-state",
8262
+ seq: 3,
8263
+ dependsOn: [],
8264
+ store: "settings",
8265
+ scope: "cluster",
8266
+ onFailure: "block-owner",
8267
+ bodyKind: "procedure",
8268
+ description: "Flatten the nested auth of every Home Assistant broker record under its own id, moving each token to a top-level keychain secret",
8269
+ fixtureMark: "a non-secret field stored beside the auth of a broker record"
8270
+ },
8271
+ procedure: flattenBrokerRecords
8272
+ }
8273
+ ];
8097
8274
  //#endregion
8098
8275
  //#region src/ha-calendar-source.ts
8099
8276
  function isCalendarEntity(entityId) {
@@ -8707,10 +8884,7 @@ function planStandaloneReconcile(devices, freshById, brokerId) {
8707
8884
  var ADOPTION_FILTER_DEVICES = "devices";
8708
8885
  var ADOPTION_FILTER_ENTITIES = "entities";
8709
8886
  var HA_KIND = "home-assistant";
8710
- var DEFAULT_CONFIG = {
8711
- brokers: [],
8712
- calendarIds: []
8713
- };
8887
+ var DEFAULT_CONFIG = { calendarIds: [] };
8714
8888
  /**
8715
8889
  * Home Assistant provider addon.
8716
8890
  *
@@ -8757,6 +8931,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
8757
8931
  }
8758
8932
  registry = null;
8759
8933
  brokerStore = null;
8934
+ instances = null;
8760
8935
  /** Runtime broker rows (records when B2 is live, else settings). */
8761
8936
  activeBrokers = [];
8762
8937
  /** Periodic re-sync sweep handle (cleared on shutdown). */
@@ -8771,11 +8946,26 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
8771
8946
  constructor() {
8772
8947
  super({ ...DEFAULT_CONFIG });
8773
8948
  }
8949
+ flattenPorts(records) {
8950
+ if (this.ctx.secrets === void 0) throw new Error("provider-homeassistant: ctx.secrets is required");
8951
+ return {
8952
+ readStored: (key) => this.readStoredGlobalKey(key),
8953
+ ...require_ha_instance_settings.haInstanceSettingsPortsOver(this.ctx.api.integrations),
8954
+ ...require_ha_instance_settings.flattenPortsOver(records.kind(haBrokersEntryKind))
8955
+ };
8956
+ }
8774
8957
  async onInitialize() {
8775
8958
  const records = this.ctx.records;
8776
8959
  if (records === void 0) throw new Error("provider-homeassistant: ctx.records is required (hub-only addon)");
8777
8960
  await records.declare(haAllRecordKinds);
8778
8961
  this.brokerStore = HaBrokerStore.of(records);
8962
+ this.instances = buildHaInstances(records, {
8963
+ mintId: () => this.requireRegistry().allocateId(),
8964
+ infoOf: (id) => this.registry?.get(id) ?? null,
8965
+ onChanged: (id) => this.syncBrokerRuntime(id),
8966
+ probe: (settings) => this.requireRegistry().testSettings(settings)
8967
+ });
8968
+ await this.runMigrations(require_ha_instance_settings.defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id === FLATTEN_BROKER_RECORDS_ID), this.flattenPorts(records)));
8779
8969
  this.activeBrokers = await this.loadPersistedBrokers();
8780
8970
  this.registry = new HaBrokerRegistry(this.ctx.logger, this.ctx.eventBus);
8781
8971
  this.registry.setOnBrokerConnected((brokerId) => {
@@ -8792,10 +8982,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
8792
8982
  this.reconcileAllConnectedBrokers();
8793
8983
  }, HaProviderAddon.RESYNC_INTERVAL_MS);
8794
8984
  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
- }));
8985
+ await this.runMigrations(require_ha_instance_settings.defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id !== FLATTEN_BROKER_RECORDS_ID), this.flattenPorts(records)));
8799
8986
  await this.reconcileIntegrationsToBrokers();
8800
8987
  this.subscribeIntegrationLifecycle();
8801
8988
  return [
@@ -8803,6 +8990,10 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
8803
8990
  capability: require_ha_instance_settings.brokerCapability,
8804
8991
  provider: this.buildBrokerProvider()
8805
8992
  },
8993
+ {
8994
+ capability: require_ha_instance_settings.integrationInstancesCapability,
8995
+ provider: this.requireInstances().toProvider()
8996
+ },
8806
8997
  {
8807
8998
  capability: require_ha_instance_settings.deviceAdoptionCapability,
8808
8999
  provider: this.buildAdoptionProvider()
@@ -9082,11 +9273,8 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9082
9273
  for (const entry of persisted) {
9083
9274
  if (entry.id && liveIds.has(entry.id)) {
9084
9275
  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);
9276
+ const settings = entryToSettings(entry);
9277
+ if (settings !== null) await reg.updateEntry(entry.id, settings);
9090
9278
  } catch (err) {
9091
9279
  this.ctx.logger.warn("ha onConfigChanged: updateEntry failed", { meta: {
9092
9280
  brokerId: entry.id,
@@ -9096,10 +9284,8 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9096
9284
  continue;
9097
9285
  }
9098
9286
  try {
9099
- const settings = entry.auth.kind === "supervisor" ? { supervisorToken: entry.auth.supervisorToken } : {
9100
- baseUrl: entry.auth.baseUrl,
9101
- accessToken: entry.auth.accessToken
9102
- };
9287
+ const settings = entryToSettings(entry);
9288
+ if (settings === null) continue;
9103
9289
  const created = await reg.createEntry(entry.name, settings);
9104
9290
  if (created.id !== entry.id) {
9105
9291
  const next = persisted.map((b) => b === entry ? {
@@ -9117,21 +9303,59 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9117
9303
  }
9118
9304
  }
9119
9305
  async loadPersistedBrokers() {
9120
- const store = this.brokerStore;
9121
- if (store !== null) {
9122
- const fromRecords = await store.listResolved();
9123
- if (fromRecords.length > 0) return fromRecords;
9306
+ return this.requireBrokerStore().listResolved();
9307
+ }
9308
+ requireInstances() {
9309
+ if (this.instances === null) throw new Error("provider-homeassistant: instances not ready");
9310
+ return this.instances;
9311
+ }
9312
+ /**
9313
+ * After an instance changed (created, edited, switched, deleted) the runtime follows the record:
9314
+ * a missing record removes the manager and its adopted devices, an off or unfilled one stops the
9315
+ * manager but keeps the devices, a filled one starts or re-authenticates it.
9316
+ */
9317
+ async syncBrokerRuntime(id) {
9318
+ const reg = this.requireRegistry();
9319
+ const entry = await this.requireBrokerStore().getById(id);
9320
+ this.activeBrokers = await this.loadPersistedBrokers();
9321
+ if (entry === null) {
9322
+ await reg.removeEntry(id);
9323
+ const deviceRegistry = this.ctx.kernel.deviceRegistry;
9324
+ const devices = this.ctx.kernel.devices;
9325
+ if (deviceRegistry && devices) await cascadeRemoveDevicesForBroker({
9326
+ reg: deviceRegistry,
9327
+ devices,
9328
+ addonId: this.ctx.id,
9329
+ brokerId: id,
9330
+ logger: this.ctx.logger
9331
+ });
9332
+ return;
9124
9333
  }
9125
- return this.config.brokers;
9334
+ const settings = entry.enabled === false ? null : entryToSettings(entry);
9335
+ if (settings === null) {
9336
+ await reg.removeEntry(id);
9337
+ return;
9338
+ }
9339
+ try {
9340
+ if (reg.get(id) !== null) await reg.updateEntry(id, settings);
9341
+ else await reg.createEntry(entry.name, settings, {
9342
+ id,
9343
+ ...entry.integrationId !== void 0 ? { integrationId: entry.integrationId } : {}
9344
+ });
9345
+ } catch (err) {
9346
+ this.ctx.logger.warn("ha instance: could not start the broker", { meta: {
9347
+ brokerId: id,
9348
+ error: require_ha_instance_settings.errMsg(err)
9349
+ } });
9350
+ }
9351
+ }
9352
+ requireBrokerStore() {
9353
+ if (this.brokerStore === null) throw new Error("provider-homeassistant: broker store not ready");
9354
+ return this.brokerStore;
9126
9355
  }
9127
9356
  async persistBrokerEntries(entries) {
9128
9357
  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 });
9358
+ await this.requireBrokerStore().replaceAll(entries);
9135
9359
  }
9136
9360
  buildBrokerProvider() {
9137
9361
  const ownerAddonId = this.ctx.id;
@@ -9205,7 +9429,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9205
9429
  getSettings: async ({ id }) => {
9206
9430
  const entry = this.activeBrokers.find((b) => b.id === id);
9207
9431
  if (!entry) return null;
9208
- return authToSettings(entry.auth);
9432
+ return entryToSettings(entry);
9209
9433
  },
9210
9434
  setSettings: async ({ id, settings }) => {
9211
9435
  if (!owns(id)) return;
@@ -9216,7 +9440,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings
9216
9440
  getBrokerConfig: async ({ id }) => {
9217
9441
  const entry = this.activeBrokers.find((b) => b.id === id);
9218
9442
  if (!entry) return null;
9219
- return authToSettings(entry.auth);
9443
+ return entryToSettings(entry);
9220
9444
  },
9221
9445
  getSettingsSchema: async ({ kind }) => {
9222
9446
  if (kind !== HA_KIND) return null;