chore: remove stale sense index.js from source and tracking
小橘 <xiaoju@shazhou.work>
This commit is contained in:
parent
07be0d3dfa
commit
1da41c7f08
@ -1,85 +0,0 @@
|
|||||||
// senses/git-workspace-status/src/index.ts
|
|
||||||
import { execFileSync } from "node:child_process";
|
|
||||||
import { resolve } from "node:path";
|
|
||||||
|
|
||||||
// senses/git-workspace-status/src/schema.ts
|
|
||||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
||||||
var snapshots = sqliteTable("snapshots", {
|
|
||||||
ts: integer("ts").primaryKey(),
|
|
||||||
branch: text("branch").notNull(),
|
|
||||||
headShort: text("head_short").notNull(),
|
|
||||||
porcelainLines: integer("porcelain_lines").notNull(),
|
|
||||||
hasUpstream: integer("has_upstream").notNull(),
|
|
||||||
aheadCount: integer("ahead_count").notNull(),
|
|
||||||
behindCount: integer("behind_count").notNull(),
|
|
||||||
/** Empty string when the snapshot succeeded; otherwise a short error summary. */
|
|
||||||
gitError: text("git_error").notNull()
|
|
||||||
});
|
|
||||||
|
|
||||||
// senses/git-workspace-status/src/index.ts
|
|
||||||
var GIT_TIMEOUT_MS = 15e3;
|
|
||||||
function workspaceRoot() {
|
|
||||||
const raw = process.env.GIT_WORKSPACE_ROOT;
|
|
||||||
return raw ? resolve(raw) : resolve(process.cwd());
|
|
||||||
}
|
|
||||||
function gitErrorMessage(err) {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
const m = err.message.trim();
|
|
||||||
return m.length > 200 ? `${m.slice(0, 197)}...` : m;
|
|
||||||
}
|
|
||||||
return String(err);
|
|
||||||
}
|
|
||||||
function runGit(cwd, args) {
|
|
||||||
return execFileSync("git", args, {
|
|
||||||
cwd,
|
|
||||||
encoding: "utf8",
|
|
||||||
timeout: GIT_TIMEOUT_MS,
|
|
||||||
maxBuffer: 2 * 1024 * 1024
|
|
||||||
}).trimEnd();
|
|
||||||
}
|
|
||||||
function countPorcelainLines(output) {
|
|
||||||
if (!output) return 0;
|
|
||||||
return output.split("\n").filter((line) => line.length > 0).length;
|
|
||||||
}
|
|
||||||
async function compute() {
|
|
||||||
const root = workspaceRoot();
|
|
||||||
const ts = Date.now();
|
|
||||||
let branch = "";
|
|
||||||
let headShort = "";
|
|
||||||
let porcelainLines = 0;
|
|
||||||
let hasUpstream = 0;
|
|
||||||
let aheadCount = 0;
|
|
||||||
let behindCount = 0;
|
|
||||||
let gitError = "";
|
|
||||||
try {
|
|
||||||
const inside = runGit(root, ["rev-parse", "--is-inside-work-tree"]).trim();
|
|
||||||
if (inside !== "true") {
|
|
||||||
gitError = "not a git work tree";
|
|
||||||
return { signal: { ts, branch, headShort, porcelainLines, hasUpstream, aheadCount, behindCount, gitError }, workflow: null };
|
|
||||||
}
|
|
||||||
branch = runGit(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
||||||
headShort = runGit(root, ["rev-parse", "--short", "HEAD"]);
|
|
||||||
porcelainLines = countPorcelainLines(runGit(root, ["status", "--porcelain"]));
|
|
||||||
try {
|
|
||||||
runGit(root, ["rev-parse", "--abbrev-ref", "@{upstream}"]);
|
|
||||||
hasUpstream = 1;
|
|
||||||
const lb = runGit(root, ["rev-list", "--left-right", "--count", "HEAD...@{upstream}"]);
|
|
||||||
const parts = lb.split(/[\t\s]+/).filter(Boolean);
|
|
||||||
if (parts.length >= 2) {
|
|
||||||
aheadCount = Number.parseInt(parts[0], 10) || 0;
|
|
||||||
behindCount = Number.parseInt(parts[1], 10) || 0;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
hasUpstream = 0;
|
|
||||||
aheadCount = 0;
|
|
||||||
behindCount = 0;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
gitError = gitErrorMessage(e);
|
|
||||||
}
|
|
||||||
return { signal: { ts, branch, headShort, porcelainLines, hasUpstream, aheadCount, behindCount, gitError }, workflow: null };
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
compute,
|
|
||||||
snapshots as table
|
|
||||||
};
|
|
||||||
@ -1,361 +0,0 @@
|
|||||||
// senses/hermes-gateway-health/src/index.ts
|
|
||||||
import { execFile } from "node:child_process";
|
|
||||||
|
|
||||||
// senses/hermes-gateway-health/src/schema.ts
|
|
||||||
import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
||||||
var hermesGatewayHealth = sqliteTable("hermes_gateway_health", {
|
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
ts: integer("ts").notNull(),
|
|
||||||
alive: integer("alive").notNull(),
|
|
||||||
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()
|
|
||||||
});
|
|
||||||
|
|
||||||
// senses/hermes-gateway-health/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() {
|
|
||||||
const u = process.env.HERMES_GATEWAY_HEALTH_URL ?? process.env.NERVE_HERMES_GATEWAY_URL ?? "";
|
|
||||||
return String(u).trim();
|
|
||||||
}
|
|
||||||
function truncateHttpError(err) {
|
|
||||||
const raw = err && typeof err === "object" && "code" in err && err.code ? String(err.code) : String(err?.message ?? err ?? "error");
|
|
||||||
const s = raw.trim() || "error";
|
|
||||||
return s.length > HTTP_ERROR_MAX_LEN ? s.slice(0, HTTP_ERROR_MAX_LEN) : s;
|
|
||||||
}
|
|
||||||
async function probeGatewayHttp(url) {
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function etimeToSeconds(etime) {
|
|
||||||
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 * 86400 + parts[0] * 3600 + parts[1] * 60 + parts[2]);
|
|
||||||
}
|
|
||||||
if (parts.length === 2) {
|
|
||||||
return Math.trunc(days * 86400 + parts[0] * 60 + parts[1]);
|
|
||||||
}
|
|
||||||
if (parts.length === 1) {
|
|
||||||
return Math.trunc(days * 86400 + parts[0]);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
function execFileUtf8(file, args, opts = {}) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
execFile(
|
|
||||||
file,
|
|
||||||
args,
|
|
||||||
{
|
|
||||||
encoding: "utf8",
|
|
||||||
maxBuffer: 8 * 1024 * 1024,
|
|
||||||
timeout: EXEC_TIMEOUT_MS,
|
|
||||||
...opts
|
|
||||||
},
|
|
||||||
(err, stdout, stderr) => {
|
|
||||||
const exitCode = err && typeof err.status === "number" ? err.status : err ? -1 : 0;
|
|
||||||
resolve({
|
|
||||||
exitCode,
|
|
||||||
errCode: err?.code,
|
|
||||||
stdout: String(stdout ?? ""),
|
|
||||||
stderr: String(stderr ?? "")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function parseMainPidFromStatus(text2) {
|
|
||||||
const m = text2.match(/Main PID:\s*(\d+)/i);
|
|
||||||
return m ? Math.trunc(Number.parseInt(m[1], 10)) || 0 : 0;
|
|
||||||
}
|
|
||||||
function parseActiveLineFromStatus(text2) {
|
|
||||||
for (const line of text2.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(text2) {
|
|
||||||
let mainPid = 0;
|
|
||||||
let active = false;
|
|
||||||
let subRunning = false;
|
|
||||||
for (const line of text2.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() {
|
|
||||||
const status = await execFileUtf8("systemctl", [
|
|
||||||
"--user",
|
|
||||||
"--no-pager",
|
|
||||||
"status",
|
|
||||||
"hermes-gateway"
|
|
||||||
]);
|
|
||||||
const combined = `${status.stdout}
|
|
||||||
${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}
|
|
||||||
${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) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
async function readPsMetrics(mainPid) {
|
|
||||||
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 rssKiB2 = Number(parts[0]);
|
|
||||||
const cpu2 = Number(parts[1]);
|
|
||||||
const uptimeSec2 = etimeToSeconds(parts.slice(2).join(" "));
|
|
||||||
const rssBytes2 = Number.isFinite(rssKiB2) ? Math.trunc(rssKiB2 * 1024) : 0;
|
|
||||||
const cpuPercent2 = Number.isFinite(cpu2) ? Math.round(cpu2 * 100) / 100 : 0;
|
|
||||||
return { rssBytes: rssBytes2, cpuPercent: cpuPercent2, uptimeSec: uptimeSec2 };
|
|
||||||
}
|
|
||||||
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(text2) {
|
|
||||||
const src = String(text2);
|
|
||||||
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() {
|
|
||||||
try {
|
|
||||||
const r = await execFileUtf8("hermes", ["sessions", "stats"]);
|
|
||||||
if (r.errCode === "ENOENT") return 0;
|
|
||||||
return parseActiveSessionsFromHermesStats(`${r.stdout}
|
|
||||||
${r.stderr}`);
|
|
||||||
} catch {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function countDirectChildren(mainPid) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function compute() {
|
|
||||||
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
|
|
||||||
};
|
|
||||||
return { signal: row, workflow: null };
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
compute,
|
|
||||||
hermesGatewayHealth as table
|
|
||||||
};
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
// senses/hermes-session-message-stats/src/index.ts
|
|
||||||
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";
|
|
||||||
|
|
||||||
// senses/hermes-session-message-stats/src/schema.ts
|
|
||||||
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()
|
|
||||||
});
|
|
||||||
|
|
||||||
// senses/hermes-session-message-stats/src/index.ts
|
|
||||||
var MEASUREMENT_WINDOW_MS = 9e5;
|
|
||||||
var MEASUREMENT_WINDOW_SECONDS = 900;
|
|
||||||
async function aggregateJsonlFile(filePath, cutoffMs, nowMs) {
|
|
||||||
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;
|
|
||||||
try {
|
|
||||||
obj = JSON.parse(trimmed);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (typeof obj !== "object" || obj === null || typeof obj.role !== "string" || typeof obj.timestamp !== "string") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const record = obj;
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
async function compute() {
|
|
||||||
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 = [];
|
|
||||||
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.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
|
|
||||||
};
|
|
||||||
return { signal: row, workflow: null };
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
compute,
|
|
||||||
hermesSessionMessageStats as table
|
|
||||||
};
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
// senses/linux-system-health/src/index.ts
|
|
||||||
import { loadavg, totalmem, freemem, uptime } from "node:os";
|
|
||||||
import { execSync } from "node:child_process";
|
|
||||||
import { readFile } from "node:fs/promises";
|
|
||||||
|
|
||||||
// senses/linux-system-health/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")
|
|
||||||
});
|
|
||||||
|
|
||||||
// senses/linux-system-health/src/index.ts
|
|
||||||
var SOCKSTAT_PATH = "/proc/net/sockstat";
|
|
||||||
function parseSockstat(content) {
|
|
||||||
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 = {};
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
async function compute() {
|
|
||||||
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 * 1e4) / 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 * 1e4) / 100 : 0;
|
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
let tcp = { 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());
|
|
||||||
const data = {
|
|
||||||
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 { signal: data, workflow: null };
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
compute,
|
|
||||||
snapshots as table
|
|
||||||
};
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
// senses/worker-process-metrics/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()
|
|
||||||
});
|
|
||||||
|
|
||||||
// senses/worker-process-metrics/src/index.ts
|
|
||||||
function round2(n) {
|
|
||||||
return Math.round(n * 100) / 100;
|
|
||||||
}
|
|
||||||
async function compute() {
|
|
||||||
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
|
|
||||||
};
|
|
||||||
return { signal: row, workflow: null };
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
compute,
|
|
||||||
workerProcessMetrics as table
|
|
||||||
};
|
|
||||||
Loading…
x
Reference in New Issue
Block a user