@camstack/system 1.0.7 → 1.1.0

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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { a as ensureModel, c as isModelDownloaded, d as createAuthenticatedFileServer, f as parseRangeHeader, i as downloadModel, l as createFileDataPlaneHandler, m as resolveFilePath, n as deleteModelFromDisk, o as fetchJson, p as parseTokenizedUrl, r as downloadFile, s as getModelFilePath, t as ModelDownloadService, u as contentTypeFor } from "./model-download-service-C7AjBsX9.mjs";
2
+ import { a as ensureModel, c as isModelDownloaded, d as createAuthenticatedFileServer, f as parseRangeHeader, i as downloadModel, l as createFileDataPlaneHandler, m as resolveFilePath, n as deleteModelFromDisk, o as fetchJson, p as parseTokenizedUrl, r as downloadFile, s as getModelFilePath, t as ModelDownloadService, u as contentTypeFor } from "./model-download-service-RxAOiYvX.mjs";
3
3
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BkP504Vq.mjs";
4
4
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
5
5
  import { SqliteSettingsAddon, t as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
@@ -19,7 +19,7 @@ import { LocalAuthAddon, a as require_safe_buffer, i as AuthManager, n as ApiKey
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 { A as createUdsLoggerWithControl, B as createLocalTransport, C as ipcParentLink, D as createUdsEventBus, E as createUdsEventBridge, F as CapRouteError, G as FrameDecoder, H as UdsLocalTransportServer, I as classifyCapRoute, J as buildUdsNativeCapProxy, K as encodeFrame, L as LocalChildClient, M as AGENT_CAP_FWD_SERVICE, N as CapRouteResolver, O as udsChildLogToWorkerEntry, P as callWithServiceDiscovery, Q as mountNativeCapService, R as LocalChildRegistry, 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 AGENT_CAP_FWD_ACTION, 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 UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-CoJXeb9u.mjs";
22
+ import { A as createUdsLoggerWithControl, B as createLocalTransport, C as ipcParentLink, D as createUdsEventBus, E as createUdsEventBridge, F as CapRouteError, G as FrameDecoder, H as UdsLocalTransportServer, I as classifyCapRoute, J as buildUdsNativeCapProxy, K as encodeFrame, L as LocalChildClient, M as AGENT_CAP_FWD_SERVICE, N as CapRouteResolver, O as udsChildLogToWorkerEntry, P as callWithServiceDiscovery, Q as mountNativeCapService, R as LocalChildRegistry, 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 AGENT_CAP_FWD_ACTION, 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 UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-BcrTzHH_.mjs";
23
23
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
24
24
  import { n as require_src, t as require_graceful_fs } from "./graceful-fs-BoR9GuPS.mjs";
25
25
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
@@ -30,7 +30,7 @@ import * as path$40 from "node:path";
30
30
  import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
31
31
  import { DATAPLANE_SECRET_HEADER, EventCategory, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, asJsonObject, asNumber, asString, createEvent, emitDownForOwnedCaps, errMsg, lifecycleJobSchema, parseJsonObject, parseJsonUnknown, readinessKey, scopeKey } from "@camstack/types";
32
32
  import { X509Certificate, createHash, randomUUID, timingSafeEqual } from "node:crypto";
33
- import { execFile, execFileSync, spawn } from "node:child_process";
33
+ import { execFile, spawn } from "node:child_process";
34
34
  import * as util$8 from "node:util";
35
35
  import { promisify } from "node:util";
36
36
  import * as vm from "node:vm";
@@ -177,7 +177,7 @@ function proxyToUpstream(opts) {
177
177
  }
178
178
  //#endregion
179
179
  //#region src/python/python-env-manager.ts
180
- var execFileAsync$2 = promisify(execFile);
180
+ var execFileAsync$3 = promisify(execFile);
181
181
  var PythonEnvManager = class {
182
182
  venvPath;
183
183
  cachedProbe = null;
@@ -187,12 +187,12 @@ var PythonEnvManager = class {
187
187
  async probe() {
188
188
  if (this.cachedProbe) return this.cachedProbe;
189
189
  for (const cmd of ["python3", "python"]) try {
190
- const { stdout } = await execFileAsync$2(cmd, ["--version"]);
190
+ const { stdout } = await execFileAsync$3(cmd, ["--version"]);
191
191
  const version = stdout.trim().replace("Python ", "");
192
192
  const major = parseInt(version.split(".")[0] ?? "0", 10);
193
193
  const minor = parseInt(version.split(".")[1] ?? "0", 10);
194
194
  if (major < 3 || major === 3 && minor < 10) continue;
195
- const { stdout: pathOut } = await execFileAsync$2(cmd, ["-c", "import sys; print(sys.executable)"]);
195
+ const { stdout: pathOut } = await execFileAsync$3(cmd, ["-c", "import sys; print(sys.executable)"]);
196
196
  this.cachedProbe = {
197
197
  available: true,
198
198
  version,
@@ -208,13 +208,13 @@ var PythonEnvManager = class {
208
208
  async ensure(options) {
209
209
  const probe = await this.probe();
210
210
  if (!probe.available || !probe.path) throw new Error("Python 3.10+ is required but not found on this system");
211
- if (!fs$17.existsSync(path$40.join(this.venvPath, "bin", "python"))) await execFileAsync$2(probe.path, [
211
+ if (!fs$17.existsSync(path$40.join(this.venvPath, "bin", "python"))) await execFileAsync$3(probe.path, [
212
212
  "-m",
213
213
  "venv",
214
214
  this.venvPath
215
215
  ]);
216
216
  const venvPython = path$40.join(this.venvPath, "bin", "python");
217
- if (options.packages.length > 0) await execFileAsync$2(venvPython, [
217
+ if (options.packages.length > 0) await execFileAsync$3(venvPython, [
218
218
  "-m",
219
219
  "pip",
220
220
  "install",
@@ -1413,12 +1413,11 @@ var StorageManager = class {
1413
1413
  return namespace ? this.createNamespacedLocation(location, namespace) : location;
1414
1414
  }
1415
1415
  createLegacyShim() {
1416
- const self = this;
1417
1416
  return {
1418
1417
  async initialize() {},
1419
1418
  async shutdown() {},
1420
- getLocation(name) {
1421
- return self.getLocation(name);
1419
+ getLocation: (name) => {
1420
+ return this.getLocation(name);
1422
1421
  }
1423
1422
  };
1424
1423
  }
@@ -1694,7 +1693,7 @@ function matchPath(pattern, path) {
1694
1693
  }
1695
1694
  //#endregion
1696
1695
  //#region src/tls/cert-manager.ts
1697
- var execFileAsync$1 = promisify(execFile);
1696
+ var execFileAsync$2 = promisify(execFile);
1698
1697
  /**
1699
1698
  * Ensure a self-signed TLS certificate exists in the given directory.
1700
1699
  * Generates one if missing. Returns paths to cert and key files.
@@ -1731,7 +1730,7 @@ async function ensureTlsCert(dataDir, options) {
1731
1730
  for (const dns of sanDns) sanParts.push(`DNS:${dns}`);
1732
1731
  for (const ip of sanIps) sanParts.push(`IP:${ip}`);
1733
1732
  const sanString = sanParts.join(",");
1734
- await execFileAsync$1("openssl", [
1733
+ await execFileAsync$2("openssl", [
1735
1734
  "req",
1736
1735
  "-x509",
1737
1736
  "-newkey",
@@ -2564,6 +2563,7 @@ var AddonEngineManager = class {
2564
2563
  };
2565
2564
  //#endregion
2566
2565
  //#region src/kernel/fs-utils.ts
2566
+ var execFileAsync$1 = promisify(execFile);
2567
2567
  /**
2568
2568
  * Ensure a directory exists (recursive).
2569
2569
  * Single source of truth — replaces scattered mkdirSync calls.
@@ -2572,18 +2572,20 @@ function ensureDir(dirPath) {
2572
2572
  fs$17.mkdirSync(dirPath, { recursive: true });
2573
2573
  }
2574
2574
  /**
2575
- * Copy a directory recursively.
2576
- * Single source of truth — extracted from addon-installer + first-boot-installer.
2575
+ * Copy a directory recursively — ASYNC so it never blocks the event loop.
2576
+ *
2577
+ * The hub installs addons into `/data/addons`, which on Unraid is a slow
2578
+ * shfs/FUSE mount. The former synchronous `fs.copyFileSync`-per-file loop
2579
+ * blocked the Node event loop for the whole copy of a large bundle (e.g.
2580
+ * `addon-pipeline`), which froze the hub's HTTP listener mid-`camstack deploy`
2581
+ * (accept backlog piling up, existing connections stuck in CLOSE_WAIT). Using
2582
+ * `fs.promises.cp` keeps the I/O off the event loop. (Node ≥18 stable.)
2577
2583
  */
2578
- function copyDirRecursive(src, dest) {
2579
- ensureDir(dest);
2580
- const entries = fs$17.readdirSync(src, { withFileTypes: true });
2581
- for (const entry of entries) {
2582
- const srcPath = path$40.join(src, entry.name);
2583
- const destPath = path$40.join(dest, entry.name);
2584
- if (entry.isDirectory()) copyDirRecursive(srcPath, destPath);
2585
- else fs$17.copyFileSync(srcPath, destPath);
2586
- }
2584
+ async function copyDirRecursive(src, dest) {
2585
+ await fs$17.promises.cp(src, dest, {
2586
+ recursive: true,
2587
+ force: true
2588
+ });
2587
2589
  }
2588
2590
  /**
2589
2591
  * Strip @camstack/* dependencies and devDependencies from a package.json object.
@@ -2613,7 +2615,7 @@ function stripCamstackDeps(pkg) {
2613
2615
  * Copies directories (not individual files) from source to destination.
2614
2616
  * Skips "dist" (already handled) and glob patterns.
2615
2617
  */
2616
- function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
2618
+ async function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
2617
2619
  const rawFiles = pkgJson.files;
2618
2620
  if (!Array.isArray(rawFiles)) return;
2619
2621
  for (const fileEntry of rawFiles) {
@@ -2622,11 +2624,11 @@ function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
2622
2624
  const srcPath = path$40.join(sourceDir, fileEntry);
2623
2625
  if (!fs$17.existsSync(srcPath)) continue;
2624
2626
  const destPath = path$40.join(destDir, fileEntry);
2625
- const stat = fs$17.statSync(srcPath);
2626
- if (stat.isDirectory()) copyDirRecursive(srcPath, destPath);
2627
+ const stat = await fs$17.promises.stat(srcPath);
2628
+ if (stat.isDirectory()) await copyDirRecursive(srcPath, destPath);
2627
2629
  else if (stat.isFile()) {
2628
2630
  ensureDir(path$40.dirname(destPath));
2629
- fs$17.copyFileSync(srcPath, destPath);
2631
+ await fs$17.promises.copyFile(srcPath, destPath);
2630
2632
  }
2631
2633
  }
2632
2634
  }
@@ -2671,12 +2673,16 @@ function ensureLibraryBuilt(packageName, packagesDir) {
2671
2673
  /**
2672
2674
  * Install a single npm package into a target directory (package.json + dist/).
2673
2675
  * No validation on camstack.addons -- works for any @camstack/* package.
2674
- * Uses synchronous child_process calls (suitable for first-boot and update paths).
2676
+ *
2677
+ * ASYNC: uses `execFile`/`fs.promises` throughout so a package install on a
2678
+ * live node never blocks the event loop (the `npm pack` + tar extract + copy to
2679
+ * the slow shfs/FUSE `/data` would otherwise freeze the HTTP listener). See
2680
+ * `copyDirRecursive` for the wedge this prevents.
2675
2681
  */
2676
- function installPackageFromNpmSync(packageName, targetDir) {
2677
- const tmpDir = fs$17.mkdtempSync(path$40.join(os$17.tmpdir(), "camstack-install-"));
2682
+ async function installPackageFromNpm(packageName, targetDir) {
2683
+ const tmpDir = await fs$17.promises.mkdtemp(path$40.join(os$17.tmpdir(), "camstack-install-"));
2678
2684
  try {
2679
- const tgzFilename = execFileSync("npm", [
2685
+ const { stdout } = await execFileAsync$1("npm", [
2680
2686
  "pack",
2681
2687
  packageName,
2682
2688
  "--pack-destination",
@@ -2684,12 +2690,13 @@ function installPackageFromNpmSync(packageName, targetDir) {
2684
2690
  ], {
2685
2691
  timeout: 12e4,
2686
2692
  encoding: "utf-8"
2687
- }).trim().split("\n").pop()?.trim();
2693
+ });
2694
+ const tgzFilename = stdout.trim().split("\n").pop()?.trim();
2688
2695
  if (!tgzFilename) throw new Error("npm pack produced no output");
2689
2696
  const tgzPath = path$40.join(tmpDir, tgzFilename);
2690
2697
  const extractDir = path$40.join(tmpDir, "extracted");
2691
2698
  ensureDir(extractDir);
2692
- execFileSync("tar", [
2699
+ await execFileAsync$1("tar", [
2693
2700
  "-xzf",
2694
2701
  tgzPath,
2695
2702
  "-C",
@@ -2697,20 +2704,20 @@ function installPackageFromNpmSync(packageName, targetDir) {
2697
2704
  ], { timeout: 3e4 });
2698
2705
  const packageSubDir = path$40.join(extractDir, "package");
2699
2706
  const srcPkgJsonDir = fs$17.existsSync(path$40.join(packageSubDir, "package.json")) ? packageSubDir : extractDir;
2700
- fs$17.rmSync(targetDir, {
2707
+ await fs$17.promises.rm(targetDir, {
2701
2708
  recursive: true,
2702
2709
  force: true
2703
2710
  });
2704
2711
  ensureDir(targetDir);
2705
- fs$17.copyFileSync(path$40.join(srcPkgJsonDir, "package.json"), path$40.join(targetDir, "package.json"));
2712
+ await fs$17.promises.copyFile(path$40.join(srcPkgJsonDir, "package.json"), path$40.join(targetDir, "package.json"));
2706
2713
  const distSrc = path$40.join(srcPkgJsonDir, "dist");
2707
- if (fs$17.existsSync(distSrc)) copyDirRecursive(distSrc, path$40.join(targetDir, "dist"));
2714
+ if (fs$17.existsSync(distSrc)) await copyDirRecursive(distSrc, path$40.join(targetDir, "dist"));
2708
2715
  try {
2709
- const npmPkg = asJsonObject(parseJsonUnknown(fs$17.readFileSync(path$40.join(srcPkgJsonDir, "package.json"), "utf-8")));
2710
- if (npmPkg) copyExtraFileDirs(npmPkg, srcPkgJsonDir, targetDir);
2716
+ const npmPkg = asJsonObject(parseJsonUnknown(await fs$17.promises.readFile(path$40.join(srcPkgJsonDir, "package.json"), "utf-8")));
2717
+ if (npmPkg) await copyExtraFileDirs(npmPkg, srcPkgJsonDir, targetDir);
2711
2718
  } catch {}
2712
2719
  } finally {
2713
- fs$17.rmSync(tmpDir, {
2720
+ await fs$17.promises.rm(tmpDir, {
2714
2721
  recursive: true,
2715
2722
  force: true
2716
2723
  });
@@ -3160,15 +3167,15 @@ var AddonInstaller = class AddonInstaller {
3160
3167
  await this.ensureBuilt(packageName, sourceDir, pkgData);
3161
3168
  const distDir = path$40.join(sourceDir, "dist");
3162
3169
  if (!fs$17.existsSync(distDir)) throw new Error(`${packageName} has no dist/ after build`);
3163
- fs$17.rmSync(targetDir, {
3170
+ await fs$17.promises.rm(targetDir, {
3164
3171
  recursive: true,
3165
3172
  force: true
3166
3173
  });
3167
3174
  ensureDir(targetDir);
3168
- fs$17.writeFileSync(path$40.join(targetDir, "package.json"), JSON.stringify(stripCamstackDeps(pkgData), null, 2));
3169
- copyDirRecursive(distDir, path$40.join(targetDir, "dist"));
3170
- copyExtraFileDirs(pkgData, sourceDir, targetDir);
3171
- fs$17.writeFileSync(path$40.join(targetDir, ".install-source"), "local");
3175
+ await fs$17.promises.writeFile(path$40.join(targetDir, "package.json"), JSON.stringify(stripCamstackDeps(pkgData), null, 2));
3176
+ await copyDirRecursive(distDir, path$40.join(targetDir, "dist"));
3177
+ await copyExtraFileDirs(pkgData, sourceDir, targetDir);
3178
+ await fs$17.promises.writeFile(path$40.join(targetDir, ".install-source"), "local");
3172
3179
  const localPkgVersion = asString(pkgData.version, "0.0.0");
3173
3180
  this.manifest.upsert(packageName, {
3174
3181
  version: localPkgVersion,
@@ -3271,6 +3278,66 @@ var AddonInstaller = class AddonInstaller {
3271
3278
  fs$17.writeFileSync(tgzPath, buf);
3272
3279
  return tgzPath;
3273
3280
  }
3281
+ /**
3282
+ * Evict an existing install dir by atomic rename-aside, then best-effort
3283
+ * delete the aside copy.
3284
+ *
3285
+ * Why not a plain `fs.promises.rm(targetDir, {recursive})`: during a
3286
+ * `camstack deploy` the addon's live runner still holds its native
3287
+ * prebuilds (`.node`/`.so`) open. On the hub's shfs/FUSE `/data/addons`,
3288
+ * a recursive rm of a held subdir fails `ENOTEMPTY` mid-walk and leaves
3289
+ * the dir gutted. A rename only touches the dirent (metadata), so it
3290
+ * succeeds with the inodes still open — the old runner keeps its fds on
3291
+ * the moved inodes until it restarts.
3292
+ *
3293
+ * The aside copy is removed best-effort: the held native libs are only
3294
+ * released once the post-install `restartAddon` recycles the runner, so a
3295
+ * failure to delete it now must NOT fail the install. Any residue is swept
3296
+ * on the next deploy.
3297
+ */
3298
+ async evictInstallDir(targetDir) {
3299
+ if (!fs$17.existsSync(targetDir)) return;
3300
+ const graveyard = path$40.join(this.addonsDir, ".evicting");
3301
+ ensureDir(graveyard);
3302
+ await this.sweepGraveyard(graveyard);
3303
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3304
+ const flatName = path$40.relative(this.addonsDir, targetDir).replace(/[/\\]/g, "-");
3305
+ const asideDir = path$40.join(graveyard, `${flatName}-${ts}`);
3306
+ try {
3307
+ fs$17.renameSync(targetDir, asideDir);
3308
+ } catch (renameErr) {
3309
+ if (renameErr.code === "EXDEV") {
3310
+ await fs$17.promises.rm(targetDir, {
3311
+ recursive: true,
3312
+ force: true
3313
+ });
3314
+ return;
3315
+ }
3316
+ throw renameErr;
3317
+ }
3318
+ await fs$17.promises.rm(asideDir, {
3319
+ recursive: true,
3320
+ force: true
3321
+ }).catch((cleanupErr) => {
3322
+ this.logger.debug("evictInstallDir: aside cleanup deferred (likely held open)", { meta: {
3323
+ asideDir,
3324
+ error: errMsg(cleanupErr)
3325
+ } });
3326
+ });
3327
+ }
3328
+ /** Best-effort removal of leftover `.evicting/*` dirs from earlier deploys. */
3329
+ async sweepGraveyard(graveyard) {
3330
+ let entries;
3331
+ try {
3332
+ entries = fs$17.readdirSync(graveyard);
3333
+ } catch {
3334
+ return;
3335
+ }
3336
+ await Promise.all(entries.map((name) => fs$17.promises.rm(path$40.join(graveyard, name), {
3337
+ recursive: true,
3338
+ force: true
3339
+ }).catch(() => void 0)));
3340
+ }
3274
3341
  /** Install addon from a tgz file (uploaded or downloaded) */
3275
3342
  async installFromTgz(tgzPath) {
3276
3343
  const tmpDir = fs$17.mkdtempSync(path$40.join(os$17.tmpdir(), "camstack-addon-install-"));
@@ -3288,17 +3355,14 @@ var AddonInstaller = class AddonInstaller {
3288
3355
  if (!pkgView) throw new Error(`Invalid package.json at ${pkgJsonPath}`);
3289
3356
  if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
3290
3357
  const targetDir = path$40.join(this.addonsDir, pkgView.name);
3291
- fs$17.rmSync(targetDir, {
3292
- recursive: true,
3293
- force: true
3294
- });
3358
+ await this.evictInstallDir(targetDir);
3295
3359
  ensureDir(targetDir);
3296
3360
  const sourceDir = path$40.dirname(pkgJsonPath);
3297
3361
  const strippedManifest = stripCamstackDeps(pkgView.raw);
3298
- fs$17.writeFileSync(path$40.join(targetDir, "package.json"), JSON.stringify(strippedManifest, null, 2));
3362
+ await fs$17.promises.writeFile(path$40.join(targetDir, "package.json"), JSON.stringify(strippedManifest, null, 2));
3299
3363
  const sourceDist = path$40.join(sourceDir, "dist");
3300
- if (fs$17.existsSync(sourceDist)) copyDirRecursive(sourceDist, path$40.join(targetDir, "dist"));
3301
- copyExtraFileDirs(pkgView.raw, sourceDir, targetDir);
3364
+ if (fs$17.existsSync(sourceDist)) await copyDirRecursive(sourceDist, path$40.join(targetDir, "dist"));
3365
+ await copyExtraFileDirs(pkgView.raw, sourceDir, targetDir);
3302
3366
  const strippedRuntimeDeps = strippedManifest["dependencies"];
3303
3367
  if (strippedRuntimeDeps != null && typeof strippedRuntimeDeps === "object" && Object.keys(strippedRuntimeDeps).length > 0) {
3304
3368
  this.logger.info(`${pkgView.name} — installing runtime dependencies`, { meta: { targetDir } });
@@ -3322,7 +3386,7 @@ var AddonInstaller = class AddonInstaller {
3322
3386
  try {
3323
3387
  await installManifestNativeDeps(targetDir, pkgView.raw, this.logger, this.registry);
3324
3388
  } catch (nativeErr) {
3325
- fs$17.rmSync(targetDir, {
3389
+ await fs$17.promises.rm(targetDir, {
3326
3390
  recursive: true,
3327
3391
  force: true
3328
3392
  });
@@ -3337,7 +3401,7 @@ var AddonInstaller = class AddonInstaller {
3337
3401
  version: pkgView.version
3338
3402
  };
3339
3403
  } finally {
3340
- fs$17.rmSync(tmpDir, {
3404
+ await fs$17.promises.rm(tmpDir, {
3341
3405
  recursive: true,
3342
3406
  force: true
3343
3407
  });
@@ -4105,7 +4169,7 @@ var CapabilityRegistry = class CapabilityRegistry {
4105
4169
  const bare = this.bareAddonId(addonId);
4106
4170
  const nodeMap = this.singletonNodeOverrides.get(capabilityName);
4107
4171
  if (nodeMap) {
4108
- for (const [nodeId, ov] of [...nodeMap]) if (ov === bare) nodeMap.delete(nodeId);
4172
+ for (const [nodeId, ov] of Array.from(nodeMap)) if (ov === bare) nodeMap.delete(nodeId);
4109
4173
  if (nodeMap.size === 0) this.singletonNodeOverrides.delete(capabilityName);
4110
4174
  }
4111
4175
  this.logger.info("Provider unregistered from capability", {
@@ -7382,20 +7446,20 @@ var ConfigManager = class ConfigManager {
7382
7446
  setSettingsStore(store) {
7383
7447
  this.settingsStore = store;
7384
7448
  }
7385
- get(path) {
7386
- return this.resolveConfigValue(path);
7449
+ get(configPath) {
7450
+ return this.resolveConfigValue(configPath);
7387
7451
  }
7388
- resolveConfigValue(path) {
7389
- const bootstrapValue = this.getFromBootstrap(path);
7452
+ resolveConfigValue(configPath) {
7453
+ const bootstrapValue = this.getFromBootstrap(configPath);
7390
7454
  if (bootstrapValue !== void 0) return bootstrapValue;
7391
7455
  if (this.settingsStore !== null) {
7392
- const storeValue = this.settingsStore.getSystem(path);
7456
+ const storeValue = this.settingsStore.getSystem(configPath);
7393
7457
  if (storeValue !== void 0) return storeValue;
7394
- const nested = this.getNestedFromSystemSettings(path);
7458
+ const nested = this.getNestedFromSystemSettings(configPath);
7395
7459
  if (nested !== null) return nested;
7396
7460
  }
7397
- if (path in RUNTIME_DEFAULTS) return RUNTIME_DEFAULTS[path];
7398
- return this.getFromRuntimeDefaults(path) ?? void 0;
7461
+ if (configPath in RUNTIME_DEFAULTS) return RUNTIME_DEFAULTS[configPath];
7462
+ return this.getFromRuntimeDefaults(configPath) ?? void 0;
7399
7463
  }
7400
7464
  /**
7401
7465
  * Write a value to the settings-store.
@@ -7611,8 +7675,8 @@ var ConfigManager = class ConfigManager {
7611
7675
  };
7612
7676
  this.saveRuntimeState();
7613
7677
  }
7614
- getBootstrap(path) {
7615
- return this.getFromBootstrap(path);
7678
+ getBootstrap(configPath) {
7679
+ return this.getFromBootstrap(configPath);
7616
7680
  }
7617
7681
  /** Features accessor -- reads from settings-store when available, falls back to RUNTIME_DEFAULTS */
7618
7682
  get features() {
@@ -7702,8 +7766,8 @@ var ConfigManager = class ConfigManager {
7702
7766
  * Deep-set a value in a nested plain object using a dot-notation path.
7703
7767
  * Returns a new object (immutable).
7704
7768
  */
7705
- setNested(obj, path, value) {
7706
- const [head, ...rest] = path.split(".");
7769
+ setNested(obj, configPath, value) {
7770
+ const [head, ...rest] = configPath.split(".");
7707
7771
  if (!head) return obj;
7708
7772
  if (rest.length === 0) return {
7709
7773
  ...obj,
@@ -7743,8 +7807,8 @@ var ConfigManager = class ConfigManager {
7743
7807
  warnDefaultCredentials() {
7744
7808
  if (this.bootstrapConfig.auth.adminPassword === "changeme") console.warn("[ConfigManager] Warning: Using default admin password \"changeme\". Set auth.adminPassword in your config.yaml or the CAMSTACK_ADMIN_PASS env var.");
7745
7809
  }
7746
- getFromBootstrap(path) {
7747
- const keys = path.split(".");
7810
+ getFromBootstrap(configPath) {
7811
+ const keys = configPath.split(".");
7748
7812
  let current = this.bootstrapConfig;
7749
7813
  for (const key of keys) {
7750
7814
  if (!isRecord(current)) return void 0;
@@ -7752,8 +7816,8 @@ var ConfigManager = class ConfigManager {
7752
7816
  }
7753
7817
  return current;
7754
7818
  }
7755
- getFromRuntimeDefaults(path) {
7756
- const prefix = path + ".";
7819
+ getFromRuntimeDefaults(configPath) {
7820
+ const prefix = configPath + ".";
7757
7821
  const result = {};
7758
7822
  let found = false;
7759
7823
  for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) if (key.startsWith(prefix)) {
@@ -7768,10 +7832,10 @@ var ConfigManager = class ConfigManager {
7768
7832
  * e.g. path='features' matches keys 'features.streaming', 'features.notifications', etc.
7769
7833
  * Returns an object keyed by the sub-key, or undefined if nothing is found.
7770
7834
  */
7771
- getNestedFromSystemSettings(path) {
7835
+ getNestedFromSystemSettings(configPath) {
7772
7836
  if (this.settingsStore === null) return null;
7773
7837
  const all = this.settingsStore.getAllSystem();
7774
- const prefix = path + ".";
7838
+ const prefix = configPath + ".";
7775
7839
  const result = {};
7776
7840
  let found = false;
7777
7841
  for (const [key, value] of Object.entries(all)) if (key.startsWith(prefix)) {
@@ -89891,6 +89955,15 @@ function normalizeHubUrl(raw, defaultPort) {
89891
89955
  return `${/:\d+$/.test(hostPort) ? hostPort : `${hostPort}:${defaultPort}`}/${nodeId}`;
89892
89956
  }
89893
89957
  //#endregion
89958
+ //#region src/kernel/moleculer/addon-deploy-source.ts
89959
+ function isAddonDeploySource(v) {
89960
+ if (!v || typeof v !== "object") return false;
89961
+ const o = v;
89962
+ if (o["kind"] === "npm") return typeof o["version"] === "string";
89963
+ if (o["kind"] === "hub-http") return typeof o["url"] === "string" && typeof o["token"] === "string" && typeof o["sha256"] === "string" && typeof o["bytes"] === "number";
89964
+ return false;
89965
+ }
89966
+ //#endregion
89894
89967
  //#region src/kernel/moleculer/core-cap-service.ts
89895
89968
  /**
89896
89969
  * Default Moleculer service name for the hub core-capability bridge.
@@ -90932,7 +91005,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
90932
91005
  respawned.restartCount = prevRestartCount + 1;
90933
91006
  });
90934
91007
  restarted.push(name);
90935
- } catch (err) {
91008
+ } catch {
90936
91009
  failed.push(name);
90937
91010
  }
90938
91011
  return {
@@ -91327,8 +91400,8 @@ var LifecycleJobEngine = class {
91327
91400
  } finally {
91328
91401
  clearTimeout(timer);
91329
91402
  }
91330
- if (packages.length === 0) throw new Error("stageFramework returned no packages");
91331
91403
  const [firstPackage] = packages;
91404
+ if (!firstPackage) throw new Error("stageFramework returned no packages");
91332
91405
  this.advance(job.jobId, task, "staged", { stagedPath: firstPackage.stagedPath });
91333
91406
  requestFrameworkSwap({
91334
91407
  jobId: job.jobId,
@@ -91351,14 +91424,15 @@ var LifecycleJobEngine = class {
91351
91424
  */
91352
91425
  async fetchAddonsBounded(job, addonTasks) {
91353
91426
  const limit = Math.max(1, this.deps.fetchConcurrency ?? DEFAULT_FETCH_CONCURRENCY);
91354
- const outcomes = new Array(addonTasks.length);
91427
+ const outcomes = Array.from({ length: addonTasks.length });
91355
91428
  let nextIndex = 0;
91356
91429
  const worker = async () => {
91357
91430
  for (;;) {
91358
91431
  const index = nextIndex;
91359
91432
  nextIndex += 1;
91360
- if (index >= addonTasks.length) return;
91361
- outcomes[index] = await this.fetchAddonTask(job, addonTasks[index]);
91433
+ const addonTask = addonTasks[index];
91434
+ if (addonTask === void 0) return;
91435
+ outcomes[index] = await this.fetchAddonTask(job, addonTask);
91362
91436
  }
91363
91437
  };
91364
91438
  const workerCount = Math.min(limit, addonTasks.length);
@@ -91484,4 +91558,4 @@ async function stageFrameworkLockstep(input) {
91484
91558
  return results;
91485
91559
  }
91486
91560
  //#endregion
91487
- export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FRAMEWORK_LOCKSTEP, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterSecretMatches, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubService, createHwAccelService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensurePython, ensureTlsCert, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpmSync, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isClusterSecretMismatchError, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, stageFrameworkLockstep, stripCamstackDeps, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
91561
+ export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FRAMEWORK_LOCKSTEP, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterSecretMatches, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubService, createHwAccelService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensurePython, ensureTlsCert, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isClusterSecretMismatchError, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, stageFrameworkLockstep, stripCamstackDeps, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
@@ -167,6 +167,26 @@ export declare class AddonInstaller {
167
167
  * the legacy fallback path remains the right answer for those.
168
168
  */
169
169
  private downloadNpmTarball;
170
+ /**
171
+ * Evict an existing install dir by atomic rename-aside, then best-effort
172
+ * delete the aside copy.
173
+ *
174
+ * Why not a plain `fs.promises.rm(targetDir, {recursive})`: during a
175
+ * `camstack deploy` the addon's live runner still holds its native
176
+ * prebuilds (`.node`/`.so`) open. On the hub's shfs/FUSE `/data/addons`,
177
+ * a recursive rm of a held subdir fails `ENOTEMPTY` mid-walk and leaves
178
+ * the dir gutted. A rename only touches the dirent (metadata), so it
179
+ * succeeds with the inodes still open — the old runner keeps its fds on
180
+ * the moved inodes until it restarts.
181
+ *
182
+ * The aside copy is removed best-effort: the held native libs are only
183
+ * released once the post-install `restartAddon` recycles the runner, so a
184
+ * failure to delete it now must NOT fail the install. Any residue is swept
185
+ * on the next deploy.
186
+ */
187
+ private evictInstallDir;
188
+ /** Best-effort removal of leftover `.evicting/*` dirs from earlier deploys. */
189
+ private sweepGraveyard;
170
190
  /** Install addon from a tgz file (uploaded or downloaded) */
171
191
  installFromTgz(tgzPath: string): Promise<{
172
192
  name: string;
@@ -79,8 +79,8 @@ export declare class ConfigManager {
79
79
  * as a convenience alias for legacy call sites; it performs NO runtime
80
80
  * check and is the one documented type-level bridge in this function.
81
81
  */
82
- get(path: string): unknown;
83
- get<T>(path: string): T | undefined;
82
+ get(configPath: string): unknown;
83
+ get<T>(configPath: string): T | undefined;
84
84
  private resolveConfigValue;
85
85
  /**
86
86
  * Write a value to the settings-store.
@@ -171,8 +171,8 @@ export declare class ConfigManager {
171
171
  /** Get a value from the parsed bootstrap config.
172
172
  * Generic overload is a documented type-level bridge — callers are responsible
173
173
  * for passing a T that matches the config.yaml shape. */
174
- getBootstrap(path: string): unknown;
175
- getBootstrap<T>(path: string): T | undefined;
174
+ getBootstrap(configPath: string): unknown;
175
+ getBootstrap<T>(configPath: string): T | undefined;
176
176
  /** Features accessor -- reads from settings-store when available, falls back to RUNTIME_DEFAULTS */
177
177
  get features(): FeatureManifest;
178
178
  /**
@@ -4,10 +4,16 @@
4
4
  */
5
5
  export declare function ensureDir(dirPath: string): void;
6
6
  /**
7
- * Copy a directory recursively.
8
- * Single source of truth — extracted from addon-installer + first-boot-installer.
7
+ * Copy a directory recursively — ASYNC so it never blocks the event loop.
8
+ *
9
+ * The hub installs addons into `/data/addons`, which on Unraid is a slow
10
+ * shfs/FUSE mount. The former synchronous `fs.copyFileSync`-per-file loop
11
+ * blocked the Node event loop for the whole copy of a large bundle (e.g.
12
+ * `addon-pipeline`), which froze the hub's HTTP listener mid-`camstack deploy`
13
+ * (accept backlog piling up, existing connections stuck in CLOSE_WAIT). Using
14
+ * `fs.promises.cp` keeps the I/O off the event loop. (Node ≥18 stable.)
9
15
  */
10
- export declare function copyDirRecursive(src: string, dest: string): void;
16
+ export declare function copyDirRecursive(src: string, dest: string): Promise<void>;
11
17
  /**
12
18
  * Strip @camstack/* dependencies and devDependencies from a package.json object.
13
19
  * Used when installing addons into the addons directory — @camstack packages
@@ -21,7 +27,7 @@ export declare function stripCamstackDeps(pkg: Record<string, unknown>): Record<
21
27
  * Copies directories (not individual files) from source to destination.
22
28
  * Skips "dist" (already handled) and glob patterns.
23
29
  */
24
- export declare function copyExtraFileDirs(pkgJson: Record<string, unknown>, sourceDir: string, destDir: string): void;
30
+ export declare function copyExtraFileDirs(pkgJson: Record<string, unknown>, sourceDir: string, destDir: string): Promise<void>;
25
31
  /**
26
32
  * Check if any file in src/ is newer than dist/.
27
33
  * Returns true if rebuild is needed.
@@ -36,6 +42,10 @@ export declare function ensureLibraryBuilt(packageName: string, packagesDir: str
36
42
  /**
37
43
  * Install a single npm package into a target directory (package.json + dist/).
38
44
  * No validation on camstack.addons -- works for any @camstack/* package.
39
- * Uses synchronous child_process calls (suitable for first-boot and update paths).
45
+ *
46
+ * ASYNC: uses `execFile`/`fs.promises` throughout so a package install on a
47
+ * live node never blocks the event loop (the `npm pack` + tar extract + copy to
48
+ * the slow shfs/FUSE `/data` would otherwise freeze the HTTP listener). See
49
+ * `copyDirRecursive` for the wedge this prevents.
40
50
  */
41
- export declare function installPackageFromNpmSync(packageName: string, targetDir: string): void;
51
+ export declare function installPackageFromNpm(packageName: string, targetDir: string): Promise<void>;
@@ -5,7 +5,7 @@ export type { AddonInstallerConfig, InstallSource, InstalledPackage } from './ad
5
5
  export { AddonInstaller } from './addon-installer.js';
6
6
  export type { AddonInstallSource, AddonManifestEntry, AddonManifestFile } from './addon-manifest.js';
7
7
  export { AddonManifest } from './addon-manifest.js';
8
- export { copyDirRecursive, ensureDir, stripCamstackDeps, copyExtraFileDirs, ensureLibraryBuilt, installPackageFromNpmSync, isSourceNewer, } from './fs-utils.js';
8
+ export { copyDirRecursive, ensureDir, stripCamstackDeps, copyExtraFileDirs, ensureLibraryBuilt, installPackageFromNpm, isSourceNewer, } from './fs-utils.js';
9
9
  export { detectWorkspacePackagesDir } from './workspace-detect.js';
10
10
  export { RESTART_MARKER_FILE, clearPendingRestart, getRestartMarkerPath, readPendingRestart, scheduleSelfRestart, writePendingRestart, } from './restart-coordinator.js';
11
11
  export type { PendingRestartMarker, RestartKind, ScheduleSelfRestartOptions, } from './restart-coordinator.js';
@@ -27,6 +27,7 @@ export { buildStorageLocationRegistry } from './storage-location-registry.js';
27
27
  export type { StorageLocationRegistry } from './storage-location-registry.js';
28
28
  export { createBroker, deriveAgentListenPort } from './moleculer/broker-factory.js';
29
29
  export type { BrokerConfig } from './moleculer/broker-factory.js';
30
+ export { type AddonDeploySource, isAddonDeploySource } from './moleculer/addon-deploy-source.js';
30
31
  export { createAddonService, validateProviderRegistrations, } from './moleculer/addon-service-factory.js';
31
32
  export { createCoreCapService, CORE_CAP_SERVICE_NAME } from './moleculer/core-cap-service.js';
32
33
  export type { CoreCapAction, CoreCapServiceOptions } from './moleculer/core-cap-service.js';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Where an agent should fetch an addon's bytes from for a `$agent.deploy`.
3
+ * `npm`: the agent runs its own AddonInstaller pull. `hub-http`: the agent
4
+ * streams the staged tgz from the hub's one-time-token bundle route.
5
+ */
6
+ export type AddonDeploySource = {
7
+ readonly kind: 'npm';
8
+ readonly version: string;
9
+ } | {
10
+ readonly kind: 'hub-http';
11
+ readonly url: string;
12
+ readonly token: string;
13
+ readonly sha256: string;
14
+ readonly bytes: number;
15
+ };
16
+ export declare function isAddonDeploySource(v: unknown): v is AddonDeploySource;