@camstack/agent 1.0.4 → 1.0.6
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/{chunk-5JHGVZFN.mjs → chunk-GM2IV7NO.mjs} +354 -111
- package/dist/chunk-GM2IV7NO.mjs.map +1 -0
- package/dist/cli.js +360 -117
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +370 -127
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-5JHGVZFN.mjs.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -27,8 +27,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var os3 = __toESM(require("os"));
|
|
28
28
|
|
|
29
29
|
// src/agent-bootstrap.ts
|
|
30
|
-
var
|
|
31
|
-
var
|
|
30
|
+
var fs6 = __toESM(require("fs"));
|
|
31
|
+
var path6 = __toESM(require("path"));
|
|
32
32
|
|
|
33
33
|
// src/agent-http.ts
|
|
34
34
|
var import_fastify = __toESM(require("fastify"));
|
|
@@ -219,8 +219,8 @@ async function startAgentHttpServer(getBroker, config) {
|
|
|
219
219
|
|
|
220
220
|
// src/agent-bootstrap.ts
|
|
221
221
|
var import_system3 = require("@camstack/system");
|
|
222
|
-
var import_types = require("@camstack/types");
|
|
223
222
|
var import_types2 = require("@camstack/types");
|
|
223
|
+
var import_types3 = require("@camstack/types");
|
|
224
224
|
|
|
225
225
|
// src/agent-config.ts
|
|
226
226
|
var fs2 = __toESM(require("fs"));
|
|
@@ -288,10 +288,87 @@ function loadAgentConfig(configPath, dataDirOverride) {
|
|
|
288
288
|
|
|
289
289
|
// src/agent-service.ts
|
|
290
290
|
var os2 = __toESM(require("os"));
|
|
291
|
-
var
|
|
292
|
-
var
|
|
291
|
+
var fs4 = __toESM(require("fs"));
|
|
292
|
+
var path4 = __toESM(require("path"));
|
|
293
293
|
var import_moleculer = require("moleculer");
|
|
294
294
|
var import_system = require("@camstack/system");
|
|
295
|
+
|
|
296
|
+
// src/agent-deploy-swap.ts
|
|
297
|
+
var fs3 = __toESM(require("fs"));
|
|
298
|
+
var path3 = __toESM(require("path"));
|
|
299
|
+
var import_node_crypto = require("crypto");
|
|
300
|
+
function rmrf(target) {
|
|
301
|
+
fs3.rmSync(target, { recursive: true, force: true });
|
|
302
|
+
}
|
|
303
|
+
function moveDir(from, to) {
|
|
304
|
+
try {
|
|
305
|
+
fs3.renameSync(from, to);
|
|
306
|
+
} catch (err) {
|
|
307
|
+
const code = err.code;
|
|
308
|
+
if (code === "EXDEV") {
|
|
309
|
+
fs3.cpSync(from, to, { recursive: true });
|
|
310
|
+
rmrf(from);
|
|
311
|
+
} else {
|
|
312
|
+
throw err;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function applyDeployedBundle(input) {
|
|
317
|
+
const { addonsDir, addonId, bundle, extract, logger } = input;
|
|
318
|
+
fs3.mkdirSync(addonsDir, { recursive: true });
|
|
319
|
+
const liveDir = path3.join(addonsDir, addonId);
|
|
320
|
+
const liveParent = path3.dirname(liveDir);
|
|
321
|
+
const liveBase = path3.basename(liveDir);
|
|
322
|
+
fs3.mkdirSync(liveParent, { recursive: true });
|
|
323
|
+
const token = (0, import_node_crypto.randomBytes)(6).toString("hex");
|
|
324
|
+
const nextDir = path3.join(liveParent, `.${liveBase}.next.${token}`);
|
|
325
|
+
const backupDir = path3.join(liveParent, `.${liveBase}.backup.${token}`);
|
|
326
|
+
fs3.mkdirSync(nextDir, { recursive: true });
|
|
327
|
+
try {
|
|
328
|
+
await extract(bundle, nextDir);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
rmrf(nextDir);
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
333
|
+
const hadLive = fs3.existsSync(liveDir);
|
|
334
|
+
if (hadLive) {
|
|
335
|
+
fs3.renameSync(liveDir, backupDir);
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
moveDir(nextDir, liveDir);
|
|
339
|
+
} catch (swapErr) {
|
|
340
|
+
if (hadLive) {
|
|
341
|
+
try {
|
|
342
|
+
if (fs3.existsSync(liveDir)) rmrf(liveDir);
|
|
343
|
+
fs3.renameSync(backupDir, liveDir);
|
|
344
|
+
} catch (restoreErr) {
|
|
345
|
+
logger.error(
|
|
346
|
+
`agent deploy swap: restore of "${addonId}" failed after a failed swap \u2014 manual recovery may be needed`,
|
|
347
|
+
{
|
|
348
|
+
meta: {
|
|
349
|
+
backupDir,
|
|
350
|
+
error: restoreErr instanceof Error ? restoreErr.message : String(restoreErr)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
rmrf(nextDir);
|
|
357
|
+
throw swapErr;
|
|
358
|
+
}
|
|
359
|
+
if (hadLive) rmrf(backupDir);
|
|
360
|
+
return { addonDir: liveDir };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/agent-service.ts
|
|
364
|
+
var deploySwapLogger = {
|
|
365
|
+
info: (msg) => console.log(`[Agent] ${msg}`),
|
|
366
|
+
warn: (msg) => console.warn(`[Agent] ${msg}`),
|
|
367
|
+
error: (msg) => console.error(`[Agent] ${msg}`),
|
|
368
|
+
debug: (msg) => console.debug(`[Agent] ${msg}`),
|
|
369
|
+
child: () => deploySwapLogger,
|
|
370
|
+
withTags: () => deploySwapLogger
|
|
371
|
+
};
|
|
295
372
|
function getLocalIps() {
|
|
296
373
|
const interfaces = os2.networkInterfaces();
|
|
297
374
|
const ips = [];
|
|
@@ -304,11 +381,23 @@ function getLocalIps() {
|
|
|
304
381
|
}
|
|
305
382
|
return ips;
|
|
306
383
|
}
|
|
384
|
+
async function withTimeout(p, ms, fallback) {
|
|
385
|
+
let timer;
|
|
386
|
+
const timeout = new Promise((resolve5) => {
|
|
387
|
+
timer = setTimeout(() => resolve5(fallback), ms);
|
|
388
|
+
});
|
|
389
|
+
try {
|
|
390
|
+
return await Promise.race([p, timeout]);
|
|
391
|
+
} finally {
|
|
392
|
+
if (timer) clearTimeout(timer);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
var METRICS_SNAPSHOT_TIMEOUT_MS = 2e3;
|
|
307
396
|
function readHubAddressFromConfig(configPath) {
|
|
308
397
|
if (!configPath) return null;
|
|
309
398
|
try {
|
|
310
|
-
if (!
|
|
311
|
-
const raw = JSON.parse(
|
|
399
|
+
if (!fs4.existsSync(configPath)) return null;
|
|
400
|
+
const raw = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
312
401
|
return typeof raw.hubAddress === "string" && raw.hubAddress.length > 0 ? raw.hubAddress : null;
|
|
313
402
|
} catch {
|
|
314
403
|
return null;
|
|
@@ -316,9 +405,9 @@ function readHubAddressFromConfig(configPath) {
|
|
|
316
405
|
}
|
|
317
406
|
function readDeployedAddonIds(addonDir) {
|
|
318
407
|
try {
|
|
319
|
-
const manifestPath =
|
|
320
|
-
if (!
|
|
321
|
-
const raw = JSON.parse(
|
|
408
|
+
const manifestPath = path4.join(addonDir, "package.json");
|
|
409
|
+
if (!fs4.existsSync(manifestPath)) return [];
|
|
410
|
+
const raw = JSON.parse(fs4.readFileSync(manifestPath, "utf-8"));
|
|
322
411
|
const entries = raw.camstack?.addons ?? [];
|
|
323
412
|
const ids = [];
|
|
324
413
|
for (const entry of entries) {
|
|
@@ -350,7 +439,11 @@ function createAgentService(deps) {
|
|
|
350
439
|
let memoryPercent = 0;
|
|
351
440
|
const metrics = deps.getMetricsProvider?.();
|
|
352
441
|
if (metrics) {
|
|
353
|
-
const snapshot = await
|
|
442
|
+
const snapshot = await withTimeout(
|
|
443
|
+
metrics.getCached(),
|
|
444
|
+
METRICS_SNAPSHOT_TIMEOUT_MS,
|
|
445
|
+
null
|
|
446
|
+
);
|
|
354
447
|
if (snapshot) {
|
|
355
448
|
cpuPercent = snapshot.cpu.total;
|
|
356
449
|
memoryPercent = snapshot.memory.percent;
|
|
@@ -373,7 +466,9 @@ function createAgentService(deps) {
|
|
|
373
466
|
addons: [...deps.loadedAddons.values()].map((a) => ({
|
|
374
467
|
id: a.id,
|
|
375
468
|
status: a.status,
|
|
376
|
-
version
|
|
469
|
+
// Report the PACKAGE version (falls back to the declaration
|
|
470
|
+
// version) so the hub's per-node update check is accurate.
|
|
471
|
+
version: a.packageVersion ?? a.version,
|
|
377
472
|
packageName: a.packageName
|
|
378
473
|
}))
|
|
379
474
|
};
|
|
@@ -387,7 +482,11 @@ function createAgentService(deps) {
|
|
|
387
482
|
const metrics = deps.getMetricsProvider?.();
|
|
388
483
|
if (metrics) {
|
|
389
484
|
try {
|
|
390
|
-
const snapshot = await
|
|
485
|
+
const snapshot = await withTimeout(
|
|
486
|
+
metrics.getCached(),
|
|
487
|
+
METRICS_SNAPSHOT_TIMEOUT_MS,
|
|
488
|
+
null
|
|
489
|
+
);
|
|
391
490
|
if (snapshot) {
|
|
392
491
|
cpuPercent = snapshot.cpu.total;
|
|
393
492
|
memoryPercent = snapshot.memory.percent;
|
|
@@ -437,17 +536,17 @@ function createAgentService(deps) {
|
|
|
437
536
|
deps.agentName = newName.trim();
|
|
438
537
|
broker.logger.info(`Agent renamed: "${oldName}" \u2192 "${deps.agentName}"`);
|
|
439
538
|
try {
|
|
440
|
-
const configFile =
|
|
539
|
+
const configFile = path4.resolve(deps.configPath);
|
|
441
540
|
let raw = {};
|
|
442
|
-
if (
|
|
541
|
+
if (fs4.existsSync(configFile)) {
|
|
443
542
|
try {
|
|
444
|
-
raw = JSON.parse(
|
|
543
|
+
raw = JSON.parse(fs4.readFileSync(configFile, "utf-8"));
|
|
445
544
|
} catch {
|
|
446
545
|
}
|
|
447
546
|
}
|
|
448
547
|
raw.name = deps.agentName;
|
|
449
|
-
|
|
450
|
-
|
|
548
|
+
fs4.mkdirSync(path4.dirname(configFile), { recursive: true });
|
|
549
|
+
fs4.writeFileSync(configFile, JSON.stringify(raw, null, 2), "utf-8");
|
|
451
550
|
broker.logger.info(`Agent name persisted to ${configFile}`);
|
|
452
551
|
} catch (err) {
|
|
453
552
|
broker.logger.warn(
|
|
@@ -467,20 +566,31 @@ function createAgentService(deps) {
|
|
|
467
566
|
handler: async (ctx) => {
|
|
468
567
|
const { params } = ctx;
|
|
469
568
|
const { addonId, bundle } = params;
|
|
470
|
-
const addonDir = path3.join(deps.addonsDir, addonId);
|
|
471
|
-
fs3.mkdirSync(deps.addonsDir, { recursive: true });
|
|
472
|
-
const bundlePath = path3.join(deps.addonsDir, `${addonId}.tgz`);
|
|
473
569
|
const bufferData = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
|
|
474
|
-
|
|
475
|
-
const {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
570
|
+
const { execFile } = await import("child_process");
|
|
571
|
+
const { promisify } = await import("util");
|
|
572
|
+
const execFileAsync = promisify(execFile);
|
|
573
|
+
const extract = async (tgz, destDir) => {
|
|
574
|
+
const tgzPath = path4.join(destDir, "..", `.${path4.basename(destDir)}.tgz`);
|
|
575
|
+
fs4.writeFileSync(tgzPath, tgz);
|
|
576
|
+
try {
|
|
577
|
+
await execFileAsync("tar", ["-xzf", tgzPath, "-C", destDir, "--strip-components=1"], {
|
|
578
|
+
timeout: 6e4
|
|
579
|
+
});
|
|
580
|
+
} finally {
|
|
581
|
+
try {
|
|
582
|
+
fs4.unlinkSync(tgzPath);
|
|
583
|
+
} catch {
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
const { addonDir } = await applyDeployedBundle({
|
|
588
|
+
addonsDir: deps.addonsDir,
|
|
589
|
+
addonId,
|
|
590
|
+
bundle: bufferData,
|
|
591
|
+
extract,
|
|
592
|
+
logger: deploySwapLogger
|
|
482
593
|
});
|
|
483
|
-
fs3.unlinkSync(bundlePath);
|
|
484
594
|
for (const declId of readDeployedAddonIds(addonDir)) {
|
|
485
595
|
deps.loadedAddons.delete(declId);
|
|
486
596
|
}
|
|
@@ -527,15 +637,15 @@ function createAgentService(deps) {
|
|
|
527
637
|
} catch {
|
|
528
638
|
}
|
|
529
639
|
deps.loadedAddons.delete(addonId);
|
|
530
|
-
const addonDir =
|
|
531
|
-
if (
|
|
532
|
-
|
|
640
|
+
const addonDir = path4.join(deps.addonsDir, addonId);
|
|
641
|
+
if (fs4.existsSync(addonDir)) {
|
|
642
|
+
fs4.rmSync(addonDir, { recursive: true, force: true });
|
|
533
643
|
}
|
|
534
644
|
const pkgName = entry?.packageName;
|
|
535
645
|
if (pkgName && pkgName !== addonId) {
|
|
536
|
-
const pkgDir =
|
|
537
|
-
if (
|
|
538
|
-
|
|
646
|
+
const pkgDir = path4.join(deps.addonsDir, pkgName);
|
|
647
|
+
if (fs4.existsSync(pkgDir)) {
|
|
648
|
+
fs4.rmSync(pkgDir, { recursive: true, force: true });
|
|
539
649
|
}
|
|
540
650
|
}
|
|
541
651
|
broker.logger.info(`$agent.undeploy: ${addonId} disposed (instance + service + folder)`);
|
|
@@ -589,6 +699,84 @@ function createAgentService(deps) {
|
|
|
589
699
|
};
|
|
590
700
|
}
|
|
591
701
|
|
|
702
|
+
// src/agent-group-runner.ts
|
|
703
|
+
var fs5 = __toESM(require("fs"));
|
|
704
|
+
var path5 = __toESM(require("path"));
|
|
705
|
+
var import_types = require("@camstack/types");
|
|
706
|
+
function readPackageManifestAddons(dir) {
|
|
707
|
+
try {
|
|
708
|
+
const pkgPath = path5.join(dir, "package.json");
|
|
709
|
+
if (!fs5.existsSync(pkgPath)) return null;
|
|
710
|
+
const raw = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
711
|
+
const packageName = typeof raw.name === "string" ? raw.name : "";
|
|
712
|
+
const packageVersion = typeof raw.version === "string" ? raw.version : "0.0.0";
|
|
713
|
+
const entries = Array.isArray(raw.camstack?.addons) ? raw.camstack.addons : [];
|
|
714
|
+
const declarations = [];
|
|
715
|
+
for (const entry of entries) {
|
|
716
|
+
if (entry !== null && typeof entry === "object" && typeof entry.id === "string") {
|
|
717
|
+
declarations.push(entry);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (!packageName || declarations.length === 0) return null;
|
|
721
|
+
return { packageName, packageVersion, declarations };
|
|
722
|
+
} catch {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function isGroupRunnerAddon(decl) {
|
|
727
|
+
return decl.execution !== void 0 && (0, import_types.resolveAddonPlacement)(decl) !== "hub-only";
|
|
728
|
+
}
|
|
729
|
+
function registerGroupRunnerProviders(deps, addons) {
|
|
730
|
+
for (const a of addons) {
|
|
731
|
+
for (const cap of a.capabilities) {
|
|
732
|
+
const capName = typeof cap === "string" ? cap : cap.name;
|
|
733
|
+
const proxy = new Proxy(
|
|
734
|
+
{},
|
|
735
|
+
{
|
|
736
|
+
get(_target, prop) {
|
|
737
|
+
if (prop === "then" || typeof prop === "symbol") return void 0;
|
|
738
|
+
return (params) => deps.broker.call(`${a.addonId}.${capName}.${prop}`, params);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
);
|
|
742
|
+
deps.capabilityRegistry.registerProvider(capName, a.addonId, proxy);
|
|
743
|
+
}
|
|
744
|
+
deps.loadedAddons.set(a.addonId, {
|
|
745
|
+
id: a.addonId,
|
|
746
|
+
status: "running",
|
|
747
|
+
version: a.version,
|
|
748
|
+
packageName: a.packageName,
|
|
749
|
+
packageVersion: a.packageVersion
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
async function ensureGroupRunner(deps, groupId, addons) {
|
|
754
|
+
try {
|
|
755
|
+
const restart = await deps.broker.call("$process.restart", { name: groupId });
|
|
756
|
+
if (!restart.success) {
|
|
757
|
+
if (restart.reason !== void 0 && restart.reason !== "not found") {
|
|
758
|
+
deps.logger.warn(
|
|
759
|
+
`group "${groupId}" restart returned "${restart.reason}" \u2014 spawning a fresh runner`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
await deps.broker.call("$process.spawnRunner", {
|
|
763
|
+
runnerId: groupId,
|
|
764
|
+
addons: addons.map((a) => ({ addonId: a.addonId, addonDir: a.addonDir }))
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
registerGroupRunnerProviders(deps, addons);
|
|
768
|
+
deps.logger.info(
|
|
769
|
+
`group "${groupId}" live with ${addons.length} addon(s): ${addons.map((a) => a.addonId).join(", ")}`
|
|
770
|
+
);
|
|
771
|
+
} catch (err) {
|
|
772
|
+
const msg = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
773
|
+
deps.logger.error(`failed to ensure group "${groupId}": ${msg}`);
|
|
774
|
+
for (const a of addons) {
|
|
775
|
+
deps.loadedAddons.set(a.addonId, { id: a.addonId, status: "error" });
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
592
780
|
// src/agent-cap-dispatch-service.ts
|
|
593
781
|
var import_system2 = require("@camstack/system");
|
|
594
782
|
function narrowParams(raw) {
|
|
@@ -670,7 +858,7 @@ async function startAgent(configPath) {
|
|
|
670
858
|
const bundledDir = process.env["CAMSTACK_BUNDLED_ADDONS_DIR"];
|
|
671
859
|
let workspaceDir = null;
|
|
672
860
|
let resolvedSource = explicitSource;
|
|
673
|
-
if (bundledDir &&
|
|
861
|
+
if (bundledDir && fs6.existsSync(bundledDir)) {
|
|
674
862
|
workspaceDir = bundledDir;
|
|
675
863
|
resolvedSource = "local";
|
|
676
864
|
console.log(`[Agent] Using bundled addons from ${bundledDir}`);
|
|
@@ -789,17 +977,17 @@ async function startAgent(configPath) {
|
|
|
789
977
|
};
|
|
790
978
|
const capabilityRegistry = new import_system3.CapabilityRegistry(consoleLogger);
|
|
791
979
|
const agentCapabilities = [
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
980
|
+
import_types3.storageCapability,
|
|
981
|
+
import_types3.storageProviderCapability,
|
|
982
|
+
import_types3.settingsStoreCapability,
|
|
983
|
+
import_types3.logDestinationCapability,
|
|
984
|
+
import_types3.metricsProviderCapability,
|
|
985
|
+
import_types3.decoderCapability,
|
|
986
|
+
import_types3.motionDetectionCapability,
|
|
987
|
+
import_types3.pipelineExecutorCapability,
|
|
988
|
+
import_types3.pipelineRunnerCapability,
|
|
989
|
+
import_types3.audioAnalyzerCapability,
|
|
990
|
+
import_types3.platformProbeCapability
|
|
803
991
|
];
|
|
804
992
|
for (const cap of agentCapabilities) {
|
|
805
993
|
capabilityRegistry.declareCapability(cap);
|
|
@@ -998,7 +1186,7 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
|
|
|
998
1186
|
try {
|
|
999
1187
|
await loader.loadFromAddonDir(dir);
|
|
1000
1188
|
} catch (err) {
|
|
1001
|
-
console.warn(`[Agent] Failed to scan ${dir}: ${(0,
|
|
1189
|
+
console.warn(`[Agent] Failed to scan ${dir}: ${(0, import_types3.errMsg)(err)}`);
|
|
1002
1190
|
}
|
|
1003
1191
|
}
|
|
1004
1192
|
for (const infra of AGENT_INFRA) {
|
|
@@ -1027,10 +1215,15 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
|
|
|
1027
1215
|
storageProvider,
|
|
1028
1216
|
addonConfig: { rootPath: config.dataDir },
|
|
1029
1217
|
createLogger: loggerFactory,
|
|
1030
|
-
capabilityRegistry: registry
|
|
1218
|
+
capabilityRegistry: registry,
|
|
1219
|
+
// Feed the storage-orchestrator its first-boot seed declarations
|
|
1220
|
+
// (addons-data:default, models:default, …). Without this the agent's
|
|
1221
|
+
// sqlite-settings aborts boot: "No default storage location
|
|
1222
|
+
// configured for type addons-data" → fatal crash-loop.
|
|
1223
|
+
listStorageLocationDeclarations: () => loader.listStorageLocationDeclarations()
|
|
1031
1224
|
}
|
|
1032
1225
|
);
|
|
1033
|
-
const initResult = (0,
|
|
1226
|
+
const initResult = (0, import_types2.normalizeAddonInitResult)(await instance.initialize(context));
|
|
1034
1227
|
for (const reg of initResult?.providers ?? []) {
|
|
1035
1228
|
const capName = reg.capability.name;
|
|
1036
1229
|
registry.registerProvider(capName, addonId, reg.provider);
|
|
@@ -1041,11 +1234,12 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
|
|
|
1041
1234
|
status: "running",
|
|
1042
1235
|
version: addon.declaration.version,
|
|
1043
1236
|
packageName: addon.packageName,
|
|
1237
|
+
packageVersion: addon.packageVersion,
|
|
1044
1238
|
addon: instance
|
|
1045
1239
|
});
|
|
1046
1240
|
console.log(`[Agent] Core addon "${addonId}" initialized`);
|
|
1047
1241
|
} catch (err) {
|
|
1048
|
-
const msg = (0,
|
|
1242
|
+
const msg = (0, import_types3.errMsg)(err);
|
|
1049
1243
|
console.error(`[Agent] Failed to initialize core addon "${addonId}": ${msg}`);
|
|
1050
1244
|
if (infra.required) {
|
|
1051
1245
|
throw new Error(`Required infrastructure addon "${addonId}" failed: ${msg}`, { cause: err });
|
|
@@ -1063,21 +1257,22 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
|
|
|
1063
1257
|
try {
|
|
1064
1258
|
await loader.loadFromAddonDir(dir);
|
|
1065
1259
|
} catch (err) {
|
|
1066
|
-
console.warn(`[Agent] Skipping ${dir}: ${(0,
|
|
1260
|
+
console.warn(`[Agent] Skipping ${dir}: ${(0, import_types3.errMsg)(err)}`);
|
|
1067
1261
|
continue;
|
|
1068
1262
|
}
|
|
1069
1263
|
dirToLoader.set(dir, loader);
|
|
1070
1264
|
for (const registered of loader.listAddons()) {
|
|
1071
1265
|
if (loadedAddons.has(registered.declaration.id)) continue;
|
|
1072
1266
|
if (registered.declaration.execution === void 0) continue;
|
|
1073
|
-
const placement = (0,
|
|
1267
|
+
const placement = (0, import_types2.resolveAddonPlacement)(registered.declaration);
|
|
1074
1268
|
if (placement === "hub-only") continue;
|
|
1075
1269
|
allGroupCandidates.push({
|
|
1076
|
-
groupId: (0,
|
|
1270
|
+
groupId: (0, import_types2.resolveRunnerId)(registered.declaration, registered.declaration.id),
|
|
1077
1271
|
addonId: registered.declaration.id,
|
|
1078
1272
|
addonDir: dir,
|
|
1079
1273
|
version: registered.declaration.version ?? "0.0.0",
|
|
1080
1274
|
packageName: registered.packageName,
|
|
1275
|
+
packageVersion: registered.packageVersion,
|
|
1081
1276
|
capabilities: registered.declaration.capabilities ?? []
|
|
1082
1277
|
});
|
|
1083
1278
|
}
|
|
@@ -1095,27 +1290,7 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
|
|
|
1095
1290
|
runnerId: groupId,
|
|
1096
1291
|
addons: addons.map((a) => ({ addonId: a.addonId, addonDir: a.addonDir }))
|
|
1097
1292
|
});
|
|
1098
|
-
|
|
1099
|
-
for (const cap of a.capabilities) {
|
|
1100
|
-
const capName = typeof cap === "string" ? cap : cap.name;
|
|
1101
|
-
const proxy = new Proxy(
|
|
1102
|
-
{},
|
|
1103
|
-
{
|
|
1104
|
-
get(_target, prop) {
|
|
1105
|
-
if (prop === "then" || typeof prop === "symbol") return void 0;
|
|
1106
|
-
return (params) => broker.call(`${a.addonId}.${capName}.${prop}`, params);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
);
|
|
1110
|
-
capabilityRegistry.registerProvider(capName, a.addonId, proxy);
|
|
1111
|
-
}
|
|
1112
|
-
loadedAddons.set(a.addonId, {
|
|
1113
|
-
id: a.addonId,
|
|
1114
|
-
status: "running",
|
|
1115
|
-
version: a.version,
|
|
1116
|
-
packageName: a.packageName
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1293
|
+
registerGroupRunnerProviders({ broker, capabilityRegistry, loadedAddons }, addons);
|
|
1119
1294
|
console.log(
|
|
1120
1295
|
`[Agent] Group "${groupId}" spawned with ${addons.length} addon(s): ${addons.map((a) => a.addonId).join(", ")}`
|
|
1121
1296
|
);
|
|
@@ -1134,7 +1309,7 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
|
|
|
1134
1309
|
for (const registered of loader.listAddons()) {
|
|
1135
1310
|
const addonId = registered.declaration.id;
|
|
1136
1311
|
if (loadedAddons.has(addonId)) continue;
|
|
1137
|
-
if (!(0,
|
|
1312
|
+
if (!(0, import_types2.isDeployableToAgent)(registered.declaration)) continue;
|
|
1138
1313
|
console.warn(
|
|
1139
1314
|
`[Agent] Addon "${addonId}" is deployable but missing from any spawned group \u2014 verify package.json execution field`
|
|
1140
1315
|
);
|
|
@@ -1142,9 +1317,66 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
|
|
|
1142
1317
|
}
|
|
1143
1318
|
}
|
|
1144
1319
|
async function loadDeployedAddons(broker, addonsDir, dataDir, loadedAddons, storageProvider, loggerFactory, capabilityRegistry) {
|
|
1145
|
-
if (!
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1320
|
+
if (!fs6.existsSync(addonsDir)) return;
|
|
1321
|
+
const packageDirs = resolveAddonPackageDirs(addonsDir);
|
|
1322
|
+
const groupCandidates = [];
|
|
1323
|
+
const inProcessDirs = /* @__PURE__ */ new Set();
|
|
1324
|
+
for (const dir of packageDirs) {
|
|
1325
|
+
const manifest = readPackageManifestAddons(dir);
|
|
1326
|
+
if (!manifest) continue;
|
|
1327
|
+
for (const decl of manifest.declarations) {
|
|
1328
|
+
const addonId = decl.id;
|
|
1329
|
+
if (loadedAddons.has(addonId)) continue;
|
|
1330
|
+
if (!(0, import_types2.isDeployableToAgent)(decl)) continue;
|
|
1331
|
+
if (isGroupRunnerAddon(decl)) {
|
|
1332
|
+
groupCandidates.push({
|
|
1333
|
+
groupId: (0, import_types2.resolveRunnerId)(decl, addonId),
|
|
1334
|
+
addonId,
|
|
1335
|
+
addonDir: dir,
|
|
1336
|
+
version: decl.version ?? "0.0.0",
|
|
1337
|
+
packageName: manifest.packageName,
|
|
1338
|
+
packageVersion: manifest.packageVersion,
|
|
1339
|
+
capabilities: decl.capabilities ?? []
|
|
1340
|
+
});
|
|
1341
|
+
} else {
|
|
1342
|
+
inProcessDirs.add(dir);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (inProcessDirs.size > 0) {
|
|
1347
|
+
await loadInProcessDeployedAddons(
|
|
1348
|
+
broker,
|
|
1349
|
+
[...inProcessDirs],
|
|
1350
|
+
dataDir,
|
|
1351
|
+
loadedAddons,
|
|
1352
|
+
storageProvider,
|
|
1353
|
+
loggerFactory,
|
|
1354
|
+
capabilityRegistry
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
if (groupCandidates.length === 0) return;
|
|
1358
|
+
if (!capabilityRegistry) {
|
|
1359
|
+
console.warn(
|
|
1360
|
+
`[Agent] ${groupCandidates.length} deployed group addon(s) skipped \u2014 no capabilityRegistry passed`
|
|
1361
|
+
);
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1365
|
+
for (const c of groupCandidates) {
|
|
1366
|
+
const arr = grouped.get(c.groupId) ?? [];
|
|
1367
|
+
arr.push(c);
|
|
1368
|
+
grouped.set(c.groupId, arr);
|
|
1369
|
+
}
|
|
1370
|
+
const deployLogger = loggerFactory("agent-group-runner");
|
|
1371
|
+
for (const [groupId, addons] of grouped) {
|
|
1372
|
+
await ensureGroupRunner(
|
|
1373
|
+
{ broker, capabilityRegistry, loadedAddons, logger: deployLogger },
|
|
1374
|
+
groupId,
|
|
1375
|
+
addons
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
async function loadInProcessDeployedAddons(broker, dirs, dataDir, loadedAddons, storageProvider, loggerFactory, capabilityRegistry) {
|
|
1148
1380
|
const modelsDir = storageProvider ? await storageProvider.resolve({ location: "models", relativePath: "" }).catch(() => void 0) : void 0;
|
|
1149
1381
|
const contextOptions = {
|
|
1150
1382
|
storageProvider,
|
|
@@ -1152,45 +1384,56 @@ async function loadDeployedAddons(broker, addonsDir, dataDir, loadedAddons, stor
|
|
|
1152
1384
|
createLogger: loggerFactory,
|
|
1153
1385
|
capabilityRegistry
|
|
1154
1386
|
};
|
|
1155
|
-
for (const
|
|
1156
|
-
const
|
|
1157
|
-
if (loadedAddons.has(addonId)) continue;
|
|
1158
|
-
if (!(0, import_types.isDeployableToAgent)(registered.declaration)) continue;
|
|
1387
|
+
for (const dir of dirs) {
|
|
1388
|
+
const loader = new import_system3.AddonLoader();
|
|
1159
1389
|
try {
|
|
1160
|
-
|
|
1161
|
-
const context = await (0, import_system3.createAddonContext)(
|
|
1162
|
-
broker,
|
|
1163
|
-
registered.declaration,
|
|
1164
|
-
dataDir,
|
|
1165
|
-
contextOptions
|
|
1166
|
-
);
|
|
1167
|
-
await instance.initialize(context);
|
|
1168
|
-
const serviceSchema = (0, import_system3.createAddonService)(instance, registered.declaration);
|
|
1169
|
-
broker.createService(serviceSchema);
|
|
1170
|
-
loadedAddons.set(addonId, {
|
|
1171
|
-
id: addonId,
|
|
1172
|
-
status: "running",
|
|
1173
|
-
version: registered.declaration.version,
|
|
1174
|
-
packageName: registered.packageName,
|
|
1175
|
-
addon: instance
|
|
1176
|
-
});
|
|
1177
|
-
console.log(`[Agent] Deployed addon "${addonId}" loaded`);
|
|
1390
|
+
await loader.loadFromAddonDir(dir);
|
|
1178
1391
|
} catch (err) {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1392
|
+
console.warn(`[Agent] Skipping ${dir}: ${(0, import_types3.errMsg)(err)}`);
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
for (const registered of loader.listAddons()) {
|
|
1396
|
+
const addonId = registered.declaration.id;
|
|
1397
|
+
if (loadedAddons.has(addonId)) continue;
|
|
1398
|
+
if (!(0, import_types2.isDeployableToAgent)(registered.declaration)) continue;
|
|
1399
|
+
if (isGroupRunnerAddon(registered.declaration)) continue;
|
|
1400
|
+
try {
|
|
1401
|
+
const instance = new registered.addonClass();
|
|
1402
|
+
const context = await (0, import_system3.createAddonContext)(
|
|
1403
|
+
broker,
|
|
1404
|
+
registered.declaration,
|
|
1405
|
+
dataDir,
|
|
1406
|
+
contextOptions
|
|
1407
|
+
);
|
|
1408
|
+
await instance.initialize(context);
|
|
1409
|
+
const serviceSchema = (0, import_system3.createAddonService)(instance, registered.declaration);
|
|
1410
|
+
broker.createService(serviceSchema);
|
|
1411
|
+
loadedAddons.set(addonId, {
|
|
1412
|
+
id: addonId,
|
|
1413
|
+
status: "running",
|
|
1414
|
+
version: registered.declaration.version,
|
|
1415
|
+
packageName: registered.packageName,
|
|
1416
|
+
packageVersion: registered.packageVersion,
|
|
1417
|
+
addon: instance
|
|
1418
|
+
});
|
|
1419
|
+
console.log(`[Agent] Deployed addon "${addonId}" loaded in-process`);
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
const msg = (0, import_types3.errMsg)(err);
|
|
1422
|
+
console.error(`[Agent] Failed to load deployed addon "${addonId}": ${msg}`);
|
|
1423
|
+
loadedAddons.set(addonId, { id: addonId, status: "error" });
|
|
1424
|
+
}
|
|
1182
1425
|
}
|
|
1183
1426
|
}
|
|
1184
1427
|
}
|
|
1185
1428
|
function readAgentVersion() {
|
|
1186
1429
|
const candidates = [
|
|
1187
|
-
|
|
1188
|
-
|
|
1430
|
+
path6.resolve(__dirname, "..", "package.json"),
|
|
1431
|
+
path6.resolve(__dirname, "..", "..", "package.json")
|
|
1189
1432
|
];
|
|
1190
1433
|
for (const candidate of candidates) {
|
|
1191
1434
|
try {
|
|
1192
|
-
if (!
|
|
1193
|
-
const raw = JSON.parse(
|
|
1435
|
+
if (!fs6.existsSync(candidate)) continue;
|
|
1436
|
+
const raw = JSON.parse(fs6.readFileSync(candidate, "utf-8"));
|
|
1194
1437
|
if (raw.name === "@camstack/agent" && typeof raw.version === "string") return raw.version;
|
|
1195
1438
|
} catch {
|
|
1196
1439
|
}
|
|
@@ -1199,24 +1442,24 @@ function readAgentVersion() {
|
|
|
1199
1442
|
}
|
|
1200
1443
|
function isDir(p) {
|
|
1201
1444
|
try {
|
|
1202
|
-
return
|
|
1445
|
+
return fs6.statSync(p).isDirectory();
|
|
1203
1446
|
} catch {
|
|
1204
1447
|
return false;
|
|
1205
1448
|
}
|
|
1206
1449
|
}
|
|
1207
1450
|
function resolveAddonPackageDirs(addonsDir) {
|
|
1208
1451
|
const dirs = [];
|
|
1209
|
-
if (!
|
|
1210
|
-
for (const name of
|
|
1211
|
-
const full =
|
|
1452
|
+
if (!fs6.existsSync(addonsDir)) return dirs;
|
|
1453
|
+
for (const name of fs6.readdirSync(addonsDir)) {
|
|
1454
|
+
const full = path6.join(addonsDir, name);
|
|
1212
1455
|
if (name.startsWith("@") && isDir(full)) {
|
|
1213
|
-
for (const sub of
|
|
1214
|
-
const subFull =
|
|
1215
|
-
if (isDir(subFull) &&
|
|
1456
|
+
for (const sub of fs6.readdirSync(full)) {
|
|
1457
|
+
const subFull = path6.join(full, sub);
|
|
1458
|
+
if (isDir(subFull) && fs6.existsSync(path6.join(subFull, "package.json"))) {
|
|
1216
1459
|
dirs.push(subFull);
|
|
1217
1460
|
}
|
|
1218
1461
|
}
|
|
1219
|
-
} else if (isDir(full) &&
|
|
1462
|
+
} else if (isDir(full) && fs6.existsSync(path6.join(full, "package.json"))) {
|
|
1220
1463
|
dirs.push(full);
|
|
1221
1464
|
}
|
|
1222
1465
|
}
|