@camstack/system 1.0.8 → 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/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/index.js +72 -5
- package/dist/index.mjs +72 -6
- package/dist/kernel/addon-installer.d.ts +20 -0
- package/dist/kernel/index.d.ts +1 -0
- package/dist/kernel/moleculer/addon-deploy-source.d.ts +16 -0
- package/dist/{model-download-service-JtVQtbb6.js → model-download-service-1eEOkNeS.js} +35 -5
- package/dist/{model-download-service-C7AjBsX9.mjs → model-download-service-RxAOiYvX.mjs} +35 -5
- package/package.json +1 -1
package/dist/addon-utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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-
|
|
3
|
+
const require_model_download_service = require("./model-download-service-1eEOkNeS.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;
|
package/dist/addon-utils.mjs
CHANGED
|
@@ -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-
|
|
1
|
+
import { a as ensureModel, c as isModelDownloaded, l as createFileDataPlaneHandler, n as deleteModelFromDisk, r as downloadFile, t as ModelDownloadService } from "./model-download-service-RxAOiYvX.mjs";
|
|
2
2
|
import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
|
|
3
3
|
export { CustomActionRegistry, ModelDownloadService, createFileDataPlaneHandler, deleteModelFromDisk, downloadFile, ensureModel, isModelDownloaded };
|
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-
|
|
3
|
+
const require_model_download_service = require("./model-download-service-1eEOkNeS.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");
|
|
@@ -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
|
|
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.
|
|
@@ -91755,6 +91821,7 @@ Object.defineProperty(exports, "installPythonRequirements", {
|
|
|
91755
91821
|
});
|
|
91756
91822
|
exports.ipcChildLink = require_manifest_python_deps.ipcChildLink;
|
|
91757
91823
|
exports.ipcParentLink = require_manifest_python_deps.ipcParentLink;
|
|
91824
|
+
exports.isAddonDeploySource = isAddonDeploySource;
|
|
91758
91825
|
exports.isClusterSecretMismatchError = isClusterSecretMismatchError;
|
|
91759
91826
|
exports.isInfraCapability = isInfraCapability;
|
|
91760
91827
|
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-
|
|
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";
|
|
@@ -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
|
|
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, 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;
|
package/dist/kernel/index.d.ts
CHANGED
|
@@ -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;
|
|
@@ -301,6 +301,24 @@ function createFileDataPlaneHandler(opts) {
|
|
|
301
301
|
}
|
|
302
302
|
//#endregion
|
|
303
303
|
//#region src/download/model-downloader.ts
|
|
304
|
+
function isNonEmptyFile(filePath) {
|
|
305
|
+
return node_fs.existsSync(filePath) && node_fs.statSync(filePath).size > 0;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Sibling files of a single-file (non-directory) format — extra files the
|
|
309
|
+
* format needs, fetched from the same remote directory as `url` and stored
|
|
310
|
+
* flat alongside the main file in `modelsDir`. Catalog-declared via
|
|
311
|
+
* `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
|
|
312
|
+
* `.xml`. The downloader stays format-agnostic; the convention lives in the
|
|
313
|
+
* catalog data (like `MLPACKAGE_FILES` for the directory case).
|
|
314
|
+
*/
|
|
315
|
+
function siblingFilesFor(formatEntry) {
|
|
316
|
+
return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
|
|
317
|
+
}
|
|
318
|
+
/** Resolve a sibling's remote URL relative to the main file's directory. */
|
|
319
|
+
function siblingUrl(mainUrl, sibling) {
|
|
320
|
+
return mainUrl.replace(/[^/]+$/, sibling);
|
|
321
|
+
}
|
|
304
322
|
/** Build fetch headers, including HF auth token for huggingface.co URLs */
|
|
305
323
|
function buildHeaders(url) {
|
|
306
324
|
const headers = { "User-Agent": "CamStack/1.0" };
|
|
@@ -466,14 +484,18 @@ async function ensureModel(modelsDir, entry, format, onProgress) {
|
|
|
466
484
|
if (entry.extraFiles) for (const extra of entry.extraFiles) await downloadFile(extra.url, node_path.join(modelsDir, extra.filename));
|
|
467
485
|
const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
|
|
468
486
|
const modelPath = node_path.join(modelsDir, filename);
|
|
487
|
+
const siblings = siblingFilesFor(formatEntry);
|
|
469
488
|
if (node_fs.existsSync(modelPath)) if (formatEntry.isDirectory && !node_fs.existsSync(node_path.join(modelPath, "Manifest.json"))) node_fs.rmSync(modelPath, {
|
|
470
489
|
recursive: true,
|
|
471
490
|
force: true
|
|
472
491
|
});
|
|
473
|
-
else return modelPath;
|
|
492
|
+
else if (siblings.some((f) => !isNonEmptyFile(node_path.join(modelsDir, f)))) {} else return modelPath;
|
|
474
493
|
node_fs.mkdirSync(modelsDir, { recursive: true });
|
|
475
494
|
if (formatEntry.isDirectory) await downloadDirectory(formatEntry.url, modelPath, formatEntry.files, onProgress);
|
|
476
|
-
else
|
|
495
|
+
else {
|
|
496
|
+
await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
|
|
497
|
+
for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), node_path.join(modelsDir, sibling));
|
|
498
|
+
}
|
|
477
499
|
return modelPath;
|
|
478
500
|
}
|
|
479
501
|
/** Compute the on-disk path for a given model + format, even when not yet downloaded. */
|
|
@@ -490,17 +512,25 @@ function isModelDownloaded(modelsDir, entry, format) {
|
|
|
490
512
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
491
513
|
if (!modelPath || !node_fs.existsSync(modelPath)) return false;
|
|
492
514
|
if (formatEntry.isDirectory) return node_fs.existsSync(node_path.join(modelPath, "Manifest.json"));
|
|
493
|
-
|
|
515
|
+
if (node_fs.statSync(modelPath).size <= 0) return false;
|
|
516
|
+
return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(node_path.join(modelsDir, f)));
|
|
494
517
|
}
|
|
495
518
|
/** Remove the on-disk model file/directory. Returns true if something was deleted. */
|
|
496
519
|
function deleteModelFromDisk(modelsDir, entry, format) {
|
|
497
520
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
498
521
|
if (!modelPath || !node_fs.existsSync(modelPath)) return false;
|
|
499
|
-
|
|
522
|
+
const formatEntry = entry.formats[format];
|
|
523
|
+
if (formatEntry?.isDirectory) node_fs.rmSync(modelPath, {
|
|
500
524
|
recursive: true,
|
|
501
525
|
force: true
|
|
502
526
|
});
|
|
503
|
-
else
|
|
527
|
+
else {
|
|
528
|
+
node_fs.unlinkSync(modelPath);
|
|
529
|
+
if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
|
|
530
|
+
const sibPath = node_path.join(modelsDir, sibling);
|
|
531
|
+
if (node_fs.existsSync(sibPath)) node_fs.unlinkSync(sibPath);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
504
534
|
return true;
|
|
505
535
|
}
|
|
506
536
|
//#endregion
|
|
@@ -300,6 +300,24 @@ function createFileDataPlaneHandler(opts) {
|
|
|
300
300
|
}
|
|
301
301
|
//#endregion
|
|
302
302
|
//#region src/download/model-downloader.ts
|
|
303
|
+
function isNonEmptyFile(filePath) {
|
|
304
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Sibling files of a single-file (non-directory) format — extra files the
|
|
308
|
+
* format needs, fetched from the same remote directory as `url` and stored
|
|
309
|
+
* flat alongside the main file in `modelsDir`. Catalog-declared via
|
|
310
|
+
* `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
|
|
311
|
+
* `.xml`. The downloader stays format-agnostic; the convention lives in the
|
|
312
|
+
* catalog data (like `MLPACKAGE_FILES` for the directory case).
|
|
313
|
+
*/
|
|
314
|
+
function siblingFilesFor(formatEntry) {
|
|
315
|
+
return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
|
|
316
|
+
}
|
|
317
|
+
/** Resolve a sibling's remote URL relative to the main file's directory. */
|
|
318
|
+
function siblingUrl(mainUrl, sibling) {
|
|
319
|
+
return mainUrl.replace(/[^/]+$/, sibling);
|
|
320
|
+
}
|
|
303
321
|
/** Build fetch headers, including HF auth token for huggingface.co URLs */
|
|
304
322
|
function buildHeaders(url) {
|
|
305
323
|
const headers = { "User-Agent": "CamStack/1.0" };
|
|
@@ -465,14 +483,18 @@ async function ensureModel(modelsDir, entry, format, onProgress) {
|
|
|
465
483
|
if (entry.extraFiles) for (const extra of entry.extraFiles) await downloadFile(extra.url, path$1.join(modelsDir, extra.filename));
|
|
466
484
|
const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
|
|
467
485
|
const modelPath = path$1.join(modelsDir, filename);
|
|
486
|
+
const siblings = siblingFilesFor(formatEntry);
|
|
468
487
|
if (fs.existsSync(modelPath)) if (formatEntry.isDirectory && !fs.existsSync(path$1.join(modelPath, "Manifest.json"))) fs.rmSync(modelPath, {
|
|
469
488
|
recursive: true,
|
|
470
489
|
force: true
|
|
471
490
|
});
|
|
472
|
-
else return modelPath;
|
|
491
|
+
else if (siblings.some((f) => !isNonEmptyFile(path$1.join(modelsDir, f)))) {} else return modelPath;
|
|
473
492
|
fs.mkdirSync(modelsDir, { recursive: true });
|
|
474
493
|
if (formatEntry.isDirectory) await downloadDirectory(formatEntry.url, modelPath, formatEntry.files, onProgress);
|
|
475
|
-
else
|
|
494
|
+
else {
|
|
495
|
+
await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
|
|
496
|
+
for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), path$1.join(modelsDir, sibling));
|
|
497
|
+
}
|
|
476
498
|
return modelPath;
|
|
477
499
|
}
|
|
478
500
|
/** Compute the on-disk path for a given model + format, even when not yet downloaded. */
|
|
@@ -489,17 +511,25 @@ function isModelDownloaded(modelsDir, entry, format) {
|
|
|
489
511
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
490
512
|
if (!modelPath || !fs.existsSync(modelPath)) return false;
|
|
491
513
|
if (formatEntry.isDirectory) return fs.existsSync(path$1.join(modelPath, "Manifest.json"));
|
|
492
|
-
|
|
514
|
+
if (fs.statSync(modelPath).size <= 0) return false;
|
|
515
|
+
return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(path$1.join(modelsDir, f)));
|
|
493
516
|
}
|
|
494
517
|
/** Remove the on-disk model file/directory. Returns true if something was deleted. */
|
|
495
518
|
function deleteModelFromDisk(modelsDir, entry, format) {
|
|
496
519
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
497
520
|
if (!modelPath || !fs.existsSync(modelPath)) return false;
|
|
498
|
-
|
|
521
|
+
const formatEntry = entry.formats[format];
|
|
522
|
+
if (formatEntry?.isDirectory) fs.rmSync(modelPath, {
|
|
499
523
|
recursive: true,
|
|
500
524
|
force: true
|
|
501
525
|
});
|
|
502
|
-
else
|
|
526
|
+
else {
|
|
527
|
+
fs.unlinkSync(modelPath);
|
|
528
|
+
if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
|
|
529
|
+
const sibPath = path$1.join(modelsDir, sibling);
|
|
530
|
+
if (fs.existsSync(sibPath)) fs.unlinkSync(sibPath);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
503
533
|
return true;
|
|
504
534
|
}
|
|
505
535
|
//#endregion
|