@remcp/runtime 0.2.171 → 0.2.172

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
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.172 — 2026-10-22
4
+
5
+ - Add bounded host-wide CPU utilization sampling based on deltas from `os.cpus().times` so the dashboard can display real CPU percentages instead of treating load average as CPU%.
6
+ - Add `cpu.frequencyMHz` from reported per-core clock rates, with `null` for unsupported or invalid measurements.
7
+ - Preserve the existing `get_system_info` schema, 83-tool catalog, authentication restrictions, and backwards compatibility.
8
+
9
+
3
10
  ## 0.2.171 — 2026-10-21
4
11
 
5
12
  - Require MCP Server SDK 2.3.0 or later; update the published dependency lock to the patched 2.3.x line.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.171",
3
+ "version": "0.2.172",
4
4
  "description": "First-party ReMCP local device runtime: filesystem, terminal, computer-use, browser, diagnostics and document tools over MCP.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,34 @@
1
+ // OS-wide CPU utilization over a short bounded interval, not process CPU time or load average.
2
+ const timeKeys = ['user', 'nice', 'sys', 'idle', 'irq'];
3
+
4
+ function totals(cpu) {
5
+ const times = cpu?.times;
6
+ if (!times || !timeKeys.every(key => Number.isFinite(times[key]) && times[key] >= 0)) return null;
7
+ return {
8
+ total: timeKeys.reduce((sum, key) => sum + times[key], 0),
9
+ idle: times.idle,
10
+ };
11
+ }
12
+
13
+ export function cpuUtilizationRatio(before, after) {
14
+ if (!Array.isArray(before) || !Array.isArray(after) || !before.length || before.length !== after.length) return null;
15
+ let deltaTotal = 0;
16
+ let deltaIdle = 0;
17
+ for (let i = 0; i < before.length; i++) {
18
+ const previous = totals(before[i]);
19
+ const current = totals(after[i]);
20
+ if (!previous || !current) return null;
21
+ const total = current.total - previous.total;
22
+ const idle = current.idle - previous.idle;
23
+ if (total < 0 || idle < 0 || idle > total) return null;
24
+ deltaTotal += total;
25
+ deltaIdle += idle;
26
+ }
27
+ return deltaTotal > 0 ? Number(Math.max(0, Math.min(1, 1 - deltaIdle / deltaTotal)).toFixed(3)) : null;
28
+ }
29
+
30
+ export function cpuFrequencyMHz(cores) {
31
+ if (!Array.isArray(cores) || !cores.length) return null;
32
+ const speeds = cores.map(core => core?.speed).filter(speed => Number.isFinite(speed) && speed > 0 && speed <= 100000);
33
+ return speeds.length ? Math.round(speeds.reduce((sum, speed) => sum + speed, 0) / speeds.length) : null;
34
+ }
@@ -4,6 +4,7 @@ import { execFile } from 'node:child_process';
4
4
  import { promisify } from 'node:util';
5
5
  import { runtimeConfig } from '../config.mjs';
6
6
  import { clampInteger, fail, requireInteger, text } from '../util.mjs';
7
+ import { cpuUtilizationRatio, cpuFrequencyMHz } from './cpu-telemetry.mjs';
7
8
 
8
9
  const run = promisify(execFile);
9
10
 
@@ -42,6 +43,12 @@ export async function getSystemInfoTool() {
42
43
  const totalMem = os.totalmem();
43
44
  const freeMem = os.freemem();
44
45
  const load = os.loadavg();
46
+ // Sample for 140 ms while retrieving disk facts, so CPU % is actual host activity.
47
+ // Load average is a queue-length metric and must never be displayed as a percentage.
48
+ const diskPromise = diskUsage();
49
+ const sampledCpus = await new Promise(resolve => setTimeout(() => resolve(os.cpus()), 140));
50
+ const utilizationRatio = cpuUtilizationRatio(cpus, sampledCpus);
51
+ const frequencyMHz = cpuFrequencyMHz(sampledCpus);
45
52
  return text(JSON.stringify({
46
53
  hostname: os.hostname(),
47
54
  platform: process.platform,
@@ -53,6 +60,8 @@ export async function getSystemInfoTool() {
53
60
  cpu: {
54
61
  model: cpus[0]?.model?.trim() || 'unknown',
55
62
  count: cpus.length,
63
+ utilizationRatio,
64
+ frequencyMHz,
56
65
  loadAverage: process.platform === 'win32' ? null : load.map(value => Number(value.toFixed(2))),
57
66
  loadPerCore: process.platform === 'win32' || !cpus.length ? null : Number((load[0] / cpus.length).toFixed(2)),
58
67
  },
@@ -61,7 +70,7 @@ export async function getSystemInfoTool() {
61
70
  freeBytes: freeMem,
62
71
  usedRatio: totalMem ? Number(((totalMem - freeMem) / totalMem).toFixed(3)) : 0,
63
72
  },
64
- disk: await diskUsage(),
73
+ disk: await diskPromise,
65
74
  home: os.homedir(),
66
75
  tempDir: os.tmpdir(),
67
76
  runtimeName: runtimeConfig.name,