@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
package/dist/cli.js
CHANGED
|
@@ -65,6 +65,12 @@ function getRegistryNodes(broker) {
|
|
|
65
65
|
return [];
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
function deriveHubConnectionState(nodes, discoveryMode) {
|
|
69
|
+
const hubInRegistry = nodes.some((n) => n.id === "hub");
|
|
70
|
+
if (hubInRegistry) return "connected";
|
|
71
|
+
if (discoveryMode) return "searching";
|
|
72
|
+
return "disconnected";
|
|
73
|
+
}
|
|
68
74
|
function getEffectiveConfig(configPath, nodeId) {
|
|
69
75
|
const raw = readConfigFile(configPath);
|
|
70
76
|
return {
|
|
@@ -96,6 +102,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
96
102
|
const nodes = getRegistryNodes(broker);
|
|
97
103
|
const hubConnected = nodes.some((n) => n.id === "hub");
|
|
98
104
|
const discoveryMode = !eff.hubAddress;
|
|
105
|
+
const hubConnectionState = config.getHubConnectionState ? config.getHubConnectionState() : deriveHubConnectionState(nodes, discoveryMode);
|
|
99
106
|
try {
|
|
100
107
|
const status = await broker.call("$agent.status");
|
|
101
108
|
return {
|
|
@@ -103,6 +110,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
103
110
|
name: eff.name,
|
|
104
111
|
hubAddress: eff.hubAddress,
|
|
105
112
|
hubConnected,
|
|
113
|
+
hubConnectionState,
|
|
106
114
|
discoveryMode,
|
|
107
115
|
hasSecret: eff.hasSecret,
|
|
108
116
|
discoveredNodes: nodes.filter((n) => n.id !== broker.nodeID).map((n) => n.id)
|
|
@@ -113,6 +121,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
113
121
|
name: eff.name,
|
|
114
122
|
hubAddress: eff.hubAddress,
|
|
115
123
|
hubConnected,
|
|
124
|
+
hubConnectionState,
|
|
116
125
|
discoveryMode,
|
|
117
126
|
hasSecret: eff.hasSecret,
|
|
118
127
|
discoveredNodes: [],
|
|
@@ -360,6 +369,29 @@ async function applyDeployedBundle(input) {
|
|
|
360
369
|
return { addonDir: liveDir };
|
|
361
370
|
}
|
|
362
371
|
|
|
372
|
+
// src/fetch-bundle-from-hub.ts
|
|
373
|
+
var import_node_crypto2 = require("crypto");
|
|
374
|
+
var import_undici = require("undici");
|
|
375
|
+
async function fetchBundleFromHub(opts) {
|
|
376
|
+
const authHeader = { Authorization: `Bearer ${opts.token}` };
|
|
377
|
+
const res = opts.fetchImpl ? await opts.fetchImpl(opts.url, { headers: authHeader }) : await (0, import_undici.fetch)(opts.url, {
|
|
378
|
+
headers: authHeader,
|
|
379
|
+
dispatcher: new import_undici.Agent({ connect: { rejectUnauthorized: false } })
|
|
380
|
+
});
|
|
381
|
+
if (!res.ok) {
|
|
382
|
+
throw new Error(`bundle fetch failed: HTTP ${res.status}`);
|
|
383
|
+
}
|
|
384
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
385
|
+
if (buffer.length !== opts.bytes) {
|
|
386
|
+
throw new Error(`bundle bytes mismatch: expected ${opts.bytes}, got ${buffer.length}`);
|
|
387
|
+
}
|
|
388
|
+
const sha = (0, import_node_crypto2.createHash)("sha256").update(buffer).digest("hex");
|
|
389
|
+
if (sha !== opts.sha256) {
|
|
390
|
+
throw new Error(`bundle sha256 mismatch: expected ${opts.sha256}, got ${sha}`);
|
|
391
|
+
}
|
|
392
|
+
return buffer;
|
|
393
|
+
}
|
|
394
|
+
|
|
363
395
|
// src/agent-service.ts
|
|
364
396
|
var deploySwapLogger = {
|
|
365
397
|
info: (msg) => console.log(`[Agent] ${msg}`),
|
|
@@ -393,6 +425,28 @@ async function withTimeout(p, ms, fallback) {
|
|
|
393
425
|
}
|
|
394
426
|
}
|
|
395
427
|
var METRICS_SNAPSHOT_TIMEOUT_MS = 2e3;
|
|
428
|
+
function resolveDeployAction(seam) {
|
|
429
|
+
return async (params) => {
|
|
430
|
+
const { addonId, source, bundle } = params;
|
|
431
|
+
if (source && (0, import_system.isAddonDeploySource)(source)) {
|
|
432
|
+
if (source.kind === "npm") {
|
|
433
|
+
await seam.installFromNpm(addonId, source.version);
|
|
434
|
+
return { success: true, addonId };
|
|
435
|
+
}
|
|
436
|
+
const buf2 = await seam.fetchBundle(source);
|
|
437
|
+
const { addonDir: addonDir2 } = await seam.applyBundle(buf2);
|
|
438
|
+
seam.onApplied(addonDir2);
|
|
439
|
+
return { success: true, addonId, path: addonDir2 };
|
|
440
|
+
}
|
|
441
|
+
if (bundle === void 0) {
|
|
442
|
+
throw new Error("$agent.deploy: no source descriptor and no legacy bundle provided");
|
|
443
|
+
}
|
|
444
|
+
const buf = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
|
|
445
|
+
const { addonDir } = await seam.applyBundle(buf);
|
|
446
|
+
seam.onApplied(addonDir);
|
|
447
|
+
return { success: true, addonId, path: addonDir };
|
|
448
|
+
};
|
|
449
|
+
}
|
|
396
450
|
function readHubAddressFromConfig(configPath) {
|
|
397
451
|
if (!configPath) return null;
|
|
398
452
|
try {
|
|
@@ -565,8 +619,7 @@ function createAgentService(deps) {
|
|
|
565
619
|
deploy: {
|
|
566
620
|
handler: async (ctx) => {
|
|
567
621
|
const { params } = ctx;
|
|
568
|
-
const { addonId, bundle } = params;
|
|
569
|
-
const bufferData = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
|
|
622
|
+
const { addonId, source, bundle } = params;
|
|
570
623
|
const { execFile } = await import("child_process");
|
|
571
624
|
const { promisify } = await import("util");
|
|
572
625
|
const execFileAsync = promisify(execFile);
|
|
@@ -584,17 +637,37 @@ function createAgentService(deps) {
|
|
|
584
637
|
}
|
|
585
638
|
}
|
|
586
639
|
};
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
640
|
+
const seam = {
|
|
641
|
+
installFromNpm: (pkg, v) => {
|
|
642
|
+
if (!deps.installFromNpm) {
|
|
643
|
+
throw new Error("npm deploy source unsupported on this agent");
|
|
644
|
+
}
|
|
645
|
+
return deps.installFromNpm(pkg, v);
|
|
646
|
+
},
|
|
647
|
+
applyBundle: (buf) => applyDeployedBundle({
|
|
648
|
+
addonsDir: deps.addonsDir,
|
|
649
|
+
addonId,
|
|
650
|
+
bundle: buf,
|
|
651
|
+
extract,
|
|
652
|
+
logger: deploySwapLogger
|
|
653
|
+
}),
|
|
654
|
+
fetchBundle: (s) => fetchBundleFromHub(s),
|
|
655
|
+
// Evict every addon DECLARATION id this package contributes
|
|
656
|
+
// from `loadedAddons` so the follow-up `$agent.reload` actually
|
|
657
|
+
// re-instantiates it. `loadDeployedAddons` skips any addon
|
|
658
|
+
// still present in `loadedAddons`; without this eviction a
|
|
659
|
+
// redeploy would leave the agent pinned to the pre-update
|
|
660
|
+
// version. The `deploy` param `addonId` is the PACKAGE name
|
|
661
|
+
// (used only as the on-disk dir), whereas `loadedAddons` is
|
|
662
|
+
// keyed by the addon DECLARATION id — they differ for scoped
|
|
663
|
+
// packages, so we read the extracted manifest to bridge them.
|
|
664
|
+
onApplied: (addonDir) => {
|
|
665
|
+
for (const declId of readDeployedAddonIds(addonDir)) {
|
|
666
|
+
deps.loadedAddons.delete(declId);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
return resolveDeployAction(seam)({ addonId, source, bundle });
|
|
598
671
|
}
|
|
599
672
|
},
|
|
600
673
|
/**
|
|
@@ -802,7 +875,7 @@ function narrowParams(raw) {
|
|
|
802
875
|
deviceId: typeof deviceId === "number" ? deviceId : void 0
|
|
803
876
|
};
|
|
804
877
|
}
|
|
805
|
-
function createAgentCapDispatchService(agentNodeId, agentUdsRegistry, logger) {
|
|
878
|
+
function createAgentCapDispatchService(agentNodeId, agentUdsRegistry, inProcessLookup, logger) {
|
|
806
879
|
return {
|
|
807
880
|
name: import_system2.AGENT_CAP_FWD_SERVICE,
|
|
808
881
|
actions: {
|
|
@@ -812,11 +885,15 @@ function createAgentCapDispatchService(agentNodeId, agentUdsRegistry, logger) {
|
|
|
812
885
|
const { capName, method, args: args2, deviceId } = params;
|
|
813
886
|
const childId = params.childId !== void 0 ? params.childId : agentUdsRegistry.resolveChildId(capName, deviceId);
|
|
814
887
|
if (childId == null) {
|
|
888
|
+
const ref = inProcessLookup?.(capName) ?? null;
|
|
889
|
+
if (ref !== null) {
|
|
890
|
+
return ref.invoke(method, args2);
|
|
891
|
+
}
|
|
815
892
|
logger?.info(
|
|
816
|
-
`agent ${agentNodeId}: no
|
|
893
|
+
`agent ${agentNodeId}: no provider for cap "${capName}"${deviceId !== void 0 ? ` (deviceId ${deviceId})` : ""}`
|
|
817
894
|
);
|
|
818
895
|
throw new Error(
|
|
819
|
-
`agent ${agentNodeId} has no
|
|
896
|
+
`agent ${agentNodeId} has no provider for cap "${capName}"${deviceId !== void 0 ? ` (deviceId ${deviceId})` : ""}`
|
|
820
897
|
);
|
|
821
898
|
}
|
|
822
899
|
const input = {
|
|
@@ -833,9 +910,11 @@ function createAgentCapDispatchService(agentNodeId, agentUdsRegistry, logger) {
|
|
|
833
910
|
}
|
|
834
911
|
|
|
835
912
|
// src/register-agent-cap-dispatch.ts
|
|
836
|
-
function registerAgentCapDispatch(registrar, agentUdsRegistry, logger) {
|
|
913
|
+
function registerAgentCapDispatch(registrar, agentUdsRegistry, inProcessLookup, logger) {
|
|
837
914
|
if (agentUdsRegistry === void 0) return;
|
|
838
|
-
registrar.createService(
|
|
915
|
+
registrar.createService(
|
|
916
|
+
createAgentCapDispatchService(registrar.nodeID, agentUdsRegistry, inProcessLookup, logger)
|
|
917
|
+
);
|
|
839
918
|
}
|
|
840
919
|
|
|
841
920
|
// src/agent-bootstrap.ts
|
|
@@ -901,6 +980,27 @@ async function startAgent(configPath) {
|
|
|
901
980
|
};
|
|
902
981
|
const loadedAddons = /* @__PURE__ */ new Map();
|
|
903
982
|
const subtree = new import_system3.HubNodeRegistry();
|
|
983
|
+
let hubConnectionStateOverride = null;
|
|
984
|
+
function getHubConnectionState() {
|
|
985
|
+
if (hubConnectionStateOverride !== null) return hubConnectionStateOverride;
|
|
986
|
+
try {
|
|
987
|
+
const registry = broker.registry;
|
|
988
|
+
const nodes = registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
|
|
989
|
+
if (nodes.some((n) => n.id === "hub")) return "connected";
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
const configNow = readConfigFile2(config.configPath);
|
|
993
|
+
const discoveryMode = typeof configNow.hubAddress !== "string" || configNow.hubAddress.length === 0;
|
|
994
|
+
return discoveryMode ? "searching" : "disconnected";
|
|
995
|
+
}
|
|
996
|
+
function readConfigFile2(p) {
|
|
997
|
+
if (!fs6.existsSync(p)) return {};
|
|
998
|
+
try {
|
|
999
|
+
return JSON.parse(fs6.readFileSync(p, "utf-8"));
|
|
1000
|
+
} catch {
|
|
1001
|
+
return {};
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
904
1004
|
const registerAbortController = new AbortController();
|
|
905
1005
|
function buildAgentOwnManifest() {
|
|
906
1006
|
const addonCapMap = /* @__PURE__ */ new Map();
|
|
@@ -963,6 +1063,7 @@ async function startAgent(configPath) {
|
|
|
963
1063
|
consoleLogger.error(
|
|
964
1064
|
"hub registration rejected: cluster secret mismatch \u2014 correct CAMSTACK_CLUSTER_SECRET and restart the agent"
|
|
965
1065
|
);
|
|
1066
|
+
hubConnectionStateOverride = "secret-mismatch";
|
|
966
1067
|
return;
|
|
967
1068
|
}
|
|
968
1069
|
});
|
|
@@ -1033,7 +1134,10 @@ async function startAgent(configPath) {
|
|
|
1033
1134
|
subtree.registerNode(params);
|
|
1034
1135
|
triggerUpwardRegistration();
|
|
1035
1136
|
},
|
|
1036
|
-
expectedClusterSecretHash: config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0
|
|
1137
|
+
expectedClusterSecretHash: config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0,
|
|
1138
|
+
installFromNpm: async (pkg, version) => {
|
|
1139
|
+
await installer.install(pkg, version);
|
|
1140
|
+
}
|
|
1037
1141
|
});
|
|
1038
1142
|
broker.createService(agentServiceSchema);
|
|
1039
1143
|
const agentTcpPort = (0, import_system3.deriveAgentListenPort)(broker.nodeID);
|
|
@@ -1092,7 +1196,21 @@ async function startAgent(configPath) {
|
|
|
1092
1196
|
agentParentUdsPath
|
|
1093
1197
|
);
|
|
1094
1198
|
broker.createService(processServiceSchema);
|
|
1095
|
-
|
|
1199
|
+
const agentInProcessLookup = (capName) => {
|
|
1200
|
+
const provider = capabilityRegistry.getSingleton(capName);
|
|
1201
|
+
if (provider === null || provider === void 0) return null;
|
|
1202
|
+
return {
|
|
1203
|
+
invoke: (method, args2) => {
|
|
1204
|
+
const fn = provider[method];
|
|
1205
|
+
if (typeof fn !== "function") {
|
|
1206
|
+
return Promise.reject(new Error(`method "${method}" not found on cap "${capName}"`));
|
|
1207
|
+
}
|
|
1208
|
+
const result = fn.call(provider, args2);
|
|
1209
|
+
return Promise.resolve(result);
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
};
|
|
1213
|
+
registerAgentCapDispatch(broker, agentUdsRegistry, agentInProcessLookup, consoleLogger);
|
|
1096
1214
|
(0, import_system3.registerEventBusService)(broker);
|
|
1097
1215
|
broker.createService((0, import_system3.createHwAccelService)((0, import_system3.createKernelHwAccel)()));
|
|
1098
1216
|
await broker.start();
|
|
@@ -1119,7 +1237,8 @@ async function startAgent(configPath) {
|
|
|
1119
1237
|
nodeId: config.nodeId,
|
|
1120
1238
|
dataDir: config.dataDir,
|
|
1121
1239
|
configPath: config.configPath,
|
|
1122
|
-
onReconnect: reconnect
|
|
1240
|
+
onReconnect: reconnect,
|
|
1241
|
+
getHubConnectionState
|
|
1123
1242
|
});
|
|
1124
1243
|
await bootCoreAddons(broker, config, capabilityRegistry, loadedAddons, loggerFactory);
|
|
1125
1244
|
for (const dest of capabilityRegistry.getCollection("log-destination")) {
|
|
@@ -1141,6 +1260,9 @@ async function startAgent(configPath) {
|
|
|
1141
1260
|
broker.localBus.on("$node.connected", (data) => {
|
|
1142
1261
|
const node = data.node;
|
|
1143
1262
|
if (node?.id !== "hub") return;
|
|
1263
|
+
if (hubConnectionStateOverride === "secret-mismatch") {
|
|
1264
|
+
hubConnectionStateOverride = null;
|
|
1265
|
+
}
|
|
1144
1266
|
triggerUpwardRegistration();
|
|
1145
1267
|
if (hubReachableFired) return;
|
|
1146
1268
|
hubReachableFired = true;
|