@camstack/system 1.0.8 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-BWURo7dc.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-RihGbmpb.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { $ as setWorkerNativeCapsChangeListener, A as createUdsLoggerWithControl, L as LocalChildClient, X as getWorkerNativeCapProvider, Z as getWorkerNativeCapSnapshot, i as createUdsAddonContext, mt as resolveAddonClass, pt as installManifestNativeDeps, t as installManifestPythonDeps, tt as validateProviderRegistrations } from "./manifest-python-deps-BcrTzHH_.mjs";
1
+ import { $ as setWorkerNativeCapsChangeListener, A as createUdsLoggerWithControl, X as getWorkerNativeCapProvider, Z as getWorkerNativeCapSnapshot, i as createUdsAddonContext, j as LocalChildClient, mt as resolveAddonClass, pt as installManifestNativeDeps, t as installManifestPythonDeps, tt as validateProviderRegistrations } from "./manifest-python-deps-8Wvwz-d1.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";
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * See memory: camstack_runner_ram_and_build_rootcause.
16
16
  */
17
- export { downloadFile, ensureModel, isModelDownloaded, deleteModelFromDisk, } from './download/model-downloader.js';
17
+ export { downloadFile, ensureModel, isModelDownloaded, deleteModelFromDisk, collectModelFiles, getModelFilePath, } from './download/model-downloader.js';
18
18
  export { ModelDownloadService } from './download/model-download-service.js';
19
19
  export { createFileDataPlaneHandler } from './http/file-data-plane.js';
20
20
  export { CustomActionRegistry } from './kernel/custom-action-registry.js';
@@ -1,11 +1,13 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("./chunk-Cek0wNdY.js");
3
- const require_model_download_service = require("./model-download-service-JtVQtbb6.js");
3
+ const require_model_download_service = require("./model-download-service-D-Umz4-L.js");
4
4
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
5
5
  exports.CustomActionRegistry = require_custom_action_registry.CustomActionRegistry;
6
6
  exports.ModelDownloadService = require_model_download_service.ModelDownloadService;
7
+ exports.collectModelFiles = require_model_download_service.collectModelFiles;
7
8
  exports.createFileDataPlaneHandler = require_model_download_service.createFileDataPlaneHandler;
8
9
  exports.deleteModelFromDisk = require_model_download_service.deleteModelFromDisk;
9
10
  exports.downloadFile = require_model_download_service.downloadFile;
10
11
  exports.ensureModel = require_model_download_service.ensureModel;
12
+ exports.getModelFilePath = require_model_download_service.getModelFilePath;
11
13
  exports.isModelDownloaded = require_model_download_service.isModelDownloaded;
@@ -1,3 +1,3 @@
1
- import { a as ensureModel, c as isModelDownloaded, l as createFileDataPlaneHandler, n as deleteModelFromDisk, r as downloadFile, t as ModelDownloadService } from "./model-download-service-C7AjBsX9.mjs";
1
+ import { c as getModelFilePath, i as downloadFile, l as isModelDownloaded, n as collectModelFiles, o as ensureModel, r as deleteModelFromDisk, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-C-IHWnXx.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
3
- export { CustomActionRegistry, ModelDownloadService, createFileDataPlaneHandler, deleteModelFromDisk, downloadFile, ensureModel, isModelDownloaded };
3
+ export { CustomActionRegistry, ModelDownloadService, collectModelFiles, createFileDataPlaneHandler, deleteModelFromDisk, downloadFile, ensureModel, getModelFilePath, isModelDownloaded };
@@ -1,4 +1,28 @@
1
- import { ModelCatalogEntry, ModelDownloadOptions, ModelDownloadResult, ModelFormat } from '@camstack/types';
1
+ import { ModelCatalogEntry, ModelDownloadOptions, ModelDownloadResult, ModelFormat, ModelFormatEntry } from '@camstack/types';
2
+ export declare function isNonEmptyFile(filePath: string): boolean;
3
+ /**
4
+ * Sibling files of a single-file (non-directory) format — extra files the
5
+ * format needs, fetched from the same remote directory as `url` and stored
6
+ * flat alongside the main file in `modelsDir`. Catalog-declared via
7
+ * `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
8
+ * `.xml`. The downloader stays format-agnostic; the convention lives in the
9
+ * catalog data (like `MLPACKAGE_FILES` for the directory case).
10
+ */
11
+ export declare function siblingFilesFor(formatEntry: ModelFormatEntry): readonly string[];
12
+ /** Resolve a sibling's remote URL relative to the main file's directory. */
13
+ export declare function siblingUrl(mainUrl: string, sibling: string): string;
14
+ /**
15
+ * The relative filenames a model format occupies under `modelsDir` — the main
16
+ * file (URL basename, or `${id}.${format}` fallback), its declared siblings
17
+ * (e.g. the OpenVINO `.bin` next to the `.xml`), and any `extraFiles`. For a
18
+ * directory bundle the single entry is the bundle dir itself (tar/extract
19
+ * recurse into it).
20
+ *
21
+ * Used by model distribution (P2): the hub tars exactly these names from its
22
+ * `modelsDir` and the agent untars them into its own `modelsDir`, so both ends
23
+ * agree on the on-disk layout without re-deriving it.
24
+ */
25
+ export declare function collectModelFiles(entry: ModelCatalogEntry, format: ModelFormat): string[];
2
26
  /**
3
27
  * Download a single file from a URL to a destination path.
4
28
  * Uses native fetch() (Node 22+) which handles redirects natively.
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export { proxyToUpstream } from './http/reverse-proxy.js';
9
9
  export type { ReverseProxyOptions } from './http/reverse-proxy.js';
10
10
  export { createFileDataPlaneHandler } from './http/file-data-plane.js';
11
11
  export type { FileDataPlaneOptions } from './http/file-data-plane.js';
12
- export { downloadModel, downloadFile, fetchJson, ensureModel, getModelFilePath, isModelDownloaded, deleteModelFromDisk, } from './download/model-downloader.js';
12
+ export { downloadModel, downloadFile, fetchJson, ensureModel, getModelFilePath, isModelDownloaded, deleteModelFromDisk, collectModelFiles, } from './download/model-downloader.js';
13
13
  export type { DownloadProgressCallback } from './download/model-downloader.js';
14
14
  export { ModelDownloadService } from './download/model-download-service.js';
15
15
  export { PythonEnvManager } from './python/python-env-manager.js';
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_model_download_service = require("./model-download-service-JtVQtbb6.js");
3
+ const require_model_download_service = require("./model-download-service-D-Umz4-L.js");
4
4
  const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
5
5
  const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
6
6
  const require_builtins_sqlite_storage_sqlite_settings_addon = require("./builtins/sqlite-storage/sqlite-settings.addon.js");
@@ -20,7 +20,7 @@ const require_builtins_local_auth_local_auth_addon = require("./builtins/local-a
20
20
  require("./builtins/local-auth/index.js");
21
21
  const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
22
22
  require("./builtins/device-manager/index.js");
23
- const require_manifest_python_deps = require("./manifest-python-deps-BWURo7dc.js");
23
+ const require_manifest_python_deps = require("./manifest-python-deps-RihGbmpb.js");
24
24
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
25
25
  const require_graceful_fs$1 = require("./graceful-fs-lg19SZNz.js");
26
26
  let _camstack_types_node = require("@camstack/types/node");
@@ -3284,6 +3284,66 @@ var AddonInstaller = class AddonInstaller {
3284
3284
  node_fs.writeFileSync(tgzPath, buf);
3285
3285
  return tgzPath;
3286
3286
  }
3287
+ /**
3288
+ * Evict an existing install dir by atomic rename-aside, then best-effort
3289
+ * delete the aside copy.
3290
+ *
3291
+ * Why not a plain `fs.promises.rm(targetDir, {recursive})`: during a
3292
+ * `camstack deploy` the addon's live runner still holds its native
3293
+ * prebuilds (`.node`/`.so`) open. On the hub's shfs/FUSE `/data/addons`,
3294
+ * a recursive rm of a held subdir fails `ENOTEMPTY` mid-walk and leaves
3295
+ * the dir gutted. A rename only touches the dirent (metadata), so it
3296
+ * succeeds with the inodes still open — the old runner keeps its fds on
3297
+ * the moved inodes until it restarts.
3298
+ *
3299
+ * The aside copy is removed best-effort: the held native libs are only
3300
+ * released once the post-install `restartAddon` recycles the runner, so a
3301
+ * failure to delete it now must NOT fail the install. Any residue is swept
3302
+ * on the next deploy.
3303
+ */
3304
+ async evictInstallDir(targetDir) {
3305
+ if (!node_fs.existsSync(targetDir)) return;
3306
+ const graveyard = node_path.join(this.addonsDir, ".evicting");
3307
+ ensureDir(graveyard);
3308
+ await this.sweepGraveyard(graveyard);
3309
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3310
+ const flatName = node_path.relative(this.addonsDir, targetDir).replace(/[/\\]/g, "-");
3311
+ const asideDir = node_path.join(graveyard, `${flatName}-${ts}`);
3312
+ try {
3313
+ node_fs.renameSync(targetDir, asideDir);
3314
+ } catch (renameErr) {
3315
+ if (renameErr.code === "EXDEV") {
3316
+ await node_fs.promises.rm(targetDir, {
3317
+ recursive: true,
3318
+ force: true
3319
+ });
3320
+ return;
3321
+ }
3322
+ throw renameErr;
3323
+ }
3324
+ await node_fs.promises.rm(asideDir, {
3325
+ recursive: true,
3326
+ force: true
3327
+ }).catch((cleanupErr) => {
3328
+ this.logger.debug("evictInstallDir: aside cleanup deferred (likely held open)", { meta: {
3329
+ asideDir,
3330
+ error: (0, _camstack_types.errMsg)(cleanupErr)
3331
+ } });
3332
+ });
3333
+ }
3334
+ /** Best-effort removal of leftover `.evicting/*` dirs from earlier deploys. */
3335
+ async sweepGraveyard(graveyard) {
3336
+ let entries;
3337
+ try {
3338
+ entries = node_fs.readdirSync(graveyard);
3339
+ } catch {
3340
+ return;
3341
+ }
3342
+ await Promise.all(entries.map((name) => node_fs.promises.rm(node_path.join(graveyard, name), {
3343
+ recursive: true,
3344
+ force: true
3345
+ }).catch(() => void 0)));
3346
+ }
3287
3347
  /** Install addon from a tgz file (uploaded or downloaded) */
3288
3348
  async installFromTgz(tgzPath) {
3289
3349
  const tmpDir = node_fs.mkdtempSync(node_path.join(node_os.tmpdir(), "camstack-addon-install-"));
@@ -3301,10 +3361,7 @@ var AddonInstaller = class AddonInstaller {
3301
3361
  if (!pkgView) throw new Error(`Invalid package.json at ${pkgJsonPath}`);
3302
3362
  if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
3303
3363
  const targetDir = node_path.join(this.addonsDir, pkgView.name);
3304
- await node_fs.promises.rm(targetDir, {
3305
- recursive: true,
3306
- force: true
3307
- });
3364
+ await this.evictInstallDir(targetDir);
3308
3365
  ensureDir(targetDir);
3309
3366
  const sourceDir = node_path.dirname(pkgJsonPath);
3310
3367
  const strippedManifest = stripCamstackDeps(pkgView.raw);
@@ -89904,6 +89961,15 @@ function normalizeHubUrl(raw, defaultPort) {
89904
89961
  return `${/:\d+$/.test(hostPort) ? hostPort : `${hostPort}:${defaultPort}`}/${nodeId}`;
89905
89962
  }
89906
89963
  //#endregion
89964
+ //#region src/kernel/moleculer/addon-deploy-source.ts
89965
+ function isAddonDeploySource(v) {
89966
+ if (!v || typeof v !== "object") return false;
89967
+ const o = v;
89968
+ if (o["kind"] === "npm") return typeof o["version"] === "string";
89969
+ if (o["kind"] === "hub-http") return typeof o["url"] === "string" && typeof o["token"] === "string" && typeof o["sha256"] === "string" && typeof o["bytes"] === "number";
89970
+ return false;
89971
+ }
89972
+ //#endregion
89907
89973
  //#region src/kernel/moleculer/core-cap-service.ts
89908
89974
  /**
89909
89975
  * Default Moleculer service name for the hub core-capability bridge.
@@ -91636,6 +91702,7 @@ exports.capServiceName = require_manifest_python_deps.capServiceName;
91636
91702
  exports.classifyCapRoute = require_manifest_python_deps.classifyCapRoute;
91637
91703
  exports.clearPendingRestart = clearPendingRestart;
91638
91704
  exports.clusterSecretMatches = clusterSecretMatches;
91705
+ exports.collectModelFiles = require_model_download_service.collectModelFiles;
91639
91706
  exports.contentTypeFor = require_model_download_service.contentTypeFor;
91640
91707
  exports.copyDirRecursive = copyDirRecursive;
91641
91708
  exports.copyExtraFileDirs = copyExtraFileDirs;
@@ -91755,6 +91822,7 @@ Object.defineProperty(exports, "installPythonRequirements", {
91755
91822
  });
91756
91823
  exports.ipcChildLink = require_manifest_python_deps.ipcChildLink;
91757
91824
  exports.ipcParentLink = require_manifest_python_deps.ipcParentLink;
91825
+ exports.isAddonDeploySource = isAddonDeploySource;
91758
91826
  exports.isClusterSecretMismatchError = isClusterSecretMismatchError;
91759
91827
  exports.isInfraCapability = isInfraCapability;
91760
91828
  exports.isModelDownloaded = require_model_download_service.isModelDownloaded;
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 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-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-BcrTzHH_.mjs";
22
+ import { A as createUdsLoggerWithControl, B as createLocalTransport, C as ipcParentLink, D as createUdsEventBus, E as createUdsEventBridge, F as AGENT_CAP_FWD_SERVICE, G as FrameDecoder, H as UdsLocalTransportServer, I as CapRouteResolver, J as buildUdsNativeCapProxy, K as encodeFrame, L as CapRouteError, M as LocalChildRegistry, N as UDS_NO_ROUTE_PREFIX, O as udsChildLogToWorkerEntry, P as AGENT_CAP_FWD_ACTION, Q as mountNativeCapService, R as classifyCapRoute, S as ipcChildLink, T as createParentUnownedCallHandler, U as SocketChannel, V as UdsLocalTransportClient, W as localEndpointPath, Y as createBrokerDeviceManagerApi, _ as __resetCapUsageRegistryForTests, a as getWorkerDeviceRegistry, at as capBareAction, b as brokerTransportLink, c as setHubConnected, ct as deserializeTypedArrays, d as registerEventBusService, dt as CapabilityHandle, et as createAddonService, f as AddonDepsManager, ft as CapabilityUnavailableError, g as CapUsageRegistry, h as createHwAccelService, i as createUdsAddonContext, it as capActionSuffix, j as LocalChildClient, k as createUdsLogger, l as EVENT_TOPIC_PREFIX, lt as serializeTypedArrays, m as resolveHwAccel, mt as resolveAddonClass, n as adaptBrokerToCluster, nt as NATIVE_PROVIDER_SERVICE_INFIX, o as getOrInitReadinessRegistry, ot as capServiceName, p as createKernelHwAccel, pt as installManifestNativeDeps, q as buildNativeCapProxy, r as createAddonContext, rt as capActionName, s as getOrInitReadinessRegistryForClient, st as parseCapAction, t as installManifestPythonDeps, tt as validateProviderRegistrations, u as getBrokerEventBus, ut as DeviceRegistry, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as callWithServiceDiscovery } from "./manifest-python-deps-8Wvwz-d1.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";
@@ -3278,6 +3278,66 @@ var AddonInstaller = class AddonInstaller {
3278
3278
  fs$17.writeFileSync(tgzPath, buf);
3279
3279
  return tgzPath;
3280
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
+ }
3281
3341
  /** Install addon from a tgz file (uploaded or downloaded) */
3282
3342
  async installFromTgz(tgzPath) {
3283
3343
  const tmpDir = fs$17.mkdtempSync(path$40.join(os$17.tmpdir(), "camstack-addon-install-"));
@@ -3295,10 +3355,7 @@ var AddonInstaller = class AddonInstaller {
3295
3355
  if (!pkgView) throw new Error(`Invalid package.json at ${pkgJsonPath}`);
3296
3356
  if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
3297
3357
  const targetDir = path$40.join(this.addonsDir, pkgView.name);
3298
- await fs$17.promises.rm(targetDir, {
3299
- recursive: true,
3300
- force: true
3301
- });
3358
+ await this.evictInstallDir(targetDir);
3302
3359
  ensureDir(targetDir);
3303
3360
  const sourceDir = path$40.dirname(pkgJsonPath);
3304
3361
  const strippedManifest = stripCamstackDeps(pkgView.raw);
@@ -89898,6 +89955,15 @@ function normalizeHubUrl(raw, defaultPort) {
89898
89955
  return `${/:\d+$/.test(hostPort) ? hostPort : `${hostPort}:${defaultPort}`}/${nodeId}`;
89899
89956
  }
89900
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
89901
89967
  //#region src/kernel/moleculer/core-cap-service.ts
89902
89968
  /**
89903
89969
  * Default Moleculer service name for the hub core-capability bridge.
@@ -91492,4 +91558,4 @@ async function stageFrameworkLockstep(input) {
91492
91558
  return results;
91493
91559
  }
91494
91560
  //#endregion
91495
- 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, 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, collectModelFiles, 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;
@@ -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;
@@ -19,6 +19,17 @@ import { LocalChildRegistry, CapCallInput } from '../transport/index.js';
19
19
  export interface LocalProviderResolver {
20
20
  /** First matching provider (active singleton or first collection member). */
21
21
  getByName(capabilityName: string): unknown | null;
22
+ /**
23
+ * The node this resolver's providers run on (e.g. `'hub'`, `'hub/model-studio'`,
24
+ * `'little-unraid/model-studio'`). When set, {@link localProviderLink} defers a
25
+ * call carrying an explicit `nodePin` to a DIFFERENT node instead of
26
+ * short-circuiting to a co-located provider — so the pin routes to the
27
+ * intended node (parent → `onUnownedCall` → CapRouteResolver). Symmetric to
28
+ * the LocalChildRegistry sibling-skip: an explicit pin must win over a
29
+ * same-named co-located provider. Omit for legacy/in-process callers that
30
+ * never pin a cap they themselves host.
31
+ */
32
+ localNodeId?: string;
22
33
  }
23
34
  /**
24
35
  * A single tRPC cap call observation. Recorded by `localProviderLink`
@@ -105,6 +105,21 @@ export declare const AGENT_CAP_FWD_SERVICE: "$agent-cap-fwd";
105
105
  /** The Moleculer action (service.action) for agent cap forwarding. */
106
106
  export declare const AGENT_CAP_FWD_ACTION: "$agent-cap-fwd.forward";
107
107
  export declare function extractDeviceId(args: unknown): number | undefined;
108
+ /**
109
+ * Extract an inline `nodeId` string from a cap call's args — the per-call node
110
+ * pin a forked addon expresses by carrying `nodeId` in the input (the SAME way
111
+ * device-scoped caps carry `deviceId`, and the way the generated cap-router on
112
+ * the hub already honours an inline pin). Mirrors {@link extractDeviceId}.
113
+ *
114
+ * This is the routing source for hub→remote-node EXECUTION pinning (e.g. the
115
+ * benchmark addon running a synthetic/decoder workload ON a chosen agent). The
116
+ * out-of-band `nodePin(op.context)` path does NOT survive the forked addon →
117
+ * hub UDS link chain, so `onUnownedCall` reads the inline pin instead. Provider
118
+ * methods that don't declare `nodeId` simply ignore the extra field (they
119
+ * destructure the fields they need); methods that DO declare it (e.g.
120
+ * `getEngineProvisioning({nodeId})`) still receive it unchanged.
121
+ */
122
+ export declare function extractNodeId(args: unknown): string | undefined;
108
123
  export declare class CapRouteResolver {
109
124
  private readonly hubNodeId;
110
125
  private readonly broker;