@camstack/agent 1.0.7 → 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/{chunk-GM2IV7NO.mjs → chunk-PFQZTXGW.mjs} +149 -23
- package/dist/chunk-PFQZTXGW.mjs.map +1 -0
- package/dist/cli.js +143 -21
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.ts +24 -0
- package/dist/index.js +143 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +3 -2
- package/dist/chunk-GM2IV7NO.mjs.map +0 -1
|
@@ -69,7 +69,11 @@ import * as os2 from "os";
|
|
|
69
69
|
import * as fs3 from "fs";
|
|
70
70
|
import * as path3 from "path";
|
|
71
71
|
import { Errors } from "moleculer";
|
|
72
|
-
import {
|
|
72
|
+
import {
|
|
73
|
+
clusterSecretMatches,
|
|
74
|
+
CLUSTER_SECRET_MISMATCH_TYPE,
|
|
75
|
+
isAddonDeploySource
|
|
76
|
+
} from "@camstack/system";
|
|
73
77
|
|
|
74
78
|
// src/agent-deploy-swap.ts
|
|
75
79
|
import * as fs2 from "fs";
|
|
@@ -138,6 +142,29 @@ async function applyDeployedBundle(input) {
|
|
|
138
142
|
return { addonDir: liveDir };
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
// src/fetch-bundle-from-hub.ts
|
|
146
|
+
import { createHash } from "crypto";
|
|
147
|
+
import { Agent, fetch as undicicFetch } from "undici";
|
|
148
|
+
async function fetchBundleFromHub(opts) {
|
|
149
|
+
const authHeader = { Authorization: `Bearer ${opts.token}` };
|
|
150
|
+
const res = opts.fetchImpl ? await opts.fetchImpl(opts.url, { headers: authHeader }) : await undicicFetch(opts.url, {
|
|
151
|
+
headers: authHeader,
|
|
152
|
+
dispatcher: new Agent({ connect: { rejectUnauthorized: false } })
|
|
153
|
+
});
|
|
154
|
+
if (!res.ok) {
|
|
155
|
+
throw new Error(`bundle fetch failed: HTTP ${res.status}`);
|
|
156
|
+
}
|
|
157
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
158
|
+
if (buffer.length !== opts.bytes) {
|
|
159
|
+
throw new Error(`bundle bytes mismatch: expected ${opts.bytes}, got ${buffer.length}`);
|
|
160
|
+
}
|
|
161
|
+
const sha = createHash("sha256").update(buffer).digest("hex");
|
|
162
|
+
if (sha !== opts.sha256) {
|
|
163
|
+
throw new Error(`bundle sha256 mismatch: expected ${opts.sha256}, got ${sha}`);
|
|
164
|
+
}
|
|
165
|
+
return buffer;
|
|
166
|
+
}
|
|
167
|
+
|
|
141
168
|
// src/agent-service.ts
|
|
142
169
|
var deploySwapLogger = {
|
|
143
170
|
info: (msg) => console.log(`[Agent] ${msg}`),
|
|
@@ -171,6 +198,28 @@ async function withTimeout(p, ms, fallback) {
|
|
|
171
198
|
}
|
|
172
199
|
}
|
|
173
200
|
var METRICS_SNAPSHOT_TIMEOUT_MS = 2e3;
|
|
201
|
+
function resolveDeployAction(seam) {
|
|
202
|
+
return async (params) => {
|
|
203
|
+
const { addonId, source, bundle } = params;
|
|
204
|
+
if (source && isAddonDeploySource(source)) {
|
|
205
|
+
if (source.kind === "npm") {
|
|
206
|
+
await seam.installFromNpm(addonId, source.version);
|
|
207
|
+
return { success: true, addonId };
|
|
208
|
+
}
|
|
209
|
+
const buf2 = await seam.fetchBundle(source);
|
|
210
|
+
const { addonDir: addonDir2 } = await seam.applyBundle(buf2);
|
|
211
|
+
seam.onApplied(addonDir2);
|
|
212
|
+
return { success: true, addonId, path: addonDir2 };
|
|
213
|
+
}
|
|
214
|
+
if (bundle === void 0) {
|
|
215
|
+
throw new Error("$agent.deploy: no source descriptor and no legacy bundle provided");
|
|
216
|
+
}
|
|
217
|
+
const buf = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
|
|
218
|
+
const { addonDir } = await seam.applyBundle(buf);
|
|
219
|
+
seam.onApplied(addonDir);
|
|
220
|
+
return { success: true, addonId, path: addonDir };
|
|
221
|
+
};
|
|
222
|
+
}
|
|
174
223
|
function readHubAddressFromConfig(configPath) {
|
|
175
224
|
if (!configPath) return null;
|
|
176
225
|
try {
|
|
@@ -343,8 +392,7 @@ function createAgentService(deps) {
|
|
|
343
392
|
deploy: {
|
|
344
393
|
handler: async (ctx) => {
|
|
345
394
|
const { params } = ctx;
|
|
346
|
-
const { addonId, bundle } = params;
|
|
347
|
-
const bufferData = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
|
|
395
|
+
const { addonId, source, bundle } = params;
|
|
348
396
|
const { execFile } = await import("child_process");
|
|
349
397
|
const { promisify } = await import("util");
|
|
350
398
|
const execFileAsync = promisify(execFile);
|
|
@@ -362,17 +410,37 @@ function createAgentService(deps) {
|
|
|
362
410
|
}
|
|
363
411
|
}
|
|
364
412
|
};
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
413
|
+
const seam = {
|
|
414
|
+
installFromNpm: (pkg, v) => {
|
|
415
|
+
if (!deps.installFromNpm) {
|
|
416
|
+
throw new Error("npm deploy source unsupported on this agent");
|
|
417
|
+
}
|
|
418
|
+
return deps.installFromNpm(pkg, v);
|
|
419
|
+
},
|
|
420
|
+
applyBundle: (buf) => applyDeployedBundle({
|
|
421
|
+
addonsDir: deps.addonsDir,
|
|
422
|
+
addonId,
|
|
423
|
+
bundle: buf,
|
|
424
|
+
extract,
|
|
425
|
+
logger: deploySwapLogger
|
|
426
|
+
}),
|
|
427
|
+
fetchBundle: (s) => fetchBundleFromHub(s),
|
|
428
|
+
// Evict every addon DECLARATION id this package contributes
|
|
429
|
+
// from `loadedAddons` so the follow-up `$agent.reload` actually
|
|
430
|
+
// re-instantiates it. `loadDeployedAddons` skips any addon
|
|
431
|
+
// still present in `loadedAddons`; without this eviction a
|
|
432
|
+
// redeploy would leave the agent pinned to the pre-update
|
|
433
|
+
// version. The `deploy` param `addonId` is the PACKAGE name
|
|
434
|
+
// (used only as the on-disk dir), whereas `loadedAddons` is
|
|
435
|
+
// keyed by the addon DECLARATION id — they differ for scoped
|
|
436
|
+
// packages, so we read the extracted manifest to bridge them.
|
|
437
|
+
onApplied: (addonDir) => {
|
|
438
|
+
for (const declId of readDeployedAddonIds(addonDir)) {
|
|
439
|
+
deps.loadedAddons.delete(declId);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
return resolveDeployAction(seam)({ addonId, source, bundle });
|
|
376
444
|
}
|
|
377
445
|
},
|
|
378
446
|
/**
|
|
@@ -512,6 +580,12 @@ function getRegistryNodes(broker) {
|
|
|
512
580
|
return [];
|
|
513
581
|
}
|
|
514
582
|
}
|
|
583
|
+
function deriveHubConnectionState(nodes, discoveryMode) {
|
|
584
|
+
const hubInRegistry = nodes.some((n) => n.id === "hub");
|
|
585
|
+
if (hubInRegistry) return "connected";
|
|
586
|
+
if (discoveryMode) return "searching";
|
|
587
|
+
return "disconnected";
|
|
588
|
+
}
|
|
515
589
|
function getEffectiveConfig(configPath, nodeId) {
|
|
516
590
|
const raw = readConfigFile(configPath);
|
|
517
591
|
return {
|
|
@@ -543,6 +617,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
543
617
|
const nodes = getRegistryNodes(broker);
|
|
544
618
|
const hubConnected = nodes.some((n) => n.id === "hub");
|
|
545
619
|
const discoveryMode = !eff.hubAddress;
|
|
620
|
+
const hubConnectionState = config.getHubConnectionState ? config.getHubConnectionState() : deriveHubConnectionState(nodes, discoveryMode);
|
|
546
621
|
try {
|
|
547
622
|
const status = await broker.call("$agent.status");
|
|
548
623
|
return {
|
|
@@ -550,6 +625,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
550
625
|
name: eff.name,
|
|
551
626
|
hubAddress: eff.hubAddress,
|
|
552
627
|
hubConnected,
|
|
628
|
+
hubConnectionState,
|
|
553
629
|
discoveryMode,
|
|
554
630
|
hasSecret: eff.hasSecret,
|
|
555
631
|
discoveredNodes: nodes.filter((n) => n.id !== broker.nodeID).map((n) => n.id)
|
|
@@ -560,6 +636,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
560
636
|
name: eff.name,
|
|
561
637
|
hubAddress: eff.hubAddress,
|
|
562
638
|
hubConnected,
|
|
639
|
+
hubConnectionState,
|
|
563
640
|
discoveryMode,
|
|
564
641
|
hasSecret: eff.hasSecret,
|
|
565
642
|
discoveredNodes: [],
|
|
@@ -819,7 +896,7 @@ function narrowParams(raw) {
|
|
|
819
896
|
deviceId: typeof deviceId === "number" ? deviceId : void 0
|
|
820
897
|
};
|
|
821
898
|
}
|
|
822
|
-
function createAgentCapDispatchService(agentNodeId, agentUdsRegistry, logger) {
|
|
899
|
+
function createAgentCapDispatchService(agentNodeId, agentUdsRegistry, inProcessLookup, logger) {
|
|
823
900
|
return {
|
|
824
901
|
name: AGENT_CAP_FWD_SERVICE,
|
|
825
902
|
actions: {
|
|
@@ -829,11 +906,15 @@ function createAgentCapDispatchService(agentNodeId, agentUdsRegistry, logger) {
|
|
|
829
906
|
const { capName, method, args, deviceId } = params;
|
|
830
907
|
const childId = params.childId !== void 0 ? params.childId : agentUdsRegistry.resolveChildId(capName, deviceId);
|
|
831
908
|
if (childId == null) {
|
|
909
|
+
const ref = inProcessLookup?.(capName) ?? null;
|
|
910
|
+
if (ref !== null) {
|
|
911
|
+
return ref.invoke(method, args);
|
|
912
|
+
}
|
|
832
913
|
logger?.info(
|
|
833
|
-
`agent ${agentNodeId}: no
|
|
914
|
+
`agent ${agentNodeId}: no provider for cap "${capName}"${deviceId !== void 0 ? ` (deviceId ${deviceId})` : ""}`
|
|
834
915
|
);
|
|
835
916
|
throw new Error(
|
|
836
|
-
`agent ${agentNodeId} has no
|
|
917
|
+
`agent ${agentNodeId} has no provider for cap "${capName}"${deviceId !== void 0 ? ` (deviceId ${deviceId})` : ""}`
|
|
837
918
|
);
|
|
838
919
|
}
|
|
839
920
|
const input = {
|
|
@@ -850,9 +931,11 @@ function createAgentCapDispatchService(agentNodeId, agentUdsRegistry, logger) {
|
|
|
850
931
|
}
|
|
851
932
|
|
|
852
933
|
// src/register-agent-cap-dispatch.ts
|
|
853
|
-
function registerAgentCapDispatch(registrar, agentUdsRegistry, logger) {
|
|
934
|
+
function registerAgentCapDispatch(registrar, agentUdsRegistry, inProcessLookup, logger) {
|
|
854
935
|
if (agentUdsRegistry === void 0) return;
|
|
855
|
-
registrar.createService(
|
|
936
|
+
registrar.createService(
|
|
937
|
+
createAgentCapDispatchService(registrar.nodeID, agentUdsRegistry, inProcessLookup, logger)
|
|
938
|
+
);
|
|
856
939
|
}
|
|
857
940
|
|
|
858
941
|
// src/agent-bootstrap.ts
|
|
@@ -918,6 +1001,27 @@ async function startAgent(configPath) {
|
|
|
918
1001
|
};
|
|
919
1002
|
const loadedAddons = /* @__PURE__ */ new Map();
|
|
920
1003
|
const subtree = new HubNodeRegistry();
|
|
1004
|
+
let hubConnectionStateOverride = null;
|
|
1005
|
+
function getHubConnectionState() {
|
|
1006
|
+
if (hubConnectionStateOverride !== null) return hubConnectionStateOverride;
|
|
1007
|
+
try {
|
|
1008
|
+
const registry = broker.registry;
|
|
1009
|
+
const nodes = registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
|
|
1010
|
+
if (nodes.some((n) => n.id === "hub")) return "connected";
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
const configNow = readConfigFile2(config.configPath);
|
|
1014
|
+
const discoveryMode = typeof configNow.hubAddress !== "string" || configNow.hubAddress.length === 0;
|
|
1015
|
+
return discoveryMode ? "searching" : "disconnected";
|
|
1016
|
+
}
|
|
1017
|
+
function readConfigFile2(p) {
|
|
1018
|
+
if (!fs6.existsSync(p)) return {};
|
|
1019
|
+
try {
|
|
1020
|
+
return JSON.parse(fs6.readFileSync(p, "utf-8"));
|
|
1021
|
+
} catch {
|
|
1022
|
+
return {};
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
921
1025
|
const registerAbortController = new AbortController();
|
|
922
1026
|
function buildAgentOwnManifest() {
|
|
923
1027
|
const addonCapMap = /* @__PURE__ */ new Map();
|
|
@@ -980,6 +1084,7 @@ async function startAgent(configPath) {
|
|
|
980
1084
|
consoleLogger.error(
|
|
981
1085
|
"hub registration rejected: cluster secret mismatch \u2014 correct CAMSTACK_CLUSTER_SECRET and restart the agent"
|
|
982
1086
|
);
|
|
1087
|
+
hubConnectionStateOverride = "secret-mismatch";
|
|
983
1088
|
return;
|
|
984
1089
|
}
|
|
985
1090
|
});
|
|
@@ -1050,7 +1155,10 @@ async function startAgent(configPath) {
|
|
|
1050
1155
|
subtree.registerNode(params);
|
|
1051
1156
|
triggerUpwardRegistration();
|
|
1052
1157
|
},
|
|
1053
|
-
expectedClusterSecretHash: config.secret ? hashClusterSecret(config.secret) : void 0
|
|
1158
|
+
expectedClusterSecretHash: config.secret ? hashClusterSecret(config.secret) : void 0,
|
|
1159
|
+
installFromNpm: async (pkg, version) => {
|
|
1160
|
+
await installer.install(pkg, version);
|
|
1161
|
+
}
|
|
1054
1162
|
});
|
|
1055
1163
|
broker.createService(agentServiceSchema);
|
|
1056
1164
|
const agentTcpPort = deriveAgentListenPort(broker.nodeID);
|
|
@@ -1109,7 +1217,21 @@ async function startAgent(configPath) {
|
|
|
1109
1217
|
agentParentUdsPath
|
|
1110
1218
|
);
|
|
1111
1219
|
broker.createService(processServiceSchema);
|
|
1112
|
-
|
|
1220
|
+
const agentInProcessLookup = (capName) => {
|
|
1221
|
+
const provider = capabilityRegistry.getSingleton(capName);
|
|
1222
|
+
if (provider === null || provider === void 0) return null;
|
|
1223
|
+
return {
|
|
1224
|
+
invoke: (method, args) => {
|
|
1225
|
+
const fn = provider[method];
|
|
1226
|
+
if (typeof fn !== "function") {
|
|
1227
|
+
return Promise.reject(new Error(`method "${method}" not found on cap "${capName}"`));
|
|
1228
|
+
}
|
|
1229
|
+
const result = fn.call(provider, args);
|
|
1230
|
+
return Promise.resolve(result);
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
};
|
|
1234
|
+
registerAgentCapDispatch(broker, agentUdsRegistry, agentInProcessLookup, consoleLogger);
|
|
1113
1235
|
registerEventBusService(broker);
|
|
1114
1236
|
broker.createService(createHwAccelService(createKernelHwAccel()));
|
|
1115
1237
|
await broker.start();
|
|
@@ -1136,7 +1258,8 @@ async function startAgent(configPath) {
|
|
|
1136
1258
|
nodeId: config.nodeId,
|
|
1137
1259
|
dataDir: config.dataDir,
|
|
1138
1260
|
configPath: config.configPath,
|
|
1139
|
-
onReconnect: reconnect
|
|
1261
|
+
onReconnect: reconnect,
|
|
1262
|
+
getHubConnectionState
|
|
1140
1263
|
});
|
|
1141
1264
|
await bootCoreAddons(broker, config, capabilityRegistry, loadedAddons, loggerFactory);
|
|
1142
1265
|
for (const dest of capabilityRegistry.getCollection("log-destination")) {
|
|
@@ -1158,6 +1281,9 @@ async function startAgent(configPath) {
|
|
|
1158
1281
|
broker.localBus.on("$node.connected", (data) => {
|
|
1159
1282
|
const node = data.node;
|
|
1160
1283
|
if (node?.id !== "hub") return;
|
|
1284
|
+
if (hubConnectionStateOverride === "secret-mismatch") {
|
|
1285
|
+
hubConnectionStateOverride = null;
|
|
1286
|
+
}
|
|
1161
1287
|
triggerUpwardRegistration();
|
|
1162
1288
|
if (hubReachableFired) return;
|
|
1163
1289
|
hubReachableFired = true;
|
|
@@ -1509,4 +1635,4 @@ export {
|
|
|
1509
1635
|
startAgentHttpServer,
|
|
1510
1636
|
startAgent
|
|
1511
1637
|
};
|
|
1512
|
-
//# sourceMappingURL=chunk-
|
|
1638
|
+
//# sourceMappingURL=chunk-PFQZTXGW.mjs.map
|