feat: migrate senses to TypeScript source + esbuild bundle
- Move index.js → src/index.ts with proper types for all 4 senses - Move schema.ts → src/schema.ts - Add package.json with esbuild build script per sense - Bundle to index.js at sense root (daemon loads this) - Update sense-generator coder prompt with TypeScript conventions Fixes #224
This commit is contained in:
parent
1940ccedd6
commit
4cf10ad7bf
@ -6,7 +6,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@uncaged/nerve-core": "latest",
|
"@uncaged/nerve-core": "latest",
|
||||||
"@uncaged/nerve-daemon": "latest",
|
"@uncaged/nerve-daemon": "latest",
|
||||||
"@uncaged/nerve-workflow-utils": "latest",
|
"@uncaged/nerve-workflow-utils": "link:../repos/nerve/packages/workflow-utils",
|
||||||
"drizzle-orm": "latest",
|
"drizzle-orm": "latest",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,42 +1,44 @@
|
|||||||
|
// src/index.ts
|
||||||
import { execFile } from "node:child_process";
|
import { execFile } from "node:child_process";
|
||||||
import { hermesGatewayHealth } from "./schema.ts";
|
|
||||||
|
|
||||||
/** Keep subprocess deadlines slightly under typical sense timeout (30s). */
|
// src/schema.ts
|
||||||
const EXEC_TIMEOUT_MS = 25_000;
|
import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||||
|
var hermesGatewayHealth = sqliteTable("hermes_gateway_health", {
|
||||||
/** HTTP probe stays below EXEC_TIMEOUT_MS and sense timeout (30s). */
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
const HTTP_TIMEOUT_MS = Math.min(23_000, EXEC_TIMEOUT_MS - 2000);
|
ts: integer("ts").notNull(),
|
||||||
|
alive: integer("alive").notNull(),
|
||||||
const HTTP_ERROR_MAX_LEN = 256;
|
mainPid: integer("main_pid").notNull(),
|
||||||
|
rssBytes: integer("rss_bytes").notNull(),
|
||||||
|
cpuPercent: real("cpu_percent").notNull(),
|
||||||
|
uptimeSec: integer("uptime_sec").notNull(),
|
||||||
|
activeSessions: integer("active_sessions").notNull(),
|
||||||
|
childProcessCount: integer("child_process_count").notNull(),
|
||||||
|
httpOk: integer("http_ok").notNull(),
|
||||||
|
httpStatusCode: integer("http_status_code").notNull(),
|
||||||
|
httpLatencyMs: integer("http_latency_ms").notNull(),
|
||||||
|
httpError: text("http_error").notNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/index.ts
|
||||||
|
var EXEC_TIMEOUT_MS = 25e3;
|
||||||
|
var HTTP_TIMEOUT_MS = Math.min(23e3, EXEC_TIMEOUT_MS - 2e3);
|
||||||
|
var HTTP_ERROR_MAX_LEN = 256;
|
||||||
function gatewayProbeUrl() {
|
function gatewayProbeUrl() {
|
||||||
const u =
|
const u = process.env.HERMES_GATEWAY_HEALTH_URL ?? process.env.NERVE_HERMES_GATEWAY_URL ?? "";
|
||||||
process.env.HERMES_GATEWAY_HEALTH_URL ??
|
|
||||||
process.env.NERVE_HERMES_GATEWAY_URL ??
|
|
||||||
"";
|
|
||||||
return String(u).trim();
|
return String(u).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function truncateHttpError(err) {
|
function truncateHttpError(err) {
|
||||||
const raw =
|
const raw = err && typeof err === "object" && "code" in err && err.code ? String(err.code) : String(err?.message ?? err ?? "error");
|
||||||
err && typeof err === "object" && "code" in err && err.code
|
|
||||||
? String(err.code)
|
|
||||||
: String(err?.message ?? err ?? "error");
|
|
||||||
const s = raw.trim() || "error";
|
const s = raw.trim() || "error";
|
||||||
return s.length > HTTP_ERROR_MAX_LEN ? s.slice(0, HTTP_ERROR_MAX_LEN) : s;
|
return s.length > HTTP_ERROR_MAX_LEN ? s.slice(0, HTTP_ERROR_MAX_LEN) : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* GET the gateway URL; success = HTTP 200–399.
|
|
||||||
* URL must be set via HERMES_GATEWAY_HEALTH_URL or NERVE_HERMES_GATEWAY_URL.
|
|
||||||
*/
|
|
||||||
async function probeGatewayHttp(url) {
|
async function probeGatewayHttp(url) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return {
|
return {
|
||||||
httpOk: 0,
|
httpOk: 0,
|
||||||
httpStatusCode: 0,
|
httpStatusCode: 0,
|
||||||
httpLatencyMs: 0,
|
httpLatencyMs: 0,
|
||||||
httpError: "missing_url",
|
httpError: "missing_url"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
@ -45,7 +47,7 @@ async function probeGatewayHttp(url) {
|
|||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
signal,
|
signal,
|
||||||
redirect: "follow",
|
redirect: "follow"
|
||||||
});
|
});
|
||||||
const httpLatencyMs = Date.now() - t0;
|
const httpLatencyMs = Date.now() - t0;
|
||||||
const code = res.status;
|
const code = res.status;
|
||||||
@ -54,7 +56,7 @@ async function probeGatewayHttp(url) {
|
|||||||
httpOk: ok ? 1 : 0,
|
httpOk: ok ? 1 : 0,
|
||||||
httpStatusCode: code,
|
httpStatusCode: code,
|
||||||
httpLatencyMs,
|
httpLatencyMs,
|
||||||
httpError: ok ? "" : truncateHttpError({ message: `HTTP ${code}` }),
|
httpError: ok ? "" : truncateHttpError({ message: `HTTP ${code}` })
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const httpLatencyMs = Date.now() - t0;
|
const httpLatencyMs = Date.now() - t0;
|
||||||
@ -62,15 +64,10 @@ async function probeGatewayHttp(url) {
|
|||||||
httpOk: 0,
|
httpOk: 0,
|
||||||
httpStatusCode: 0,
|
httpStatusCode: 0,
|
||||||
httpLatencyMs,
|
httpLatencyMs,
|
||||||
httpError: truncateHttpError(err),
|
httpError: truncateHttpError(err)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* When `ps` lacks `etimes` (wall-clock seconds since start), parse `etime`
|
|
||||||
* ([[dd-]hh:]mm:ss) into seconds. See ps(1) `etime` field description.
|
|
||||||
*/
|
|
||||||
function etimeToSeconds(etime) {
|
function etimeToSeconds(etime) {
|
||||||
let s = String(etime).trim();
|
let s = String(etime).trim();
|
||||||
if (!s) return 0;
|
if (!s) return 0;
|
||||||
@ -84,17 +81,16 @@ function etimeToSeconds(etime) {
|
|||||||
const parts = s.split(":").map((x) => Number.parseInt(String(x).trim(), 10));
|
const parts = s.split(":").map((x) => Number.parseInt(String(x).trim(), 10));
|
||||||
if (parts.some((n) => !Number.isFinite(n))) return 0;
|
if (parts.some((n) => !Number.isFinite(n))) return 0;
|
||||||
if (parts.length === 3) {
|
if (parts.length === 3) {
|
||||||
return Math.trunc(days * 86_400 + parts[0] * 3600 + parts[1] * 60 + parts[2]);
|
return Math.trunc(days * 86400 + parts[0] * 3600 + parts[1] * 60 + parts[2]);
|
||||||
}
|
}
|
||||||
if (parts.length === 2) {
|
if (parts.length === 2) {
|
||||||
return Math.trunc(days * 86_400 + parts[0] * 60 + parts[1]);
|
return Math.trunc(days * 86400 + parts[0] * 60 + parts[1]);
|
||||||
}
|
}
|
||||||
if (parts.length === 1) {
|
if (parts.length === 1) {
|
||||||
return Math.trunc(days * 86_400 + parts[0]);
|
return Math.trunc(days * 86400 + parts[0]);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function execFileUtf8(file, args, opts = {}) {
|
function execFileUtf8(file, args, opts = {}) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
execFile(
|
execFile(
|
||||||
@ -104,47 +100,43 @@ function execFileUtf8(file, args, opts = {}) {
|
|||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
maxBuffer: 8 * 1024 * 1024,
|
maxBuffer: 8 * 1024 * 1024,
|
||||||
timeout: EXEC_TIMEOUT_MS,
|
timeout: EXEC_TIMEOUT_MS,
|
||||||
...opts,
|
...opts
|
||||||
},
|
},
|
||||||
(err, stdout, stderr) => {
|
(err, stdout, stderr) => {
|
||||||
const exitCode =
|
const exitCode = err && typeof err.status === "number" ? err.status : err ? -1 : 0;
|
||||||
err && typeof err.status === "number" ? err.status : err ? -1 : 0;
|
|
||||||
resolve({
|
resolve({
|
||||||
exitCode,
|
exitCode,
|
||||||
errCode: err?.code,
|
errCode: err?.code,
|
||||||
stdout: String(stdout ?? ""),
|
stdout: String(stdout ?? ""),
|
||||||
stderr: String(stderr ?? ""),
|
stderr: String(stderr ?? "")
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function parseMainPidFromStatus(text2) {
|
||||||
function parseMainPidFromStatus(text) {
|
const m = text2.match(/Main PID:\s*(\d+)/i);
|
||||||
const m = text.match(/Main PID:\s*(\d+)/i);
|
|
||||||
return m ? Math.trunc(Number.parseInt(m[1], 10)) || 0 : 0;
|
return m ? Math.trunc(Number.parseInt(m[1], 10)) || 0 : 0;
|
||||||
}
|
}
|
||||||
|
function parseActiveLineFromStatus(text2) {
|
||||||
function parseActiveLineFromStatus(text) {
|
for (const line of text2.split("\n")) {
|
||||||
for (const line of text.split("\n")) {
|
|
||||||
if (/^\s*Active:/i.test(line)) {
|
if (/^\s*Active:/i.test(line)) {
|
||||||
const m = line.match(/Active:\s*(\S+)\s*\(([^)]*)\)/i);
|
const m = line.match(/Active:\s*(\S+)\s*\(([^)]*)\)/i);
|
||||||
if (m) {
|
if (m) {
|
||||||
return {
|
return {
|
||||||
active: m[1].toLowerCase() === "active",
|
active: m[1].toLowerCase() === "active",
|
||||||
subRunning: m[2].toLowerCase().includes("running"),
|
subRunning: m[2].toLowerCase().includes("running")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { active: false, subRunning: false };
|
return { active: false, subRunning: false };
|
||||||
}
|
}
|
||||||
|
function parseSystemctlShow(text2) {
|
||||||
function parseSystemctlShow(text) {
|
|
||||||
let mainPid = 0;
|
let mainPid = 0;
|
||||||
let active = false;
|
let active = false;
|
||||||
let subRunning = false;
|
let subRunning = false;
|
||||||
for (const line of text.split("\n")) {
|
for (const line of text2.split("\n")) {
|
||||||
const t = line.trim();
|
const t = line.trim();
|
||||||
if (t.startsWith("MainPID=")) {
|
if (t.startsWith("MainPID=")) {
|
||||||
mainPid = Math.trunc(Number.parseInt(t.slice("MainPID=".length), 10)) || 0;
|
mainPid = Math.trunc(Number.parseInt(t.slice("MainPID=".length), 10)) || 0;
|
||||||
@ -156,21 +148,18 @@ function parseSystemctlShow(text) {
|
|||||||
}
|
}
|
||||||
return { mainPid, active, subRunning };
|
return { mainPid, active, subRunning };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readSystemdState() {
|
async function readSystemdState() {
|
||||||
const status = await execFileUtf8("systemctl", [
|
const status = await execFileUtf8("systemctl", [
|
||||||
"--user",
|
"--user",
|
||||||
"--no-pager",
|
"--no-pager",
|
||||||
"status",
|
"status",
|
||||||
"hermes-gateway",
|
"hermes-gateway"
|
||||||
]);
|
]);
|
||||||
const combined = `${status.stdout}\n${status.stderr}`.trim();
|
const combined = `${status.stdout}
|
||||||
|
${status.stderr}`.trim();
|
||||||
let mainPid = parseMainPidFromStatus(combined);
|
let mainPid = parseMainPidFromStatus(combined);
|
||||||
let { active, subRunning } = parseActiveLineFromStatus(combined);
|
let { active, subRunning } = parseActiveLineFromStatus(combined);
|
||||||
|
const needShow = mainPid <= 0 || !active || !subRunning;
|
||||||
const needShow =
|
|
||||||
mainPid <= 0 || !active || !subRunning;
|
|
||||||
|
|
||||||
if (needShow) {
|
if (needShow) {
|
||||||
const show = await execFileUtf8("systemctl", [
|
const show = await execFileUtf8("systemctl", [
|
||||||
"--user",
|
"--user",
|
||||||
@ -182,25 +171,23 @@ async function readSystemdState() {
|
|||||||
"-p",
|
"-p",
|
||||||
"ActiveState",
|
"ActiveState",
|
||||||
"-p",
|
"-p",
|
||||||
"SubState",
|
"SubState"
|
||||||
]);
|
]);
|
||||||
const showText = `${show.stdout}\n${show.stderr}`;
|
const showText = `${show.stdout}
|
||||||
|
${show.stderr}`;
|
||||||
const s = parseSystemctlShow(showText);
|
const s = parseSystemctlShow(showText);
|
||||||
if (mainPid <= 0 && s.mainPid > 0) mainPid = s.mainPid;
|
if (mainPid <= 0 && s.mainPid > 0) mainPid = s.mainPid;
|
||||||
if (!active) active = s.active;
|
if (!active) active = s.active;
|
||||||
if (!subRunning) subRunning = s.subRunning;
|
if (!subRunning) subRunning = s.subRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { mainPid, systemdActiveRunning: active && subRunning };
|
return { mainPid, systemdActiveRunning: active && subRunning };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processExists(mainPid) {
|
async function processExists(mainPid) {
|
||||||
if (mainPid <= 0) return false;
|
if (mainPid <= 0) return false;
|
||||||
const r = await execFileUtf8("ps", ["-p", String(mainPid), "-o", "pid="]);
|
const r = await execFileUtf8("ps", ["-p", String(mainPid), "-o", "pid="]);
|
||||||
if (r.errCode === "ENOENT") return false;
|
if (r.errCode === "ENOENT") return false;
|
||||||
return r.stdout.trim().length > 0;
|
return r.stdout.trim().length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readPsMetrics(mainPid) {
|
async function readPsMetrics(mainPid) {
|
||||||
if (mainPid <= 0) {
|
if (mainPid <= 0) {
|
||||||
return { rssBytes: 0, cpuPercent: 0, uptimeSec: 0 };
|
return { rssBytes: 0, cpuPercent: 0, uptimeSec: 0 };
|
||||||
@ -209,7 +196,7 @@ async function readPsMetrics(mainPid) {
|
|||||||
"-p",
|
"-p",
|
||||||
String(mainPid),
|
String(mainPid),
|
||||||
"-o",
|
"-o",
|
||||||
"rss=,%cpu=,etimes=",
|
"rss=,%cpu=,etimes="
|
||||||
]);
|
]);
|
||||||
let line = r.stdout.trim().replace(/\s+/g, " ");
|
let line = r.stdout.trim().replace(/\s+/g, " ");
|
||||||
if (r.errCode === "ENOENT" || !line) {
|
if (r.errCode === "ENOENT" || !line) {
|
||||||
@ -221,41 +208,34 @@ async function readPsMetrics(mainPid) {
|
|||||||
"-p",
|
"-p",
|
||||||
String(mainPid),
|
String(mainPid),
|
||||||
"-o",
|
"-o",
|
||||||
"rss=,%cpu=,etime=",
|
"rss=,%cpu=,etime="
|
||||||
]);
|
]);
|
||||||
line = r.stdout.trim().replace(/\s+/g, " ");
|
line = r.stdout.trim().replace(/\s+/g, " ");
|
||||||
parts = line.split(" ").filter(Boolean);
|
parts = line.split(" ").filter(Boolean);
|
||||||
if (parts.length < 3) {
|
if (parts.length < 3) {
|
||||||
return { rssBytes: 0, cpuPercent: 0, uptimeSec: 0 };
|
return { rssBytes: 0, cpuPercent: 0, uptimeSec: 0 };
|
||||||
}
|
}
|
||||||
const rssKiB = Number(parts[0]);
|
const rssKiB2 = Number(parts[0]);
|
||||||
const cpu = Number(parts[1]);
|
const cpu2 = Number(parts[1]);
|
||||||
const uptimeSec = etimeToSeconds(parts.slice(2).join(" "));
|
const uptimeSec2 = etimeToSeconds(parts.slice(2).join(" "));
|
||||||
const rssBytes = Number.isFinite(rssKiB)
|
const rssBytes2 = Number.isFinite(rssKiB2) ? Math.trunc(rssKiB2 * 1024) : 0;
|
||||||
? Math.trunc(rssKiB * 1024)
|
const cpuPercent2 = Number.isFinite(cpu2) ? Math.round(cpu2 * 100) / 100 : 0;
|
||||||
: 0;
|
return { rssBytes: rssBytes2, cpuPercent: cpuPercent2, uptimeSec: uptimeSec2 };
|
||||||
const cpuPercent = Number.isFinite(cpu)
|
|
||||||
? Math.round(cpu * 100) / 100
|
|
||||||
: 0;
|
|
||||||
return { rssBytes, cpuPercent, uptimeSec };
|
|
||||||
}
|
}
|
||||||
const rssKiB = Number(parts[0]);
|
const rssKiB = Number(parts[0]);
|
||||||
const cpu = Number(parts[1]);
|
const cpu = Number(parts[1]);
|
||||||
const etimes = Number(parts[2]);
|
const etimes = Number(parts[2]);
|
||||||
const rssBytes = Number.isFinite(rssKiB) ? Math.trunc(rssKiB * 1024) : 0;
|
const rssBytes = Number.isFinite(rssKiB) ? Math.trunc(rssKiB * 1024) : 0;
|
||||||
const cpuPercent = Number.isFinite(cpu) ? Math.round(cpu * 100) / 100 : 0;
|
const cpuPercent = Number.isFinite(cpu) ? Math.round(cpu * 100) / 100 : 0;
|
||||||
const uptimeSec = Number.isFinite(etimes)
|
const uptimeSec = Number.isFinite(etimes) ? Math.trunc(etimes) : 0;
|
||||||
? Math.trunc(etimes)
|
|
||||||
: 0;
|
|
||||||
return { rssBytes, cpuPercent, uptimeSec };
|
return { rssBytes, cpuPercent, uptimeSec };
|
||||||
}
|
}
|
||||||
|
function parseActiveSessionsFromHermesStats(text2) {
|
||||||
function parseActiveSessionsFromHermesStats(text) {
|
const src = String(text2);
|
||||||
const src = String(text);
|
|
||||||
const patterns = [
|
const patterns = [
|
||||||
/^\s*Active\s+sessions?:\s*(\d+)/gim,
|
/^\s*Active\s+sessions?:\s*(\d+)/gim,
|
||||||
/^\s*active\s+sessions?:\s*(\d+)/gim,
|
/^\s*active\s+sessions?:\s*(\d+)/gim,
|
||||||
/^\s*Total\s+sessions?:\s*(\d+)/gim,
|
/^\s*Total\s+sessions?:\s*(\d+)/gim
|
||||||
];
|
];
|
||||||
for (const re of patterns) {
|
for (const re of patterns) {
|
||||||
re.lastIndex = 0;
|
re.lastIndex = 0;
|
||||||
@ -267,17 +247,16 @@ function parseActiveSessionsFromHermesStats(text) {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readActiveSessions() {
|
async function readActiveSessions() {
|
||||||
try {
|
try {
|
||||||
const r = await execFileUtf8("hermes", ["sessions", "stats"]);
|
const r = await execFileUtf8("hermes", ["sessions", "stats"]);
|
||||||
if (r.errCode === "ENOENT") return 0;
|
if (r.errCode === "ENOENT") return 0;
|
||||||
return parseActiveSessionsFromHermesStats(`${r.stdout}\n${r.stderr}`);
|
return parseActiveSessionsFromHermesStats(`${r.stdout}
|
||||||
|
${r.stderr}`);
|
||||||
} catch {
|
} catch {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function countDirectChildren(mainPid) {
|
async function countDirectChildren(mainPid) {
|
||||||
if (mainPid <= 0) return 0;
|
if (mainPid <= 0) return 0;
|
||||||
try {
|
try {
|
||||||
@ -286,25 +265,19 @@ async function countDirectChildren(mainPid) {
|
|||||||
"-o",
|
"-o",
|
||||||
"pid",
|
"pid",
|
||||||
"--ppid",
|
"--ppid",
|
||||||
String(mainPid),
|
String(mainPid)
|
||||||
]);
|
]);
|
||||||
if (r.errCode === "ENOENT") return 0;
|
if (r.errCode === "ENOENT") return 0;
|
||||||
const lines = r.stdout
|
const lines = r.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
||||||
.split("\n")
|
|
||||||
.map((l) => l.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
return lines.length;
|
return lines.length;
|
||||||
} catch {
|
} catch {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function compute(db, _peers) {
|
||||||
export async function compute(db, _peers) {
|
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
|
|
||||||
let mainPid = 0;
|
let mainPid = 0;
|
||||||
let systemdActiveRunning = false;
|
let systemdActiveRunning = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const st = await readSystemdState();
|
const st = await readSystemdState();
|
||||||
mainPid = st.mainPid;
|
mainPid = st.mainPid;
|
||||||
@ -313,14 +286,12 @@ export async function compute(db, _peers) {
|
|||||||
mainPid = 0;
|
mainPid = 0;
|
||||||
systemdActiveRunning = false;
|
systemdActiveRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let psOk = false;
|
let psOk = false;
|
||||||
try {
|
try {
|
||||||
psOk = await processExists(mainPid);
|
psOk = await processExists(mainPid);
|
||||||
} catch {
|
} catch {
|
||||||
psOk = false;
|
psOk = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rssBytes = 0;
|
let rssBytes = 0;
|
||||||
let cpuPercent = 0;
|
let cpuPercent = 0;
|
||||||
let uptimeSec = 0;
|
let uptimeSec = 0;
|
||||||
@ -336,17 +307,13 @@ export async function compute(db, _peers) {
|
|||||||
uptimeSec = 0;
|
uptimeSec = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const alive = systemdActiveRunning && mainPid > 0 && psOk ? 1 : 0;
|
||||||
const alive =
|
|
||||||
systemdActiveRunning && mainPid > 0 && psOk ? 1 : 0;
|
|
||||||
|
|
||||||
let activeSessions = 0;
|
let activeSessions = 0;
|
||||||
try {
|
try {
|
||||||
activeSessions = await readActiveSessions();
|
activeSessions = await readActiveSessions();
|
||||||
} catch {
|
} catch {
|
||||||
activeSessions = 0;
|
activeSessions = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let childProcessCount = 0;
|
let childProcessCount = 0;
|
||||||
if (alive && mainPid > 0) {
|
if (alive && mainPid > 0) {
|
||||||
try {
|
try {
|
||||||
@ -355,7 +322,6 @@ export async function compute(db, _peers) {
|
|||||||
childProcessCount = 0;
|
childProcessCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let httpOk = 0;
|
let httpOk = 0;
|
||||||
let httpStatusCode = 0;
|
let httpStatusCode = 0;
|
||||||
let httpLatencyMs = 0;
|
let httpLatencyMs = 0;
|
||||||
@ -372,9 +338,7 @@ export async function compute(db, _peers) {
|
|||||||
httpLatencyMs = 0;
|
httpLatencyMs = 0;
|
||||||
httpError = "probe_failed";
|
httpError = "probe_failed";
|
||||||
}
|
}
|
||||||
|
|
||||||
const storedMainPid = mainPid > 0 ? mainPid : 0;
|
const storedMainPid = mainPid > 0 ? mainPid : 0;
|
||||||
|
|
||||||
const row = {
|
const row = {
|
||||||
ts,
|
ts,
|
||||||
alive,
|
alive,
|
||||||
@ -387,11 +351,9 @@ export async function compute(db, _peers) {
|
|||||||
httpOk,
|
httpOk,
|
||||||
httpStatusCode,
|
httpStatusCode,
|
||||||
httpLatencyMs,
|
httpLatencyMs,
|
||||||
httpError,
|
httpError
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.insert(hermesGatewayHealth).values(row);
|
await db.insert(hermesGatewayHealth).values(row);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ts: row.ts,
|
ts: row.ts,
|
||||||
alive: row.alive,
|
alive: row.alive,
|
||||||
@ -404,6 +366,9 @@ export async function compute(db, _peers) {
|
|||||||
httpOk: row.httpOk,
|
httpOk: row.httpOk,
|
||||||
httpStatusCode: row.httpStatusCode,
|
httpStatusCode: row.httpStatusCode,
|
||||||
httpLatencyMs: row.httpLatencyMs,
|
httpLatencyMs: row.httpLatencyMs,
|
||||||
httpError: row.httpError,
|
httpError: row.httpError
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export {
|
||||||
|
compute
|
||||||
|
};
|
||||||
|
|||||||
17
senses/hermes-gateway-health/package.json
Normal file
17
senses/hermes-gateway-health/package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "sense-hermes-gateway-health",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=index.js --packages=external"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"esbuild": "^0.27.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": ["esbuild"]
|
||||||
|
}
|
||||||
|
}
|
||||||
310
senses/hermes-gateway-health/pnpm-lock.yaml
generated
Normal file
310
senses/hermes-gateway-health/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.0.0
|
||||||
|
version: 22.19.17
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.27.0
|
||||||
|
version: 0.27.7
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.7.0
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@types/node@22.19.17':
|
||||||
|
resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==}
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
typescript@5.9.3:
|
||||||
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
undici-types@6.21.0:
|
||||||
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@types/node@22.19.17':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.21.0
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.27.7
|
||||||
|
'@esbuild/android-arm': 0.27.7
|
||||||
|
'@esbuild/android-arm64': 0.27.7
|
||||||
|
'@esbuild/android-x64': 0.27.7
|
||||||
|
'@esbuild/darwin-arm64': 0.27.7
|
||||||
|
'@esbuild/darwin-x64': 0.27.7
|
||||||
|
'@esbuild/freebsd-arm64': 0.27.7
|
||||||
|
'@esbuild/freebsd-x64': 0.27.7
|
||||||
|
'@esbuild/linux-arm': 0.27.7
|
||||||
|
'@esbuild/linux-arm64': 0.27.7
|
||||||
|
'@esbuild/linux-ia32': 0.27.7
|
||||||
|
'@esbuild/linux-loong64': 0.27.7
|
||||||
|
'@esbuild/linux-mips64el': 0.27.7
|
||||||
|
'@esbuild/linux-ppc64': 0.27.7
|
||||||
|
'@esbuild/linux-riscv64': 0.27.7
|
||||||
|
'@esbuild/linux-s390x': 0.27.7
|
||||||
|
'@esbuild/linux-x64': 0.27.7
|
||||||
|
'@esbuild/netbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/netbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/openbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openharmony-arm64': 0.27.7
|
||||||
|
'@esbuild/sunos-x64': 0.27.7
|
||||||
|
'@esbuild/win32-arm64': 0.27.7
|
||||||
|
'@esbuild/win32-ia32': 0.27.7
|
||||||
|
'@esbuild/win32-x64': 0.27.7
|
||||||
|
|
||||||
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
|
undici-types@6.21.0: {}
|
||||||
424
senses/hermes-gateway-health/src/index.ts
Normal file
424
senses/hermes-gateway-health/src/index.ts
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
import { execFile } from "node:child_process";
|
||||||
|
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
||||||
|
import { hermesGatewayHealth } from "./schema.ts";
|
||||||
|
|
||||||
|
/** Keep subprocess deadlines slightly under typical sense timeout (30s). */
|
||||||
|
const EXEC_TIMEOUT_MS = 25_000;
|
||||||
|
|
||||||
|
/** HTTP probe stays below EXEC_TIMEOUT_MS and sense timeout (30s). */
|
||||||
|
const HTTP_TIMEOUT_MS = Math.min(23_000, EXEC_TIMEOUT_MS - 2000);
|
||||||
|
|
||||||
|
const HTTP_ERROR_MAX_LEN = 256;
|
||||||
|
|
||||||
|
function gatewayProbeUrl(): string {
|
||||||
|
const u =
|
||||||
|
process.env.HERMES_GATEWAY_HEALTH_URL ??
|
||||||
|
process.env.NERVE_HERMES_GATEWAY_URL ??
|
||||||
|
"";
|
||||||
|
return String(u).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function truncateHttpError(err: unknown): string {
|
||||||
|
const raw =
|
||||||
|
err && typeof err === "object" && "code" in err && (err as { code: unknown }).code
|
||||||
|
? String((err as { code: unknown }).code)
|
||||||
|
: String((err as { message?: unknown } | null)?.message ?? err ?? "error");
|
||||||
|
const s = raw.trim() || "error";
|
||||||
|
return s.length > HTTP_ERROR_MAX_LEN ? s.slice(0, HTTP_ERROR_MAX_LEN) : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HttpProbeResult {
|
||||||
|
httpOk: number;
|
||||||
|
httpStatusCode: number;
|
||||||
|
httpLatencyMs: number;
|
||||||
|
httpError: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET the gateway URL; success = HTTP 200–399.
|
||||||
|
* URL must be set via HERMES_GATEWAY_HEALTH_URL or NERVE_HERMES_GATEWAY_URL.
|
||||||
|
*/
|
||||||
|
async function probeGatewayHttp(url: string): Promise<HttpProbeResult> {
|
||||||
|
if (!url) {
|
||||||
|
return {
|
||||||
|
httpOk: 0,
|
||||||
|
httpStatusCode: 0,
|
||||||
|
httpLatencyMs: 0,
|
||||||
|
httpError: "missing_url",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const t0 = Date.now();
|
||||||
|
try {
|
||||||
|
const signal = AbortSignal.timeout(HTTP_TIMEOUT_MS);
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
signal,
|
||||||
|
redirect: "follow",
|
||||||
|
});
|
||||||
|
const httpLatencyMs = Date.now() - t0;
|
||||||
|
const code = res.status;
|
||||||
|
const ok = code >= 200 && code < 400;
|
||||||
|
return {
|
||||||
|
httpOk: ok ? 1 : 0,
|
||||||
|
httpStatusCode: code,
|
||||||
|
httpLatencyMs,
|
||||||
|
httpError: ok ? "" : truncateHttpError({ message: `HTTP ${code}` }),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const httpLatencyMs = Date.now() - t0;
|
||||||
|
return {
|
||||||
|
httpOk: 0,
|
||||||
|
httpStatusCode: 0,
|
||||||
|
httpLatencyMs,
|
||||||
|
httpError: truncateHttpError(err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `ps` lacks `etimes` (wall-clock seconds since start), parse `etime`
|
||||||
|
* ([[dd-]hh:]mm:ss) into seconds. See ps(1) `etime` field description.
|
||||||
|
*/
|
||||||
|
function etimeToSeconds(etime: string): number {
|
||||||
|
let s = String(etime).trim();
|
||||||
|
if (!s) return 0;
|
||||||
|
let days = 0;
|
||||||
|
if (s.includes("-")) {
|
||||||
|
const idx = s.indexOf("-");
|
||||||
|
const d = Number.parseInt(s.slice(0, idx), 10);
|
||||||
|
days = Number.isFinite(d) ? d : 0;
|
||||||
|
s = s.slice(idx + 1);
|
||||||
|
}
|
||||||
|
const parts = s.split(":").map((x) => Number.parseInt(String(x).trim(), 10));
|
||||||
|
if (parts.some((n) => !Number.isFinite(n))) return 0;
|
||||||
|
if (parts.length === 3) {
|
||||||
|
return Math.trunc(days * 86_400 + parts[0] * 3600 + parts[1] * 60 + parts[2]);
|
||||||
|
}
|
||||||
|
if (parts.length === 2) {
|
||||||
|
return Math.trunc(days * 86_400 + parts[0] * 60 + parts[1]);
|
||||||
|
}
|
||||||
|
if (parts.length === 1) {
|
||||||
|
return Math.trunc(days * 86_400 + parts[0]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExecResult {
|
||||||
|
exitCode: number;
|
||||||
|
errCode: string | undefined;
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function execFileUtf8(file: string, args: string[], opts: Record<string, unknown> = {}): Promise<ExecResult> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
execFile(
|
||||||
|
file,
|
||||||
|
args,
|
||||||
|
{
|
||||||
|
encoding: "utf8",
|
||||||
|
maxBuffer: 8 * 1024 * 1024,
|
||||||
|
timeout: EXEC_TIMEOUT_MS,
|
||||||
|
...opts,
|
||||||
|
} as Parameters<typeof execFile>[2],
|
||||||
|
(err, stdout, stderr) => {
|
||||||
|
const exitCode =
|
||||||
|
err && typeof (err as NodeJS.ErrnoException).status === "number"
|
||||||
|
? (err as NodeJS.ErrnoException & { status: number }).status
|
||||||
|
: err ? -1 : 0;
|
||||||
|
resolve({
|
||||||
|
exitCode,
|
||||||
|
errCode: (err as NodeJS.ErrnoException | null)?.code,
|
||||||
|
stdout: String(stdout ?? ""),
|
||||||
|
stderr: String(stderr ?? ""),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMainPidFromStatus(text: string): number {
|
||||||
|
const m = text.match(/Main PID:\s*(\d+)/i);
|
||||||
|
return m ? Math.trunc(Number.parseInt(m[1], 10)) || 0 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseActiveLineFromStatus(text: string): { active: boolean; subRunning: boolean } {
|
||||||
|
for (const line of text.split("\n")) {
|
||||||
|
if (/^\s*Active:/i.test(line)) {
|
||||||
|
const m = line.match(/Active:\s*(\S+)\s*\(([^)]*)\)/i);
|
||||||
|
if (m) {
|
||||||
|
return {
|
||||||
|
active: m[1].toLowerCase() === "active",
|
||||||
|
subRunning: m[2].toLowerCase().includes("running"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { active: false, subRunning: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSystemctlShow(text: string): { mainPid: number; active: boolean; subRunning: boolean } {
|
||||||
|
let mainPid = 0;
|
||||||
|
let active = false;
|
||||||
|
let subRunning = false;
|
||||||
|
for (const line of text.split("\n")) {
|
||||||
|
const t = line.trim();
|
||||||
|
if (t.startsWith("MainPID=")) {
|
||||||
|
mainPid = Math.trunc(Number.parseInt(t.slice("MainPID=".length), 10)) || 0;
|
||||||
|
} else if (t.startsWith("ActiveState=")) {
|
||||||
|
active = t.slice("ActiveState=".length).trim().toLowerCase() === "active";
|
||||||
|
} else if (t.startsWith("SubState=")) {
|
||||||
|
subRunning = t.slice("SubState=".length).trim().toLowerCase() === "running";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { mainPid, active, subRunning };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readSystemdState(): Promise<{ mainPid: number; systemdActiveRunning: boolean }> {
|
||||||
|
const status = await execFileUtf8("systemctl", [
|
||||||
|
"--user",
|
||||||
|
"--no-pager",
|
||||||
|
"status",
|
||||||
|
"hermes-gateway",
|
||||||
|
]);
|
||||||
|
const combined = `${status.stdout}\n${status.stderr}`.trim();
|
||||||
|
let mainPid = parseMainPidFromStatus(combined);
|
||||||
|
let { active, subRunning } = parseActiveLineFromStatus(combined);
|
||||||
|
|
||||||
|
const needShow = mainPid <= 0 || !active || !subRunning;
|
||||||
|
|
||||||
|
if (needShow) {
|
||||||
|
const show = await execFileUtf8("systemctl", [
|
||||||
|
"--user",
|
||||||
|
"--no-pager",
|
||||||
|
"show",
|
||||||
|
"hermes-gateway",
|
||||||
|
"-p",
|
||||||
|
"MainPID",
|
||||||
|
"-p",
|
||||||
|
"ActiveState",
|
||||||
|
"-p",
|
||||||
|
"SubState",
|
||||||
|
]);
|
||||||
|
const showText = `${show.stdout}\n${show.stderr}`;
|
||||||
|
const s = parseSystemctlShow(showText);
|
||||||
|
if (mainPid <= 0 && s.mainPid > 0) mainPid = s.mainPid;
|
||||||
|
if (!active) active = s.active;
|
||||||
|
if (!subRunning) subRunning = s.subRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mainPid, systemdActiveRunning: active && subRunning };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processExists(mainPid: number): Promise<boolean> {
|
||||||
|
if (mainPid <= 0) return false;
|
||||||
|
const r = await execFileUtf8("ps", ["-p", String(mainPid), "-o", "pid="]);
|
||||||
|
if (r.errCode === "ENOENT") return false;
|
||||||
|
return r.stdout.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PsMetrics {
|
||||||
|
rssBytes: number;
|
||||||
|
cpuPercent: number;
|
||||||
|
uptimeSec: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readPsMetrics(mainPid: number): Promise<PsMetrics> {
|
||||||
|
if (mainPid <= 0) {
|
||||||
|
return { rssBytes: 0, cpuPercent: 0, uptimeSec: 0 };
|
||||||
|
}
|
||||||
|
let r = await execFileUtf8("ps", [
|
||||||
|
"-p",
|
||||||
|
String(mainPid),
|
||||||
|
"-o",
|
||||||
|
"rss=,%cpu=,etimes=",
|
||||||
|
]);
|
||||||
|
let line = r.stdout.trim().replace(/\s+/g, " ");
|
||||||
|
if (r.errCode === "ENOENT" || !line) {
|
||||||
|
return { rssBytes: 0, cpuPercent: 0, uptimeSec: 0 };
|
||||||
|
}
|
||||||
|
let parts = line.split(" ").filter(Boolean);
|
||||||
|
if (parts.length < 3) {
|
||||||
|
r = await execFileUtf8("ps", [
|
||||||
|
"-p",
|
||||||
|
String(mainPid),
|
||||||
|
"-o",
|
||||||
|
"rss=,%cpu=,etime=",
|
||||||
|
]);
|
||||||
|
line = r.stdout.trim().replace(/\s+/g, " ");
|
||||||
|
parts = line.split(" ").filter(Boolean);
|
||||||
|
if (parts.length < 3) {
|
||||||
|
return { rssBytes: 0, cpuPercent: 0, uptimeSec: 0 };
|
||||||
|
}
|
||||||
|
const rssKiB = Number(parts[0]);
|
||||||
|
const cpu = Number(parts[1]);
|
||||||
|
const uptimeSec = etimeToSeconds(parts.slice(2).join(" "));
|
||||||
|
const rssBytes = Number.isFinite(rssKiB) ? Math.trunc(rssKiB * 1024) : 0;
|
||||||
|
const cpuPercent = Number.isFinite(cpu) ? Math.round(cpu * 100) / 100 : 0;
|
||||||
|
return { rssBytes, cpuPercent, uptimeSec };
|
||||||
|
}
|
||||||
|
const rssKiB = Number(parts[0]);
|
||||||
|
const cpu = Number(parts[1]);
|
||||||
|
const etimes = Number(parts[2]);
|
||||||
|
const rssBytes = Number.isFinite(rssKiB) ? Math.trunc(rssKiB * 1024) : 0;
|
||||||
|
const cpuPercent = Number.isFinite(cpu) ? Math.round(cpu * 100) / 100 : 0;
|
||||||
|
const uptimeSec = Number.isFinite(etimes) ? Math.trunc(etimes) : 0;
|
||||||
|
return { rssBytes, cpuPercent, uptimeSec };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseActiveSessionsFromHermesStats(text: string): number {
|
||||||
|
const src = String(text);
|
||||||
|
const patterns = [
|
||||||
|
/^\s*Active\s+sessions?:\s*(\d+)/gim,
|
||||||
|
/^\s*active\s+sessions?:\s*(\d+)/gim,
|
||||||
|
/^\s*Total\s+sessions?:\s*(\d+)/gim,
|
||||||
|
];
|
||||||
|
for (const re of patterns) {
|
||||||
|
re.lastIndex = 0;
|
||||||
|
const m = re.exec(src);
|
||||||
|
if (m) {
|
||||||
|
const n = Math.trunc(Number.parseInt(m[1], 10));
|
||||||
|
return Number.isFinite(n) ? n : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readActiveSessions(): Promise<number> {
|
||||||
|
try {
|
||||||
|
const r = await execFileUtf8("hermes", ["sessions", "stats"]);
|
||||||
|
if (r.errCode === "ENOENT") return 0;
|
||||||
|
return parseActiveSessionsFromHermesStats(`${r.stdout}\n${r.stderr}`);
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function countDirectChildren(mainPid: number): Promise<number> {
|
||||||
|
if (mainPid <= 0) return 0;
|
||||||
|
try {
|
||||||
|
const r = await execFileUtf8("ps", [
|
||||||
|
"--no-headers",
|
||||||
|
"-o",
|
||||||
|
"pid",
|
||||||
|
"--ppid",
|
||||||
|
String(mainPid),
|
||||||
|
]);
|
||||||
|
if (r.errCode === "ENOENT") return 0;
|
||||||
|
const lines = r.stdout
|
||||||
|
.split("\n")
|
||||||
|
.map((l) => l.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
return lines.length;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
||||||
|
const ts = Date.now();
|
||||||
|
|
||||||
|
let mainPid = 0;
|
||||||
|
let systemdActiveRunning = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const st = await readSystemdState();
|
||||||
|
mainPid = st.mainPid;
|
||||||
|
systemdActiveRunning = st.systemdActiveRunning;
|
||||||
|
} catch {
|
||||||
|
mainPid = 0;
|
||||||
|
systemdActiveRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let psOk = false;
|
||||||
|
try {
|
||||||
|
psOk = await processExists(mainPid);
|
||||||
|
} catch {
|
||||||
|
psOk = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rssBytes = 0;
|
||||||
|
let cpuPercent = 0;
|
||||||
|
let uptimeSec = 0;
|
||||||
|
if (psOk) {
|
||||||
|
try {
|
||||||
|
const m = await readPsMetrics(mainPid);
|
||||||
|
rssBytes = m.rssBytes;
|
||||||
|
cpuPercent = m.cpuPercent;
|
||||||
|
uptimeSec = m.uptimeSec;
|
||||||
|
} catch {
|
||||||
|
rssBytes = 0;
|
||||||
|
cpuPercent = 0;
|
||||||
|
uptimeSec = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const alive = systemdActiveRunning && mainPid > 0 && psOk ? 1 : 0;
|
||||||
|
|
||||||
|
let activeSessions = 0;
|
||||||
|
try {
|
||||||
|
activeSessions = await readActiveSessions();
|
||||||
|
} catch {
|
||||||
|
activeSessions = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let childProcessCount = 0;
|
||||||
|
if (alive && mainPid > 0) {
|
||||||
|
try {
|
||||||
|
childProcessCount = await countDirectChildren(mainPid);
|
||||||
|
} catch {
|
||||||
|
childProcessCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let httpOk = 0;
|
||||||
|
let httpStatusCode = 0;
|
||||||
|
let httpLatencyMs = 0;
|
||||||
|
let httpError = "";
|
||||||
|
try {
|
||||||
|
const h = await probeGatewayHttp(gatewayProbeUrl());
|
||||||
|
httpOk = h.httpOk;
|
||||||
|
httpStatusCode = h.httpStatusCode;
|
||||||
|
httpLatencyMs = h.httpLatencyMs;
|
||||||
|
httpError = h.httpError;
|
||||||
|
} catch {
|
||||||
|
httpOk = 0;
|
||||||
|
httpStatusCode = 0;
|
||||||
|
httpLatencyMs = 0;
|
||||||
|
httpError = "probe_failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedMainPid = mainPid > 0 ? mainPid : 0;
|
||||||
|
|
||||||
|
const row = {
|
||||||
|
ts,
|
||||||
|
alive,
|
||||||
|
mainPid: storedMainPid,
|
||||||
|
rssBytes: alive ? rssBytes : 0,
|
||||||
|
cpuPercent: alive ? cpuPercent : 0,
|
||||||
|
uptimeSec: alive ? uptimeSec : 0,
|
||||||
|
activeSessions,
|
||||||
|
childProcessCount: alive ? childProcessCount : 0,
|
||||||
|
httpOk,
|
||||||
|
httpStatusCode,
|
||||||
|
httpLatencyMs,
|
||||||
|
httpError,
|
||||||
|
};
|
||||||
|
|
||||||
|
await db.insert(hermesGatewayHealth).values(row);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ts: row.ts,
|
||||||
|
alive: row.alive,
|
||||||
|
mainPid: row.mainPid,
|
||||||
|
rssBytes: row.rssBytes,
|
||||||
|
cpuPercent: row.cpuPercent,
|
||||||
|
uptimeSec: row.uptimeSec,
|
||||||
|
activeSessions: row.activeSessions,
|
||||||
|
childProcessCount: row.childProcessCount,
|
||||||
|
httpOk: row.httpOk,
|
||||||
|
httpStatusCode: row.httpStatusCode,
|
||||||
|
httpLatencyMs: row.httpLatencyMs,
|
||||||
|
httpError: row.httpError,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,25 +1,31 @@
|
|||||||
|
// src/index.ts
|
||||||
import { createReadStream } from "node:fs";
|
import { createReadStream } from "node:fs";
|
||||||
import { readdir } from "node:fs/promises";
|
import { readdir } from "node:fs/promises";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { createInterface } from "node:readline";
|
import { createInterface } from "node:readline";
|
||||||
import { hermesSessionMessageStats } from "./schema.ts";
|
|
||||||
|
|
||||||
const MEASUREMENT_WINDOW_MS = 900_000;
|
// src/schema.ts
|
||||||
const MEASUREMENT_WINDOW_SECONDS = 900;
|
import { integer, sqliteTable } from "drizzle-orm/sqlite-core";
|
||||||
|
var hermesSessionMessageStats = sqliteTable("hermes_session_message_stats", {
|
||||||
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
|
ts: integer("ts").notNull(),
|
||||||
|
totalUserMessages: integer("total_user_messages").notNull(),
|
||||||
|
totalAssistantMessages: integer("total_assistant_messages").notNull(),
|
||||||
|
totalToolMessages: integer("total_tool_messages").notNull(),
|
||||||
|
totalMessages: integer("total_messages").notNull(),
|
||||||
|
activeSessions: integer("active_sessions").notNull(),
|
||||||
|
measurementWindowSeconds: integer("measurement_window_seconds").notNull()
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
// src/index.ts
|
||||||
* @param {string} filePath
|
var MEASUREMENT_WINDOW_MS = 9e5;
|
||||||
* @param {number} cutoffMs
|
var MEASUREMENT_WINDOW_SECONDS = 900;
|
||||||
* @param {number} nowMs
|
|
||||||
* @returns {Promise<{ user: number; assistant: number; tool: number; fileHadActivity: boolean }>}
|
|
||||||
*/
|
|
||||||
async function aggregateJsonlFile(filePath, cutoffMs, nowMs) {
|
async function aggregateJsonlFile(filePath, cutoffMs, nowMs) {
|
||||||
let user = 0;
|
let user = 0;
|
||||||
let assistant = 0;
|
let assistant = 0;
|
||||||
let tool = 0;
|
let tool = 0;
|
||||||
let fileHadActivity = false;
|
let fileHadActivity = false;
|
||||||
|
|
||||||
const input = createReadStream(filePath, { encoding: "utf8" });
|
const input = createReadStream(filePath, { encoding: "utf8" });
|
||||||
const rl = createInterface({ input, crlfDelay: Infinity });
|
const rl = createInterface({ input, crlfDelay: Infinity });
|
||||||
try {
|
try {
|
||||||
@ -32,13 +38,13 @@ async function aggregateJsonlFile(filePath, cutoffMs, nowMs) {
|
|||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (typeof obj.role !== "string" || typeof obj.timestamp !== "string") {
|
if (typeof obj !== "object" || obj === null || typeof obj.role !== "string" || typeof obj.timestamp !== "string") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const t = Date.parse(obj.timestamp);
|
const record = obj;
|
||||||
|
const t = Date.parse(record.timestamp);
|
||||||
if (!Number.isFinite(t) || t < cutoffMs || t > nowMs) continue;
|
if (!Number.isFinite(t) || t < cutoffMs || t > nowMs) continue;
|
||||||
|
const roleNorm = record.role.trim().toLowerCase();
|
||||||
const roleNorm = obj.role.trim().toLowerCase();
|
|
||||||
if (roleNorm === "user") {
|
if (roleNorm === "user") {
|
||||||
user++;
|
user++;
|
||||||
fileHadActivity = true;
|
fileHadActivity = true;
|
||||||
@ -53,27 +59,21 @@ async function aggregateJsonlFile(filePath, cutoffMs, nowMs) {
|
|||||||
} finally {
|
} finally {
|
||||||
rl.close();
|
rl.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { user, assistant, tool, fileHadActivity };
|
return { user, assistant, tool, fileHadActivity };
|
||||||
}
|
}
|
||||||
|
async function compute(db, _peers) {
|
||||||
export async function compute(db, _peers) {
|
|
||||||
const nowMs = Date.now();
|
const nowMs = Date.now();
|
||||||
const cutoffMs = nowMs - MEASUREMENT_WINDOW_MS;
|
const cutoffMs = nowMs - MEASUREMENT_WINDOW_MS;
|
||||||
const ts = nowMs;
|
const ts = nowMs;
|
||||||
|
|
||||||
let totalUserMessages = 0;
|
let totalUserMessages = 0;
|
||||||
let totalAssistantMessages = 0;
|
let totalAssistantMessages = 0;
|
||||||
let totalToolMessages = 0;
|
let totalToolMessages = 0;
|
||||||
let activeSessions = 0;
|
let activeSessions = 0;
|
||||||
|
|
||||||
const sessionsDir = join(homedir(), ".hermes", "sessions");
|
const sessionsDir = join(homedir(), ".hermes", "sessions");
|
||||||
let files = [];
|
let files = [];
|
||||||
try {
|
try {
|
||||||
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
||||||
files = entries
|
files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => join(sessionsDir, e.name));
|
||||||
.filter((e) => e.isFile() && e.name.endsWith(".jsonl"))
|
|
||||||
.map((e) => join(sessionsDir, e.name));
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
|
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
|
||||||
files = [];
|
files = [];
|
||||||
@ -81,22 +81,18 @@ export async function compute(db, _peers) {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const filePath of files) {
|
for (const filePath of files) {
|
||||||
const { user, assistant, tool, fileHadActivity } = await aggregateJsonlFile(
|
const { user, assistant, tool, fileHadActivity } = await aggregateJsonlFile(
|
||||||
filePath,
|
filePath,
|
||||||
cutoffMs,
|
cutoffMs,
|
||||||
nowMs,
|
nowMs
|
||||||
);
|
);
|
||||||
totalUserMessages += user;
|
totalUserMessages += user;
|
||||||
totalAssistantMessages += assistant;
|
totalAssistantMessages += assistant;
|
||||||
totalToolMessages += tool;
|
totalToolMessages += tool;
|
||||||
if (fileHadActivity) activeSessions++;
|
if (fileHadActivity) activeSessions++;
|
||||||
}
|
}
|
||||||
|
const totalMessages = totalUserMessages + totalAssistantMessages + totalToolMessages;
|
||||||
const totalMessages =
|
|
||||||
totalUserMessages + totalAssistantMessages + totalToolMessages;
|
|
||||||
|
|
||||||
const row = {
|
const row = {
|
||||||
ts,
|
ts,
|
||||||
totalUserMessages,
|
totalUserMessages,
|
||||||
@ -104,11 +100,9 @@ export async function compute(db, _peers) {
|
|||||||
totalToolMessages,
|
totalToolMessages,
|
||||||
totalMessages,
|
totalMessages,
|
||||||
activeSessions,
|
activeSessions,
|
||||||
measurementWindowSeconds: MEASUREMENT_WINDOW_SECONDS,
|
measurementWindowSeconds: MEASUREMENT_WINDOW_SECONDS
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.insert(hermesSessionMessageStats).values(row);
|
await db.insert(hermesSessionMessageStats).values(row);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ts: row.ts,
|
ts: row.ts,
|
||||||
totalUserMessages: row.totalUserMessages,
|
totalUserMessages: row.totalUserMessages,
|
||||||
@ -116,6 +110,9 @@ export async function compute(db, _peers) {
|
|||||||
totalToolMessages: row.totalToolMessages,
|
totalToolMessages: row.totalToolMessages,
|
||||||
totalMessages: row.totalMessages,
|
totalMessages: row.totalMessages,
|
||||||
activeSessions: row.activeSessions,
|
activeSessions: row.activeSessions,
|
||||||
measurementWindowSeconds: row.measurementWindowSeconds,
|
measurementWindowSeconds: row.measurementWindowSeconds
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export {
|
||||||
|
compute
|
||||||
|
};
|
||||||
|
|||||||
17
senses/hermes-session-message-stats/package.json
Normal file
17
senses/hermes-session-message-stats/package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "sense-hermes-session-message-stats",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=index.js --packages=external"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"esbuild": "^0.27.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": ["esbuild"]
|
||||||
|
}
|
||||||
|
}
|
||||||
310
senses/hermes-session-message-stats/pnpm-lock.yaml
generated
Normal file
310
senses/hermes-session-message-stats/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.0.0
|
||||||
|
version: 22.19.17
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.27.0
|
||||||
|
version: 0.27.7
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.7.0
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@types/node@22.19.17':
|
||||||
|
resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==}
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
typescript@5.9.3:
|
||||||
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
undici-types@6.21.0:
|
||||||
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@types/node@22.19.17':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.21.0
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.27.7
|
||||||
|
'@esbuild/android-arm': 0.27.7
|
||||||
|
'@esbuild/android-arm64': 0.27.7
|
||||||
|
'@esbuild/android-x64': 0.27.7
|
||||||
|
'@esbuild/darwin-arm64': 0.27.7
|
||||||
|
'@esbuild/darwin-x64': 0.27.7
|
||||||
|
'@esbuild/freebsd-arm64': 0.27.7
|
||||||
|
'@esbuild/freebsd-x64': 0.27.7
|
||||||
|
'@esbuild/linux-arm': 0.27.7
|
||||||
|
'@esbuild/linux-arm64': 0.27.7
|
||||||
|
'@esbuild/linux-ia32': 0.27.7
|
||||||
|
'@esbuild/linux-loong64': 0.27.7
|
||||||
|
'@esbuild/linux-mips64el': 0.27.7
|
||||||
|
'@esbuild/linux-ppc64': 0.27.7
|
||||||
|
'@esbuild/linux-riscv64': 0.27.7
|
||||||
|
'@esbuild/linux-s390x': 0.27.7
|
||||||
|
'@esbuild/linux-x64': 0.27.7
|
||||||
|
'@esbuild/netbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/netbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/openbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openharmony-arm64': 0.27.7
|
||||||
|
'@esbuild/sunos-x64': 0.27.7
|
||||||
|
'@esbuild/win32-arm64': 0.27.7
|
||||||
|
'@esbuild/win32-ia32': 0.27.7
|
||||||
|
'@esbuild/win32-x64': 0.27.7
|
||||||
|
|
||||||
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
|
undici-types@6.21.0: {}
|
||||||
128
senses/hermes-session-message-stats/src/index.ts
Normal file
128
senses/hermes-session-message-stats/src/index.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { createReadStream } from "node:fs";
|
||||||
|
import { readdir } from "node:fs/promises";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { createInterface } from "node:readline";
|
||||||
|
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
||||||
|
import { hermesSessionMessageStats } from "./schema.ts";
|
||||||
|
|
||||||
|
const MEASUREMENT_WINDOW_MS = 900_000;
|
||||||
|
const MEASUREMENT_WINDOW_SECONDS = 900;
|
||||||
|
|
||||||
|
interface MessageCounts {
|
||||||
|
user: number;
|
||||||
|
assistant: number;
|
||||||
|
tool: number;
|
||||||
|
fileHadActivity: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function aggregateJsonlFile(filePath: string, cutoffMs: number, nowMs: number): Promise<MessageCounts> {
|
||||||
|
let user = 0;
|
||||||
|
let assistant = 0;
|
||||||
|
let tool = 0;
|
||||||
|
let fileHadActivity = false;
|
||||||
|
|
||||||
|
const input = createReadStream(filePath, { encoding: "utf8" });
|
||||||
|
const rl = createInterface({ input, crlfDelay: Infinity });
|
||||||
|
try {
|
||||||
|
for await (const line of rl) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed) continue;
|
||||||
|
let obj: unknown;
|
||||||
|
try {
|
||||||
|
obj = JSON.parse(trimmed);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof obj !== "object" || obj === null ||
|
||||||
|
typeof (obj as Record<string, unknown>).role !== "string" ||
|
||||||
|
typeof (obj as Record<string, unknown>).timestamp !== "string"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const record = obj as { role: string; timestamp: string };
|
||||||
|
const t = Date.parse(record.timestamp);
|
||||||
|
if (!Number.isFinite(t) || t < cutoffMs || t > nowMs) continue;
|
||||||
|
|
||||||
|
const roleNorm = record.role.trim().toLowerCase();
|
||||||
|
if (roleNorm === "user") {
|
||||||
|
user++;
|
||||||
|
fileHadActivity = true;
|
||||||
|
} else if (roleNorm === "assistant") {
|
||||||
|
assistant++;
|
||||||
|
fileHadActivity = true;
|
||||||
|
} else if (roleNorm === "tool") {
|
||||||
|
tool++;
|
||||||
|
fileHadActivity = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { user, assistant, tool, fileHadActivity };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
||||||
|
const nowMs = Date.now();
|
||||||
|
const cutoffMs = nowMs - MEASUREMENT_WINDOW_MS;
|
||||||
|
const ts = nowMs;
|
||||||
|
|
||||||
|
let totalUserMessages = 0;
|
||||||
|
let totalAssistantMessages = 0;
|
||||||
|
let totalToolMessages = 0;
|
||||||
|
let activeSessions = 0;
|
||||||
|
|
||||||
|
const sessionsDir = join(homedir(), ".hermes", "sessions");
|
||||||
|
let files: string[] = [];
|
||||||
|
try {
|
||||||
|
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
||||||
|
files = entries
|
||||||
|
.filter((e) => e.isFile() && e.name.endsWith(".jsonl"))
|
||||||
|
.map((e) => join(sessionsDir, e.name));
|
||||||
|
} catch (err) {
|
||||||
|
if (err && typeof err === "object" && "code" in err && (err as NodeJS.ErrnoException).code === "ENOENT") {
|
||||||
|
files = [];
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const filePath of files) {
|
||||||
|
const { user, assistant, tool, fileHadActivity } = await aggregateJsonlFile(
|
||||||
|
filePath,
|
||||||
|
cutoffMs,
|
||||||
|
nowMs,
|
||||||
|
);
|
||||||
|
totalUserMessages += user;
|
||||||
|
totalAssistantMessages += assistant;
|
||||||
|
totalToolMessages += tool;
|
||||||
|
if (fileHadActivity) activeSessions++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalMessages =
|
||||||
|
totalUserMessages + totalAssistantMessages + totalToolMessages;
|
||||||
|
|
||||||
|
const row = {
|
||||||
|
ts,
|
||||||
|
totalUserMessages,
|
||||||
|
totalAssistantMessages,
|
||||||
|
totalToolMessages,
|
||||||
|
totalMessages,
|
||||||
|
activeSessions,
|
||||||
|
measurementWindowSeconds: MEASUREMENT_WINDOW_SECONDS,
|
||||||
|
};
|
||||||
|
|
||||||
|
await db.insert(hermesSessionMessageStats).values(row);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ts: row.ts,
|
||||||
|
totalUserMessages: row.totalUserMessages,
|
||||||
|
totalAssistantMessages: row.totalAssistantMessages,
|
||||||
|
totalToolMessages: row.totalToolMessages,
|
||||||
|
totalMessages: row.totalMessages,
|
||||||
|
activeSessions: row.activeSessions,
|
||||||
|
measurementWindowSeconds: row.measurementWindowSeconds,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,13 +1,35 @@
|
|||||||
|
// src/index.ts
|
||||||
import { loadavg, totalmem, freemem, uptime } from "node:os";
|
import { loadavg, totalmem, freemem, uptime } from "node:os";
|
||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
import { snapshots } from "./schema.ts";
|
|
||||||
|
|
||||||
const SOCKSTAT_PATH = "/proc/net/sockstat";
|
// src/schema.ts
|
||||||
|
import { integer, real, sqliteTable } from "drizzle-orm/sqlite-core";
|
||||||
|
var snapshots = sqliteTable("snapshots", {
|
||||||
|
ts: integer("ts").primaryKey(),
|
||||||
|
cpuLoad1m: real("cpu_load_1m").notNull(),
|
||||||
|
cpuLoad5m: real("cpu_load_5m").notNull(),
|
||||||
|
cpuLoad15m: real("cpu_load_15m").notNull(),
|
||||||
|
memTotalMB: integer("mem_total_mb").notNull(),
|
||||||
|
memUsedMB: integer("mem_used_mb").notNull(),
|
||||||
|
memUsedPct: real("mem_used_pct").notNull(),
|
||||||
|
diskTotalGB: real("disk_total_gb").notNull(),
|
||||||
|
diskUsedGB: real("disk_used_gb").notNull(),
|
||||||
|
diskUsedPct: real("disk_used_pct").notNull(),
|
||||||
|
uptimeSec: integer("uptime_sec").notNull(),
|
||||||
|
// TCP socket stats (merged from linux-tcp-socket-stats)
|
||||||
|
socketsUsed: integer("sockets_used"),
|
||||||
|
tcpInuse: integer("tcp_inuse"),
|
||||||
|
tcpOrphan: integer("tcp_orphan"),
|
||||||
|
tcpTw: integer("tcp_tw"),
|
||||||
|
tcpAlloc: integer("tcp_alloc"),
|
||||||
|
tcpMemPages: integer("tcp_mem_pages")
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/index.ts
|
||||||
|
var SOCKSTAT_PATH = "/proc/net/sockstat";
|
||||||
function parseSockstat(content) {
|
function parseSockstat(content) {
|
||||||
let socketsUsed = 0, tcpInuse = 0, tcpOrphan = 0, tcpTw = 0, tcpAlloc = 0, tcpMemPages = 0;
|
let socketsUsed = 0, tcpInuse = 0, tcpOrphan = 0, tcpTw = 0, tcpAlloc = 0, tcpMemPages = 0;
|
||||||
|
|
||||||
for (const line of content.split("\n")) {
|
for (const line of content.split("\n")) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (trimmed.startsWith("sockets:")) {
|
if (trimmed.startsWith("sockets:")) {
|
||||||
@ -29,20 +51,16 @@ function parseSockstat(content) {
|
|||||||
tcpMemPages = map.mem ?? 0;
|
tcpMemPages = map.mem ?? 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { socketsUsed, tcpInuse, tcpOrphan, tcpTw, tcpAlloc, tcpMemPages };
|
return { socketsUsed, tcpInuse, tcpOrphan, tcpTw, tcpAlloc, tcpMemPages };
|
||||||
}
|
}
|
||||||
|
async function compute(db, _peers) {
|
||||||
export async function compute(db, _peers) {
|
|
||||||
const [load1, load5, load15] = loadavg();
|
const [load1, load5, load15] = loadavg();
|
||||||
|
|
||||||
const memTotal = totalmem();
|
const memTotal = totalmem();
|
||||||
const memFree = freemem();
|
const memFree = freemem();
|
||||||
const memUsed = memTotal - memFree;
|
const memUsed = memTotal - memFree;
|
||||||
const memTotalMB = Math.round(memTotal / 1024 / 1024);
|
const memTotalMB = Math.round(memTotal / 1024 / 1024);
|
||||||
const memUsedMB = Math.round(memUsed / 1024 / 1024);
|
const memUsedMB = Math.round(memUsed / 1024 / 1024);
|
||||||
const memUsedPct = Math.round((memUsed / memTotal) * 10000) / 100;
|
const memUsedPct = Math.round(memUsed / memTotal * 1e4) / 100;
|
||||||
|
|
||||||
let diskTotalGB = 0, diskUsedGB = 0, diskUsedPct = 0;
|
let diskTotalGB = 0, diskUsedGB = 0, diskUsedPct = 0;
|
||||||
try {
|
try {
|
||||||
const df = execSync("df -B1 / | tail -1", { encoding: "utf-8" }).trim();
|
const df = execSync("df -B1 / | tail -1", { encoding: "utf-8" }).trim();
|
||||||
@ -51,37 +69,44 @@ export async function compute(db, _peers) {
|
|||||||
const used = Number(parts[2]);
|
const used = Number(parts[2]);
|
||||||
diskTotalGB = Math.round(total / 1024 / 1024 / 1024 * 100) / 100;
|
diskTotalGB = Math.round(total / 1024 / 1024 / 1024 * 100) / 100;
|
||||||
diskUsedGB = Math.round(used / 1024 / 1024 / 1024 * 100) / 100;
|
diskUsedGB = Math.round(used / 1024 / 1024 / 1024 * 100) / 100;
|
||||||
diskUsedPct = total > 0 ? Math.round((used / total) * 10000) / 100 : 0;
|
diskUsedPct = total > 0 ? Math.round(used / total * 1e4) / 100 : 0;
|
||||||
} catch {}
|
} catch {
|
||||||
|
}
|
||||||
// TCP socket stats
|
|
||||||
let tcp = { socketsUsed: 0, tcpInuse: 0, tcpOrphan: 0, tcpTw: 0, tcpAlloc: 0, tcpMemPages: 0 };
|
let tcp = { socketsUsed: 0, tcpInuse: 0, tcpOrphan: 0, tcpTw: 0, tcpAlloc: 0, tcpMemPages: 0 };
|
||||||
try {
|
try {
|
||||||
const content = await readFile(SOCKSTAT_PATH, "utf8");
|
const content = await readFile(SOCKSTAT_PATH, "utf8");
|
||||||
tcp = parseSockstat(content);
|
tcp = parseSockstat(content);
|
||||||
} catch {}
|
} catch {
|
||||||
|
}
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
const uptimeSec = Math.round(uptime());
|
const uptimeSec = Math.round(uptime());
|
||||||
|
|
||||||
await db.insert(snapshots).values({
|
await db.insert(snapshots).values({
|
||||||
ts, cpuLoad1m: load1, cpuLoad5m: load5, cpuLoad15m: load15,
|
ts,
|
||||||
memTotalMB, memUsedMB, memUsedPct,
|
cpuLoad1m: load1,
|
||||||
diskTotalGB, diskUsedGB, diskUsedPct,
|
cpuLoad5m: load5,
|
||||||
|
cpuLoad15m: load15,
|
||||||
|
memTotalMB,
|
||||||
|
memUsedMB,
|
||||||
|
memUsedPct,
|
||||||
|
diskTotalGB,
|
||||||
|
diskUsedGB,
|
||||||
|
diskUsedPct,
|
||||||
uptimeSec,
|
uptimeSec,
|
||||||
socketsUsed: tcp.socketsUsed,
|
socketsUsed: tcp.socketsUsed,
|
||||||
tcpInuse: tcp.tcpInuse,
|
tcpInuse: tcp.tcpInuse,
|
||||||
tcpOrphan: tcp.tcpOrphan,
|
tcpOrphan: tcp.tcpOrphan,
|
||||||
tcpTw: tcp.tcpTw,
|
tcpTw: tcp.tcpTw,
|
||||||
tcpAlloc: tcp.tcpAlloc,
|
tcpAlloc: tcp.tcpAlloc,
|
||||||
tcpMemPages: tcp.tcpMemPages,
|
tcpMemPages: tcp.tcpMemPages
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cpu: { load1m: load1, load5m: load5, load15m: load15 },
|
cpu: { load1m: load1, load5m: load5, load15m: load15 },
|
||||||
memory: { totalMB: memTotalMB, usedMB: memUsedMB, usedPct: memUsedPct },
|
memory: { totalMB: memTotalMB, usedMB: memUsedMB, usedPct: memUsedPct },
|
||||||
disk: { totalGB: diskTotalGB, usedGB: diskUsedGB, usedPct: diskUsedPct },
|
disk: { totalGB: diskTotalGB, usedGB: diskUsedGB, usedPct: diskUsedPct },
|
||||||
tcp: { socketsUsed: tcp.socketsUsed, inuse: tcp.tcpInuse, orphan: tcp.tcpOrphan, tw: tcp.tcpTw, alloc: tcp.tcpAlloc, memPages: tcp.tcpMemPages },
|
tcp: { socketsUsed: tcp.socketsUsed, inuse: tcp.tcpInuse, orphan: tcp.tcpOrphan, tw: tcp.tcpTw, alloc: tcp.tcpAlloc, memPages: tcp.tcpMemPages },
|
||||||
uptimeSec,
|
uptimeSec
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export {
|
||||||
|
compute
|
||||||
|
};
|
||||||
|
|||||||
17
senses/linux-system-health/package.json
Normal file
17
senses/linux-system-health/package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "sense-linux-system-health",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=index.js --packages=external"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"esbuild": "^0.27.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": ["esbuild"]
|
||||||
|
}
|
||||||
|
}
|
||||||
310
senses/linux-system-health/pnpm-lock.yaml
generated
Normal file
310
senses/linux-system-health/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.0.0
|
||||||
|
version: 22.19.17
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.27.0
|
||||||
|
version: 0.27.7
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.7.0
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@types/node@22.19.17':
|
||||||
|
resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==}
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
typescript@5.9.3:
|
||||||
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
undici-types@6.21.0:
|
||||||
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@types/node@22.19.17':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.21.0
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.27.7
|
||||||
|
'@esbuild/android-arm': 0.27.7
|
||||||
|
'@esbuild/android-arm64': 0.27.7
|
||||||
|
'@esbuild/android-x64': 0.27.7
|
||||||
|
'@esbuild/darwin-arm64': 0.27.7
|
||||||
|
'@esbuild/darwin-x64': 0.27.7
|
||||||
|
'@esbuild/freebsd-arm64': 0.27.7
|
||||||
|
'@esbuild/freebsd-x64': 0.27.7
|
||||||
|
'@esbuild/linux-arm': 0.27.7
|
||||||
|
'@esbuild/linux-arm64': 0.27.7
|
||||||
|
'@esbuild/linux-ia32': 0.27.7
|
||||||
|
'@esbuild/linux-loong64': 0.27.7
|
||||||
|
'@esbuild/linux-mips64el': 0.27.7
|
||||||
|
'@esbuild/linux-ppc64': 0.27.7
|
||||||
|
'@esbuild/linux-riscv64': 0.27.7
|
||||||
|
'@esbuild/linux-s390x': 0.27.7
|
||||||
|
'@esbuild/linux-x64': 0.27.7
|
||||||
|
'@esbuild/netbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/netbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/openbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openharmony-arm64': 0.27.7
|
||||||
|
'@esbuild/sunos-x64': 0.27.7
|
||||||
|
'@esbuild/win32-arm64': 0.27.7
|
||||||
|
'@esbuild/win32-ia32': 0.27.7
|
||||||
|
'@esbuild/win32-x64': 0.27.7
|
||||||
|
|
||||||
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
|
undici-types@6.21.0: {}
|
||||||
96
senses/linux-system-health/src/index.ts
Normal file
96
senses/linux-system-health/src/index.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { loadavg, totalmem, freemem, uptime } from "node:os";
|
||||||
|
import { execSync } from "node:child_process";
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
||||||
|
import { snapshots } from "./schema.ts";
|
||||||
|
|
||||||
|
const SOCKSTAT_PATH = "/proc/net/sockstat";
|
||||||
|
|
||||||
|
interface SockstatResult {
|
||||||
|
socketsUsed: number;
|
||||||
|
tcpInuse: number;
|
||||||
|
tcpOrphan: number;
|
||||||
|
tcpTw: number;
|
||||||
|
tcpAlloc: number;
|
||||||
|
tcpMemPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSockstat(content: string): SockstatResult {
|
||||||
|
let socketsUsed = 0, tcpInuse = 0, tcpOrphan = 0, tcpTw = 0, tcpAlloc = 0, tcpMemPages = 0;
|
||||||
|
|
||||||
|
for (const line of content.split("\n")) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed.startsWith("sockets:")) {
|
||||||
|
const parts = trimmed.split(/\s+/);
|
||||||
|
const idx = parts.indexOf("used");
|
||||||
|
if (idx !== -1 && idx + 1 < parts.length) {
|
||||||
|
socketsUsed = Number.parseInt(parts[idx + 1], 10) || 0;
|
||||||
|
}
|
||||||
|
} else if (trimmed.startsWith("TCP:")) {
|
||||||
|
const parts = trimmed.split(/\s+/);
|
||||||
|
const map: Record<string, number> = {};
|
||||||
|
for (let i = 1; i + 1 < parts.length; i += 2) {
|
||||||
|
map[parts[i]] = Number.parseInt(parts[i + 1], 10) || 0;
|
||||||
|
}
|
||||||
|
tcpInuse = map.inuse ?? 0;
|
||||||
|
tcpOrphan = map.orphan ?? 0;
|
||||||
|
tcpTw = map.tw ?? 0;
|
||||||
|
tcpAlloc = map.alloc ?? 0;
|
||||||
|
tcpMemPages = map.mem ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { socketsUsed, tcpInuse, tcpOrphan, tcpTw, tcpAlloc, tcpMemPages };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
||||||
|
const [load1, load5, load15] = loadavg();
|
||||||
|
|
||||||
|
const memTotal = totalmem();
|
||||||
|
const memFree = freemem();
|
||||||
|
const memUsed = memTotal - memFree;
|
||||||
|
const memTotalMB = Math.round(memTotal / 1024 / 1024);
|
||||||
|
const memUsedMB = Math.round(memUsed / 1024 / 1024);
|
||||||
|
const memUsedPct = Math.round((memUsed / memTotal) * 10000) / 100;
|
||||||
|
|
||||||
|
let diskTotalGB = 0, diskUsedGB = 0, diskUsedPct = 0;
|
||||||
|
try {
|
||||||
|
const df = execSync("df -B1 / | tail -1", { encoding: "utf-8" }).trim();
|
||||||
|
const parts = df.split(/\s+/);
|
||||||
|
const total = Number(parts[1]);
|
||||||
|
const used = Number(parts[2]);
|
||||||
|
diskTotalGB = Math.round(total / 1024 / 1024 / 1024 * 100) / 100;
|
||||||
|
diskUsedGB = Math.round(used / 1024 / 1024 / 1024 * 100) / 100;
|
||||||
|
diskUsedPct = total > 0 ? Math.round((used / total) * 10000) / 100 : 0;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
let tcp: SockstatResult = { socketsUsed: 0, tcpInuse: 0, tcpOrphan: 0, tcpTw: 0, tcpAlloc: 0, tcpMemPages: 0 };
|
||||||
|
try {
|
||||||
|
const content = await readFile(SOCKSTAT_PATH, "utf8");
|
||||||
|
tcp = parseSockstat(content);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const ts = Date.now();
|
||||||
|
const uptimeSec = Math.round(uptime());
|
||||||
|
|
||||||
|
await db.insert(snapshots).values({
|
||||||
|
ts, cpuLoad1m: load1, cpuLoad5m: load5, cpuLoad15m: load15,
|
||||||
|
memTotalMB, memUsedMB, memUsedPct,
|
||||||
|
diskTotalGB, diskUsedGB, diskUsedPct,
|
||||||
|
uptimeSec,
|
||||||
|
socketsUsed: tcp.socketsUsed,
|
||||||
|
tcpInuse: tcp.tcpInuse,
|
||||||
|
tcpOrphan: tcp.tcpOrphan,
|
||||||
|
tcpTw: tcp.tcpTw,
|
||||||
|
tcpAlloc: tcp.tcpAlloc,
|
||||||
|
tcpMemPages: tcp.tcpMemPages,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
cpu: { load1m: load1, load5m: load5, load15m: load15 },
|
||||||
|
memory: { totalMB: memTotalMB, usedMB: memUsedMB, usedPct: memUsedPct },
|
||||||
|
disk: { totalGB: diskTotalGB, usedGB: diskUsedGB, usedPct: diskUsedPct },
|
||||||
|
tcp: { socketsUsed: tcp.socketsUsed, inuse: tcp.tcpInuse, orphan: tcp.tcpOrphan, tw: tcp.tcpTw, alloc: tcp.tcpAlloc, memPages: tcp.tcpMemPages },
|
||||||
|
uptimeSec,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,10 +1,19 @@
|
|||||||
import { workerProcessMetrics } from "./schema.ts";
|
// src/schema.ts
|
||||||
|
import { integer, real, sqliteTable } from "drizzle-orm/sqlite-core";
|
||||||
|
var workerProcessMetrics = sqliteTable("worker_process_metrics", {
|
||||||
|
ts: integer("ts").primaryKey(),
|
||||||
|
pid: integer("pid").notNull(),
|
||||||
|
uptimeSec: real("uptime_sec").notNull(),
|
||||||
|
heapUsedMB: real("heap_used_mb").notNull(),
|
||||||
|
rssMB: real("rss_mb").notNull(),
|
||||||
|
externalMB: real("external_mb").notNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/index.ts
|
||||||
function round2(n) {
|
function round2(n) {
|
||||||
return Math.round(n * 100) / 100;
|
return Math.round(n * 100) / 100;
|
||||||
}
|
}
|
||||||
|
async function compute(db, _peers) {
|
||||||
export async function compute(db, _peers) {
|
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
const pid = process.pid;
|
const pid = process.pid;
|
||||||
const uptimeSec = process.uptime();
|
const uptimeSec = process.uptime();
|
||||||
@ -12,24 +21,24 @@ export async function compute(db, _peers) {
|
|||||||
const heapUsedMB = round2(m.heapUsed / 1024 / 1024);
|
const heapUsedMB = round2(m.heapUsed / 1024 / 1024);
|
||||||
const rssMB = round2(m.rss / 1024 / 1024);
|
const rssMB = round2(m.rss / 1024 / 1024);
|
||||||
const externalMB = round2(m.external / 1024 / 1024);
|
const externalMB = round2(m.external / 1024 / 1024);
|
||||||
|
|
||||||
const row = {
|
const row = {
|
||||||
ts,
|
ts,
|
||||||
pid,
|
pid,
|
||||||
uptimeSec,
|
uptimeSec,
|
||||||
heapUsedMB,
|
heapUsedMB,
|
||||||
rssMB,
|
rssMB,
|
||||||
externalMB,
|
externalMB
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.insert(workerProcessMetrics).values(row);
|
await db.insert(workerProcessMetrics).values(row);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ts: row.ts,
|
ts: row.ts,
|
||||||
pid: row.pid,
|
pid: row.pid,
|
||||||
uptimeSec: row.uptimeSec,
|
uptimeSec: row.uptimeSec,
|
||||||
heapUsedMB: row.heapUsedMB,
|
heapUsedMB: row.heapUsedMB,
|
||||||
rssMB: row.rssMB,
|
rssMB: row.rssMB,
|
||||||
externalMB: row.externalMB,
|
externalMB: row.externalMB
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export {
|
||||||
|
compute
|
||||||
|
};
|
||||||
|
|||||||
17
senses/worker-process-metrics/package.json
Normal file
17
senses/worker-process-metrics/package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "sense-worker-process-metrics",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=index.js --packages=external"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"esbuild": "^0.27.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": ["esbuild"]
|
||||||
|
}
|
||||||
|
}
|
||||||
310
senses/worker-process-metrics/pnpm-lock.yaml
generated
Normal file
310
senses/worker-process-metrics/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.0.0
|
||||||
|
version: 22.19.17
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.27.0
|
||||||
|
version: 0.27.7
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.7.0
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@types/node@22.19.17':
|
||||||
|
resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==}
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
typescript@5.9.3:
|
||||||
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
undici-types@6.21.0:
|
||||||
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@types/node@22.19.17':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.21.0
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.27.7
|
||||||
|
'@esbuild/android-arm': 0.27.7
|
||||||
|
'@esbuild/android-arm64': 0.27.7
|
||||||
|
'@esbuild/android-x64': 0.27.7
|
||||||
|
'@esbuild/darwin-arm64': 0.27.7
|
||||||
|
'@esbuild/darwin-x64': 0.27.7
|
||||||
|
'@esbuild/freebsd-arm64': 0.27.7
|
||||||
|
'@esbuild/freebsd-x64': 0.27.7
|
||||||
|
'@esbuild/linux-arm': 0.27.7
|
||||||
|
'@esbuild/linux-arm64': 0.27.7
|
||||||
|
'@esbuild/linux-ia32': 0.27.7
|
||||||
|
'@esbuild/linux-loong64': 0.27.7
|
||||||
|
'@esbuild/linux-mips64el': 0.27.7
|
||||||
|
'@esbuild/linux-ppc64': 0.27.7
|
||||||
|
'@esbuild/linux-riscv64': 0.27.7
|
||||||
|
'@esbuild/linux-s390x': 0.27.7
|
||||||
|
'@esbuild/linux-x64': 0.27.7
|
||||||
|
'@esbuild/netbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/netbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/openbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openharmony-arm64': 0.27.7
|
||||||
|
'@esbuild/sunos-x64': 0.27.7
|
||||||
|
'@esbuild/win32-arm64': 0.27.7
|
||||||
|
'@esbuild/win32-ia32': 0.27.7
|
||||||
|
'@esbuild/win32-x64': 0.27.7
|
||||||
|
|
||||||
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
|
undici-types@6.21.0: {}
|
||||||
36
senses/worker-process-metrics/src/index.ts
Normal file
36
senses/worker-process-metrics/src/index.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
||||||
|
import { workerProcessMetrics } from "./schema.ts";
|
||||||
|
|
||||||
|
function round2(n: number): number {
|
||||||
|
return Math.round(n * 100) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
||||||
|
const ts = Date.now();
|
||||||
|
const pid = process.pid;
|
||||||
|
const uptimeSec = process.uptime();
|
||||||
|
const m = process.memoryUsage();
|
||||||
|
const heapUsedMB = round2(m.heapUsed / 1024 / 1024);
|
||||||
|
const rssMB = round2(m.rss / 1024 / 1024);
|
||||||
|
const externalMB = round2(m.external / 1024 / 1024);
|
||||||
|
|
||||||
|
const row = {
|
||||||
|
ts,
|
||||||
|
pid,
|
||||||
|
uptimeSec,
|
||||||
|
heapUsedMB,
|
||||||
|
rssMB,
|
||||||
|
externalMB,
|
||||||
|
};
|
||||||
|
|
||||||
|
await db.insert(workerProcessMetrics).values(row);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ts: row.ts,
|
||||||
|
pid: row.pid,
|
||||||
|
uptimeSec: row.uptimeSec,
|
||||||
|
heapUsedMB: row.heapUsedMB,
|
||||||
|
rssMB: row.rssMB,
|
||||||
|
externalMB: row.externalMB,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -3,5 +3,41 @@ export function coderPrompt({ threadId }: { threadId: string }): string {
|
|||||||
Read the nerve-dev skill for sense file structure and conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
|
Read the nerve-dev skill for sense file structure and conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
|
||||||
|
|
||||||
Implement the sense following the patterns from existing senses and the skill guide.
|
Implement the sense following the patterns from existing senses and the skill guide.
|
||||||
Create all required files and update nerve.yaml.`;
|
|
||||||
|
File structure for each sense:
|
||||||
|
- \`senses/<name>/src/index.ts\` — TypeScript source with proper types; import schema as \`./schema.ts\`
|
||||||
|
- \`senses/<name>/src/schema.ts\` — Drizzle schema (TypeScript)
|
||||||
|
- \`senses/<name>/migrations/\` — Drizzle migration files (at sense root, not inside src/)
|
||||||
|
- \`senses/<name>/package.json\` — with esbuild build script (see below)
|
||||||
|
- \`senses/<name>/index.js\` — bundled output generated by \`pnpm build\` (do NOT edit by hand)
|
||||||
|
|
||||||
|
package.json template for each sense:
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"name": "sense-<name>",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=index.js --packages=external"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"esbuild": "^0.27.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": ["esbuild"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
After creating all files, run inside the sense directory:
|
||||||
|
\`\`\`
|
||||||
|
pnpm install --no-cache && pnpm build
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
This generates the bundled \`index.js\` at the sense root that the daemon loads.
|
||||||
|
|
||||||
|
Then update nerve.yaml and run any required migrations.`;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user