@camstack/server 1.2.680 → 1.2.682
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/CHANGELOG.md +13 -0
- package/dist/agent/agent-config.js +21 -3
- package/dist/agent/agent-http.js +7 -6
- package/dist/agent/agent-hub-addon-update.js +8 -2
- package/dist/agent/agent-update-routes.js +1 -1
- package/dist/agent/main.js +1 -0
- package/dist/api/core/agent-pairing.router.js +18 -13
- package/dist/core/agent/agent-addon-backfill.js +49 -14
- package/dist/core/agent/agent-registry.service.js +22 -2
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.682] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- A freshly paired or re-registered agent node now receives every any-node addon installed on the hub, at the hub version
|
|
15
|
+
- A fresh agent is named after its short host name and architecture, and the hub offers agents its internal address instead of the public endpoint
|
|
16
|
+
|
|
17
|
+
## [1.2.681] - 2026-10-28
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Rebuilt against updated dependencies.
|
|
22
|
+
|
|
10
23
|
## [1.2.680] - 2026-10-28
|
|
11
24
|
|
|
12
25
|
### Fixed
|
|
@@ -33,7 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.defaultAgentName = defaultAgentName;
|
|
36
37
|
exports.loadAgentConfig = loadAgentConfig;
|
|
38
|
+
exports.seededAgentName = seededAgentName;
|
|
37
39
|
const fs = __importStar(require("node:fs"));
|
|
38
40
|
const path = __importStar(require("node:path"));
|
|
39
41
|
const crypto = __importStar(require("node:crypto"));
|
|
@@ -95,15 +97,31 @@ function parseMeshTls(value) {
|
|
|
95
97
|
return undefined;
|
|
96
98
|
return { certPem, keyPem, caCertPem };
|
|
97
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* The name an agent without a chosen one carries: the host name up to its first dot (so
|
|
102
|
+
* `.local` / `.localdomain` and a domain suffix drop out), then the architecture.
|
|
103
|
+
*/
|
|
104
|
+
function defaultAgentName(hostname, arch) {
|
|
105
|
+
const short = hostname.trim().split('.')[0] ?? '';
|
|
106
|
+
return `${short.length > 0 ? short : 'agent'}-${arch}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The name before anyone renamed the agent: `CAMSTACK_AGENT_NAME` when set, else the default.
|
|
110
|
+
* Never the node id - that is an identity, not a label.
|
|
111
|
+
*/
|
|
112
|
+
function seededAgentName() {
|
|
113
|
+
const fromEnv = process.env.CAMSTACK_AGENT_NAME;
|
|
114
|
+
return fromEnv !== undefined && fromEnv.trim().length > 0
|
|
115
|
+
? fromEnv.trim()
|
|
116
|
+
: defaultAgentName(os.hostname(), os.arch());
|
|
117
|
+
}
|
|
98
118
|
function loadAgentConfig(configPath, dataDirOverride) {
|
|
99
119
|
const filePath = configPath ?? 'agent.json';
|
|
100
120
|
const dataDir = dataDirOverride ?? process.env.CAMSTACK_DATA ?? './camstack-data';
|
|
101
121
|
const resolvedDataDir = path.resolve(dataDir);
|
|
102
122
|
// Name: config file takes priority over env (allows UI rename to stick).
|
|
103
123
|
// Env is the initial seed, config file is the persisted override.
|
|
104
|
-
const envName =
|
|
105
|
-
process.env.CAMSTACK_AGENT_NAME ??
|
|
106
|
-
`${os.hostname()}-${os.arch()}`;
|
|
124
|
+
const envName = seededAgentName();
|
|
107
125
|
// Environment variable for the hub connection (optional — agent starts
|
|
108
126
|
// without). Pairing material lives only in agent.json.
|
|
109
127
|
const envHubAddress = process.env.CAMSTACK_HUB_ADDRESS || undefined;
|
package/dist/agent/agent-http.js
CHANGED
|
@@ -64,6 +64,7 @@ const agent_http_auth_js_1 = require("./agent-http-auth.js");
|
|
|
64
64
|
const agent_pairing_routes_js_1 = require("./agent-pairing-routes.js");
|
|
65
65
|
const agent_pairing_store_js_1 = require("./agent-pairing-store.js");
|
|
66
66
|
const node_1 = require("@camstack/types/node");
|
|
67
|
+
const agent_config_js_1 = require("./agent-config.js");
|
|
67
68
|
const agent_update_routes_js_1 = require("./agent-update-routes.js");
|
|
68
69
|
const derive_hub_url_js_1 = require("./derive-hub-url.js");
|
|
69
70
|
Object.defineProperty(exports, "pickIpv4", { enumerable: true, get: function () { return derive_hub_url_js_1.pickIpv4; } });
|
|
@@ -259,10 +260,10 @@ function deriveHubConnectionState(nodes, hubAddressConfigured, paired) {
|
|
|
259
260
|
* Read the current effective config from the persisted file.
|
|
260
261
|
* This is the single source of truth -- always reflects what the UI wrote.
|
|
261
262
|
*/
|
|
262
|
-
function getEffectiveConfig(configPath
|
|
263
|
+
function getEffectiveConfig(configPath) {
|
|
263
264
|
const raw = readConfigFile(configPath);
|
|
264
265
|
return {
|
|
265
|
-
name: typeof raw.name === 'string' ? raw.name :
|
|
266
|
+
name: typeof raw.name === 'string' && raw.name.length > 0 ? raw.name : (0, agent_config_js_1.seededAgentName)(),
|
|
266
267
|
hubAddress: typeof raw.hubAddress === 'string' ? raw.hubAddress : null,
|
|
267
268
|
paired: (0, agent_pairing_store_js_1.isAgentPaired)(configPath),
|
|
268
269
|
hasMeshTls: raw.meshTls !== null && typeof raw.meshTls === 'object',
|
|
@@ -387,7 +388,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
387
388
|
// -- Agent status (enriched with connection state) ------------------
|
|
388
389
|
app.get('/api/agent/status', async () => {
|
|
389
390
|
const broker = getBroker();
|
|
390
|
-
const eff = getEffectiveConfig(config.configPath
|
|
391
|
+
const eff = getEffectiveConfig(config.configPath);
|
|
391
392
|
const nodes = getRegistryNodes(broker);
|
|
392
393
|
const hubConnected = nodes.some((n) => n.id === 'hub');
|
|
393
394
|
const hubConnectionState = config.getHubConnectionState
|
|
@@ -458,7 +459,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
458
459
|
});
|
|
459
460
|
// -- Unlock info (public; Continue-with-hub needs hub origin while locked) --
|
|
460
461
|
app.get('/api/agent/unlock-info', async () => {
|
|
461
|
-
const eff = getEffectiveConfig(config.configPath
|
|
462
|
+
const eff = getEffectiveConfig(config.configPath);
|
|
462
463
|
const nodes = getRegistryNodes(getBroker());
|
|
463
464
|
const resolvedHubAddress = resolveHubEndpoint(nodes);
|
|
464
465
|
const hubHttpsOrigin = (eff.hubAddress !== null ? (0, derive_hub_url_js_1.deriveHubUrlFromHubAddress)(eff.hubAddress) : undefined) ??
|
|
@@ -539,7 +540,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
539
540
|
// -- Config read (always from file -- single source of truth) -------
|
|
540
541
|
app.get('/api/agent/config', async () => {
|
|
541
542
|
const persisted = readConfigFile(config.configPath);
|
|
542
|
-
const eff = getEffectiveConfig(config.configPath
|
|
543
|
+
const eff = getEffectiveConfig(config.configPath);
|
|
543
544
|
return {
|
|
544
545
|
nodeId: config.nodeId,
|
|
545
546
|
name: eff.name,
|
|
@@ -608,7 +609,7 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
608
609
|
// status poll.
|
|
609
610
|
const hubsFinder = config.hubsFinder ??
|
|
610
611
|
(0, hubs_found_js_1.createHubsFinder)({
|
|
611
|
-
getConfiguredHubAddress: () => getEffectiveConfig(config.configPath
|
|
612
|
+
getConfiguredHubAddress: () => getEffectiveConfig(config.configPath).hubAddress,
|
|
612
613
|
log: (message) => console.warn(`[Agent] ${message}`),
|
|
613
614
|
});
|
|
614
615
|
app.get('/api/agent/hubs-found', async () => hubsFinder.snapshot());
|
|
@@ -79,12 +79,16 @@ function failureOf(result) {
|
|
|
79
79
|
function createAgentHubAddonControls(deps) {
|
|
80
80
|
const states = new Map();
|
|
81
81
|
const getHubVersions = async () => {
|
|
82
|
+
if (deps.isPaired !== undefined && !deps.isPaired())
|
|
83
|
+
return { ok: false, reason: 'unpaired' };
|
|
82
84
|
try {
|
|
83
85
|
const list = await deps.callHub(exports.HUB_ADDON_LIST_ACTION, undefined, HUB_LIST_TIMEOUT_MS);
|
|
84
86
|
return { ok: true, versions: hubVersionsFromList(list) };
|
|
85
87
|
}
|
|
86
88
|
catch (err) {
|
|
87
|
-
|
|
89
|
+
// The transport's own words (a service name) are for the log, not the operator.
|
|
90
|
+
deps.onProblem?.(`the hub did not answer addons.list: ${errorMessage(err)}`);
|
|
91
|
+
return { ok: false, reason: 'unanswered' };
|
|
88
92
|
}
|
|
89
93
|
};
|
|
90
94
|
const run = async (packageName, version) => {
|
|
@@ -118,7 +122,9 @@ function createAgentHubAddonControls(deps) {
|
|
|
118
122
|
if (!hub.ok) {
|
|
119
123
|
return {
|
|
120
124
|
started: false,
|
|
121
|
-
message:
|
|
125
|
+
message: hub.reason === 'unpaired'
|
|
126
|
+
? 'This agent is not paired with a hub, so there is no version to bring it to.'
|
|
127
|
+
: 'The hub did not answer, so the version this node should run is unknown.',
|
|
122
128
|
};
|
|
123
129
|
}
|
|
124
130
|
const hubVersion = hub.versions.get(packageName);
|
|
@@ -74,7 +74,7 @@ async function withHubVersions(report, hub) {
|
|
|
74
74
|
const progress = hub?.progress();
|
|
75
75
|
return {
|
|
76
76
|
...report,
|
|
77
|
-
|
|
77
|
+
hubVersions: versions === null || versions.ok ? 'known' : versions.reason,
|
|
78
78
|
entries: report.entries.map((entry) => {
|
|
79
79
|
const hubVersion = versions?.ok === true ? (versions.versions.get(entry.packageName) ?? null) : null;
|
|
80
80
|
return {
|
package/dist/agent/main.js
CHANGED
|
@@ -879,6 +879,7 @@ async function startAgent(configPath) {
|
|
|
879
879
|
addons: {
|
|
880
880
|
hub: (0, agent_hub_addon_update_js_1.createAgentHubAddonControls)({
|
|
881
881
|
nodeId: config.nodeId,
|
|
882
|
+
isPaired: () => (0, agent_pairing_store_js_1.isAgentPaired)(config.configPath),
|
|
882
883
|
// The mesh channel the agent already holds; the hub names this caller
|
|
883
884
|
// `node:<id>` and answers per its remote privilege grant.
|
|
884
885
|
callHub: (action, params, timeoutMs) => broker.call(action, params, { nodeID: 'hub', timeout: timeoutMs }),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveAgentHubAddresses = resolveAgentHubAddresses;
|
|
3
4
|
exports.createAgentPairingRouter = createAgentPairingRouter;
|
|
4
5
|
const node_crypto_1 = require("node:crypto");
|
|
5
6
|
/**
|
|
@@ -68,6 +69,21 @@ const UnpairResultSchema = zod_1.z.object({
|
|
|
68
69
|
disconnected: zod_1.z.number().int(),
|
|
69
70
|
next: zod_1.z.string(),
|
|
70
71
|
});
|
|
72
|
+
/**
|
|
73
|
+
* What an agent is offered: the hub's INTERNAL role for the API URL and the mesh
|
|
74
|
+
* address alike. The public endpoint is for people outside the LAN; an agent on the
|
|
75
|
+
* LAN is shown, and keeps, the address it actually dials.
|
|
76
|
+
*/
|
|
77
|
+
function resolveAgentHubAddresses(book) {
|
|
78
|
+
const internal = book.roles.internal.resolved;
|
|
79
|
+
if (internal === null) {
|
|
80
|
+
throw new Error('The hub has no LAN address to give an agent yet - set one on System -> Network');
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
hubUrl: internal,
|
|
84
|
+
hubMeshAddress: `${new URL(internal).hostname}:${system_1.DEFAULT_HUB_TLS_TCP_PORT}`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
71
87
|
function createAgentPairingRouter(deps) {
|
|
72
88
|
const transport = deps.transport ?? (0, agent_pairing_http_js_1.createHttpAgentPairingTransport)();
|
|
73
89
|
const meshCa = deps.meshCa ??
|
|
@@ -89,20 +105,9 @@ function createAgentPairingRouter(deps) {
|
|
|
89
105
|
}
|
|
90
106
|
return provider;
|
|
91
107
|
};
|
|
92
|
-
/** The address an agent dials
|
|
108
|
+
/** The address an agent dials and is shown: the hub's `internal` role. */
|
|
93
109
|
const hubAddresses = deps.hubAddresses ??
|
|
94
|
-
(async () => {
|
|
95
|
-
const book = await localNetwork().getAddresses({ scheme: 'https' });
|
|
96
|
-
const internal = book.roles.internal.resolved;
|
|
97
|
-
const hubUrl = book.roles.publicEndpoint.resolved ?? internal;
|
|
98
|
-
if (internal === null || hubUrl === null) {
|
|
99
|
-
throw new Error('The hub has no LAN address to give an agent yet - set one on System -> Network');
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
hubUrl,
|
|
103
|
-
hubMeshAddress: `${new URL(internal).hostname}:${system_1.DEFAULT_HUB_TLS_TCP_PORT}`,
|
|
104
|
-
};
|
|
105
|
-
});
|
|
110
|
+
(async () => resolveAgentHubAddresses(await localNetwork().getAddresses({ scheme: 'https' })));
|
|
106
111
|
const pairing = new system_1.HubPairing({
|
|
107
112
|
transport,
|
|
108
113
|
ca: meshCa,
|
|
@@ -53,22 +53,10 @@
|
|
|
53
53
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
54
|
exports.BackfillAttemptTracker = exports.MAX_BACKFILL_ATTEMPTS = void 0;
|
|
55
55
|
exports.triggerMayDeliver = triggerMayDeliver;
|
|
56
|
+
exports.freshNodeSkipReason = freshNodeSkipReason;
|
|
56
57
|
exports.computeBackfillPlan = computeBackfillPlan;
|
|
57
58
|
exports.confirmBackfillPlan = confirmBackfillPlan;
|
|
58
59
|
exports.executeBackfill = executeBackfill;
|
|
59
|
-
/**
|
|
60
|
-
* Why a reconcile is running. Named on every back-fill decision, and an
|
|
61
|
-
* allowlist: a future caller has to CHOOSE a trigger, and a trigger that must
|
|
62
|
-
* not deliver bytes is refused here rather than in a comment.
|
|
63
|
-
*
|
|
64
|
-
* `node-registration` covers both a first registration and a rejoin — the
|
|
65
|
-
* distinction is not the discriminator it looks like. The case the back-fill
|
|
66
|
-
* exists for (a node that lost `<dataDir>/addons` and came back with 4 of 14
|
|
67
|
-
* packages) presents as a rejoin, so excluding rejoins would delete the only
|
|
68
|
-
* legitimate purpose. The transient-roster-gap case is excluded by the delivery
|
|
69
|
-
* ledger and the confirmation read instead, which discriminate on FACTS rather
|
|
70
|
-
* than on which callback fired.
|
|
71
|
-
*/
|
|
72
60
|
const system_1 = require("@camstack/system");
|
|
73
61
|
/** Triggers permitted to install packages onto a node. */
|
|
74
62
|
const DELIVERING_TRIGGERS = new Set([
|
|
@@ -80,11 +68,26 @@ const DELIVERING_TRIGGERS = new Set([
|
|
|
80
68
|
function triggerMayDeliver(trigger) {
|
|
81
69
|
return DELIVERING_TRIGGERS.has(trigger);
|
|
82
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Why a hub package may not go to a fresh node, or `null` when it may.
|
|
73
|
+
* A package with a hub-only addon is refused whole: the stale sweep would
|
|
74
|
+
* undeploy that addon and with it the shared bundle directory.
|
|
75
|
+
*/
|
|
76
|
+
function freshNodeSkipReason(placements) {
|
|
77
|
+
if (placements.includes('hub-only'))
|
|
78
|
+
return 'hub-only placement';
|
|
79
|
+
if (!placements.includes('any-node'))
|
|
80
|
+
return 'placement does not allow any node';
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
83
|
/**
|
|
84
84
|
* Decide what a node is missing relative to its own last-declared roster.
|
|
85
85
|
* Pure — no I/O, no clock, no randomness.
|
|
86
86
|
*/
|
|
87
87
|
function computeBackfillPlan(input) {
|
|
88
|
+
if (input.freshNode !== undefined && input.recorded.length === 0) {
|
|
89
|
+
return computeFreshNodePlan(input, input.freshNode);
|
|
90
|
+
}
|
|
88
91
|
const liveByName = new Map(input.live.map((p) => [p.name, p.version]));
|
|
89
92
|
const targets = [];
|
|
90
93
|
const retired = [];
|
|
@@ -113,7 +116,39 @@ function computeBackfillPlan(input) {
|
|
|
113
116
|
}
|
|
114
117
|
targets.push({ name: entry.name, version: hubVersion, versionSource: 'hub-installed' });
|
|
115
118
|
}
|
|
116
|
-
return { targets, retired, notOnHub, liveCount: liveByName.size };
|
|
119
|
+
return { targets, retired, notOnHub, liveCount: liveByName.size, skipped: [] };
|
|
120
|
+
}
|
|
121
|
+
function computeFreshNodePlan(input, fresh) {
|
|
122
|
+
const liveByName = new Map(input.live.map((p) => [p.name, p.version]));
|
|
123
|
+
const targets = [];
|
|
124
|
+
const retired = [];
|
|
125
|
+
const skipped = [];
|
|
126
|
+
for (const [name, placements] of fresh.hubPackagePlacements) {
|
|
127
|
+
if (input.bootstrap.has(name))
|
|
128
|
+
continue;
|
|
129
|
+
if (input.undeployed.has(name))
|
|
130
|
+
continue;
|
|
131
|
+
const reason = freshNodeSkipReason(placements);
|
|
132
|
+
if (reason !== null) {
|
|
133
|
+
skipped.push({ name, reason });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const hubVersion = input.hubVersions.get(name);
|
|
137
|
+
if (hubVersion === undefined) {
|
|
138
|
+
skipped.push({ name, reason: 'hub version unknown' });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const present = liveByName.has(name);
|
|
142
|
+
const decision = (0, system_1.decideAddonVersionMove)(present ? (liveByName.get(name) ?? null) : null, hubVersion, system_1.ADDON_VERSION_REASON_HUB);
|
|
143
|
+
if (!decision.move)
|
|
144
|
+
continue;
|
|
145
|
+
if (input.retired.has(name)) {
|
|
146
|
+
retired.push(name);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
targets.push({ name, version: hubVersion, versionSource: 'hub-installed' });
|
|
150
|
+
}
|
|
151
|
+
return { targets, retired, notOnHub: [], liveCount: liveByName.size, skipped };
|
|
117
152
|
}
|
|
118
153
|
function confirmBackfillPlan(plan, secondRead) {
|
|
119
154
|
const seen = new Map(secondRead.map((p) => [p.name, p.version]));
|
|
@@ -675,6 +675,7 @@ class AgentRegistryService {
|
|
|
675
675
|
const hubPlacements = new Map();
|
|
676
676
|
const hubPackages = new Set();
|
|
677
677
|
const hubVersions = new Map();
|
|
678
|
+
const hubPackagePlacements = new Map();
|
|
678
679
|
for (const row of this.addonRegistry.listAddons()) {
|
|
679
680
|
const declId = row.manifest.id;
|
|
680
681
|
if (typeof declId !== 'string')
|
|
@@ -684,6 +685,9 @@ class AgentRegistryService {
|
|
|
684
685
|
const packageName = row.manifest.packageName;
|
|
685
686
|
if (typeof packageName === 'string' && packageName.length > 0) {
|
|
686
687
|
hubPackages.add(packageName);
|
|
688
|
+
const siblings = hubPackagePlacements.get(packageName) ?? [];
|
|
689
|
+
siblings.push((0, types_1.resolveAddonPlacement)(decl));
|
|
690
|
+
hubPackagePlacements.set(packageName, siblings);
|
|
687
691
|
const packageVersion = row.manifest.packageVersion;
|
|
688
692
|
if (typeof packageVersion === 'string' && packageVersion.length > 0) {
|
|
689
693
|
hubVersions.set(packageName, packageVersion);
|
|
@@ -754,7 +758,7 @@ class AgentRegistryService {
|
|
|
754
758
|
console.error(`[agent-registry] Reconcile ${agentId}: failed to undeploy "${addon.id}":`, err instanceof Error ? err.message : String(err));
|
|
755
759
|
}
|
|
756
760
|
}
|
|
757
|
-
await this.backfillAgentAddons(agentId, agentAddons, undeployedPackages, hubVersions, trigger);
|
|
761
|
+
await this.backfillAgentAddons(agentId, agentAddons, undeployedPackages, hubVersions, hubPackagePlacements, trigger);
|
|
758
762
|
}
|
|
759
763
|
catch (err) {
|
|
760
764
|
console.error(`[agent-registry] Reconcile failed for agent ${agentId}:`, err instanceof Error ? err.message : String(err));
|
|
@@ -775,10 +779,13 @@ class AgentRegistryService {
|
|
|
775
779
|
* Every leg is idempotent: run twice with no change in between and step 3
|
|
776
780
|
* finds nothing to do.
|
|
777
781
|
*/
|
|
778
|
-
async backfillAgentAddons(agentId, agentAddons, undeployedPackages, hubVersions, trigger) {
|
|
782
|
+
async backfillAgentAddons(agentId, agentAddons, undeployedPackages, hubVersions, hubPackagePlacements, trigger) {
|
|
779
783
|
const store = this.historyStore;
|
|
780
784
|
if (!store)
|
|
781
785
|
return;
|
|
786
|
+
// Read BEFORE recording: a fresh node is one that never declared a roster
|
|
787
|
+
// (D962), and the record below would make it look like it had one.
|
|
788
|
+
const priorRoster = await store.getPackages(agentId);
|
|
782
789
|
const live = rosterFromAddons(agentAddons);
|
|
783
790
|
await store.recordPackages(agentId, live);
|
|
784
791
|
if (undeployedPackages.size > 0) {
|
|
@@ -795,7 +802,20 @@ class AgentRegistryService {
|
|
|
795
802
|
undeployed: undeployedPackages,
|
|
796
803
|
retired: this.backfillTracker.retiredFor(agentId),
|
|
797
804
|
hubVersions,
|
|
805
|
+
freshNode: priorRoster.length === 0 ? { hubPackagePlacements } : undefined,
|
|
798
806
|
});
|
|
807
|
+
if (priorRoster.length === 0) {
|
|
808
|
+
this.logger?.info('back-fill: fresh node (no prior declared roster), plan from the hub any-node set', {
|
|
809
|
+
tags: { nodeId: agentId },
|
|
810
|
+
meta: { planned: planned.targets.map((t) => `${t.name}@${t.version}`) },
|
|
811
|
+
});
|
|
812
|
+
for (const skip of planned.skipped) {
|
|
813
|
+
this.logger?.info('back-fill: fresh node not given a hub package', {
|
|
814
|
+
tags: { nodeId: agentId },
|
|
815
|
+
meta: { package: skip.name, reason: skip.reason },
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
799
819
|
// Never a delivery, and said so: an addon the hub no longer installs stays
|
|
800
820
|
// off the node even though the node once declared it (the stale sweep above
|
|
801
821
|
// removes a copy that is still there).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.682",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -49,19 +49,19 @@
|
|
|
49
49
|
]
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
53
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
52
|
+
"@camstack/addon-admin-ui": "1.2.510",
|
|
53
|
+
"@camstack/addon-agent-ui": "1.2.289",
|
|
54
54
|
"@camstack/addon-auth": "1.2.170",
|
|
55
55
|
"@camstack/addon-benchmark": "1.2.288",
|
|
56
56
|
"@camstack/addon-decoder-nodeav": "1.2.144",
|
|
57
57
|
"@camstack/addon-notifiers": "1.2.623",
|
|
58
58
|
"@camstack/addon-pipeline": "1.2.633",
|
|
59
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
59
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.637",
|
|
60
60
|
"@camstack/addon-post-analysis": "1.2.650",
|
|
61
61
|
"@camstack/develop": "0.1.38",
|
|
62
62
|
"@camstack/sdk": "1.2.154",
|
|
63
|
-
"@camstack/system": "1.2.
|
|
64
|
-
"@camstack/types": "1.2.
|
|
63
|
+
"@camstack/system": "1.2.513",
|
|
64
|
+
"@camstack/types": "1.2.439",
|
|
65
65
|
"@camstack/ui-library": "1.2.346",
|
|
66
66
|
"@fastify/compress": "^9.0.0",
|
|
67
67
|
"@fastify/cookie": "^11.0.2",
|