@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.
@@ -66,10 +66,87 @@ function loadAgentConfig(configPath, dataDirOverride) {
66
66
 
67
67
  // src/agent-service.ts
68
68
  import * as os2 from "os";
69
- import * as fs2 from "fs";
70
- import * as path2 from "path";
69
+ import * as fs3 from "fs";
70
+ import * as path3 from "path";
71
71
  import { Errors } from "moleculer";
72
72
  import { clusterSecretMatches, CLUSTER_SECRET_MISMATCH_TYPE } from "@camstack/system";
73
+
74
+ // src/agent-deploy-swap.ts
75
+ import * as fs2 from "fs";
76
+ import * as path2 from "path";
77
+ import { randomBytes as randomBytes2 } from "crypto";
78
+ function rmrf(target) {
79
+ fs2.rmSync(target, { recursive: true, force: true });
80
+ }
81
+ function moveDir(from, to) {
82
+ try {
83
+ fs2.renameSync(from, to);
84
+ } catch (err) {
85
+ const code = err.code;
86
+ if (code === "EXDEV") {
87
+ fs2.cpSync(from, to, { recursive: true });
88
+ rmrf(from);
89
+ } else {
90
+ throw err;
91
+ }
92
+ }
93
+ }
94
+ async function applyDeployedBundle(input) {
95
+ const { addonsDir, addonId, bundle, extract, logger } = input;
96
+ fs2.mkdirSync(addonsDir, { recursive: true });
97
+ const liveDir = path2.join(addonsDir, addonId);
98
+ const liveParent = path2.dirname(liveDir);
99
+ const liveBase = path2.basename(liveDir);
100
+ fs2.mkdirSync(liveParent, { recursive: true });
101
+ const token = randomBytes2(6).toString("hex");
102
+ const nextDir = path2.join(liveParent, `.${liveBase}.next.${token}`);
103
+ const backupDir = path2.join(liveParent, `.${liveBase}.backup.${token}`);
104
+ fs2.mkdirSync(nextDir, { recursive: true });
105
+ try {
106
+ await extract(bundle, nextDir);
107
+ } catch (err) {
108
+ rmrf(nextDir);
109
+ throw err;
110
+ }
111
+ const hadLive = fs2.existsSync(liveDir);
112
+ if (hadLive) {
113
+ fs2.renameSync(liveDir, backupDir);
114
+ }
115
+ try {
116
+ moveDir(nextDir, liveDir);
117
+ } catch (swapErr) {
118
+ if (hadLive) {
119
+ try {
120
+ if (fs2.existsSync(liveDir)) rmrf(liveDir);
121
+ fs2.renameSync(backupDir, liveDir);
122
+ } catch (restoreErr) {
123
+ logger.error(
124
+ `agent deploy swap: restore of "${addonId}" failed after a failed swap \u2014 manual recovery may be needed`,
125
+ {
126
+ meta: {
127
+ backupDir,
128
+ error: restoreErr instanceof Error ? restoreErr.message : String(restoreErr)
129
+ }
130
+ }
131
+ );
132
+ }
133
+ }
134
+ rmrf(nextDir);
135
+ throw swapErr;
136
+ }
137
+ if (hadLive) rmrf(backupDir);
138
+ return { addonDir: liveDir };
139
+ }
140
+
141
+ // src/agent-service.ts
142
+ var deploySwapLogger = {
143
+ info: (msg) => console.log(`[Agent] ${msg}`),
144
+ warn: (msg) => console.warn(`[Agent] ${msg}`),
145
+ error: (msg) => console.error(`[Agent] ${msg}`),
146
+ debug: (msg) => console.debug(`[Agent] ${msg}`),
147
+ child: () => deploySwapLogger,
148
+ withTags: () => deploySwapLogger
149
+ };
73
150
  function getLocalIps() {
74
151
  const interfaces = os2.networkInterfaces();
75
152
  const ips = [];
@@ -82,11 +159,23 @@ function getLocalIps() {
82
159
  }
83
160
  return ips;
84
161
  }
162
+ async function withTimeout(p, ms, fallback) {
163
+ let timer;
164
+ const timeout = new Promise((resolve5) => {
165
+ timer = setTimeout(() => resolve5(fallback), ms);
166
+ });
167
+ try {
168
+ return await Promise.race([p, timeout]);
169
+ } finally {
170
+ if (timer) clearTimeout(timer);
171
+ }
172
+ }
173
+ var METRICS_SNAPSHOT_TIMEOUT_MS = 2e3;
85
174
  function readHubAddressFromConfig(configPath) {
86
175
  if (!configPath) return null;
87
176
  try {
88
- if (!fs2.existsSync(configPath)) return null;
89
- const raw = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
177
+ if (!fs3.existsSync(configPath)) return null;
178
+ const raw = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
90
179
  return typeof raw.hubAddress === "string" && raw.hubAddress.length > 0 ? raw.hubAddress : null;
91
180
  } catch {
92
181
  return null;
@@ -94,9 +183,9 @@ function readHubAddressFromConfig(configPath) {
94
183
  }
95
184
  function readDeployedAddonIds(addonDir) {
96
185
  try {
97
- const manifestPath = path2.join(addonDir, "package.json");
98
- if (!fs2.existsSync(manifestPath)) return [];
99
- const raw = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
186
+ const manifestPath = path3.join(addonDir, "package.json");
187
+ if (!fs3.existsSync(manifestPath)) return [];
188
+ const raw = JSON.parse(fs3.readFileSync(manifestPath, "utf-8"));
100
189
  const entries = raw.camstack?.addons ?? [];
101
190
  const ids = [];
102
191
  for (const entry of entries) {
@@ -128,7 +217,11 @@ function createAgentService(deps) {
128
217
  let memoryPercent = 0;
129
218
  const metrics = deps.getMetricsProvider?.();
130
219
  if (metrics) {
131
- const snapshot = await metrics.getCached();
220
+ const snapshot = await withTimeout(
221
+ metrics.getCached(),
222
+ METRICS_SNAPSHOT_TIMEOUT_MS,
223
+ null
224
+ );
132
225
  if (snapshot) {
133
226
  cpuPercent = snapshot.cpu.total;
134
227
  memoryPercent = snapshot.memory.percent;
@@ -151,7 +244,9 @@ function createAgentService(deps) {
151
244
  addons: [...deps.loadedAddons.values()].map((a) => ({
152
245
  id: a.id,
153
246
  status: a.status,
154
- version: a.version,
247
+ // Report the PACKAGE version (falls back to the declaration
248
+ // version) so the hub's per-node update check is accurate.
249
+ version: a.packageVersion ?? a.version,
155
250
  packageName: a.packageName
156
251
  }))
157
252
  };
@@ -165,7 +260,11 @@ function createAgentService(deps) {
165
260
  const metrics = deps.getMetricsProvider?.();
166
261
  if (metrics) {
167
262
  try {
168
- const snapshot = await metrics.getCached();
263
+ const snapshot = await withTimeout(
264
+ metrics.getCached(),
265
+ METRICS_SNAPSHOT_TIMEOUT_MS,
266
+ null
267
+ );
169
268
  if (snapshot) {
170
269
  cpuPercent = snapshot.cpu.total;
171
270
  memoryPercent = snapshot.memory.percent;
@@ -215,17 +314,17 @@ function createAgentService(deps) {
215
314
  deps.agentName = newName.trim();
216
315
  broker.logger.info(`Agent renamed: "${oldName}" \u2192 "${deps.agentName}"`);
217
316
  try {
218
- const configFile = path2.resolve(deps.configPath);
317
+ const configFile = path3.resolve(deps.configPath);
219
318
  let raw = {};
220
- if (fs2.existsSync(configFile)) {
319
+ if (fs3.existsSync(configFile)) {
221
320
  try {
222
- raw = JSON.parse(fs2.readFileSync(configFile, "utf-8"));
321
+ raw = JSON.parse(fs3.readFileSync(configFile, "utf-8"));
223
322
  } catch {
224
323
  }
225
324
  }
226
325
  raw.name = deps.agentName;
227
- fs2.mkdirSync(path2.dirname(configFile), { recursive: true });
228
- fs2.writeFileSync(configFile, JSON.stringify(raw, null, 2), "utf-8");
326
+ fs3.mkdirSync(path3.dirname(configFile), { recursive: true });
327
+ fs3.writeFileSync(configFile, JSON.stringify(raw, null, 2), "utf-8");
229
328
  broker.logger.info(`Agent name persisted to ${configFile}`);
230
329
  } catch (err) {
231
330
  broker.logger.warn(
@@ -245,20 +344,31 @@ function createAgentService(deps) {
245
344
  handler: async (ctx) => {
246
345
  const { params } = ctx;
247
346
  const { addonId, bundle } = params;
248
- const addonDir = path2.join(deps.addonsDir, addonId);
249
- fs2.mkdirSync(deps.addonsDir, { recursive: true });
250
- const bundlePath = path2.join(deps.addonsDir, `${addonId}.tgz`);
251
347
  const bufferData = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
252
- fs2.writeFileSync(bundlePath, bufferData);
253
- const { execFileSync } = await import("child_process");
254
- if (fs2.existsSync(addonDir)) {
255
- fs2.rmSync(addonDir, { recursive: true, force: true });
256
- }
257
- fs2.mkdirSync(addonDir, { recursive: true });
258
- execFileSync("tar", ["-xzf", bundlePath, "-C", addonDir, "--strip-components=1"], {
259
- timeout: 3e4
348
+ const { execFile } = await import("child_process");
349
+ const { promisify } = await import("util");
350
+ const execFileAsync = promisify(execFile);
351
+ const extract = async (tgz, destDir) => {
352
+ const tgzPath = path3.join(destDir, "..", `.${path3.basename(destDir)}.tgz`);
353
+ fs3.writeFileSync(tgzPath, tgz);
354
+ try {
355
+ await execFileAsync("tar", ["-xzf", tgzPath, "-C", destDir, "--strip-components=1"], {
356
+ timeout: 6e4
357
+ });
358
+ } finally {
359
+ try {
360
+ fs3.unlinkSync(tgzPath);
361
+ } catch {
362
+ }
363
+ }
364
+ };
365
+ const { addonDir } = await applyDeployedBundle({
366
+ addonsDir: deps.addonsDir,
367
+ addonId,
368
+ bundle: bufferData,
369
+ extract,
370
+ logger: deploySwapLogger
260
371
  });
261
- fs2.unlinkSync(bundlePath);
262
372
  for (const declId of readDeployedAddonIds(addonDir)) {
263
373
  deps.loadedAddons.delete(declId);
264
374
  }
@@ -305,15 +415,15 @@ function createAgentService(deps) {
305
415
  } catch {
306
416
  }
307
417
  deps.loadedAddons.delete(addonId);
308
- const addonDir = path2.join(deps.addonsDir, addonId);
309
- if (fs2.existsSync(addonDir)) {
310
- fs2.rmSync(addonDir, { recursive: true, force: true });
418
+ const addonDir = path3.join(deps.addonsDir, addonId);
419
+ if (fs3.existsSync(addonDir)) {
420
+ fs3.rmSync(addonDir, { recursive: true, force: true });
311
421
  }
312
422
  const pkgName = entry?.packageName;
313
423
  if (pkgName && pkgName !== addonId) {
314
- const pkgDir = path2.join(deps.addonsDir, pkgName);
315
- if (fs2.existsSync(pkgDir)) {
316
- fs2.rmSync(pkgDir, { recursive: true, force: true });
424
+ const pkgDir = path3.join(deps.addonsDir, pkgName);
425
+ if (fs3.existsSync(pkgDir)) {
426
+ fs3.rmSync(pkgDir, { recursive: true, force: true });
317
427
  }
318
428
  }
319
429
  broker.logger.info(`$agent.undeploy: ${addonId} disposed (instance + service + folder)`);
@@ -369,30 +479,30 @@ function createAgentService(deps) {
369
479
 
370
480
  // src/agent-http.ts
371
481
  import Fastify from "fastify";
372
- import * as fs3 from "fs";
373
- import * as path3 from "path";
482
+ import * as fs4 from "fs";
483
+ import * as path4 from "path";
374
484
  function resolveUiDistDir(dataDir) {
375
485
  const candidates = [
376
- path3.resolve(__dirname, "../../addon-agent-ui/dist"),
377
- path3.join(dataDir, "addons", "@camstack", "addon-agent-ui", "dist"),
378
- path3.join(dataDir, "agent-ui")
486
+ path4.resolve(__dirname, "../../addon-agent-ui/dist"),
487
+ path4.join(dataDir, "addons", "@camstack", "addon-agent-ui", "dist"),
488
+ path4.join(dataDir, "agent-ui")
379
489
  ];
380
490
  for (const dir of candidates) {
381
- if (fs3.existsSync(path3.join(dir, "index.html"))) return dir;
491
+ if (fs4.existsSync(path4.join(dir, "index.html"))) return dir;
382
492
  }
383
493
  return null;
384
494
  }
385
495
  function readConfigFile(configPath) {
386
- if (!fs3.existsSync(configPath)) return {};
496
+ if (!fs4.existsSync(configPath)) return {};
387
497
  try {
388
- return JSON.parse(fs3.readFileSync(configPath, "utf-8"));
498
+ return JSON.parse(fs4.readFileSync(configPath, "utf-8"));
389
499
  } catch {
390
500
  return {};
391
501
  }
392
502
  }
393
503
  function writeConfigFile(configPath, data) {
394
- fs3.mkdirSync(path3.dirname(configPath), { recursive: true });
395
- fs3.writeFileSync(configPath, JSON.stringify(data, null, 2), "utf-8");
504
+ fs4.mkdirSync(path4.dirname(configPath), { recursive: true });
505
+ fs4.writeFileSync(configPath, JSON.stringify(data, null, 2), "utf-8");
396
506
  }
397
507
  function getRegistryNodes(broker) {
398
508
  try {
@@ -555,8 +665,8 @@ async function startAgentHttpServer(getBroker, config) {
555
665
  }
556
666
 
557
667
  // src/agent-bootstrap.ts
558
- import * as fs4 from "fs";
559
- import * as path4 from "path";
668
+ import * as fs6 from "fs";
669
+ import * as path6 from "path";
560
670
  import {
561
671
  createBroker,
562
672
  createAddonService,
@@ -589,7 +699,7 @@ import {
589
699
  normalizeAddonInitResult,
590
700
  isDeployableToAgent,
591
701
  resolveRunnerId,
592
- resolveAddonPlacement
702
+ resolveAddonPlacement as resolveAddonPlacement2
593
703
  } from "@camstack/types";
594
704
  import {
595
705
  storageCapability,
@@ -606,6 +716,84 @@ import {
606
716
  errMsg
607
717
  } from "@camstack/types";
608
718
 
719
+ // src/agent-group-runner.ts
720
+ import * as fs5 from "fs";
721
+ import * as path5 from "path";
722
+ import { resolveAddonPlacement } from "@camstack/types";
723
+ function readPackageManifestAddons(dir) {
724
+ try {
725
+ const pkgPath = path5.join(dir, "package.json");
726
+ if (!fs5.existsSync(pkgPath)) return null;
727
+ const raw = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
728
+ const packageName = typeof raw.name === "string" ? raw.name : "";
729
+ const packageVersion = typeof raw.version === "string" ? raw.version : "0.0.0";
730
+ const entries = Array.isArray(raw.camstack?.addons) ? raw.camstack.addons : [];
731
+ const declarations = [];
732
+ for (const entry of entries) {
733
+ if (entry !== null && typeof entry === "object" && typeof entry.id === "string") {
734
+ declarations.push(entry);
735
+ }
736
+ }
737
+ if (!packageName || declarations.length === 0) return null;
738
+ return { packageName, packageVersion, declarations };
739
+ } catch {
740
+ return null;
741
+ }
742
+ }
743
+ function isGroupRunnerAddon(decl) {
744
+ return decl.execution !== void 0 && resolveAddonPlacement(decl) !== "hub-only";
745
+ }
746
+ function registerGroupRunnerProviders(deps, addons) {
747
+ for (const a of addons) {
748
+ for (const cap of a.capabilities) {
749
+ const capName = typeof cap === "string" ? cap : cap.name;
750
+ const proxy = new Proxy(
751
+ {},
752
+ {
753
+ get(_target, prop) {
754
+ if (prop === "then" || typeof prop === "symbol") return void 0;
755
+ return (params) => deps.broker.call(`${a.addonId}.${capName}.${prop}`, params);
756
+ }
757
+ }
758
+ );
759
+ deps.capabilityRegistry.registerProvider(capName, a.addonId, proxy);
760
+ }
761
+ deps.loadedAddons.set(a.addonId, {
762
+ id: a.addonId,
763
+ status: "running",
764
+ version: a.version,
765
+ packageName: a.packageName,
766
+ packageVersion: a.packageVersion
767
+ });
768
+ }
769
+ }
770
+ async function ensureGroupRunner(deps, groupId, addons) {
771
+ try {
772
+ const restart = await deps.broker.call("$process.restart", { name: groupId });
773
+ if (!restart.success) {
774
+ if (restart.reason !== void 0 && restart.reason !== "not found") {
775
+ deps.logger.warn(
776
+ `group "${groupId}" restart returned "${restart.reason}" \u2014 spawning a fresh runner`
777
+ );
778
+ }
779
+ await deps.broker.call("$process.spawnRunner", {
780
+ runnerId: groupId,
781
+ addons: addons.map((a) => ({ addonId: a.addonId, addonDir: a.addonDir }))
782
+ });
783
+ }
784
+ registerGroupRunnerProviders(deps, addons);
785
+ deps.logger.info(
786
+ `group "${groupId}" live with ${addons.length} addon(s): ${addons.map((a) => a.addonId).join(", ")}`
787
+ );
788
+ } catch (err) {
789
+ const msg = err instanceof Error ? err.stack ?? err.message : String(err);
790
+ deps.logger.error(`failed to ensure group "${groupId}": ${msg}`);
791
+ for (const a of addons) {
792
+ deps.loadedAddons.set(a.addonId, { id: a.addonId, status: "error" });
793
+ }
794
+ }
795
+ }
796
+
609
797
  // src/agent-cap-dispatch-service.ts
610
798
  import { AGENT_CAP_FWD_SERVICE } from "@camstack/system";
611
799
  function narrowParams(raw) {
@@ -687,7 +875,7 @@ async function startAgent(configPath) {
687
875
  const bundledDir = process.env["CAMSTACK_BUNDLED_ADDONS_DIR"];
688
876
  let workspaceDir = null;
689
877
  let resolvedSource = explicitSource;
690
- if (bundledDir && fs4.existsSync(bundledDir)) {
878
+ if (bundledDir && fs6.existsSync(bundledDir)) {
691
879
  workspaceDir = bundledDir;
692
880
  resolvedSource = "local";
693
881
  console.log(`[Agent] Using bundled addons from ${bundledDir}`);
@@ -1044,7 +1232,12 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
1044
1232
  storageProvider,
1045
1233
  addonConfig: { rootPath: config.dataDir },
1046
1234
  createLogger: loggerFactory,
1047
- capabilityRegistry: registry
1235
+ capabilityRegistry: registry,
1236
+ // Feed the storage-orchestrator its first-boot seed declarations
1237
+ // (addons-data:default, models:default, …). Without this the agent's
1238
+ // sqlite-settings aborts boot: "No default storage location
1239
+ // configured for type addons-data" → fatal crash-loop.
1240
+ listStorageLocationDeclarations: () => loader.listStorageLocationDeclarations()
1048
1241
  }
1049
1242
  );
1050
1243
  const initResult = normalizeAddonInitResult(await instance.initialize(context));
@@ -1058,6 +1251,7 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
1058
1251
  status: "running",
1059
1252
  version: addon.declaration.version,
1060
1253
  packageName: addon.packageName,
1254
+ packageVersion: addon.packageVersion,
1061
1255
  addon: instance
1062
1256
  });
1063
1257
  console.log(`[Agent] Core addon "${addonId}" initialized`);
@@ -1087,7 +1281,7 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
1087
1281
  for (const registered of loader.listAddons()) {
1088
1282
  if (loadedAddons.has(registered.declaration.id)) continue;
1089
1283
  if (registered.declaration.execution === void 0) continue;
1090
- const placement = resolveAddonPlacement(registered.declaration);
1284
+ const placement = resolveAddonPlacement2(registered.declaration);
1091
1285
  if (placement === "hub-only") continue;
1092
1286
  allGroupCandidates.push({
1093
1287
  groupId: resolveRunnerId(registered.declaration, registered.declaration.id),
@@ -1095,6 +1289,7 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
1095
1289
  addonDir: dir,
1096
1290
  version: registered.declaration.version ?? "0.0.0",
1097
1291
  packageName: registered.packageName,
1292
+ packageVersion: registered.packageVersion,
1098
1293
  capabilities: registered.declaration.capabilities ?? []
1099
1294
  });
1100
1295
  }
@@ -1112,27 +1307,7 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
1112
1307
  runnerId: groupId,
1113
1308
  addons: addons.map((a) => ({ addonId: a.addonId, addonDir: a.addonDir }))
1114
1309
  });
1115
- for (const a of addons) {
1116
- for (const cap of a.capabilities) {
1117
- const capName = typeof cap === "string" ? cap : cap.name;
1118
- const proxy = new Proxy(
1119
- {},
1120
- {
1121
- get(_target, prop) {
1122
- if (prop === "then" || typeof prop === "symbol") return void 0;
1123
- return (params) => broker.call(`${a.addonId}.${capName}.${prop}`, params);
1124
- }
1125
- }
1126
- );
1127
- capabilityRegistry.registerProvider(capName, a.addonId, proxy);
1128
- }
1129
- loadedAddons.set(a.addonId, {
1130
- id: a.addonId,
1131
- status: "running",
1132
- version: a.version,
1133
- packageName: a.packageName
1134
- });
1135
- }
1310
+ registerGroupRunnerProviders({ broker, capabilityRegistry, loadedAddons }, addons);
1136
1311
  console.log(
1137
1312
  `[Agent] Group "${groupId}" spawned with ${addons.length} addon(s): ${addons.map((a) => a.addonId).join(", ")}`
1138
1313
  );
@@ -1159,9 +1334,66 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
1159
1334
  }
1160
1335
  }
1161
1336
  async function loadDeployedAddons(broker, addonsDir, dataDir, loadedAddons, storageProvider, loggerFactory, capabilityRegistry) {
1162
- if (!fs4.existsSync(addonsDir)) return;
1163
- const loader = new AddonLoader();
1164
- await loader.loadFromDirectory(addonsDir);
1337
+ if (!fs6.existsSync(addonsDir)) return;
1338
+ const packageDirs = resolveAddonPackageDirs(addonsDir);
1339
+ const groupCandidates = [];
1340
+ const inProcessDirs = /* @__PURE__ */ new Set();
1341
+ for (const dir of packageDirs) {
1342
+ const manifest = readPackageManifestAddons(dir);
1343
+ if (!manifest) continue;
1344
+ for (const decl of manifest.declarations) {
1345
+ const addonId = decl.id;
1346
+ if (loadedAddons.has(addonId)) continue;
1347
+ if (!isDeployableToAgent(decl)) continue;
1348
+ if (isGroupRunnerAddon(decl)) {
1349
+ groupCandidates.push({
1350
+ groupId: resolveRunnerId(decl, addonId),
1351
+ addonId,
1352
+ addonDir: dir,
1353
+ version: decl.version ?? "0.0.0",
1354
+ packageName: manifest.packageName,
1355
+ packageVersion: manifest.packageVersion,
1356
+ capabilities: decl.capabilities ?? []
1357
+ });
1358
+ } else {
1359
+ inProcessDirs.add(dir);
1360
+ }
1361
+ }
1362
+ }
1363
+ if (inProcessDirs.size > 0) {
1364
+ await loadInProcessDeployedAddons(
1365
+ broker,
1366
+ [...inProcessDirs],
1367
+ dataDir,
1368
+ loadedAddons,
1369
+ storageProvider,
1370
+ loggerFactory,
1371
+ capabilityRegistry
1372
+ );
1373
+ }
1374
+ if (groupCandidates.length === 0) return;
1375
+ if (!capabilityRegistry) {
1376
+ console.warn(
1377
+ `[Agent] ${groupCandidates.length} deployed group addon(s) skipped \u2014 no capabilityRegistry passed`
1378
+ );
1379
+ return;
1380
+ }
1381
+ const grouped = /* @__PURE__ */ new Map();
1382
+ for (const c of groupCandidates) {
1383
+ const arr = grouped.get(c.groupId) ?? [];
1384
+ arr.push(c);
1385
+ grouped.set(c.groupId, arr);
1386
+ }
1387
+ const deployLogger = loggerFactory("agent-group-runner");
1388
+ for (const [groupId, addons] of grouped) {
1389
+ await ensureGroupRunner(
1390
+ { broker, capabilityRegistry, loadedAddons, logger: deployLogger },
1391
+ groupId,
1392
+ addons
1393
+ );
1394
+ }
1395
+ }
1396
+ async function loadInProcessDeployedAddons(broker, dirs, dataDir, loadedAddons, storageProvider, loggerFactory, capabilityRegistry) {
1165
1397
  const modelsDir = storageProvider ? await storageProvider.resolve({ location: "models", relativePath: "" }).catch(() => void 0) : void 0;
1166
1398
  const contextOptions = {
1167
1399
  storageProvider,
@@ -1169,45 +1401,56 @@ async function loadDeployedAddons(broker, addonsDir, dataDir, loadedAddons, stor
1169
1401
  createLogger: loggerFactory,
1170
1402
  capabilityRegistry
1171
1403
  };
1172
- for (const registered of loader.listAddons()) {
1173
- const addonId = registered.declaration.id;
1174
- if (loadedAddons.has(addonId)) continue;
1175
- if (!isDeployableToAgent(registered.declaration)) continue;
1404
+ for (const dir of dirs) {
1405
+ const loader = new AddonLoader();
1176
1406
  try {
1177
- const instance = new registered.addonClass();
1178
- const context = await createAddonContext(
1179
- broker,
1180
- registered.declaration,
1181
- dataDir,
1182
- contextOptions
1183
- );
1184
- await instance.initialize(context);
1185
- const serviceSchema = createAddonService(instance, registered.declaration);
1186
- broker.createService(serviceSchema);
1187
- loadedAddons.set(addonId, {
1188
- id: addonId,
1189
- status: "running",
1190
- version: registered.declaration.version,
1191
- packageName: registered.packageName,
1192
- addon: instance
1193
- });
1194
- console.log(`[Agent] Deployed addon "${addonId}" loaded`);
1407
+ await loader.loadFromAddonDir(dir);
1195
1408
  } catch (err) {
1196
- const msg = errMsg(err);
1197
- console.error(`[Agent] Failed to load deployed addon "${addonId}": ${msg}`);
1198
- loadedAddons.set(addonId, { id: addonId, status: "error" });
1409
+ console.warn(`[Agent] Skipping ${dir}: ${errMsg(err)}`);
1410
+ continue;
1411
+ }
1412
+ for (const registered of loader.listAddons()) {
1413
+ const addonId = registered.declaration.id;
1414
+ if (loadedAddons.has(addonId)) continue;
1415
+ if (!isDeployableToAgent(registered.declaration)) continue;
1416
+ if (isGroupRunnerAddon(registered.declaration)) continue;
1417
+ try {
1418
+ const instance = new registered.addonClass();
1419
+ const context = await createAddonContext(
1420
+ broker,
1421
+ registered.declaration,
1422
+ dataDir,
1423
+ contextOptions
1424
+ );
1425
+ await instance.initialize(context);
1426
+ const serviceSchema = createAddonService(instance, registered.declaration);
1427
+ broker.createService(serviceSchema);
1428
+ loadedAddons.set(addonId, {
1429
+ id: addonId,
1430
+ status: "running",
1431
+ version: registered.declaration.version,
1432
+ packageName: registered.packageName,
1433
+ packageVersion: registered.packageVersion,
1434
+ addon: instance
1435
+ });
1436
+ console.log(`[Agent] Deployed addon "${addonId}" loaded in-process`);
1437
+ } catch (err) {
1438
+ const msg = errMsg(err);
1439
+ console.error(`[Agent] Failed to load deployed addon "${addonId}": ${msg}`);
1440
+ loadedAddons.set(addonId, { id: addonId, status: "error" });
1441
+ }
1199
1442
  }
1200
1443
  }
1201
1444
  }
1202
1445
  function readAgentVersion() {
1203
1446
  const candidates = [
1204
- path4.resolve(__dirname, "..", "package.json"),
1205
- path4.resolve(__dirname, "..", "..", "package.json")
1447
+ path6.resolve(__dirname, "..", "package.json"),
1448
+ path6.resolve(__dirname, "..", "..", "package.json")
1206
1449
  ];
1207
1450
  for (const candidate of candidates) {
1208
1451
  try {
1209
- if (!fs4.existsSync(candidate)) continue;
1210
- const raw = JSON.parse(fs4.readFileSync(candidate, "utf-8"));
1452
+ if (!fs6.existsSync(candidate)) continue;
1453
+ const raw = JSON.parse(fs6.readFileSync(candidate, "utf-8"));
1211
1454
  if (raw.name === "@camstack/agent" && typeof raw.version === "string") return raw.version;
1212
1455
  } catch {
1213
1456
  }
@@ -1216,24 +1459,24 @@ function readAgentVersion() {
1216
1459
  }
1217
1460
  function isDir(p) {
1218
1461
  try {
1219
- return fs4.statSync(p).isDirectory();
1462
+ return fs6.statSync(p).isDirectory();
1220
1463
  } catch {
1221
1464
  return false;
1222
1465
  }
1223
1466
  }
1224
1467
  function resolveAddonPackageDirs(addonsDir) {
1225
1468
  const dirs = [];
1226
- if (!fs4.existsSync(addonsDir)) return dirs;
1227
- for (const name of fs4.readdirSync(addonsDir)) {
1228
- const full = path4.join(addonsDir, name);
1469
+ if (!fs6.existsSync(addonsDir)) return dirs;
1470
+ for (const name of fs6.readdirSync(addonsDir)) {
1471
+ const full = path6.join(addonsDir, name);
1229
1472
  if (name.startsWith("@") && isDir(full)) {
1230
- for (const sub of fs4.readdirSync(full)) {
1231
- const subFull = path4.join(full, sub);
1232
- if (isDir(subFull) && fs4.existsSync(path4.join(subFull, "package.json"))) {
1473
+ for (const sub of fs6.readdirSync(full)) {
1474
+ const subFull = path6.join(full, sub);
1475
+ if (isDir(subFull) && fs6.existsSync(path6.join(subFull, "package.json"))) {
1233
1476
  dirs.push(subFull);
1234
1477
  }
1235
1478
  }
1236
- } else if (isDir(full) && fs4.existsSync(path4.join(full, "package.json"))) {
1479
+ } else if (isDir(full) && fs6.existsSync(path6.join(full, "package.json"))) {
1237
1480
  dirs.push(full);
1238
1481
  }
1239
1482
  }
@@ -1266,4 +1509,4 @@ export {
1266
1509
  startAgentHttpServer,
1267
1510
  startAgent
1268
1511
  };
1269
- //# sourceMappingURL=chunk-5JHGVZFN.mjs.map
1512
+ //# sourceMappingURL=chunk-GM2IV7NO.mjs.map