@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/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 fs2 = __toESM(require("fs"));
108
- var path2 = __toESM(require("path"));
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 (!fs2.existsSync(configPath)) return null;
127
- const raw = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
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 = path2.join(addonDir, "package.json");
136
- if (!fs2.existsSync(manifestPath)) return [];
137
- const raw = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
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 metrics.getCached();
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: a.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 metrics.getCached();
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 = path2.resolve(deps.configPath);
355
+ const configFile = path3.resolve(deps.configPath);
257
356
  let raw = {};
258
- if (fs2.existsSync(configFile)) {
357
+ if (fs3.existsSync(configFile)) {
259
358
  try {
260
- raw = JSON.parse(fs2.readFileSync(configFile, "utf-8"));
359
+ raw = JSON.parse(fs3.readFileSync(configFile, "utf-8"));
261
360
  } catch {
262
361
  }
263
362
  }
264
363
  raw.name = deps.agentName;
265
- fs2.mkdirSync(path2.dirname(configFile), { recursive: true });
266
- fs2.writeFileSync(configFile, JSON.stringify(raw, null, 2), "utf-8");
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
- fs2.writeFileSync(bundlePath, bufferData);
291
- const { execFileSync } = await import("child_process");
292
- if (fs2.existsSync(addonDir)) {
293
- fs2.rmSync(addonDir, { recursive: true, force: true });
294
- }
295
- fs2.mkdirSync(addonDir, { recursive: true });
296
- execFileSync("tar", ["-xzf", bundlePath, "-C", addonDir, "--strip-components=1"], {
297
- timeout: 3e4
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 = path2.join(deps.addonsDir, addonId);
347
- if (fs2.existsSync(addonDir)) {
348
- fs2.rmSync(addonDir, { recursive: true, force: true });
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 = path2.join(deps.addonsDir, pkgName);
353
- if (fs2.existsSync(pkgDir)) {
354
- fs2.rmSync(pkgDir, { recursive: true, force: true });
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 fs4 = __toESM(require("fs"));
410
- var path4 = __toESM(require("path"));
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 fs3 = __toESM(require("fs"));
415
- var path3 = __toESM(require("path"));
524
+ var fs4 = __toESM(require("fs"));
525
+ var path4 = __toESM(require("path"));
416
526
  function resolveUiDistDir(dataDir) {
417
527
  const candidates = [
418
- path3.resolve(__dirname, "../../addon-agent-ui/dist"),
419
- path3.join(dataDir, "addons", "@camstack", "addon-agent-ui", "dist"),
420
- path3.join(dataDir, "agent-ui")
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 (fs3.existsSync(path3.join(dir, "index.html"))) return dir;
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 (!fs3.existsSync(configPath)) return {};
538
+ if (!fs4.existsSync(configPath)) return {};
429
539
  try {
430
- return JSON.parse(fs3.readFileSync(configPath, "utf-8"));
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
- fs3.mkdirSync(path3.dirname(configPath), { recursive: true });
437
- fs3.writeFileSync(configPath, JSON.stringify(data, null, 2), "utf-8");
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 && fs4.existsSync(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
- import_types2.storageCapability,
805
- import_types2.storageProviderCapability,
806
- import_types2.settingsStoreCapability,
807
- import_types2.logDestinationCapability,
808
- import_types2.metricsProviderCapability,
809
- import_types2.decoderCapability,
810
- import_types2.motionDetectionCapability,
811
- import_types2.pipelineExecutorCapability,
812
- import_types2.pipelineRunnerCapability,
813
- import_types2.audioAnalyzerCapability,
814
- import_types2.platformProbeCapability
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, import_types2.errMsg)(err)}`);
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, import_types.normalizeAddonInitResult)(await instance.initialize(context));
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, import_types2.errMsg)(err);
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, import_types2.errMsg)(err)}`);
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, import_types.resolveAddonPlacement)(registered.declaration);
1279
+ const placement = (0, import_types2.resolveAddonPlacement)(registered.declaration);
1086
1280
  if (placement === "hub-only") continue;
1087
1281
  allGroupCandidates.push({
1088
- groupId: (0, import_types.resolveRunnerId)(registered.declaration, registered.declaration.id),
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
- for (const a of addons) {
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, import_types.isDeployableToAgent)(registered.declaration)) continue;
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 (!fs4.existsSync(addonsDir)) return;
1158
- const loader = new import_system3.AddonLoader();
1159
- await loader.loadFromDirectory(addonsDir);
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 registered of loader.listAddons()) {
1168
- const addonId = registered.declaration.id;
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
- const instance = new registered.addonClass();
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
- const msg = (0, import_types2.errMsg)(err);
1192
- console.error(`[Agent] Failed to load deployed addon "${addonId}": ${msg}`);
1193
- loadedAddons.set(addonId, { id: addonId, status: "error" });
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
- path4.resolve(__dirname, "..", "package.json"),
1200
- path4.resolve(__dirname, "..", "..", "package.json")
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 (!fs4.existsSync(candidate)) continue;
1205
- const raw = JSON.parse(fs4.readFileSync(candidate, "utf-8"));
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 fs4.statSync(p).isDirectory();
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 (!fs4.existsSync(addonsDir)) return dirs;
1222
- for (const name of fs4.readdirSync(addonsDir)) {
1223
- const full = path4.join(addonsDir, name);
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 fs4.readdirSync(full)) {
1226
- const subFull = path4.join(full, sub);
1227
- if (isDir(subFull) && fs4.existsSync(path4.join(subFull, "package.json"))) {
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) && fs4.existsSync(path4.join(full, "package.json"))) {
1474
+ } else if (isDir(full) && fs6.existsSync(path6.join(full, "package.json"))) {
1232
1475
  dirs.push(full);
1233
1476
  }
1234
1477
  }