@camstack/server 1.2.676 → 1.2.678
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
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.678] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Rebuilt against updated dependencies.
|
|
15
|
+
|
|
16
|
+
## [1.2.677] - 2026-10-28
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- An agent now keeps its own server closure current: unpaired it follows the npm latest, paired it follows the hub's version, checked at boot and every 6 hours (turn off with CAMSTACK_AGENT_SELF_UPDATE=off).
|
|
21
|
+
|
|
10
22
|
## [1.2.676] - 2026-10-28
|
|
11
23
|
|
|
12
24
|
### Fixed
|
|
@@ -50,9 +50,9 @@ function createAgentHubRegistration(opts) {
|
|
|
50
50
|
opts.onCertificateRefused?.(err);
|
|
51
51
|
},
|
|
52
52
|
})
|
|
53
|
-
.then(() => {
|
|
53
|
+
.then((ack) => {
|
|
54
54
|
opts.log(`[register-node] the hub acked this node's capabilities: ${manifest.addons.length} addon(s), ${bindings} binding(s) (${reason})`);
|
|
55
|
-
opts.onAcked?.();
|
|
55
|
+
opts.onAcked?.((0, system_1.hubServerVersionOf)(ack));
|
|
56
56
|
})
|
|
57
57
|
.catch(() => {
|
|
58
58
|
// Aborted: shutting down.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decideAgentSelfUpdate = decideAgentSelfUpdate;
|
|
4
|
+
exports.agentSelfUpdateEnabled = agentSelfUpdateEnabled;
|
|
5
|
+
exports.runAgentSelfUpdate = runAgentSelfUpdate;
|
|
6
|
+
/**
|
|
7
|
+
* An agent keeps its own `@camstack/server` closure current, with nobody driving it.
|
|
8
|
+
*
|
|
9
|
+
* Until now a closure reached an agent only when the HUB called `applyServerUpdate` on it. A
|
|
10
|
+
* freshly installed desktop agent boots the seed closure baked into the app and, being unpaired,
|
|
11
|
+
* had no hub to receive a fix from — not even the fix for its own unpaired boot (D857).
|
|
12
|
+
*
|
|
13
|
+
* The target version:
|
|
14
|
+
* - UNPAIRED: the npm `latest` of `@camstack/server` — there is no hub to follow;
|
|
15
|
+
* - PAIRED: the version the HUB runs (carried on the registration ack), never npm latest, so
|
|
16
|
+
* the cluster has no hub/agent skew.
|
|
17
|
+
*
|
|
18
|
+
* It reuses the one updater (`AgentUpdateService` → stage → `scheduleAgentRestart`, lifecycle =
|
|
19
|
+
* respawn, D5), never downgrades, and treats a failed read as "unknown", never as "up to date"
|
|
20
|
+
* (D49, D393): the reason is logged and nothing changes. The cadence is the update-check
|
|
21
|
+
* constant; there is no retry loop (D3).
|
|
22
|
+
*
|
|
23
|
+
* Off switch: `CAMSTACK_AGENT_SELF_UPDATE=off` (or `0`/`false`).
|
|
24
|
+
*/
|
|
25
|
+
const index_js_1 = require("../server-root/index.js");
|
|
26
|
+
function skip(reason, failedRead = false) {
|
|
27
|
+
return { action: 'skip', reason, failedRead };
|
|
28
|
+
}
|
|
29
|
+
function decideAgentSelfUpdate(input) {
|
|
30
|
+
if (input.running === null)
|
|
31
|
+
return skip('the running version is unknown', true);
|
|
32
|
+
let target;
|
|
33
|
+
let source;
|
|
34
|
+
if (input.paired) {
|
|
35
|
+
if (input.hubVersion === null) {
|
|
36
|
+
return skip('paired, but the hub version is unknown (no registration ack carrying it yet, or an older hub)', true);
|
|
37
|
+
}
|
|
38
|
+
target = input.hubVersion;
|
|
39
|
+
source = 'hub';
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
if (!input.npmLatest.ok) {
|
|
43
|
+
return skip(`the registry read failed: ${input.npmLatest.reason}`, true);
|
|
44
|
+
}
|
|
45
|
+
target = input.npmLatest.version;
|
|
46
|
+
source = 'npm-latest';
|
|
47
|
+
}
|
|
48
|
+
const order = (0, index_js_1.compareSemver)(target, input.running);
|
|
49
|
+
if (order === 0)
|
|
50
|
+
return skip(`already running ${target} (${source})`);
|
|
51
|
+
if (order < 0)
|
|
52
|
+
return skip(`target ${target} (${source}) is older than running ${input.running}: never a downgrade`);
|
|
53
|
+
if (input.pendingVersion === target)
|
|
54
|
+
return skip(`${target} is already staged, awaiting restart`);
|
|
55
|
+
return { action: 'stage', version: target, source };
|
|
56
|
+
}
|
|
57
|
+
function agentSelfUpdateEnabled(env) {
|
|
58
|
+
const raw = env['CAMSTACK_AGENT_SELF_UPDATE']?.trim().toLowerCase();
|
|
59
|
+
return !(raw === 'off' || raw === '0' || raw === 'false' || raw === 'no');
|
|
60
|
+
}
|
|
61
|
+
/** One self-update pass. Every decision is logged with the versions; it never throws. */
|
|
62
|
+
async function runAgentSelfUpdate(ports) {
|
|
63
|
+
const { log } = ports;
|
|
64
|
+
try {
|
|
65
|
+
if (!ports.enabled()) {
|
|
66
|
+
log.info('agent self-update: off (CAMSTACK_AGENT_SELF_UPDATE) — nothing checked');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const paired = ports.isPaired();
|
|
70
|
+
const running = ports.runningVersion();
|
|
71
|
+
const hubVersion = ports.getHubServerVersion();
|
|
72
|
+
let npmLatest = { ok: false, reason: 'not needed' };
|
|
73
|
+
let pendingVersion = null;
|
|
74
|
+
if (!paired) {
|
|
75
|
+
const check = await ports.checkLatest();
|
|
76
|
+
pendingVersion = check.pendingVersion;
|
|
77
|
+
npmLatest =
|
|
78
|
+
check.error === null && check.latestVersion !== null
|
|
79
|
+
? { ok: true, version: check.latestVersion }
|
|
80
|
+
: {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: check.error ?? 'the registry returned no version',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
log.info(`agent self-update: checked (${paired ? 'paired' : 'unpaired'}) running=${running ?? 'unknown'} ` +
|
|
86
|
+
`hub=${hubVersion ?? 'unknown'} npmLatest=${npmLatest.ok ? npmLatest.version : 'n/a'}`);
|
|
87
|
+
const decision = decideAgentSelfUpdate({
|
|
88
|
+
paired,
|
|
89
|
+
running,
|
|
90
|
+
npmLatest,
|
|
91
|
+
hubVersion,
|
|
92
|
+
pendingVersion,
|
|
93
|
+
});
|
|
94
|
+
if (decision.action === 'skip') {
|
|
95
|
+
const line = `agent self-update: skip — ${decision.reason}`;
|
|
96
|
+
if (decision.failedRead)
|
|
97
|
+
log.warn(line);
|
|
98
|
+
else
|
|
99
|
+
log.info(line);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
log.info(`agent self-update: staging ${decision.version} (${decision.source}) over ${running ?? 'unknown'}`);
|
|
103
|
+
const result = await ports.apply(decision.version);
|
|
104
|
+
if (result.accepted) {
|
|
105
|
+
log.info(`agent self-update: applied ${decision.version} (${result.outcome}) — restarting to swap the closure`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
log.warn(`agent self-update: ${decision.version} not applied (${result.outcome}): ${result.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
log.warn(`agent self-update: failed — ${err instanceof Error ? err.message : String(err)}; nothing changed`);
|
|
113
|
+
}
|
|
114
|
+
}
|
package/dist/agent/main.js
CHANGED
|
@@ -57,6 +57,8 @@ const agent_node_certificates_js_1 = require("./agent-node-certificates.js");
|
|
|
57
57
|
const agent_reconnect_js_1 = require("./agent-reconnect.js");
|
|
58
58
|
const serial_queue_js_1 = require("./serial-queue.js");
|
|
59
59
|
const agent_boot_mode_js_1 = require("./agent-boot-mode.js");
|
|
60
|
+
const agent_self_update_js_1 = require("./agent-self-update.js");
|
|
61
|
+
const update_check_scheduler_js_1 = require("../core/updates/update-check-scheduler.js");
|
|
60
62
|
const agent_infra_capabilities_js_1 = require("./agent-infra-capabilities.js");
|
|
61
63
|
const agent_service_js_1 = require("./agent-service.js");
|
|
62
64
|
const agent_hub_addon_update_js_1 = require("./agent-hub-addon-update.js");
|
|
@@ -306,6 +308,9 @@ async function startAgent(configPath) {
|
|
|
306
308
|
// AbortController for the upward hub registration retry loop — cancelled on
|
|
307
309
|
// agent shutdown so we don't leak the retry loop after stop().
|
|
308
310
|
const registerAbortController = new AbortController();
|
|
311
|
+
// The hub's `@camstack/server` version from the last `registerNode` ack (agent self-update).
|
|
312
|
+
let hubServerVersion = null;
|
|
313
|
+
let selfUpdateOnFirstAck = null;
|
|
309
314
|
/**
|
|
310
315
|
* Build the manifest for the agent's OWN in-process addons (those loaded by
|
|
311
316
|
* bootCoreAddons and loadDeployedAddons that ended up in `loadedAddons` WITH
|
|
@@ -395,10 +400,15 @@ async function startAgent(configPath) {
|
|
|
395
400
|
log: (msg) => console.log(`[Agent] ${msg}`),
|
|
396
401
|
// The hub acked the manifest — the agent reached its registered state.
|
|
397
402
|
// Run the single-copy GC sweep once (first ack only; no-op after).
|
|
398
|
-
onAcked: () => {
|
|
403
|
+
onAcked: (ackedHubServerVersion) => {
|
|
399
404
|
if (hubConnectionStateOverride === 'certificate-rejected')
|
|
400
405
|
hubConnectionStateOverride = null;
|
|
406
|
+
hubServerVersion = ackedHubServerVersion;
|
|
401
407
|
confirmAgentBootHealthy();
|
|
408
|
+
// Paired: the first ack is the first moment the hub's version is known.
|
|
409
|
+
const firstAck = selfUpdateOnFirstAck;
|
|
410
|
+
selfUpdateOnFirstAck = null;
|
|
411
|
+
firstAck?.();
|
|
402
412
|
},
|
|
403
413
|
onCertificateRefused: () => {
|
|
404
414
|
if (hubConnectionStateOverride === 'certificate-rejected')
|
|
@@ -902,6 +912,43 @@ async function startAgent(configPath) {
|
|
|
902
912
|
});
|
|
903
913
|
};
|
|
904
914
|
const addonsBooted = (0, agent_boot_mode_js_1.decideAgentBootMode)({ paired: (0, agent_pairing_store_js_1.isAgentPaired)(config.configPath) }) === 'full';
|
|
915
|
+
// ── Self-update (agent-self-update.ts): keep this closure current with nobody driving it.
|
|
916
|
+
// Unpaired -> npm latest, at boot; paired -> the hub's version, at the first registration ack.
|
|
917
|
+
// Then on the update-check cadence (a periodic check, no retry loop). Lifecycle = respawn (D5).
|
|
918
|
+
const selfUpdatePorts = {
|
|
919
|
+
enabled: () => (0, agent_self_update_js_1.agentSelfUpdateEnabled)(process.env),
|
|
920
|
+
isPaired: () => (0, agent_pairing_store_js_1.isAgentPaired)(config.configPath),
|
|
921
|
+
getHubServerVersion: () => hubServerVersion,
|
|
922
|
+
runningVersion: () => agentUpdateService.getRunningRootPackage().version,
|
|
923
|
+
checkLatest: async () => {
|
|
924
|
+
const check = await agentUpdateService.checkServerUpdate();
|
|
925
|
+
return {
|
|
926
|
+
latestVersion: check.latestVersion,
|
|
927
|
+
error: check.error,
|
|
928
|
+
pendingVersion: check.pendingVersion ?? null,
|
|
929
|
+
};
|
|
930
|
+
},
|
|
931
|
+
apply: async (version) => {
|
|
932
|
+
const result = await agentUpdateService.applyServerUpdate({ version });
|
|
933
|
+
return {
|
|
934
|
+
accepted: result.accepted,
|
|
935
|
+
outcome: result.outcome ?? 'unknown',
|
|
936
|
+
message: result.message,
|
|
937
|
+
};
|
|
938
|
+
},
|
|
939
|
+
log: {
|
|
940
|
+
info: (message) => consoleLogger.info(message, { tags: { nodeId: config.nodeId } }),
|
|
941
|
+
warn: (message) => consoleLogger.warn(message, { tags: { nodeId: config.nodeId } }),
|
|
942
|
+
},
|
|
943
|
+
};
|
|
944
|
+
if ((0, agent_pairing_store_js_1.isAgentPaired)(config.configPath)) {
|
|
945
|
+
selfUpdateOnFirstAck = () => void (0, agent_self_update_js_1.runAgentSelfUpdate)(selfUpdatePorts);
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
void (0, agent_self_update_js_1.runAgentSelfUpdate)(selfUpdatePorts);
|
|
949
|
+
}
|
|
950
|
+
const selfUpdateTimer = setInterval(() => void (0, agent_self_update_js_1.runAgentSelfUpdate)(selfUpdatePorts), update_check_scheduler_js_1.DEFAULT_UPDATE_CHECK_INTERVAL_SECONDS * 1000);
|
|
951
|
+
selfUpdateTimer.unref();
|
|
905
952
|
const localPairing = (0, agent_pairing_local_js_1.createLocalPairingAnnouncer)(config.dataDir, consoleLogger);
|
|
906
953
|
const pairing = new system_1.AgentPairing({
|
|
907
954
|
nodeId: config.nodeId,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readServerVersionFrom = readServerVersionFrom;
|
|
37
|
+
exports.readHubServerVersion = readHubServerVersion;
|
|
38
|
+
/**
|
|
39
|
+
* The `@camstack/server` version this hub runs, carried on every `$hub.registerNode` ack so an
|
|
40
|
+
* agent can align its own closure to it (agent self-update). Read once from the running
|
|
41
|
+
* package.json (`dist/core/moleculer/` -> `../../../package.json`); null when it cannot be read —
|
|
42
|
+
* the ack then simply omits it and the agent says "hub version unknown" instead of guessing.
|
|
43
|
+
*/
|
|
44
|
+
const fs = __importStar(require("node:fs"));
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
46
|
+
const index_js_1 = require("../../server-root/index.js");
|
|
47
|
+
function readServerVersionFrom(packageJsonPath) {
|
|
48
|
+
try {
|
|
49
|
+
const raw = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
50
|
+
if (typeof raw !== 'object' || raw === null)
|
|
51
|
+
return null;
|
|
52
|
+
const name = Reflect.get(raw, 'name');
|
|
53
|
+
const version = Reflect.get(raw, 'version');
|
|
54
|
+
if (name !== index_js_1.SERVER_ROOT_PACKAGE_NAME)
|
|
55
|
+
return null;
|
|
56
|
+
return typeof version === 'string' && version.length > 0 ? version : null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let cached;
|
|
63
|
+
function readHubServerVersion() {
|
|
64
|
+
if (cached === undefined) {
|
|
65
|
+
cached = readServerVersionFrom(path.resolve(__dirname, '..', '..', '..', 'package.json'));
|
|
66
|
+
}
|
|
67
|
+
return cached;
|
|
68
|
+
}
|
|
@@ -11,6 +11,7 @@ const core_cap_bridge_js_1 = require("../../api/trpc/core-cap-bridge.js");
|
|
|
11
11
|
const agent_readiness_pull_js_1 = require("./agent-readiness-pull.js");
|
|
12
12
|
const agent_registration_request_js_1 = require("./agent-registration-request.js");
|
|
13
13
|
const hub_mesh_guard_js_1 = require("./hub-mesh-guard.js");
|
|
14
|
+
const hub_server_version_js_1 = require("./hub-server-version.js");
|
|
14
15
|
const cap_call_fn_js_1 = require("./cap-call-fn.js");
|
|
15
16
|
const cap_route_authority_js_1 = require("./cap-route-authority.js");
|
|
16
17
|
class MoleculerService {
|
|
@@ -270,6 +271,8 @@ class MoleculerService {
|
|
|
270
271
|
onUnregisterNode: (nodeId) => {
|
|
271
272
|
this.removeNodeFromRegistry(nodeId);
|
|
272
273
|
},
|
|
274
|
+
// The ack carries the hub's closure version so an agent aligns to it (agent self-update).
|
|
275
|
+
getServerVersion: () => (0, hub_server_version_js_1.readHubServerVersion)(),
|
|
273
276
|
// D857: a node registers only itself, and only with a certificate identity.
|
|
274
277
|
meshIdentity: this.meshGuard.gate,
|
|
275
278
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.678",
|
|
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.
|
|
52
|
+
"@camstack/addon-admin-ui": "1.2.507",
|
|
53
53
|
"@camstack/addon-agent-ui": "1.2.286",
|
|
54
54
|
"@camstack/addon-auth": "1.2.170",
|
|
55
55
|
"@camstack/addon-benchmark": "1.2.287",
|
|
56
56
|
"@camstack/addon-decoder-nodeav": "1.2.144",
|
|
57
57
|
"@camstack/addon-notifiers": "1.2.623",
|
|
58
|
-
"@camstack/addon-pipeline": "1.2.
|
|
58
|
+
"@camstack/addon-pipeline": "1.2.633",
|
|
59
59
|
"@camstack/addon-pipeline-orchestrator": "1.2.635",
|
|
60
60
|
"@camstack/addon-post-analysis": "1.2.649",
|
|
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.509",
|
|
64
|
+
"@camstack/types": "1.2.437",
|
|
65
65
|
"@camstack/ui-library": "1.2.345",
|
|
66
66
|
"@fastify/compress": "^9.0.0",
|
|
67
67
|
"@fastify/cookie": "^11.0.2",
|