@camstack/agent 1.0.3 → 1.0.5
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/index.js
CHANGED
|
@@ -104,10 +104,87 @@ function loadAgentConfig(configPath, dataDirOverride) {
|
|
|
104
104
|
|
|
105
105
|
// src/agent-service.ts
|
|
106
106
|
var os2 = __toESM(require("os"));
|
|
107
|
-
var
|
|
108
|
-
var
|
|
107
|
+
var fs3 = __toESM(require("fs"));
|
|
108
|
+
var path3 = __toESM(require("path"));
|
|
109
109
|
var import_moleculer = require("moleculer");
|
|
110
110
|
var import_system = require("@camstack/system");
|
|
111
|
+
|
|
112
|
+
// src/agent-deploy-swap.ts
|
|
113
|
+
var fs2 = __toESM(require("fs"));
|
|
114
|
+
var path2 = __toESM(require("path"));
|
|
115
|
+
var import_node_crypto = require("crypto");
|
|
116
|
+
function rmrf(target) {
|
|
117
|
+
fs2.rmSync(target, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
function moveDir(from, to) {
|
|
120
|
+
try {
|
|
121
|
+
fs2.renameSync(from, to);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const code = err.code;
|
|
124
|
+
if (code === "EXDEV") {
|
|
125
|
+
fs2.cpSync(from, to, { recursive: true });
|
|
126
|
+
rmrf(from);
|
|
127
|
+
} else {
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function applyDeployedBundle(input) {
|
|
133
|
+
const { addonsDir, addonId, bundle, extract, logger } = input;
|
|
134
|
+
fs2.mkdirSync(addonsDir, { recursive: true });
|
|
135
|
+
const liveDir = path2.join(addonsDir, addonId);
|
|
136
|
+
const liveParent = path2.dirname(liveDir);
|
|
137
|
+
const liveBase = path2.basename(liveDir);
|
|
138
|
+
fs2.mkdirSync(liveParent, { recursive: true });
|
|
139
|
+
const token = (0, import_node_crypto.randomBytes)(6).toString("hex");
|
|
140
|
+
const nextDir = path2.join(liveParent, `.${liveBase}.next.${token}`);
|
|
141
|
+
const backupDir = path2.join(liveParent, `.${liveBase}.backup.${token}`);
|
|
142
|
+
fs2.mkdirSync(nextDir, { recursive: true });
|
|
143
|
+
try {
|
|
144
|
+
await extract(bundle, nextDir);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
rmrf(nextDir);
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
const hadLive = fs2.existsSync(liveDir);
|
|
150
|
+
if (hadLive) {
|
|
151
|
+
fs2.renameSync(liveDir, backupDir);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
moveDir(nextDir, liveDir);
|
|
155
|
+
} catch (swapErr) {
|
|
156
|
+
if (hadLive) {
|
|
157
|
+
try {
|
|
158
|
+
if (fs2.existsSync(liveDir)) rmrf(liveDir);
|
|
159
|
+
fs2.renameSync(backupDir, liveDir);
|
|
160
|
+
} catch (restoreErr) {
|
|
161
|
+
logger.error(
|
|
162
|
+
`agent deploy swap: restore of "${addonId}" failed after a failed swap \u2014 manual recovery may be needed`,
|
|
163
|
+
{
|
|
164
|
+
meta: {
|
|
165
|
+
backupDir,
|
|
166
|
+
error: restoreErr instanceof Error ? restoreErr.message : String(restoreErr)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
rmrf(nextDir);
|
|
173
|
+
throw swapErr;
|
|
174
|
+
}
|
|
175
|
+
if (hadLive) rmrf(backupDir);
|
|
176
|
+
return { addonDir: liveDir };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/agent-service.ts
|
|
180
|
+
var deploySwapLogger = {
|
|
181
|
+
info: (msg) => console.log(`[Agent] ${msg}`),
|
|
182
|
+
warn: (msg) => console.warn(`[Agent] ${msg}`),
|
|
183
|
+
error: (msg) => console.error(`[Agent] ${msg}`),
|
|
184
|
+
debug: (msg) => console.debug(`[Agent] ${msg}`),
|
|
185
|
+
child: () => deploySwapLogger,
|
|
186
|
+
withTags: () => deploySwapLogger
|
|
187
|
+
};
|
|
111
188
|
function getLocalIps() {
|
|
112
189
|
const interfaces = os2.networkInterfaces();
|
|
113
190
|
const ips = [];
|
|
@@ -120,11 +197,23 @@ function getLocalIps() {
|
|
|
120
197
|
}
|
|
121
198
|
return ips;
|
|
122
199
|
}
|
|
200
|
+
async function withTimeout(p, ms, fallback) {
|
|
201
|
+
let timer;
|
|
202
|
+
const timeout = new Promise((resolve5) => {
|
|
203
|
+
timer = setTimeout(() => resolve5(fallback), ms);
|
|
204
|
+
});
|
|
205
|
+
try {
|
|
206
|
+
return await Promise.race([p, timeout]);
|
|
207
|
+
} finally {
|
|
208
|
+
if (timer) clearTimeout(timer);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
var METRICS_SNAPSHOT_TIMEOUT_MS = 2e3;
|
|
123
212
|
function readHubAddressFromConfig(configPath) {
|
|
124
213
|
if (!configPath) return null;
|
|
125
214
|
try {
|
|
126
|
-
if (!
|
|
127
|
-
const raw = JSON.parse(
|
|
215
|
+
if (!fs3.existsSync(configPath)) return null;
|
|
216
|
+
const raw = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
128
217
|
return typeof raw.hubAddress === "string" && raw.hubAddress.length > 0 ? raw.hubAddress : null;
|
|
129
218
|
} catch {
|
|
130
219
|
return null;
|
|
@@ -132,9 +221,9 @@ function readHubAddressFromConfig(configPath) {
|
|
|
132
221
|
}
|
|
133
222
|
function readDeployedAddonIds(addonDir) {
|
|
134
223
|
try {
|
|
135
|
-
const manifestPath =
|
|
136
|
-
if (!
|
|
137
|
-
const raw = JSON.parse(
|
|
224
|
+
const manifestPath = path3.join(addonDir, "package.json");
|
|
225
|
+
if (!fs3.existsSync(manifestPath)) return [];
|
|
226
|
+
const raw = JSON.parse(fs3.readFileSync(manifestPath, "utf-8"));
|
|
138
227
|
const entries = raw.camstack?.addons ?? [];
|
|
139
228
|
const ids = [];
|
|
140
229
|
for (const entry of entries) {
|
|
@@ -166,7 +255,11 @@ function createAgentService(deps) {
|
|
|
166
255
|
let memoryPercent = 0;
|
|
167
256
|
const metrics = deps.getMetricsProvider?.();
|
|
168
257
|
if (metrics) {
|
|
169
|
-
const snapshot = await
|
|
258
|
+
const snapshot = await withTimeout(
|
|
259
|
+
metrics.getCached(),
|
|
260
|
+
METRICS_SNAPSHOT_TIMEOUT_MS,
|
|
261
|
+
null
|
|
262
|
+
);
|
|
170
263
|
if (snapshot) {
|
|
171
264
|
cpuPercent = snapshot.cpu.total;
|
|
172
265
|
memoryPercent = snapshot.memory.percent;
|
|
@@ -189,7 +282,9 @@ function createAgentService(deps) {
|
|
|
189
282
|
addons: [...deps.loadedAddons.values()].map((a) => ({
|
|
190
283
|
id: a.id,
|
|
191
284
|
status: a.status,
|
|
192
|
-
version
|
|
285
|
+
// Report the PACKAGE version (falls back to the declaration
|
|
286
|
+
// version) so the hub's per-node update check is accurate.
|
|
287
|
+
version: a.packageVersion ?? a.version,
|
|
193
288
|
packageName: a.packageName
|
|
194
289
|
}))
|
|
195
290
|
};
|
|
@@ -203,7 +298,11 @@ function createAgentService(deps) {
|
|
|
203
298
|
const metrics = deps.getMetricsProvider?.();
|
|
204
299
|
if (metrics) {
|
|
205
300
|
try {
|
|
206
|
-
const snapshot = await
|
|
301
|
+
const snapshot = await withTimeout(
|
|
302
|
+
metrics.getCached(),
|
|
303
|
+
METRICS_SNAPSHOT_TIMEOUT_MS,
|
|
304
|
+
null
|
|
305
|
+
);
|
|
207
306
|
if (snapshot) {
|
|
208
307
|
cpuPercent = snapshot.cpu.total;
|
|
209
308
|
memoryPercent = snapshot.memory.percent;
|
|
@@ -253,17 +352,17 @@ function createAgentService(deps) {
|
|
|
253
352
|
deps.agentName = newName.trim();
|
|
254
353
|
broker.logger.info(`Agent renamed: "${oldName}" \u2192 "${deps.agentName}"`);
|
|
255
354
|
try {
|
|
256
|
-
const configFile =
|
|
355
|
+
const configFile = path3.resolve(deps.configPath);
|
|
257
356
|
let raw = {};
|
|
258
|
-
if (
|
|
357
|
+
if (fs3.existsSync(configFile)) {
|
|
259
358
|
try {
|
|
260
|
-
raw = JSON.parse(
|
|
359
|
+
raw = JSON.parse(fs3.readFileSync(configFile, "utf-8"));
|
|
261
360
|
} catch {
|
|
262
361
|
}
|
|
263
362
|
}
|
|
264
363
|
raw.name = deps.agentName;
|
|
265
|
-
|
|
266
|
-
|
|
364
|
+
fs3.mkdirSync(path3.dirname(configFile), { recursive: true });
|
|
365
|
+
fs3.writeFileSync(configFile, JSON.stringify(raw, null, 2), "utf-8");
|
|
267
366
|
broker.logger.info(`Agent name persisted to ${configFile}`);
|
|
268
367
|
} catch (err) {
|
|
269
368
|
broker.logger.warn(
|
|
@@ -283,20 +382,31 @@ function createAgentService(deps) {
|
|
|
283
382
|
handler: async (ctx) => {
|
|
284
383
|
const { params } = ctx;
|
|
285
384
|
const { addonId, bundle } = params;
|
|
286
|
-
const addonDir = path2.join(deps.addonsDir, addonId);
|
|
287
|
-
fs2.mkdirSync(deps.addonsDir, { recursive: true });
|
|
288
|
-
const bundlePath = path2.join(deps.addonsDir, `${addonId}.tgz`);
|
|
289
385
|
const bufferData = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
|
|
290
|
-
|
|
291
|
-
const {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
386
|
+
const { execFile } = await import("child_process");
|
|
387
|
+
const { promisify } = await import("util");
|
|
388
|
+
const execFileAsync = promisify(execFile);
|
|
389
|
+
const extract = async (tgz, destDir) => {
|
|
390
|
+
const tgzPath = path3.join(destDir, "..", `.${path3.basename(destDir)}.tgz`);
|
|
391
|
+
fs3.writeFileSync(tgzPath, tgz);
|
|
392
|
+
try {
|
|
393
|
+
await execFileAsync("tar", ["-xzf", tgzPath, "-C", destDir, "--strip-components=1"], {
|
|
394
|
+
timeout: 6e4
|
|
395
|
+
});
|
|
396
|
+
} finally {
|
|
397
|
+
try {
|
|
398
|
+
fs3.unlinkSync(tgzPath);
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
const { addonDir } = await applyDeployedBundle({
|
|
404
|
+
addonsDir: deps.addonsDir,
|
|
405
|
+
addonId,
|
|
406
|
+
bundle: bufferData,
|
|
407
|
+
extract,
|
|
408
|
+
logger: deploySwapLogger
|
|
298
409
|
});
|
|
299
|
-
fs2.unlinkSync(bundlePath);
|
|
300
410
|
for (const declId of readDeployedAddonIds(addonDir)) {
|
|
301
411
|
deps.loadedAddons.delete(declId);
|
|
302
412
|
}
|
|
@@ -343,15 +453,15 @@ function createAgentService(deps) {
|
|
|
343
453
|
} catch {
|
|
344
454
|
}
|
|
345
455
|
deps.loadedAddons.delete(addonId);
|
|
346
|
-
const addonDir =
|
|
347
|
-
if (
|
|
348
|
-
|
|
456
|
+
const addonDir = path3.join(deps.addonsDir, addonId);
|
|
457
|
+
if (fs3.existsSync(addonDir)) {
|
|
458
|
+
fs3.rmSync(addonDir, { recursive: true, force: true });
|
|
349
459
|
}
|
|
350
460
|
const pkgName = entry?.packageName;
|
|
351
461
|
if (pkgName && pkgName !== addonId) {
|
|
352
|
-
const pkgDir =
|
|
353
|
-
if (
|
|
354
|
-
|
|
462
|
+
const pkgDir = path3.join(deps.addonsDir, pkgName);
|
|
463
|
+
if (fs3.existsSync(pkgDir)) {
|
|
464
|
+
fs3.rmSync(pkgDir, { recursive: true, force: true });
|
|
355
465
|
}
|
|
356
466
|
}
|
|
357
467
|
broker.logger.info(`$agent.undeploy: ${addonId} disposed (instance + service + folder)`);
|
|
@@ -406,35 +516,35 @@ function createAgentService(deps) {
|
|
|
406
516
|
}
|
|
407
517
|
|
|
408
518
|
// src/agent-bootstrap.ts
|
|
409
|
-
var
|
|
410
|
-
var
|
|
519
|
+
var fs6 = __toESM(require("fs"));
|
|
520
|
+
var path6 = __toESM(require("path"));
|
|
411
521
|
|
|
412
522
|
// src/agent-http.ts
|
|
413
523
|
var import_fastify = __toESM(require("fastify"));
|
|
414
|
-
var
|
|
415
|
-
var
|
|
524
|
+
var fs4 = __toESM(require("fs"));
|
|
525
|
+
var path4 = __toESM(require("path"));
|
|
416
526
|
function resolveUiDistDir(dataDir) {
|
|
417
527
|
const candidates = [
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
528
|
+
path4.resolve(__dirname, "../../addon-agent-ui/dist"),
|
|
529
|
+
path4.join(dataDir, "addons", "@camstack", "addon-agent-ui", "dist"),
|
|
530
|
+
path4.join(dataDir, "agent-ui")
|
|
421
531
|
];
|
|
422
532
|
for (const dir of candidates) {
|
|
423
|
-
if (
|
|
533
|
+
if (fs4.existsSync(path4.join(dir, "index.html"))) return dir;
|
|
424
534
|
}
|
|
425
535
|
return null;
|
|
426
536
|
}
|
|
427
537
|
function readConfigFile(configPath) {
|
|
428
|
-
if (!
|
|
538
|
+
if (!fs4.existsSync(configPath)) return {};
|
|
429
539
|
try {
|
|
430
|
-
return JSON.parse(
|
|
540
|
+
return JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
431
541
|
} catch {
|
|
432
542
|
return {};
|
|
433
543
|
}
|
|
434
544
|
}
|
|
435
545
|
function writeConfigFile(configPath, data) {
|
|
436
|
-
|
|
437
|
-
|
|
546
|
+
fs4.mkdirSync(path4.dirname(configPath), { recursive: true });
|
|
547
|
+
fs4.writeFileSync(configPath, JSON.stringify(data, null, 2), "utf-8");
|
|
438
548
|
}
|
|
439
549
|
function getRegistryNodes(broker) {
|
|
440
550
|
try {
|
|
@@ -598,8 +708,86 @@ async function startAgentHttpServer(getBroker, config) {
|
|
|
598
708
|
|
|
599
709
|
// src/agent-bootstrap.ts
|
|
600
710
|
var import_system3 = require("@camstack/system");
|
|
601
|
-
var import_types = require("@camstack/types");
|
|
602
711
|
var import_types2 = require("@camstack/types");
|
|
712
|
+
var import_types3 = require("@camstack/types");
|
|
713
|
+
|
|
714
|
+
// src/agent-group-runner.ts
|
|
715
|
+
var fs5 = __toESM(require("fs"));
|
|
716
|
+
var path5 = __toESM(require("path"));
|
|
717
|
+
var import_types = require("@camstack/types");
|
|
718
|
+
function readPackageManifestAddons(dir) {
|
|
719
|
+
try {
|
|
720
|
+
const pkgPath = path5.join(dir, "package.json");
|
|
721
|
+
if (!fs5.existsSync(pkgPath)) return null;
|
|
722
|
+
const raw = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
723
|
+
const packageName = typeof raw.name === "string" ? raw.name : "";
|
|
724
|
+
const packageVersion = typeof raw.version === "string" ? raw.version : "0.0.0";
|
|
725
|
+
const entries = Array.isArray(raw.camstack?.addons) ? raw.camstack.addons : [];
|
|
726
|
+
const declarations = [];
|
|
727
|
+
for (const entry of entries) {
|
|
728
|
+
if (entry !== null && typeof entry === "object" && typeof entry.id === "string") {
|
|
729
|
+
declarations.push(entry);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (!packageName || declarations.length === 0) return null;
|
|
733
|
+
return { packageName, packageVersion, declarations };
|
|
734
|
+
} catch {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function isGroupRunnerAddon(decl) {
|
|
739
|
+
return decl.execution !== void 0 && (0, import_types.resolveAddonPlacement)(decl) !== "hub-only";
|
|
740
|
+
}
|
|
741
|
+
function registerGroupRunnerProviders(deps, addons) {
|
|
742
|
+
for (const a of addons) {
|
|
743
|
+
for (const cap of a.capabilities) {
|
|
744
|
+
const capName = typeof cap === "string" ? cap : cap.name;
|
|
745
|
+
const proxy = new Proxy(
|
|
746
|
+
{},
|
|
747
|
+
{
|
|
748
|
+
get(_target, prop) {
|
|
749
|
+
if (prop === "then" || typeof prop === "symbol") return void 0;
|
|
750
|
+
return (params) => deps.broker.call(`${a.addonId}.${capName}.${prop}`, params);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
);
|
|
754
|
+
deps.capabilityRegistry.registerProvider(capName, a.addonId, proxy);
|
|
755
|
+
}
|
|
756
|
+
deps.loadedAddons.set(a.addonId, {
|
|
757
|
+
id: a.addonId,
|
|
758
|
+
status: "running",
|
|
759
|
+
version: a.version,
|
|
760
|
+
packageName: a.packageName,
|
|
761
|
+
packageVersion: a.packageVersion
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
async function ensureGroupRunner(deps, groupId, addons) {
|
|
766
|
+
try {
|
|
767
|
+
const restart = await deps.broker.call("$process.restart", { name: groupId });
|
|
768
|
+
if (!restart.success) {
|
|
769
|
+
if (restart.reason !== void 0 && restart.reason !== "not found") {
|
|
770
|
+
deps.logger.warn(
|
|
771
|
+
`group "${groupId}" restart returned "${restart.reason}" \u2014 spawning a fresh runner`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
await deps.broker.call("$process.spawnRunner", {
|
|
775
|
+
runnerId: groupId,
|
|
776
|
+
addons: addons.map((a) => ({ addonId: a.addonId, addonDir: a.addonDir }))
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
registerGroupRunnerProviders(deps, addons);
|
|
780
|
+
deps.logger.info(
|
|
781
|
+
`group "${groupId}" live with ${addons.length} addon(s): ${addons.map((a) => a.addonId).join(", ")}`
|
|
782
|
+
);
|
|
783
|
+
} catch (err) {
|
|
784
|
+
const msg = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
785
|
+
deps.logger.error(`failed to ensure group "${groupId}": ${msg}`);
|
|
786
|
+
for (const a of addons) {
|
|
787
|
+
deps.loadedAddons.set(a.addonId, { id: a.addonId, status: "error" });
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
603
791
|
|
|
604
792
|
// src/agent-cap-dispatch-service.ts
|
|
605
793
|
var import_system2 = require("@camstack/system");
|
|
@@ -682,7 +870,7 @@ async function startAgent(configPath) {
|
|
|
682
870
|
const bundledDir = process.env["CAMSTACK_BUNDLED_ADDONS_DIR"];
|
|
683
871
|
let workspaceDir = null;
|
|
684
872
|
let resolvedSource = explicitSource;
|
|
685
|
-
if (bundledDir &&
|
|
873
|
+
if (bundledDir && fs6.existsSync(bundledDir)) {
|
|
686
874
|
workspaceDir = bundledDir;
|
|
687
875
|
resolvedSource = "local";
|
|
688
876
|
console.log(`[Agent] Using bundled addons from ${bundledDir}`);
|
|
@@ -801,17 +989,17 @@ async function startAgent(configPath) {
|
|
|
801
989
|
};
|
|
802
990
|
const capabilityRegistry = new import_system3.CapabilityRegistry(consoleLogger);
|
|
803
991
|
const agentCapabilities = [
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
992
|
+
import_types3.storageCapability,
|
|
993
|
+
import_types3.storageProviderCapability,
|
|
994
|
+
import_types3.settingsStoreCapability,
|
|
995
|
+
import_types3.logDestinationCapability,
|
|
996
|
+
import_types3.metricsProviderCapability,
|
|
997
|
+
import_types3.decoderCapability,
|
|
998
|
+
import_types3.motionDetectionCapability,
|
|
999
|
+
import_types3.pipelineExecutorCapability,
|
|
1000
|
+
import_types3.pipelineRunnerCapability,
|
|
1001
|
+
import_types3.audioAnalyzerCapability,
|
|
1002
|
+
import_types3.platformProbeCapability
|
|
815
1003
|
];
|
|
816
1004
|
for (const cap of agentCapabilities) {
|
|
817
1005
|
capabilityRegistry.declareCapability(cap);
|
|
@@ -1010,7 +1198,7 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
|
|
|
1010
1198
|
try {
|
|
1011
1199
|
await loader.loadFromAddonDir(dir);
|
|
1012
1200
|
} catch (err) {
|
|
1013
|
-
console.warn(`[Agent] Failed to scan ${dir}: ${(0,
|
|
1201
|
+
console.warn(`[Agent] Failed to scan ${dir}: ${(0, import_types3.errMsg)(err)}`);
|
|
1014
1202
|
}
|
|
1015
1203
|
}
|
|
1016
1204
|
for (const infra of AGENT_INFRA) {
|
|
@@ -1039,10 +1227,15 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
|
|
|
1039
1227
|
storageProvider,
|
|
1040
1228
|
addonConfig: { rootPath: config.dataDir },
|
|
1041
1229
|
createLogger: loggerFactory,
|
|
1042
|
-
capabilityRegistry: registry
|
|
1230
|
+
capabilityRegistry: registry,
|
|
1231
|
+
// Feed the storage-orchestrator its first-boot seed declarations
|
|
1232
|
+
// (addons-data:default, models:default, …). Without this the agent's
|
|
1233
|
+
// sqlite-settings aborts boot: "No default storage location
|
|
1234
|
+
// configured for type addons-data" → fatal crash-loop.
|
|
1235
|
+
listStorageLocationDeclarations: () => loader.listStorageLocationDeclarations()
|
|
1043
1236
|
}
|
|
1044
1237
|
);
|
|
1045
|
-
const initResult = (0,
|
|
1238
|
+
const initResult = (0, import_types2.normalizeAddonInitResult)(await instance.initialize(context));
|
|
1046
1239
|
for (const reg of initResult?.providers ?? []) {
|
|
1047
1240
|
const capName = reg.capability.name;
|
|
1048
1241
|
registry.registerProvider(capName, addonId, reg.provider);
|
|
@@ -1053,11 +1246,12 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
|
|
|
1053
1246
|
status: "running",
|
|
1054
1247
|
version: addon.declaration.version,
|
|
1055
1248
|
packageName: addon.packageName,
|
|
1249
|
+
packageVersion: addon.packageVersion,
|
|
1056
1250
|
addon: instance
|
|
1057
1251
|
});
|
|
1058
1252
|
console.log(`[Agent] Core addon "${addonId}" initialized`);
|
|
1059
1253
|
} catch (err) {
|
|
1060
|
-
const msg = (0,
|
|
1254
|
+
const msg = (0, import_types3.errMsg)(err);
|
|
1061
1255
|
console.error(`[Agent] Failed to initialize core addon "${addonId}": ${msg}`);
|
|
1062
1256
|
if (infra.required) {
|
|
1063
1257
|
throw new Error(`Required infrastructure addon "${addonId}" failed: ${msg}`, { cause: err });
|
|
@@ -1075,21 +1269,22 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
|
|
|
1075
1269
|
try {
|
|
1076
1270
|
await loader.loadFromAddonDir(dir);
|
|
1077
1271
|
} catch (err) {
|
|
1078
|
-
console.warn(`[Agent] Skipping ${dir}: ${(0,
|
|
1272
|
+
console.warn(`[Agent] Skipping ${dir}: ${(0, import_types3.errMsg)(err)}`);
|
|
1079
1273
|
continue;
|
|
1080
1274
|
}
|
|
1081
1275
|
dirToLoader.set(dir, loader);
|
|
1082
1276
|
for (const registered of loader.listAddons()) {
|
|
1083
1277
|
if (loadedAddons.has(registered.declaration.id)) continue;
|
|
1084
1278
|
if (registered.declaration.execution === void 0) continue;
|
|
1085
|
-
const placement = (0,
|
|
1279
|
+
const placement = (0, import_types2.resolveAddonPlacement)(registered.declaration);
|
|
1086
1280
|
if (placement === "hub-only") continue;
|
|
1087
1281
|
allGroupCandidates.push({
|
|
1088
|
-
groupId: (0,
|
|
1282
|
+
groupId: (0, import_types2.resolveRunnerId)(registered.declaration, registered.declaration.id),
|
|
1089
1283
|
addonId: registered.declaration.id,
|
|
1090
1284
|
addonDir: dir,
|
|
1091
1285
|
version: registered.declaration.version ?? "0.0.0",
|
|
1092
1286
|
packageName: registered.packageName,
|
|
1287
|
+
packageVersion: registered.packageVersion,
|
|
1093
1288
|
capabilities: registered.declaration.capabilities ?? []
|
|
1094
1289
|
});
|
|
1095
1290
|
}
|
|
@@ -1107,27 +1302,7 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
|
|
|
1107
1302
|
runnerId: groupId,
|
|
1108
1303
|
addons: addons.map((a) => ({ addonId: a.addonId, addonDir: a.addonDir }))
|
|
1109
1304
|
});
|
|
1110
|
-
|
|
1111
|
-
for (const cap of a.capabilities) {
|
|
1112
|
-
const capName = typeof cap === "string" ? cap : cap.name;
|
|
1113
|
-
const proxy = new Proxy(
|
|
1114
|
-
{},
|
|
1115
|
-
{
|
|
1116
|
-
get(_target, prop) {
|
|
1117
|
-
if (prop === "then" || typeof prop === "symbol") return void 0;
|
|
1118
|
-
return (params) => broker.call(`${a.addonId}.${capName}.${prop}`, params);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
);
|
|
1122
|
-
capabilityRegistry.registerProvider(capName, a.addonId, proxy);
|
|
1123
|
-
}
|
|
1124
|
-
loadedAddons.set(a.addonId, {
|
|
1125
|
-
id: a.addonId,
|
|
1126
|
-
status: "running",
|
|
1127
|
-
version: a.version,
|
|
1128
|
-
packageName: a.packageName
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1305
|
+
registerGroupRunnerProviders({ broker, capabilityRegistry, loadedAddons }, addons);
|
|
1131
1306
|
console.log(
|
|
1132
1307
|
`[Agent] Group "${groupId}" spawned with ${addons.length} addon(s): ${addons.map((a) => a.addonId).join(", ")}`
|
|
1133
1308
|
);
|
|
@@ -1146,7 +1321,7 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
|
|
|
1146
1321
|
for (const registered of loader.listAddons()) {
|
|
1147
1322
|
const addonId = registered.declaration.id;
|
|
1148
1323
|
if (loadedAddons.has(addonId)) continue;
|
|
1149
|
-
if (!(0,
|
|
1324
|
+
if (!(0, import_types2.isDeployableToAgent)(registered.declaration)) continue;
|
|
1150
1325
|
console.warn(
|
|
1151
1326
|
`[Agent] Addon "${addonId}" is deployable but missing from any spawned group \u2014 verify package.json execution field`
|
|
1152
1327
|
);
|
|
@@ -1154,9 +1329,66 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
|
|
|
1154
1329
|
}
|
|
1155
1330
|
}
|
|
1156
1331
|
async function loadDeployedAddons(broker, addonsDir, dataDir, loadedAddons, storageProvider, loggerFactory, capabilityRegistry) {
|
|
1157
|
-
if (!
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1332
|
+
if (!fs6.existsSync(addonsDir)) return;
|
|
1333
|
+
const packageDirs = resolveAddonPackageDirs(addonsDir);
|
|
1334
|
+
const groupCandidates = [];
|
|
1335
|
+
const inProcessDirs = /* @__PURE__ */ new Set();
|
|
1336
|
+
for (const dir of packageDirs) {
|
|
1337
|
+
const manifest = readPackageManifestAddons(dir);
|
|
1338
|
+
if (!manifest) continue;
|
|
1339
|
+
for (const decl of manifest.declarations) {
|
|
1340
|
+
const addonId = decl.id;
|
|
1341
|
+
if (loadedAddons.has(addonId)) continue;
|
|
1342
|
+
if (!(0, import_types2.isDeployableToAgent)(decl)) continue;
|
|
1343
|
+
if (isGroupRunnerAddon(decl)) {
|
|
1344
|
+
groupCandidates.push({
|
|
1345
|
+
groupId: (0, import_types2.resolveRunnerId)(decl, addonId),
|
|
1346
|
+
addonId,
|
|
1347
|
+
addonDir: dir,
|
|
1348
|
+
version: decl.version ?? "0.0.0",
|
|
1349
|
+
packageName: manifest.packageName,
|
|
1350
|
+
packageVersion: manifest.packageVersion,
|
|
1351
|
+
capabilities: decl.capabilities ?? []
|
|
1352
|
+
});
|
|
1353
|
+
} else {
|
|
1354
|
+
inProcessDirs.add(dir);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (inProcessDirs.size > 0) {
|
|
1359
|
+
await loadInProcessDeployedAddons(
|
|
1360
|
+
broker,
|
|
1361
|
+
[...inProcessDirs],
|
|
1362
|
+
dataDir,
|
|
1363
|
+
loadedAddons,
|
|
1364
|
+
storageProvider,
|
|
1365
|
+
loggerFactory,
|
|
1366
|
+
capabilityRegistry
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
if (groupCandidates.length === 0) return;
|
|
1370
|
+
if (!capabilityRegistry) {
|
|
1371
|
+
console.warn(
|
|
1372
|
+
`[Agent] ${groupCandidates.length} deployed group addon(s) skipped \u2014 no capabilityRegistry passed`
|
|
1373
|
+
);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1377
|
+
for (const c of groupCandidates) {
|
|
1378
|
+
const arr = grouped.get(c.groupId) ?? [];
|
|
1379
|
+
arr.push(c);
|
|
1380
|
+
grouped.set(c.groupId, arr);
|
|
1381
|
+
}
|
|
1382
|
+
const deployLogger = loggerFactory("agent-group-runner");
|
|
1383
|
+
for (const [groupId, addons] of grouped) {
|
|
1384
|
+
await ensureGroupRunner(
|
|
1385
|
+
{ broker, capabilityRegistry, loadedAddons, logger: deployLogger },
|
|
1386
|
+
groupId,
|
|
1387
|
+
addons
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
async function loadInProcessDeployedAddons(broker, dirs, dataDir, loadedAddons, storageProvider, loggerFactory, capabilityRegistry) {
|
|
1160
1392
|
const modelsDir = storageProvider ? await storageProvider.resolve({ location: "models", relativePath: "" }).catch(() => void 0) : void 0;
|
|
1161
1393
|
const contextOptions = {
|
|
1162
1394
|
storageProvider,
|
|
@@ -1164,45 +1396,56 @@ async function loadDeployedAddons(broker, addonsDir, dataDir, loadedAddons, stor
|
|
|
1164
1396
|
createLogger: loggerFactory,
|
|
1165
1397
|
capabilityRegistry
|
|
1166
1398
|
};
|
|
1167
|
-
for (const
|
|
1168
|
-
const
|
|
1169
|
-
if (loadedAddons.has(addonId)) continue;
|
|
1170
|
-
if (!(0, import_types.isDeployableToAgent)(registered.declaration)) continue;
|
|
1399
|
+
for (const dir of dirs) {
|
|
1400
|
+
const loader = new import_system3.AddonLoader();
|
|
1171
1401
|
try {
|
|
1172
|
-
|
|
1173
|
-
const context = await (0, import_system3.createAddonContext)(
|
|
1174
|
-
broker,
|
|
1175
|
-
registered.declaration,
|
|
1176
|
-
dataDir,
|
|
1177
|
-
contextOptions
|
|
1178
|
-
);
|
|
1179
|
-
await instance.initialize(context);
|
|
1180
|
-
const serviceSchema = (0, import_system3.createAddonService)(instance, registered.declaration);
|
|
1181
|
-
broker.createService(serviceSchema);
|
|
1182
|
-
loadedAddons.set(addonId, {
|
|
1183
|
-
id: addonId,
|
|
1184
|
-
status: "running",
|
|
1185
|
-
version: registered.declaration.version,
|
|
1186
|
-
packageName: registered.packageName,
|
|
1187
|
-
addon: instance
|
|
1188
|
-
});
|
|
1189
|
-
console.log(`[Agent] Deployed addon "${addonId}" loaded`);
|
|
1402
|
+
await loader.loadFromAddonDir(dir);
|
|
1190
1403
|
} catch (err) {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1404
|
+
console.warn(`[Agent] Skipping ${dir}: ${(0, import_types3.errMsg)(err)}`);
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
for (const registered of loader.listAddons()) {
|
|
1408
|
+
const addonId = registered.declaration.id;
|
|
1409
|
+
if (loadedAddons.has(addonId)) continue;
|
|
1410
|
+
if (!(0, import_types2.isDeployableToAgent)(registered.declaration)) continue;
|
|
1411
|
+
if (isGroupRunnerAddon(registered.declaration)) continue;
|
|
1412
|
+
try {
|
|
1413
|
+
const instance = new registered.addonClass();
|
|
1414
|
+
const context = await (0, import_system3.createAddonContext)(
|
|
1415
|
+
broker,
|
|
1416
|
+
registered.declaration,
|
|
1417
|
+
dataDir,
|
|
1418
|
+
contextOptions
|
|
1419
|
+
);
|
|
1420
|
+
await instance.initialize(context);
|
|
1421
|
+
const serviceSchema = (0, import_system3.createAddonService)(instance, registered.declaration);
|
|
1422
|
+
broker.createService(serviceSchema);
|
|
1423
|
+
loadedAddons.set(addonId, {
|
|
1424
|
+
id: addonId,
|
|
1425
|
+
status: "running",
|
|
1426
|
+
version: registered.declaration.version,
|
|
1427
|
+
packageName: registered.packageName,
|
|
1428
|
+
packageVersion: registered.packageVersion,
|
|
1429
|
+
addon: instance
|
|
1430
|
+
});
|
|
1431
|
+
console.log(`[Agent] Deployed addon "${addonId}" loaded in-process`);
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
const msg = (0, import_types3.errMsg)(err);
|
|
1434
|
+
console.error(`[Agent] Failed to load deployed addon "${addonId}": ${msg}`);
|
|
1435
|
+
loadedAddons.set(addonId, { id: addonId, status: "error" });
|
|
1436
|
+
}
|
|
1194
1437
|
}
|
|
1195
1438
|
}
|
|
1196
1439
|
}
|
|
1197
1440
|
function readAgentVersion() {
|
|
1198
1441
|
const candidates = [
|
|
1199
|
-
|
|
1200
|
-
|
|
1442
|
+
path6.resolve(__dirname, "..", "package.json"),
|
|
1443
|
+
path6.resolve(__dirname, "..", "..", "package.json")
|
|
1201
1444
|
];
|
|
1202
1445
|
for (const candidate of candidates) {
|
|
1203
1446
|
try {
|
|
1204
|
-
if (!
|
|
1205
|
-
const raw = JSON.parse(
|
|
1447
|
+
if (!fs6.existsSync(candidate)) continue;
|
|
1448
|
+
const raw = JSON.parse(fs6.readFileSync(candidate, "utf-8"));
|
|
1206
1449
|
if (raw.name === "@camstack/agent" && typeof raw.version === "string") return raw.version;
|
|
1207
1450
|
} catch {
|
|
1208
1451
|
}
|
|
@@ -1211,24 +1454,24 @@ function readAgentVersion() {
|
|
|
1211
1454
|
}
|
|
1212
1455
|
function isDir(p) {
|
|
1213
1456
|
try {
|
|
1214
|
-
return
|
|
1457
|
+
return fs6.statSync(p).isDirectory();
|
|
1215
1458
|
} catch {
|
|
1216
1459
|
return false;
|
|
1217
1460
|
}
|
|
1218
1461
|
}
|
|
1219
1462
|
function resolveAddonPackageDirs(addonsDir) {
|
|
1220
1463
|
const dirs = [];
|
|
1221
|
-
if (!
|
|
1222
|
-
for (const name of
|
|
1223
|
-
const full =
|
|
1464
|
+
if (!fs6.existsSync(addonsDir)) return dirs;
|
|
1465
|
+
for (const name of fs6.readdirSync(addonsDir)) {
|
|
1466
|
+
const full = path6.join(addonsDir, name);
|
|
1224
1467
|
if (name.startsWith("@") && isDir(full)) {
|
|
1225
|
-
for (const sub of
|
|
1226
|
-
const subFull =
|
|
1227
|
-
if (isDir(subFull) &&
|
|
1468
|
+
for (const sub of fs6.readdirSync(full)) {
|
|
1469
|
+
const subFull = path6.join(full, sub);
|
|
1470
|
+
if (isDir(subFull) && fs6.existsSync(path6.join(subFull, "package.json"))) {
|
|
1228
1471
|
dirs.push(subFull);
|
|
1229
1472
|
}
|
|
1230
1473
|
}
|
|
1231
|
-
} else if (isDir(full) &&
|
|
1474
|
+
} else if (isDir(full) && fs6.existsSync(path6.join(full, "package.json"))) {
|
|
1232
1475
|
dirs.push(full);
|
|
1233
1476
|
}
|
|
1234
1477
|
}
|