@camstack/system 1.1.38 → 1.1.40

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.
Files changed (47) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +48 -25
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +48 -25
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  15. package/dist/builtins/hub-forwarder/index.js +1 -1
  16. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  17. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  18. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  19. package/dist/builtins/local-network/local-network.addon.js +1 -1
  20. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  21. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  22. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  23. package/dist/builtins/platform-probe/index.js +1 -1
  24. package/dist/builtins/platform-probe/index.mjs +1 -1
  25. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  26. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  27. package/dist/builtins/snapshot/index.js +1 -1
  28. package/dist/builtins/snapshot/index.mjs +1 -1
  29. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  30. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  31. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  32. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  33. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  34. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  35. package/dist/builtins/system-backup/system-backup.service.d.ts +20 -2
  36. package/dist/builtins/system-config/system-config.addon.js +1 -1
  37. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  38. package/dist/builtins/winston-logging/index.js +1 -1
  39. package/dist/builtins/winston-logging/index.mjs +1 -1
  40. package/dist/{dist-CU3lb_Ys.mjs → dist-C0d3UidT.mjs} +38 -0
  41. package/dist/{dist-fRVWwrkF.js → dist-D_ijwYtl.js} +38 -0
  42. package/dist/index.js +70 -5
  43. package/dist/index.mjs +70 -5
  44. package/dist/kernel/fs-utils.d.ts +28 -0
  45. package/dist/{manifest-python-deps-4QvOLiBn.mjs → manifest-python-deps-6u3YnO2u.mjs} +3 -2
  46. package/dist/{manifest-python-deps-B6ahe90k.js → manifest-python-deps-Cp9WdXiT.js} +3 -2
  47. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-B6ahe90k.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-Cp9WdXiT.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 { F as createUdsLoggerWithControl, I as LocalChildClient, i as createUdsAddonContext, it as setWorkerNativeCapsChangeListener, nt as getWorkerNativeCapSnapshot, ot as validateProviderRegistrations, t as installManifestPythonDeps, tt as getWorkerNativeCapProvider, vt as installManifestNativeDeps, yt as resolveAddonClass } from "./manifest-python-deps-4QvOLiBn.mjs";
1
+ import { F as createUdsLoggerWithControl, I as LocalChildClient, i as createUdsAddonContext, it as setWorkerNativeCapsChangeListener, nt as getWorkerNativeCapSnapshot, ot as validateProviderRegistrations, t as installManifestPythonDeps, tt as getWorkerNativeCapProvider, vt as installManifestNativeDeps, yt as resolveAddonClass } from "./manifest-python-deps-6u3YnO2u.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";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, et as BaseAddon, f as addonPagesCapability, st as EventCategory } from "../../dist-CU3lb_Ys.mjs";
1
+ import { $ as errMsg, et as BaseAddon, f as addonPagesCapability, st as EventCategory } from "../../dist-C0d3UidT.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, et as BaseAddon, m as addonWidgetsCapability, st as EventCategory } from "../../dist-CU3lb_Ys.mjs";
1
+ import { $ as errMsg, et as BaseAddon, m as addonWidgetsCapability, st as EventCategory } from "../../dist-C0d3UidT.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  //#region src/builtins/alerts/alerts.addon.ts
8
8
  var CATEGORY_MAPPINGS = {
9
9
  "addon.crashed": {
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, et as BaseAddon, h as alertsCapability, mt as createEvent, n as AlertSchema, st as EventCategory } from "../../dist-CU3lb_Ys.mjs";
1
+ import { $ as errMsg, et as BaseAddon, h as alertsCapability, mt as createEvent, n as AlertSchema, st as EventCategory } from "../../dist-C0d3UidT.mjs";
2
2
  //#region src/builtins/alerts/alerts.addon.ts
3
3
  var CATEGORY_MAPPINGS = {
4
4
  "addon.crashed": {
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_fs$1 = require_chunk.__toESM(node_fs, 1);
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -3021,6 +3021,7 @@ var DEFAULT_BACKUP_LOCATIONS = [
3021
3021
  ];
3022
3022
  /** Path of the manifest file embedded inside every archive. */
3023
3023
  var ARCHIVE_MANIFEST_NAME = ".camstack-backup-manifest.json";
3024
+ var DEFAULT_STAT_CACHE_TTL_MS = 6e4;
3024
3025
  /**
3025
3026
  * System-level archive primitives. Construct one per server boot;
3026
3027
  * methods are pure I/O.
@@ -3028,33 +3029,55 @@ var ARCHIVE_MANIFEST_NAME = ".camstack-backup-manifest.json";
3028
3029
  var SystemBackupService = class {
3029
3030
  dataDir;
3030
3031
  logger;
3031
- constructor(dataDir, logger) {
3032
+ statCacheTtlMs;
3033
+ statCache = /* @__PURE__ */ new Map();
3034
+ constructor(dataDir, logger, opts) {
3032
3035
  this.dataDir = dataDir;
3033
3036
  this.logger = logger;
3037
+ this.statCacheTtlMs = opts?.statCacheTtlMs ?? DEFAULT_STAT_CACHE_TTL_MS;
3034
3038
  }
3035
3039
  /**
3036
3040
  * Snapshot the current size + file count of each well-known
3037
3041
  * location. Used by the admin UI to render an opt-in checklist
3038
3042
  * before triggering a backup, and by `createArchive` to derive the
3039
3043
  * embedded manifest.
3044
+ *
3045
+ * The walk is async (yields to the event loop per directory) and
3046
+ * per-location results are cached + single-flighted: the service
3047
+ * runs in-process in the hub, and a synchronous walk of the 70k+
3048
+ * file `addons` location blocked the event loop long enough to kill
3049
+ * WS keepalives cluster-wide.
3040
3050
  */
3041
3051
  statLocations(locations = DEFAULT_BACKUP_LOCATIONS) {
3042
- return locations.map((loc) => {
3043
- const abs = node_path.isAbsolute(loc) ? loc : node_path.join(this.dataDir, loc);
3044
- if (!node_fs.existsSync(abs)) return {
3045
- name: loc,
3046
- sizeBytes: 0,
3047
- fileCount: 0,
3048
- present: false
3049
- };
3050
- const walked = walkAbs(abs, abs);
3051
- return {
3052
- name: loc,
3053
- sizeBytes: walked.reduce((acc, e) => acc + e.sizeBytes, 0),
3054
- fileCount: walked.filter((e) => e.kind === "file").length,
3055
- present: true
3056
- };
3052
+ return Promise.all(locations.map((loc) => this.statLocationCached(loc)));
3053
+ }
3054
+ statLocationCached(loc) {
3055
+ const now = Date.now();
3056
+ const hit = this.statCache.get(loc);
3057
+ if (hit && now - hit.at < this.statCacheTtlMs) return hit.value;
3058
+ const value = this.statLocation(loc);
3059
+ this.statCache.set(loc, {
3060
+ at: now,
3061
+ value
3057
3062
  });
3063
+ value.catch(() => this.statCache.delete(loc));
3064
+ return value;
3065
+ }
3066
+ async statLocation(loc) {
3067
+ const abs = node_path.isAbsolute(loc) ? loc : node_path.join(this.dataDir, loc);
3068
+ if (!node_fs.existsSync(abs)) return {
3069
+ name: loc,
3070
+ sizeBytes: 0,
3071
+ fileCount: 0,
3072
+ present: false
3073
+ };
3074
+ const walked = await walkAbs(abs, abs);
3075
+ return {
3076
+ name: loc,
3077
+ sizeBytes: walked.reduce((acc, e) => acc + e.sizeBytes, 0),
3078
+ fileCount: walked.filter((e) => e.kind === "file").length,
3079
+ present: true
3080
+ };
3058
3081
  }
3059
3082
  /**
3060
3083
  * Create a tar.gz of the given locations. Uses the `tar` npm package
@@ -3082,8 +3105,8 @@ var SystemBackupService = class {
3082
3105
  const entries = [];
3083
3106
  for (const loc of present) {
3084
3107
  const abs = node_path.join(this.dataDir, loc);
3085
- const stat = node_fs.statSync(abs);
3086
- if (stat.isDirectory()) for (const e of walkAbs(abs, this.dataDir)) entries.push(e);
3108
+ const stat = await node_fs_promises.stat(abs);
3109
+ if (stat.isDirectory()) for (const e of await walkAbs(abs, this.dataDir)) entries.push(e);
3087
3110
  else entries.push(toArchiveEntry(abs, this.dataDir, stat));
3088
3111
  }
3089
3112
  const manifest = {
@@ -3258,15 +3281,15 @@ var SystemBackupService = class {
3258
3281
  * path. Symlinks are dereferenced (matches the `follow: true` flag we
3259
3282
  * pass to tar.create).
3260
3283
  */
3261
- function walkAbs(absRoot, dataDir) {
3284
+ async function walkAbs(absRoot, dataDir) {
3262
3285
  const out = [];
3263
- walkRec(absRoot, dataDir, out);
3286
+ await walkRec(absRoot, dataDir, out);
3264
3287
  return out;
3265
3288
  }
3266
- function walkRec(abs, dataDir, out) {
3289
+ async function walkRec(abs, dataDir, out) {
3267
3290
  let stat;
3268
3291
  try {
3269
- stat = node_fs.statSync(abs);
3292
+ stat = await node_fs_promises.stat(abs);
3270
3293
  } catch {
3271
3294
  return;
3272
3295
  }
@@ -3274,11 +3297,11 @@ function walkRec(abs, dataDir, out) {
3274
3297
  if (stat.isDirectory()) {
3275
3298
  let children;
3276
3299
  try {
3277
- children = node_fs.readdirSync(abs);
3300
+ children = await node_fs_promises.readdir(abs);
3278
3301
  } catch {
3279
3302
  return;
3280
3303
  }
3281
- for (const child of children) walkRec(node_path.join(abs, child), dataDir, out);
3304
+ for (const child of children) await walkRec(node_path.join(abs, child), dataDir, out);
3282
3305
  }
3283
3306
  }
3284
3307
  function toArchiveEntry(abs, dataDir, stat) {
@@ -1,4 +1,4 @@
1
- import { et as BaseAddon, st as EventCategory, v as backupCapability } from "../../dist-CU3lb_Ys.mjs";
1
+ import { et as BaseAddon, st as EventCategory, v as backupCapability } from "../../dist-C0d3UidT.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import Kt from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3006,6 +3006,7 @@ var DEFAULT_BACKUP_LOCATIONS = [
3006
3006
  ];
3007
3007
  /** Path of the manifest file embedded inside every archive. */
3008
3008
  var ARCHIVE_MANIFEST_NAME = ".camstack-backup-manifest.json";
3009
+ var DEFAULT_STAT_CACHE_TTL_MS = 6e4;
3009
3010
  /**
3010
3011
  * System-level archive primitives. Construct one per server boot;
3011
3012
  * methods are pure I/O.
@@ -3013,33 +3014,55 @@ var ARCHIVE_MANIFEST_NAME = ".camstack-backup-manifest.json";
3013
3014
  var SystemBackupService = class {
3014
3015
  dataDir;
3015
3016
  logger;
3016
- constructor(dataDir, logger) {
3017
+ statCacheTtlMs;
3018
+ statCache = /* @__PURE__ */ new Map();
3019
+ constructor(dataDir, logger, opts) {
3017
3020
  this.dataDir = dataDir;
3018
3021
  this.logger = logger;
3022
+ this.statCacheTtlMs = opts?.statCacheTtlMs ?? DEFAULT_STAT_CACHE_TTL_MS;
3019
3023
  }
3020
3024
  /**
3021
3025
  * Snapshot the current size + file count of each well-known
3022
3026
  * location. Used by the admin UI to render an opt-in checklist
3023
3027
  * before triggering a backup, and by `createArchive` to derive the
3024
3028
  * embedded manifest.
3029
+ *
3030
+ * The walk is async (yields to the event loop per directory) and
3031
+ * per-location results are cached + single-flighted: the service
3032
+ * runs in-process in the hub, and a synchronous walk of the 70k+
3033
+ * file `addons` location blocked the event loop long enough to kill
3034
+ * WS keepalives cluster-wide.
3025
3035
  */
3026
3036
  statLocations(locations = DEFAULT_BACKUP_LOCATIONS) {
3027
- return locations.map((loc) => {
3028
- const abs = path$1.isAbsolute(loc) ? loc : path$1.join(this.dataDir, loc);
3029
- if (!fs.existsSync(abs)) return {
3030
- name: loc,
3031
- sizeBytes: 0,
3032
- fileCount: 0,
3033
- present: false
3034
- };
3035
- const walked = walkAbs(abs, abs);
3036
- return {
3037
- name: loc,
3038
- sizeBytes: walked.reduce((acc, e) => acc + e.sizeBytes, 0),
3039
- fileCount: walked.filter((e) => e.kind === "file").length,
3040
- present: true
3041
- };
3037
+ return Promise.all(locations.map((loc) => this.statLocationCached(loc)));
3038
+ }
3039
+ statLocationCached(loc) {
3040
+ const now = Date.now();
3041
+ const hit = this.statCache.get(loc);
3042
+ if (hit && now - hit.at < this.statCacheTtlMs) return hit.value;
3043
+ const value = this.statLocation(loc);
3044
+ this.statCache.set(loc, {
3045
+ at: now,
3046
+ value
3042
3047
  });
3048
+ value.catch(() => this.statCache.delete(loc));
3049
+ return value;
3050
+ }
3051
+ async statLocation(loc) {
3052
+ const abs = path$1.isAbsolute(loc) ? loc : path$1.join(this.dataDir, loc);
3053
+ if (!fs.existsSync(abs)) return {
3054
+ name: loc,
3055
+ sizeBytes: 0,
3056
+ fileCount: 0,
3057
+ present: false
3058
+ };
3059
+ const walked = await walkAbs(abs, abs);
3060
+ return {
3061
+ name: loc,
3062
+ sizeBytes: walked.reduce((acc, e) => acc + e.sizeBytes, 0),
3063
+ fileCount: walked.filter((e) => e.kind === "file").length,
3064
+ present: true
3065
+ };
3043
3066
  }
3044
3067
  /**
3045
3068
  * Create a tar.gz of the given locations. Uses the `tar` npm package
@@ -3067,8 +3090,8 @@ var SystemBackupService = class {
3067
3090
  const entries = [];
3068
3091
  for (const loc of present) {
3069
3092
  const abs = path$1.join(this.dataDir, loc);
3070
- const stat = fs.statSync(abs);
3071
- if (stat.isDirectory()) for (const e of walkAbs(abs, this.dataDir)) entries.push(e);
3093
+ const stat = await fsp.stat(abs);
3094
+ if (stat.isDirectory()) for (const e of await walkAbs(abs, this.dataDir)) entries.push(e);
3072
3095
  else entries.push(toArchiveEntry(abs, this.dataDir, stat));
3073
3096
  }
3074
3097
  const manifest = {
@@ -3243,15 +3266,15 @@ var SystemBackupService = class {
3243
3266
  * path. Symlinks are dereferenced (matches the `follow: true` flag we
3244
3267
  * pass to tar.create).
3245
3268
  */
3246
- function walkAbs(absRoot, dataDir) {
3269
+ async function walkAbs(absRoot, dataDir) {
3247
3270
  const out = [];
3248
- walkRec(absRoot, dataDir, out);
3271
+ await walkRec(absRoot, dataDir, out);
3249
3272
  return out;
3250
3273
  }
3251
- function walkRec(abs, dataDir, out) {
3274
+ async function walkRec(abs, dataDir, out) {
3252
3275
  let stat;
3253
3276
  try {
3254
- stat = fs.statSync(abs);
3277
+ stat = await fsp.stat(abs);
3255
3278
  } catch {
3256
3279
  return;
3257
3280
  }
@@ -3259,11 +3282,11 @@ function walkRec(abs, dataDir, out) {
3259
3282
  if (stat.isDirectory()) {
3260
3283
  let children;
3261
3284
  try {
3262
- children = fs.readdirSync(abs);
3285
+ children = await fsp.readdir(abs);
3263
3286
  } catch {
3264
3287
  return;
3265
3288
  }
3266
- for (const child of children) walkRec(path$1.join(abs, child), dataDir, out);
3289
+ for (const child of children) await walkRec(path$1.join(abs, child), dataDir, out);
3267
3290
  }
3268
3291
  }
3269
3292
  function toArchiveEntry(abs, dataDir, stat) {
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { P as logDestinationCapability, et as BaseAddon } from "../../dist-CU3lb_Ys.mjs";
1
+ import { P as logDestinationCapability, et as BaseAddon } from "../../dist-C0d3UidT.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  let _camstack_types_node = require("@camstack/types/node");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, C as enumerateItemArrayFields, D as getByPath, Q as validateExpressionSource, R as normalizeUnit, S as deviceStatusCapability, T as evaluateLinkExpression, Tt as sleep, W as setByPath, X as toExpressionValue, a as DeviceStatusSchema, at as DeviceRole, b as deviceManagerCapability, c as STREAM_PROFILE_META, et as BaseAddon, g as applyTransform, i as CAP_NAMES_WITH_STATUS, it as DeviceFeature, ot as DeviceType, st as EventCategory, t as ALL_CAPABILITY_DEFINITIONS, ut as WELL_KNOWN_TAB_MAP, w as enumerateSchemaFields, x as deviceStateCapability, y as buildStreamParamsConfigSchema, yt as isDeviceConfigCap, z as parseStreamParamsFormPatch } from "../../dist-CU3lb_Ys.mjs";
1
+ import { $ as errMsg, C as enumerateItemArrayFields, D as getByPath, Q as validateExpressionSource, R as normalizeUnit, S as deviceStatusCapability, T as evaluateLinkExpression, Tt as sleep, W as setByPath, X as toExpressionValue, a as DeviceStatusSchema, at as DeviceRole, b as deviceManagerCapability, c as STREAM_PROFILE_META, et as BaseAddon, g as applyTransform, i as CAP_NAMES_WITH_STATUS, it as DeviceFeature, ot as DeviceType, st as EventCategory, t as ALL_CAPABILITY_DEFINITIONS, ut as WELL_KNOWN_TAB_MAP, w as enumerateSchemaFields, x as deviceStateCapability, y as buildStreamParamsConfigSchema, yt as isDeviceConfigCap, z as parseStreamParamsFormPatch } from "../../dist-C0d3UidT.mjs";
2
2
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { P as logDestinationCapability, et as BaseAddon } from "../../dist-CU3lb_Ys.mjs";
1
+ import { P as logDestinationCapability, et as BaseAddon } from "../../dist-C0d3UidT.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,4 +1,4 @@
1
- import { Z as userManagementCapability, _ as authProviderCapability, d as UserRecordSchema, et as BaseAddon, l as ScopedTokenSchema, r as ApiKeyRecordSchema } from "../../dist-CU3lb_Ys.mjs";
1
+ import { Z as userManagementCapability, _ as authProviderCapability, d as UserRecordSchema, et as BaseAddon, l as ScopedTokenSchema, r as ApiKeyRecordSchema } from "../../dist-C0d3UidT.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as crypto$1 from "node:crypto";
4
4
  import { randomUUID } from "node:crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-fRVWwrkF.js");
3
+ const require_dist = require("../../dist-D_ijwYtl.js");
4
4
  let node_os = require("node:os");
5
5
  node_os = require_chunk.__toESM(node_os);
6
6
  //#region src/builtins/local-network/local-network.addon.ts
@@ -1,4 +1,4 @@
1
- import { N as localNetworkCapability, et as BaseAddon, st as EventCategory } from "../../dist-CU3lb_Ys.mjs";
1
+ import { N as localNetworkCapability, et as BaseAddon, st as EventCategory } from "../../dist-C0d3UidT.mjs";
2
2
  import * as os from "node:os";
3
3
  //#region src/builtins/local-network/local-network.addon.ts
4
4
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,4 +1,4 @@
1
- import { I as metricsProviderCapability, et as BaseAddon, mt as createEvent, st as EventCategory } from "../../dist-CU3lb_Ys.mjs";
1
+ import { I as metricsProviderCapability, et as BaseAddon, mt as createEvent, st as EventCategory } from "../../dist-C0d3UidT.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile, execFileSync } from "node:child_process";
4
4
  import { promisify } from "node:util";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, B as platformProbeCapability, U as scoreRuntimes, et as BaseAddon, gt as emitReadiness, st as EventCategory } from "../../dist-CU3lb_Ys.mjs";
1
+ import { $ as errMsg, B as platformProbeCapability, U as scoreRuntimes, et as BaseAddon, gt as emitReadiness, st as EventCategory } from "../../dist-C0d3UidT.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { et as BaseAddon, st as EventCategory } from "../../dist-CU3lb_Ys.mjs";
1
+ import { et as BaseAddon, st as EventCategory } from "../../dist-C0d3UidT.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  //#region src/builtins/snapshot/snapshot.addon.ts
9
9
  /** Default cache window for non-battery cams (seconds). 10s feels live. */
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, K as snapshotCapability, L as nodePin, Y as streamQualityLabel, et as BaseAddon, it as DeviceFeature, ot as DeviceType } from "../../dist-CU3lb_Ys.mjs";
1
+ import { $ as errMsg, K as snapshotCapability, L as nodePin, Y as streamQualityLabel, et as BaseAddon, it as DeviceFeature, ot as DeviceType } from "../../dist-C0d3UidT.mjs";
2
2
  import { execFile } from "node:child_process";
3
3
  //#region src/builtins/snapshot/snapshot.addon.ts
4
4
  /** Default cache window for non-battery cams (seconds). 10s feels live. */
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { E as filesystemBrowseCapability, J as storageProviderCapability, et as BaseAddon } from "../../dist-CU3lb_Ys.mjs";
1
+ import { E as filesystemBrowseCapability, J as storageProviderCapability, et as BaseAddon } from "../../dist-C0d3UidT.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import * as path$1 from "node:path";
4
4
  import { basename, dirname, join, resolve, sep } from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let better_sqlite3 = require("better-sqlite3");
9
9
  better_sqlite3 = require_chunk.__toESM(better_sqlite3);
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, G as settingsStoreCapability, dt as asJsonObject, et as BaseAddon, s as RUNTIME_DEFAULTS, xt as parseJsonUnknown } from "../../dist-CU3lb_Ys.mjs";
1
+ import { $ as errMsg, G as settingsStoreCapability, dt as asJsonObject, et as BaseAddon, s as RUNTIME_DEFAULTS, xt as parseJsonUnknown } from "../../dist-C0d3UidT.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import Database from "better-sqlite3";
4
4
  //#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_chunk.__toESM(node_path);
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { bt as parseJsonObject, et as BaseAddon, q as storageCapability, u as StorageLocationTypeSchema } from "../../dist-CU3lb_Ys.mjs";
1
+ import { bt as parseJsonObject, et as BaseAddon, q as storageCapability, u as StorageLocationTypeSchema } from "../../dist-C0d3UidT.mjs";
2
2
  import * as path$1 from "node:path";
3
3
  import * as fs from "node:fs/promises";
4
4
  import { buildStorageLocationRegistry } from "@camstack/system";
@@ -64,6 +64,14 @@ export interface LocationStat {
64
64
  /** False when the location doesn't exist under dataDir. */
65
65
  readonly present: boolean;
66
66
  }
67
+ export interface SystemBackupServiceOptions {
68
+ /**
69
+ * How long a `statLocations` walk result stays fresh. The `addons`
70
+ * location holds tens of thousands of files; without a cache every
71
+ * admin-UI visit re-walks it.
72
+ */
73
+ readonly statCacheTtlMs?: number;
74
+ }
67
75
  /**
68
76
  * System-level archive primitives. Construct one per server boot;
69
77
  * methods are pure I/O.
@@ -71,14 +79,24 @@ export interface LocationStat {
71
79
  export declare class SystemBackupService {
72
80
  private readonly dataDir;
73
81
  private readonly logger;
74
- constructor(dataDir: string, logger: IScopedLogger);
82
+ private readonly statCacheTtlMs;
83
+ private readonly statCache;
84
+ constructor(dataDir: string, logger: IScopedLogger, opts?: SystemBackupServiceOptions);
75
85
  /**
76
86
  * Snapshot the current size + file count of each well-known
77
87
  * location. Used by the admin UI to render an opt-in checklist
78
88
  * before triggering a backup, and by `createArchive` to derive the
79
89
  * embedded manifest.
90
+ *
91
+ * The walk is async (yields to the event loop per directory) and
92
+ * per-location results are cached + single-flighted: the service
93
+ * runs in-process in the hub, and a synchronous walk of the 70k+
94
+ * file `addons` location blocked the event loop long enough to kill
95
+ * WS keepalives cluster-wide.
80
96
  */
81
- statLocations(locations?: readonly string[]): readonly LocationStat[];
97
+ statLocations(locations?: readonly string[]): Promise<readonly LocationStat[]>;
98
+ private statLocationCached;
99
+ private statLocation;
82
100
  /**
83
101
  * Create a tar.gz of the given locations. Uses the `tar` npm package
84
102
  * (used by npm itself) instead of shelling out — gives us:
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, et as BaseAddon, vt as hydrateSchema } from "../../dist-CU3lb_Ys.mjs";
1
+ import { $ as errMsg, et as BaseAddon, vt as hydrateSchema } from "../../dist-C0d3UidT.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-fRVWwrkF.js");
6
+ const require_dist = require("../../dist-D_ijwYtl.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { P as logDestinationCapability, et as BaseAddon } from "../../dist-CU3lb_Ys.mjs";
1
+ import { P as logDestinationCapability, et as BaseAddon } from "../../dist-C0d3UidT.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
@@ -16578,6 +16578,19 @@ getTurnServers: method(z.void(), z.array(TurnServerSchema).readonly()) }
16578
16578
  * b. `finishAuthentication({userId, response})` → server verifies
16579
16579
  * the assertion, bumps the credential counter, returns ok.
16580
16580
  *
16581
+ * 2b. Usernameless (discoverable-credential) authentication — the
16582
+ * passkey IS the primary factor, no password leg:
16583
+ * a. `beginDiscoverableAuthentication({})` → assertion options with
16584
+ * EMPTY `allowCredentials` (the browser offers every resident
16585
+ * passkey it holds for this RP) + `userVerification: 'required'`
16586
+ * (the passkey replaces both factors, so UV is mandatory).
16587
+ * The challenge is stored server-side, NOT bound to any user.
16588
+ * b. `finishDiscoverableAuthentication({response})` → the provider
16589
+ * resolves the credential by the response's credential id,
16590
+ * verifies the assertion against the stored challenge + that
16591
+ * credential's public key/counter, and returns the OWNING
16592
+ * `userId` — the caller (core auth router) mints the session.
16593
+ *
16581
16594
  * 3. Management:
16582
16595
  * - `listPasskeys({userId})` — enumerate user's enrolled credentials.
16583
16596
  * - `removePasskey({userId, credentialId})` — revoke one credential.
@@ -16634,6 +16647,19 @@ var userPasskeysCapability = {
16634
16647
  kind: "mutation",
16635
16648
  access: "view"
16636
16649
  }),
16650
+ beginDiscoverableAuthentication: method(z.object({}), z.object({ optionsJSON: z.record(z.string(), z.unknown()) }), {
16651
+ kind: "mutation",
16652
+ access: "view"
16653
+ }),
16654
+ finishDiscoverableAuthentication: method(z.object({
16655
+ /** AuthenticationResponseJSON from the browser. */
16656
+ response: z.record(z.string(), z.unknown()) }), z.object({
16657
+ verified: z.boolean(),
16658
+ userId: z.string().nullable()
16659
+ }), {
16660
+ kind: "mutation",
16661
+ access: "view"
16662
+ }),
16637
16663
  listPasskeys: method(z.object({ userId: z.string() }), z.array(PasskeySummarySchema), { auth: "admin" }),
16638
16664
  removePasskey: method(z.object({
16639
16665
  userId: z.string(),
@@ -24532,6 +24558,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
24532
24558
  addonId: null,
24533
24559
  access: "view"
24534
24560
  },
24561
+ "userPasskeys.beginDiscoverableAuthentication": {
24562
+ capName: "user-passkeys",
24563
+ capScope: "system",
24564
+ addonId: null,
24565
+ access: "view"
24566
+ },
24535
24567
  "userPasskeys.beginRegistration": {
24536
24568
  capName: "user-passkeys",
24537
24569
  capScope: "system",
@@ -24544,6 +24576,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
24544
24576
  addonId: null,
24545
24577
  access: "view"
24546
24578
  },
24579
+ "userPasskeys.finishDiscoverableAuthentication": {
24580
+ capName: "user-passkeys",
24581
+ capScope: "system",
24582
+ addonId: null,
24583
+ access: "view"
24584
+ },
24547
24585
  "userPasskeys.finishRegistration": {
24548
24586
  capName: "user-passkeys",
24549
24587
  capScope: "system",
@@ -16578,6 +16578,19 @@ getTurnServers: method(zod.z.void(), zod.z.array(TurnServerSchema).readonly()) }
16578
16578
  * b. `finishAuthentication({userId, response})` → server verifies
16579
16579
  * the assertion, bumps the credential counter, returns ok.
16580
16580
  *
16581
+ * 2b. Usernameless (discoverable-credential) authentication — the
16582
+ * passkey IS the primary factor, no password leg:
16583
+ * a. `beginDiscoverableAuthentication({})` → assertion options with
16584
+ * EMPTY `allowCredentials` (the browser offers every resident
16585
+ * passkey it holds for this RP) + `userVerification: 'required'`
16586
+ * (the passkey replaces both factors, so UV is mandatory).
16587
+ * The challenge is stored server-side, NOT bound to any user.
16588
+ * b. `finishDiscoverableAuthentication({response})` → the provider
16589
+ * resolves the credential by the response's credential id,
16590
+ * verifies the assertion against the stored challenge + that
16591
+ * credential's public key/counter, and returns the OWNING
16592
+ * `userId` — the caller (core auth router) mints the session.
16593
+ *
16581
16594
  * 3. Management:
16582
16595
  * - `listPasskeys({userId})` — enumerate user's enrolled credentials.
16583
16596
  * - `removePasskey({userId, credentialId})` — revoke one credential.
@@ -16634,6 +16647,19 @@ var userPasskeysCapability = {
16634
16647
  kind: "mutation",
16635
16648
  access: "view"
16636
16649
  }),
16650
+ beginDiscoverableAuthentication: method(zod.z.object({}), zod.z.object({ optionsJSON: zod.z.record(zod.z.string(), zod.z.unknown()) }), {
16651
+ kind: "mutation",
16652
+ access: "view"
16653
+ }),
16654
+ finishDiscoverableAuthentication: method(zod.z.object({
16655
+ /** AuthenticationResponseJSON from the browser. */
16656
+ response: zod.z.record(zod.z.string(), zod.z.unknown()) }), zod.z.object({
16657
+ verified: zod.z.boolean(),
16658
+ userId: zod.z.string().nullable()
16659
+ }), {
16660
+ kind: "mutation",
16661
+ access: "view"
16662
+ }),
16637
16663
  listPasskeys: method(zod.z.object({ userId: zod.z.string() }), zod.z.array(PasskeySummarySchema), { auth: "admin" }),
16638
16664
  removePasskey: method(zod.z.object({
16639
16665
  userId: zod.z.string(),
@@ -24532,6 +24558,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
24532
24558
  addonId: null,
24533
24559
  access: "view"
24534
24560
  },
24561
+ "userPasskeys.beginDiscoverableAuthentication": {
24562
+ capName: "user-passkeys",
24563
+ capScope: "system",
24564
+ addonId: null,
24565
+ access: "view"
24566
+ },
24535
24567
  "userPasskeys.beginRegistration": {
24536
24568
  capName: "user-passkeys",
24537
24569
  capScope: "system",
@@ -24544,6 +24576,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
24544
24576
  addonId: null,
24545
24577
  access: "view"
24546
24578
  },
24579
+ "userPasskeys.finishDiscoverableAuthentication": {
24580
+ capName: "user-passkeys",
24581
+ capScope: "system",
24582
+ addonId: null,
24583
+ access: "view"
24584
+ },
24547
24585
  "userPasskeys.finishRegistration": {
24548
24586
  capName: "user-passkeys",
24549
24587
  capScope: "system",
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-fRVWwrkF.js");
3
+ const require_dist = require("./dist-D_ijwYtl.js");
4
4
  const require_model_download_service = require("./model-download-service-D-Umz4-L.js");
5
5
  const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
6
6
  const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
@@ -21,7 +21,7 @@ const require_builtins_local_auth_local_auth_addon = require("./builtins/local-a
21
21
  require("./builtins/local-auth/index.js");
22
22
  const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
23
23
  require("./builtins/device-manager/index.js");
24
- const require_manifest_python_deps = require("./manifest-python-deps-B6ahe90k.js");
24
+ const require_manifest_python_deps = require("./manifest-python-deps-Cp9WdXiT.js");
25
25
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
26
26
  let _camstack_types_node = require("@camstack/types/node");
27
27
  let zod = require("zod");
@@ -2587,6 +2587,71 @@ async function copyDirRecursive(src, dest) {
2587
2587
  });
2588
2588
  }
2589
2589
  /**
2590
+ * Runtime natives that must stay installable even when an addon forgets to
2591
+ * declare them: the mirror of the build preset's `COMMON_NATIVE_DEPS`
2592
+ * (tools/build/vite-lib.preset.ts — build tooling, not loadable at runtime).
2593
+ * These are the ONLY packages a self-contained dist genuinely `require()`s
2594
+ * from node_modules; everything else is already inlined by the bundle.
2595
+ */
2596
+ var RUNTIME_NATIVE_KEEP = [
2597
+ "better-sqlite3",
2598
+ "node-av",
2599
+ "onnxruntime-node",
2600
+ "sharp",
2601
+ "ssh2",
2602
+ "werift",
2603
+ "systeminformation"
2604
+ ];
2605
+ /** Read `camstack.runtimeDependencies` (extra non-native deps the dist
2606
+ * requires at runtime — versions come from the manifest's own dep maps). */
2607
+ function readRuntimeDependencies(pkg) {
2608
+ const camstack = pkg.camstack;
2609
+ if (camstack === null || typeof camstack !== "object" || Array.isArray(camstack)) return [];
2610
+ const raw = camstack.runtimeDependencies;
2611
+ if (!Array.isArray(raw)) return [];
2612
+ return raw.filter((entry) => typeof entry === "string");
2613
+ }
2614
+ /**
2615
+ * Strip every dependency an addon's self-contained dist has already bundled,
2616
+ * keeping ONLY what must resolve from node_modules at runtime:
2617
+ *
2618
+ * keep = RUNTIME_NATIVE_KEEP ∪ camstack.runtimeDependencies (minus @camstack/*)
2619
+ *
2620
+ * Addon dists are fully bundled (vite `externals: 'self-contained'`), yet the
2621
+ * blanket `npm install` of the manifest's dependencies re-installed the whole
2622
+ * tree the dist already contains — 98% of the deployed addons dir was
2623
+ * redundant node_modules (74k files / 1.34 GB on the live hub; it stalled the
2624
+ * backup walk and bloated every deploy). Kept PEER deps are folded into
2625
+ * `dependencies` so `--omit=peer` installs can't drop them (pipeline's
2626
+ * patched `werift`).
2627
+ *
2628
+ * Escape hatch: `CAMSTACK_INSTALL_FULL_DEPS=1` restores the legacy behaviour
2629
+ * (strip only @camstack/*) for debugging a suspected undeclared runtime dep.
2630
+ *
2631
+ * Returns a new object (immutable).
2632
+ */
2633
+ function stripBundledDeps(pkg) {
2634
+ if (process.env.CAMSTACK_INSTALL_FULL_DEPS === "1") return stripCamstackDeps(pkg);
2635
+ const camstack = pkg.camstack;
2636
+ if (camstack !== null && typeof camstack === "object" && !Array.isArray(camstack) && camstack.system === true) return stripCamstackDeps(pkg);
2637
+ const keep = new Set([...RUNTIME_NATIVE_KEEP, ...readRuntimeDependencies(pkg)]);
2638
+ const kept = {};
2639
+ for (const depType of ["dependencies", "peerDependencies"]) {
2640
+ const rawDeps = pkg[depType];
2641
+ if (rawDeps === null || typeof rawDeps !== "object" || Array.isArray(rawDeps)) continue;
2642
+ for (const [name, version] of Object.entries(rawDeps)) {
2643
+ if (name.startsWith("@camstack/")) continue;
2644
+ if (!keep.has(name) || typeof version !== "string") continue;
2645
+ if (!(name in kept)) kept[name] = version;
2646
+ }
2647
+ }
2648
+ const result = { ...pkg };
2649
+ result.dependencies = Object.keys(kept).length > 0 ? kept : void 0;
2650
+ result.peerDependencies = void 0;
2651
+ delete result.devDependencies;
2652
+ return result;
2653
+ }
2654
+ /**
2590
2655
  * Strip @camstack/* dependencies and devDependencies from a package.json object.
2591
2656
  * Used when installing addons into the addons directory — @camstack packages
2592
2657
  * are provided by the host runtime, not installed per-addon.
@@ -3171,7 +3236,7 @@ var AddonInstaller = class AddonInstaller {
3171
3236
  force: true
3172
3237
  });
3173
3238
  ensureDir(targetDir);
3174
- await node_fs.promises.writeFile(node_path.join(targetDir, "package.json"), JSON.stringify(stripCamstackDeps(pkgData), null, 2));
3239
+ await node_fs.promises.writeFile(node_path.join(targetDir, "package.json"), JSON.stringify(stripBundledDeps(pkgData), null, 2));
3175
3240
  await copyDirRecursive(distDir, node_path.join(targetDir, "dist"));
3176
3241
  await copyExtraFileDirs(pkgData, sourceDir, targetDir);
3177
3242
  await node_fs.promises.writeFile(node_path.join(targetDir, ".install-source"), "local");
@@ -3180,7 +3245,7 @@ var AddonInstaller = class AddonInstaller {
3180
3245
  version: localPkgVersion,
3181
3246
  source: "local"
3182
3247
  });
3183
- const strippedDeps = stripCamstackDeps(pkgData);
3248
+ const strippedDeps = stripBundledDeps(pkgData);
3184
3249
  if (strippedDeps["dependencies"] && typeof strippedDeps["dependencies"] === "object" && Object.keys(strippedDeps["dependencies"]).length > 0) try {
3185
3250
  await execFileAsync("npm", [
3186
3251
  "install",
@@ -3358,7 +3423,7 @@ var AddonInstaller = class AddonInstaller {
3358
3423
  if (!pkgView) throw new Error(`Invalid package.json at ${pkgJsonPath}`);
3359
3424
  if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
3360
3425
  const targetDir = node_path.join(this.addonsDir, pkgView.name);
3361
- const strippedManifest = stripCamstackDeps(pkgView.raw);
3426
+ const strippedManifest = stripBundledDeps(pkgView.raw);
3362
3427
  await node_fs.promises.writeFile(pkgJsonPath, JSON.stringify(strippedManifest, null, 2));
3363
3428
  const strippedRuntimeDeps = strippedManifest["dependencies"];
3364
3429
  if (strippedRuntimeDeps != null && typeof strippedRuntimeDeps === "object" && Object.keys(strippedRuntimeDeps).length > 0) {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as errMsg$1, A as isVoidInput, Ct as resolveCapMount, F as looseSchema, M as lifecycleJobSchema, O as isArrayOutputSchema, St as readinessKey, V as procedureAuthKey, _t as expandCapMethods, bt as parseJsonObject, ct as ReadinessRegistry, dt as asJsonObject$1, ft as asNumber, ht as emitDownForOwnedCaps, j as kebabToCamel, k as isObjectInput, lt as ReadinessTimeoutError, mt as createEvent, nt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, o as METHOD_ACCESS_MAP, p as addonSettingsCapability, pt as asString$1, rt as DEVICE_STATUS_METHOD, s as RUNTIME_DEFAULTS, st as EventCategory$1, t as ALL_CAPABILITY_DEFINITIONS, tt as DATAPLANE_SECRET_HEADER$1, wt as scopeKey, xt as parseJsonUnknown$1 } from "./dist-CU3lb_Ys.mjs";
1
+ import { $ as errMsg$1, A as isVoidInput, Ct as resolveCapMount, F as looseSchema, M as lifecycleJobSchema, O as isArrayOutputSchema, St as readinessKey, V as procedureAuthKey, _t as expandCapMethods, bt as parseJsonObject, ct as ReadinessRegistry, dt as asJsonObject$1, ft as asNumber, ht as emitDownForOwnedCaps, j as kebabToCamel, k as isObjectInput, lt as ReadinessTimeoutError, mt as createEvent, nt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, o as METHOD_ACCESS_MAP, p as addonSettingsCapability, pt as asString$1, rt as DEVICE_STATUS_METHOD, s as RUNTIME_DEFAULTS, st as EventCategory$1, t as ALL_CAPABILITY_DEFINITIONS, tt as DATAPLANE_SECRET_HEADER$1, wt as scopeKey, xt as parseJsonUnknown$1 } from "./dist-C0d3UidT.mjs";
2
2
  import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-C-IHWnXx.mjs";
3
3
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-Le3l0qS2.mjs";
4
4
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
@@ -19,7 +19,7 @@ import { LocalAuthAddon, a as require_ms, c as __esmMin, d as __toCommonJS, f as
19
19
  import "./builtins/local-auth/index.mjs";
20
20
  import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
21
21
  import "./builtins/device-manager/index.mjs";
22
- import { $ as buildUdsNativeCapProxy, A as createParentUnownedCallHandler, B as AGENT_CAP_FWD_SERVICE, C as buildLinkChain, D as HUB_CAP_FWD_ACTION, E as localProviderLink, F as createUdsLoggerWithControl, G as createLocalTransport, H as CapRouteError, I as LocalChildClient, J as SocketChannel, K as UdsLocalTransportClient, L as LocalChildRegistry, M as createUdsEventBus, N as udsChildLogToWorkerEntry, O as HUB_CAP_FWD_SERVICE, P as createUdsLogger, Q as buildNativeCapProxy, R as UDS_NO_ROUTE_PREFIX, S as brokerTransportLink, T as ipcParentLink, U as classifyCapRoute, V as CapRouteResolver, W as callWithServiceDiscovery, X as FrameDecoder, Y as localEndpointPath, Z as encodeFrame, _ as resolveHwAccel, _t as CapabilityUnavailableError, a as getWorkerDeviceRegistry, at as createAddonService, b as getCapUsageRegistry, c as setHubConnected, ct as capActionName, d as getMoleculerEventStats, dt as capServiceName, et as createBrokerDeviceManagerApi, f as registerEventBusService, ft as parseCapAction, g as createKernelHwAccel, gt as CapabilityHandle, h as AddonDepsManager, ht as DeviceRegistry, i as createUdsAddonContext, j as createUdsEventBridge, k as createHubCapForwardService, l as EVENT_TOPIC_PREFIX, lt as capActionSuffix, m as subscribePassthrough, mt as serializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as validateProviderRegistrations, p as setNodeEventInterest, pt as deserializeTypedArrays, q as UdsLocalTransportServer, r as createAddonContext, rt as mountNativeCapService, s as getOrInitReadinessRegistryForClient, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestPythonDeps, u as getBrokerEventBus, ut as capBareAction, v as CapUsageRegistry, vt as installManifestNativeDeps, w as ipcChildLink, x as brokerCallForCap, y as __resetCapUsageRegistryForTests, yt as resolveAddonClass, z as AGENT_CAP_FWD_ACTION } from "./manifest-python-deps-4QvOLiBn.mjs";
22
+ import { $ as buildUdsNativeCapProxy, A as createParentUnownedCallHandler, B as AGENT_CAP_FWD_SERVICE, C as buildLinkChain, D as HUB_CAP_FWD_ACTION, E as localProviderLink, F as createUdsLoggerWithControl, G as createLocalTransport, H as CapRouteError, I as LocalChildClient, J as SocketChannel, K as UdsLocalTransportClient, L as LocalChildRegistry, M as createUdsEventBus, N as udsChildLogToWorkerEntry, O as HUB_CAP_FWD_SERVICE, P as createUdsLogger, Q as buildNativeCapProxy, R as UDS_NO_ROUTE_PREFIX, S as brokerTransportLink, T as ipcParentLink, U as classifyCapRoute, V as CapRouteResolver, W as callWithServiceDiscovery, X as FrameDecoder, Y as localEndpointPath, Z as encodeFrame, _ as resolveHwAccel, _t as CapabilityUnavailableError, a as getWorkerDeviceRegistry, at as createAddonService, b as getCapUsageRegistry, c as setHubConnected, ct as capActionName, d as getMoleculerEventStats, dt as capServiceName, et as createBrokerDeviceManagerApi, f as registerEventBusService, ft as parseCapAction, g as createKernelHwAccel, gt as CapabilityHandle, h as AddonDepsManager, ht as DeviceRegistry, i as createUdsAddonContext, j as createUdsEventBridge, k as createHubCapForwardService, l as EVENT_TOPIC_PREFIX, lt as capActionSuffix, m as subscribePassthrough, mt as serializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as validateProviderRegistrations, p as setNodeEventInterest, pt as deserializeTypedArrays, q as UdsLocalTransportServer, r as createAddonContext, rt as mountNativeCapService, s as getOrInitReadinessRegistryForClient, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestPythonDeps, u as getBrokerEventBus, ut as capBareAction, v as CapUsageRegistry, vt as installManifestNativeDeps, w as ipcChildLink, x as brokerCallForCap, y as __resetCapUsageRegistryForTests, yt as resolveAddonClass, z as AGENT_CAP_FWD_ACTION } from "./manifest-python-deps-6u3YnO2u.mjs";
23
23
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
24
24
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
25
25
  import { z } from "zod";
@@ -2579,6 +2579,71 @@ async function copyDirRecursive(src, dest) {
2579
2579
  });
2580
2580
  }
2581
2581
  /**
2582
+ * Runtime natives that must stay installable even when an addon forgets to
2583
+ * declare them: the mirror of the build preset's `COMMON_NATIVE_DEPS`
2584
+ * (tools/build/vite-lib.preset.ts — build tooling, not loadable at runtime).
2585
+ * These are the ONLY packages a self-contained dist genuinely `require()`s
2586
+ * from node_modules; everything else is already inlined by the bundle.
2587
+ */
2588
+ var RUNTIME_NATIVE_KEEP = [
2589
+ "better-sqlite3",
2590
+ "node-av",
2591
+ "onnxruntime-node",
2592
+ "sharp",
2593
+ "ssh2",
2594
+ "werift",
2595
+ "systeminformation"
2596
+ ];
2597
+ /** Read `camstack.runtimeDependencies` (extra non-native deps the dist
2598
+ * requires at runtime — versions come from the manifest's own dep maps). */
2599
+ function readRuntimeDependencies(pkg) {
2600
+ const camstack = pkg.camstack;
2601
+ if (camstack === null || typeof camstack !== "object" || Array.isArray(camstack)) return [];
2602
+ const raw = camstack.runtimeDependencies;
2603
+ if (!Array.isArray(raw)) return [];
2604
+ return raw.filter((entry) => typeof entry === "string");
2605
+ }
2606
+ /**
2607
+ * Strip every dependency an addon's self-contained dist has already bundled,
2608
+ * keeping ONLY what must resolve from node_modules at runtime:
2609
+ *
2610
+ * keep = RUNTIME_NATIVE_KEEP ∪ camstack.runtimeDependencies (minus @camstack/*)
2611
+ *
2612
+ * Addon dists are fully bundled (vite `externals: 'self-contained'`), yet the
2613
+ * blanket `npm install` of the manifest's dependencies re-installed the whole
2614
+ * tree the dist already contains — 98% of the deployed addons dir was
2615
+ * redundant node_modules (74k files / 1.34 GB on the live hub; it stalled the
2616
+ * backup walk and bloated every deploy). Kept PEER deps are folded into
2617
+ * `dependencies` so `--omit=peer` installs can't drop them (pipeline's
2618
+ * patched `werift`).
2619
+ *
2620
+ * Escape hatch: `CAMSTACK_INSTALL_FULL_DEPS=1` restores the legacy behaviour
2621
+ * (strip only @camstack/*) for debugging a suspected undeclared runtime dep.
2622
+ *
2623
+ * Returns a new object (immutable).
2624
+ */
2625
+ function stripBundledDeps(pkg) {
2626
+ if (process.env.CAMSTACK_INSTALL_FULL_DEPS === "1") return stripCamstackDeps(pkg);
2627
+ const camstack = pkg.camstack;
2628
+ if (camstack !== null && typeof camstack === "object" && !Array.isArray(camstack) && camstack.system === true) return stripCamstackDeps(pkg);
2629
+ const keep = new Set([...RUNTIME_NATIVE_KEEP, ...readRuntimeDependencies(pkg)]);
2630
+ const kept = {};
2631
+ for (const depType of ["dependencies", "peerDependencies"]) {
2632
+ const rawDeps = pkg[depType];
2633
+ if (rawDeps === null || typeof rawDeps !== "object" || Array.isArray(rawDeps)) continue;
2634
+ for (const [name, version] of Object.entries(rawDeps)) {
2635
+ if (name.startsWith("@camstack/")) continue;
2636
+ if (!keep.has(name) || typeof version !== "string") continue;
2637
+ if (!(name in kept)) kept[name] = version;
2638
+ }
2639
+ }
2640
+ const result = { ...pkg };
2641
+ result.dependencies = Object.keys(kept).length > 0 ? kept : void 0;
2642
+ result.peerDependencies = void 0;
2643
+ delete result.devDependencies;
2644
+ return result;
2645
+ }
2646
+ /**
2582
2647
  * Strip @camstack/* dependencies and devDependencies from a package.json object.
2583
2648
  * Used when installing addons into the addons directory — @camstack packages
2584
2649
  * are provided by the host runtime, not installed per-addon.
@@ -3163,7 +3228,7 @@ var AddonInstaller = class AddonInstaller {
3163
3228
  force: true
3164
3229
  });
3165
3230
  ensureDir(targetDir);
3166
- await fs$17.promises.writeFile(path$39.join(targetDir, "package.json"), JSON.stringify(stripCamstackDeps(pkgData), null, 2));
3231
+ await fs$17.promises.writeFile(path$39.join(targetDir, "package.json"), JSON.stringify(stripBundledDeps(pkgData), null, 2));
3167
3232
  await copyDirRecursive(distDir, path$39.join(targetDir, "dist"));
3168
3233
  await copyExtraFileDirs(pkgData, sourceDir, targetDir);
3169
3234
  await fs$17.promises.writeFile(path$39.join(targetDir, ".install-source"), "local");
@@ -3172,7 +3237,7 @@ var AddonInstaller = class AddonInstaller {
3172
3237
  version: localPkgVersion,
3173
3238
  source: "local"
3174
3239
  });
3175
- const strippedDeps = stripCamstackDeps(pkgData);
3240
+ const strippedDeps = stripBundledDeps(pkgData);
3176
3241
  if (strippedDeps["dependencies"] && typeof strippedDeps["dependencies"] === "object" && Object.keys(strippedDeps["dependencies"]).length > 0) try {
3177
3242
  await execFileAsync("npm", [
3178
3243
  "install",
@@ -3350,7 +3415,7 @@ var AddonInstaller = class AddonInstaller {
3350
3415
  if (!pkgView) throw new Error(`Invalid package.json at ${pkgJsonPath}`);
3351
3416
  if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
3352
3417
  const targetDir = path$39.join(this.addonsDir, pkgView.name);
3353
- const strippedManifest = stripCamstackDeps(pkgView.raw);
3418
+ const strippedManifest = stripBundledDeps(pkgView.raw);
3354
3419
  await fs$17.promises.writeFile(pkgJsonPath, JSON.stringify(strippedManifest, null, 2));
3355
3420
  const strippedRuntimeDeps = strippedManifest["dependencies"];
3356
3421
  if (strippedRuntimeDeps != null && typeof strippedRuntimeDeps === "object" && Object.keys(strippedRuntimeDeps).length > 0) {
@@ -14,6 +14,34 @@ export declare function ensureDir(dirPath: string): void;
14
14
  * `fs.promises.cp` keeps the I/O off the event loop. (Node ≥18 stable.)
15
15
  */
16
16
  export declare function copyDirRecursive(src: string, dest: string): Promise<void>;
17
+ /**
18
+ * Runtime natives that must stay installable even when an addon forgets to
19
+ * declare them: the mirror of the build preset's `COMMON_NATIVE_DEPS`
20
+ * (tools/build/vite-lib.preset.ts — build tooling, not loadable at runtime).
21
+ * These are the ONLY packages a self-contained dist genuinely `require()`s
22
+ * from node_modules; everything else is already inlined by the bundle.
23
+ */
24
+ export declare const RUNTIME_NATIVE_KEEP: readonly string[];
25
+ /**
26
+ * Strip every dependency an addon's self-contained dist has already bundled,
27
+ * keeping ONLY what must resolve from node_modules at runtime:
28
+ *
29
+ * keep = RUNTIME_NATIVE_KEEP ∪ camstack.runtimeDependencies (minus @camstack/*)
30
+ *
31
+ * Addon dists are fully bundled (vite `externals: 'self-contained'`), yet the
32
+ * blanket `npm install` of the manifest's dependencies re-installed the whole
33
+ * tree the dist already contains — 98% of the deployed addons dir was
34
+ * redundant node_modules (74k files / 1.34 GB on the live hub; it stalled the
35
+ * backup walk and bloated every deploy). Kept PEER deps are folded into
36
+ * `dependencies` so `--omit=peer` installs can't drop them (pipeline's
37
+ * patched `werift`).
38
+ *
39
+ * Escape hatch: `CAMSTACK_INSTALL_FULL_DEPS=1` restores the legacy behaviour
40
+ * (strip only @camstack/*) for debugging a suspected undeclared runtime dep.
41
+ *
42
+ * Returns a new object (immutable).
43
+ */
44
+ export declare function stripBundledDeps(pkg: Record<string, unknown>): Record<string, unknown>;
17
45
  /**
18
46
  * Strip @camstack/* dependencies and devDependencies from a package.json object.
19
47
  * Used when installing addons into the addons directory — @camstack packages
@@ -1,4 +1,4 @@
1
- import { H as readNodePin } from "./dist-CU3lb_Ys.mjs";
1
+ import { H as readNodePin } from "./dist-C0d3UidT.mjs";
2
2
  import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
3
3
  import { createServer } from "node:http";
4
4
  import * as fs from "node:fs";
@@ -4527,7 +4527,8 @@ var LocalChildClient = class {
4527
4527
  capName: msg.capName,
4528
4528
  method: msg.method,
4529
4529
  args: msg.args,
4530
- ...msg.deviceId !== void 0 ? { deviceId: msg.deviceId } : {}
4530
+ ...msg.deviceId !== void 0 ? { deviceId: msg.deviceId } : {},
4531
+ ...msg.addonId !== void 0 ? { addonId: msg.addonId } : {}
4531
4532
  });
4532
4533
  if (msg.kind === "addon-call") {
4533
4534
  if (this.addonCallHandler === null) throw new Error(`LocalChildClient: addon-call for "${msg.addonId}" arrived but no onAddonCall handler is registered`);
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_dist = require("./dist-fRVWwrkF.js");
2
+ const require_dist = require("./dist-D_ijwYtl.js");
3
3
  let _camstack_types_node = require("@camstack/types/node");
4
4
  let node_http = require("node:http");
5
5
  let node_fs = require("node:fs");
@@ -4529,7 +4529,8 @@ var LocalChildClient = class {
4529
4529
  capName: msg.capName,
4530
4530
  method: msg.method,
4531
4531
  args: msg.args,
4532
- ...msg.deviceId !== void 0 ? { deviceId: msg.deviceId } : {}
4532
+ ...msg.deviceId !== void 0 ? { deviceId: msg.deviceId } : {},
4533
+ ...msg.addonId !== void 0 ? { addonId: msg.addonId } : {}
4533
4534
  });
4534
4535
  if (msg.kind === "addon-call") {
4535
4536
  if (this.addonCallHandler === null) throw new Error(`LocalChildClient: addon-call for "${msg.addonId}" arrived but no onAddonCall handler is registered`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.1.38",
3
+ "version": "1.1.40",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",