@camstack/server 1.2.688 → 1.2.690
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 +23 -0
- package/dist/agent/agent-heap-watch.js +38 -0
- package/dist/agent/agent-service.js +8 -3
- package/dist/agent/main.js +7 -1
- package/dist/api/core/cap-providers.js +5 -3
- package/dist/api/core/cluster-nodes.router.js +3 -3
- package/dist/api/core/server-management.provider.js +14 -1
- package/dist/api/trpc/generated-cap-mounts.js +4 -1
- package/dist/api/trpc/generated-cap-routers.js +1270 -1134
- package/dist/api/trpc/trpc.router.js +1 -1
- package/dist/core/agent/agent-registry.service.js +30 -22
- package/dist/core/moleculer/moleculer.service.js +8 -0
- package/dist/launch-args.js +255 -0
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.690] - 2026-10-29
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- The agent reports its memory, reclaims stranded pages and gets a heap ceiling sized to the host; a node's launch parameters (heap ceiling, allocator) come from the server and can be overridden per node from the hub
|
|
15
|
+
|
|
16
|
+
## [1.2.689] - 2026-10-29
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- A node's free memory comes from the metrics provider instead of os.freemem(), which undercounts on macOS
|
|
21
|
+
- device-export gains optional methods to list, expose and unexpose actions and notification targets beside devices.
|
|
22
|
+
- Forget node now works on an online node too: its certificates are revoked, its connection is cut and calls pinned to it fail at once.
|
|
23
|
+
- Generated routers for the pet-fountain capability
|
|
24
|
+
- Agents report their own process memory in the topology (the core process showed 0), and an unreadable memory shows as unknown
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- The agent now reports its own [mem] agent-main heartbeat and reclaims stranded memory, as the hub does
|
|
29
|
+
- The hub tells its addon runners which telemetry categories anyone consumes, so unused ones stop crossing the process boundary
|
|
30
|
+
- Node launch parameters (heap ceiling, allocator env) are decided by a closure module, with a per-node override and a source line in the boot log
|
|
31
|
+
- A node's heap-ceiling override can be read and set through server-management (applies at the next restart)
|
|
32
|
+
|
|
10
33
|
## [1.2.688] - 2026-10-29
|
|
11
34
|
|
|
12
35
|
### Changed
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startAgentHeapWatch = startAgentHeapWatch;
|
|
4
|
+
/**
|
|
5
|
+
* The agent root's own memory, on the record and returned to the OS.
|
|
6
|
+
*
|
|
7
|
+
* hub-main has had the `[mem]` heartbeat and the stranded-page reclaimer since
|
|
8
|
+
* 2026-08; the agent root process (container AND desktop — both reach
|
|
9
|
+
* `startAgent` through the same launcher) had neither, so a measured 0.8–1.4 GB
|
|
10
|
+
* agent root emitted no line at all and never gave a page back. This is the same
|
|
11
|
+
* mechanism, not a second one: `startHeapWatch` with the V8 reclaimer, labelled
|
|
12
|
+
* `agent-main`.
|
|
13
|
+
*/
|
|
14
|
+
const system_1 = require("@camstack/system");
|
|
15
|
+
const DEFAULT_DEPS = {
|
|
16
|
+
start: system_1.startHeapWatch,
|
|
17
|
+
createReclaimer: system_1.createV8Reclaimer,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Start the heartbeat + reclaim for the agent root. Returns the stop function.
|
|
21
|
+
* `createV8Reclaimer()` returns undefined when V8 declines; the heartbeat then
|
|
22
|
+
* runs report-only, exactly as hub-main does.
|
|
23
|
+
*/
|
|
24
|
+
function startAgentHeapWatch(logger, deps = DEFAULT_DEPS) {
|
|
25
|
+
const reclaimer = deps.createReclaimer();
|
|
26
|
+
const sink = {
|
|
27
|
+
info: (line) => {
|
|
28
|
+
logger.info(line);
|
|
29
|
+
},
|
|
30
|
+
warn: (line) => {
|
|
31
|
+
logger.warn(line);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
return deps.start(system_1.AGENT_MAIN_HEAP_WATCH_LABEL, sink, undefined, reclaimer === undefined ? undefined : { reclaim: reclaimer }, undefined, undefined,
|
|
35
|
+
// Say who chose the ceiling: the default ceiling below is applied by the
|
|
36
|
+
// launcher's argv, and an absent flag means the image/desktop predates it.
|
|
37
|
+
true);
|
|
38
|
+
}
|
|
@@ -240,7 +240,8 @@ function createAgentService(deps) {
|
|
|
240
240
|
const { broker } = ctx;
|
|
241
241
|
const cpus = os.cpus();
|
|
242
242
|
let cpuPercent = 0;
|
|
243
|
-
let memoryPercent =
|
|
243
|
+
let memoryPercent = null;
|
|
244
|
+
let freeMemoryBytes = null;
|
|
244
245
|
const metrics = deps.getMetricsProvider?.();
|
|
245
246
|
if (metrics) {
|
|
246
247
|
// Bounded so $agent.status never hangs (and drops the node from
|
|
@@ -249,6 +250,7 @@ function createAgentService(deps) {
|
|
|
249
250
|
if (snapshot) {
|
|
250
251
|
cpuPercent = snapshot.cpu.total;
|
|
251
252
|
memoryPercent = snapshot.memory.percent;
|
|
253
|
+
freeMemoryBytes = snapshot.memory.availableBytes;
|
|
252
254
|
}
|
|
253
255
|
}
|
|
254
256
|
const mem = process.memoryUsage();
|
|
@@ -262,9 +264,12 @@ function createAgentService(deps) {
|
|
|
262
264
|
cpuCores: cpus.length,
|
|
263
265
|
cpuModel: cpus[0]?.model,
|
|
264
266
|
totalMemoryMB: Math.round(os.totalmem() / 1024 / 1024),
|
|
265
|
-
freeMemoryMB: Math.round(
|
|
267
|
+
freeMemoryMB: freeMemoryBytes === null ? null : Math.round(freeMemoryBytes / 1024 / 1024),
|
|
266
268
|
cpuPercent,
|
|
267
269
|
memoryPercent,
|
|
270
|
+
// The agent's own (core) process, so a topology row can attribute
|
|
271
|
+
// the largest process on the node instead of showing 0.
|
|
272
|
+
processRssBytes: process.memoryUsage().rss,
|
|
268
273
|
uptime: os.uptime(),
|
|
269
274
|
// The agent *process* itself (distinct from the host `uptime` /
|
|
270
275
|
// memory above) — surfaced so the UI can show how long THIS agent
|
|
@@ -298,7 +303,7 @@ function createAgentService(deps) {
|
|
|
298
303
|
handler: async (ctx) => {
|
|
299
304
|
const { broker } = ctx;
|
|
300
305
|
let cpuPercent = 0;
|
|
301
|
-
let memoryPercent =
|
|
306
|
+
let memoryPercent = null;
|
|
302
307
|
const metrics = deps.getMetricsProvider?.();
|
|
303
308
|
if (metrics) {
|
|
304
309
|
try {
|
package/dist/agent/main.js
CHANGED
|
@@ -76,6 +76,7 @@ const register_agent_cap_dispatch_js_1 = require("./register-agent-cap-dispatch.
|
|
|
76
76
|
// entries flow through it (console output + hub forwarding).
|
|
77
77
|
// ---------------------------------------------------------------------------
|
|
78
78
|
const system_2 = require("@camstack/system");
|
|
79
|
+
const agent_heap_watch_js_1 = require("./agent-heap-watch.js");
|
|
79
80
|
/** A reconnect (stop + rebuild + start) that takes longer than this is abandoned. */
|
|
80
81
|
const RECONNECT_DEADLINE_MS = 90_000;
|
|
81
82
|
/** How often the agent re-evaluates whether to ask its hub "am I still a member?" (D857). */
|
|
@@ -97,6 +98,11 @@ const DATA_NODEID_CAPS = new Set(types_2.ALL_CAPABILITY_DEFINITIONS.filter((c) =
|
|
|
97
98
|
// Main
|
|
98
99
|
// ---------------------------------------------------------------------------
|
|
99
100
|
async function startAgent(configPath) {
|
|
101
|
+
// The agent root's own memory: `[mem] agent-main` heartbeat + the stranded-page
|
|
102
|
+
// reclaim hub-main has had since 2026-08. Started first so a slow boot is on the
|
|
103
|
+
// record; lines reach the hub through the same LogManager the addons use.
|
|
104
|
+
const heapWatchLogger = agentLogManager.createLogger().withTags({ addonId: 'heap-watch' });
|
|
105
|
+
(0, agent_heap_watch_js_1.startAgentHeapWatch)(heapWatchLogger);
|
|
100
106
|
const config = (0, agent_config_js_1.loadAgentConfig)(configPath);
|
|
101
107
|
// Derive CAMSTACK_HUB_URL from the configured hub address so remote agents
|
|
102
108
|
// no longer need a second, redundant env var set by hand (the cross-node
|
|
@@ -480,7 +486,7 @@ async function startAgent(configPath) {
|
|
|
480
486
|
dataDir: config.dataDir,
|
|
481
487
|
runnerBootCheck: () => agentRunnerBootCheck?.current(),
|
|
482
488
|
});
|
|
483
|
-
capabilityRegistry.registerProvider('server-management', agent_update_service_js_1.AGENT_RUNTIME_ADDON_ID, (0, server_management_provider_js_1.buildServerManagementProvider)(agentUpdateService));
|
|
489
|
+
capabilityRegistry.registerProvider('server-management', agent_update_service_js_1.AGENT_RUNTIME_ADDON_ID, (0, server_management_provider_js_1.buildServerManagementProvider)(agentUpdateService, config.dataDir));
|
|
484
490
|
// The agent's certificates (D856): mesh leaf swap with automatic rollback, and the
|
|
485
491
|
// hub-issued HTTPS leaf hot-swapped into the sniffing listener. The listener is
|
|
486
492
|
// created later in boot, so it is read through a getter.
|
|
@@ -349,7 +349,9 @@ async function computeTopologyInternal(agentRegistry, addonRegistry, getNodeRoot
|
|
|
349
349
|
name: `${node.info.id} (core)`,
|
|
350
350
|
state: 'running',
|
|
351
351
|
cpuPercent: node.status?.cpuPercent ?? 0,
|
|
352
|
-
|
|
352
|
+
// The agent's own process RSS from $agent.status; 0 only for an agent
|
|
353
|
+
// that predates the field.
|
|
354
|
+
memoryRss: node.status?.processRssBytes ?? 0,
|
|
353
355
|
uptimeSeconds: Math.floor((Date.now() - node.connectedSince) / 1000),
|
|
354
356
|
services: mainProcessServices,
|
|
355
357
|
};
|
|
@@ -414,7 +416,7 @@ async function computeTopologyInternal(agentRegistry, addonRegistry, getNodeRoot
|
|
|
414
416
|
isHub: node.isHub,
|
|
415
417
|
isOnline: node.isOnline !== false,
|
|
416
418
|
cpuPercent: node.status?.cpuPercent ?? 0,
|
|
417
|
-
memoryPercent: node.status?.memoryPercent ??
|
|
419
|
+
memoryPercent: node.status?.memoryPercent ?? null,
|
|
418
420
|
uptime: Date.now() - node.connectedSince,
|
|
419
421
|
// Fix (was hardcoded `new Date().toISOString()` — always "now", a bug
|
|
420
422
|
// even for online nodes): report the persisted `lastActive` when we have
|
|
@@ -456,7 +458,7 @@ async function computeTopologyInternal(agentRegistry, addonRegistry, getNodeRoot
|
|
|
456
458
|
isHub: persisted.isHub,
|
|
457
459
|
isOnline: false,
|
|
458
460
|
cpuPercent: 0,
|
|
459
|
-
memoryPercent:
|
|
461
|
+
memoryPercent: null,
|
|
460
462
|
uptime: 0,
|
|
461
463
|
lastSeen: new Date(persisted.lastActive).toISOString(),
|
|
462
464
|
localIps: [...persisted.localIps],
|
|
@@ -69,9 +69,9 @@ function createClusterNodesRouter(agentRegistry) {
|
|
|
69
69
|
await agentRegistry.repairNodeAddons(input.nodeId);
|
|
70
70
|
return { success: true };
|
|
71
71
|
}),
|
|
72
|
-
//
|
|
73
|
-
// `pipelineOrchestrator.removeAgentSettings` on the frontend
|
|
74
|
-
//
|
|
72
|
+
// Forget a node, online or offline: revoke its certificates, cut it from routing, purge its
|
|
73
|
+
// persisted history. Pairs with `pipelineOrchestrator.removeAgentSettings` on the frontend
|
|
74
|
+
// (per-node pipeline assignments).
|
|
75
75
|
forgetNode: trpc_middleware_js_1.adminProcedure
|
|
76
76
|
.input(ForgetNodeInputSchema)
|
|
77
77
|
.output(ForgetNodeResultSchema)
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildLaunchOverrideMethods = buildLaunchOverrideMethods;
|
|
3
4
|
exports.buildServerManagementProvider = buildServerManagementProvider;
|
|
4
|
-
|
|
5
|
+
const launch_args_js_1 = require("../../launch-args.js");
|
|
6
|
+
/** The per-node launch override methods (ADR 0970), backed by the node's data dir. */
|
|
7
|
+
function buildLaunchOverrideMethods(dataDir) {
|
|
8
|
+
return {
|
|
9
|
+
getLaunchOverrides: () => Promise.resolve({ heapMb: (0, launch_args_js_1.readHeapOverrideMb)(dataDir) }),
|
|
10
|
+
setLaunchOverrides: (input) => {
|
|
11
|
+
(0, launch_args_js_1.writeHeapOverrideMb)(dataDir, input.heapMb);
|
|
12
|
+
return Promise.resolve({ heapMb: input.heapMb, restartRequired: true });
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function buildServerManagementProvider(service, dataDir) {
|
|
5
17
|
if (service === null)
|
|
6
18
|
return null;
|
|
7
19
|
return {
|
|
@@ -10,5 +22,6 @@ function buildServerManagementProvider(service) {
|
|
|
10
22
|
applyServerUpdate: (input) => service.applyServerUpdate(input),
|
|
11
23
|
rollbackServerUpdate: () => service.rollbackServerUpdate(),
|
|
12
24
|
restartServer: () => Promise.resolve(service.restartNode()),
|
|
25
|
+
...buildLaunchOverrideMethods(dataDir),
|
|
13
26
|
};
|
|
14
27
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// AUTO-GENERATED by scripts/generate-cap-mounts.ts — DO NOT EDIT
|
|
3
3
|
// Re-run: npx tsx scripts/generate-cap-mounts.ts
|
|
4
4
|
//
|
|
5
|
-
// Mounted:
|
|
5
|
+
// Mounted: 189 Skipped (legacy): 3
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.LEGACY_SHAPE_SKIP = void 0;
|
|
8
8
|
exports.mountAllCaps = mountAllCaps;
|
|
@@ -231,6 +231,8 @@ function mountAllCaps(services) {
|
|
|
231
231
|
...first,
|
|
232
232
|
listSupportedDeviceKinds: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'listSupportedDeviceKinds'),
|
|
233
233
|
listExposedDevices: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'listExposedDevices'),
|
|
234
|
+
listSupportedEntityKinds: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'listSupportedEntityKinds'),
|
|
235
|
+
listExposedEntities: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'listExposedEntities'),
|
|
234
236
|
};
|
|
235
237
|
}, remoteCapProxy),
|
|
236
238
|
deviceExtension: (0, generated_cap_routers_js_1.createCapRouter_deviceExtension)((_ctx) => (0, device_collection_dispatch_js_1.requireDeviceCollection)(reg, 'device-extension'), remoteCapProxy),
|
|
@@ -501,6 +503,7 @@ function mountAllCaps(services) {
|
|
|
501
503
|
osd: (0, generated_cap_routers_js_1.createCapRouter_osd)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'osd'), remoteCapProxy),
|
|
502
504
|
osdManager: (0, generated_cap_routers_js_1.createCapRouter_osdManager)((_ctx) => reg?.getSingleton('osd-manager') ?? null, remoteCapProxy),
|
|
503
505
|
petFeeder: (0, generated_cap_routers_js_1.createCapRouter_petFeeder)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'pet-feeder'), remoteCapProxy),
|
|
506
|
+
petFountain: (0, generated_cap_routers_js_1.createCapRouter_petFountain)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'pet-fountain'), remoteCapProxy),
|
|
504
507
|
pipelineAnalytics: (0, generated_cap_routers_js_1.createCapRouter_pipelineAnalytics)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'pipeline-analytics'), remoteCapProxy),
|
|
505
508
|
pipelineExecutor: (0, generated_cap_routers_js_1.createCapRouter_pipelineExecutor)((_ctx) => reg?.getSingleton('pipeline-executor') ??
|
|
506
509
|
null, remoteCapProxy),
|