@riddix/hamh 2.1.0-alpha.829 → 2.1.0-alpha.831

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.
@@ -131529,6 +131529,12 @@ var init_bridge_config_schema = __esm({
131529
131529
  description: "When a controller drops all subscriptions, clean up the dead session and re-announce after 5 seconds instead of 60. Opt-in for Google Home users whose devices go offline after a cancelled subscription (#386). It shortens the offline window but cannot stop the controller from rejecting the subscription. Default off.",
131530
131530
  type: "boolean",
131531
131531
  default: false
131532
+ },
131533
+ stableIdentity: {
131534
+ title: "Stable Device Identity",
131535
+ description: "Anchor each device to its Home Assistant entity registry id instead of the entity id, so renaming an entity no longer re-adds it in your controller (Alexa, Google Home, Apple Home) and keeps groups and automations. Records are seeded from the start, so enabling this later is safe and never re-adds existing devices. Default off.",
131536
+ type: "boolean",
131537
+ default: false
131532
131538
  }
131533
131539
  }
131534
131540
  };
@@ -132293,6 +132299,12 @@ var init_home_assistant_entity_behavior = __esm({
132293
132299
  customName;
132294
132300
  /** Entity mapping configuration (optional, used for advanced features like filter life sensor) */
132295
132301
  mapping;
132302
+ /**
132303
+ * Stable identity anchor for uniqueId/serialNumber. Set to the entity_id the
132304
+ * identity was first seeded under, so those stay frozen across HA renames.
132305
+ * Undefined falls back to the live entity_id (legacy behaviour).
132306
+ */
132307
+ identityAnchor;
132296
132308
  }
132297
132309
  HomeAssistantEntityBehavior2.State = State;
132298
132310
  class Events2 extends EventEmitter {
@@ -133576,7 +133588,7 @@ function diagnosticApi(bridgeService, haClient, haRegistry, version2, startTime)
133576
133588
 
133577
133589
  // src/api/entity-mapping-api.ts
133578
133590
  import express7 from "express";
133579
- function entityMappingApi(mappingStorage) {
133591
+ function entityMappingApi(mappingStorage, identityStorage) {
133580
133592
  const router = express7.Router();
133581
133593
  router.get("/:bridgeId", (req, res) => {
133582
133594
  const { bridgeId } = req.params;
@@ -133657,6 +133669,7 @@ function entityMappingApi(mappingStorage) {
133657
133669
  router.delete("/:bridgeId", async (req, res) => {
133658
133670
  const { bridgeId } = req.params;
133659
133671
  await mappingStorage.deleteBridgeMappings(bridgeId);
133672
+ await identityStorage.deleteBridgeIdentities(bridgeId);
133660
133673
  res.status(204).send();
133661
133674
  });
133662
133675
  return router;
@@ -134523,7 +134536,7 @@ function endpointToJson(endpoint, parentId) {
134523
134536
 
134524
134537
  // src/api/matter-api.ts
134525
134538
  var ajv = new Ajv();
134526
- function matterApi(bridgeService, haRegistry) {
134539
+ function matterApi(bridgeService, haRegistry, identityStorage) {
134527
134540
  const router = express11.Router();
134528
134541
  router.get("/", (_, res) => {
134529
134542
  res.status(200).json({});
@@ -134600,6 +134613,7 @@ function matterApi(bridgeService, haRegistry) {
134600
134613
  const bridgeId = req.params.bridgeId;
134601
134614
  try {
134602
134615
  await bridgeService.delete(bridgeId);
134616
+ await identityStorage?.deleteBridgeIdentities(bridgeId);
134603
134617
  res.status(204).send();
134604
134618
  } catch (e) {
134605
134619
  res.status(500).json({
@@ -134615,6 +134629,7 @@ function matterApi(bridgeService, haRegistry) {
134615
134629
  return;
134616
134630
  }
134617
134631
  try {
134632
+ await identityStorage?.deleteBridgeIdentities(bridgeId);
134618
134633
  await bridge.factoryReset();
134619
134634
  await bridge.start();
134620
134635
  res.status(200).json(bridge.data);
@@ -136597,13 +136612,14 @@ var WebSocketApi = class {
136597
136612
 
136598
136613
  // src/api/web-api.ts
136599
136614
  var WebApi = class extends Service {
136600
- constructor(logger245, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, lockCredentialStorage, settingsStorage, backupService, props) {
136615
+ constructor(logger245, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, identityStorage, lockCredentialStorage, settingsStorage, backupService, props) {
136601
136616
  super("WebApi");
136602
136617
  this.bridgeService = bridgeService;
136603
136618
  this.haClient = haClient;
136604
136619
  this.haRegistry = haRegistry;
136605
136620
  this.bridgeStorage = bridgeStorage;
136606
136621
  this.mappingStorage = mappingStorage;
136622
+ this.identityStorage = identityStorage;
136607
136623
  this.lockCredentialStorage = lockCredentialStorage;
136608
136624
  this.settingsStorage = settingsStorage;
136609
136625
  this.backupService = backupService;
@@ -136623,6 +136639,7 @@ var WebApi = class extends Service {
136623
136639
  haRegistry;
136624
136640
  bridgeStorage;
136625
136641
  mappingStorage;
136642
+ identityStorage;
136626
136643
  lockCredentialStorage;
136627
136644
  settingsStorage;
136628
136645
  backupService;
@@ -136639,7 +136656,10 @@ var WebApi = class extends Service {
136639
136656
  }
136640
136657
  async initialize() {
136641
136658
  const api = express17.Router();
136642
- api.use(express17.json()).use(nocache()).use("/matter", matterApi(this.bridgeService, this.haRegistry)).use(
136659
+ api.use(express17.json()).use(nocache()).use(
136660
+ "/matter",
136661
+ matterApi(this.bridgeService, this.haRegistry, this.identityStorage)
136662
+ ).use(
136643
136663
  "/health",
136644
136664
  healthApi(
136645
136665
  this.bridgeService,
@@ -136650,7 +136670,10 @@ var WebApi = class extends Service {
136650
136670
  ).use("/bridges", bridgeExportApi(this.bridgeStorage)).use("/bridge-icons", bridgeIconApi(this.props.storageLocation)).use(
136651
136671
  "/device-images",
136652
136672
  deviceImageApi(this.props.storageLocation, this.haRegistry)
136653
- ).use("/entity-mappings", entityMappingApi(this.mappingStorage)).use("/mapping-profiles", mappingProfileApi(this.mappingStorage)).use("/lock-credentials", lockCredentialApi(this.lockCredentialStorage)).use(
136673
+ ).use(
136674
+ "/entity-mappings",
136675
+ entityMappingApi(this.mappingStorage, this.identityStorage)
136676
+ ).use("/mapping-profiles", mappingProfileApi(this.mappingStorage)).use("/lock-credentials", lockCredentialApi(this.lockCredentialStorage)).use(
136654
136677
  "/settings",
136655
136678
  settingsApi(this.settingsStorage, this.bridgeService, this.props.auth)
136656
136679
  ).use(
@@ -138355,9 +138378,112 @@ var BridgeStorage = class extends Service {
138355
138378
  }
138356
138379
  };
138357
138380
 
138358
- // src/services/storage/entity-mapping-storage.ts
138381
+ // src/services/storage/entity-identity-storage.ts
138359
138382
  init_service();
138360
138383
  var CURRENT_VERSION = 1;
138384
+ var PERSIST_DEBOUNCE_MS = 500;
138385
+ var EntityIdentityStorage = class extends Service {
138386
+ constructor(appStorage) {
138387
+ super("EntityIdentityStorage");
138388
+ this.appStorage = appStorage;
138389
+ }
138390
+ appStorage;
138391
+ storage;
138392
+ identities = /* @__PURE__ */ new Map();
138393
+ persistTimer = null;
138394
+ async initialize() {
138395
+ this.storage = this.appStorage.createContext("entity-identities");
138396
+ await this.load();
138397
+ }
138398
+ async dispose() {
138399
+ await this.flush();
138400
+ }
138401
+ async load() {
138402
+ const stored = await this.storage.get("data", {
138403
+ version: CURRENT_VERSION,
138404
+ identities: {}
138405
+ });
138406
+ if (!stored || Object.keys(stored).length === 0) {
138407
+ return;
138408
+ }
138409
+ const data = stored;
138410
+ if (data.version !== CURRENT_VERSION) {
138411
+ await this.migrate(data);
138412
+ return;
138413
+ }
138414
+ for (const [bridgeId, records] of Object.entries(data.identities)) {
138415
+ const bridgeMap = /* @__PURE__ */ new Map();
138416
+ for (const [key, record] of Object.entries(records)) {
138417
+ bridgeMap.set(key, record);
138418
+ }
138419
+ this.identities.set(bridgeId, bridgeMap);
138420
+ }
138421
+ }
138422
+ async migrate(data) {
138423
+ if (data.version < CURRENT_VERSION) {
138424
+ for (const [bridgeId, records] of Object.entries(data.identities)) {
138425
+ const bridgeMap = /* @__PURE__ */ new Map();
138426
+ for (const [key, record] of Object.entries(records)) {
138427
+ bridgeMap.set(key, record);
138428
+ }
138429
+ this.identities.set(bridgeId, bridgeMap);
138430
+ }
138431
+ await this.persist();
138432
+ }
138433
+ }
138434
+ async persist() {
138435
+ const data = {
138436
+ version: CURRENT_VERSION,
138437
+ identities: {}
138438
+ };
138439
+ for (const [bridgeId, bridgeMap] of this.identities) {
138440
+ const records = {};
138441
+ for (const [key, record] of bridgeMap) {
138442
+ records[key] = record;
138443
+ }
138444
+ data.identities[bridgeId] = records;
138445
+ }
138446
+ await this.storage.set("data", data);
138447
+ }
138448
+ schedulePersist() {
138449
+ if (this.persistTimer) return;
138450
+ this.persistTimer = setTimeout(() => {
138451
+ this.persistTimer = null;
138452
+ void this.persist();
138453
+ }, PERSIST_DEBOUNCE_MS);
138454
+ }
138455
+ // Flush any pending debounced write, used on dispose and by tests.
138456
+ async flush() {
138457
+ if (this.persistTimer) {
138458
+ clearTimeout(this.persistTimer);
138459
+ this.persistTimer = null;
138460
+ }
138461
+ await this.persist();
138462
+ }
138463
+ getIdentity(bridgeId, key) {
138464
+ return this.identities.get(bridgeId)?.get(key);
138465
+ }
138466
+ getBridgeIdentities(bridgeId) {
138467
+ return this.identities.get(bridgeId) ?? /* @__PURE__ */ new Map();
138468
+ }
138469
+ setIdentity(bridgeId, key, record) {
138470
+ let bridgeMap = this.identities.get(bridgeId);
138471
+ if (!bridgeMap) {
138472
+ bridgeMap = /* @__PURE__ */ new Map();
138473
+ this.identities.set(bridgeId, bridgeMap);
138474
+ }
138475
+ bridgeMap.set(key, record);
138476
+ this.schedulePersist();
138477
+ }
138478
+ async deleteBridgeIdentities(bridgeId) {
138479
+ this.identities.delete(bridgeId);
138480
+ await this.flush();
138481
+ }
138482
+ };
138483
+
138484
+ // src/services/storage/entity-mapping-storage.ts
138485
+ init_service();
138486
+ var CURRENT_VERSION2 = 1;
138361
138487
  var EntityMappingStorage = class extends Service {
138362
138488
  constructor(appStorage) {
138363
138489
  super("EntityMappingStorage");
@@ -138372,14 +138498,14 @@ var EntityMappingStorage = class extends Service {
138372
138498
  }
138373
138499
  async load() {
138374
138500
  const stored = await this.storage.get("data", {
138375
- version: CURRENT_VERSION,
138501
+ version: CURRENT_VERSION2,
138376
138502
  mappings: {}
138377
138503
  });
138378
138504
  if (!stored || Object.keys(stored).length === 0) {
138379
138505
  return;
138380
138506
  }
138381
138507
  const data = stored;
138382
- if (data.version !== CURRENT_VERSION) {
138508
+ if (data.version !== CURRENT_VERSION2) {
138383
138509
  await this.migrate(data);
138384
138510
  return;
138385
138511
  }
@@ -138392,7 +138518,7 @@ var EntityMappingStorage = class extends Service {
138392
138518
  }
138393
138519
  }
138394
138520
  async migrate(data) {
138395
- if (data.version < CURRENT_VERSION) {
138521
+ if (data.version < CURRENT_VERSION2) {
138396
138522
  for (const [bridgeId, configs] of Object.entries(data.mappings)) {
138397
138523
  const bridgeMap = /* @__PURE__ */ new Map();
138398
138524
  for (const config11 of configs) {
@@ -138405,7 +138531,7 @@ var EntityMappingStorage = class extends Service {
138405
138531
  }
138406
138532
  async persist() {
138407
138533
  const data = {
138408
- version: CURRENT_VERSION,
138534
+ version: CURRENT_VERSION2,
138409
138535
  mappings: {}
138410
138536
  };
138411
138537
  for (const [bridgeId, bridgeMap] of this.mappings) {
@@ -138565,7 +138691,7 @@ function sanitizeThrottleMs(value) {
138565
138691
  // src/services/storage/lock-credential-storage.ts
138566
138692
  init_service();
138567
138693
  import { pbkdf2Sync, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
138568
- var CURRENT_VERSION2 = 2;
138694
+ var CURRENT_VERSION3 = 2;
138569
138695
  var HASH_ITERATIONS = 1e5;
138570
138696
  var HASH_KEY_LENGTH = 64;
138571
138697
  var HASH_ALGORITHM = "sha512";
@@ -138584,14 +138710,14 @@ var LockCredentialStorage = class extends Service {
138584
138710
  }
138585
138711
  async load() {
138586
138712
  const stored = await this.storage.get("data", {
138587
- version: CURRENT_VERSION2,
138713
+ version: CURRENT_VERSION3,
138588
138714
  credentials: []
138589
138715
  });
138590
138716
  if (!stored || Object.keys(stored).length === 0) {
138591
138717
  return;
138592
138718
  }
138593
138719
  const data = stored;
138594
- if (data.version !== CURRENT_VERSION2) {
138720
+ if (data.version !== CURRENT_VERSION3) {
138595
138721
  await this.migrate(data);
138596
138722
  return;
138597
138723
  }
@@ -138620,7 +138746,7 @@ var LockCredentialStorage = class extends Service {
138620
138746
  }
138621
138747
  async persist() {
138622
138748
  const data = {
138623
- version: CURRENT_VERSION2,
138749
+ version: CURRENT_VERSION3,
138624
138750
  credentials: Array.from(this.credentials.values())
138625
138751
  };
138626
138752
  await this.storage.set("data", data);
@@ -156975,8 +157101,8 @@ var AggregatorEndpoint2 = class extends Endpoint {
156975
157101
  // src/matter/endpoints/entity-endpoint.ts
156976
157102
  init_esm7();
156977
157103
  var EntityEndpoint = class extends Endpoint {
156978
- constructor(type, entityId, customName, mappedEntityIds) {
156979
- super(type, { id: createEndpointId(entityId, customName) });
157104
+ constructor(type, entityId, customName, mappedEntityIds, endpointId) {
157105
+ super(type, { id: endpointId ?? createEndpointId(entityId, customName) });
156980
157106
  this.entityId = entityId;
156981
157107
  this.mappedEntityIds = mappedEntityIds ?? [];
156982
157108
  }
@@ -157262,13 +157388,14 @@ var BasicInformationServer2 = class extends BridgedDeviceBasicInformationServer
157262
157388
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
157263
157389
  const device = entity.deviceRegistry;
157264
157390
  const mapping = homeAssistant.state.mapping;
157391
+ const anchor = homeAssistant.state.identityAnchor ?? entity.entity_id;
157265
157392
  const registryName = featureFlags?.preferEntityRegistryName ? entity.registry?.name ?? entity.registry?.original_name : void 0;
157266
157393
  const nodeLabel = ellipse(32, homeAssistant.state.customName) ?? ellipse(32, registryName) ?? ellipse(32, entity.state?.attributes?.friendly_name) ?? ellipse(32, entity.entity_id);
157267
157394
  const productNameFromNodeLabel = featureFlags?.productNameFromNodeLabel === true ? ellipse(32, sanitizeMatterString(nodeLabel ?? "")) ?? void 0 : void 0;
157268
157395
  const serialNumberSuffix = this.env.get(BridgeDataProvider).serialNumberSuffix;
157269
157396
  const maxRawLen = 32 - (serialNumberSuffix?.length ?? 0);
157270
157397
  const registrySerial = featureFlags?.useHaRegistrySerial ? ellipse(maxRawLen, device?.serial_number) : void 0;
157271
- const rawSerial = ellipse(maxRawLen, mapping?.customSerialNumber) ?? registrySerial ?? hash(maxRawLen, entity.entity_id);
157398
+ const rawSerial = ellipse(maxRawLen, mapping?.customSerialNumber) ?? registrySerial ?? hash(maxRawLen, anchor);
157272
157399
  const serialNumber = rawSerial && serialNumberSuffix ? `${rawSerial}${serialNumberSuffix}` : rawSerial;
157273
157400
  const customVendorId = mapping?.customVendorId;
157274
157401
  const vendorId3 = isValidVendorId(customVendorId) ? customVendorId : basicInformation.vendorId;
@@ -157288,18 +157415,18 @@ var BasicInformationServer2 = class extends BridgedDeviceBasicInformationServer
157288
157415
  // multiple fabric connections. Using MD5 hash of entity_id for stability.
157289
157416
  // uniqueIdSuffix mints a fresh identity so stale controller cloud
157290
157417
  // records keyed on it can be shed (#385).
157291
- uniqueId: this.frozenUniqueId(entity.entity_id)
157418
+ uniqueId: this.frozenUniqueId(anchor)
157292
157419
  });
157293
157420
  logger201.debug(
157294
157421
  `[${entity.entity_id}] basicInfo vendor=${this.state.vendorName} product=${this.state.productName} label=${this.state.productLabel} serial=${this.state.serialNumber} node=${this.state.nodeLabel} uniqueId=${this.state.uniqueId}`
157295
157422
  );
157296
157423
  }
157297
- frozenUniqueId(entityId) {
157424
+ frozenUniqueId(anchor) {
157298
157425
  const provider = this.env.get(BridgeDataProvider);
157299
- const key = `${provider.id}:${entityId}`;
157426
+ const key = `${provider.id}:${anchor}`;
157300
157427
  let uniqueId = appliedUniqueIds.get(key);
157301
157428
  if (uniqueId == null) {
157302
- uniqueId = crypto6.createHash("md5").update(entityId + (provider.uniqueIdSuffix ?? "")).digest("hex").substring(0, 32);
157429
+ uniqueId = crypto6.createHash("md5").update(anchor + (provider.uniqueIdSuffix ?? "")).digest("hex").substring(0, 32);
157303
157430
  appliedUniqueIds.set(key, uniqueId);
157304
157431
  }
157305
157432
  return uniqueId;
@@ -159071,7 +159198,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
159071
159198
  })
159072
159199
  );
159073
159200
  }
159074
- const endpointId = createEndpointId2(primaryEntityId, config11.customName);
159201
+ const endpointId = config11.endpointId ?? createEndpointId2(primaryEntityId, config11.customName);
159075
159202
  const parts = [];
159076
159203
  const airPurifierSub = new Endpoint(
159077
159204
  airPurifierSubType.set({
@@ -159116,7 +159243,8 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
159116
159243
  homeAssistantEntity: {
159117
159244
  entity: primaryPayload,
159118
159245
  customName: config11.customName,
159119
- mapping: parentMapping
159246
+ mapping: parentMapping,
159247
+ identityAnchor: config11.identityAnchor
159120
159248
  }
159121
159249
  });
159122
159250
  const mappedIds = [];
@@ -160574,7 +160702,7 @@ var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpo
160574
160702
  const { registry: registry2, primaryEntityId } = config11;
160575
160703
  const payload = buildEntityPayload2(registry2, primaryEntityId);
160576
160704
  if (!payload) return void 0;
160577
- const endpointId = createEndpointId3(primaryEntityId, config11.customName);
160705
+ const endpointId = config11.endpointId ?? createEndpointId3(primaryEntityId, config11.customName);
160578
160706
  const mapping = config11.mapping ?? {
160579
160707
  entityId: primaryEntityId
160580
160708
  };
@@ -160618,7 +160746,8 @@ var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpo
160618
160746
  homeAssistantEntity: {
160619
160747
  entity: payload,
160620
160748
  customName: config11.customName,
160621
- mapping
160749
+ mapping,
160750
+ identityAnchor: config11.identityAnchor
160622
160751
  }
160623
160752
  });
160624
160753
  const endpoint = new _ComposedClimateFanEndpoint(
@@ -160882,7 +161011,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
160882
161011
  })
160883
161012
  );
160884
161013
  }
160885
- const endpointId = createEndpointId4(primaryEntityId, config11.customName);
161014
+ const endpointId = config11.endpointId ?? createEndpointId4(primaryEntityId, config11.customName);
160886
161015
  const parts = [];
160887
161016
  const tempSub = new Endpoint(
160888
161017
  TemperatureSubType2.set({
@@ -160924,7 +161053,8 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
160924
161053
  homeAssistantEntity: {
160925
161054
  entity: primaryPayload,
160926
161055
  customName: config11.customName,
160927
- mapping
161056
+ mapping,
161057
+ identityAnchor: config11.identityAnchor
160928
161058
  }
160929
161059
  });
160930
161060
  const mappedIds = [];
@@ -169960,7 +170090,7 @@ function WeatherDevice(homeAssistant) {
169960
170090
 
169961
170091
  // src/matter/endpoints/legacy/create-legacy-endpoint-type.ts
169962
170092
  var legacyLogger = Logger.get("LegacyEndpointType");
169963
- function createLegacyEndpointType(entity, mapping, areaName, options) {
170093
+ function createLegacyEndpointType(entity, mapping, areaName, options, identityAnchor) {
169964
170094
  if (mapping?.disableBatteryMapping) {
169965
170095
  entity = {
169966
170096
  ...entity,
@@ -169973,11 +170103,12 @@ function createLegacyEndpointType(entity, mapping, areaName, options) {
169973
170103
  }
169974
170104
  const domain = entity.entity_id.split(".")[0];
169975
170105
  const customName = mapping?.customName;
170106
+ const ha = { entity, customName, mapping, identityAnchor };
169976
170107
  let type;
169977
170108
  if (mapping?.matterDeviceType) {
169978
170109
  const overrideFactory = matterDeviceTypeFactories[mapping.matterDeviceType];
169979
170110
  if (overrideFactory) {
169980
- type = overrideFactory({ entity, customName, mapping });
170111
+ type = overrideFactory(ha);
169981
170112
  if (type && mapping.batteryEntity && !hasPowerSource(type)) {
169982
170113
  type = addPowerSource(type);
169983
170114
  }
@@ -169986,14 +170117,14 @@ function createLegacyEndpointType(entity, mapping, areaName, options) {
169986
170117
  if (!type) {
169987
170118
  if (domain === "vacuum") {
169988
170119
  type = VacuumDevice(
169989
- { entity, customName, mapping },
170120
+ ha,
169990
170121
  options?.vacuumOnOff,
169991
170122
  options?.cleaningModeOptions
169992
170123
  );
169993
170124
  } else {
169994
170125
  const factory = deviceCtrs[domain];
169995
170126
  if (factory) {
169996
- type = factory({ entity, customName, mapping });
170127
+ type = factory(ha);
169997
170128
  } else if (options?.pluginDomainMappings?.has(domain)) {
169998
170129
  const mappedType = options.pluginDomainMappings.get(domain);
169999
170130
  const mappedFactory = matterDeviceTypeFactories[mappedType];
@@ -170001,7 +170132,7 @@ function createLegacyEndpointType(entity, mapping, areaName, options) {
170001
170132
  legacyLogger.info(
170002
170133
  `Using plugin domain mapping for "${domain}" \u2192 "${mappedType}"`
170003
170134
  );
170004
- type = mappedFactory({ entity, customName, mapping });
170135
+ type = mappedFactory(ha);
170005
170136
  }
170006
170137
  } else {
170007
170138
  return void 0;
@@ -170186,7 +170317,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
170186
170317
  if (hasParentBattery) {
170187
170318
  parentType = parentType.with(DefaultPowerSourceServer);
170188
170319
  }
170189
- const endpointId = createEndpointId5(primaryEntityId, config11.customName);
170320
+ const endpointId = config11.endpointId ?? createEndpointId5(primaryEntityId, config11.customName);
170190
170321
  const parts = [];
170191
170322
  const subEndpointMap = /* @__PURE__ */ new Map();
170192
170323
  const mappedIds = [];
@@ -170264,7 +170395,8 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
170264
170395
  homeAssistantEntity: {
170265
170396
  entity: primaryPayload,
170266
170397
  customName: config11.customName,
170267
- mapping: config11.mapping
170398
+ mapping: config11.mapping,
170399
+ identityAnchor: config11.identityAnchor
170268
170400
  }
170269
170401
  });
170270
170402
  const endpoint = new _UserComposedEndpoint(
@@ -170386,7 +170518,7 @@ function asStandaloneEndpointType(type) {
170386
170518
  // src/matter/endpoints/legacy/legacy-endpoint.ts
170387
170519
  var logger240 = Logger.get("LegacyEndpoint");
170388
170520
  var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
170389
- static async create(registry2, entityId, mapping, pluginDomainMappings, standalone = false) {
170521
+ static async create(registry2, entityId, mapping, pluginDomainMappings, standalone = false, endpointId, identityAnchor) {
170390
170522
  const deviceRegistry = registry2.deviceOf(entityId);
170391
170523
  let state = registry2.initialState(entityId);
170392
170524
  const entity = registry2.entity(entityId);
@@ -170639,7 +170771,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
170639
170771
  mapping: effectiveMapping,
170640
170772
  composedEntities: effectiveMapping.composedEntities,
170641
170773
  customName: effectiveMapping?.customName,
170642
- areaName: composedAreaName
170774
+ areaName: composedAreaName,
170775
+ endpointId,
170776
+ identityAnchor
170643
170777
  });
170644
170778
  if (composed) {
170645
170779
  return composed;
@@ -170661,7 +170795,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
170661
170795
  powerEntityId: effectiveMapping?.powerEntity,
170662
170796
  energyEntityId: effectiveMapping?.energyEntity,
170663
170797
  customName: effectiveMapping?.customName,
170664
- areaName: composedAreaName
170798
+ areaName: composedAreaName,
170799
+ endpointId,
170800
+ identityAnchor
170665
170801
  });
170666
170802
  return composed;
170667
170803
  }
@@ -170681,7 +170817,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
170681
170817
  energyEntityId: effectiveMapping?.energyEntity,
170682
170818
  mapping: effectiveMapping,
170683
170819
  customName: effectiveMapping?.customName,
170684
- areaName: composedAreaName
170820
+ areaName: composedAreaName,
170821
+ endpointId,
170822
+ identityAnchor
170685
170823
  });
170686
170824
  if (composed) {
170687
170825
  return composed;
@@ -170698,7 +170836,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
170698
170836
  primaryEntityId: entityId,
170699
170837
  mapping: effectiveMapping,
170700
170838
  customName: effectiveMapping?.customName,
170701
- areaName: composedAreaName
170839
+ areaName: composedAreaName,
170840
+ endpointId,
170841
+ identityAnchor
170702
170842
  });
170703
170843
  if (composed) {
170704
170844
  return composed;
@@ -170732,11 +170872,17 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
170732
170872
  }
170733
170873
  }
170734
170874
  const areaName = registry2.getAreaName(entityId);
170735
- let type = createLegacyEndpointType(payload, effectiveMapping, areaName, {
170736
- vacuumOnOff: registry2.isVacuumOnOffEnabled(),
170737
- cleaningModeOptions,
170738
- pluginDomainMappings
170739
- });
170875
+ let type = createLegacyEndpointType(
170876
+ payload,
170877
+ effectiveMapping,
170878
+ areaName,
170879
+ {
170880
+ vacuumOnOff: registry2.isVacuumOnOffEnabled(),
170881
+ cleaningModeOptions,
170882
+ pluginDomainMappings
170883
+ },
170884
+ identityAnchor
170885
+ );
170740
170886
  if (!type) {
170741
170887
  return;
170742
170888
  }
@@ -170750,11 +170896,12 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
170750
170896
  entityId,
170751
170897
  customName,
170752
170898
  mappedIds,
170753
- effectiveMapping?.updateThrottleMs
170899
+ effectiveMapping?.updateThrottleMs,
170900
+ endpointId
170754
170901
  );
170755
170902
  }
170756
- constructor(type, entityId, customName, mappedEntityIds, throttleMs) {
170757
- super(type, entityId, customName, mappedEntityIds);
170903
+ constructor(type, entityId, customName, mappedEntityIds, throttleMs, endpointId) {
170904
+ super(type, entityId, customName, mappedEntityIds, endpointId);
170758
170905
  this.flushUpdate = throttleMs && throttleMs > 50 ? throttleLatest(this.flushPendingUpdate.bind(this), throttleMs) : debounce6(this.flushPendingUpdate.bind(this), 50);
170759
170906
  }
170760
170907
  lastState;
@@ -171601,11 +171748,124 @@ var EntityIsolationServiceImpl = class {
171601
171748
  };
171602
171749
  var EntityIsolationService = new EntityIsolationServiceImpl();
171603
171750
 
171751
+ // src/services/bridges/identity-resolver.ts
171752
+ var SEP = "\0";
171753
+ function identityKey(entity) {
171754
+ const uniqueId = entity.registry?.unique_id;
171755
+ const platform = entity.registry?.platform;
171756
+ if (!uniqueId || !platform) {
171757
+ return null;
171758
+ }
171759
+ const domain = entity.entity_id.split(".")[0];
171760
+ return platform + SEP + domain + SEP + uniqueId;
171761
+ }
171762
+ var IdentityResolver = class {
171763
+ constructor(identityStorage, mappingStorage) {
171764
+ this.identityStorage = identityStorage;
171765
+ this.mappingStorage = mappingStorage;
171766
+ }
171767
+ identityStorage;
171768
+ mappingStorage;
171769
+ async resolveIdentity(bridgeId, entity, mapping, opts) {
171770
+ const entityId = entity.entity_id;
171771
+ const key = identityKey(entity);
171772
+ if (key == null) {
171773
+ return {
171774
+ endpointId: createEndpointId(entityId, mapping?.customName),
171775
+ anchorEntityId: entityId,
171776
+ protected: false
171777
+ };
171778
+ }
171779
+ const existing = this.identityStorage.getIdentity(bridgeId, key);
171780
+ if (existing) {
171781
+ const renamedFrom = existing.lastEntityId !== entityId ? existing.lastEntityId ?? existing.anchorEntityId : void 0;
171782
+ if (opts.stableIdentity) {
171783
+ if (renamedFrom) {
171784
+ this.identityStorage.setIdentity(bridgeId, key, {
171785
+ ...existing,
171786
+ lastEntityId: entityId
171787
+ });
171788
+ await this.rekeyMapping(bridgeId, renamedFrom, entityId);
171789
+ }
171790
+ return {
171791
+ endpointId: existing.endpointId,
171792
+ anchorEntityId: existing.anchorEntityId,
171793
+ protected: true,
171794
+ renamedFrom
171795
+ };
171796
+ }
171797
+ const liveEndpointId = createEndpointId(entityId, mapping?.customName);
171798
+ if (existing.endpointId !== liveEndpointId || existing.anchorEntityId !== entityId || existing.lastEntityId !== entityId) {
171799
+ this.identityStorage.setIdentity(bridgeId, key, {
171800
+ ...existing,
171801
+ endpointId: liveEndpointId,
171802
+ anchorEntityId: entityId,
171803
+ lastEntityId: entityId
171804
+ });
171805
+ }
171806
+ return {
171807
+ endpointId: liveEndpointId,
171808
+ anchorEntityId: entityId,
171809
+ protected: false,
171810
+ renamedFrom
171811
+ };
171812
+ }
171813
+ const desired = createEndpointId(entityId, mapping?.customName);
171814
+ let endpointId = desired;
171815
+ if (opts.stableIdentity) {
171816
+ let n = 2;
171817
+ while (opts.isEndpointIdTaken(endpointId, key)) {
171818
+ endpointId = `${desired}_${n++}`;
171819
+ }
171820
+ }
171821
+ const record = {
171822
+ endpointId,
171823
+ anchorEntityId: entityId,
171824
+ lastEntityId: entityId,
171825
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
171826
+ };
171827
+ this.identityStorage.setIdentity(bridgeId, key, record);
171828
+ if (opts.stableIdentity) {
171829
+ return {
171830
+ endpointId: record.endpointId,
171831
+ anchorEntityId: record.anchorEntityId,
171832
+ protected: true
171833
+ };
171834
+ }
171835
+ return {
171836
+ endpointId: desired,
171837
+ anchorEntityId: entityId,
171838
+ protected: false
171839
+ };
171840
+ }
171841
+ // Carry a mapping from the old entity_id to the new one on rename. anchorEntityId
171842
+ // never changes, only the mapping key. Skips when no old mapping exists or a new
171843
+ // one already does, so a manual mapping under the new id is never clobbered.
171844
+ async rekeyMapping(bridgeId, oldEntityId, newEntityId) {
171845
+ if (oldEntityId === newEntityId) {
171846
+ return;
171847
+ }
171848
+ const old = this.mappingStorage.getMapping(bridgeId, oldEntityId);
171849
+ if (!old) {
171850
+ return;
171851
+ }
171852
+ if (this.mappingStorage.getMapping(bridgeId, newEntityId)) {
171853
+ return;
171854
+ }
171855
+ await this.mappingStorage.setMapping({
171856
+ ...old,
171857
+ bridgeId,
171858
+ entityId: newEntityId
171859
+ });
171860
+ await this.mappingStorage.deleteMapping(bridgeId, oldEntityId);
171861
+ }
171862
+ };
171863
+
171604
171864
  // src/services/bridges/bridge-endpoint-manager.ts
171605
171865
  var MAX_ENTITY_ID_LENGTH = 150;
171606
171866
  var ENDPOINT_REMOVAL_GRACE_MS = 6e4;
171607
171867
  var BridgeEndpointManager = class extends Service {
171608
- constructor(client, registry2, mappingStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
171868
+ constructor(client, registry2, mappingStorage, identityStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
171609
171869
  super("BridgeEndpointManager");
171610
171870
  this.client = client;
171611
171871
  this.registry = registry2;
@@ -171616,6 +171876,10 @@ var BridgeEndpointManager = class extends Service {
171616
171876
  this.pluginRegistry = pluginRegistry;
171617
171877
  this.pluginInstaller = pluginInstaller;
171618
171878
  this.root = new AggregatorEndpoint2("aggregator");
171879
+ this.identityResolver = new IdentityResolver(
171880
+ identityStorage,
171881
+ mappingStorage
171882
+ );
171619
171883
  EntityIsolationService.registerIsolationCallback(
171620
171884
  bridgeId,
171621
171885
  this.isolateEntity.bind(this)
@@ -171654,6 +171918,7 @@ var BridgeEndpointManager = class extends Service {
171654
171918
  failedAt: (/* @__PURE__ */ new Date()).toISOString()
171655
171919
  });
171656
171920
  }
171921
+ identityResolver;
171657
171922
  wirePluginCallbacks() {
171658
171923
  if (!this.pluginManager) return;
171659
171924
  this.pluginManager.onDeviceRegistered = async (pluginName, device) => {
@@ -171993,10 +172258,57 @@ var BridgeEndpointManager = class extends Service {
171993
172258
  if (humId) this.registry.markComposedSubEntityUsed(humId);
171994
172259
  }
171995
172260
  }
172261
+ const stableIdentity = this.registry.isStableIdentityEnabled();
172262
+ const resolvedByEntity = /* @__PURE__ */ new Map();
172263
+ const endpointIdToEntity = /* @__PURE__ */ new Map();
172264
+ const claimedEndpointIds = /* @__PURE__ */ new Map();
172265
+ for (const entityId of this.entityIds) {
172266
+ const entityInfo = {
172267
+ entity_id: entityId,
172268
+ registry: this.registry.entity(entityId)
172269
+ };
172270
+ const resolved = await this.identityResolver.resolveIdentity(
172271
+ this.bridgeId,
172272
+ entityInfo,
172273
+ this.getEntityMapping(entityId),
172274
+ {
172275
+ stableIdentity,
172276
+ isEndpointIdTaken: (id, key) => claimedEndpointIds.has(id) && claimedEndpointIds.get(id) !== key
172277
+ }
172278
+ );
172279
+ resolvedByEntity.set(entityId, resolved);
172280
+ claimedEndpointIds.set(
172281
+ resolved.endpointId,
172282
+ identityKey(entityInfo) ?? `\0e:${entityId}`
172283
+ );
172284
+ endpointIdToEntity.set(resolved.endpointId, entityId);
172285
+ if (resolved.renamedFrom && this.mappingFingerprints.has(resolved.renamedFrom)) {
172286
+ const fp = this.mappingFingerprints.get(resolved.renamedFrom);
172287
+ this.mappingFingerprints.delete(resolved.renamedFrom);
172288
+ this.mappingFingerprints.set(entityId, fp);
172289
+ }
172290
+ }
171996
172291
  const existingEndpoints = [];
171997
172292
  const now = Date.now();
171998
172293
  for (const endpoint of endpoints) {
171999
172294
  const present = this.entityIds.includes(endpoint.entityId);
172295
+ const claimant = endpointIdToEntity.get(endpoint.id);
172296
+ if (!present && claimant != null && claimant !== endpoint.entityId) {
172297
+ this.log.info(
172298
+ `Entity renamed ${endpoint.entityId} -> ${claimant}, keeping endpoint ${endpoint.id}`
172299
+ );
172300
+ try {
172301
+ await endpoint.close();
172302
+ } catch (e) {
172303
+ this.log.warn(
172304
+ `Failed to close renamed endpoint ${endpoint.entityId}:`,
172305
+ e
172306
+ );
172307
+ }
172308
+ this.pendingRemovals.delete(endpoint.entityId);
172309
+ this.mappingFingerprints.delete(endpoint.entityId);
172310
+ continue;
172311
+ }
172000
172312
  if (present) {
172001
172313
  this.pendingRemovals.delete(endpoint.entityId);
172002
172314
  }
@@ -172039,7 +172351,8 @@ var BridgeEndpointManager = class extends Service {
172039
172351
  this.log.info(
172040
172352
  `Mapping changed for ${endpoint.entityId}, recreating endpoint`
172041
172353
  );
172042
- const sameId = createEndpointId(endpoint.entityId, currentMapping?.customName) === endpoint.id;
172354
+ const resolvedId = resolvedByEntity.get(endpoint.entityId)?.endpointId ?? createEndpointId(endpoint.entityId, currentMapping?.customName);
172355
+ const sameId = resolvedId === endpoint.id;
172043
172356
  try {
172044
172357
  if (sameId) {
172045
172358
  await endpoint.close();
@@ -172097,11 +172410,15 @@ var BridgeEndpointManager = class extends Service {
172097
172410
  if (!endpoint) {
172098
172411
  try {
172099
172412
  const domainMappings = this.getPluginDomainMappings();
172413
+ const resolved = resolvedByEntity.get(entityId);
172100
172414
  endpoint = await LegacyEndpoint.create(
172101
172415
  this.registry,
172102
172416
  entityId,
172103
172417
  mapping,
172104
- domainMappings
172418
+ domainMappings,
172419
+ false,
172420
+ resolved?.endpointId,
172421
+ resolved?.anchorEntityId
172105
172422
  );
172106
172423
  } catch (e) {
172107
172424
  const reason = this.extractErrorReason(e);
@@ -172513,6 +172830,11 @@ var BridgeRegistry = class _BridgeRegistry {
172513
172830
  isVacuumOnOffEnabled() {
172514
172831
  return this.dataProvider.featureFlags?.vacuumOnOff === true;
172515
172832
  }
172833
+ // Consume frozen device identities (#404). Seeding always runs; only
172834
+ // consumption of the stored endpoint id/anchor is gated on this flag.
172835
+ isStableIdentityEnabled() {
172836
+ return this.dataProvider.featureFlags?.stableIdentity === true;
172837
+ }
172516
172838
  /**
172517
172839
  * Check if the vacuum OnOff cluster should be included for server-mode vacuums.
172518
172840
  * Defaults to OFF. OnOff is NOT part of the RoboticVacuumCleaner (0x74) device
@@ -173942,7 +174264,7 @@ function ServerModeVacuumDevice(homeAssistantEntity, includeOnOff = false, clean
173942
174264
  // src/matter/endpoints/server-mode-vacuum-endpoint.ts
173943
174265
  var logger243 = Logger.get("ServerModeVacuumEndpoint");
173944
174266
  var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEndpoint {
173945
- static async create(registry2, entityId, mapping) {
174267
+ static async create(registry2, entityId, mapping, endpointId) {
173946
174268
  const deviceRegistry = registry2.deviceOf(entityId);
173947
174269
  let state = registry2.initialState(entityId);
173948
174270
  const entity = registry2.entity(entityId);
@@ -174123,14 +174445,15 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
174123
174445
  endpointType,
174124
174446
  entityId,
174125
174447
  customName,
174126
- mappedIds
174448
+ mappedIds,
174449
+ endpointId
174127
174450
  );
174128
174451
  }
174129
174452
  lastState;
174130
174453
  pendingMappedChange = false;
174131
174454
  flushUpdate;
174132
- constructor(type, entityId, customName, mappedEntityIds) {
174133
- super(type, entityId, customName, mappedEntityIds);
174455
+ constructor(type, entityId, customName, mappedEntityIds, endpointId) {
174456
+ super(type, entityId, customName, mappedEntityIds, endpointId);
174134
174457
  this.flushUpdate = debounce7(this.flushPendingUpdate.bind(this), 50);
174135
174458
  }
174136
174459
  clearTimers() {
@@ -174197,7 +174520,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
174197
174520
  // src/services/bridges/server-mode-endpoint-manager.ts
174198
174521
  var MAX_SERVER_MODE_DEVICES = 10;
174199
174522
  var ServerModeEndpointManager = class extends Service {
174200
- constructor(serverNode, client, registry2, mappingStorage, dataProvider, log) {
174523
+ constructor(serverNode, client, registry2, mappingStorage, identityStorage, dataProvider, log) {
174201
174524
  super("ServerModeEndpointManager");
174202
174525
  this.serverNode = serverNode;
174203
174526
  this.client = client;
@@ -174205,6 +174528,10 @@ var ServerModeEndpointManager = class extends Service {
174205
174528
  this.mappingStorage = mappingStorage;
174206
174529
  this.dataProvider = dataProvider;
174207
174530
  this.log = log;
174531
+ this.identityResolver = new IdentityResolver(
174532
+ identityStorage,
174533
+ mappingStorage
174534
+ );
174208
174535
  }
174209
174536
  serverNode;
174210
174537
  client;
@@ -174223,6 +174550,7 @@ var ServerModeEndpointManager = class extends Service {
174223
174550
  get devices() {
174224
174551
  return [...this.endpoints.values()].map((entry) => entry.endpoint);
174225
174552
  }
174553
+ identityResolver;
174226
174554
  getEntityMapping(entityId) {
174227
174555
  return this.mappingStorage.getMapping(this.dataProvider.id, entityId);
174228
174556
  }
@@ -174291,6 +174619,19 @@ var ServerModeEndpointManager = class extends Service {
174291
174619
  this.endpoints.delete(entityId);
174292
174620
  }
174293
174621
  }
174622
+ // Like removeEndpoints but close() keeps the persisted number pre-allocated,
174623
+ // so a same-id recreate reuses it. Used on rename and same-id recreates (#404).
174624
+ async closeEndpoint(entityId) {
174625
+ const entry = this.endpoints.get(entityId);
174626
+ if (!entry) return;
174627
+ try {
174628
+ await entry.endpoint.close();
174629
+ } catch (e) {
174630
+ this.log.warn(`Failed to close endpoint ${entityId}:`, e);
174631
+ }
174632
+ this.serverNode.forgetDevice(entry.endpoint);
174633
+ this.endpoints.delete(entityId);
174634
+ }
174294
174635
  async refreshDevices() {
174295
174636
  this.registry.refresh();
174296
174637
  this._failedEntities = [];
@@ -174318,10 +174659,45 @@ var ServerModeEndpointManager = class extends Service {
174318
174659
  `Server mode node is capped at ${MAX_SERVER_MODE_DEVICES} devices, ${surplus.length} entities skipped`
174319
174660
  );
174320
174661
  }
174662
+ const stableIdentity = this.dataProvider.featureFlags?.stableIdentity === true;
174663
+ const resolvedByEntity = /* @__PURE__ */ new Map();
174664
+ const endpointIdToEntity = /* @__PURE__ */ new Map();
174665
+ const claimedEndpointIds = /* @__PURE__ */ new Map();
174666
+ for (const entityId of orderedIds) {
174667
+ const entityInfo = {
174668
+ entity_id: entityId,
174669
+ registry: this.registry.entity(entityId)
174670
+ };
174671
+ const resolved = await this.identityResolver.resolveIdentity(
174672
+ this.dataProvider.id,
174673
+ entityInfo,
174674
+ this.getEntityMapping(entityId),
174675
+ {
174676
+ stableIdentity,
174677
+ isEndpointIdTaken: (id, key) => claimedEndpointIds.has(id) && claimedEndpointIds.get(id) !== key
174678
+ }
174679
+ );
174680
+ resolvedByEntity.set(entityId, resolved);
174681
+ claimedEndpointIds.set(
174682
+ resolved.endpointId,
174683
+ identityKey(entityInfo) ?? `\0e:${entityId}`
174684
+ );
174685
+ endpointIdToEntity.set(resolved.endpointId, entityId);
174686
+ }
174321
174687
  const keep = new Set(orderedIds);
174322
174688
  const removed = [...this.endpoints.keys()].filter((id) => !keep.has(id));
174323
174689
  let structureChanged = removed.length > 0;
174324
- await this.removeEndpoints(removed);
174690
+ const genuinelyRemoved = [];
174691
+ for (const oldId of removed) {
174692
+ const entry = this.endpoints.get(oldId);
174693
+ const claimant = entry ? endpointIdToEntity.get(entry.endpoint.id) : void 0;
174694
+ if (entry && claimant != null && claimant !== oldId) {
174695
+ await this.closeEndpoint(oldId);
174696
+ } else {
174697
+ genuinelyRemoved.push(oldId);
174698
+ }
174699
+ }
174700
+ await this.removeEndpoints(genuinelyRemoved);
174325
174701
  for (const entityId of orderedIds) {
174326
174702
  const mapping = this.getEntityMapping(entityId);
174327
174703
  if (mapping?.disabled) {
@@ -174344,9 +174720,14 @@ var ServerModeEndpointManager = class extends Service {
174344
174720
  this.log.debug(`Device endpoint already exists for ${entityId}`);
174345
174721
  continue;
174346
174722
  }
174723
+ const resolved = resolvedByEntity.get(entityId);
174347
174724
  if (existing) {
174348
174725
  this.log.info(`Mapping changed for ${entityId}, recreating endpoint`);
174349
- await this.removeEndpoints([entityId]);
174726
+ if (resolved && existing.endpoint.id === resolved.endpointId) {
174727
+ await this.closeEndpoint(entityId);
174728
+ } else {
174729
+ await this.removeEndpoints([entityId]);
174730
+ }
174350
174731
  structureChanged = true;
174351
174732
  }
174352
174733
  const claimedBy = [...this.endpoints.entries()].find(
@@ -174359,7 +174740,7 @@ var ServerModeEndpointManager = class extends Service {
174359
174740
  });
174360
174741
  continue;
174361
174742
  }
174362
- const endpointId = createEndpointId(entityId, mapping?.customName);
174743
+ const endpointId = resolved?.endpointId ?? createEndpointId(entityId, mapping?.customName);
174363
174744
  const collision = [...this.endpoints.entries()].find(
174364
174745
  ([, entry]) => entry.endpoint.id === endpointId
174365
174746
  );
@@ -174382,12 +174763,18 @@ var ServerModeEndpointManager = class extends Service {
174382
174763
  }
174383
174764
  try {
174384
174765
  const domain = entityId.split(".")[0];
174385
- const endpoint = domain === "vacuum" ? await this.createServerModeVacuumEndpoint(entityId, mapping) : await LegacyEndpoint.create(
174766
+ const endpoint = domain === "vacuum" ? await this.createServerModeVacuumEndpoint(
174767
+ entityId,
174768
+ mapping,
174769
+ resolved?.endpointId
174770
+ ) : await LegacyEndpoint.create(
174386
174771
  this.registry,
174387
174772
  entityId,
174388
174773
  mapping,
174389
174774
  void 0,
174390
- true
174775
+ true,
174776
+ resolved?.endpointId,
174777
+ resolved?.anchorEntityId
174391
174778
  );
174392
174779
  if (!endpoint) {
174393
174780
  this._failedEntities.push({
@@ -174455,8 +174842,13 @@ var ServerModeEndpointManager = class extends Service {
174455
174842
  * This makes the vacuum appear as a standalone Matter device, which is required
174456
174843
  * for Apple Home Siri voice commands and Alexa discovery.
174457
174844
  */
174458
- async createServerModeVacuumEndpoint(entityId, mapping) {
174459
- return ServerModeVacuumEndpoint.create(this.registry, entityId, mapping);
174845
+ async createServerModeVacuumEndpoint(entityId, mapping, endpointId) {
174846
+ return ServerModeVacuumEndpoint.create(
174847
+ this.registry,
174848
+ entityId,
174849
+ mapping,
174850
+ endpointId
174851
+ );
174460
174852
  }
174461
174853
  };
174462
174854
 
@@ -174540,6 +174932,7 @@ var BridgeEnvironment = class _BridgeEnvironment extends EnvironmentBase {
174540
174932
  await this.load(HomeAssistantClient),
174541
174933
  this.get(BridgeRegistry),
174542
174934
  await this.load(EntityMappingStorage),
174935
+ await this.load(EntityIdentityStorage),
174543
174936
  bridgeId,
174544
174937
  this.endpointManagerLogger,
174545
174938
  pluginManager,
@@ -174625,6 +175018,7 @@ var BridgeEnvironmentFactory = class extends BridgeFactory {
174625
175018
  await env.load(HomeAssistantClient),
174626
175019
  env.get(BridgeRegistry),
174627
175020
  await env.load(EntityMappingStorage),
175021
+ await env.load(EntityIdentityStorage),
174628
175022
  dataProvider,
174629
175023
  loggerService.get("ServerModeEndpointManager")
174630
175024
  );
@@ -174676,6 +175070,10 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
174676
175070
  EntityMappingStorage,
174677
175071
  new EntityMappingStorage(await this.load(AppStorage))
174678
175072
  );
175073
+ this.set(
175074
+ EntityIdentityStorage,
175075
+ new EntityIdentityStorage(await this.load(AppStorage))
175076
+ );
174679
175077
  this.set(
174680
175078
  LockCredentialStorage,
174681
175079
  new LockCredentialStorage(await this.load(AppStorage))
@@ -174736,6 +175134,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
174736
175134
  await this.load(HomeAssistantRegistry),
174737
175135
  await this.load(BridgeStorage),
174738
175136
  await this.load(EntityMappingStorage),
175137
+ await this.load(EntityIdentityStorage),
174739
175138
  await this.load(LockCredentialStorage),
174740
175139
  await this.load(AppSettingsStorage),
174741
175140
  await this.load(BackupService),