infinicode 2.6.1 → 2.7.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.
@@ -7,6 +7,11 @@ export declare class TelemetryCollector {
7
7
  /** Per-core % from a single snapshot (approximate, no per-core delta kept). */
8
8
  private perCore;
9
9
  private network;
10
+ /** Usage of the primary filesystem (root on POSIX, system drive on Windows) via
11
+ * fs.statfs (Node ≥18.15, no native deps). Never throws. */
12
+ private diskUsage;
13
+ /** CPU temperature in °C from the Linux thermal zone (Pi/SBC). null elsewhere. */
14
+ private temperatureC;
10
15
  collect(): HardwareSnapshot;
11
16
  }
12
17
  /** A 0–100 load figure derived from a snapshot (used by the compute router). */
@@ -9,6 +9,7 @@
9
9
  * on a fixed cadence (~1 Hz) so deltas are meaningful.
10
10
  */
11
11
  import os from 'node:os';
12
+ import { readFileSync, statfsSync } from 'node:fs';
12
13
  export class TelemetryCollector {
13
14
  lastCpu = null;
14
15
  lastNet = new Map();
@@ -44,22 +45,83 @@ export class TelemetryCollector {
44
45
  });
45
46
  }
46
47
  network() {
47
- // node:os doesn't expose byte counters; report interface presence with
48
- // zeroed rates by default. A platform collector can override later.
49
- const ifaces = os.networkInterfaces();
48
+ // Real rx/tx byte-rate deltas where the kernel exposes counters. Linux lists
49
+ // per-interface byte totals in /proc/net/dev; we diff against the previous
50
+ // tick. On platforms without it (e.g. Windows) rates stay 0 but interface
51
+ // presence is still reported. Never throws — a read failure degrades to the
52
+ // os.networkInterfaces() presence list with zeroed rates.
53
+ const now = Date.now();
50
54
  const out = [];
55
+ let counters = null;
56
+ if (os.platform() === 'linux') {
57
+ try {
58
+ counters = new Map();
59
+ for (const line of readFileSync('/proc/net/dev', 'utf8').split('\n')) {
60
+ const m = line.match(/^\s*([^:]+):\s*(.+)$/);
61
+ if (!m)
62
+ continue;
63
+ const cols = m[2].trim().split(/\s+/).map(Number);
64
+ // Receive bytes = column 0, Transmit bytes = column 8.
65
+ counters.set(m[1].trim(), { rx: cols[0] || 0, tx: cols[8] || 0 });
66
+ }
67
+ }
68
+ catch {
69
+ counters = null;
70
+ }
71
+ }
72
+ const ifaces = os.networkInterfaces();
51
73
  for (const [name, addrs] of Object.entries(ifaces)) {
52
74
  if (!addrs || addrs.every(a => a.internal))
53
75
  continue;
54
76
  const prev = this.lastNet.get(name);
55
- const now = Date.now();
56
- // Without kernel counters we can't compute a true rate; keep 0 but retain
57
- // the hook so a native collector can populate rx/tx deltas.
58
- out.push({ name, rxBytesPerSec: 0, txBytesPerSec: 0 });
59
- this.lastNet.set(name, { rx: prev?.rx ?? 0, tx: prev?.tx ?? 0, at: now });
77
+ const cur = counters?.get(name);
78
+ let rxRate = 0;
79
+ let txRate = 0;
80
+ if (cur && prev && prev.at) {
81
+ const dt = (now - prev.at) / 1000;
82
+ if (dt > 0) {
83
+ rxRate = Math.max(0, Math.round((cur.rx - prev.rx) / dt));
84
+ txRate = Math.max(0, Math.round((cur.tx - prev.tx) / dt));
85
+ }
86
+ }
87
+ out.push({ name, rxBytesPerSec: rxRate, txBytesPerSec: txRate });
88
+ this.lastNet.set(name, { rx: cur?.rx ?? 0, tx: cur?.tx ?? 0, at: now });
60
89
  }
61
90
  return { interfaces: out };
62
91
  }
92
+ /** Usage of the primary filesystem (root on POSIX, system drive on Windows) via
93
+ * fs.statfs (Node ≥18.15, no native deps). Never throws. */
94
+ diskUsage() {
95
+ const path = os.platform() === 'win32'
96
+ ? (process.env.SystemDrive ? process.env.SystemDrive + '\\' : 'C:\\')
97
+ : '/';
98
+ try {
99
+ const st = statfsSync(path);
100
+ const totalBytes = Number(st.blocks) * Number(st.bsize);
101
+ const freeBytes = Number(st.bavail) * Number(st.bsize);
102
+ const usedBytes = Math.max(0, totalBytes - freeBytes);
103
+ const percent = totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 100) : 0;
104
+ return { mounts: [{ path, totalBytes, usedBytes, percent }] };
105
+ }
106
+ catch {
107
+ return { mounts: [] };
108
+ }
109
+ }
110
+ /** CPU temperature in °C from the Linux thermal zone (Pi/SBC). null elsewhere. */
111
+ temperatureC() {
112
+ if (os.platform() !== 'linux')
113
+ return null;
114
+ try {
115
+ const milli = Number(readFileSync('/sys/class/thermal/thermal_zone0/temp', 'utf8').trim());
116
+ if (!Number.isFinite(milli) || milli <= 0)
117
+ return null;
118
+ // Usually millidegrees (47000 = 47°C); some SoCs report whole °C.
119
+ return Math.round((milli > 1000 ? milli / 1000 : milli) * 10) / 10;
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
63
125
  collect() {
64
126
  const total = os.totalmem();
65
127
  const free = os.freemem();
@@ -80,7 +142,7 @@ export class TelemetryCollector {
80
142
  percent: total > 0 ? Math.round((used / total) * 100) : 0,
81
143
  },
82
144
  gpu: null, // populated by an optional platform GPU probe later
83
- disk: { mounts: [] },
145
+ disk: this.diskUsage(),
84
146
  network: this.network(),
85
147
  system: {
86
148
  hostname: os.hostname(),
@@ -90,6 +152,7 @@ export class TelemetryCollector {
90
152
  kernelRelease: os.release(),
91
153
  },
92
154
  processes: [],
155
+ temperatureC: this.temperatureC(),
93
156
  timestamp: Date.now(),
94
157
  };
95
158
  }
@@ -16,7 +16,28 @@
16
16
  import { createServer } from 'node:http';
17
17
  import { createServer as createHttpsServer } from 'node:https';
18
18
  import { timingSafeEqual } from 'node:crypto';
19
+ import { readFileSync } from 'node:fs';
20
+ import { createRequire } from 'node:module';
19
21
  import { frameToSSE, parseSSE } from './protocol.js';
22
+ /** Read the livekit-client UMD bundle from the installed dependency, once.
23
+ * Returns null (cached) if the package isn't present — the dashboard then
24
+ * falls back to an idle camera panel instead of erroring. */
25
+ let _lkUmd;
26
+ function loadLiveKitUmd() {
27
+ if (_lkUmd !== undefined)
28
+ return _lkUmd;
29
+ try {
30
+ const require = createRequire(import.meta.url);
31
+ // The package's exports map blocks deep subpaths, but its main/require entry
32
+ // IS the UMD bundle — so a bare resolve returns dist/livekit-client.umd.js.
33
+ const file = require.resolve('livekit-client');
34
+ _lkUmd = readFileSync(file, 'utf8');
35
+ }
36
+ catch {
37
+ _lkUmd = null;
38
+ }
39
+ return _lkUmd;
40
+ }
20
41
  /** Constant-time string compare — avoids leaking the token via response timing. */
21
42
  function safeEqual(a, b) {
22
43
  const ab = Buffer.from(a);
@@ -131,6 +152,20 @@ export class MeshServer {
131
152
  res.end(this.opts.dashboardHtml);
132
153
  return;
133
154
  }
155
+ // LiveKit browser SDK, served from the installed dependency so the Park
156
+ // tab can subscribe to a robot's live camera track. Loaded by the dashboard
157
+ // with the ?token= appended (so it passes the auth gate above). Optional:
158
+ // if livekit-client isn't installed, the dashboard just shows an idle cam.
159
+ if (req.method === 'GET' && path === '/vendor/livekit-client.umd.js') {
160
+ const js = loadLiveKitUmd();
161
+ if (!js) {
162
+ res.writeHead(404).end('livekit-client not installed');
163
+ return;
164
+ }
165
+ res.writeHead(200, { 'content-type': 'application/javascript; charset=utf-8', 'cache-control': 'public, max-age=86400' });
166
+ res.end(js);
167
+ return;
168
+ }
134
169
  if (req.method === 'GET' && path === '/fed/status') {
135
170
  res.writeHead(200, { 'content-type': 'application/json' });
136
171
  res.end(JSON.stringify(this.opts.getStatus?.() ?? {}));
@@ -107,6 +107,9 @@ export interface HardwareSnapshot {
107
107
  cpuPercent: number;
108
108
  memoryBytes: number;
109
109
  }>;
110
+ /** CPU package temperature in °C (Pi thermal zone / lm-sensors). null when the
111
+ * platform doesn't expose it (e.g. most Windows hosts). */
112
+ temperatureC?: number | null;
110
113
  timestamp: number;
111
114
  }
112
115
  /** Node status entry consumed by the mesh map (neutral form of nodes.status). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
4
4
  "description": "OpenKernel — provider-agnostic AI execution kernel. Native coding agent + mission-driven execution runtime.",
5
5
  "type": "module",
6
6
  "main": "./dist/kernel/index.js",
@@ -75,6 +75,7 @@
75
75
  "conf": "^12.0.0",
76
76
  "execa": "^9.3.0",
77
77
  "inquirer": "^9.2.23",
78
+ "livekit-client": "^2.20.1",
78
79
  "mitt": "^3.0.1",
79
80
  "nanoid": "^5.0.7",
80
81
  "node-fetch": "^3.3.2",
@@ -88,12 +89,12 @@
88
89
  "typescript": "^5.4.5"
89
90
  },
90
91
  "optionalDependencies": {
91
- "playwright": "^1.47.0",
92
- "infinicode-tui-win32-x64": "^2.3.0",
93
- "infinicode-tui-win32-arm64": "^2.3.0",
94
- "infinicode-tui-linux-x64": "^2.3.0",
95
- "infinicode-tui-linux-arm64": "^2.3.0",
92
+ "infinicode-tui-darwin-arm64": "^2.3.0",
96
93
  "infinicode-tui-darwin-x64": "^2.3.0",
97
- "infinicode-tui-darwin-arm64": "^2.3.0"
94
+ "infinicode-tui-linux-arm64": "^2.3.0",
95
+ "infinicode-tui-linux-x64": "^2.3.0",
96
+ "infinicode-tui-win32-arm64": "^2.3.0",
97
+ "infinicode-tui-win32-x64": "^2.3.0",
98
+ "playwright": "^1.47.0"
98
99
  }
99
100
  }