@camstack/agent 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __cr } from 'node:module'; const require = globalThis.require ?? __cr(import.meta.url);
3
3
  import {
4
4
  startAgent
5
- } from "./chunk-U6SD2QE6.mjs";
5
+ } from "./chunk-PFQZTXGW.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import * as os from "os";
package/dist/index.d.ts CHANGED
@@ -75,6 +75,8 @@ interface AgentServiceDeps {
75
75
  * presenting a matching `clusterSecretHash`. (Cluster-secret gate.)
76
76
  */
77
77
  readonly expectedClusterSecretHash?: string;
78
+ /** Pull-install a published addon from npm/GHCR (wired by agent-bootstrap). */
79
+ readonly installFromNpm?: (packageName: string, version: string) => Promise<void>;
78
80
  }
79
81
  declare function createAgentService(deps: AgentServiceDeps): ServiceSchema;
80
82
 
package/dist/index.js CHANGED
@@ -176,6 +176,29 @@ async function applyDeployedBundle(input) {
176
176
  return { addonDir: liveDir };
177
177
  }
178
178
 
179
+ // src/fetch-bundle-from-hub.ts
180
+ var import_node_crypto2 = require("crypto");
181
+ var import_undici = require("undici");
182
+ async function fetchBundleFromHub(opts) {
183
+ const authHeader = { Authorization: `Bearer ${opts.token}` };
184
+ const res = opts.fetchImpl ? await opts.fetchImpl(opts.url, { headers: authHeader }) : await (0, import_undici.fetch)(opts.url, {
185
+ headers: authHeader,
186
+ dispatcher: new import_undici.Agent({ connect: { rejectUnauthorized: false } })
187
+ });
188
+ if (!res.ok) {
189
+ throw new Error(`bundle fetch failed: HTTP ${res.status}`);
190
+ }
191
+ const buffer = Buffer.from(await res.arrayBuffer());
192
+ if (buffer.length !== opts.bytes) {
193
+ throw new Error(`bundle bytes mismatch: expected ${opts.bytes}, got ${buffer.length}`);
194
+ }
195
+ const sha = (0, import_node_crypto2.createHash)("sha256").update(buffer).digest("hex");
196
+ if (sha !== opts.sha256) {
197
+ throw new Error(`bundle sha256 mismatch: expected ${opts.sha256}, got ${sha}`);
198
+ }
199
+ return buffer;
200
+ }
201
+
179
202
  // src/agent-service.ts
180
203
  var deploySwapLogger = {
181
204
  info: (msg) => console.log(`[Agent] ${msg}`),
@@ -209,6 +232,28 @@ async function withTimeout(p, ms, fallback) {
209
232
  }
210
233
  }
211
234
  var METRICS_SNAPSHOT_TIMEOUT_MS = 2e3;
235
+ function resolveDeployAction(seam) {
236
+ return async (params) => {
237
+ const { addonId, source, bundle } = params;
238
+ if (source && (0, import_system.isAddonDeploySource)(source)) {
239
+ if (source.kind === "npm") {
240
+ await seam.installFromNpm(addonId, source.version);
241
+ return { success: true, addonId };
242
+ }
243
+ const buf2 = await seam.fetchBundle(source);
244
+ const { addonDir: addonDir2 } = await seam.applyBundle(buf2);
245
+ seam.onApplied(addonDir2);
246
+ return { success: true, addonId, path: addonDir2 };
247
+ }
248
+ if (bundle === void 0) {
249
+ throw new Error("$agent.deploy: no source descriptor and no legacy bundle provided");
250
+ }
251
+ const buf = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
252
+ const { addonDir } = await seam.applyBundle(buf);
253
+ seam.onApplied(addonDir);
254
+ return { success: true, addonId, path: addonDir };
255
+ };
256
+ }
212
257
  function readHubAddressFromConfig(configPath) {
213
258
  if (!configPath) return null;
214
259
  try {
@@ -381,8 +426,7 @@ function createAgentService(deps) {
381
426
  deploy: {
382
427
  handler: async (ctx) => {
383
428
  const { params } = ctx;
384
- const { addonId, bundle } = params;
385
- const bufferData = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
429
+ const { addonId, source, bundle } = params;
386
430
  const { execFile } = await import("child_process");
387
431
  const { promisify } = await import("util");
388
432
  const execFileAsync = promisify(execFile);
@@ -400,17 +444,37 @@ function createAgentService(deps) {
400
444
  }
401
445
  }
402
446
  };
403
- const { addonDir } = await applyDeployedBundle({
404
- addonsDir: deps.addonsDir,
405
- addonId,
406
- bundle: bufferData,
407
- extract,
408
- logger: deploySwapLogger
409
- });
410
- for (const declId of readDeployedAddonIds(addonDir)) {
411
- deps.loadedAddons.delete(declId);
412
- }
413
- return { success: true, addonId, path: addonDir };
447
+ const seam = {
448
+ installFromNpm: (pkg, v) => {
449
+ if (!deps.installFromNpm) {
450
+ throw new Error("npm deploy source unsupported on this agent");
451
+ }
452
+ return deps.installFromNpm(pkg, v);
453
+ },
454
+ applyBundle: (buf) => applyDeployedBundle({
455
+ addonsDir: deps.addonsDir,
456
+ addonId,
457
+ bundle: buf,
458
+ extract,
459
+ logger: deploySwapLogger
460
+ }),
461
+ fetchBundle: (s) => fetchBundleFromHub(s),
462
+ // Evict every addon DECLARATION id this package contributes
463
+ // from `loadedAddons` so the follow-up `$agent.reload` actually
464
+ // re-instantiates it. `loadDeployedAddons` skips any addon
465
+ // still present in `loadedAddons`; without this eviction a
466
+ // redeploy would leave the agent pinned to the pre-update
467
+ // version. The `deploy` param `addonId` is the PACKAGE name
468
+ // (used only as the on-disk dir), whereas `loadedAddons` is
469
+ // keyed by the addon DECLARATION id — they differ for scoped
470
+ // packages, so we read the extracted manifest to bridge them.
471
+ onApplied: (addonDir) => {
472
+ for (const declId of readDeployedAddonIds(addonDir)) {
473
+ deps.loadedAddons.delete(declId);
474
+ }
475
+ }
476
+ };
477
+ return resolveDeployAction(seam)({ addonId, source, bundle });
414
478
  }
415
479
  },
416
480
  /**
@@ -1082,7 +1146,10 @@ async function startAgent(configPath) {
1082
1146
  subtree.registerNode(params);
1083
1147
  triggerUpwardRegistration();
1084
1148
  },
1085
- expectedClusterSecretHash: config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0
1149
+ expectedClusterSecretHash: config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0,
1150
+ installFromNpm: async (pkg, version) => {
1151
+ await installer.install(pkg, version);
1152
+ }
1086
1153
  });
1087
1154
  broker.createService(agentServiceSchema);
1088
1155
  const agentTcpPort = (0, import_system3.deriveAgentListenPort)(broker.nodeID);