@camstack/system 1.1.10 → 1.1.11

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.
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-Ba_eOEPT.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-Cro19O4u.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { $ as setWorkerNativeCapsChangeListener, A as createUdsLoggerWithControl, X as getWorkerNativeCapProvider, Z as getWorkerNativeCapSnapshot, i as createUdsAddonContext, j as LocalChildClient, mt as resolveAddonClass, pt as installManifestNativeDeps, t as installManifestPythonDeps, tt as validateProviderRegistrations } from "./manifest-python-deps-BqId8WrC.mjs";
1
+ import { $ as setWorkerNativeCapsChangeListener, A as createUdsLoggerWithControl, X as getWorkerNativeCapProvider, Z as getWorkerNativeCapSnapshot, i as createUdsAddonContext, j as LocalChildClient, mt as resolveAddonClass, pt as installManifestNativeDeps, t as installManifestPythonDeps, tt as validateProviderRegistrations } from "./manifest-python-deps-1zTy3vXe.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
3
3
  import { register } from "node:module";
4
4
  import * as fs from "node:fs";
@@ -239,7 +239,8 @@ function buildLinkIndexes(entries) {
239
239
  *
240
240
  * Addon store layout:
241
241
  * deviceIndex → Record<addonId, stableId[]> (which devices exist per addon)
242
- * deviceMeta → Record<"addonId:stableId", DeviceMeta> (type, name, parentDeviceId, id)
242
+ * deviceMeta → Record<numericId, DeviceMeta> (keyed by numeric device id;
243
+ * addonId + stableId are FIELDS on the record)
243
244
  *
244
245
  * Device store (per-device config):
245
246
  * readDeviceStore(numericDeviceId) → config blob
@@ -282,9 +283,6 @@ function shallowEqual(a, b) {
282
283
  for (const k of ak) if (a[k] !== b[k]) return false;
283
284
  return true;
284
285
  }
285
- function deviceKey(addonId, stableId) {
286
- return `${addonId}:${stableId}`;
287
- }
288
286
  /** Returns true when `x` is a non-null, non-array plain object. */
289
287
  function isRecord(x) {
290
288
  return x !== null && typeof x === "object" && !Array.isArray(x);
@@ -777,12 +775,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
777
775
  * numeric deviceId; this helper is display-only.
778
776
  */
779
777
  async lookupPersistedStableId(deviceId) {
780
- const meta = (await this.ctx.settings.readAddonStore()).deviceMeta ?? {};
781
- for (const [key, m] of Object.entries(meta)) if (m.id === deviceId) {
782
- const sep = key.indexOf(":");
783
- if (sep < 0) continue;
784
- return key.slice(sep + 1);
785
- }
778
+ return ((await this.ctx.settings.readAddonStore()).deviceMeta ?? {})[String(deviceId)]?.stableId;
786
779
  }
787
780
  async getDeviceAggregate(deviceId, kind) {
788
781
  const registry = this.capabilityRegistry;
@@ -1474,28 +1467,32 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1474
1467
  * Returns null when no device with that id is known to the hub.
1475
1468
  */
1476
1469
  const resolvePersistedById = async (deviceId) => {
1470
+ const m = (await readMeta())[String(deviceId)];
1471
+ if (!m) return null;
1472
+ return {
1473
+ addonId: m.addonId,
1474
+ stableId: m.stableId,
1475
+ meta: m
1476
+ };
1477
+ };
1478
+ /** Direct children of a device: the union of the live registry's children
1479
+ * and the persisted-meta scan (`parentDeviceId === parentId`), deduplicated
1480
+ * and excluding self. Shared by the `remove` cascade and the `resetToSource`
1481
+ * resync purge (#19). */
1482
+ const directChildIds = async (parentId) => {
1483
+ const ids = /* @__PURE__ */ new Set();
1484
+ if (registry) for (const c of registry.getChildren(parentId)) ids.add(c.id);
1477
1485
  const meta = await readMeta();
1478
- for (const [key, m] of Object.entries(meta)) if (m.id === deviceId) {
1479
- const sep = key.indexOf(":");
1480
- if (sep < 0) continue;
1481
- return {
1482
- addonId: key.slice(0, sep),
1483
- stableId: key.slice(sep + 1),
1484
- meta: m
1485
- };
1486
- }
1487
- return null;
1486
+ for (const m of Object.values(meta)) if (m.parentDeviceId === parentId) ids.add(m.id);
1487
+ ids.delete(parentId);
1488
+ return [...ids];
1488
1489
  };
1489
1490
  /** Resolve a link's `sourceKey` to a live device id: the sibling accessory
1490
1491
  * whose stableId is `${parentStableId}-${sourceKey}`. Null when absent.
1491
1492
  * Pure over a pre-read meta map so a multi-link resolve reads the store once. */
1492
1493
  const resolveSourceDeviceId = (parentStableId, sourceKey, meta) => {
1493
1494
  const wanted = `${parentStableId}-${sourceKey}`;
1494
- for (const [key, m] of Object.entries(meta)) {
1495
- const sep = key.indexOf(":");
1496
- if (sep < 0) continue;
1497
- if (key.slice(sep + 1) === wanted) return m.id;
1498
- }
1495
+ for (const m of Object.values(meta)) if (m.stableId === wanted) return m.id;
1499
1496
  return null;
1500
1497
  };
1501
1498
  /** Rebuild the `linkTargets` / `linkDependents` reverse-index maps from the
@@ -1513,12 +1510,9 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1513
1510
  const allMeta = await readMeta();
1514
1511
  const stableIdById = /* @__PURE__ */ new Map();
1515
1512
  const idByStableId = /* @__PURE__ */ new Map();
1516
- for (const [key, m] of Object.entries(allMeta)) {
1517
- const sep = key.indexOf(":");
1518
- if (sep < 0) continue;
1519
- const sid = key.slice(sep + 1);
1520
- stableIdById.set(m.id, sid);
1521
- idByStableId.set(sid, m.id);
1513
+ for (const m of Object.values(allMeta)) {
1514
+ stableIdById.set(m.id, m.stableId);
1515
+ idByStableId.set(m.stableId, m.id);
1522
1516
  }
1523
1517
  const entries = [];
1524
1518
  const expectedSources = /* @__PURE__ */ new Set();
@@ -1546,10 +1540,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1546
1540
  const idToAddonId = /* @__PURE__ */ new Map();
1547
1541
  {
1548
1542
  const meta = await readMeta();
1549
- for (const [key, m] of Object.entries(meta)) {
1550
- const sep = key.indexOf(":");
1551
- if (sep < 0) continue;
1552
- idToAddonId.set(m.id, key.slice(0, sep));
1543
+ for (const m of Object.values(meta)) {
1544
+ idToAddonId.set(m.id, m.addonId);
1553
1545
  if (Array.isArray(m.deviceLinks) && m.deviceLinks.length > 0) this.devicesWithLinks.add(m.id);
1554
1546
  }
1555
1547
  }
@@ -1563,8 +1555,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1563
1555
  await withMetaWriteLock(async () => {
1564
1556
  const persisted = await resolvePersistedById(deviceId);
1565
1557
  if (!persisted) throw new Error(`[device-manager] stampIntegrationId: unknown device id=${deviceId}`);
1566
- const { addonId: aid, stableId, meta: m } = persisted;
1567
- const key = deviceKey(aid, stableId);
1558
+ const { meta: m } = persisted;
1559
+ const key = String(deviceId);
1568
1560
  const allMeta = await readMeta();
1569
1561
  await settings.writeAddonStore({ deviceMeta: {
1570
1562
  ...allMeta,
@@ -1645,10 +1637,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1645
1637
  let containerStableId = persisted.stableId;
1646
1638
  const parentId = persisted.meta.parentDeviceId;
1647
1639
  if (parentId !== null) {
1648
- for (const [key, m] of Object.entries(allMeta)) if (m.id === parentId) {
1649
- containerStableId = key.slice(key.indexOf(":") + 1);
1650
- break;
1651
- }
1640
+ const parentMeta = allMeta[String(parentId)];
1641
+ if (parentMeta) containerStableId = parentMeta.stableId;
1652
1642
  }
1653
1643
  const resolved = [];
1654
1644
  for (const link of links) {
@@ -1688,15 +1678,16 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1688
1678
  * `registerDevice` completes. */
1689
1679
  allocateDeviceId: async (input) => {
1690
1680
  const { addonId, stableId } = input;
1691
- const key = deviceKey(addonId, stableId);
1692
1681
  return await withMetaWriteLock(async () => {
1693
1682
  const meta = await readMeta();
1694
- const existing = meta[key];
1683
+ const existing = Object.values(meta).find((m) => m.addonId === addonId && m.stableId === stableId);
1695
1684
  if (existing) return { id: existing.id };
1696
1685
  const id = await allocateNextDeviceId();
1697
1686
  await settings.writeAddonStore({ deviceMeta: {
1698
1687
  ...meta,
1699
- [key]: {
1688
+ [String(id)]: {
1689
+ addonId,
1690
+ stableId,
1700
1691
  type: "generic",
1701
1692
  name: stableId,
1702
1693
  location: null,
@@ -1710,7 +1701,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1710
1701
  },
1711
1702
  registerDevice: async (input) => {
1712
1703
  const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input;
1713
- const key = deviceKey(addonId, stableId);
1704
+ const key = String(id);
1714
1705
  const featuresArr = Array.isArray(features) ? [...features] : [];
1715
1706
  const { isFirstRegistration, exportFingerprint, fingerprintChanged } = await withMetaWriteLock(async () => {
1716
1707
  const index = await readIndex();
@@ -1722,6 +1713,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1722
1713
  } });
1723
1714
  const meta = await readMeta();
1724
1715
  const existingMeta = meta[key];
1716
+ const wasUserNamed = existingMeta?.userNamed ?? (existingMeta !== void 0 && existingMeta.name !== stableId);
1717
+ const resolvedName = wasUserNamed && existingMeta !== void 0 ? existingMeta.name : name;
1725
1718
  const isFirst = !existingMeta || !wasInIndex;
1726
1719
  const fingerprint = (0, _camstack_types_node.canonicalDeviceFingerprint)({
1727
1720
  deviceId: id,
@@ -1731,8 +1724,11 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1731
1724
  await settings.writeAddonStore({ deviceMeta: {
1732
1725
  ...meta,
1733
1726
  [key]: {
1727
+ addonId,
1728
+ stableId,
1734
1729
  type,
1735
- name: existingMeta && existingMeta.name !== stableId ? existingMeta.name : name,
1730
+ name: resolvedName,
1731
+ userNamed: wasUserNamed,
1736
1732
  location: existingMeta?.location ?? null,
1737
1733
  disabled: existingMeta?.disabled ?? false,
1738
1734
  ...existingMeta?.integrationId !== void 0 ? { integrationId: existingMeta.integrationId } : {},
@@ -1807,7 +1803,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1807
1803
  const persisted = await resolvePersistedById(deviceId);
1808
1804
  if (!persisted) return;
1809
1805
  const { addonId, stableId, meta: persistedMeta } = persisted;
1810
- const key = deviceKey(addonId, stableId);
1806
+ const key = String(deviceId);
1811
1807
  const deviceName = persistedMeta.name;
1812
1808
  await withMetaWriteLock(async () => {
1813
1809
  const index = await readIndex();
@@ -1888,7 +1884,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1888
1884
  const persisted = await resolvePersistedById(deviceId);
1889
1885
  if (!persisted) return null;
1890
1886
  const { addonId, stableId, meta: m } = persisted;
1891
- const key = deviceKey(addonId, stableId);
1887
+ const key = String(deviceId);
1892
1888
  const metadata = (await readMetadataMap())[key] ?? null;
1893
1889
  return {
1894
1890
  id: m.id,
@@ -1917,14 +1913,15 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1917
1913
  await withMetaWriteLock(async () => {
1918
1914
  const persisted = await resolvePersistedById(deviceId);
1919
1915
  if (!persisted) throw new Error(`[device-manager] setName: unknown device id=${deviceId}`);
1920
- const { addonId, stableId, meta: m } = persisted;
1921
- const key = deviceKey(addonId, stableId);
1916
+ const { meta: m } = persisted;
1917
+ const key = String(deviceId);
1922
1918
  const allMeta = await readMeta();
1923
1919
  await settings.writeAddonStore({ deviceMeta: {
1924
1920
  ...allMeta,
1925
1921
  [key]: {
1926
1922
  ...m,
1927
- name
1923
+ name,
1924
+ userNamed: true
1928
1925
  }
1929
1926
  } });
1930
1927
  });
@@ -1954,8 +1951,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1954
1951
  await withMetaWriteLock(async () => {
1955
1952
  const persisted = await resolvePersistedById(deviceId);
1956
1953
  if (!persisted) throw new Error(`[device-manager] setLocation: unknown device id=${deviceId}`);
1957
- const { addonId, stableId, meta: m } = persisted;
1958
- const key = deviceKey(addonId, stableId);
1954
+ const { meta: m } = persisted;
1955
+ const key = String(deviceId);
1959
1956
  const allMeta = await readMeta();
1960
1957
  await settings.writeAddonStore({ deviceMeta: {
1961
1958
  ...allMeta,
@@ -1992,8 +1989,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1992
1989
  await withMetaWriteLock(async () => {
1993
1990
  const persisted = await resolvePersistedById(deviceId);
1994
1991
  if (!persisted) throw new Error(`[device-manager] setType: unknown device id=${deviceId}`);
1995
- const { addonId, stableId, meta: m } = persisted;
1996
- const key = deviceKey(addonId, stableId);
1992
+ const { meta: m } = persisted;
1993
+ const key = String(deviceId);
1997
1994
  const allMeta = await readMeta();
1998
1995
  await settings.writeAddonStore({ deviceMeta: {
1999
1996
  ...allMeta,
@@ -2038,8 +2035,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2038
2035
  await withMetaWriteLock(async () => {
2039
2036
  const persisted = await resolvePersistedById(deviceId);
2040
2037
  if (!persisted) throw new Error(`[device-manager] setLinkDeviceId: unknown device id=${deviceId}`);
2041
- const { addonId: aid, stableId, meta: m } = persisted;
2042
- const key = deviceKey(aid, stableId);
2038
+ const { meta: m } = persisted;
2039
+ const key = String(deviceId);
2043
2040
  const allMeta = await readMeta();
2044
2041
  await settings.writeAddonStore({ deviceMeta: {
2045
2042
  ...allMeta,
@@ -2076,8 +2073,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2076
2073
  await withMetaWriteLock(async () => {
2077
2074
  const persisted = await resolvePersistedById(deviceId);
2078
2075
  if (!persisted) throw new Error(`[device-manager] setPrimaryChildEntityId: unknown device id=${deviceId}`);
2079
- const { addonId: aid, stableId, meta: m } = persisted;
2080
- const key = deviceKey(aid, stableId);
2076
+ const { meta: m } = persisted;
2077
+ const key = String(deviceId);
2081
2078
  const allMeta = await readMeta();
2082
2079
  await settings.writeAddonStore({ deviceMeta: {
2083
2080
  ...allMeta,
@@ -2113,8 +2110,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2113
2110
  await withMetaWriteLock(async () => {
2114
2111
  const persisted = await resolvePersistedById(deviceId);
2115
2112
  if (!persisted) throw new Error(`[device-manager] setChildLayout: unknown device id=${deviceId}`);
2116
- const { addonId: aid, stableId, meta: m } = persisted;
2117
- const key = deviceKey(aid, stableId);
2113
+ const { meta: m } = persisted;
2114
+ const key = String(deviceId);
2118
2115
  const allMeta = await readMeta();
2119
2116
  await settings.writeAddonStore({ deviceMeta: {
2120
2117
  ...allMeta,
@@ -2151,8 +2148,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2151
2148
  await withMetaWriteLock(async () => {
2152
2149
  const persisted = await resolvePersistedById(deviceId);
2153
2150
  if (!persisted) throw new Error(`[device-manager] setDeviceLinks: unknown device id=${deviceId}`);
2154
- const { addonId: aid, stableId, meta: m } = persisted;
2155
- const key = deviceKey(aid, stableId);
2151
+ const { meta: m } = persisted;
2152
+ const key = String(deviceId);
2156
2153
  const allMeta = await readMeta();
2157
2154
  await settings.writeAddonStore({ deviceMeta: {
2158
2155
  ...allMeta,
@@ -2191,8 +2188,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2191
2188
  await withMetaWriteLock(async () => {
2192
2189
  const persisted = await resolvePersistedById(deviceId);
2193
2190
  if (!persisted) throw new Error(`[device-manager] setRole: unknown device id=${deviceId}`);
2194
- const { addonId: aid, stableId, meta: m } = persisted;
2195
- const key = deviceKey(aid, stableId);
2191
+ const { meta: m } = persisted;
2192
+ const key = String(deviceId);
2196
2193
  const allMeta = await readMeta();
2197
2194
  await settings.writeAddonStore({ deviceMeta: {
2198
2195
  ...allMeta,
@@ -2238,8 +2235,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2238
2235
  await withMetaWriteLock(async () => {
2239
2236
  const persisted = await resolvePersistedById(deviceId);
2240
2237
  if (!persisted) throw new Error(`[device-manager] applyInitialMeta: unknown device id=${deviceId}`);
2241
- const { addonId: aid, stableId, meta: m } = persisted;
2242
- const key = deviceKey(aid, stableId);
2238
+ const { meta: m } = persisted;
2239
+ const key = String(deviceId);
2243
2240
  const allMeta = await readMeta();
2244
2241
  const merged = {
2245
2242
  ...m,
@@ -2289,10 +2286,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2289
2286
  setMetadata: async (input) => {
2290
2287
  const { deviceId, patch } = input;
2291
2288
  const result = await withMetaWriteLock(async () => {
2292
- const persisted = await resolvePersistedById(deviceId);
2293
- if (!persisted) throw new Error(`[device-manager] setMetadata: unknown device id=${deviceId}`);
2294
- const { addonId, stableId } = persisted;
2295
- const key = deviceKey(addonId, stableId);
2289
+ if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] setMetadata: unknown device id=${deviceId}`);
2290
+ const key = String(deviceId);
2296
2291
  const map = await readMetadataMap();
2297
2292
  const next = { ...map[key] ?? {} };
2298
2293
  let changed = false;
@@ -2343,8 +2338,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2343
2338
  await withMetaWriteLock(async () => {
2344
2339
  const persisted = await resolvePersistedById(deviceId);
2345
2340
  if (!persisted) throw new Error(`[device-manager] setDisabled: unknown device id=${deviceId}`);
2346
- const { addonId, stableId, meta: m } = persisted;
2347
- const key = deviceKey(addonId, stableId);
2341
+ const { meta: m } = persisted;
2342
+ const key = String(deviceId);
2348
2343
  const allMeta = await readMeta();
2349
2344
  await settings.writeAddonStore({ deviceMeta: {
2350
2345
  ...allMeta,
@@ -2461,8 +2456,11 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2461
2456
  listPersistedByAddon: async (input) => {
2462
2457
  const { addonId } = input;
2463
2458
  const [index, meta] = await Promise.all([readIndex(), readMeta()]);
2464
- return (index[addonId] ?? []).map((stableId) => {
2465
- const m = meta[deviceKey(addonId, stableId)];
2459
+ const stableIds = index[addonId] ?? [];
2460
+ const byStableId = /* @__PURE__ */ new Map();
2461
+ for (const m of Object.values(meta)) if (m.addonId === addonId) byStableId.set(m.stableId, m);
2462
+ return stableIds.map((stableId) => {
2463
+ const m = byStableId.get(stableId);
2466
2464
  return {
2467
2465
  id: m.id,
2468
2466
  stableId,
@@ -2486,7 +2484,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2486
2484
  device
2487
2485
  })) : registry.getAllWithAddonId();
2488
2486
  for (const { addonId: aid, device } of liveEntries) {
2489
- const key = deviceKey(aid, device.stableId);
2487
+ const key = String(device.id);
2490
2488
  const metadata = metadataMap[key] ?? null;
2491
2489
  const metaRow = meta[key] ?? null;
2492
2490
  results.push(toDeviceInfo(aid, device, metadata, metaRow));
@@ -2494,11 +2492,13 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2494
2492
  }
2495
2493
  }
2496
2494
  const index = await readIndex();
2495
+ const metaByAddonStable = /* @__PURE__ */ new Map();
2496
+ for (const m of Object.values(meta)) metaByAddonStable.set(`${m.addonId}${m.stableId}`, m);
2497
2497
  const targetAddons = addonId ? [addonId] : Object.keys(index);
2498
2498
  for (const aid of targetAddons) for (const stableId of index[aid] ?? []) {
2499
- const key = deviceKey(aid, stableId);
2499
+ const m = metaByAddonStable.get(`${aid}${stableId}`);
2500
+ const key = String(m.id);
2500
2501
  if (seen.has(key)) continue;
2501
- const m = meta[key];
2502
2502
  const persistedType = m.type;
2503
2503
  const persistedConfig = await settings.readDeviceStore(m.id);
2504
2504
  const metadata = metadataMap[key] ?? null;
@@ -2536,7 +2536,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2536
2536
  if (registry) {
2537
2537
  const found = resolveDeviceById(registry, deviceId);
2538
2538
  if (found) {
2539
- const key = deviceKey(found.addonId, found.device.stableId);
2539
+ const key = String(found.device.id);
2540
2540
  const [map, metaMap] = await Promise.all([readMetadataMap(), readMeta()]);
2541
2541
  const metadata = map[key] ?? null;
2542
2542
  const metaRow = metaMap[key] ?? null;
@@ -2547,7 +2547,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2547
2547
  if (!persisted) return null;
2548
2548
  const { addonId: aid, stableId, meta: m } = persisted;
2549
2549
  const persistedConfig = await settings.readDeviceStore(m.id);
2550
- const key = deviceKey(aid, stableId);
2550
+ const key = String(deviceId);
2551
2551
  const metadata = (await readMetadataMap())[key] ?? null;
2552
2552
  const sourceInfoGetDevice = buildSourceInfoFromConfig(persistedConfig ?? {}, stableId, aid);
2553
2553
  return {
@@ -2595,18 +2595,20 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2595
2595
  if (registry) {
2596
2596
  const liveChildren = registry.getChildren(parentDeviceId);
2597
2597
  for (const device of liveChildren) {
2598
- const key = deviceKey(ownerAddonId, device.stableId);
2598
+ const key = String(device.id);
2599
2599
  const metadata = metadataMap[key] ?? null;
2600
2600
  const metaRow = meta[key] ?? null;
2601
2601
  results.push(toDeviceInfo(ownerAddonId, device, metadata, metaRow));
2602
2602
  seen.add(key);
2603
2603
  }
2604
2604
  }
2605
- const persistedChildren = (index[ownerAddonId] ?? []).filter((sid) => meta[deviceKey(ownerAddonId, sid)]?.parentDeviceId === parentDeviceId);
2605
+ const ownerMetaByStableId = /* @__PURE__ */ new Map();
2606
+ for (const m of Object.values(meta)) if (m.addonId === ownerAddonId) ownerMetaByStableId.set(m.stableId, m);
2607
+ const persistedChildren = (index[ownerAddonId] ?? []).filter((sid) => ownerMetaByStableId.get(sid)?.parentDeviceId === parentDeviceId);
2606
2608
  for (const childStableId of persistedChildren) {
2607
- const key = deviceKey(ownerAddonId, childStableId);
2609
+ const m = ownerMetaByStableId.get(childStableId);
2610
+ const key = String(m.id);
2608
2611
  if (seen.has(key)) continue;
2609
- const m = meta[key];
2610
2612
  const persistedConfig = await settings.readDeviceStore(m.id);
2611
2613
  const metadata = metadataMap[key] ?? null;
2612
2614
  const sourceInfoChild = buildSourceInfoFromConfig(persistedConfig ?? {}, childStableId, ownerAddonId);
@@ -2712,14 +2714,6 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2712
2714
  },
2713
2715
  remove: async (input) => {
2714
2716
  const { deviceId } = input;
2715
- const directChildIds = async (parentId) => {
2716
- const ids = /* @__PURE__ */ new Set();
2717
- if (registry) for (const c of registry.getChildren(parentId)) ids.add(c.id);
2718
- const meta = await readMeta();
2719
- for (const m of Object.values(meta)) if (m.parentDeviceId === parentId) ids.add(m.id);
2720
- ids.delete(parentId);
2721
- return [...ids];
2722
- };
2723
2717
  const removeOne = async (id) => {
2724
2718
  if (registry) {
2725
2719
  const live = resolveDeviceById(registry, id);
@@ -2910,11 +2904,29 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2910
2904
  adoptionRelease: async ({ addonId, ...rest }) => {
2911
2905
  return (await this.requireDeviceAdoptionProvider(addonId)).release(rest);
2912
2906
  },
2913
- adoptionResync: async ({ camDeviceId }) => {
2907
+ adoptionResync: async ({ camDeviceId, resetToSource }) => {
2914
2908
  let owningAddonId = registry?.getAddonId(camDeviceId) ?? null;
2915
2909
  if (!owningAddonId) owningAddonId = (await resolvePersistedById(camDeviceId))?.addonId ?? null;
2916
2910
  if (!owningAddonId) throw new Error(`adoptionResync: device ${camDeviceId} not found`);
2917
- return (await this.requireDeviceAdoptionProvider(owningAddonId)).resync({ camDeviceId });
2911
+ let removedChildren = 0;
2912
+ if (resetToSource === true) {
2913
+ const childIds = await directChildIds(camDeviceId);
2914
+ for (const childId of childIds) {
2915
+ await provider.remove({ deviceId: childId });
2916
+ removedChildren += 1;
2917
+ }
2918
+ this.ctx.logger.info("resetToSource purge before resync", { tags: {
2919
+ deviceId: camDeviceId,
2920
+ removedChildren
2921
+ } });
2922
+ }
2923
+ return {
2924
+ ...await (await this.requireDeviceAdoptionProvider(owningAddonId)).resync({
2925
+ camDeviceId,
2926
+ resetToSource
2927
+ }),
2928
+ removedChildren
2929
+ };
2918
2930
  },
2919
2931
  testField: async (input) => {
2920
2932
  const { deviceId } = input;
@@ -234,7 +234,8 @@ function buildLinkIndexes(entries) {
234
234
  *
235
235
  * Addon store layout:
236
236
  * deviceIndex → Record<addonId, stableId[]> (which devices exist per addon)
237
- * deviceMeta → Record<"addonId:stableId", DeviceMeta> (type, name, parentDeviceId, id)
237
+ * deviceMeta → Record<numericId, DeviceMeta> (keyed by numeric device id;
238
+ * addonId + stableId are FIELDS on the record)
238
239
  *
239
240
  * Device store (per-device config):
240
241
  * readDeviceStore(numericDeviceId) → config blob
@@ -277,9 +278,6 @@ function shallowEqual(a, b) {
277
278
  for (const k of ak) if (a[k] !== b[k]) return false;
278
279
  return true;
279
280
  }
280
- function deviceKey(addonId, stableId) {
281
- return `${addonId}:${stableId}`;
282
- }
283
281
  /** Returns true when `x` is a non-null, non-array plain object. */
284
282
  function isRecord(x) {
285
283
  return x !== null && typeof x === "object" && !Array.isArray(x);
@@ -772,12 +770,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
772
770
  * numeric deviceId; this helper is display-only.
773
771
  */
774
772
  async lookupPersistedStableId(deviceId) {
775
- const meta = (await this.ctx.settings.readAddonStore()).deviceMeta ?? {};
776
- for (const [key, m] of Object.entries(meta)) if (m.id === deviceId) {
777
- const sep = key.indexOf(":");
778
- if (sep < 0) continue;
779
- return key.slice(sep + 1);
780
- }
773
+ return ((await this.ctx.settings.readAddonStore()).deviceMeta ?? {})[String(deviceId)]?.stableId;
781
774
  }
782
775
  async getDeviceAggregate(deviceId, kind) {
783
776
  const registry = this.capabilityRegistry;
@@ -1469,28 +1462,32 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1469
1462
  * Returns null when no device with that id is known to the hub.
1470
1463
  */
1471
1464
  const resolvePersistedById = async (deviceId) => {
1465
+ const m = (await readMeta())[String(deviceId)];
1466
+ if (!m) return null;
1467
+ return {
1468
+ addonId: m.addonId,
1469
+ stableId: m.stableId,
1470
+ meta: m
1471
+ };
1472
+ };
1473
+ /** Direct children of a device: the union of the live registry's children
1474
+ * and the persisted-meta scan (`parentDeviceId === parentId`), deduplicated
1475
+ * and excluding self. Shared by the `remove` cascade and the `resetToSource`
1476
+ * resync purge (#19). */
1477
+ const directChildIds = async (parentId) => {
1478
+ const ids = /* @__PURE__ */ new Set();
1479
+ if (registry) for (const c of registry.getChildren(parentId)) ids.add(c.id);
1472
1480
  const meta = await readMeta();
1473
- for (const [key, m] of Object.entries(meta)) if (m.id === deviceId) {
1474
- const sep = key.indexOf(":");
1475
- if (sep < 0) continue;
1476
- return {
1477
- addonId: key.slice(0, sep),
1478
- stableId: key.slice(sep + 1),
1479
- meta: m
1480
- };
1481
- }
1482
- return null;
1481
+ for (const m of Object.values(meta)) if (m.parentDeviceId === parentId) ids.add(m.id);
1482
+ ids.delete(parentId);
1483
+ return [...ids];
1483
1484
  };
1484
1485
  /** Resolve a link's `sourceKey` to a live device id: the sibling accessory
1485
1486
  * whose stableId is `${parentStableId}-${sourceKey}`. Null when absent.
1486
1487
  * Pure over a pre-read meta map so a multi-link resolve reads the store once. */
1487
1488
  const resolveSourceDeviceId = (parentStableId, sourceKey, meta) => {
1488
1489
  const wanted = `${parentStableId}-${sourceKey}`;
1489
- for (const [key, m] of Object.entries(meta)) {
1490
- const sep = key.indexOf(":");
1491
- if (sep < 0) continue;
1492
- if (key.slice(sep + 1) === wanted) return m.id;
1493
- }
1490
+ for (const m of Object.values(meta)) if (m.stableId === wanted) return m.id;
1494
1491
  return null;
1495
1492
  };
1496
1493
  /** Rebuild the `linkTargets` / `linkDependents` reverse-index maps from the
@@ -1508,12 +1505,9 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1508
1505
  const allMeta = await readMeta();
1509
1506
  const stableIdById = /* @__PURE__ */ new Map();
1510
1507
  const idByStableId = /* @__PURE__ */ new Map();
1511
- for (const [key, m] of Object.entries(allMeta)) {
1512
- const sep = key.indexOf(":");
1513
- if (sep < 0) continue;
1514
- const sid = key.slice(sep + 1);
1515
- stableIdById.set(m.id, sid);
1516
- idByStableId.set(sid, m.id);
1508
+ for (const m of Object.values(allMeta)) {
1509
+ stableIdById.set(m.id, m.stableId);
1510
+ idByStableId.set(m.stableId, m.id);
1517
1511
  }
1518
1512
  const entries = [];
1519
1513
  const expectedSources = /* @__PURE__ */ new Set();
@@ -1541,10 +1535,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1541
1535
  const idToAddonId = /* @__PURE__ */ new Map();
1542
1536
  {
1543
1537
  const meta = await readMeta();
1544
- for (const [key, m] of Object.entries(meta)) {
1545
- const sep = key.indexOf(":");
1546
- if (sep < 0) continue;
1547
- idToAddonId.set(m.id, key.slice(0, sep));
1538
+ for (const m of Object.values(meta)) {
1539
+ idToAddonId.set(m.id, m.addonId);
1548
1540
  if (Array.isArray(m.deviceLinks) && m.deviceLinks.length > 0) this.devicesWithLinks.add(m.id);
1549
1541
  }
1550
1542
  }
@@ -1558,8 +1550,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1558
1550
  await withMetaWriteLock(async () => {
1559
1551
  const persisted = await resolvePersistedById(deviceId);
1560
1552
  if (!persisted) throw new Error(`[device-manager] stampIntegrationId: unknown device id=${deviceId}`);
1561
- const { addonId: aid, stableId, meta: m } = persisted;
1562
- const key = deviceKey(aid, stableId);
1553
+ const { meta: m } = persisted;
1554
+ const key = String(deviceId);
1563
1555
  const allMeta = await readMeta();
1564
1556
  await settings.writeAddonStore({ deviceMeta: {
1565
1557
  ...allMeta,
@@ -1640,10 +1632,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1640
1632
  let containerStableId = persisted.stableId;
1641
1633
  const parentId = persisted.meta.parentDeviceId;
1642
1634
  if (parentId !== null) {
1643
- for (const [key, m] of Object.entries(allMeta)) if (m.id === parentId) {
1644
- containerStableId = key.slice(key.indexOf(":") + 1);
1645
- break;
1646
- }
1635
+ const parentMeta = allMeta[String(parentId)];
1636
+ if (parentMeta) containerStableId = parentMeta.stableId;
1647
1637
  }
1648
1638
  const resolved = [];
1649
1639
  for (const link of links) {
@@ -1683,15 +1673,16 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1683
1673
  * `registerDevice` completes. */
1684
1674
  allocateDeviceId: async (input) => {
1685
1675
  const { addonId, stableId } = input;
1686
- const key = deviceKey(addonId, stableId);
1687
1676
  return await withMetaWriteLock(async () => {
1688
1677
  const meta = await readMeta();
1689
- const existing = meta[key];
1678
+ const existing = Object.values(meta).find((m) => m.addonId === addonId && m.stableId === stableId);
1690
1679
  if (existing) return { id: existing.id };
1691
1680
  const id = await allocateNextDeviceId();
1692
1681
  await settings.writeAddonStore({ deviceMeta: {
1693
1682
  ...meta,
1694
- [key]: {
1683
+ [String(id)]: {
1684
+ addonId,
1685
+ stableId,
1695
1686
  type: "generic",
1696
1687
  name: stableId,
1697
1688
  location: null,
@@ -1705,7 +1696,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1705
1696
  },
1706
1697
  registerDevice: async (input) => {
1707
1698
  const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input;
1708
- const key = deviceKey(addonId, stableId);
1699
+ const key = String(id);
1709
1700
  const featuresArr = Array.isArray(features) ? [...features] : [];
1710
1701
  const { isFirstRegistration, exportFingerprint, fingerprintChanged } = await withMetaWriteLock(async () => {
1711
1702
  const index = await readIndex();
@@ -1717,6 +1708,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1717
1708
  } });
1718
1709
  const meta = await readMeta();
1719
1710
  const existingMeta = meta[key];
1711
+ const wasUserNamed = existingMeta?.userNamed ?? (existingMeta !== void 0 && existingMeta.name !== stableId);
1712
+ const resolvedName = wasUserNamed && existingMeta !== void 0 ? existingMeta.name : name;
1720
1713
  const isFirst = !existingMeta || !wasInIndex;
1721
1714
  const fingerprint = canonicalDeviceFingerprint({
1722
1715
  deviceId: id,
@@ -1726,8 +1719,11 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1726
1719
  await settings.writeAddonStore({ deviceMeta: {
1727
1720
  ...meta,
1728
1721
  [key]: {
1722
+ addonId,
1723
+ stableId,
1729
1724
  type,
1730
- name: existingMeta && existingMeta.name !== stableId ? existingMeta.name : name,
1725
+ name: resolvedName,
1726
+ userNamed: wasUserNamed,
1731
1727
  location: existingMeta?.location ?? null,
1732
1728
  disabled: existingMeta?.disabled ?? false,
1733
1729
  ...existingMeta?.integrationId !== void 0 ? { integrationId: existingMeta.integrationId } : {},
@@ -1802,7 +1798,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1802
1798
  const persisted = await resolvePersistedById(deviceId);
1803
1799
  if (!persisted) return;
1804
1800
  const { addonId, stableId, meta: persistedMeta } = persisted;
1805
- const key = deviceKey(addonId, stableId);
1801
+ const key = String(deviceId);
1806
1802
  const deviceName = persistedMeta.name;
1807
1803
  await withMetaWriteLock(async () => {
1808
1804
  const index = await readIndex();
@@ -1883,7 +1879,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1883
1879
  const persisted = await resolvePersistedById(deviceId);
1884
1880
  if (!persisted) return null;
1885
1881
  const { addonId, stableId, meta: m } = persisted;
1886
- const key = deviceKey(addonId, stableId);
1882
+ const key = String(deviceId);
1887
1883
  const metadata = (await readMetadataMap())[key] ?? null;
1888
1884
  return {
1889
1885
  id: m.id,
@@ -1912,14 +1908,15 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1912
1908
  await withMetaWriteLock(async () => {
1913
1909
  const persisted = await resolvePersistedById(deviceId);
1914
1910
  if (!persisted) throw new Error(`[device-manager] setName: unknown device id=${deviceId}`);
1915
- const { addonId, stableId, meta: m } = persisted;
1916
- const key = deviceKey(addonId, stableId);
1911
+ const { meta: m } = persisted;
1912
+ const key = String(deviceId);
1917
1913
  const allMeta = await readMeta();
1918
1914
  await settings.writeAddonStore({ deviceMeta: {
1919
1915
  ...allMeta,
1920
1916
  [key]: {
1921
1917
  ...m,
1922
- name
1918
+ name,
1919
+ userNamed: true
1923
1920
  }
1924
1921
  } });
1925
1922
  });
@@ -1949,8 +1946,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1949
1946
  await withMetaWriteLock(async () => {
1950
1947
  const persisted = await resolvePersistedById(deviceId);
1951
1948
  if (!persisted) throw new Error(`[device-manager] setLocation: unknown device id=${deviceId}`);
1952
- const { addonId, stableId, meta: m } = persisted;
1953
- const key = deviceKey(addonId, stableId);
1949
+ const { meta: m } = persisted;
1950
+ const key = String(deviceId);
1954
1951
  const allMeta = await readMeta();
1955
1952
  await settings.writeAddonStore({ deviceMeta: {
1956
1953
  ...allMeta,
@@ -1987,8 +1984,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1987
1984
  await withMetaWriteLock(async () => {
1988
1985
  const persisted = await resolvePersistedById(deviceId);
1989
1986
  if (!persisted) throw new Error(`[device-manager] setType: unknown device id=${deviceId}`);
1990
- const { addonId, stableId, meta: m } = persisted;
1991
- const key = deviceKey(addonId, stableId);
1987
+ const { meta: m } = persisted;
1988
+ const key = String(deviceId);
1992
1989
  const allMeta = await readMeta();
1993
1990
  await settings.writeAddonStore({ deviceMeta: {
1994
1991
  ...allMeta,
@@ -2033,8 +2030,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2033
2030
  await withMetaWriteLock(async () => {
2034
2031
  const persisted = await resolvePersistedById(deviceId);
2035
2032
  if (!persisted) throw new Error(`[device-manager] setLinkDeviceId: unknown device id=${deviceId}`);
2036
- const { addonId: aid, stableId, meta: m } = persisted;
2037
- const key = deviceKey(aid, stableId);
2033
+ const { meta: m } = persisted;
2034
+ const key = String(deviceId);
2038
2035
  const allMeta = await readMeta();
2039
2036
  await settings.writeAddonStore({ deviceMeta: {
2040
2037
  ...allMeta,
@@ -2071,8 +2068,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2071
2068
  await withMetaWriteLock(async () => {
2072
2069
  const persisted = await resolvePersistedById(deviceId);
2073
2070
  if (!persisted) throw new Error(`[device-manager] setPrimaryChildEntityId: unknown device id=${deviceId}`);
2074
- const { addonId: aid, stableId, meta: m } = persisted;
2075
- const key = deviceKey(aid, stableId);
2071
+ const { meta: m } = persisted;
2072
+ const key = String(deviceId);
2076
2073
  const allMeta = await readMeta();
2077
2074
  await settings.writeAddonStore({ deviceMeta: {
2078
2075
  ...allMeta,
@@ -2108,8 +2105,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2108
2105
  await withMetaWriteLock(async () => {
2109
2106
  const persisted = await resolvePersistedById(deviceId);
2110
2107
  if (!persisted) throw new Error(`[device-manager] setChildLayout: unknown device id=${deviceId}`);
2111
- const { addonId: aid, stableId, meta: m } = persisted;
2112
- const key = deviceKey(aid, stableId);
2108
+ const { meta: m } = persisted;
2109
+ const key = String(deviceId);
2113
2110
  const allMeta = await readMeta();
2114
2111
  await settings.writeAddonStore({ deviceMeta: {
2115
2112
  ...allMeta,
@@ -2146,8 +2143,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2146
2143
  await withMetaWriteLock(async () => {
2147
2144
  const persisted = await resolvePersistedById(deviceId);
2148
2145
  if (!persisted) throw new Error(`[device-manager] setDeviceLinks: unknown device id=${deviceId}`);
2149
- const { addonId: aid, stableId, meta: m } = persisted;
2150
- const key = deviceKey(aid, stableId);
2146
+ const { meta: m } = persisted;
2147
+ const key = String(deviceId);
2151
2148
  const allMeta = await readMeta();
2152
2149
  await settings.writeAddonStore({ deviceMeta: {
2153
2150
  ...allMeta,
@@ -2186,8 +2183,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2186
2183
  await withMetaWriteLock(async () => {
2187
2184
  const persisted = await resolvePersistedById(deviceId);
2188
2185
  if (!persisted) throw new Error(`[device-manager] setRole: unknown device id=${deviceId}`);
2189
- const { addonId: aid, stableId, meta: m } = persisted;
2190
- const key = deviceKey(aid, stableId);
2186
+ const { meta: m } = persisted;
2187
+ const key = String(deviceId);
2191
2188
  const allMeta = await readMeta();
2192
2189
  await settings.writeAddonStore({ deviceMeta: {
2193
2190
  ...allMeta,
@@ -2233,8 +2230,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2233
2230
  await withMetaWriteLock(async () => {
2234
2231
  const persisted = await resolvePersistedById(deviceId);
2235
2232
  if (!persisted) throw new Error(`[device-manager] applyInitialMeta: unknown device id=${deviceId}`);
2236
- const { addonId: aid, stableId, meta: m } = persisted;
2237
- const key = deviceKey(aid, stableId);
2233
+ const { meta: m } = persisted;
2234
+ const key = String(deviceId);
2238
2235
  const allMeta = await readMeta();
2239
2236
  const merged = {
2240
2237
  ...m,
@@ -2284,10 +2281,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2284
2281
  setMetadata: async (input) => {
2285
2282
  const { deviceId, patch } = input;
2286
2283
  const result = await withMetaWriteLock(async () => {
2287
- const persisted = await resolvePersistedById(deviceId);
2288
- if (!persisted) throw new Error(`[device-manager] setMetadata: unknown device id=${deviceId}`);
2289
- const { addonId, stableId } = persisted;
2290
- const key = deviceKey(addonId, stableId);
2284
+ if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] setMetadata: unknown device id=${deviceId}`);
2285
+ const key = String(deviceId);
2291
2286
  const map = await readMetadataMap();
2292
2287
  const next = { ...map[key] ?? {} };
2293
2288
  let changed = false;
@@ -2338,8 +2333,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2338
2333
  await withMetaWriteLock(async () => {
2339
2334
  const persisted = await resolvePersistedById(deviceId);
2340
2335
  if (!persisted) throw new Error(`[device-manager] setDisabled: unknown device id=${deviceId}`);
2341
- const { addonId, stableId, meta: m } = persisted;
2342
- const key = deviceKey(addonId, stableId);
2336
+ const { meta: m } = persisted;
2337
+ const key = String(deviceId);
2343
2338
  const allMeta = await readMeta();
2344
2339
  await settings.writeAddonStore({ deviceMeta: {
2345
2340
  ...allMeta,
@@ -2456,8 +2451,11 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2456
2451
  listPersistedByAddon: async (input) => {
2457
2452
  const { addonId } = input;
2458
2453
  const [index, meta] = await Promise.all([readIndex(), readMeta()]);
2459
- return (index[addonId] ?? []).map((stableId) => {
2460
- const m = meta[deviceKey(addonId, stableId)];
2454
+ const stableIds = index[addonId] ?? [];
2455
+ const byStableId = /* @__PURE__ */ new Map();
2456
+ for (const m of Object.values(meta)) if (m.addonId === addonId) byStableId.set(m.stableId, m);
2457
+ return stableIds.map((stableId) => {
2458
+ const m = byStableId.get(stableId);
2461
2459
  return {
2462
2460
  id: m.id,
2463
2461
  stableId,
@@ -2481,7 +2479,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2481
2479
  device
2482
2480
  })) : registry.getAllWithAddonId();
2483
2481
  for (const { addonId: aid, device } of liveEntries) {
2484
- const key = deviceKey(aid, device.stableId);
2482
+ const key = String(device.id);
2485
2483
  const metadata = metadataMap[key] ?? null;
2486
2484
  const metaRow = meta[key] ?? null;
2487
2485
  results.push(toDeviceInfo(aid, device, metadata, metaRow));
@@ -2489,11 +2487,13 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2489
2487
  }
2490
2488
  }
2491
2489
  const index = await readIndex();
2490
+ const metaByAddonStable = /* @__PURE__ */ new Map();
2491
+ for (const m of Object.values(meta)) metaByAddonStable.set(`${m.addonId}${m.stableId}`, m);
2492
2492
  const targetAddons = addonId ? [addonId] : Object.keys(index);
2493
2493
  for (const aid of targetAddons) for (const stableId of index[aid] ?? []) {
2494
- const key = deviceKey(aid, stableId);
2494
+ const m = metaByAddonStable.get(`${aid}${stableId}`);
2495
+ const key = String(m.id);
2495
2496
  if (seen.has(key)) continue;
2496
- const m = meta[key];
2497
2497
  const persistedType = m.type;
2498
2498
  const persistedConfig = await settings.readDeviceStore(m.id);
2499
2499
  const metadata = metadataMap[key] ?? null;
@@ -2531,7 +2531,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2531
2531
  if (registry) {
2532
2532
  const found = resolveDeviceById(registry, deviceId);
2533
2533
  if (found) {
2534
- const key = deviceKey(found.addonId, found.device.stableId);
2534
+ const key = String(found.device.id);
2535
2535
  const [map, metaMap] = await Promise.all([readMetadataMap(), readMeta()]);
2536
2536
  const metadata = map[key] ?? null;
2537
2537
  const metaRow = metaMap[key] ?? null;
@@ -2542,7 +2542,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2542
2542
  if (!persisted) return null;
2543
2543
  const { addonId: aid, stableId, meta: m } = persisted;
2544
2544
  const persistedConfig = await settings.readDeviceStore(m.id);
2545
- const key = deviceKey(aid, stableId);
2545
+ const key = String(deviceId);
2546
2546
  const metadata = (await readMetadataMap())[key] ?? null;
2547
2547
  const sourceInfoGetDevice = buildSourceInfoFromConfig(persistedConfig ?? {}, stableId, aid);
2548
2548
  return {
@@ -2590,18 +2590,20 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2590
2590
  if (registry) {
2591
2591
  const liveChildren = registry.getChildren(parentDeviceId);
2592
2592
  for (const device of liveChildren) {
2593
- const key = deviceKey(ownerAddonId, device.stableId);
2593
+ const key = String(device.id);
2594
2594
  const metadata = metadataMap[key] ?? null;
2595
2595
  const metaRow = meta[key] ?? null;
2596
2596
  results.push(toDeviceInfo(ownerAddonId, device, metadata, metaRow));
2597
2597
  seen.add(key);
2598
2598
  }
2599
2599
  }
2600
- const persistedChildren = (index[ownerAddonId] ?? []).filter((sid) => meta[deviceKey(ownerAddonId, sid)]?.parentDeviceId === parentDeviceId);
2600
+ const ownerMetaByStableId = /* @__PURE__ */ new Map();
2601
+ for (const m of Object.values(meta)) if (m.addonId === ownerAddonId) ownerMetaByStableId.set(m.stableId, m);
2602
+ const persistedChildren = (index[ownerAddonId] ?? []).filter((sid) => ownerMetaByStableId.get(sid)?.parentDeviceId === parentDeviceId);
2601
2603
  for (const childStableId of persistedChildren) {
2602
- const key = deviceKey(ownerAddonId, childStableId);
2604
+ const m = ownerMetaByStableId.get(childStableId);
2605
+ const key = String(m.id);
2603
2606
  if (seen.has(key)) continue;
2604
- const m = meta[key];
2605
2607
  const persistedConfig = await settings.readDeviceStore(m.id);
2606
2608
  const metadata = metadataMap[key] ?? null;
2607
2609
  const sourceInfoChild = buildSourceInfoFromConfig(persistedConfig ?? {}, childStableId, ownerAddonId);
@@ -2707,14 +2709,6 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2707
2709
  },
2708
2710
  remove: async (input) => {
2709
2711
  const { deviceId } = input;
2710
- const directChildIds = async (parentId) => {
2711
- const ids = /* @__PURE__ */ new Set();
2712
- if (registry) for (const c of registry.getChildren(parentId)) ids.add(c.id);
2713
- const meta = await readMeta();
2714
- for (const m of Object.values(meta)) if (m.parentDeviceId === parentId) ids.add(m.id);
2715
- ids.delete(parentId);
2716
- return [...ids];
2717
- };
2718
2712
  const removeOne = async (id) => {
2719
2713
  if (registry) {
2720
2714
  const live = resolveDeviceById(registry, id);
@@ -2905,11 +2899,29 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2905
2899
  adoptionRelease: async ({ addonId, ...rest }) => {
2906
2900
  return (await this.requireDeviceAdoptionProvider(addonId)).release(rest);
2907
2901
  },
2908
- adoptionResync: async ({ camDeviceId }) => {
2902
+ adoptionResync: async ({ camDeviceId, resetToSource }) => {
2909
2903
  let owningAddonId = registry?.getAddonId(camDeviceId) ?? null;
2910
2904
  if (!owningAddonId) owningAddonId = (await resolvePersistedById(camDeviceId))?.addonId ?? null;
2911
2905
  if (!owningAddonId) throw new Error(`adoptionResync: device ${camDeviceId} not found`);
2912
- return (await this.requireDeviceAdoptionProvider(owningAddonId)).resync({ camDeviceId });
2906
+ let removedChildren = 0;
2907
+ if (resetToSource === true) {
2908
+ const childIds = await directChildIds(camDeviceId);
2909
+ for (const childId of childIds) {
2910
+ await provider.remove({ deviceId: childId });
2911
+ removedChildren += 1;
2912
+ }
2913
+ this.ctx.logger.info("resetToSource purge before resync", { tags: {
2914
+ deviceId: camDeviceId,
2915
+ removedChildren
2916
+ } });
2917
+ }
2918
+ return {
2919
+ ...await (await this.requireDeviceAdoptionProvider(owningAddonId)).resync({
2920
+ camDeviceId,
2921
+ resetToSource
2922
+ }),
2923
+ removedChildren
2924
+ };
2913
2925
  },
2914
2926
  testField: async (input) => {
2915
2927
  const { deviceId } = input;
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ const require_builtins_local_auth_local_auth_addon = require("./builtins/local-a
20
20
  require("./builtins/local-auth/index.js");
21
21
  const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
22
22
  require("./builtins/device-manager/index.js");
23
- const require_manifest_python_deps = require("./manifest-python-deps-Ba_eOEPT.js");
23
+ const require_manifest_python_deps = require("./manifest-python-deps-Cro19O4u.js");
24
24
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
25
25
  let _camstack_types_node = require("@camstack/types/node");
26
26
  let node_http = require("node:http");
package/dist/index.mjs CHANGED
@@ -18,7 +18,7 @@ import { LocalAuthAddon, a as require_ms, c as __esmMin, d as __toCommonJS, f as
18
18
  import "./builtins/local-auth/index.mjs";
19
19
  import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
20
20
  import "./builtins/device-manager/index.mjs";
21
- import { A as createUdsLoggerWithControl, B as createLocalTransport, C as ipcParentLink, D as createUdsEventBus, E as createUdsEventBridge, F as AGENT_CAP_FWD_SERVICE, G as FrameDecoder, H as UdsLocalTransportServer, I as CapRouteResolver, J as buildUdsNativeCapProxy, K as encodeFrame, L as CapRouteError, M as LocalChildRegistry, N as UDS_NO_ROUTE_PREFIX, O as udsChildLogToWorkerEntry, P as AGENT_CAP_FWD_ACTION, Q as mountNativeCapService, R as classifyCapRoute, S as ipcChildLink, T as createParentUnownedCallHandler, U as SocketChannel, V as UdsLocalTransportClient, W as localEndpointPath, Y as createBrokerDeviceManagerApi, _ as __resetCapUsageRegistryForTests, a as getWorkerDeviceRegistry, at as capBareAction, b as brokerTransportLink, c as setHubConnected, ct as deserializeTypedArrays, d as registerEventBusService, dt as CapabilityHandle, et as createAddonService, f as AddonDepsManager, ft as CapabilityUnavailableError, g as CapUsageRegistry, h as createHwAccelService, i as createUdsAddonContext, it as capActionSuffix, j as LocalChildClient, k as createUdsLogger, l as EVENT_TOPIC_PREFIX, lt as serializeTypedArrays, m as resolveHwAccel, mt as resolveAddonClass, n as adaptBrokerToCluster, nt as NATIVE_PROVIDER_SERVICE_INFIX, o as getOrInitReadinessRegistry, ot as capServiceName, p as createKernelHwAccel, pt as installManifestNativeDeps, q as buildNativeCapProxy, r as createAddonContext, rt as capActionName, s as getOrInitReadinessRegistryForClient, st as parseCapAction, t as installManifestPythonDeps, tt as validateProviderRegistrations, u as getBrokerEventBus, ut as DeviceRegistry, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as callWithServiceDiscovery } from "./manifest-python-deps-BqId8WrC.mjs";
21
+ import { A as createUdsLoggerWithControl, B as createLocalTransport, C as ipcParentLink, D as createUdsEventBus, E as createUdsEventBridge, F as AGENT_CAP_FWD_SERVICE, G as FrameDecoder, H as UdsLocalTransportServer, I as CapRouteResolver, J as buildUdsNativeCapProxy, K as encodeFrame, L as CapRouteError, M as LocalChildRegistry, N as UDS_NO_ROUTE_PREFIX, O as udsChildLogToWorkerEntry, P as AGENT_CAP_FWD_ACTION, Q as mountNativeCapService, R as classifyCapRoute, S as ipcChildLink, T as createParentUnownedCallHandler, U as SocketChannel, V as UdsLocalTransportClient, W as localEndpointPath, Y as createBrokerDeviceManagerApi, _ as __resetCapUsageRegistryForTests, a as getWorkerDeviceRegistry, at as capBareAction, b as brokerTransportLink, c as setHubConnected, ct as deserializeTypedArrays, d as registerEventBusService, dt as CapabilityHandle, et as createAddonService, f as AddonDepsManager, ft as CapabilityUnavailableError, g as CapUsageRegistry, h as createHwAccelService, i as createUdsAddonContext, it as capActionSuffix, j as LocalChildClient, k as createUdsLogger, l as EVENT_TOPIC_PREFIX, lt as serializeTypedArrays, m as resolveHwAccel, mt as resolveAddonClass, n as adaptBrokerToCluster, nt as NATIVE_PROVIDER_SERVICE_INFIX, o as getOrInitReadinessRegistry, ot as capServiceName, p as createKernelHwAccel, pt as installManifestNativeDeps, q as buildNativeCapProxy, r as createAddonContext, rt as capActionName, s as getOrInitReadinessRegistryForClient, st as parseCapAction, t as installManifestPythonDeps, tt as validateProviderRegistrations, u as getBrokerEventBus, ut as DeviceRegistry, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as callWithServiceDiscovery } from "./manifest-python-deps-1zTy3vXe.mjs";
22
22
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
23
23
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
24
24
  import { request } from "node:http";
@@ -1288,11 +1288,19 @@ function createBrokerDeviceManagerApi(opts) {
1288
1288
  deviceId: id,
1289
1289
  data: spec.config
1290
1290
  }).catch(() => {});
1291
- const [initialRuntimeState, persistedConfig, deviceMeta] = await Promise.all([
1291
+ const [initialRuntimeState, loadedConfig, deviceMeta] = await Promise.all([
1292
1292
  callDeviceManager(api, "loadRuntimeState", { deviceId: id }).catch(() => empty),
1293
1293
  callDeviceManager(api, "loadConfig", { deviceId: id }).catch(() => empty),
1294
1294
  callDeviceManager(api, "loadMeta", { deviceId: id }).catch(() => null)
1295
1295
  ]);
1296
+ let persistedConfig = loadedConfig;
1297
+ if (!isFirstSpawn && Object.keys(loadedConfig).length === 0 && Object.keys(spec.config).length > 0) {
1298
+ await callDeviceManager(api, "persistConfig", {
1299
+ deviceId: id,
1300
+ data: spec.config
1301
+ }).catch(() => {});
1302
+ persistedConfig = spec.config;
1303
+ }
1296
1304
  const childCtx = buildContext(childStableId, id, parent.id, initialRuntimeState, persistedConfig, deviceMeta);
1297
1305
  const child = spec.factory(childCtx);
1298
1306
  await deviceApi.register(child);
@@ -1290,11 +1290,19 @@ function createBrokerDeviceManagerApi(opts) {
1290
1290
  deviceId: id,
1291
1291
  data: spec.config
1292
1292
  }).catch(() => {});
1293
- const [initialRuntimeState, persistedConfig, deviceMeta] = await Promise.all([
1293
+ const [initialRuntimeState, loadedConfig, deviceMeta] = await Promise.all([
1294
1294
  callDeviceManager(api, "loadRuntimeState", { deviceId: id }).catch(() => empty),
1295
1295
  callDeviceManager(api, "loadConfig", { deviceId: id }).catch(() => empty),
1296
1296
  callDeviceManager(api, "loadMeta", { deviceId: id }).catch(() => null)
1297
1297
  ]);
1298
+ let persistedConfig = loadedConfig;
1299
+ if (!isFirstSpawn && Object.keys(loadedConfig).length === 0 && Object.keys(spec.config).length > 0) {
1300
+ await callDeviceManager(api, "persistConfig", {
1301
+ deviceId: id,
1302
+ data: spec.config
1303
+ }).catch(() => {});
1304
+ persistedConfig = spec.config;
1305
+ }
1298
1306
  const childCtx = buildContext(childStableId, id, parent.id, initialRuntimeState, persistedConfig, deviceMeta);
1299
1307
  const child = spec.factory(childCtx);
1300
1308
  await deviceApi.register(child);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",