@camstack/server 1.2.702 → 1.2.704

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.704] - 2026-10-30
11
+
12
+ ### Changed
13
+
14
+ - Rebuilt against updated dependencies.
15
+
16
+ ## [1.2.703] - 2026-10-30
17
+
18
+ ### Added
19
+
20
+ - The hub exposes the network scan engine, saved scan subnets and discovery-mode classification calls
21
+
22
+ ### Changed
23
+
24
+ - Integration settings accept an owner-only skipRestart so an addon can save its own settings without a respawn
25
+ - An updated addon shows its new manifest (icon, name, capabilities) without restarting the hub
26
+ - A staged addon update that fails to swap now fails its update task instead of reporting it done
27
+ - Hub: settings dispatch can ask an addon which keys it still declares and remove a stale one
28
+
10
29
  ## [1.2.702] - 2026-10-30
11
30
 
12
31
  ### Changed
@@ -1212,6 +1212,19 @@ function buildIntegrationsProvider(ar, eb, loggingService, capabilityRegistry) {
1212
1212
  // The integration's own addon writing back is not the operator.
1213
1213
  operatorWrite: typeof Reflect.get(input, 'callerAddonId') !== 'string',
1214
1214
  });
1215
+ // Only the integration's own addon may skip the respawn: it reconciles
1216
+ // the change in-process, so an unrelated caller cannot leave an addon
1217
+ // running on settings it never reloaded.
1218
+ const callerAddon = Reflect.get(input, 'callerAddonId');
1219
+ if (input.skipRestart === true &&
1220
+ typeof callerAddon === 'string' &&
1221
+ (callerAddon === integration.addonId || ar.runnerIdFor(integration.addonId) === callerAddon)) {
1222
+ logger.info('persisted — the owner applies it in-process, no restart', {
1223
+ tags: { addonId: integration.addonId },
1224
+ meta: { phase: 'setSettings' },
1225
+ });
1226
+ return { success: true };
1227
+ }
1215
1228
  logger.info('persisted — restarting addon', {
1216
1229
  tags: { addonId: integration.addonId },
1217
1230
  meta: { phase: 'setSettings' },
@@ -75,7 +75,22 @@ async function readIntegrationSettings(deps, input) {
75
75
  // a marker becomes its "set" view, and so does a suspect plaintext.
76
76
  const out = {};
77
77
  for (const [key, value] of Object.entries(stored)) {
78
- out[key] = (0, types_1.isSecretMarker)(value) || suspects.has(key) ? (0, types_1.toSecretFieldView)(value) : value;
78
+ out[key] = suspects.has(key) ? (0, types_1.toSecretFieldView)(value) : toFormView(value);
79
79
  }
80
80
  return out;
81
81
  }
82
+ /**
83
+ * A marker anywhere in a stored value becomes its "set" view: a secret inside
84
+ * an array row (`boards[id].encryptionKey`) is stored as a marker in the row,
85
+ * and the admin form must not receive the marker itself.
86
+ */
87
+ function toFormView(value) {
88
+ if ((0, types_1.isSecretMarker)(value))
89
+ return (0, types_1.toSecretFieldView)(value);
90
+ if (Array.isArray(value))
91
+ return value.map(toFormView);
92
+ if (typeof value === 'object' && value !== null) {
93
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toFormView(v)]));
94
+ }
95
+ return value;
96
+ }
@@ -254,6 +254,8 @@ function mountAllCaps(services) {
254
254
  ...first,
255
255
  getDevices: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'getDevices'),
256
256
  discoverDevices: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'discoverDevices'),
257
+ getDiscoveryModes: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'getDiscoveryModes'),
258
+ classifyDiscoveryHits: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'classifyDiscoveryHits'),
257
259
  };
258
260
  }, remoteCapProxy),
259
261
  deviceRemovalHook: (0, generated_cap_routers_js_1.createCapRouter_deviceRemovalHook)((_ctx, addonId) => {
@@ -648,6 +648,22 @@ function createCapRouter_addonSettings(getProvider, _createRemoteProxy) {
648
648
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
649
649
  return p.getIntegrationSettings(input);
650
650
  }),
651
+ getDeclaredSettingsKeys: trpc_middleware_js_1.adminProcedure
652
+ .input(types_10.addonSettingsCapability.methods.getDeclaredSettingsKeys.input.loose())
653
+ .output(types_10.addonSettingsCapability.methods.getDeclaredSettingsKeys.output)
654
+ .query(async ({ input, ctx }) => {
655
+ const p = requireCapProvider('addon-settings', () => getProvider(ctx));
656
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
657
+ return p.getDeclaredSettingsKeys(input);
658
+ }),
659
+ unsetStoredSettingsKeys: trpc_middleware_js_1.adminProcedure
660
+ .input(types_10.addonSettingsCapability.methods.unsetStoredSettingsKeys.input.loose())
661
+ .output(types_10.addonSettingsCapability.methods.unsetStoredSettingsKeys.output)
662
+ .mutation(async ({ input, ctx }) => {
663
+ const p = requireCapProvider('addon-settings', () => getProvider(ctx));
664
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
665
+ return p.unsetStoredSettingsKeys(input);
666
+ }),
651
667
  getDeviceSettings: trpc_middleware_js_1.protectedProcedure
652
668
  .input(types_10.addonSettingsCapability.methods.getDeviceSettings.input.loose())
653
669
  .output(types_10.addonSettingsCapability.methods.getDeviceSettings.output)
@@ -4361,6 +4377,24 @@ function createCapRouter_deviceProvider(getProvider, createRemoteProxy) {
4361
4377
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4362
4378
  return p.discoverDevices(methodInput);
4363
4379
  }),
4380
+ getDiscoveryModes: trpc_middleware_js_1.protectedProcedure
4381
+ .input(types_55.deviceProviderCapability.methods.getDiscoveryModes.input.loose())
4382
+ .output(types_55.deviceProviderCapability.methods.getDiscoveryModes.output)
4383
+ .query(async ({ input, ctx }) => {
4384
+ const { nodeId, addonId, ...methodInput } = input;
4385
+ const p = resolveProvider('device-provider', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
4386
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4387
+ return p.getDiscoveryModes(methodInput);
4388
+ }),
4389
+ classifyDiscoveryHits: trpc_middleware_js_1.adminProcedure
4390
+ .input(types_55.deviceProviderCapability.methods.classifyDiscoveryHits.input.loose())
4391
+ .output(types_55.deviceProviderCapability.methods.classifyDiscoveryHits.output)
4392
+ .mutation(async ({ input, ctx }) => {
4393
+ const { nodeId, addonId, ...methodInput } = input;
4394
+ const p = resolveProvider('device-provider', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
4395
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4396
+ return p.classifyDiscoveryHits(methodInput);
4397
+ }),
4364
4398
  getDiscoveryParamsSchema: trpc_middleware_js_1.protectedProcedure
4365
4399
  .input(types_55.deviceProviderCapability.methods.getDiscoveryParamsSchema.input.loose())
4366
4400
  .output(types_55.deviceProviderCapability.methods.getDiscoveryParamsSchema.output)
@@ -5433,6 +5467,16 @@ function createCapRouter_keychain(getProvider, createRemoteProxy) {
5433
5467
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5434
5468
  return p.deleteDeviceScope(methodInput);
5435
5469
  }),
5470
+ releaseRecordRefs: trpc_middleware_js_1.adminProcedure
5471
+ .input(types_70.keychainCapability.methods.releaseRecordRefs.input.loose())
5472
+ .output(types_70.keychainCapability.methods.releaseRecordRefs.output)
5473
+ .mutation(async ({ input, ctx }) => {
5474
+ const parsedInput = ctx.req !== undefined ? (0, types_3.withoutCallerStamp)(input) : input;
5475
+ const { nodeId, ...methodInput } = parsedInput;
5476
+ const p = resolveProvider('keychain', nodeId, () => getProvider(ctx), createRemoteProxy);
5477
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5478
+ return p.releaseRecordRefs(methodInput);
5479
+ }),
5436
5480
  listMeta: trpc_middleware_js_1.adminProcedure
5437
5481
  .input(types_70.keychainCapability.methods.listMeta.input.loose())
5438
5482
  .output(types_70.keychainCapability.methods.listMeta.output)
@@ -6070,6 +6114,31 @@ function createCapRouter_localNetwork(getProvider, createRemoteProxy) {
6070
6114
  const p = resolveProvider('local-network', input?.nodeId, () => getProvider(ctx), createRemoteProxy);
6071
6115
  return p.useStoredUploadedCertificate();
6072
6116
  }),
6117
+ scanNetwork: trpc_middleware_js_1.adminProcedure
6118
+ .input(types_76.localNetworkCapability.methods.scanNetwork.input.loose())
6119
+ .output(types_76.localNetworkCapability.methods.scanNetwork.output)
6120
+ .mutation(async ({ input, ctx }) => {
6121
+ const { nodeId, ...methodInput } = input;
6122
+ const p = resolveProvider('local-network', nodeId, () => getProvider(ctx), createRemoteProxy);
6123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6124
+ return p.scanNetwork(methodInput);
6125
+ }),
6126
+ getScanSubnets: trpc_middleware_js_1.adminProcedure
6127
+ .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
6128
+ .output(types_76.localNetworkCapability.methods.getScanSubnets.output)
6129
+ .query(async ({ input, ctx }) => {
6130
+ const p = resolveProvider('local-network', input?.nodeId, () => getProvider(ctx), createRemoteProxy);
6131
+ return p.getScanSubnets();
6132
+ }),
6133
+ setScanSubnets: trpc_middleware_js_1.adminProcedure
6134
+ .input(types_76.localNetworkCapability.methods.setScanSubnets.input.loose())
6135
+ .output(types_76.localNetworkCapability.methods.setScanSubnets.output)
6136
+ .mutation(async ({ input, ctx }) => {
6137
+ const { nodeId, ...methodInput } = input;
6138
+ const p = resolveProvider('local-network', nodeId, () => getProvider(ctx), createRemoteProxy);
6139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6140
+ return p.setScanSubnets(methodInput);
6141
+ }),
6073
6142
  revertToGeneratedCertificate: trpc_middleware_js_1.adminProcedure
6074
6143
  .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
6075
6144
  .output(types_76.localNetworkCapability.methods.revertToGeneratedCertificate.output)
@@ -11616,6 +11685,42 @@ function createCapRouter_storageAdmin(getProvider, createRemoteProxy) {
11616
11685
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11617
11686
  return p.readRecords(methodInput);
11618
11687
  }),
11688
+ staleDataReport: trpc_middleware_js_1.adminProcedure
11689
+ .input(types_133.storageAdminCapability.methods.staleDataReport.input.loose())
11690
+ .output(types_133.storageAdminCapability.methods.staleDataReport.output)
11691
+ .query(async ({ input, ctx }) => {
11692
+ const { nodeId, ...methodInput } = input;
11693
+ const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
11694
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11695
+ return p.staleDataReport(methodInput);
11696
+ }),
11697
+ scanStaleData: trpc_middleware_js_1.adminProcedure
11698
+ .input(types_133.storageAdminCapability.methods.scanStaleData.input.loose())
11699
+ .output(types_133.storageAdminCapability.methods.scanStaleData.output)
11700
+ .mutation(async ({ input, ctx }) => {
11701
+ const { nodeId, ...methodInput } = input;
11702
+ const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
11703
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11704
+ return p.scanStaleData(methodInput);
11705
+ }),
11706
+ dropOrphanedKind: trpc_middleware_js_1.adminProcedure
11707
+ .input(types_133.storageAdminCapability.methods.dropOrphanedKind.input.loose())
11708
+ .output(types_133.storageAdminCapability.methods.dropOrphanedKind.output)
11709
+ .mutation(async ({ input, ctx }) => {
11710
+ const { nodeId, ...methodInput } = input;
11711
+ const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
11712
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11713
+ return p.dropOrphanedKind(methodInput);
11714
+ }),
11715
+ unsetStaleSettingsKey: trpc_middleware_js_1.adminProcedure
11716
+ .input(types_133.storageAdminCapability.methods.unsetStaleSettingsKey.input.loose())
11717
+ .output(types_133.storageAdminCapability.methods.unsetStaleSettingsKey.output)
11718
+ .mutation(async ({ input, ctx }) => {
11719
+ const { nodeId, ...methodInput } = input;
11720
+ const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
11721
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11722
+ return p.unsetStaleSettingsKey(methodInput);
11723
+ }),
11619
11724
  });
11620
11725
  }
11621
11726
  function createCapRouter_storageEvictable(getProvider, createRemoteProxy) {
@@ -344,7 +344,7 @@ class AddonPackageService {
344
344
  // Refresh registry + emit a synthetic 'updated' lifecycle event
345
345
  // (the version went BACK, but downstream consumers care about the
346
346
  // version change itself, not the direction).
347
- this.addonRegistry.refreshPackageVersion(name, rolledBackTo);
347
+ await this.refreshRegistryManifest(name, rolledBackTo);
348
348
  this.addonRegistry.emitUpdateEvent(name, previousVersion, rolledBackTo);
349
349
  try {
350
350
  const restart = await this.addonRegistry.restartPackage(name);
@@ -1103,7 +1103,7 @@ class AddonPackageService {
1103
1103
  }
1104
1104
  updatedVersion = result.version;
1105
1105
  // Update version in registry so UI reflects the change immediately
1106
- this.addonRegistry.refreshPackageVersion(name, updatedVersion);
1106
+ await this.refreshRegistryManifest(name, updatedVersion);
1107
1107
  // Emit addon.updated lifecycle event
1108
1108
  this.addonRegistry.emitUpdateEvent(name, previousVersion, updatedVersion);
1109
1109
  this.logger.info('Addon package updated, triggering hot-reload', {
@@ -1183,7 +1183,7 @@ class AddonPackageService {
1183
1183
  const r = await addonInstaller.applyUpdateFromStaged(name, version, stagedPath);
1184
1184
  const updatedVersion = r.version;
1185
1185
  // Update version in registry so UI reflects the change immediately
1186
- this.addonRegistry.refreshPackageVersion(name, updatedVersion);
1186
+ await this.refreshRegistryManifest(name, updatedVersion);
1187
1187
  // Emit addon.updated lifecycle event
1188
1188
  this.addonRegistry.emitUpdateEvent(name, previousVersion, updatedVersion);
1189
1189
  this.logger.info('Staged addon swap complete, triggering hot-reload', {
@@ -1963,6 +1963,22 @@ class AddonPackageService {
1963
1963
  });
1964
1964
  return null;
1965
1965
  }
1966
+ /**
1967
+ * After a swap on disk, make `addons.list` serve the NEW manifest (icon, name,
1968
+ * capabilities), not just the new version. A failed re-read falls back to the
1969
+ * version-only refresh and is logged with the package name.
1970
+ */
1971
+ async refreshRegistryManifest(name, version) {
1972
+ try {
1973
+ await this.addonRegistry.refreshPackageFromDisk(name);
1974
+ }
1975
+ catch (error) {
1976
+ this.logger.warn('Manifest refresh from disk failed; only the version was updated', {
1977
+ meta: { name, error: (0, types_2.errMsg)(error) },
1978
+ });
1979
+ this.addonRegistry.refreshPackageVersion(name, version);
1980
+ }
1981
+ }
1966
1982
  /** Only allow @camstack/* scoped packages */
1967
1983
  isAllowedPackage(name) {
1968
1984
  return name.startsWith('@camstack/');
@@ -1747,6 +1747,46 @@ class AddonRegistryService {
1747
1747
  });
1748
1748
  }
1749
1749
  }
1750
+ /**
1751
+ * Copy the freshly scanned package metadata (version, name, bundle, the
1752
+ * declaration `listAddons()` overlays, and the declared capabilities) onto an
1753
+ * already-registered entry. The live `entry.addon` instance and
1754
+ * `entry.initialized` stay untouched so `restartAddon` still shuts the old
1755
+ * instance down cleanly.
1756
+ */
1757
+ applyDiskMetadata(existing, registered) {
1758
+ existing.packageVersion = registered.packageVersion;
1759
+ existing.packageName = registered.packageName;
1760
+ existing.packageDisplayName = registered.packageDisplayName;
1761
+ if (registered.bundle !== undefined) {
1762
+ existing.bundle = registered.bundle;
1763
+ }
1764
+ existing.declaration = registered.declaration;
1765
+ existing.declaredCapabilities = (registered.declaration.capabilities ?? []).map((c) => typeof c === 'string' ? { name: c, mode: 'singleton' } : c);
1766
+ }
1767
+ /**
1768
+ * Re-read ONE installed package from `<addonsDir>/<packageName>` and refresh
1769
+ * the metadata of its registered addons (declaration incl. icon/name,
1770
+ * version, bundle, capabilities). Called after an update or rollback swapped
1771
+ * the package on disk: without it `listAddons()` keeps serving the manifest
1772
+ * captured at boot. Never registers a new addon (that is `loadNewAddons`).
1773
+ */
1774
+ async refreshPackageFromDisk(packageName) {
1775
+ const dataDir = path.resolve(process.env.CAMSTACK_DATA ?? 'camstack-data');
1776
+ const addonsDir = process.env.CAMSTACK_ADDONS_DIR ?? path.resolve(dataDir, 'addons');
1777
+ const freshLoader = new system_1.AddonLoader(this.loggingService.createLogger('AddonLoader'));
1778
+ await freshLoader.loadFromAddonDir(path.join(addonsDir, packageName));
1779
+ for (const registered of freshLoader.listAddons()) {
1780
+ const existing = this.addonEntries.get(registered.declaration.id);
1781
+ if (!existing || existing.packageName !== packageName)
1782
+ continue;
1783
+ this.applyDiskMetadata(existing, registered);
1784
+ this.logger.info('Refreshed addon manifest from disk', {
1785
+ tags: { addonId: registered.declaration.id },
1786
+ meta: { packageName, version: registered.packageVersion },
1787
+ });
1788
+ }
1789
+ }
1750
1790
  /**
1751
1791
  * Refresh the package version for all addons from a given package.
1752
1792
  * Call this after an update to ensure the UI shows the new version.
@@ -2419,14 +2459,7 @@ class AddonRegistryService {
2419
2459
  continue;
2420
2460
  }
2421
2461
  if (existing) {
2422
- existing.packageVersion = registered.packageVersion;
2423
- existing.packageName = registered.packageName;
2424
- existing.packageDisplayName = registered.packageDisplayName;
2425
- if (registered.bundle !== undefined) {
2426
- existing.bundle = registered.bundle;
2427
- }
2428
- existing.declaration = registered.declaration;
2429
- existing.declaredCapabilities = (registered.declaration.capabilities ?? []).map((c) => typeof c === 'string' ? { name: c, mode: 'singleton' } : c);
2462
+ this.applyDiskMetadata(existing, registered);
2430
2463
  continue;
2431
2464
  }
2432
2465
  // Skip agent-only addons on the hub. Same rule as the boot loader
@@ -1,6 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createAddonSettingsProvider = createAddonSettingsProvider;
4
+ /**
5
+ * Hub-side singleton provider for the `addon-settings` capability.
6
+ *
7
+ * Resolves `addonId` and delegates the settings call. In-process hub builtins
8
+ * are called directly; every FORKED addon (hub-local child over UDS, or remote
9
+ * agent over Moleculer) routes through the shared {@link AddonCallGateway} —
10
+ * the SAME centralised router routes/custom-actions use. Settings used to live
11
+ * on its own Moleculer-only path here, which is why forked hub-local addons'
12
+ * panels went empty after the UDS migration; that path is gone.
13
+ */
14
+ const zod_1 = require("zod");
15
+ const DeclaredKeysAnswer = zod_1.z.object({ keys: zod_1.z.array(zod_1.z.string()) });
16
+ const UnsetKeysAnswer = zod_1.z.object({
17
+ removed: zod_1.z.array(zod_1.z.string()),
18
+ refused: zod_1.z.array(zod_1.z.object({ key: zod_1.z.string(), reason: zod_1.z.string() })),
19
+ secretsCleared: zod_1.z.number(),
20
+ });
4
21
  function reshapeForOutput(schema) {
5
22
  return {
6
23
  tabs: schema.tabs
@@ -97,6 +114,42 @@ function createAddonSettingsProvider(deps) {
97
114
  }
98
115
  return forkedGet(input.addonId, 'getIntegrationSettings', { ...(input.nodeId ? { nodeId: input.nodeId } : {}) }, input.nodeId);
99
116
  },
117
+ async getDeclaredSettingsKeys(input) {
118
+ try {
119
+ if (gateway.isInProcess(input.addonId, input.nodeId)) {
120
+ const addon = getAddon(input.addonId);
121
+ if (!addon || typeof addon.getDeclaredSettingsKeys !== 'function')
122
+ return null;
123
+ const answer = await addon.getDeclaredSettingsKeys();
124
+ return { keys: [...answer.keys] };
125
+ }
126
+ const raw = await gateway.callForked(input.addonId, { target: 'settings', method: 'getDeclaredSettingsKeys', args: {} }, input.nodeId);
127
+ const parsed = DeclaredKeysAnswer.safeParse(raw);
128
+ return parsed.success ? parsed.data : null;
129
+ }
130
+ catch (err) {
131
+ // An addon that does not answer is `unknown` to the audit, never stale.
132
+ if (isServiceNotFoundError(err))
133
+ return null;
134
+ throw err;
135
+ }
136
+ },
137
+ async unsetStoredSettingsKeys(input) {
138
+ if (gateway.isInProcess(input.addonId, input.nodeId)) {
139
+ const addon = getAddon(input.addonId);
140
+ if (!addon || typeof addon.unsetStoredSettingsKeys !== 'function') {
141
+ throw new Error(`Addon "${input.addonId}" does not implement unsetStoredSettingsKeys`);
142
+ }
143
+ const outcome = await addon.unsetStoredSettingsKeys(input.keys);
144
+ return {
145
+ removed: [...outcome.removed],
146
+ refused: outcome.refused.map((r) => ({ key: r.key, reason: r.reason })),
147
+ secretsCleared: outcome.secretsCleared,
148
+ };
149
+ }
150
+ const raw = await gateway.callForked(input.addonId, { target: 'settings', method: 'unsetStoredSettingsKeys', args: { keys: input.keys } }, input.nodeId);
151
+ return UnsetKeysAnswer.parse(raw);
152
+ },
100
153
  async getDeviceSettings(input) {
101
154
  if (gateway.isInProcess(input.addonId, input.nodeId)) {
102
155
  const addon = getAddon(input.addonId);
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyStagedOrThrow = applyStagedOrThrow;
4
+ /**
5
+ * The lifecycle engine marks a task `done` whenever its apply callback returns,
6
+ * and `applyStagedAddonUpdate` reports a failed or refused swap as
7
+ * `{ success: false }` instead of throwing. This adapter turns that result into
8
+ * a throw so the task ends `failed` with the reason.
9
+ */
10
+ async function applyStagedOrThrow(target, input) {
11
+ const result = await target.applyStagedAddonUpdate(input.name, input.version, input.stagedPath);
12
+ if (!result.success) {
13
+ throw new Error(`Staged update of ${input.name}@${input.version} failed: ${result.error ?? 'unknown error'}`);
14
+ }
15
+ }
@@ -44,6 +44,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
44
44
  };
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
46
  exports.bootManual = bootManual;
47
+ const staged_update_applier_js_1 = require("./core/addon/staged-update-applier.js");
47
48
  const node_crypto_1 = require("node:crypto");
48
49
  const path = __importStar(require("node:path"));
49
50
  const system_1 = require("@camstack/system");
@@ -265,7 +266,11 @@ async function bootManual(opts) {
265
266
  fetchTarball: (name, version, signal) => addonPackageService.fetchAddonTarball(name, version, signal),
266
267
  extract: (tgz, destDir) => addonPackageService.extractTarball(tgz, destDir),
267
268
  applyAddonUpdate: async ({ name, version, stagedPath }) => {
268
- await addonPackageService.applyStagedAddonUpdate(name, version, stagedPath);
269
+ await (0, staged_update_applier_js_1.applyStagedOrThrow)(addonPackageService, {
270
+ name,
271
+ version,
272
+ stagedPath,
273
+ });
269
274
  },
270
275
  emit: (kind, job, entry) => {
271
276
  if (kind === 'progress') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.702",
3
+ "version": "1.2.704",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,20 +50,20 @@
50
50
  ]
51
51
  },
52
52
  "dependencies": {
53
- "@camstack/addon-admin-ui": "1.2.526",
54
- "@camstack/addon-agent-ui": "1.2.298",
55
- "@camstack/addon-auth": "1.2.171",
56
- "@camstack/addon-benchmark": "1.2.297",
53
+ "@camstack/addon-admin-ui": "1.2.528",
54
+ "@camstack/addon-agent-ui": "1.2.300",
55
+ "@camstack/addon-auth": "1.2.172",
56
+ "@camstack/addon-benchmark": "1.2.299",
57
57
  "@camstack/addon-decoder-nodeav": "1.2.144",
58
- "@camstack/addon-notifiers": "1.2.623",
58
+ "@camstack/addon-notifiers": "1.2.624",
59
59
  "@camstack/addon-pipeline": "1.2.635",
60
- "@camstack/addon-pipeline-orchestrator": "1.2.640",
61
- "@camstack/addon-post-analysis": "1.2.653",
60
+ "@camstack/addon-pipeline-orchestrator": "1.2.641",
61
+ "@camstack/addon-post-analysis": "1.2.654",
62
62
  "@camstack/develop": "0.1.41",
63
63
  "@camstack/sdk": "1.2.154",
64
- "@camstack/system": "1.2.524",
65
- "@camstack/types": "1.2.451",
66
- "@camstack/ui-library": "1.2.356",
64
+ "@camstack/system": "1.2.526",
65
+ "@camstack/types": "1.2.453",
66
+ "@camstack/ui-library": "1.2.358",
67
67
  "@fastify/compress": "^9.0.0",
68
68
  "@fastify/cookie": "^11.0.2",
69
69
  "@fastify/cors": "^11.2.0",